GPUImagePicture class for GPUImage API documentation, gpuimage
GPUImagePicture is a type of static image processing operation. It can be a static image to be processed, or an image used as a texture. It is called to send a processImage message to it for image filter processing.
Method
-(Id) initWithURL :( NSURL *) url
Initialize GPUImagePicture with images of the specified url
-(Id) initWithImage :( UIImage *) newImageSource
Note: Use the specified UIImage object to initialize GPUImagePicture.
-(Id) initWithCGImage :( CGImageRef) newImageSource
Note: Use the specified CGImageRef object to initialize GPUImagePicture.
-(Id) initWithImage :( UIImage *) newImageSource smoothlyScaleOutput :( BOOL) smoothlyScaleOutput
Note: Use the specified UIImage object to initialize GPUImagePicture. Do you want to adjust the size of the input image proportionally?
-(Void) processImage
Note: perform image processing operations.
-(BOOL) processImageWithCompletionHandler :( void (^) (void) completion
Note: The actual operation of image processing is completed when the processing is completed.
Complete code
#import <UIKit/UIKit.h>#import "GPUImageOutput.h"@interface GPUImagePicture : GPUImageOutput{ CGSize pixelSizeOfImage; BOOL hasProcessedImage; dispatch_semaphore_t imageUpdateSemaphore;}// Initialization and teardown- (id)initWithURL:(NSURL *)url;- (id)initWithImage:(UIImage *)newImageSource;- (id)initWithCGImage:(CGImageRef)newImageSource;- (id)initWithImage:(UIImage *)newImageSource smoothlyScaleOutput:(BOOL)smoothlyScaleOutput;- (id)initWithCGImage:(CGImageRef)newImageSource smoothlyScaleOutput:(BOOL)smoothlyScaleOutput;// Image rendering- (void)processImage;- (CGSize)outputImageSize;/** * Process image with all targets and filters asynchronously * The completion handler is called after processing finished in the * GPU's dispatch queue - and only if this method did not return NO. * * @returns NO if resource is blocked and processing is discarded, YES otherwise */- (BOOL)processImageWithCompletionHandler:(void (^)(void))completion;- (void)processImageUpToFilter:(GPUImageOutput<GPUImageInput> *)finalFilterInChain withCompletionHandler:(void (^)(UIImage *processedImage))block;@end
#import "GPUImagePicture.h"@implementation GPUImagePicture#pragma mark -#pragma mark Initialization and teardown- (id)initWithURL:(NSURL *)url;{ NSData *imageData = [[NSData alloc] initWithContentsOfURL:url]; if (!(self = [self initWithData:imageData])) { return nil; } return self;}- (id)initWithData:(NSData *)imageData;{ UIImage *inputImage = [[UIImage alloc] initWithData:imageData]; if (!(self = [self initWithImage:inputImage])) { return nil; } return self;}- (id)initWithImage:(UIImage *)newImageSource;{ if (!(self = [self initWithImage:newImageSource smoothlyScaleOutput:NO])) { return nil; } return self;}- (id)initWithCGImage:(CGImageRef)newImageSource;{ if (!(self = [self initWithCGImage:newImageSource smoothlyScaleOutput:NO])) { return nil; } return self;}- (id)initWithImage:(UIImage *)newImageSource smoothlyScaleOutput:(BOOL)smoothlyScaleOutput;{ return [self initWithCGImage:[newImageSource CGImage] smoothlyScaleOutput:smoothlyScaleOutput];}- (id)initWithCGImage:(CGImageRef)newImageSource smoothlyScaleOutput:(BOOL)smoothlyScaleOutput;{ if (!(self = [super init])) { return nil; } hasProcessedImage = NO; self.shouldSmoothlyScaleOutput = smoothlyScaleOutput; imageUpdateSemaphore = dispatch_semaphore_create(0); dispatch_semaphore_signal(imageUpdateSemaphore); // TODO: Dispatch this whole thing asynchronously to move image loading off main thread CGFloat widthOfImage = CGImageGetWidth(newImageSource); CGFloat heightOfImage = CGImageGetHeight(newImageSource); // If passed an empty image reference, CGContextDrawImage will fail in future versions of the SDK. NSAssert( widthOfImage > 0 && heightOfImage > 0, @"Passed image must not be empty - it should be at least 1px tall and wide"); pixelSizeOfImage = CGSizeMake(widthOfImage, heightOfImage); CGSize pixelSizeToUseForTexture = pixelSizeOfImage; BOOL shouldRedrawUsingCoreGraphics = NO; // For now, deal with images larger than the maximum texture size by resizing to be within that limit CGSize scaledImageSizeToFitOnGPU = [GPUImageContext sizeThatFitsWithinATextureForSize:pixelSizeOfImage]; if (!CGSizeEqualToSize(scaledImageSizeToFitOnGPU, pixelSizeOfImage)) { pixelSizeOfImage = scaledImageSizeToFitOnGPU; pixelSizeToUseForTexture = pixelSizeOfImage; shouldRedrawUsingCoreGraphics = YES; } if (self.shouldSmoothlyScaleOutput) { // In order to use mipmaps, you need to provide power-of-two textures, so convert to the next largest power of two and stretch to fill CGFloat powerClosestToWidth = ceil(log2(pixelSizeOfImage.width)); CGFloat powerClosestToHeight = ceil(log2(pixelSizeOfImage.height)); pixelSizeToUseForTexture = CGSizeMake(pow(2.0, powerClosestToWidth), pow(2.0, powerClosestToHeight)); shouldRedrawUsingCoreGraphics = YES; } GLubyte *imageData = NULL; CFDataRef dataFromImageDataProvider = NULL; GLenum format = GL_BGRA; if (!shouldRedrawUsingCoreGraphics) { /* Check that the memory layout is compatible with GL, as we cannot use glPixelStore to * tell GL about the memory layout with GLES. */ if (CGImageGetBytesPerRow(newImageSource) != CGImageGetWidth(newImageSource) * 4 || CGImageGetBitsPerPixel(newImageSource) != 32 || CGImageGetBitsPerComponent(newImageSource) != 8) { shouldRedrawUsingCoreGraphics = YES; } else { /* Check that the bitmap pixel format is compatible with GL */ CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(newImageSource); if ((bitmapInfo & kCGBitmapFloatComponents) != 0) { /* We don't support float components for use directly in GL */ shouldRedrawUsingCoreGraphics = YES; } else { CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask; if (byteOrderInfo == kCGBitmapByteOrder32Little) { /* Little endian, for alpha-first we can use this bitmap directly in GL */ CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask; if (alphaInfo != kCGImageAlphaPremultipliedFirst && alphaInfo != kCGImageAlphaFirst && alphaInfo != kCGImageAlphaNoneSkipFirst) { shouldRedrawUsingCoreGraphics = YES; } } else if (byteOrderInfo == kCGBitmapByteOrderDefault || byteOrderInfo == kCGBitmapByteOrder32Big) { /* Big endian, for alpha-last we can use this bitmap directly in GL */ CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask; if (alphaInfo != kCGImageAlphaPremultipliedLast && alphaInfo != kCGImageAlphaLast && alphaInfo != kCGImageAlphaNoneSkipLast) { shouldRedrawUsingCoreGraphics = YES; } else { /* Can access directly using GL_RGBA pixel format */ format = GL_RGBA; } } } } } // CFAbsoluteTime elapsedTime, startTime = CFAbsoluteTimeGetCurrent(); if (shouldRedrawUsingCoreGraphics) { // For resized or incompatible image: redraw imageData = (GLubyte *) calloc(1, (int)pixelSizeToUseForTexture.width * (int)pixelSizeToUseForTexture.height * 4); CGColorSpaceRef genericRGBColorspace = CGColorSpaceCreateDeviceRGB(); CGContextRef imageContext = CGBitmapContextCreate(imageData, (size_t)pixelSizeToUseForTexture.width, (size_t)pixelSizeToUseForTexture.height, 8, (size_t)pixelSizeToUseForTexture.width * 4, genericRGBColorspace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); // CGContextSetBlendMode(imageContext, kCGBlendModeCopy); // From Technical Q&A QA1708: http://developer.apple.com/library/ios/#qa/qa1708/_index.html CGContextDrawImage(imageContext, CGRectMake(0.0, 0.0, pixelSizeToUseForTexture.width, pixelSizeToUseForTexture.height), newImageSource); CGContextRelease(imageContext); CGColorSpaceRelease(genericRGBColorspace); } else { // Access the raw image bytes directly dataFromImageDataProvider = CGDataProviderCopyData(CGImageGetDataProvider(newImageSource)); imageData = (GLubyte *)CFDataGetBytePtr(dataFromImageDataProvider); } // elapsedTime = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0; // NSLog(@"Core Graphics drawing time: %f", elapsedTime); // CGFloat currentRedTotal = 0.0f, currentGreenTotal = 0.0f, currentBlueTotal = 0.0f, currentAlphaTotal = 0.0f; // NSUInteger totalNumberOfPixels = round(pixelSizeToUseForTexture.width * pixelSizeToUseForTexture.height); // // for (NSUInteger currentPixel = 0; currentPixel < totalNumberOfPixels; currentPixel++) // { // currentBlueTotal += (CGFloat)imageData[(currentPixel * 4)] / 255.0f; // currentGreenTotal += (CGFloat)imageData[(currentPixel * 4) + 1] / 255.0f; // currentRedTotal += (CGFloat)imageData[(currentPixel * 4 + 2)] / 255.0f; // currentAlphaTotal += (CGFloat)imageData[(currentPixel * 4) + 3] / 255.0f; // } // // NSLog(@"Debug, average input image red: %f, green: %f, blue: %f, alpha: %f", currentRedTotal / (CGFloat)totalNumberOfPixels, currentGreenTotal / (CGFloat)totalNumberOfPixels, currentBlueTotal / (CGFloat)totalNumberOfPixels, currentAlphaTotal / (CGFloat)totalNumberOfPixels); runSynchronouslyOnVideoProcessingQueue(^{ [GPUImageContext useImageProcessingContext]; outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:pixelSizeToUseForTexture onlyTexture:YES]; [outputFramebuffer disableReferenceCounting]; glBindTexture(GL_TEXTURE_2D, [outputFramebuffer texture]); if (self.shouldSmoothlyScaleOutput) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); } // no need to use self.outputTextureOptions here since pictures need this texture formats and type glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)pixelSizeToUseForTexture.width, (int)pixelSizeToUseForTexture.height, 0, format, GL_UNSIGNED_BYTE, imageData); if (self.shouldSmoothlyScaleOutput) { glGenerateMipmap(GL_TEXTURE_2D); } glBindTexture(GL_TEXTURE_2D, 0); }); if (shouldRedrawUsingCoreGraphics) { free(imageData); } else { if (dataFromImageDataProvider) { CFRelease(dataFromImageDataProvider); } } return self;}// ARC forbids explicit message send of 'release'; since iOS 6 even for dispatch_release() calls: stripping it out in that case is required.- (void)dealloc;{ [outputFramebuffer enableReferenceCounting]; [outputFramebuffer unlock];#if !OS_OBJECT_USE_OBJC if (imageUpdateSemaphore != NULL) { dispatch_release(imageUpdateSemaphore); }#endif}#pragma mark -#pragma mark Image rendering- (void)removeAllTargets;{ [super removeAllTargets]; hasProcessedImage = NO;}- (void)processImage;{ [self processImageWithCompletionHandler:nil];}- (BOOL)processImageWithCompletionHandler:(void (^)(void))completion;{ hasProcessedImage = YES; // dispatch_semaphore_wait(imageUpdateSemaphore, DISPATCH_TIME_FOREVER); if (dispatch_semaphore_wait(imageUpdateSemaphore, DISPATCH_TIME_NOW) != 0) { return NO; } runAsynchronouslyOnVideoProcessingQueue(^{ for (id<GPUImageInput> currentTarget in targets) { NSInteger indexOfObject = [targets indexOfObject:currentTarget]; NSInteger textureIndexOfTarget = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue]; [currentTarget setCurrentlyReceivingMonochromeInput:NO]; [currentTarget setInputSize:pixelSizeOfImage atIndex:textureIndexOfTarget]; [currentTarget setInputFramebuffer:outputFramebuffer atIndex:textureIndexOfTarget]; [currentTarget newFrameReadyAtTime:kCMTimeIndefinite atIndex:textureIndexOfTarget]; } dispatch_semaphore_signal(imageUpdateSemaphore); if (completion != nil) { completion(); } }); return YES;}- (void)processImageUpToFilter:(GPUImageOutput<GPUImageInput> *)finalFilterInChain withCompletionHandler:(void (^)(UIImage *processedImage))block;{ [finalFilterInChain useNextFrameForImageCapture]; [self processImageWithCompletionHandler:^{ UIImage *imageFromFilter = [finalFilterInChain imageFromCurrentFramebuffer]; block(imageFromFilter); }];}- (CGSize)outputImageSize;{ return pixelSizeOfImage;}- (void)addTarget:(id<GPUImageInput>)newTarget atTextureLocation:(NSInteger)textureLocation;{ [super addTarget:newTarget atTextureLocation:textureLocation]; if (hasProcessedImage) { [newTarget setInputSize:pixelSizeOfImage atIndex:textureLocation]; [newTarget newFrameReadyAtTime:kCMTimeIndefinite atIndex:textureLocation]; }}@end