
GPUImageFilterGroup 源码解析
GPUImageFilterGroup 是安卓 GPUImage 开源库中用于管理多滤镜串联执行的核心类,它继承自基础滤镜类 GPUImageFilter,能够将多个独立滤镜组合成一个滤镜组,按顺序逐次应用滤镜效果,实现复杂的图像/视频处理能力。
本文将逐行解析该类的代码结构、注释及核心实现逻辑。
版权声明与包声明
java
/*
* Copyright (C) 2018 CyberAgent, Inc. // 版权归属:CyberAgent 公司
*
* Licensed under the Apache License, Version 2.0 (the "License"); // 开源协议:Apache 2.0
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0 // 协议详情地址
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 声明类所属的包:GPUImage 滤镜模块
package jp.co.cyberagent.android.gpuimage.filter;
这部分是标准的开源项目版权与包声明,指定了代码的开源协议(Apache 2.0)和所属的模块路径,保证代码的合规性和工程结构的清晰性。
导入依赖类
java
import android.annotation.SuppressLint; // 用于抑制 Android Lint 静态检查警告
import android.opengl.GLES20; // OpenGL ES 2.0 核心 API,用于操作纹理、帧缓冲等
import java.nio.ByteBuffer; // 字节缓冲区,用于构建 OpenGL 所需的浮点缓冲
import java.nio.ByteOrder; // 字节序工具,保证缓冲区字节序与本地系统一致
import java.nio.FloatBuffer; // 浮点缓冲区,存储顶点/纹理坐标数据
import java.util.ArrayList; // 动态数组,存储滤镜列表
import java.util.List; // 集合接口,定义滤镜列表规范
import jp.co.cyberagent.android.gpuimage.util.Rotation; // 旋转枚举(正常、90度、180度等)
import jp.co.cyberagent.android.gpuimage.util.TextureRotationUtil; // 纹理坐标旋转/翻转工具类
// 导入 GPUImageRenderer 中的立方体顶点常量(用于纹理映射)
import static jp.co.cyberagent.android.gpuimage.GPUImageRenderer.CUBE;
// 导入 TextureRotationUtil 中的无旋转纹理坐标常量
import static jp.co.cyberagent.android.gpuimage.util.TextureRotationUtil.TEXTURE_NO_ROTATION;
导入的类分为三类:
- Android 系统类:处理 OpenGL 调用、Lint 警告抑制;
- Java 基础类:缓冲区操作、集合存储;
- GPUImage 库内部工具类:旋转枚举、纹理坐标工具、常量定义。
类定义与核心成员变量
java
/**
* Resembles a filter that consists of multiple filters applied after each
* other. // 类注释:表示由多个滤镜按顺序应用组成的复合滤镜
*/
public class GPUImageFilterGroup extends GPUImageFilter { // 继承基础滤镜类 GPUImageFilter
// 原始滤镜列表(可包含子滤镜组 GPUImageFilterGroup)
private List<GPUImageFilter> filters;
// 扁平化后的滤镜列表(递归展开所有子滤镜组,仅保留基础滤镜)
private List<GPUImageFilter> mergedFilters;
// 帧缓冲数组:每个中间滤镜的输出会渲染到对应的帧缓冲
private int[] frameBuffers;
// 帧缓冲关联的纹理数组:帧缓冲的内容会被存储到该纹理,供下一个滤镜使用
private int[] frameBufferTextures;
// 立方体顶点缓冲:存储图像绘制的顶点坐标(固定为矩形,对应屏幕/纹理区域)
private final FloatBuffer glCubeBuffer;
// 无旋转的纹理坐标缓冲:存储原始纹理坐标(与顶点坐标映射)
private final FloatBuffer glTextureBuffer;
// 翻转后的纹理坐标缓冲:处理纹理上下翻转的场景(适配 OpenGL 纹理坐标系)
private final FloatBuffer glTextureFlipBuffer;
核心成员变量的设计思路:
filters保留原始的滤镜层级结构(支持嵌套滤镜组);mergedFilters扁平化列表简化绘制逻辑(无需递归处理子组);frameBuffers/frameBufferTextures是 OpenGL 中间渲染载体,实现"前一个滤镜输出 → 后一个滤镜输入"的串联;- 三个 FloatBuffer 是 OpenGL 绘制的基础数据,分别存储顶点坐标和不同场景的纹理坐标。
构造方法
无参构造
java
/**
* Instantiates a new GPUImageFilterGroup with no filters.
* // 构造方法注释:创建一个空的滤镜组
*/
public GPUImageFilterGroup() {
this(null); // 调用带 List 参数的构造方法,传入 null
}
4带滤镜列表的构造
java
/**
* Instantiates a new GPUImageFilterGroup with the given filters.
*
* @param filters the filters which represent this filter // 参数:组成该滤镜组的初始滤镜列表
*/
public GPUImageFilterGroup(List<GPUImageFilter> filters) {
// 初始化原始滤镜列表
this.filters = filters;
// 若传入的列表为 null,初始化空列表;否则扁平化滤镜列表
if (this.filters == null) {
this.filters = new ArrayList<>();
} else {
updateMergedFilters(); // 扁平化滤镜列表(展开子组)
}
// 初始化立方体顶点缓冲:
// 1. 分配缓冲区(CUBE 是浮点数组,每个浮点数占4字节)
// 2. 设置字节序为本地系统序(避免跨平台字节序问题)
// 3. 写入 CUBE 数据并重置指针到起始位置
glCubeBuffer = ByteBuffer.allocateDirect(CUBE.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
glCubeBuffer.put(CUBE).position(0);
// 初始化无旋转纹理坐标缓冲:逻辑同顶点缓冲
glTextureBuffer = ByteBuffer.allocateDirect(TEXTURE_NO_ROTATION.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
glTextureBuffer.put(TEXTURE_NO_ROTATION).position(0);
// 生成"正常旋转、垂直翻转"的纹理坐标(适配 OpenGL 纹理 Y 轴向下的特性)
float[] flipTexture = TextureRotationUtil.getRotation(Rotation.NORMAL, false, true);
// 初始化翻转后的纹理坐标缓冲:逻辑同前
glTextureFlipBuffer = ByteBuffer.allocateDirect(flipTexture.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
glTextureFlipBuffer.put(flipTexture).position(0);
}
构造方法核心逻辑:
- 初始化滤镜列表(处理 null 情况)并扁平化;
- 构建 OpenGL 绘制所需的顶点/纹理坐标缓冲:
- OpenGL 不直接使用 Java 数组,需转换为 NIO 缓冲(堆外内存,避免 JVM 垃圾回收影响);
- 纹理翻转缓冲是为了适配 OpenGL 纹理坐标系(Y 轴向下)与安卓屏幕坐标系(Y 轴向上)的差异。
添加滤镜方法
java
/**
* 向滤镜组添加单个滤镜
* @param aFilter 待添加的滤镜(null 则忽略)
*/
public void addFilter(GPUImageFilter aFilter) {
if (aFilter == null) { // 空值校验,避免空指针
return;
}
filters.add(aFilter); // 添加到原始滤镜列表
updateMergedFilters(); // 重新扁平化滤镜列表(适配新添加的滤镜)
}
该方法保证了添加滤镜后,mergedFilters 始终与 filters 同步,避免扁平化列表过期。
生命周期方法:初始化与销毁
初始化方法(重写父类)
java
/*
* (non-Javadoc)
* @see jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter#onInit()
* 重写父类的初始化方法:初始化滤镜组中所有滤镜
*/
@Override
public void onInit() {
super.onInit(); // 调用父类初始化(初始化基础滤镜的 OpenGL 程序等)
// 遍历所有原始滤镜,执行各自的初始化逻辑
for (GPUImageFilter filter : filters) {
filter.ifNeedInit(); // 按需初始化(避免重复初始化)
}
}
初始化逻辑:先执行父类的基础初始化,再遍历初始化所有子滤镜,保证每个滤镜的 OpenGL 程序、着色器都被正确初始化。
销毁方法(重写父类)
java
/*
* (non-Javadoc)
* @see jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter#onDestroy()
* 重写父类的销毁方法:释放滤镜组所有资源
*/
@Override
public void onDestroy() {
destroyFramebuffers(); // 销毁帧缓冲和关联纹理(释放 OpenGL 资源)
// 遍历销毁所有子滤镜的资源
for (GPUImageFilter filter : filters) {
filter.destroy();
}
super.onDestroy(); // 调用父类销毁方法(释放基础滤镜资源)
}
销毁逻辑遵循"先释放自定义资源,再释放父类资源"的原则,避免内存泄漏。
帧缓冲销毁工具方法
java
/**
* 销毁帧缓冲和关联的纹理:释放 OpenGL 资源
*/
private void destroyFramebuffers() {
// 销毁纹理:若纹理数组非空,调用 OpenGL API 删除纹理
if (frameBufferTextures != null) {
GLES20.glDeleteTextures(frameBufferTextures.length, frameBufferTextures, 0);
frameBufferTextures = null; // 置空,帮助 GC 回收
}
// 销毁帧缓冲:若帧缓冲数组非空,调用 OpenGL API 删除帧缓冲
if (frameBuffers != null) {
GLES20.glDeleteFramebuffers(frameBuffers.length, frameBuffers, 0);
frameBuffers = null; // 置空,帮助 GC 回收
}
}
OpenGL 资源(纹理、帧缓冲)不会被 JVM 自动回收,必须通过 glDeleteTextures/glDeleteFramebuffers 手动释放,否则会导致显存泄漏。
输出尺寸变化处理(重写父类)
java
/*
* (non-Javadoc)
* @see
* jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter#onOutputSizeChanged(int,
* int)
* 重写父类方法:当输出尺寸(屏幕/纹理大小)变化时,更新滤镜组的渲染配置
*/
@Override
public void onOutputSizeChanged(final int width, final int height) {
super.onOutputSizeChanged(width, height); // 调用父类尺寸更新逻辑
// 销毁旧的帧缓冲(尺寸变化后,旧帧缓冲不再适配)
if (frameBuffers != null) {
destroyFramebuffers();
}
// 第一步:更新所有原始滤镜的输出尺寸
int size = filters.size();
for (int i = 0; i < size; i++) {
filters.get(i).onOutputSizeChanged(width, height);
}
// 第二步:为扁平化后的滤镜列表创建帧缓冲(仅为前 n-1 个滤镜创建,最后一个直接渲染到屏幕)
if (mergedFilters != null && mergedFilters.size() > 0) {
size = mergedFilters.size();
// 帧缓冲数量 = 滤镜数量 - 1(最后一个滤镜无需中间帧缓冲)
frameBuffers = new int[size - 1];
frameBufferTextures = new int[size - 1];
// 遍历创建每个中间帧缓冲和纹理
for (int i = 0; i < size - 1; i++) {
// 1. 生成帧缓冲 ID 并存储到数组
GLES20.glGenFramebuffers(1, frameBuffers, i);
// 2. 生成纹理 ID 并存储到数组
GLES20.glGenTextures(1, frameBufferTextures, i);
// 3. 绑定纹理,配置纹理参数
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, frameBufferTextures[i]);
// 创建 2D 纹理:宽高为输出尺寸,格式 RGBA,无初始数据
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0,
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
// 设置纹理放大过滤:线性过滤(模糊效果,避免锯齿)
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
// 设置纹理缩小过滤:线性过滤
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
// 设置纹理 S 轴(水平)环绕模式:边缘夹紧(避免纹理重复)
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
// 设置纹理 T 轴(垂直)环绕模式:边缘夹紧
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
// 4. 绑定帧缓冲,将纹理关联到帧缓冲的颜色附件
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffers[i]);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_2D, frameBufferTextures[i], 0);
// 5. 解绑纹理和帧缓冲(避免后续操作污染)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
}
}
}
核心逻辑:
- 尺寸变化时先销毁旧帧缓冲(尺寸不匹配会导致渲染异常);
- 更新所有子滤镜的输出尺寸,保证每个滤镜适配新尺寸;
- 为扁平化后的前 n-1 个滤镜创建帧缓冲+纹理:
- 每个中间滤镜的输出会渲染到对应的帧缓冲纹理;
- 最后一个滤镜直接渲染到屏幕(无需中间帧缓冲);
- 配置纹理参数(线性过滤、边缘夹紧),保证渲染效果和纹理坐标的正确性。
核心绘制逻辑(重写父类)
java
/*
* (non-Javadoc)
* @see jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter#onDraw(int,
* java.nio.FloatBuffer, java.nio.FloatBuffer)
* 重写父类绘制方法:按顺序执行滤镜组中所有滤镜的绘制
*/
@SuppressLint("WrongCall") // 抑制 Lint 对 filter.onDraw 调用的警告(逻辑上合法)
@Override
public void onDraw(final int textureId, final FloatBuffer cubeBuffer,
final FloatBuffer textureBuffer) {
runPendingOnDrawTasks(); // 执行绘制前的待处理任务(如参数更新)
// 校验初始化状态:未初始化/帧缓冲为空则直接返回
if (!isInitialized() || frameBuffers == null || frameBufferTextures == null) {
return;
}
if (mergedFilters != null) {
int size = mergedFilters.size();
int previousTexture = textureId; // 初始输入纹理(原始图像纹理 ID)
// 遍历扁平化后的所有滤镜
for (int i = 0; i < size; i++) {
GPUImageFilter filter = mergedFilters.get(i);
boolean isNotLast = i < size - 1; // 是否为最后一个滤镜
// 若不是最后一个滤镜:绑定到对应的帧缓冲(输出到纹理)
if (isNotLast) {
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffers[i]);
GLES20.glClearColor(0, 0, 0, 0); // 清空帧缓冲(透明黑色背景)
}
// 分场景调用滤镜的绘制方法:
if (i == 0) {
// 第一个滤镜:使用原始顶点/纹理缓冲(输入为原始图像)
filter.onDraw(previousTexture, cubeBuffer, textureBuffer);
} else if (i == size - 1) {
// 最后一个滤镜:使用预定义的顶点缓冲 + 翻转/正常纹理缓冲(适配偶数个滤镜的翻转)
filter.onDraw(previousTexture, glCubeBuffer, (size % 2 == 0) ? glTextureFlipBuffer : glTextureBuffer);
} else {
// 中间滤镜:使用预定义的顶点/纹理缓冲(输入为前一个帧缓冲的纹理)
filter.onDraw(previousTexture, glCubeBuffer, glTextureBuffer);
}
// 若不是最后一个滤镜:解绑帧缓冲,更新输入纹理为当前帧缓冲的纹理
if (isNotLast) {
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
previousTexture = frameBufferTextures[i]; // 下一个滤镜的输入 = 当前帧缓冲纹理
}
}
}
}
绘制逻辑是滤镜组的核心,实现了"串联渲染":
- 初始输入为原始图像的纹理 ID;
- 前 n-1 个滤镜:渲染到对应的帧缓冲纹理,输出纹理作为下一个滤镜的输入;
- 最后一个滤镜:直接渲染到屏幕(默认帧缓冲 0);
- 纹理坐标翻转处理:偶数个滤镜时,纹理会被翻转,需通过
glTextureFlipBuffer修正,保证图像方向正确。
获取器方法
java
/**
* Gets the filters. // 获取原始滤镜列表(包含子组)
*
* @return the filters
*/
public List<GPUImageFilter> getFilters() {
return filters;
}
/**
* 获取扁平化后的滤镜列表(无嵌套子组)
* @return 扁平化滤镜列表
*/
public List<GPUImageFilter> getMergedFilters() {
return mergedFilters;
}
提供对外访问滤镜列表的接口,方便上层代码查看/修改滤镜组配置。
滤镜列表扁平化方法
java
/**
* 扁平化滤镜列表:递归展开所有子滤镜组,将嵌套结构转为一维列表
*/
public void updateMergedFilters() {
if (filters == null) { // 原始列表为空则直接返回
return;
}
// 初始化/清空扁平化列表
if (mergedFilters == null) {
mergedFilters = new ArrayList<>();
} else {
mergedFilters.clear();
}
List<GPUImageFilter> filters; // 临时变量,存储子组展开后的滤镜
// 遍历原始滤镜列表
for (GPUImageFilter filter : this.filters) {
// 若当前滤镜是子滤镜组:递归展开
if (filter instanceof GPUImageFilterGroup) {
((GPUImageFilterGroup) filter).updateMergedFilters(); // 先扁平化子组
filters = ((GPUImageFilterGroup) filter).getMergedFilters(); // 获取子组的扁平化列表
if (filters == null || filters.isEmpty()) // 子组为空则跳过
continue;
mergedFilters.addAll(filters); // 将子组的滤镜添加到当前列表
continue;
}
// 基础滤镜:直接添加到扁平化列表
mergedFilters.add(filter);
}
}
该方法解决了"嵌套滤镜组"的问题:
- 若原始列表中包含
GPUImageFilterGroup类型的子组,会递归展开子组的mergedFilters; - 最终
mergedFilters中仅保留基础滤镜(非组类型),简化绘制时的遍历逻辑(无需递归)。
总结
GPUImageFilterGroup 的核心设计思路是**"扁平化管理 + 帧缓冲串联渲染"**:
- 扁平化:将嵌套的滤镜组转为一维列表,避免绘制时递归处理;
- 帧缓冲串联:通过中间帧缓冲实现"前一个滤镜输出 → 后一个滤镜输入",完成多滤镜的顺序应用;
- 生命周期管理:严格遵循 OpenGL 资源的创建/销毁逻辑,避免内存/显存泄漏;
- 坐标适配:处理纹理坐标系与屏幕坐标系的差异,保证图像方向正确。
