【Android 美颜相机】第十一天:GPUImageFilter解析

GPUImageFilter解析

GPUImageFilter 是 Android 平台基于 OpenGL ES 2.0 实现的 GPU 图像滤镜核心基类,为各类图像滤镜(如美颜、风格化特效)提供了统一的初始化、绘制、资源管理、参数配置能力。

本文将逐行解析该类的代码结构、实现逻辑及每行代码的含义。

版权声明与包/类导入

功能说明

该部分包含开源协议声明、包路径定义,以及实现滤镜功能所需的核心类导入,是 Java 类的基础结构。

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  // Apache 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.
 */

// 声明类所属包路径,归属于 CyberAgent 的 GPUImage 安卓库滤镜模块
package jp.co.cyberagent.android.gpuimage.filter;

// 安卓上下文类,用于获取资源(如 Assets)
import android.content.Context;
// 安卓资产管理类,用于读取 assets 目录下的着色器文件
import android.content.res.AssetManager;
// 二维点坐标类,用于传递纹理/顶点坐标点
import android.graphics.PointF;
// OpenGL ES 2.0 核心 API 类,提供 OpenGL 底层调用
import android.opengl.GLES20;
// 输入流类,用于读取 assets 中的着色器文件内容
import java.io.InputStream;
// 浮点缓冲区类,用于存储顶点/纹理坐标数据(适配 OpenGL 数据格式)
import java.nio.FloatBuffer;
// 链表类,用于存储待执行的绘制任务
import java.util.LinkedList;
// OpenGL 工具类(自定义),封装着色器加载、编译等通用操作
import jp.co.cyberagent.android.gpuimage.util.OpenGlUtils;

类定义与核心常量

功能说明

定义滤镜基类的核心常量(默认着色器代码),顶点着色器负责处理顶点坐标变换,片元着色器负责处理像素颜色渲染,默认实现"无滤镜"效果(直接输出原纹理)。

java 复制代码
// 定义 GPUImageFilter 公共类,所有具体滤镜(如美白、磨皮)均继承此类
public class GPUImageFilter {
    // 无滤镜顶点着色器代码:处理顶点坐标和纹理坐标传递
    public static final String NO_FILTER_VERTEX_SHADER = "" +
            "attribute vec4 position;\n" +          // 顶点坐标属性(由 Java 层传入)
            "attribute vec4 inputTextureCoordinate;\n" +  // 纹理坐标属性(由 Java 层传入)
            " \n" +
            "varying vec2 textureCoordinate;\n" +  // 易变变量:传递纹理坐标到片元着色器
            " \n" +
            "void main()\n" +                      // 顶点着色器主函数
            "{\n" +
            "    gl_Position = position;\n" +      // 设置顶点最终坐标(OpenGL 裁剪空间)
            "    textureCoordinate = inputTextureCoordinate.xy;\n" +  // 传递纹理坐标到片元着色器
            "}";
    
    // 无滤镜片元着色器代码:直接采样原纹理像素,无任何颜色修改
    public static final String NO_FILTER_FRAGMENT_SHADER = "" +
            "varying highp vec2 textureCoordinate;\n" +  // 接收顶点着色器传递的纹理坐标(高精度)
            " \n" +
            "uniform sampler2D inputImageTexture;\n" +   // 输入纹理采样器(由 Java 层绑定)
            " \n" +
            "void main()\n" +                            // 片元着色器主函数
            "{\n" +
            "     gl_FragColor = texture2D(inputImageTexture, textureCoordinate);\n" +  // 采样纹理像素,作为输出颜色
            "}";

成员变量

功能说明

定义类的核心成员变量,涵盖绘制任务队列、着色器代码、OpenGL 程序/属性/Uniform 句柄、输出尺寸、初始化状态等,是滤镜运行的核心数据载体。

java 复制代码
    // 待执行的绘制任务链表:存储需要在绘制阶段执行的 OpenGL 操作(如设置 Uniform 参数)
    private final LinkedList<Runnable> runOnDraw;
    // 顶点着色器代码(可自定义)
    private final String vertexShader;
    // 片元着色器代码(可自定义)
    private final String fragmentShader;
    // OpenGL 程序 ID:着色器编译链接后的程序句柄
    private int glProgId;
    // position 属性位置:对应顶点着色器中 "position" 变量的句柄
    private int glAttribPosition;
    // 输入纹理 Uniform 位置:对应片元着色器中 "inputImageTexture" 变量的句柄
    private int glUniformTexture;
    // 纹理坐标属性位置:对应顶点着色器中 "inputTextureCoordinate" 变量的句柄
    private int glAttribTextureCoordinate;
    // 滤镜输出宽度
    private int outputWidth;
    // 滤镜输出高度
    private int outputHeight;
    // 初始化状态标记:标记 OpenGL 程序是否已初始化完成
    private boolean isInitialized;

构造方法

功能说明

提供无参/有参构造方法,无参构造默认使用"无滤镜"着色器,有参构造支持传入自定义顶点/片元着色器,适配不同滤镜的定制化需求。

java 复制代码
    // 无参构造方法:默认使用无滤镜顶点/片元着色器
    public GPUImageFilter() {
        this(NO_FILTER_VERTEX_SHADER, NO_FILTER_FRAGMENT_SHADER);
    }

    // 有参构造方法:接收自定义顶点/片元着色器代码
    public GPUImageFilter(final String vertexShader, final String fragmentShader) {
        // 初始化绘制任务链表
        runOnDraw = new LinkedList<>();
        // 赋值自定义顶点着色器代码
        this.vertexShader = vertexShader;
        // 赋值自定义片元着色器代码
        this.fragmentShader = fragmentShader;
    }

初始化相关方法

功能说明

封装 OpenGL 程序的初始化逻辑,包括着色器编译链接、属性/Uniform 句柄获取,提供初始化检查、回调扩展等能力,确保滤镜绘制前完成必要的初始化。

java 复制代码
    // 内部初始化入口方法:封装初始化流程(私有,仅内部调用)
    private final void init() {
        // 执行初始化核心逻辑(子类可重写)
        onInit();
        // 初始化完成回调(子类可重写扩展)
        onInitialized();
    }

    // 初始化核心逻辑:编译链接着色器程序,获取属性/Uniform 句柄
    public void onInit() {
        // 加载并编译顶点/片元着色器,链接为 OpenGL 程序,返回程序 ID
        glProgId = OpenGlUtils.loadProgram(vertexShader, fragmentShader);
        // 获取顶点着色器中 "position" 属性的句柄
        glAttribPosition = GLES20.glGetAttribLocation(glProgId, "position");
        // 获取片元着色器中 "inputImageTexture" Uniform 的句柄
        glUniformTexture = GLES20.glGetUniformLocation(glProgId, "inputImageTexture");
        // 获取顶点着色器中 "inputTextureCoordinate" 属性的句柄
        glAttribTextureCoordinate = GLES20.glGetAttribLocation(glProgId, "inputTextureCoordinate");
        // 标记初始化完成
        isInitialized = true;
    }

    // 初始化完成回调方法:空实现,子类可重写(如初始化自定义 Uniform 参数)
    public void onInitialized() {
    }

    // 初始化检查方法:若未初始化则执行 init()
    public void ifNeedInit() {
        if (!isInitialized) init();
    }

资源销毁相关方法

功能说明

封装 OpenGL 程序的销毁逻辑,释放显存资源,提供销毁回调扩展,避免内存泄漏。

java 复制代码
    // 最终销毁方法:标记未初始化,删除 OpenGL 程序,执行销毁回调
    public final void destroy() {
        // 标记初始化状态为未完成
        isInitialized = false;
        // 删除 OpenGL 程序,释放显存资源
        GLES20.glDeleteProgram(glProgId);
        // 执行销毁回调(子类可重写)
        onDestroy();
    }

    // 销毁回调方法:空实现,子类可重写(如释放自定义纹理/缓冲区)
    public void onDestroy() {
    }

输出尺寸变化处理

功能说明

响应滤镜输出尺寸变化(如屏幕旋转、视图大小调整),更新宽高参数,适配不同尺寸的渲染需求。

java 复制代码
    // 输出尺寸变化回调:更新输出宽高(子类可重写扩展)
    public void onOutputSizeChanged(final int width, final int height) {
        // 更新输出宽度
        outputWidth = width;
        // 更新输出高度
        outputHeight = height;
    }

核心绘制方法

功能说明

滤镜的核心绘制逻辑,负责绑定 OpenGL 程序、传递顶点/纹理坐标数据、绑定纹理、执行绘制操作,是滤镜渲染的核心入口。

java 复制代码
    // 绘制方法:接收纹理 ID、顶点缓冲区、纹理坐标缓冲区,执行渲染
    public void onDraw(final int textureId, final FloatBuffer cubeBuffer,
                       final FloatBuffer textureBuffer) {
        // 绑定当前 OpenGL 程序(激活着色器)
        GLES20.glUseProgram(glProgId);
        // 执行待处理的绘制任务(如设置 Uniform 参数)
        runPendingOnDrawTasks();
        // 若未初始化,直接返回(避免绘制异常)
        if (!isInitialized) {
            return;
        }

        // 重置顶点缓冲区指针到起始位置
        cubeBuffer.position(0);
        // 绑定顶点坐标数据到 position 属性:2 个分量/顶点,浮点型,非归一化,步长 0
        GLES20.glVertexAttribPointer(glAttribPosition, 2, GLES20.GL_FLOAT, false, 0, cubeBuffer);
        // 启用 position 属性数组(OpenGL 渲染时读取该属性)
        GLES20.glEnableVertexAttribArray(glAttribPosition);
        
        // 重置纹理坐标缓冲区指针到起始位置
        textureBuffer.position(0);
        // 绑定纹理坐标数据到 inputTextureCoordinate 属性:参数含义同顶点坐标
        GLES20.glVertexAttribPointer(glAttribTextureCoordinate, 2, GLES20.GL_FLOAT, false, 0,
                textureBuffer);
        // 启用 inputTextureCoordinate 属性数组
        GLES20.glEnableVertexAttribArray(glAttribTextureCoordinate);
        
        // 若纹理 ID 有效(非空纹理)
        if (textureId != OpenGlUtils.NO_TEXTURE) {
            // 激活纹理单元 0(OpenGL 多纹理单元机制)
            GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
            // 绑定纹理 ID 到 GL_TEXTURE_2D 目标
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
            // 设置 inputImageTexture Uniform:绑定到纹理单元 0
            GLES20.glUniform1i(glUniformTexture, 0);
        }
        
        // 绘制前回调(子类可重写,如设置自定义 Uniform 参数)
        onDrawArraysPre();
        // 执行绘制:使用 TRIANGLE_STRIP 模式,从第 0 个顶点开始,共 4 个顶点(绘制矩形)
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        
        // 禁用 position 属性数组(绘制完成后释放)
        GLES20.glDisableVertexAttribArray(glAttribPosition);
        // 禁用 inputTextureCoordinate 属性数组
        GLES20.glDisableVertexAttribArray(glAttribTextureCoordinate);
        // 解绑纹理(避免后续操作干扰)
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
    }

    // 绘制前扩展方法:空实现,子类可重写(如设置滤镜强度、颜色等参数)
    protected void onDrawArraysPre() {
    }

    // 执行待处理的绘制任务:遍历链表,执行所有待执行的 Runnable 任务
    protected void runPendingOnDrawTasks() {
        // 同步锁:保证多线程下任务执行安全
        synchronized (runOnDraw) {
            // 遍历任务链表,直到为空
            while (!runOnDraw.isEmpty()) {
                // 移除并执行第一个任务
                runOnDraw.removeFirst().run();
            }
        }
    }

状态与属性获取方法

功能说明

提供初始化状态、输出尺寸、OpenGL 程序/属性句柄的获取方法,方便子类或外部调用。

java 复制代码
    // 获取初始化状态:返回是否已完成 OpenGL 程序初始化
    public boolean isInitialized() {
        return isInitialized;
    }

    // 获取输出宽度
    public int getOutputWidth() {
        return outputWidth;
    }

    // 获取输出高度
    public int getOutputHeight() {
        return outputHeight;
    }

    // 获取 OpenGL 程序 ID
    public int getProgram() {
        return glProgId;
    }

    // 获取 position 属性句柄
    public int getAttribPosition() {
        return glAttribPosition;
    }

    // 获取 inputTextureCoordinate 属性句柄
    public int getAttribTextureCoordinate() {
        return glAttribTextureCoordinate;
    }

    // 获取 inputImageTexture Uniform 句柄
    public int getUniformTexture() {
        return glUniformTexture;
    }

Uniform 参数设置方法

功能说明

封装不同类型 Uniform 参数的设置逻辑,将参数设置操作加入绘制任务队列,确保在 OpenGL 绘制线程执行(避免线程安全问题)。

java 复制代码
    // 设置整型 Uniform 参数:接收 Uniform 位置和整型值
    protected void setInteger(final int location, final int intValue) {
        // 将参数设置操作加入绘制任务队列
        runOnDraw(new Runnable() {
            @Override
            public void run() {
                // 检查并初始化(避免未初始化时操作)
                ifNeedInit();
                // 设置 1 个整型 Uniform 参数
                GLES20.glUniform1i(location, intValue);
            }
        });
    }

    // 设置浮点型 Uniform 参数:接收 Uniform 位置和浮点值
    protected void setFloat(final int location, final float floatValue) {
        runOnDraw(new Runnable() {
            @Override
            public void run() {
                ifNeedInit();
                // 设置 1 个浮点型 Uniform 参数
                GLES20.glUniform1f(location, floatValue);
            }
        });
    }

    // 设置 2 维浮点向量 Uniform 参数:接收 Uniform 位置和浮点数组
    protected void setFloatVec2(final int location, final float[] arrayValue) {
        runOnDraw(new Runnable() {
            @Override
            public void run() {
                ifNeedInit();
                // 设置 2 维浮点向量 Uniform 参数(1 个向量,从数组起始位置读取)
                GLES20.glUniform2fv(location, 1, FloatBuffer.wrap(arrayValue));
            }
        });
    }

    // 设置 3 维浮点向量 Uniform 参数
    protected void setFloatVec3(final int location, final float[] arrayValue) {
        runOnDraw(new Runnable() {
            @Override
            public void run() {
                ifNeedInit();
                GLES20.glUniform3fv(location, 1, FloatBuffer.wrap(arrayValue));
            }
        });
    }

    // 设置 4 维浮点向量 Uniform 参数
    protected void setFloatVec4(final int location, final float[] arrayValue) {
        runOnDraw(new Runnable() {
            @Override
            public void run() {
                ifNeedInit();
                GLES20.glUniform4fv(location, 1, FloatBuffer.wrap(arrayValue));
            }
        });
    }

    // 设置浮点数组 Uniform 参数
    protected void setFloatArray(final int location, final float[] arrayValue) {
        runOnDraw(new Runnable() {
            @Override
            public void run() {
                ifNeedInit();
                // 设置浮点数组 Uniform 参数(数组长度为元素个数)
                GLES20.glUniform1fv(location, arrayValue.length, FloatBuffer.wrap(arrayValue));
            }
        });
    }

    // 设置 PointF 类型 Uniform 参数(转换为 2 维向量)
    protected void setPoint(final int location, final PointF point) {
        runOnDraw(new Runnable() {
            @Override
            public void run() {
                ifNeedInit();
                // 将 PointF 转换为 2 维浮点数组
                float[] vec2 = new float[2];
                vec2[0] = point.x;
                vec2[1] = point.y;
                // 设置 2 维向量 Uniform 参数
                GLES20.glUniform2fv(location, 1, vec2, 0);
            }
        });
    }

    // 设置 3x3 矩阵 Uniform 参数
    protected void setUniformMatrix3f(final int location, final float[] matrix) {
        runOnDraw(new Runnable() {
            @Override
            public void run() {
                ifNeedInit();
                // 设置 3x3 矩阵 Uniform 参数(1 个矩阵,非转置,从数组起始位置读取)
                GLES20.glUniformMatrix3fv(location, 1, false, matrix, 0);
            }
        });
    }

    // 设置 4x4 矩阵 Uniform 参数
    protected void setUniformMatrix4f(final int location, final float[] matrix) {
        runOnDraw(new Runnable() {
            @Override
            public void run() {
                ifNeedInit();
                // 设置 4x4 矩阵 Uniform 参数(参数含义同 3x3 矩阵)
                GLES20.glUniformMatrix4fv(location, 1, false, matrix, 0);
            }
        });
    }

    // 添加绘制任务到队列:子类可调用,添加自定义绘制操作
    protected void runOnDraw(final Runnable runnable) {
        // 同步锁:保证多线程下任务添加安全
        synchronized (runOnDraw) {
            // 将任务添加到链表尾部
            runOnDraw.addLast(runnable);
        }
    }

工具方法

功能说明

提供从 assets 目录加载着色器代码的工具方法,以及输入流转字符串的辅助方法,方便加载自定义着色器文件。

java 复制代码
    // 从 assets 目录加载着色器代码:接收文件路径和上下文
    public static String loadShader(String file, Context context) {
        try {
            // 获取安卓资产管理器
            AssetManager assetManager = context.getAssets();
            // 打开 assets 中的着色器文件
            InputStream ims = assetManager.open(file);
            // 将输入流转换为字符串(着色器代码)
            String re = convertStreamToString(ims);
            // 关闭输入流
            ims.close();
            // 返回着色器代码
            return re;
        } catch (Exception e) {
            // 捕获异常并打印堆栈
            e.printStackTrace();
        }
        // 异常时返回空字符串
        return "";
    }

    // 将输入流转换为字符串:辅助方法,读取 assets 文件内容
    public static String convertStreamToString(java.io.InputStream is) {
        // 创建 Scanner 对象,以 "\\A" 为分隔符(读取整个流)
        java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
        // 若流中有内容则返回,否则返回空字符串
        return s.hasNext() ? s.next() : "";
    }
}

总结

GPUImageFilter 作为安卓 GPU 图像滤镜的基类,核心价值在于:

  1. 封装 OpenGL ES 2.0 底层操作,屏蔽复杂的着色器编译、链接、绘制逻辑,降低子类滤镜的开发成本;
  2. 提供统一的参数配置接口(Uniform 设置),支持整型、浮点型、矩阵等多种类型参数,适配各类滤镜的定制化需求;
  3. 实现资源生命周期管理(初始化/销毁),避免显存泄漏;
  4. 支持从 assets 加载着色器代码,提高滤镜代码的可维护性。
相关推荐
dawudayudaxue2 小时前
sqlite在安卓下使用ndk的交叉编译
android·数据库·sqlite
YIN_尹2 小时前
【MySQL】表的约束(下)
android·数据库·mysql
爱编码的傅同学2 小时前
【线程的同步与互斥】初识互斥量与锁
android·java·开发语言
_李小白2 小时前
【Android 美颜相机】第十天:YUV420SP和RGB
android·数码相机
Σίσυφος19002 小时前
张正友标定法原理总结2
人工智能·数码相机·计算机视觉
2501_944526422 小时前
Flutter for OpenHarmony 万能游戏库App实战 - 收藏功能实现
android·java·开发语言·javascript·python·flutter·游戏
2501_944526422 小时前
Flutter for OpenHarmony 万能游戏库App实战 - 个人中心实现
android·java·javascript·python·flutter·游戏
Jomurphys2 小时前
Kotlin - 引用操作符 ::
android·kotlin
恋猫de小郭2 小时前
Meta ShapeR :基于随机拍摄视频的 3D 物体生成,未来的 XR 和机器人基建支持
android·flutter·3d·ai·音视频·xr