CGImage+WebP.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. //
  2. // CGImage+WebP.m
  3. // Pods
  4. //
  5. // Created by yeatse on 2016/10/20.
  6. //
  7. //
  8. #import "CGImage+WebP.h"
  9. #import <Accelerate/Accelerate.h>
  10. #import <CoreFoundation/CoreFoundation.h>
  11. #if __has_include("webp/decode.h") && __has_include("webp/encode.h") && __has_include("webp/demux.h") && __has_include("webp/mux.h")
  12. #import "webp/decode.h"
  13. #import "webp/encode.h"
  14. #import "webp/demux.h"
  15. #import "webp/mux.h"
  16. #elif __has_include(<libwebp/decode.h>) && __has_include(<libwebp/encode.h>) && __has_include(<libwebp/demux.h>) && __has_include(<libwebp/mux.h>)
  17. #import <libwebp/decode.h>
  18. #import <libwebp/encode.h>
  19. #import <libwebp/demux.h>
  20. #import <libwebp/mux.h>
  21. #else
  22. @import libwebp;
  23. #endif
  24. #pragma mark - Helper Functions
  25. static void WebPFreeInfoReleaseDataCallback(void *info, const void *data, size_t size) {
  26. if (info) {
  27. free(info);
  28. }
  29. }
  30. static CGColorSpaceRef WebPColorSpaceForDeviceRGB() {
  31. static CGColorSpaceRef colorSpace;
  32. static dispatch_once_t onceToken;
  33. dispatch_once(&onceToken, ^{
  34. colorSpace = CGColorSpaceCreateDeviceRGB();
  35. });
  36. return colorSpace;
  37. }
  38. /**
  39. Decode an image to bitmap buffer with the specified format.
  40. @param srcImage Source image.
  41. @param dest Destination buffer. It should be zero before call this method.
  42. If decode succeed, you should release the dest->data using free().
  43. @param destFormat Destination bitmap format.
  44. @return Whether succeed.
  45. @warning This method support iOS7.0 and later. If call it on iOS6, it just returns NO.
  46. CG_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0)
  47. */
  48. static BOOL WebPCGImageDecodeToBitmapBufferWithAnyFormat(CGImageRef srcImage, vImage_Buffer *dest, vImage_CGImageFormat *destFormat) {
  49. if (!srcImage || (((long)vImageConvert_AnyToAny) + 1 == 1) || !destFormat || !dest) return NO;
  50. size_t width = CGImageGetWidth(srcImage);
  51. size_t height = CGImageGetHeight(srcImage);
  52. if (width == 0 || height == 0) return NO;
  53. dest->data = NULL;
  54. vImage_Error error = kvImageNoError;
  55. CFDataRef srcData = NULL;
  56. vImageConverterRef convertor = NULL;
  57. vImage_CGImageFormat srcFormat = {0};
  58. srcFormat.bitsPerComponent = (uint32_t)CGImageGetBitsPerComponent(srcImage);
  59. srcFormat.bitsPerPixel = (uint32_t)CGImageGetBitsPerPixel(srcImage);
  60. srcFormat.colorSpace = CGImageGetColorSpace(srcImage);
  61. srcFormat.bitmapInfo = CGImageGetBitmapInfo(srcImage) | CGImageGetAlphaInfo(srcImage);
  62. convertor = vImageConverter_CreateWithCGImageFormat(&srcFormat, destFormat, NULL, kvImageNoFlags, NULL);
  63. if (!convertor) goto fail;
  64. CGDataProviderRef srcProvider = CGImageGetDataProvider(srcImage);
  65. srcData = srcProvider ? CGDataProviderCopyData(srcProvider) : NULL; // decode
  66. size_t srcLength = srcData ? CFDataGetLength(srcData) : 0;
  67. const void *srcBytes = srcData ? CFDataGetBytePtr(srcData) : NULL;
  68. if (srcLength == 0 || !srcBytes) goto fail;
  69. vImage_Buffer src = {0};
  70. src.data = (void *)srcBytes;
  71. src.width = width;
  72. src.height = height;
  73. src.rowBytes = CGImageGetBytesPerRow(srcImage);
  74. error = vImageBuffer_Init(dest, height, width, 32, kvImageNoFlags);
  75. if (error != kvImageNoError) goto fail;
  76. error = vImageConvert_AnyToAny(convertor, &src, dest, NULL, kvImageNoFlags); // convert
  77. if (error != kvImageNoError) goto fail;
  78. CFRelease(convertor);
  79. CFRelease(srcData);
  80. return YES;
  81. fail:
  82. if (convertor) CFRelease(convertor);
  83. if (srcData) CFRelease(srcData);
  84. if (dest->data) free(dest->data);
  85. dest->data = NULL;
  86. return NO;
  87. }
  88. /**
  89. Decode an image to bitmap buffer with the 32bit format (such as ARGB8888).
  90. @param srcImage Source image.
  91. @param dest Destination buffer. It should be zero before call this method.
  92. If decode succeed, you should release the dest->data using free().
  93. @param bitmapInfo Destination bitmap format.
  94. @return Whether succeed.
  95. */
  96. static BOOL WebPCGImageDecodeToBitmapBufferWith32BitFormat(CGImageRef srcImage, vImage_Buffer *dest, CGBitmapInfo bitmapInfo) {
  97. if (!srcImage || !dest) return NO;
  98. size_t width = CGImageGetWidth(srcImage);
  99. size_t height = CGImageGetHeight(srcImage);
  100. if (width == 0 || height == 0) return NO;
  101. BOOL hasAlpha = NO;
  102. BOOL alphaFirst = NO;
  103. BOOL alphaPremultiplied = NO;
  104. BOOL byteOrderNormal = NO;
  105. switch (bitmapInfo & kCGBitmapAlphaInfoMask) {
  106. case kCGImageAlphaPremultipliedLast: {
  107. hasAlpha = YES;
  108. alphaPremultiplied = YES;
  109. } break;
  110. case kCGImageAlphaPremultipliedFirst: {
  111. hasAlpha = YES;
  112. alphaPremultiplied = YES;
  113. alphaFirst = YES;
  114. } break;
  115. case kCGImageAlphaLast: {
  116. hasAlpha = YES;
  117. } break;
  118. case kCGImageAlphaFirst: {
  119. hasAlpha = YES;
  120. alphaFirst = YES;
  121. } break;
  122. case kCGImageAlphaNoneSkipLast: {
  123. } break;
  124. case kCGImageAlphaNoneSkipFirst: {
  125. alphaFirst = YES;
  126. } break;
  127. default: {
  128. return NO;
  129. } break;
  130. }
  131. switch (bitmapInfo & kCGBitmapByteOrderMask) {
  132. case kCGBitmapByteOrderDefault: {
  133. byteOrderNormal = YES;
  134. } break;
  135. case kCGBitmapByteOrder32Little: {
  136. } break;
  137. case kCGBitmapByteOrder32Big: {
  138. byteOrderNormal = YES;
  139. } break;
  140. default: {
  141. return NO;
  142. } break;
  143. }
  144. /*
  145. Try convert with vImageConvert_AnyToAny() (avaliable since iOS 7.0).
  146. If fail, try decode with CGContextDrawImage().
  147. CGBitmapContext use a premultiplied alpha format, unpremultiply may lose precision.
  148. */
  149. vImage_CGImageFormat destFormat = {0};
  150. destFormat.bitsPerComponent = 8;
  151. destFormat.bitsPerPixel = 32;
  152. destFormat.colorSpace = WebPColorSpaceForDeviceRGB();
  153. destFormat.bitmapInfo = bitmapInfo;
  154. dest->data = NULL;
  155. if (WebPCGImageDecodeToBitmapBufferWithAnyFormat(srcImage, dest, &destFormat)) return YES;
  156. CGBitmapInfo contextBitmapInfo = bitmapInfo & kCGBitmapByteOrderMask;
  157. if (!hasAlpha || alphaPremultiplied) {
  158. contextBitmapInfo |= (bitmapInfo & kCGBitmapAlphaInfoMask);
  159. } else {
  160. contextBitmapInfo |= alphaFirst ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaPremultipliedLast;
  161. }
  162. CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, WebPColorSpaceForDeviceRGB(), contextBitmapInfo);
  163. if (!context) goto fail;
  164. CGContextDrawImage(context, CGRectMake(0, 0, width, height), srcImage); // decode and convert
  165. size_t bytesPerRow = CGBitmapContextGetBytesPerRow(context);
  166. size_t length = height * bytesPerRow;
  167. void *data = CGBitmapContextGetData(context);
  168. if (length == 0 || !data) goto fail;
  169. dest->data = malloc(length);
  170. dest->width = width;
  171. dest->height = height;
  172. dest->rowBytes = bytesPerRow;
  173. if (!dest->data) goto fail;
  174. if (hasAlpha && !alphaPremultiplied) {
  175. vImage_Buffer tmpSrc = {0};
  176. tmpSrc.data = data;
  177. tmpSrc.width = width;
  178. tmpSrc.height = height;
  179. tmpSrc.rowBytes = bytesPerRow;
  180. vImage_Error error;
  181. if (alphaFirst && byteOrderNormal) {
  182. error = vImageUnpremultiplyData_ARGB8888(&tmpSrc, dest, kvImageNoFlags);
  183. } else {
  184. error = vImageUnpremultiplyData_RGBA8888(&tmpSrc, dest, kvImageNoFlags);
  185. }
  186. if (error != kvImageNoError) goto fail;
  187. } else {
  188. memcpy(dest->data, data, length);
  189. }
  190. CFRelease(context);
  191. return YES;
  192. fail:
  193. if (context) CFRelease(context);
  194. if (dest->data) free(dest->data);
  195. dest->data = NULL;
  196. return NO;
  197. return NO;
  198. }
  199. static int WebPPictureImportCGImage(WebPPicture *picture, CGImageRef image) {
  200. vImage_Buffer buffer = {0};
  201. int result = 0;
  202. if (WebPCGImageDecodeToBitmapBufferWith32BitFormat(image, &buffer, kCGImageAlphaLast | kCGBitmapByteOrderDefault)) {
  203. picture->width = (int)buffer.width;
  204. picture->height = (int)buffer.height;
  205. picture->use_argb = 1;
  206. result = WebPPictureImportRGBA(picture, buffer.data, (int)buffer.rowBytes);
  207. free(buffer.data);
  208. }
  209. return result;
  210. }
  211. #pragma mark - Still Images
  212. CGImageRef WebPImageCreateWithData(CFDataRef webpData) {
  213. WebPData webp_data;
  214. WebPDataInit(&webp_data);
  215. webp_data.bytes = CFDataGetBytePtr(webpData);
  216. webp_data.size = CFDataGetLength(webpData);
  217. WebPAnimDecoderOptions dec_options;
  218. WebPAnimDecoderOptionsInit(&dec_options);
  219. dec_options.use_threads = 1;
  220. dec_options.color_mode = MODE_rgbA;
  221. WebPAnimDecoder *dec = WebPAnimDecoderNew(&webp_data, &dec_options);
  222. if (!dec) {
  223. return NULL;
  224. }
  225. WebPAnimInfo anim_info;
  226. uint8_t *buf;
  227. int timestamp;
  228. if (!WebPAnimDecoderGetInfo(dec, &anim_info) || !WebPAnimDecoderGetNext(dec, &buf, &timestamp)) {
  229. WebPAnimDecoderDelete(dec);
  230. return NULL;
  231. }
  232. const size_t bufSize = anim_info.canvas_width * 4 * anim_info.canvas_height;
  233. void *bufCopy = malloc(bufSize);
  234. memcpy(bufCopy, buf, bufSize);
  235. WebPAnimDecoderDelete(dec);
  236. CGDataProviderRef provider = CGDataProviderCreateWithData(bufCopy, bufCopy, bufSize, WebPFreeInfoReleaseDataCallback);
  237. CGImageRef image = CGImageCreate(anim_info.canvas_width, anim_info.canvas_height, 8, 32, anim_info.canvas_width * 4, WebPColorSpaceForDeviceRGB(), kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault, provider, NULL, false, kCGRenderingIntentDefault);
  238. CGDataProviderRelease(provider);
  239. return image;
  240. }
  241. CFDataRef WebPDataCreateWithImage(CGImageRef image, bool isLossy, float quality) {
  242. WebPConfig config;
  243. WebPConfigInit(&config);
  244. if (isLossy) {
  245. WebPConfigPreset(&config, WEBP_PRESET_DEFAULT, quality);
  246. } else {
  247. WebPConfigLosslessPreset(&config, 0);
  248. }
  249. WebPPicture picture;
  250. WebPPictureInit(&picture);
  251. WebPMemoryWriter writer;
  252. WebPMemoryWriterInit(&writer);
  253. picture.writer = WebPMemoryWrite;
  254. picture.custom_ptr = &writer;
  255. if (!(WebPPictureImportCGImage(&picture, image))) {
  256. goto fail;
  257. }
  258. if (!WebPEncode(&config, &picture)) {
  259. goto fail;
  260. }
  261. CFDataRef data = CFDataCreate(kCFAllocatorDefault, writer.mem, writer.size);
  262. WebPMemoryWriterClear(&writer);
  263. WebPPictureFree(&picture);
  264. return data;
  265. fail:
  266. WebPMemoryWriterClear(&writer);
  267. WebPPictureFree(&picture);
  268. return NULL;
  269. }
  270. #pragma mark - Animated Images
  271. const CFStringRef kWebPAnimatedImageDuration = CFSTR("kWebPAnimatedImageDuration");
  272. const CFStringRef kWebPAnimatedImageLoopCount = CFSTR("kWebPAnimatedImageLoopCount");
  273. const CFStringRef kWebPAnimatedImageFrames = CFSTR("kWebPAnimatedImageFrames");
  274. uint32_t WebPImageFrameCountGetFromData(CFDataRef webpData) {
  275. WebPData webp_data;
  276. WebPDataInit(&webp_data);
  277. webp_data.bytes = CFDataGetBytePtr(webpData);
  278. webp_data.size = CFDataGetLength(webpData);
  279. WebPDemuxer *dmux = WebPDemux(&webp_data);
  280. if (!dmux) {
  281. return 0;
  282. }
  283. uint32_t frameCount = WebPDemuxGetI(dmux, WEBP_FF_FRAME_COUNT);
  284. WebPDemuxDelete(dmux);
  285. return frameCount;
  286. }
  287. CFDictionaryRef WebPAnimatedImageInfoCreateWithData(CFDataRef webpData) {
  288. WebPData webp_data;
  289. WebPDataInit(&webp_data);
  290. webp_data.bytes = CFDataGetBytePtr(webpData);
  291. webp_data.size = CFDataGetLength(webpData);
  292. WebPAnimDecoderOptions dec_options;
  293. WebPAnimDecoderOptionsInit(&dec_options);
  294. dec_options.use_threads = 1;
  295. dec_options.color_mode = MODE_rgbA;
  296. WebPAnimDecoder *dec = WebPAnimDecoderNew(&webp_data, &dec_options);
  297. if (!dec) {
  298. return NULL;
  299. }
  300. WebPAnimInfo anim_info;
  301. if (!WebPAnimDecoderGetInfo(dec, &anim_info)) {
  302. WebPAnimDecoderDelete(dec);
  303. return NULL;
  304. }
  305. CFMutableDictionaryRef imageInfo = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
  306. CFMutableArrayRef imageFrames = CFArrayCreateMutable(kCFAllocatorDefault, anim_info.frame_count, &kCFTypeArrayCallBacks);
  307. int duration = 0;
  308. while (WebPAnimDecoderHasMoreFrames(dec)) {
  309. uint8_t *buf;
  310. if (!WebPAnimDecoderGetNext(dec, &buf, &duration)) {
  311. break;
  312. }
  313. const size_t bufSize = anim_info.canvas_width * 4 * anim_info.canvas_height;
  314. void *bufCopy = malloc(bufSize);
  315. memcpy(bufCopy, buf, bufSize);
  316. CGDataProviderRef provider = CGDataProviderCreateWithData(bufCopy, bufCopy, bufSize, WebPFreeInfoReleaseDataCallback);
  317. CGImageRef image = CGImageCreate(anim_info.canvas_width, anim_info.canvas_height, 8, 32, anim_info.canvas_width * 4, WebPColorSpaceForDeviceRGB(), kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault, provider, NULL, false, kCGRenderingIntentDefault);
  318. CFArrayAppendValue(imageFrames, image);
  319. CGImageRelease(image);
  320. CGDataProviderRelease(provider);
  321. }
  322. // add last frame's duration
  323. const WebPDemuxer *dmux = WebPAnimDecoderGetDemuxer(dec);
  324. WebPIterator iter;
  325. if (WebPDemuxGetFrame(dmux, 0, &iter)) {
  326. duration += iter.duration;
  327. WebPDemuxReleaseIterator(&iter);
  328. }
  329. WebPAnimDecoderDelete(dec);
  330. CFDictionarySetValue(imageInfo, kWebPAnimatedImageFrames, imageFrames);
  331. CFRelease(imageFrames);
  332. CFNumberRef loopCount = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &anim_info.loop_count);
  333. CFDictionarySetValue(imageInfo, kWebPAnimatedImageLoopCount, loopCount);
  334. CFRelease(loopCount);
  335. double durationInSec = ((double)duration) / 1000;
  336. CFNumberRef durationRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &durationInSec);
  337. CFDictionarySetValue(imageInfo, kWebPAnimatedImageDuration, durationRef);
  338. CFRelease(durationRef);
  339. return imageInfo;
  340. }
  341. CFDataRef WebPDataCreateWithAnimatedImageInfo(CFDictionaryRef imageInfo, bool isLossy, float quality) {
  342. CFNumberRef loopCount = CFDictionaryGetValue(imageInfo, kWebPAnimatedImageLoopCount);
  343. CFNumberRef durationRef = CFDictionaryGetValue(imageInfo, kWebPAnimatedImageDuration);
  344. CFArrayRef imageFrames = CFDictionaryGetValue(imageInfo, kWebPAnimatedImageFrames);
  345. if (!imageFrames || CFArrayGetCount(imageFrames) < 1) {
  346. return NULL;
  347. }
  348. WebPAnimEncoderOptions enc_options;
  349. WebPAnimEncoderOptionsInit(&enc_options);
  350. if (loopCount) {
  351. CFNumberGetValue(loopCount, kCFNumberSInt32Type, &enc_options.anim_params.loop_count);
  352. }
  353. CGImageRef firstImage = (CGImageRef)CFArrayGetValueAtIndex(imageFrames, 0);
  354. WebPAnimEncoder *enc = WebPAnimEncoderNew((int)CGImageGetWidth(firstImage), (int)CGImageGetHeight(firstImage), &enc_options);
  355. if (!enc) {
  356. return NULL;
  357. }
  358. int frameDurationInMilliSec = 100;
  359. if (durationRef) {
  360. double totalDurationInSec;
  361. CFNumberGetValue(durationRef, kCFNumberDoubleType, &totalDurationInSec);
  362. frameDurationInMilliSec = (int)(totalDurationInSec * 1000 / CFArrayGetCount(imageFrames));
  363. }
  364. for (CFIndex i = 0; i < CFArrayGetCount(imageFrames); i ++) {
  365. WebPPicture frame;
  366. WebPPictureInit(&frame);
  367. if (WebPPictureImportCGImage(&frame, (CGImageRef)CFArrayGetValueAtIndex(imageFrames, i))) {
  368. WebPConfig config;
  369. WebPConfigInit(&config);
  370. if (isLossy) {
  371. WebPConfigPreset(&config, WEBP_PRESET_DEFAULT, quality);
  372. } else {
  373. WebPConfigLosslessPreset(&config, 0);
  374. }
  375. WebPAnimEncoderAdd(enc, &frame, (int)(frameDurationInMilliSec * i), &config);
  376. }
  377. WebPPictureFree(&frame);
  378. }
  379. WebPAnimEncoderAdd(enc, NULL, (int)(frameDurationInMilliSec * CFArrayGetCount(imageFrames)), NULL);
  380. WebPData webp_data;
  381. WebPDataInit(&webp_data);
  382. WebPAnimEncoderAssemble(enc, &webp_data);
  383. WebPAnimEncoderDelete(enc);
  384. CFDataRef data = CFDataCreate(kCFAllocatorDefault, webp_data.bytes, webp_data.size);
  385. WebPDataClear(&webp_data);
  386. return data;
  387. }