123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- #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 ( (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0) || (!defined(__IPHONE_6_0)) )
- 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
|