概述
GPUImage是一个著名的图像处理开源库,它让你能够在图片、视频、相机上使用GPU加速的滤镜和其它特效。与CoreImage框架相比,可以根据GPUImage提供的接口,使用自定义的滤镜。项目地址:https://github.com/BradLarson/GPUImage
这篇文章主要是阅读GPUImage框架中的GPUImageRawDataInput、GPUImageRawDataOutput、GPUImageTextureInput、GPUImageTextureOutput 这几个类的源码。之前介绍过数据来自相机、图片、音视频文件、UI渲染,这里还会介绍其它两种:RGBA原始数据(包括:RGB、RGBA、BGRA、LUMINANCE)和纹理对象。以下是源码内容:
GPUImageRawDataInput
GPUImageRawDataOutput
GPUImageTextureInput
GPUImageTextureOutput
实现效果
GPUImageRawDataInput
GPUImageRawDataInput继承自GPUImageOutput,它可以接受RawData输入(包括:RGB、RGBA、BGRA、LUMINANCE数据)并生成帧缓存对象。
- 构造方法。构造的时候主要是根据RawData数据指针,数据大小,以及数据格式进行构造。
- (id)initWithBytes:(GLubyte *)bytesToUpload size:(CGSize)imageSize;
- (id)initWithBytes:(GLubyte *)bytesToUpload size:(CGSize)imageSize pixelFormat:(GPUPixelFormat)pixelFormat;
- (id)initWithBytes:(GLubyte *)bytesToUpload size:(CGSize)imageSize pixelFormat:(GPUPixelFormat)pixelFormat type:(GPUPixelType)pixelType;
- 构造的时候如果不指定像素格式和数据类型,默认会使用GPUPixelFormatBGRA和GPUPixelTypeUByte的方式。构造的过程是:1、初始化实例变量;2、生成只有纹理对象的GPUImageFramebuffer对象;3、给纹理对象绑定RawData数据。
- (id)initWithBytes:(GLubyte *)bytesToUpload size:(CGSize)imageSize;
{
if (!(self = [self initWithBytes:bytesToUpload size:imageSize pixelFormat:GPUPixelFormatBGRA type:GPUPixelTypeUByte]))
{
return nil;
}
return self;
}
- (id)initWithBytes:(GLubyte *)bytesToUpload size:(CGSize)imageSize pixelFormat:(GPUPixelFormat)pixelFormat;
{
if (!(self = [self initWithBytes:bytesToUpload size:imageSize pixelFormat:pixelFormat type:GPUPixelTypeUByte]))
{
return nil;
}
return self;
}
- (id)initWithBytes:(GLubyte *)bytesToUpload size:(CGSize)imageSize pixelFormat:(GPUPixelFormat)pixelFormat type:(GPUPixelType)pixelType;
{
if (!(self = [super init]))
{
return nil;
}
dataUpdateSemaphore = dispatch_semaphore_create(1);
uploadedImageSize = imageSize;
self.pixelFormat = pixelFormat;
self.pixelType = pixelType;
[self uploadBytes:bytesToUpload];
return self;
}
- (void)uploadBytes:(GLubyte *)bytesToUpload;
{
// 设置上下文对象
[GPUImageContext useImageProcessingContext];
// 生成GPUImageFramebuffer
outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:uploadedImageSize textureOptions:self.outputTextureOptions onlyTexture:YES];
// 绑定纹理数据
glBindTexture(GL_TEXTURE_2D, [outputFramebuffer texture]);
glTexImage2D(GL_TEXTURE_2D, 0, _pixelFormat, (int)uploadedImageSize.width, (int)uploadedImageSize.height, 0, (GLint)_pixelFormat, (GLenum)_pixelType, bytesToUpload);
}
- 其它方法。
// 上传数据
- (void)updateDataFromBytes:(GLubyte *)bytesToUpload size:(CGSize)imageSize;
// 处理数据
- (void)processData;
- (void)processDataForTimestamp:(CMTime)frameTime;
// 输出纹理大小
- (CGSize)outputImageSize;
在其它方法中比较重要的是数据处理方法,它的主要作用是驱动响应链,也就是将读取的RawData数据传递给接下来的targets进行处理。
// 上传原始数据
- (void)updateDataFromBytes:(GLubyte *)bytesToUpload size:(CGSize)imageSize;
{
uploadedImageSize = imageSize;
// 调用数据上传方法
[self uploadBytes:bytesToUpload];
}
// 数据处理
- (void)processData;
{
if (dispatch_semaphore_wait(dataUpdateSemaphore, DISPATCH_TIME_NOW) != 0)
{
return;
}
runAsynchronouslyOnVideoProcessingQueue(^{
CGSize pixelSizeOfImage = [self outputImageSize];
// 遍历所有的targets,将outputFramebuffer交给每个target处理
for (id currentTarget in targets)
{
NSInteger indexOfObject = [targets indexOfObject:currentTarget];
NSInteger textureIndexOfTarget = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];
[currentTarget setInputSize:pixelSizeOfImage atIndex:textureIndexOfTarget];
[currentTarget setInputFramebuffer:outputFramebuffer atIndex:textureIndexOfTarget];
[currentTarget newFrameReadyAtTime:kCMTimeInvalid atIndex:textureIndexOfTarget];
}
dispatch_semaphore_signal(dataUpdateSemaphore);
});
}
// 数据处理
- (void)processDataForTimestamp:(CMTime)frameTime;
{
if (dispatch_semaphore_wait(dataUpdateSemaphore, DISPATCH_TIME_NOW) != 0)
{
return;
}
runAsynchronouslyOnVideoProcessingQueue(^{
CGSize pixelSizeOfImage = [self outputImageSize];
// 遍历所有的targets,将outputFramebuffer交给每个target处理
for (id currentTarget in targets)
{
NSInteger indexOfObject = [targets indexOfObject:currentTarget];
NSInteger textureIndexOfTarget = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];
[currentTarget setInputSize:pixelSizeOfImage atIndex:textureIndexOfTarget];
[currentTarget newFrameReadyAtTime:frameTime atIndex:textureIndexOfTarget];
}
dispatch_semaphore_signal(dataUpdateSemaphore);
});
}
// 输出纹理大小
- (CGSize)outputImageSize;
{
return uploadedImageSize;
}
GPUImageRawDataOutput
GPUImageRawDataOutput实现了GPUImageInput协议,它可以将输入的帧缓存转换为原始数据。
- 构造方法。构造方法最主要的任务是构造GL程序。
- (id)initWithImageSize:(CGSize)newImageSize resultsInBGRAFormat:(BOOL)resultsInBGRAFormat;
初始化的时候需要指定纹理大小以及是否以BGRA形式的数据输入。如果是以BGRA的形式输入,则在选择片段着色器的时候会选择kGPUImageColorSwizzlingFragmentShaderString着色器来进行从BGRA-到RGBA的转换。
- (id)initWithImageSize:(CGSize)newImageSize resultsInBGRAFormat:(BOOL)resultsInBGRAFormat;
{
if (!(self = [super init]))
{
return nil;
}
self.enabled = YES;
lockNextFramebuffer = NO;
outputBGRA = resultsInBGRAFormat;
imageSize = newImageSize;
hasReadFromTheCurrentFrame = NO;
_rawBytesForImage = NULL;
inputRotation = kGPUImageNoRotation;
[GPUImageContext useImageProcessingContext];
// 如果使用了BGRA ,则选择kGPUImageColorSwizzlingFragmentShaderString着色器
if ( (outputBGRA && ![GPUImageContext supportsFastTextureUpload]) || (!outputBGRA && [GPUImageContext supportsFastTextureUpload]) )
{
dataProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:kGPUImageVertexShaderString fragmentShaderString:kGPUImageColorSwizzlingFragmentShaderString];
}
// 否则选用kGPUImagePassthroughFragmentShaderString着色器
else
{
dataProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:kGPUImageVertexShaderString fragmentShaderString:kGPUImagePassthroughFragmentShaderString];
}
if (!dataProgram.initialized)
{
[dataProgram addAttribute:@"position"];
[dataProgram addAttribute:@"inputTextureCoordinate"];
if (![dataProgram link])
{
NSString *progLog = [dataProgram programLog];
NSLog(@"Program link log: %@", progLog);
NSString *fragLog = [dataProgram fragmentShaderLog];
NSLog(@"Fragment shader compile log: %@", fragLog);
NSString *vertLog = [dataProgram vertexShaderLog];
NSLog(@"Vertex shader compile log: %@", vertLog);
dataProgram = nil;
NSAssert(NO, @"Filter shader link failed");
}
}
// 获取统一变量和属性
dataPositionAttribute = [dataProgram attributeIndex:@"position"];
dataTextureCoordinateAttribute = [dataProgram attributeIndex:@"inputTextureCoordinate"];
dataInputTextureUniform = [dataProgram uniformIndex:@"inputImageTexture"];
return self;
}
- 其他方法
// 获取特定位置的像素向量
- (GPUByteColorVector)colorAtLocation:(CGPoint)locationInImage;
// 每行数据大小
- (NSUInteger)bytesPerRowInOutput;
// 设置纹理大小
- (void)setImageSize:(CGSize)newImageSize;
// 锁定、与解锁帧缓存
- (void)lockFramebufferForReading;
- (void)unlockFramebufferAfterReading;
方法实现如下:
// 获取特定位置的像素向量
- (GPUByteColorVector)colorAtLocation:(CGPoint)locationInImage;
{
// 将数据转为GPUByteColorVector类型
GPUByteColorVector *imageColorBytes = (GPUByteColorVector *)self.rawBytesForImage;
// NSLog(@"Row start");
// for (unsigned int currentXPosition = 0; currentXPosition < (imageSize.width * 2.0); currentXPosition++)
// {
// GPUByteColorVector byteAtPosition = imageColorBytes[currentXPosition];
// NSLog(@"%d - %d, %d, %d", currentXPosition, byteAtPosition.red, byteAtPosition.green, byteAtPosition.blue);
// }
// NSLog(@"Row end");
// GPUByteColorVector byteAtOne = imageColorBytes[1];
// GPUByteColorVector byteAtWidth = imageColorBytes[(int)imageSize.width - 3];
// GPUByteColorVector byteAtHeight = imageColorBytes[(int)(imageSize.height - 1) * (int)imageSize.width];
// NSLog(@"Byte 1: %d, %d, %d, byte 2: %d, %d, %d, byte 3: %d, %d, %d", byteAtOne.red, byteAtOne.green, byteAtOne.blue, byteAtWidth.red, byteAtWidth.green, byteAtWidth.blue, byteAtHeight.red, byteAtHeight.green, byteAtHeight.blue);
// 控制边界,0 < x < width, 0 < y < height,
CGPoint locationToPickFrom = CGPointZero;
locationToPickFrom.x = MIN(MAX(locationInImage.x, 0.0), (imageSize.width - 1.0));
locationToPickFrom.y = MIN(MAX((imageSize.height - locationInImage.y), 0.0), (imageSize.height - 1.0));
// 如果是BGRA输出,则把RGBA数据转为BGRA数据
if (outputBGRA)
{
GPUByteColorVector flippedColor = imageColorBytes[(int)(round((locationToPickFrom.y * imageSize.width) + locationToPickFrom.x))];
GLubyte temporaryRed = flippedColor.red;
flippedColor.red = flippedColor.blue;
flippedColor.blue = temporaryRed;
return flippedColor;
}
else
{
// 返回某个位置的像素向量
return imageColorBytes[(int)(round((locationToPickFrom.y * imageSize.width) + locationToPickFrom.x))];
}
}
// 每行数据大小
- (NSUInteger)bytesPerRowInOutput;
{
return [retainedFramebuffer bytesPerRow];
}
// 设置输出纹理大小
- (void)setImageSize:(CGSize)newImageSize {
imageSize = newImageSize;
if (_rawBytesForImage != NULL && (![GPUImageContext supportsFastTextureUpload]))
{
free(_rawBytesForImage);
_rawBytesForImage = NULL;
}
}
// 锁定帧缓存
- (void)lockFramebufferForReading;
{
lockNextFramebuffer = YES;
}
// 解锁帧缓存
- (void)unlockFramebufferAfterReading;
{
[retainedFramebuffer unlockAfterReading];
[retainedFramebuffer unlock];
retainedFramebuffer = nil;
}
// 获取RGBA数据
- (GLubyte *)rawBytesForImage;
{
if ( (_rawBytesForImage == NULL) && (![GPUImageContext supportsFastTextureUpload]) )
{
// 申请空间,储存读取的数据
_rawBytesForImage = (GLubyte *) calloc(imageSize.width * imageSize.height * 4, sizeof(GLubyte));
hasReadFromTheCurrentFrame = NO;
}
if (hasReadFromTheCurrentFrame)
{
return _rawBytesForImage;
}
else
{
runSynchronouslyOnVideoProcessingQueue(^{
// Note: the fast texture caches speed up 640x480 frame reads from 9.6 ms to 3.1 ms on iPhone 4S
// 设置GL下文对象
[GPUImageContext useImageProcessingContext];
// 渲染到帧缓存
[self renderAtInternalSize];
if ([GPUImageContext supportsFastTextureUpload])
{
// 等待绘制结束
glFinish();
_rawBytesForImage = [outputFramebuffer byteBuffer];
}
else
{
// 以RGBA的形式读取数据
glReadPixels(0, 0, imageSize.width, imageSize.height, GL_RGBA, GL_UNSIGNED_BYTE, _rawBytesForImage);
// GL_EXT_read_format_bgra
// glReadPixels(0, 0, imageSize.width, imageSize.height, GL_BGRA_EXT, GL_UNSIGNED_BYTE, _rawBytesForImage);
}
hasReadFromTheCurrentFrame = YES;
});
return _rawBytesForImage;
}
}
GPUImageTextureInput
GPUImageTextureInput继承自GPUImageOutput,可以用传入的纹理生成帧缓存对象。因此,可以作为响应链源使用。
- 构造方法。构造方法只有一个,接收的参数是纹理对象和纹理大小。
- (id)initWithTexture:(GLuint)newInputTexture size:(CGSize)newTextureSize;
构造的时候,主要是用传入的纹理生成帧缓存对象。
- (id)initWithTexture:(GLuint)newInputTexture size:(CGSize)newTextureSize;
{
if (!(self = [super init]))
{
return nil;
}
runSynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext useImageProcessingContext];
});
textureSize = newTextureSize;
runSynchronouslyOnVideoProcessingQueue(^{
// 生成帧缓存对象
outputFramebuffer = [[GPUImageFramebuffer alloc] initWithSize:newTextureSize overriddenTexture:newInputTexture];
});
return self;
}
- 其它方法。出去父类的方法,它只提供了一个数据处理的方法。
- (void)processTextureWithFrameTime:(CMTime)frameTime;
处理数据的过程就是将帧缓存对象传递给所有targets,驱动所有targets处理。
- (void)processTextureWithFrameTime:(CMTime)frameTime;
{
runAsynchronouslyOnVideoProcessingQueue(^{
// 遍历所有targets,驱动各个target处理数据
for (id currentTarget in targets)
{
NSInteger indexOfObject = [targets indexOfObject:currentTarget];
NSInteger targetTextureIndex = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];
[currentTarget setInputSize:textureSize atIndex:targetTextureIndex];
[currentTarget setInputFramebuffer:outputFramebuffer atIndex:targetTextureIndex];
[currentTarget newFrameReadyAtTime:frameTime atIndex:targetTextureIndex];
}
});
}
GPUImageTextureOutput
GPUImageTextureOutput继承自NSObject实现了GPUImageInput协议。它可以获取到输入的帧缓存中的纹理对象。
- 属性。这里最重要的属性是texture,我们可以拿到texture并使用。
// GPUImageTextureOutputDelegate
@property(readwrite, unsafe_unretained, nonatomic) id delegate;
// 纹理对象
@property(readonly) GLuint texture;
// 是否启用
@property(nonatomic) BOOL enabled;
- 构造方法。构造的时候不需要传递参数。
- (id)init;
构造方法特别简单,只是将enabled设为YES。
- (id)init;
{
if (!(self = [super init]))
{
return nil;
}
self.enabled = YES;
return self;
}
- 其它方法。
// 纹理使用完,解锁纹理对象
- (void)doneWithTexture;
方法很少,也比较简单。
// 纹理使用完,解锁纹理对象
- (void)doneWithTexture;
{
[firstInputFramebuffer unlock];
}
实现过程
- 通过GPUImageRawDataInput加载图片。我们将图片转换为RGBA的数据格式,然后使用GPUImageRawDataInput加载并显示。
- (void)showPicture
{
// 加载纹理
UIImage *image = [UIImage imageNamed:@"1.jpg"];
size_t width = CGImageGetWidth(image.CGImage);
size_t height = CGImageGetHeight(image.CGImage);
unsigned char *imageData = [QMImageHelper convertUIImageToBitmapRGBA8:image];
// 初始化GPUImageRawDataInput
_rawDataInput = [[GPUImageRawDataInput alloc] initWithBytes:imageData size:CGSizeMake(width, height) pixelFormat:GPUPixelFormatRGBA];
// 滤镜
GPUImageSolarizeFilter *filter = [[GPUImageSolarizeFilter alloc] init];
[_rawDataInput addTarget:filter];
[filter addTarget:_imageView];
// 开始处理数据
[_rawDataInput processData];
// 清理
if (imageData) {
free(imageData);
image = NULL;
}
}
- 通过GPUImageRawDataOutput输出RGBA数据。首先用GPUImageRawDataOutput生成RGBA原始数据,再利用libpng编码为png图片。
- (void)writeRGBADataToFile
{
// 加载纹理
UIImage *image = [UIImage imageNamed:@"2.jpg"];
size_t width = CGImageGetWidth(image.CGImage);
size_t height = CGImageGetHeight(image.CGImage);
unsigned char *imageData = [QMImageHelper convertUIImageToBitmapRGBA8:image];
// 初始化GPUImageRawDataInput
_rawDataInput = [[GPUImageRawDataInput alloc] initWithBytes:imageData size:CGSizeMake(width, height) pixelFormat:GPUPixelFormatRGBA];
// 滤镜
GPUImageSaturationFilter *filter = [[GPUImageSaturationFilter alloc] init];
filter.saturation = 0.3;
// GPUImageRawDataOutput
GPUImageRawDataOutput *rawDataOutput = [[GPUImageRawDataOutput alloc] initWithImageSize:CGSizeMake(width, height) resultsInBGRAFormat:NO];
[rawDataOutput lockFramebufferForReading];
[_rawDataInput addTarget:filter];
[filter addTarget:_imageView];
[filter addTarget:rawDataOutput];
// 开始处理数据
[_rawDataInput processData];
// 生成png图片
unsigned char *rawBytes = [rawDataOutput rawBytesForImage];
pic_data pngData = {(int)width, (int)height, 8, PNG_HAVE_ALPHA, rawBytes};
write_png_file([DOCUMENT(@"raw_data_output.png") UTF8String], &pngData);
// 清理
[rawDataOutput unlockFramebufferAfterReading];
if (imageData) {
free(imageData);
image = NULL;
}
NSLog(@"%@", DOCUMENT(@"raw_data_output.png"));
}
- 利用GPUImageTextureInput读取图片。读取图片数据,然后生成纹理对象去构造GPUImageTextureInput。
- (IBAction)textureInputButtonTapped:(UIButton *)sender
{
// 加载纹理
UIImage *image = [UIImage imageNamed:@"3.jpg"];
size_t width = CGImageGetWidth(image.CGImage);
size_t height = CGImageGetHeight(image.CGImage);
unsigned char *imageData = [QMImageHelper convertUIImageToBitmapRGBA8:image];
glGenTextures(1, &_texture);
glBindTexture(GL_TEXTURE_2D, _texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)width, (GLsizei)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
glBindTexture(GL_TEXTURE_2D, 0);
GPUImageTextureInput *textureInput = [[GPUImageTextureInput alloc] initWithTexture:_texture size:CGSizeMake(width, height)];
[textureInput addTarget:_imageView];
[textureInput processTextureWithFrameTime:kCMTimeIndefinite];
// 清理
if (imageData) {
free(imageData);
image = NULL;
}
}
- 使用GPUImageTextureOutput生成纹理对象。先由GPUImageTextureOutput生成纹理对象,然后利用OpenGL ES绘制到帧缓存对象中,最后利用帧缓存对象生成图片。
NSString *const kVertexShaderString = SHADER_STRING
(
attribute vec4 position;
attribute vec4 inputTextureCoordinate;
varying vec2 textureCoordinate;
void main()
{
gl_Position = position;
textureCoordinate = inputTextureCoordinate.xy;
}
);
NSString *const kFragmentShaderString = SHADER_STRING
(
varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
void main()
{
gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
}
);
- (IBAction)textureOutputButtonTapped:(UIButton *)sender
{
UIImage *image = [UIImage imageNamed:@"3.jpg"];
size_t width = CGImageGetWidth(image.CGImage);
size_t height = CGImageGetHeight(image.CGImage);
// GPUImagePicture
GPUImagePicture *picture = [[GPUImagePicture alloc] initWithImage:image];
// GPUImageTextureOutput
GPUImageTextureOutput *output = [[GPUImageTextureOutput alloc] init];
[picture addTarget:output];
[picture addTarget:_imageView];
[picture processImage];
// 生成图片
runSynchronouslyOnContextQueue([GPUImageContext sharedImageProcessingContext], ^{
// 设置程序
GLProgram *progam = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:kVertexShaderString fragmentShaderString:kFragmentShaderString];
[progam addAttribute:@"position"];
[progam addAttribute:@"inputTextureCoordinate"];
// 激活程序
[GPUImageContext setActiveShaderProgram:progam];
[GPUImageContext useImageProcessingContext];
// GPUImageFramebuffer
GPUImageFramebuffer *frameBuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:CGSizeMake(width, height) onlyTexture:NO];
[frameBuffer lock];
static const GLfloat imageVertices[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f,
};
static const GLfloat textureCoordinates[] = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
};
glClearColor(1.0, 1.0, 1.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, (GLsizei)width, (GLsizei)height);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, output.texture);
glUniform1i([progam uniformIndex:@"inputImageTexture"], 2);
glVertexAttribPointer([progam attributeIndex:@"position"], 2, GL_FLOAT, 0, 0, imageVertices);
glVertexAttribPointer([progam attributeIndex:@"inputTextureCoordinate"], 2, GL_FLOAT, 0, 0, textureCoordinates);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
CGImageRef outImage = [frameBuffer newCGImageFromFramebufferContents];
NSData *pngData = UIImagePNGRepresentation([UIImage imageWithCGImage:outImage]);
[pngData writeToFile:DOCUMENT(@"texture_output.png") atomically:YES];
NSLog(@"%@", DOCUMENT(@"texture_output.png"));
// unlock
[frameBuffer unlock];
[output doneWithTexture];
});
}
总结
GPUImageRawDataInput、GPUImageRawDataOutput、GPUImageTextureInput、GPUImageTextureOutput 这几个类并不是很常用,使用起来也比较简单。如果需要使用原始图片数据和纹理的时候可以考虑使用它们。
源码地址:GPUImage源码阅读系列 https://github.com/QinminiOS/GPUImage
系列文章地址:GPUImage源码阅读 http://www.jianshu.com/nb/11749791