GPUImage源码阅读(六)

概述

GPUImage是一个著名的图像处理开源库,它让你能够在图片、视频、相机上使用GPU加速的滤镜和其它特效。与CoreImage框架相比,可以根据GPUImage提供的接口,使用自定义的滤镜。项目地址:https://github.com/BradLarson/GPUImage
这篇文章主要是阅读GPUImage框架中的GPUImageRawDataInput、GPUImageRawDataOutput、GPUImageTextureInput、GPUImageTextureOutput 这几个类的源码。之前介绍过数据来自相机、图片、音视频文件、UI渲染,这里还会介绍其它两种:RGBA原始数据(包括:RGB、RGBA、BGRA、LUMINANCE)和纹理对象。以下是源码内容:
GPUImageRawDataInput
GPUImageRawDataOutput
GPUImageTextureInput
GPUImageTextureOutput

实现效果

读取RGBA.png
生成RGBA.png
纹理输入.png
纹理输出.png

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<GPUImageInput> 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<GPUImageInput> 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<GPUImageInput> 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<GPUImageTextureOutputDelegate> 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,158评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,600评论 1 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,785评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,655评论 0 220
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,075评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,002评论 1 225
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,146评论 2 318
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,918评论 0 211
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,671评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,838评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,318评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,636评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,343评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,187评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,982评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,126评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,934评论 2 279

推荐阅读更多精彩内容