
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;
实现含义:
- 成员变量分为五大类:
- 核心依赖(filter):承载滤镜的着色器编译、图像渲染逻辑;
- GL资源(glTextureId/surfaceTexture/缓冲对象):管理GL渲染的核心资源;
- 尺寸参数(output/input宽高):适配不同屏幕/图像尺寸的渲染需求;
- 变换配置(旋转/翻转/缩放):处理图像的显示姿态适配;
- 线程任务(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纹理的释放方法,必须手动调用避免内存泄漏;- 重置
glTextureId为NO_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框架的"渲染中枢",其核心设计思路可概括为:
- 线程安全:所有GL操作通过任务队列在GL线程执行,避免并发问题;
- 解耦设计:将"渲染生命周期、相机交互、滤镜处理、图像变换"拆分为独立方法,便于扩展;
- 硬件适配:处理GL纹理宽高偶数限制、相机旋转适配、多缩放类型等硬件/场景适配;
- 性能优化:复用GL纹理ID、关闭深度测试、原生方法转换图像格式,保证渲染性能。
