【Android 美颜相机】第九天:GPUImageRenderer解析

GPUImageRenderer

GPUImageRenderer是Android端GPUImage开源框架的核心渲染类,负责封装OpenGL ES的图像渲染逻辑,承接相机预览/静态图像输入、滤镜处理、图像变换(旋转/翻转/缩放)等全流程。

本文将对该类代码逐行注释,并分模块解析其实现逻辑与核心含义。

版权声明与包导入

java 复制代码
/*
 * Copyright (C) 2018 CyberAgent, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * 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;

// 导入Android位图处理类
import android.graphics.Bitmap;
// 导入画布类,用于位图补位绘制
import android.graphics.Canvas;
// 导入SurfaceTexture类,实现相机预览数据到GL纹理的映射
import android.graphics.SurfaceTexture;
// 导入相机硬件相关类
import android.hardware.Camera;
// 导入相机预览回调接口
import android.hardware.Camera.PreviewCallback;
// 导入相机尺寸类
import android.hardware.Camera.Size;
// 导入OpenGL ES 2.0核心接口
import android.opengl.GLES20;
// 导入GLSurfaceView的渲染器接口
import android.opengl.GLSurfaceView;

// 导入IO异常类,处理相机预览纹理绑定异常
import java.io.IOException;
// 导入NIO字节缓冲区,用于存储GL顶点/纹理坐标数据
import java.nio.ByteBuffer;
// 导入字节序工具类,保证跨平台数据一致性
import java.nio.ByteOrder;
// 导入浮点缓冲区,存储GL浮点型顶点/纹理坐标
import java.nio.FloatBuffer;
// 导入整型缓冲区,存储YUV转RGBA后的图像数据
import java.nio.IntBuffer;
// 导入链表,实现任务队列
import java.util.LinkedList;
// 导入队列接口,定义任务队列规范
import java.util.Queue;

// 导入EGL配置相关类,处理GL渲染环境配置
import javax.microedition.khronos.egl.EGLConfig;
// 导入早期GL接口(兼容用)
import javax.microedition.khronos.opengles.GL10;

// 导入GPUImage滤镜基类
import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter;
// 导入GL工具类(纹理加载、着色器编译等)
import jp.co.cyberagent.android.gpuimage.util.OpenGlUtils;
// 导入旋转枚举类
import jp.co.cyberagent.android.gpuimage.util.Rotation;
// 导入纹理坐标旋转工具类
import jp.co.cyberagent.android.gpuimage.util.TextureRotationUtil;

// 静态导入无旋转的纹理坐标常量,简化代码
import static jp.co.cyberagent.android.gpuimage.util.TextureRotationUtil.TEXTURE_NO_ROTATION;

实现含义

  • 版权声明遵循Apache 2.0协议,明确开源授权范围;
  • 包路径定位到GPUImage框架核心包,保证类的归属;
  • 导入的类覆盖了"图像数据处理(Bitmap/Canvas)、相机硬件交互(Camera/SurfaceTexture)、OpenGL ES渲染(GLES20/GLSurfaceView)、数据缓冲(NIO)、线程任务(Queue/LinkedList)"四大核心场景,为渲染逻辑提供基础依赖。

类定义与核心常量

java 复制代码
// 定义渲染器类,实现三大核心接口:
// 1. GLSurfaceView.Renderer:GLSurfaceView的渲染生命周期回调
// 2. GLTextureView.Renderer:GLTextureView的渲染生命周期回调(兼容不同GL视图)
// 3. PreviewCallback:相机预览数据接收回调
public class GPUImageRenderer implements GLSurfaceView.Renderer, GLTextureView.Renderer, PreviewCallback {
    // 常量:标识无有效GL纹理(初始状态)
    private static final int NO_IMAGE = -1;
    // 常量:立方体顶点坐标(2D图像渲染的基础几何形状,对应屏幕归一化坐标)
    // 四个顶点分别对应:左下、右下、左上、右上
    public static final float CUBE[] = {
            -1.0f, -1.0f,
            1.0f, -1.0f,
            -1.0f, 1.0f,
            1.0f, 1.0f,
    };

实现含义

  • 类实现多接口,兼顾"GL视图渲染"和"相机预览数据接收"两大核心能力;
  • NO_IMAGE作为纹理ID的初始值,区分"无纹理"和"有纹理"状态;
  • CUBE定义了OpenGL 2D渲染的基础几何顶点(归一化坐标,范围[-1,1]),所有图像最终都会映射到该立方体上渲染。

成员变量定义

java 复制代码
    // 当前应用的滤镜实例(核心渲染逻辑载体)
    private GPUImageFilter filter;

    // 同步锁对象:用于等待GL表面尺寸变化完成
    public final Object surfaceChangedWaiter = new Object();

    // GL纹理ID:标识当前待渲染的纹理资源(初始为NO_IMAGE)
    private int glTextureId = NO_IMAGE;
    // 相机预览关联的SurfaceTexture:实现相机数据到GL纹理的实时映射
    private SurfaceTexture surfaceTexture = null;
    // 立方体顶点缓冲:存储CUBE常量数据,供GL绘制调用
    private final FloatBuffer glCubeBuffer;
    // 纹理坐标缓冲:存储纹理映射坐标,与顶点缓冲一一对应
    private final FloatBuffer glTextureBuffer;
    // RGB整型缓冲:存储YUV转RGBA后的图像数据(相机预览专用)
    private IntBuffer glRgbBuffer;

    // GL渲染表面的输出宽度(如屏幕/视图宽度)
    private int outputWidth;
    // GL渲染表面的输出高度(如屏幕/视图高度)
    private int outputHeight;
    // 输入图像/相机帧的原始宽度
    private int imageWidth;
    // 输入图像/相机帧的原始高度
    private int imageHeight;
    // 位图补位值:处理GL纹理宽高必须为偶数的限制(奇数宽度时补1像素)
    private int addedPadding;

    // 绘制前任务队列:存储需在GL线程绘制前执行的任务(如纹理加载、滤镜切换)
    private final Queue<Runnable> runOnDraw;
    // 绘制后任务队列:存储需在GL线程绘制后执行的任务
    private Rotation rotation; // 图像旋转配置(NORMAL/90/180/270度)
    private boolean flipHorizontal; // 是否水平翻转
    private boolean flipVertical; // 是否垂直翻转
    // 图像缩放类型(默认CENTER_CROP,居中裁剪)
    private GPUImage.ScaleType scaleType = GPUImage.ScaleType.CENTER_CROP;

    // 渲染背景色-红色通道(默认黑色)
    private float backgroundRed = 0;
    // 渲染背景色-绿色通道(默认黑色)
    private float backgroundGreen = 0;
    // 渲染背景色-蓝色通道(默认黑色)
    private float backgroundBlue = 0;

实现含义

  • 成员变量分为五大类:
    1. 核心依赖(filter):承载滤镜的着色器编译、图像渲染逻辑;
    2. GL资源(glTextureId/surfaceTexture/缓冲对象):管理GL渲染的核心资源;
    3. 尺寸参数(output/input宽高):适配不同屏幕/图像尺寸的渲染需求;
    4. 变换配置(旋转/翻转/缩放):处理图像的显示姿态适配;
    5. 线程任务(runOnDraw/runOnDrawEnd):保证GL操作的线程安全(GL操作必须在GL线程执行);
  • 所有变量均为私有/受保护,遵循封装原则,仅通过公开方法修改。

构造方法

java 复制代码
    // 构造方法:初始化渲染器,绑定初始滤镜
    public GPUImageRenderer(final GPUImageFilter filter) {
        this.filter = filter; // 绑定传入的滤镜实例
        // 初始化绘制前任务队列(链表实现,高效增删)
        runOnDraw = new LinkedList<>();
        // 初始化绘制后任务队列
        runOnDrawEnd = new LinkedList<>();

        // 初始化立方体顶点缓冲:
        // 1. 分配内存:CUBE长度 * 4(float占4字节)
        // 2. 设置字节序为本地系统序(保证跨平台一致性)
        // 3. 转为FloatBuffer,存入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();
        // 设置初始旋转配置:无旋转、无水平/垂直翻转
        setRotation(Rotation.NORMAL, false, false);
    }

实现含义

  • 构造方法是渲染器的入口,核心完成"滤镜绑定、任务队列初始化、GL缓冲初始化、默认变换配置";
  • GL缓冲采用allocateDirect分配直接内存(非JVM堆内存),避免GC影响GL渲染性能;
  • 字节序设置为nativeOrder,保证在不同CPU架构(如ARM/x86)下数据解析一致;
  • 初始旋转配置为"无变换",保证默认渲染姿态正确。

OpenGL ES渲染生命周期方法

1 onSurfaceCreated:渲染环境初始化

java 复制代码
    // GL表面首次创建时调用(如GLView初始化)
    @Override
    public void onSurfaceCreated(final GL10 unused, final EGLConfig config) {
        // 设置GL清屏背景色(RGBA),默认黑色
        GLES20.glClearColor(backgroundRed, backgroundGreen, backgroundBlue, 1);
        // 关闭深度测试:2D图像渲染无需判断Z轴深度,提升性能
        GLES20.glDisable(GLES20.GL_DEPTH_TEST);
        // 初始化滤镜:编译着色器、创建GL程序、初始化uniform变量
        filter.ifNeedInit();
    }

实现含义

  • glClearColor设置帧缓冲清空后的背景色,对应成员变量的背景色参数;
  • 关闭深度测试是2D渲染的通用优化手段,避免不必要的Z轴计算;
  • filter.ifNeedInit()是滤镜的懒加载逻辑,保证仅在首次创建时初始化GL程序,避免重复编译着色器。

2 onSurfaceChanged:尺寸适配

java 复制代码
    // GL表面尺寸变化时调用(如屏幕旋转、视图大小调整)
    @Override
    public void onSurfaceChanged(final GL10 gl, final int width, final int height) {
        outputWidth = width; // 更新渲染输出宽度
        outputHeight = height; // 更新渲染输出高度
        // 设置GL视口:渲染区域为整个GL表面(x=0,y=0,宽=width,高=height)
        GLES20.glViewport(0, 0, width, height);
        // 绑定当前滤镜的GL程序(所有后续GL操作基于该程序)
        GLES20.glUseProgram(filter.getProgram());
        // 通知滤镜适配新的输出尺寸(更新投影矩阵等)
        filter.onOutputSizeChanged(width, height);
        // 调整图像缩放/纹理坐标:适配新尺寸与图像原始尺寸的比例
        adjustImageScaling();
        // 同步锁:唤醒等待surface尺寸变化的线程(如相机预览初始化线程)
        synchronized (surfaceChangedWaiter) {
            surfaceChangedWaiter.notifyAll();
        }
    }

实现含义

  • glViewport定义GL绘制的像素区域,是尺寸适配的核心步骤;
  • glUseProgram绑定滤镜的GL程序,保证后续绘制使用正确的着色器;
  • adjustImageScaling是尺寸适配的核心逻辑,下文单独解析;
  • surfaceChangedWaiter用于多线程同步,避免相机预览在尺寸未就绪时启动。

3 onDrawFrame:帧绘制核心

java 复制代码
    // 每帧渲染时调用(刷新率通常为60fps)
    @Override
    public void onDrawFrame(final GL10 gl) {
        // 清空帧缓冲:清除颜色缓冲+深度缓冲(即使关闭深度测试,仍需清空)
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        // 执行所有绘制前任务(如纹理加载、滤镜切换)
        runAll(runOnDraw);
        // 执行滤镜绘制:传入纹理ID、顶点缓冲、纹理坐标缓冲,完成图像渲染
        filter.onDraw(glTextureId, glCubeBuffer, glTextureBuffer);
        // 执行所有绘制后任务
        runAll(runOnDrawEnd);
        // 如果绑定了相机SurfaceTexture,更新纹理数据(获取最新相机帧)
        if (surfaceTexture != null) {
            surfaceTexture.updateTexImage();
        }
    }

实现含义

  • glClear保证每帧绘制前缓冲区无残留数据,避免画面重叠;
  • runAll(runOnDraw)保证所有需在GL线程执行的任务(如纹理加载)在绘制前完成;
  • filter.onDraw是渲染核心:滤镜通过着色器将纹理映射到顶点立方体上,完成图像渲染;
  • surfaceTexture.updateTexImage()实时更新相机预览的纹理数据,保证预览画面流畅。

基础工具方法

1 setBackgroundColor:设置渲染背景色

java 复制代码
    /**
     * Sets the background color
     *
     * @param red   red color value
     * @param green green color value
     * @param blue  red color value
     */
    public void setBackgroundColor(float red, float green, float blue) {
        backgroundRed = red; // 更新红色通道值
        backgroundGreen = green; // 更新绿色通道值
        backgroundBlue = blue; // 更新蓝色通道值
    }

实现含义

  • 公开方法供外部设置渲染背景色,值范围为[0,1](GL颜色值规范);
  • 背景色仅在onSurfaceCreated生效,若需实时修改需重新触发GL表面创建。

2 runAll:执行任务队列

java 复制代码
    // 执行指定队列中的所有任务(保证GL线程安全)
    private void runAll(Queue<Runnable> queue) {
        synchronized (queue) { // 同步锁:避免多线程同时操作队列
            while (!queue.isEmpty()) { // 遍历队列,执行所有任务
                queue.poll().run(); // 取出并执行任务,执行后移除
            }
        }
    }

实现含义

  • 所有GL相关操作(如纹理加载、滤镜切换)必须在GL线程执行,通过该方法将主线程提交的任务在GL线程执行;
  • 同步锁避免队列在"添加任务"和"执行任务"时出现并发问题。

相机预览相关方法

1 onPreviewFrame(重载1):接收相机预览数据

java 复制代码
    // 相机预览回调:接收原始预览数据(系统默认回调)
    @Override
    public void onPreviewFrame(final byte[] data, final Camera camera) {
        // 获取相机预览尺寸(宽高)
        final Size previewSize = camera.getParameters().getPreviewSize();
        // 调用重载方法,传入数据+宽高,解耦相机对象与数据处理
        onPreviewFrame(data, previewSize.width, previewSize.height);
    }

实现含义

  • 系统相机预览的默认回调方法,仅做数据转发,解耦"相机对象"和"数据处理逻辑";
  • 通过camera.getParameters().getPreviewSize()获取真实的预览尺寸,避免硬编码适配问题。

2 onPreviewFrame(重载2):处理预览数据

java 复制代码
    // 重载方法:处理相机预览数据(YUV格式转RGBA并加载为GL纹理)
    public void onPreviewFrame(final byte[] data, final int width, final int height) {
        if (glRgbBuffer == null) { // 初始化RGB缓冲(仅首次调用)
            glRgbBuffer = IntBuffer.allocate(width * height); // 分配内存:宽*高个整型(RGBA占4字节)
        }
        if (runOnDraw.isEmpty()) { // 避免任务堆积:仅当绘制前队列为空时添加任务
            runOnDraw(new Runnable() { // 将数据处理任务加入绘制前队列
                @Override
                public void run() {
                    // 原生方法:将YUV格式的相机数据转为RGBA格式,存入glRgbBuffer
                    GPUImageNativeLibrary.YUVtoRBGA(data, width, height, glRgbBuffer.array());
                    // 加载RGBA数据为GL纹理,复用已有纹理ID(减少GL资源创建开销)
                    glTextureId = OpenGlUtils.loadTexture(glRgbBuffer, width, height, glTextureId);

                    // 若图像尺寸变化,更新尺寸并调整缩放
                    if (imageWidth != width) {
                        imageWidth = width; // 更新图像宽度
                        imageHeight = height; // 更新图像高度
                        adjustImageScaling(); // 重新适配缩放/纹理坐标
                    }
                }
            });
        }
    }

实现含义

  • glRgbBuffer仅初始化一次,避免重复分配内存;
  • 任务仅在runOnDraw为空时添加,避免相机预览数据过快导致任务堆积;
  • GPUImageNativeLibrary.YUVtoRBGA是原生方法(C/C++实现),高效完成YUV到RGBA的格式转换(Java层转换性能差);
  • OpenGlUtils.loadTexture将RGBA数据加载为GL纹理,复用已有纹理ID减少GL资源开销。

3 setUpSurfaceTexture:绑定相机与GL纹理

java 复制代码
    // 初始化相机预览的SurfaceTexture,绑定到GL纹理
    public void setUpSurfaceTexture(final Camera camera) {
        runOnDraw(new Runnable() { // 将GL操作加入绘制前队列(保证GL线程执行)
            @Override
            public void run() {
                int[] textures = new int[1]; // 存储生成的GL纹理ID
                // 生成1个GL纹理(返回ID存入textures数组)
                GLES20.glGenTextures(1, textures, 0);
                // 创建SurfaceTexture,绑定生成的GL纹理ID(相机数据将映射到该纹理)
                surfaceTexture = new SurfaceTexture(textures[0]);
                try {
                    // 将相机预览输出绑定到SurfaceTexture
                    camera.setPreviewTexture(surfaceTexture);
                    // 设置相机预览回调为当前渲染器
                    camera.setPreviewCallback(GPUImageRenderer.this);
                    // 启动相机预览
                    camera.startPreview();
                } catch (IOException e) { // 捕获纹理绑定异常
                    e.printStackTrace(); // 打印异常(实际开发中建议添加日志)
                }
            }
        });
    }

实现含义

  • glGenTextures生成GL纹理ID,是SurfaceTexture绑定的基础;
  • SurfaceTexture是相机预览与GL纹理的桥梁,相机输出的每一帧都会自动更新到绑定的纹理;
  • 所有操作包裹在runOnDraw中,保证GL纹理创建在GL线程执行(GL操作线程不安全)。

滤镜管理方法

java 复制代码
    // 切换滤镜(公开方法,供外部调用)
    public void setFilter(final GPUImageFilter filter) {
        runOnDraw(new Runnable() { // 将滤镜切换任务加入绘制前队列

            @Override
            public void run() {
                // 保存旧滤镜实例
                final GPUImageFilter oldFilter = GPUImageRenderer.this.filter;
                // 更新当前滤镜为新实例
                GPUImageRenderer.this.filter = filter;
                if (oldFilter != null) {
                    oldFilter.destroy(); // 销毁旧滤镜:释放GL程序、着色器等资源
                }
                // 初始化新滤镜:编译着色器、创建GL程序
                GPUImageRenderer.this.filter.ifNeedInit();
                // 绑定新滤镜的GL程序
                GLES20.glUseProgram(GPUImageRenderer.this.filter.getProgram());
                // 通知新滤镜适配当前输出尺寸
                GPUImageRenderer.this.filter.onOutputSizeChanged(outputWidth, outputHeight);
            }
        });
    }

实现含义

  • 滤镜切换是高频操作,通过runOnDraw保证在GL线程执行;
  • 销毁旧滤镜避免GL资源泄漏(GL程序/纹理属于系统资源,需手动释放);
  • 新滤镜初始化后需绑定GL程序+适配尺寸,保证渲染参数正确。

图像资源管理方法

1 deleteImage:删除GL纹理

java 复制代码
    // 删除当前绑定的GL纹理(释放资源)
    public void deleteImage() {
        runOnDraw(new Runnable() { // GL操作加入绘制前队列

            @Override
            public void run() {
                // 删除GL纹理:参数1=纹理数量,参数2=纹理ID数组,参数3=数组起始索引
                GLES20.glDeleteTextures(1, new int[]{
                        glTextureId
                }, 0);
                glTextureId = NO_IMAGE; // 重置纹理ID为无图像状态
            }
        });
    }

实现含义

  • glDeleteTextures是GL纹理的释放方法,必须手动调用避免内存泄漏;
  • 重置glTextureIdNO_IMAGE,标识当前无有效纹理。

2 setImageBitmap(重载1):加载位图(默认回收)

java 复制代码
    // 加载位图为GL纹理(公开方法,默认回收输入位图)
    public void setImageBitmap(final Bitmap bitmap) {
        setImageBitmap(bitmap, true); // 调用重载方法,默认回收位图
    }

实现含义

  • 简化外部调用,默认回收输入位图以减少内存占用;
  • 若需保留位图,可调用重载2传入false

3 setImageBitmap(重载2):加载位图(自定义回收)

java 复制代码
    // 加载位图为GL纹理(核心实现)
    public void setImageBitmap(final Bitmap bitmap, final boolean recycle) {
        if (bitmap == null) { // 空判断:避免空指针异常
            return;
        }

        runOnDraw(new Runnable() { // GL操作加入绘制前队列

            @Override
            public void run() {
                Bitmap resizedBitmap = null; // 存储补位后的位图
                // GL纹理要求宽高为偶数:若位图宽度为奇数,补1像素
                if (bitmap.getWidth() % 2 == 1) {
                    // 创建新位图:宽度+1,高度不变,格式ARGB_8888(GL兼容)
                    resizedBitmap = Bitmap.createBitmap(bitmap.getWidth() + 1, bitmap.getHeight(),
                            Bitmap.Config.ARGB_8888);
                    resizedBitmap.setDensity(bitmap.getDensity()); // 保持密度一致(适配不同屏幕)
                    Canvas can = new Canvas(resizedBitmap); // 创建画布
                    can.drawARGB(0x00, 0x00, 0x00, 0x00); // 绘制透明背景
                    can.drawBitmap(bitmap, 0, 0, null); // 绘制原始位图到新画布
                    addedPadding = 1; // 标记补位值为1
                } else {
                    addedPadding = 0; // 无补位,标记为0
                }

                // 加载位图为GL纹理:复用已有纹理ID,根据recycle参数决定是否回收输入位图
                glTextureId = OpenGlUtils.loadTexture(
                        resizedBitmap != null ? resizedBitmap : bitmap, glTextureId, recycle);
                if (resizedBitmap != null) { // 回收补位的临时位图
                    resizedBitmap.recycle();
                }
                imageWidth = bitmap.getWidth(); // 更新图像原始宽度
                imageHeight = bitmap.getHeight(); // 更新图像原始高度
                adjustImageScaling(); // 调整缩放/纹理坐标,适配新图像尺寸
            }
        });
    }

实现含义

  • GL纹理硬件限制宽高为偶数,补位处理保证纹理加载不崩溃;
  • 补位后的位图仅临时使用,加载完成后立即回收,避免内存泄漏;
  • OpenGlUtils.loadTexture封装了GL纹理的创建、绑定、数据上传逻辑,简化开发。

缩放与变换核心方法

1 setScaleType:设置缩放类型

java 复制代码
    // 设置图像缩放类型(公开方法)
    public void setScaleType(GPUImage.ScaleType scaleType) {
        this.scaleType = scaleType; // 更新缩放类型
    }

实现含义

  • 公开方法供外部设置缩放类型(如CENTER_CROP/CENTER_INSIDE);
  • 缩放类型修改后需调用adjustImageScaling生效(通常在尺寸变化时自动触发)。

2 getFrameWidth/getFrameHeight:获取输出尺寸

java 复制代码
    // 获取GL渲染表面的输出宽度(受保护,供子类调用)
    protected int getFrameWidth() {
        return outputWidth;
    }

    // 获取GL渲染表面的输出高度(受保护,供子类调用)
    protected int getFrameHeight() {
        return outputHeight;
    }

实现含义

  • 受保护方法,供子类获取渲染输出尺寸,扩展自定义渲染逻辑;
  • 输出尺寸是onSurfaceChanged中更新的视图尺寸,反映真实的渲染区域。

3 adjustImageScaling:核心缩放适配逻辑

java 复制代码
    // 核心方法:调整图像缩放、旋转、翻转后的顶点/纹理坐标
    private void adjustImageScaling() {
        // 初始输出宽高=GL表面宽高
        float outputWidth = this.outputWidth;
        float outputHeight = this.outputHeight;
        // 若旋转90/270度,交换输出宽高(适配竖屏渲染)
        if (rotation == Rotation.ROTATION_270 || rotation == Rotation.ROTATION_90) {
            outputWidth = this.outputHeight;
            outputHeight = this.outputWidth;
        }

        // 计算图像与输出区域的宽高比
        float ratio1 = outputWidth / imageWidth; // 宽度缩放比
        float ratio2 = outputHeight / imageHeight; // 高度缩放比
        float ratioMax = Math.max(ratio1, ratio2); // 取最大缩放比(保证图像填满输出区域)
        // 计算缩放后的图像宽高
        int imageWidthNew = Math.round(imageWidth * ratioMax);
        int imageHeightNew = Math.round(imageHeight * ratioMax);

        // 计算缩放后的图像与输出区域的比例(用于调整坐标)
        float ratioWidth = imageWidthNew / outputWidth;
        float ratioHeight = imageHeightNew / outputHeight;

        // 初始化顶点坐标=原始立方体坐标
        float[] cube = CUBE;
        // 获取旋转/翻转后的纹理坐标
        float[] textureCords = TextureRotationUtil.getRotation(rotation, flipHorizontal, flipVertical);
        
        // 若缩放类型为CENTER_CROP(居中裁剪)
        if (scaleType == GPUImage.ScaleType.CENTER_CROP) {
            // 计算纹理坐标水平偏移(居中裁剪)
            float distHorizontal = (1 - 1 / ratioWidth) / 2;
            // 计算纹理坐标垂直偏移(居中裁剪)
            float distVertical = (1 - 1 / ratioHeight) / 2;
            // 调整纹理坐标:添加偏移,实现居中裁剪
            textureCords = new float[]{
                    addDistance(textureCords[0], distHorizontal), addDistance(textureCords[1], distVertical),
                    addDistance(textureCords[2], distHorizontal), addDistance(textureCords[3], distVertical),
                    addDistance(textureCords[4], distHorizontal), addDistance(textureCords[5], distVertical),
                    addDistance(textureCords[6], distHorizontal), addDistance(textureCords[7], distVertical),
            };
        } else { // 其他缩放类型(如CENTER_INSIDE):调整顶点坐标,适配输出区域
            cube = new float[]{
                    CUBE[0] / ratioHeight, CUBE[1] / ratioWidth,
                    CUBE[2] / ratioHeight, CUBE[3] / ratioWidth,
                    CUBE[4] / ratioHeight, CUBE[5] / ratioWidth,
                    CUBE[6] / ratioHeight, CUBE[7] / ratioWidth,
            };
        }

        // 更新顶点缓冲:清空原有数据,存入新顶点坐标,重置指针到起始位置
        glCubeBuffer.clear();
        glCubeBuffer.put(cube).position(0);
        // 更新纹理坐标缓冲:逻辑同顶点缓冲
        glTextureBuffer.clear();
        glTextureBuffer.put(textureCords).position(0);
    }

实现含义

  • 旋转90/270度时交换输出宽高,适配竖屏图像的渲染(相机预览通常为横屏,需旋转为竖屏);
  • CENTER_CROP模式下调整纹理坐标:通过偏移实现"图像填满输出区域,超出部分裁剪,居中显示";
  • 其他缩放模式下调整顶点坐标:通过缩放顶点实现"图像完整显示,无裁剪";
  • 最终更新顶点/纹理缓冲,保证绘制时使用最新的坐标数据。

4 addDistance:纹理坐标偏移辅助方法

java 复制代码
    // 辅助方法:计算纹理坐标的偏移值(适配CENTER_CROP)
    private float addDistance(float coordinate, float distance) {
        // 纹理坐标为0时加偏移,为1时减偏移(实现居中)
        return coordinate == 0.0f ? distance : 1 - distance;
    }

实现含义

  • 纹理坐标范围为[0,1],0对应左/上边界,1对应右/下边界;
  • 通过偏移调整,让纹理的有效区域居中,超出部分被裁剪(CENTER_CROP核心逻辑)。

旋转与翻转配置方法

1 setRotationCamera:相机旋转适配

java 复制代码
    // 适配相机的旋转/翻转(交换水平/垂直翻转参数,因相机传感器方向问题)
    public void setRotationCamera(final Rotation rotation, final boolean flipHorizontal,
                                  final boolean flipVertical) {
        // 交换flipHorizontal和flipVertical,适配相机传感器的默认方向
        setRotation(rotation, flipVertical, flipHorizontal);
    }

实现含义

  • 相机传感器的默认方向与屏幕方向不一致,需交换水平/垂直翻转参数以保证预览画面正确;
  • 公开方法供外部调用,简化相机场景的旋转适配。

2 setRotation(重载1):设置旋转

java 复制代码
    // 设置图像旋转(仅旋转,无翻转)
    public void setRotation(final Rotation rotation) {
        this.rotation = rotation; // 更新旋转配置
        adjustImageScaling(); // 调整缩放/坐标,使旋转生效
    }

实现含义

  • 仅设置旋转角度,无翻转,调用后立即调整坐标生效。

3 setRotation(重载2):设置旋转+翻转

java 复制代码
    // 设置图像旋转+水平/垂直翻转(核心配置方法)
    public void setRotation(final Rotation rotation,
                            final boolean flipHorizontal, final boolean flipVertical) {
        this.flipHorizontal = flipHorizontal; // 更新水平翻转配置
        this.flipVertical = flipVertical; // 更新垂直翻转配置
        setRotation(rotation); // 调用重载1,设置旋转并调整坐标
    }

实现含义

  • 完整的变换配置方法,支持旋转+翻转组合;
  • 配置后立即调用adjustImageScaling,保证变换实时生效。

4 获取变换状态

java 复制代码
    // 获取当前旋转配置(公开方法)
    public Rotation getRotation() {
        return rotation;
    }

    // 判断是否水平翻转(公开方法)
    public boolean isFlippedHorizontally() {
        return flipHorizontal;
    }

    // 判断是否垂直翻转(公开方法)
    public boolean isFlippedVertically() {
        return flipVertical;
    }

实现含义

  • 公开方法供外部获取当前变换状态,用于调试或二次处理。

任务队列辅助方法

java 复制代码
    // 添加绘制前任务(受保护,供子类扩展)
    protected void runOnDraw(final Runnable runnable) {
        synchronized (runOnDraw) { // 同步锁:避免并发问题
            runOnDraw.add(runnable); // 添加任务到队列
        }
    }

    // 添加绘制后任务(受保护,供子类扩展)
    protected void runOnDrawEnd(final Runnable runnable) {
        synchronized (runOnDrawEnd) { // 同步锁:避免并发问题
            runOnDrawEnd.add(runnable); // 添加任务到队列
        }
    }
}

实现含义

  • 受保护方法,供子类添加自定义GL任务(如自定义纹理处理、额外渲染步骤);
  • 同步锁保证队列操作的线程安全。

总结

GPUImageRenderer是GPUImage框架的"渲染中枢",其核心设计思路可概括为:

  1. 线程安全:所有GL操作通过任务队列在GL线程执行,避免并发问题;
  2. 解耦设计:将"渲染生命周期、相机交互、滤镜处理、图像变换"拆分为独立方法,便于扩展;
  3. 硬件适配:处理GL纹理宽高偶数限制、相机旋转适配、多缩放类型等硬件/场景适配;
  4. 性能优化:复用GL纹理ID、关闭深度测试、原生方法转换图像格式,保证渲染性能。
相关推荐
冬奇Lab2 小时前
【Kotlin系列07】类型系统深度解析:从空安全到智能类型推断的设计哲学
android·开发语言·安全·kotlin
八宝粥大朋友2 小时前
rabbitMQ-C 构建android 动态库
android·c语言·rabbitmq
超级任性2 小时前
Android Studio开发你的第一个Android程序
android·ide·android studio
2501_916007472 小时前
在没有 Mac 的情况下完成 iOS 应用上架 App Store
android·macos·ios·小程序·uni-app·iphone·webview
dongbaoming2 小时前
调用其他应用的activity结束后回到调用app
android
独行soc2 小时前
2026年渗透测试面试题总结-2(题目+回答)
android·java·网络·python·安全·web安全·渗透测试
TheNextByte12 小时前
iPhone存储空间已满?如何轻松释放iPhone空间?
android·ios·iphone
八宝粥大朋友2 小时前
OpenSSL构建android 脚本
android·ssl
似霰10 小时前
AIDL Hal 开发笔记2----AIDL HAL 实例分析light hal
android·framework·hal