众所周知,GPUImage是一个著名的图像处理开源库,里面提供了多达上百种的滤镜效果来加工图像,并且支持图片和视频的实时滤镜,满足我们日常美颜/滤镜需求。对这样强大的开源框架进行分析,一方面可以帮助我们在自己的项目上定制我们滤镜效果,另一方面源码中涉及的AVFoundation框架应用、图像处理逻辑、OpenGL API调用都是我们学习渲染非常实用的案例,值得仔细学习。
本篇文章主要对GPUImage框架进行一个整体的分析,了解框架的模块划分和功能概况,更多细节的讨论将在后续文章中逐步分析。
GPUImage框架的源码对模块的划分还是比较清晰的,主要分为了以下四个部分:
- 上下文环境,包括运行GPUImage的上下文定义、资源定义、缓存管理相关类都包括在其中
- 输入源,即滤镜处理链路的源头,包括视频、图片在内的各种输入源都定义其中
- 输出源,即处理链路的尽头,用于将处理后的数据绘制到屏幕、或者转成二进制数据推流等等
- 滤镜,提供多达上百种的滤镜效果使用来进行图像处理
接下来我们逐一进行分析:
1. 上下文环境
1.1 GPUImageContext
GPUImageContext是一个单例对象,用于管理渲染过程中的资源,包括:
- EAGLContext上下文
@property(readonly, retain, nonatomic) EAGLContext *context;
- 着色器程序
// 当前正在使用的着色器程序
@property(readwrite, retain, nonatomic) GLProgram *currentShaderProgram;
// 从着色器缓存中查询对应着色器,如果未nil则创建
- (GLProgram *)programForVertexShaderString:(NSString *)vertexShaderString fragmentShaderString:(NSString *)fragmentShaderString;
- FrameBuffer缓存
// 渲染过程中在创建FrameBuffer时会优先从缓存中获取符合尺寸要求的frameBuffer,避免重复创建
@property(readonly) GPUImageFramebufferCache *framebufferCache;
- 线程队列
// 在初始化上下文时,GPUImage会自动创建一个串行队列,渲染任务都将抛到该串行队列中执行
@property(readonly, nonatomic) dispatch_queue_t contextQueue;
1.2 GLProgram
GPUImage对图片的处理和渲染主要是由OpenGL ES实现的,GLProgram则是对OpenGL ES program的OOP封装,以方便调用。核心功能如下:
- 着色器程序的编译、链接、使用及日志反馈
// 着色器编译
- (id)initWithVertexShaderString:(NSString *)vShaderString
fragmentShaderString:(NSString *)fShaderString;
// 着色器链接
- (BOOL)link;
// 着色器使用
- (void)use;
// 着色器有效性校验
- (void)validate;
- 着色器程序的有效性校验
// 着色器有效性校验
- (void)validate;
- attribute、uniform变量字符串映射
// 变量名映射到变量索引
- (GLuint)attributeIndex:(NSString *)attributeName;
- (GLuint)uniformIndex:(NSString *)uniformName;
1.3 GPUImageFrameBuffer
GPUImageFrameBuffer本质是对OpenGL frameBuffer和Texture的OOP封装。
帧缓冲区(frameBuffer)可以看作是一块画板,但不是画布。它只作为容器,因此frameBuffer本身并不存储数据,数据依然是存储在画布上,而画布可以是纹理(Texture)或者是渲染缓冲区(RenderBuffer),在frameBuffer上这些位置称为附件(Attachment)。
因此,GPUImageFrameBuffer实际上管理着frameBuffer和纹理数据。其核心接口如下:
- FrameBuffer初始化
// @param framebufferSize指定纹理尺寸
// @param fboTextureOptions指定纹理参数
// @param onlyGenerateTexture指定是否只创建texture而不创建FrameBuffer
- (id)initWithSize:(CGSize)framebufferSize
textureOptions:(GPUTextureOptions)fboTextureOptions
onlyTexture:(BOOL)onlyGenerateTexture;
- FrameBuffer激活
// 在使用frameBuffer前需要先激活
- (void)activateFramebuffer
- 引用计数
// 引用计数相关接口,当frameBuffer的引用计数为0时,会自动放入缓存池
- (void)lock;
- (void)unlock;
- (void)clearAllLocks;
- (void)disableReferenceCounting;
- (void)enableReferenceCounting;
- 数据读取
// 获取frameBuffer附件中的pixelBuffer数据
- (NSUInteger)bytesPerRow;
- (GLubyte *)byteBuffer;
- (CVPixelBufferRef)pixelBuffer;
1.4 GPUImageFrameBufferCache
GPUImageFrameBuffer缓存池,实际上负责渲染过程中GPUImageFrameBuffer的创建和回收。
// 获取指定纹理尺寸和参数的frameBuffer,如果不存在则会创建一个新的,并放入缓存池
- (GPUImageFramebuffer *)fetchFramebufferForSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)textureOptions onlyTexture:(BOOL)onlyTexture;
// frameBuffer使用完成后放入缓存池中
- (void)returnFramebufferToCache:(GPUImageFramebuffer *)framebuffer;
2. 输入源Source
GPUImage本质上是一个OpenGL管理框架,它不仅提供了大量OpenGL滤镜实现,同时也提供了一套滤镜的链式调用流程,类似于工厂的流水线。而输入源即是流水线的起点,即原料。输入源继承自GPUImageOutput来实现GPUImageFramebuffer的输出行为。
2.1 GPUImageOutput
2.1.1 关键属性
// 输入转换成OutputFrameBuffer
GPUImageFramebuffer *outputFramebuffer;
// 记录frameBuffer输出的目标
NSMutableArray *targets;
// 记录frameBuffer输出到目标的那个索引位置的frameBuffer上(某些滤镜可以接受多个输入源,因此需要记录是输出到哪个位置)
NSMutableArray *targetTextureIndices
2.1.2 关键方法
// 添加一个输出目标
- (void)addTarget:(id<GPUImageInput>)newTarget;
// 移除一个输出目标
- (void)removeTarget:(id<GPUImageInput>)targetToRemove;
// 将OutputFramebuffer赋值到target的指定索引的frameBuffer上,也就是把上一个滤镜处理好的数据传递到下一个滤镜中作为输入)
- (void)setInputFramebufferForTarget:(id<GPUImageInput>)target atIndex:(NSInteger)inputTextureIndex;
2.2 输入源类型
输入源 | 类型 | 说明 |
---|---|---|
GPUImageRawDataInput | 二进制图片数据 | 将二进制数据通过glTexImage2D加载到输出纹理中 |
GPUImageUIElement | UIView或CALayer数据 | 将显示的内容绘制到CoreGraphics上下文,再通过glTexImage2D加载 |
GPUImageVideoCamera | 视频数据 | 拿到视频录制数据CMSamleBufferRef通过YUV/RGB纹理生成流程加载到frameBuffer |
GPUImageMovie | 本地视频 | 拿到视频每帧数据再通过YUV/RGB纹理生成流程加载到frame |
GPUImageTextureInput | 已存在的纹理 | 将已存在的纹理直接绑定到frameBuffer上 |
GPUImagePicture | 本地图片 | 获取图片的二进制数据再通过glTexImage2D加载到纹理上 |
3. 输出源Output
前面提到输入源是流水线的起点,那么输出源就是链路的终点了。
3.1 GPUImageInput协议
GPUImageInput是一个协议,实现该协议的对象具备接受frameBuffer输入,从而展示纹理(例如上屏)或者传递纹理到下一个target的能力,输出源和中间滤镜均实现了该协议。
3.1.1 核心方法
- 设置输入frameBuffer数据
// 设置该滤镜对应索引位置的输入frameBuffer
- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex;
- 启动下一帧渲染
// 通知该节点的target输入frameBuffer已经准备就绪,可以开始下一帧渲染
- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
3.2 输出源类型
输出源 | 类型 | 说明 |
---|---|---|
GPUImageView | 继承自UIView | 处理后的图像直接渲染到指定的原生view上 |
GPUImageMovieWriter | 封装AVAssetWriter | 将处理后的视频数据逐帧写入指定路径文件中 |
GPUImageRawDataOutput | 二进制数据 | 获取处理后纹理的二进制数据,可用于上行推流等等 |
GPUImageTextureOutput | 纹理数据 | 每一帧渲染结束后,通过texture属性返回输入纹理的索引 |
输出源实现了GPUImageInput协议,在newFrameReadyAtTime:atIndex:方法中启动下一帧渲染时,执行上屏、写入文件或者返回二进制数据等操作,而不进行纹理数据的传递,因此它是滤镜链的终点。
4. 滤镜Filters
滤镜即是GPUImage框架的核心,每个滤镜Filter实现了一种图像处理的能力,例如提高亮度、对比度、饱和度等等,GPUImage框架包括了上百种滤镜,可以根据需求选择。
4.1 GPUImageFilter
GPUImage中的滤镜均继承自GPUImageFilter,其定义了一个滤镜处理的基本流程。GPUImageFilter继承自GPUImageOutput,同时实现了GPUImageInput协议,这就使得GPUImageFilter即可以接收frameBuffer输入进行图形处理,同时可以将处理后的数据传递到下一个target进行后续处理,实现了数据流的传递。
@interface GPUImageFilter : GPUImageOutput <GPUImageInput>
4.1.1 核心方法
- 初始化滤镜
// 初始化滤镜,包括生成GLPrograme渲染程序,初始化shader程序中变量索引等
- (id)initWithVertexShaderFromString:(NSString *)vertexShaderString fragmentShaderFromString:(NSString *)fragmentShaderString;
- 启动渲染
// GPUImageInput协议实现,启动一帧渲染
- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
- 渲染逻辑
// 该滤镜根据GLProgram进行图像处理的真正渲染逻辑
- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;
- 通知targets启动下一帧渲染
// 通知该filter连接的所有targets进行下一帧数据处理
- (void)informTargetsAboutNewFrameAtTime:(CMTime)frameTime
4.2 滤镜分类
前面提到GPUImage框架包括了上百种Filter,官方实际上也对这些滤镜进行了分类(每个分类仅列举部分代表性滤镜),具体的实现逻辑读者可以根据需求进行阅读,流程上都是一致的,只是输入输出个数以及shader代码有所不同。
- 颜色调整类
滤镜 | 作用 |
---|---|
GPUImageBrightnessFilter | 调整图像的亮度 |
GPUImageExposureFilter | 调整图像的曝光 |
GPUImageContrastFilter | 调整图像的对比度 |
GPUImageSaturationFilter | 调整图像的饱和度 |
GPUImageGammaFilter | 调整图像的灰度系数 |
GPUImageRGBFilter | 调整图像的各个RGB通道 |
GPUImageHueFilter | 调整图像的色调 |
GPUImageWhiteBalanceFilter | 调整图像的白平衡 |
GPUImageHistogramFilter | 分析输入图像并创建输出直方图 |
- 图像处理类
滤镜 | 作用 |
---|---|
GPUImageTransformFilter | 对图像应用任意2-D或3-D变换 |
GPUImageCropFilter | 将图像裁剪到特定区域 |
GPUImageSharpenFilter | 锐化图像 |
GPUImageGaussianBlurFilter | 高斯模糊 |
GPUImageSobelEdgeDetectionFilter | Sobel边缘检测 |
GPUImageMotionBlurFilter | 运动模糊 |
- 混合模式类
滤镜 | 作用 |
---|---|
GPUImageAddBlendFilter | 两个图像的叠加混合 |
GPUImageSubtractBlendFilter | 两个图像的减法混合 |
GPUImageDivideBlendFilter | 两个图像的分割混合 |
GPUImageMaskFilter | 使用另一个图像掩盖一个图像 |
- 视觉效果类
滤镜 | 作用 |
---|---|
GPUImageSketchFilter | 将视频转换为草图 |
GPUImageEmbossFilter | 对图像应用浮雕效果 |
GPUImagePosterizeFilter | 卡通着色 |
GPUImageBulgeDistortionFilter | 图像捏合扭曲 |
5. 总结
通过对GPUImage框架模块的逐一分析,我们可以知道GPUImage是采用了责任链模式来实现链式处理。
GPUImage定义了一个GPUImageOutput类和一个GPUImageInput协议,实现了GPUImageInput协议的对象具备接受frameBuffer纹理输入并进行处理的能力,而继承自GPUImageOutput的对象则可以将处理后的输出纹理传递到下一个target的功能。
输入源Input继承自GPUImageOutput,可以将图片、视频等等数据上传到frameBuffer后传递到GPUImageFilter中处理,最后一个Filter在处理完成后,将数据传递到实现了GPUImageInput协议的输出源Output中进行上屏绘制或者上行推流,形成完整的滤镜链路处理。