GPUImagePicture.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. #import "GPUImagePicture.h"
  2. @implementation GPUImagePicture
  3. #pragma mark -
  4. #pragma mark Initialization and teardown
  5. - (id)initWithURL:(NSURL *)url;
  6. {
  7. NSData *imageData = [[NSData alloc] initWithContentsOfURL:url];
  8. if (!(self = [self initWithData:imageData]))
  9. {
  10. return nil;
  11. }
  12. return self;
  13. }
  14. - (id)initWithData:(NSData *)imageData;
  15. {
  16. UIImage *inputImage = [[UIImage alloc] initWithData:imageData];
  17. if (!(self = [self initWithImage:inputImage]))
  18. {
  19. return nil;
  20. }
  21. return self;
  22. }
  23. - (id)initWithImage:(UIImage *)newImageSource;
  24. {
  25. if (!(self = [self initWithImage:newImageSource smoothlyScaleOutput:NO]))
  26. {
  27. return nil;
  28. }
  29. return self;
  30. }
  31. - (id)initWithCGImage:(CGImageRef)newImageSource;
  32. {
  33. if (!(self = [self initWithCGImage:newImageSource smoothlyScaleOutput:NO]))
  34. {
  35. return nil;
  36. }
  37. return self;
  38. }
  39. - (id)initWithImage:(UIImage *)newImageSource smoothlyScaleOutput:(BOOL)smoothlyScaleOutput;
  40. {
  41. return [self initWithCGImage:[newImageSource CGImage] smoothlyScaleOutput:smoothlyScaleOutput];
  42. }
  43. - (id)initWithCGImage:(CGImageRef)newImageSource smoothlyScaleOutput:(BOOL)smoothlyScaleOutput;
  44. {
  45. if (!(self = [super init]))
  46. {
  47. return nil;
  48. }
  49. hasProcessedImage = NO;
  50. self.shouldSmoothlyScaleOutput = smoothlyScaleOutput;
  51. imageUpdateSemaphore = dispatch_semaphore_create(0);
  52. dispatch_semaphore_signal(imageUpdateSemaphore);
  53. // TODO: Dispatch this whole thing asynchronously to move image loading off main thread
  54. CGFloat widthOfImage = CGImageGetWidth(newImageSource);
  55. CGFloat heightOfImage = CGImageGetHeight(newImageSource);
  56. // If passed an empty image reference, CGContextDrawImage will fail in future versions of the SDK.
  57. NSAssert( widthOfImage > 0 && heightOfImage > 0, @"Passed image must not be empty - it should be at least 1px tall and wide");
  58. pixelSizeOfImage = CGSizeMake(widthOfImage, heightOfImage);
  59. CGSize pixelSizeToUseForTexture = pixelSizeOfImage;
  60. BOOL shouldRedrawUsingCoreGraphics = NO;
  61. // For now, deal with images larger than the maximum texture size by resizing to be within that limit
  62. CGSize scaledImageSizeToFitOnGPU = [GPUImageContext sizeThatFitsWithinATextureForSize:pixelSizeOfImage];
  63. if (!CGSizeEqualToSize(scaledImageSizeToFitOnGPU, pixelSizeOfImage))
  64. {
  65. pixelSizeOfImage = scaledImageSizeToFitOnGPU;
  66. pixelSizeToUseForTexture = pixelSizeOfImage;
  67. shouldRedrawUsingCoreGraphics = YES;
  68. }
  69. if (self.shouldSmoothlyScaleOutput)
  70. {
  71. // 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
  72. CGFloat powerClosestToWidth = ceil(log2(pixelSizeOfImage.width));
  73. CGFloat powerClosestToHeight = ceil(log2(pixelSizeOfImage.height));
  74. pixelSizeToUseForTexture = CGSizeMake(pow(2.0, powerClosestToWidth), pow(2.0, powerClosestToHeight));
  75. shouldRedrawUsingCoreGraphics = YES;
  76. }
  77. GLubyte *imageData = NULL;
  78. CFDataRef dataFromImageDataProvider = NULL;
  79. GLenum format = GL_BGRA;
  80. if (!shouldRedrawUsingCoreGraphics) {
  81. /* Check that the memory layout is compatible with GL, as we cannot use glPixelStore to
  82. * tell GL about the memory layout with GLES.
  83. */
  84. if (CGImageGetBytesPerRow(newImageSource) != CGImageGetWidth(newImageSource) * 4 ||
  85. CGImageGetBitsPerPixel(newImageSource) != 32 ||
  86. CGImageGetBitsPerComponent(newImageSource) != 8)
  87. {
  88. shouldRedrawUsingCoreGraphics = YES;
  89. } else {
  90. /* Check that the bitmap pixel format is compatible with GL */
  91. CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(newImageSource);
  92. if ((bitmapInfo & kCGBitmapFloatComponents) != 0) {
  93. /* We don't support float components for use directly in GL */
  94. shouldRedrawUsingCoreGraphics = YES;
  95. } else {
  96. CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
  97. if (byteOrderInfo == kCGBitmapByteOrder32Little) {
  98. /* Little endian, for alpha-first we can use this bitmap directly in GL */
  99. CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
  100. if (alphaInfo != kCGImageAlphaPremultipliedFirst && alphaInfo != kCGImageAlphaFirst &&
  101. alphaInfo != kCGImageAlphaNoneSkipFirst) {
  102. shouldRedrawUsingCoreGraphics = YES;
  103. }
  104. } else if (byteOrderInfo == kCGBitmapByteOrderDefault || byteOrderInfo == kCGBitmapByteOrder32Big) {
  105. /* Big endian, for alpha-last we can use this bitmap directly in GL */
  106. CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
  107. if (alphaInfo != kCGImageAlphaPremultipliedLast && alphaInfo != kCGImageAlphaLast &&
  108. alphaInfo != kCGImageAlphaNoneSkipLast) {
  109. shouldRedrawUsingCoreGraphics = YES;
  110. } else {
  111. /* Can access directly using GL_RGBA pixel format */
  112. format = GL_RGBA;
  113. }
  114. }
  115. }
  116. }
  117. }
  118. // CFAbsoluteTime elapsedTime, startTime = CFAbsoluteTimeGetCurrent();
  119. if (shouldRedrawUsingCoreGraphics)
  120. {
  121. // For resized or incompatible image: redraw
  122. imageData = (GLubyte *) calloc(1, (int)pixelSizeToUseForTexture.width * (int)pixelSizeToUseForTexture.height * 4);
  123. CGColorSpaceRef genericRGBColorspace = CGColorSpaceCreateDeviceRGB();
  124. CGContextRef imageContext = CGBitmapContextCreate(imageData, (size_t)pixelSizeToUseForTexture.width, (size_t)pixelSizeToUseForTexture.height, 8, (size_t)pixelSizeToUseForTexture.width * 4, genericRGBColorspace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
  125. // CGContextSetBlendMode(imageContext, kCGBlendModeCopy); // From Technical Q&A QA1708: http://developer.apple.com/library/ios/#qa/qa1708/_index.html
  126. CGContextDrawImage(imageContext, CGRectMake(0.0, 0.0, pixelSizeToUseForTexture.width, pixelSizeToUseForTexture.height), newImageSource);
  127. CGContextRelease(imageContext);
  128. CGColorSpaceRelease(genericRGBColorspace);
  129. }
  130. else
  131. {
  132. // Access the raw image bytes directly
  133. dataFromImageDataProvider = CGDataProviderCopyData(CGImageGetDataProvider(newImageSource));
  134. imageData = (GLubyte *)CFDataGetBytePtr(dataFromImageDataProvider);
  135. }
  136. // elapsedTime = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0;
  137. // NSLog(@"Core Graphics drawing time: %f", elapsedTime);
  138. // CGFloat currentRedTotal = 0.0f, currentGreenTotal = 0.0f, currentBlueTotal = 0.0f, currentAlphaTotal = 0.0f;
  139. // NSUInteger totalNumberOfPixels = round(pixelSizeToUseForTexture.width * pixelSizeToUseForTexture.height);
  140. //
  141. // for (NSUInteger currentPixel = 0; currentPixel < totalNumberOfPixels; currentPixel++)
  142. // {
  143. // currentBlueTotal += (CGFloat)imageData[(currentPixel * 4)] / 255.0f;
  144. // currentGreenTotal += (CGFloat)imageData[(currentPixel * 4) + 1] / 255.0f;
  145. // currentRedTotal += (CGFloat)imageData[(currentPixel * 4 + 2)] / 255.0f;
  146. // currentAlphaTotal += (CGFloat)imageData[(currentPixel * 4) + 3] / 255.0f;
  147. // }
  148. //
  149. // 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);
  150. runSynchronouslyOnVideoProcessingQueue(^{
  151. [GPUImageContext useImageProcessingContext];
  152. outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:pixelSizeToUseForTexture onlyTexture:YES];
  153. [outputFramebuffer disableReferenceCounting];
  154. glBindTexture(GL_TEXTURE_2D, [outputFramebuffer texture]);
  155. if (self.shouldSmoothlyScaleOutput)
  156. {
  157. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
  158. }
  159. // no need to use self.outputTextureOptions here since pictures need this texture formats and type
  160. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)pixelSizeToUseForTexture.width, (int)pixelSizeToUseForTexture.height, 0, format, GL_UNSIGNED_BYTE, imageData);
  161. if (self.shouldSmoothlyScaleOutput)
  162. {
  163. glGenerateMipmap(GL_TEXTURE_2D);
  164. }
  165. glBindTexture(GL_TEXTURE_2D, 0);
  166. });
  167. if (shouldRedrawUsingCoreGraphics)
  168. {
  169. free(imageData);
  170. }
  171. else
  172. {
  173. if (dataFromImageDataProvider)
  174. {
  175. CFRelease(dataFromImageDataProvider);
  176. }
  177. }
  178. return self;
  179. }
  180. // ARC forbids explicit message send of 'release'; since iOS 6 even for dispatch_release() calls: stripping it out in that case is required.
  181. - (void)dealloc;
  182. {
  183. [outputFramebuffer enableReferenceCounting];
  184. [outputFramebuffer unlock];
  185. #if ( (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0) || (!defined(__IPHONE_6_0)) )
  186. if (imageUpdateSemaphore != NULL)
  187. {
  188. dispatch_release(imageUpdateSemaphore);
  189. }
  190. #endif
  191. }
  192. #pragma mark -
  193. #pragma mark Image rendering
  194. - (void)removeAllTargets;
  195. {
  196. [super removeAllTargets];
  197. hasProcessedImage = NO;
  198. }
  199. - (void)processImage;
  200. {
  201. [self processImageWithCompletionHandler:nil];
  202. }
  203. - (BOOL)processImageWithCompletionHandler:(void (^)(void))completion;
  204. {
  205. hasProcessedImage = YES;
  206. // dispatch_semaphore_wait(imageUpdateSemaphore, DISPATCH_TIME_FOREVER);
  207. if (dispatch_semaphore_wait(imageUpdateSemaphore, DISPATCH_TIME_NOW) != 0)
  208. {
  209. return NO;
  210. }
  211. runAsynchronouslyOnVideoProcessingQueue(^{
  212. for (id<GPUImageInput> currentTarget in targets)
  213. {
  214. NSInteger indexOfObject = [targets indexOfObject:currentTarget];
  215. NSInteger textureIndexOfTarget = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];
  216. [currentTarget setCurrentlyReceivingMonochromeInput:NO];
  217. [currentTarget setInputSize:pixelSizeOfImage atIndex:textureIndexOfTarget];
  218. [currentTarget setInputFramebuffer:outputFramebuffer atIndex:textureIndexOfTarget];
  219. [currentTarget newFrameReadyAtTime:kCMTimeIndefinite atIndex:textureIndexOfTarget];
  220. }
  221. dispatch_semaphore_signal(imageUpdateSemaphore);
  222. if (completion != nil) {
  223. completion();
  224. }
  225. });
  226. return YES;
  227. }
  228. - (void)processImageUpToFilter:(GPUImageOutput<GPUImageInput> *)finalFilterInChain withCompletionHandler:(void (^)(UIImage *processedImage))block;
  229. {
  230. [finalFilterInChain useNextFrameForImageCapture];
  231. [self processImageWithCompletionHandler:^{
  232. UIImage *imageFromFilter = [finalFilterInChain imageFromCurrentFramebuffer];
  233. block(imageFromFilter);
  234. }];
  235. }
  236. - (CGSize)outputImageSize;
  237. {
  238. return pixelSizeOfImage;
  239. }
  240. - (void)addTarget:(id<GPUImageInput>)newTarget atTextureLocation:(NSInteger)textureLocation;
  241. {
  242. [super addTarget:newTarget atTextureLocation:textureLocation];
  243. if (hasProcessedImage)
  244. {
  245. [newTarget setInputSize:pixelSizeOfImage atIndex:textureLocation];
  246. [newTarget newFrameReadyAtTime:kCMTimeIndefinite atIndex:textureLocation];
  247. }
  248. }
  249. @end