123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- //
- // CGImage+WebP.m
- // Pods
- //
- // Created by yeatse on 2016/10/20.
- //
- //
- #import "CGImage+WebP.h"
- #import <Accelerate/Accelerate.h>
- #import <CoreFoundation/CoreFoundation.h>
- #if __has_include("webp/decode.h") && __has_include("webp/encode.h") && __has_include("webp/demux.h") && __has_include("webp/mux.h")
- #import "webp/decode.h"
- #import "webp/encode.h"
- #import "webp/demux.h"
- #import "webp/mux.h"
- #elif __has_include(<libwebp/decode.h>) && __has_include(<libwebp/encode.h>) && __has_include(<libwebp/demux.h>) && __has_include(<libwebp/mux.h>)
- #import <libwebp/decode.h>
- #import <libwebp/encode.h>
- #import <libwebp/demux.h>
- #import <libwebp/mux.h>
- #else
- @import libwebp;
- #endif
- #pragma mark - Helper Functions
- static void WebPFreeInfoReleaseDataCallback(void *info, const void *data, size_t size) {
- if (info) {
- free(info);
- }
- }
- static CGColorSpaceRef WebPColorSpaceForDeviceRGB() {
- static CGColorSpaceRef colorSpace;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- colorSpace = CGColorSpaceCreateDeviceRGB();
- });
- return colorSpace;
- }
- /**
- Decode an image to bitmap buffer with the specified format.
-
- @param srcImage Source image.
- @param dest Destination buffer. It should be zero before call this method.
- If decode succeed, you should release the dest->data using free().
- @param destFormat Destination bitmap format.
-
- @return Whether succeed.
-
- @warning This method support iOS7.0 and later. If call it on iOS6, it just returns NO.
- CG_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0)
- */
- static BOOL WebPCGImageDecodeToBitmapBufferWithAnyFormat(CGImageRef srcImage, vImage_Buffer *dest, vImage_CGImageFormat *destFormat) {
- if (!srcImage || (((long)vImageConvert_AnyToAny) + 1 == 1) || !destFormat || !dest) return NO;
- size_t width = CGImageGetWidth(srcImage);
- size_t height = CGImageGetHeight(srcImage);
- if (width == 0 || height == 0) return NO;
- dest->data = NULL;
-
- vImage_Error error = kvImageNoError;
- CFDataRef srcData = NULL;
- vImageConverterRef convertor = NULL;
- vImage_CGImageFormat srcFormat = {0};
- srcFormat.bitsPerComponent = (uint32_t)CGImageGetBitsPerComponent(srcImage);
- srcFormat.bitsPerPixel = (uint32_t)CGImageGetBitsPerPixel(srcImage);
- srcFormat.colorSpace = CGImageGetColorSpace(srcImage);
- srcFormat.bitmapInfo = CGImageGetBitmapInfo(srcImage) | CGImageGetAlphaInfo(srcImage);
-
- convertor = vImageConverter_CreateWithCGImageFormat(&srcFormat, destFormat, NULL, kvImageNoFlags, NULL);
- if (!convertor) goto fail;
-
- CGDataProviderRef srcProvider = CGImageGetDataProvider(srcImage);
- srcData = srcProvider ? CGDataProviderCopyData(srcProvider) : NULL; // decode
- size_t srcLength = srcData ? CFDataGetLength(srcData) : 0;
- const void *srcBytes = srcData ? CFDataGetBytePtr(srcData) : NULL;
- if (srcLength == 0 || !srcBytes) goto fail;
-
- vImage_Buffer src = {0};
- src.data = (void *)srcBytes;
- src.width = width;
- src.height = height;
- src.rowBytes = CGImageGetBytesPerRow(srcImage);
-
- error = vImageBuffer_Init(dest, height, width, 32, kvImageNoFlags);
- if (error != kvImageNoError) goto fail;
-
- error = vImageConvert_AnyToAny(convertor, &src, dest, NULL, kvImageNoFlags); // convert
- if (error != kvImageNoError) goto fail;
-
- CFRelease(convertor);
- CFRelease(srcData);
- return YES;
-
- fail:
- if (convertor) CFRelease(convertor);
- if (srcData) CFRelease(srcData);
- if (dest->data) free(dest->data);
- dest->data = NULL;
- return NO;
- }
- /**
- Decode an image to bitmap buffer with the 32bit format (such as ARGB8888).
-
- @param srcImage Source image.
- @param dest Destination buffer. It should be zero before call this method.
- If decode succeed, you should release the dest->data using free().
- @param bitmapInfo Destination bitmap format.
-
- @return Whether succeed.
- */
- static BOOL WebPCGImageDecodeToBitmapBufferWith32BitFormat(CGImageRef srcImage, vImage_Buffer *dest, CGBitmapInfo bitmapInfo) {
- if (!srcImage || !dest) return NO;
- size_t width = CGImageGetWidth(srcImage);
- size_t height = CGImageGetHeight(srcImage);
- if (width == 0 || height == 0) return NO;
-
- BOOL hasAlpha = NO;
- BOOL alphaFirst = NO;
- BOOL alphaPremultiplied = NO;
- BOOL byteOrderNormal = NO;
-
- switch (bitmapInfo & kCGBitmapAlphaInfoMask) {
- case kCGImageAlphaPremultipliedLast: {
- hasAlpha = YES;
- alphaPremultiplied = YES;
- } break;
- case kCGImageAlphaPremultipliedFirst: {
- hasAlpha = YES;
- alphaPremultiplied = YES;
- alphaFirst = YES;
- } break;
- case kCGImageAlphaLast: {
- hasAlpha = YES;
- } break;
- case kCGImageAlphaFirst: {
- hasAlpha = YES;
- alphaFirst = YES;
- } break;
- case kCGImageAlphaNoneSkipLast: {
- } break;
- case kCGImageAlphaNoneSkipFirst: {
- alphaFirst = YES;
- } break;
- default: {
- return NO;
- } break;
- }
-
- switch (bitmapInfo & kCGBitmapByteOrderMask) {
- case kCGBitmapByteOrderDefault: {
- byteOrderNormal = YES;
- } break;
- case kCGBitmapByteOrder32Little: {
- } break;
- case kCGBitmapByteOrder32Big: {
- byteOrderNormal = YES;
- } break;
- default: {
- return NO;
- } break;
- }
-
- /*
- Try convert with vImageConvert_AnyToAny() (avaliable since iOS 7.0).
- If fail, try decode with CGContextDrawImage().
- CGBitmapContext use a premultiplied alpha format, unpremultiply may lose precision.
- */
- vImage_CGImageFormat destFormat = {0};
- destFormat.bitsPerComponent = 8;
- destFormat.bitsPerPixel = 32;
- destFormat.colorSpace = WebPColorSpaceForDeviceRGB();
- destFormat.bitmapInfo = bitmapInfo;
- dest->data = NULL;
- if (WebPCGImageDecodeToBitmapBufferWithAnyFormat(srcImage, dest, &destFormat)) return YES;
-
- CGBitmapInfo contextBitmapInfo = bitmapInfo & kCGBitmapByteOrderMask;
- if (!hasAlpha || alphaPremultiplied) {
- contextBitmapInfo |= (bitmapInfo & kCGBitmapAlphaInfoMask);
- } else {
- contextBitmapInfo |= alphaFirst ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaPremultipliedLast;
- }
- CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, WebPColorSpaceForDeviceRGB(), contextBitmapInfo);
- if (!context) goto fail;
-
- CGContextDrawImage(context, CGRectMake(0, 0, width, height), srcImage); // decode and convert
- size_t bytesPerRow = CGBitmapContextGetBytesPerRow(context);
- size_t length = height * bytesPerRow;
- void *data = CGBitmapContextGetData(context);
- if (length == 0 || !data) goto fail;
-
- dest->data = malloc(length);
- dest->width = width;
- dest->height = height;
- dest->rowBytes = bytesPerRow;
- if (!dest->data) goto fail;
-
- if (hasAlpha && !alphaPremultiplied) {
- vImage_Buffer tmpSrc = {0};
- tmpSrc.data = data;
- tmpSrc.width = width;
- tmpSrc.height = height;
- tmpSrc.rowBytes = bytesPerRow;
- vImage_Error error;
- if (alphaFirst && byteOrderNormal) {
- error = vImageUnpremultiplyData_ARGB8888(&tmpSrc, dest, kvImageNoFlags);
- } else {
- error = vImageUnpremultiplyData_RGBA8888(&tmpSrc, dest, kvImageNoFlags);
- }
- if (error != kvImageNoError) goto fail;
- } else {
- memcpy(dest->data, data, length);
- }
-
- CFRelease(context);
- return YES;
-
- fail:
- if (context) CFRelease(context);
- if (dest->data) free(dest->data);
- dest->data = NULL;
- return NO;
- return NO;
- }
- static int WebPPictureImportCGImage(WebPPicture *picture, CGImageRef image) {
- vImage_Buffer buffer = {0};
- int result = 0;
- if (WebPCGImageDecodeToBitmapBufferWith32BitFormat(image, &buffer, kCGImageAlphaLast | kCGBitmapByteOrderDefault)) {
- picture->width = (int)buffer.width;
- picture->height = (int)buffer.height;
- picture->use_argb = 1;
- result = WebPPictureImportRGBA(picture, buffer.data, (int)buffer.rowBytes);
- free(buffer.data);
- }
- return result;
- }
- #pragma mark - Still Images
- CGImageRef WebPImageCreateWithData(CFDataRef webpData) {
- WebPData webp_data;
- WebPDataInit(&webp_data);
- webp_data.bytes = CFDataGetBytePtr(webpData);
- webp_data.size = CFDataGetLength(webpData);
-
- WebPAnimDecoderOptions dec_options;
- WebPAnimDecoderOptionsInit(&dec_options);
- dec_options.use_threads = 1;
- dec_options.color_mode = MODE_rgbA;
-
- WebPAnimDecoder *dec = WebPAnimDecoderNew(&webp_data, &dec_options);
- if (!dec) {
- return NULL;
- }
-
- WebPAnimInfo anim_info;
- uint8_t *buf;
- int timestamp;
- if (!WebPAnimDecoderGetInfo(dec, &anim_info) || !WebPAnimDecoderGetNext(dec, &buf, ×tamp)) {
- WebPAnimDecoderDelete(dec);
- return NULL;
- }
-
- const size_t bufSize = anim_info.canvas_width * 4 * anim_info.canvas_height;
- void *bufCopy = malloc(bufSize);
- memcpy(bufCopy, buf, bufSize);
- WebPAnimDecoderDelete(dec);
-
- CGDataProviderRef provider = CGDataProviderCreateWithData(bufCopy, bufCopy, bufSize, WebPFreeInfoReleaseDataCallback);
- 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);
- CGDataProviderRelease(provider);
-
- return image;
- }
- CFDataRef WebPDataCreateWithImage(CGImageRef image, bool isLossy, float quality) {
- WebPConfig config;
- WebPConfigInit(&config);
- if (isLossy) {
- WebPConfigPreset(&config, WEBP_PRESET_DEFAULT, quality);
- } else {
- WebPConfigLosslessPreset(&config, 0);
- }
-
- WebPPicture picture;
- WebPPictureInit(&picture);
-
- WebPMemoryWriter writer;
- WebPMemoryWriterInit(&writer);
- picture.writer = WebPMemoryWrite;
- picture.custom_ptr = &writer;
-
- if (!(WebPPictureImportCGImage(&picture, image))) {
- goto fail;
- }
-
- if (!WebPEncode(&config, &picture)) {
- goto fail;
- }
-
- CFDataRef data = CFDataCreate(kCFAllocatorDefault, writer.mem, writer.size);
- WebPMemoryWriterClear(&writer);
- WebPPictureFree(&picture);
- return data;
-
- fail:
- WebPMemoryWriterClear(&writer);
- WebPPictureFree(&picture);
- return NULL;
- }
- #pragma mark - Animated Images
- const CFStringRef kWebPAnimatedImageDuration = CFSTR("kWebPAnimatedImageDuration");
- const CFStringRef kWebPAnimatedImageLoopCount = CFSTR("kWebPAnimatedImageLoopCount");
- const CFStringRef kWebPAnimatedImageFrames = CFSTR("kWebPAnimatedImageFrames");
- uint32_t WebPImageFrameCountGetFromData(CFDataRef webpData) {
- WebPData webp_data;
- WebPDataInit(&webp_data);
- webp_data.bytes = CFDataGetBytePtr(webpData);
- webp_data.size = CFDataGetLength(webpData);
-
- WebPDemuxer *dmux = WebPDemux(&webp_data);
- if (!dmux) {
- return 0;
- }
-
- uint32_t frameCount = WebPDemuxGetI(dmux, WEBP_FF_FRAME_COUNT);
- WebPDemuxDelete(dmux);
-
- return frameCount;
- }
- CFDictionaryRef WebPAnimatedImageInfoCreateWithData(CFDataRef webpData) {
- WebPData webp_data;
- WebPDataInit(&webp_data);
- webp_data.bytes = CFDataGetBytePtr(webpData);
- webp_data.size = CFDataGetLength(webpData);
-
- WebPAnimDecoderOptions dec_options;
- WebPAnimDecoderOptionsInit(&dec_options);
- dec_options.use_threads = 1;
- dec_options.color_mode = MODE_rgbA;
-
- WebPAnimDecoder *dec = WebPAnimDecoderNew(&webp_data, &dec_options);
- if (!dec) {
- return NULL;
- }
-
- WebPAnimInfo anim_info;
- if (!WebPAnimDecoderGetInfo(dec, &anim_info)) {
- WebPAnimDecoderDelete(dec);
- return NULL;
- }
-
- CFMutableDictionaryRef imageInfo = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
-
- CFMutableArrayRef imageFrames = CFArrayCreateMutable(kCFAllocatorDefault, anim_info.frame_count, &kCFTypeArrayCallBacks);
-
- int duration = 0;
- while (WebPAnimDecoderHasMoreFrames(dec)) {
- uint8_t *buf;
-
- if (!WebPAnimDecoderGetNext(dec, &buf, &duration)) {
- break;
- }
-
- const size_t bufSize = anim_info.canvas_width * 4 * anim_info.canvas_height;
- void *bufCopy = malloc(bufSize);
- memcpy(bufCopy, buf, bufSize);
-
- CGDataProviderRef provider = CGDataProviderCreateWithData(bufCopy, bufCopy, bufSize, WebPFreeInfoReleaseDataCallback);
- 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);
- CFArrayAppendValue(imageFrames, image);
- CGImageRelease(image);
- CGDataProviderRelease(provider);
- }
-
- // add last frame's duration
- const WebPDemuxer *dmux = WebPAnimDecoderGetDemuxer(dec);
- WebPIterator iter;
- if (WebPDemuxGetFrame(dmux, 0, &iter)) {
- duration += iter.duration;
- WebPDemuxReleaseIterator(&iter);
- }
- WebPAnimDecoderDelete(dec);
-
- CFDictionarySetValue(imageInfo, kWebPAnimatedImageFrames, imageFrames);
- CFRelease(imageFrames);
-
- CFNumberRef loopCount = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &anim_info.loop_count);
- CFDictionarySetValue(imageInfo, kWebPAnimatedImageLoopCount, loopCount);
- CFRelease(loopCount);
-
- double durationInSec = ((double)duration) / 1000;
- CFNumberRef durationRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &durationInSec);
- CFDictionarySetValue(imageInfo, kWebPAnimatedImageDuration, durationRef);
- CFRelease(durationRef);
-
- return imageInfo;
- }
- CFDataRef WebPDataCreateWithAnimatedImageInfo(CFDictionaryRef imageInfo, bool isLossy, float quality) {
- CFNumberRef loopCount = CFDictionaryGetValue(imageInfo, kWebPAnimatedImageLoopCount);
- CFNumberRef durationRef = CFDictionaryGetValue(imageInfo, kWebPAnimatedImageDuration);
- CFArrayRef imageFrames = CFDictionaryGetValue(imageInfo, kWebPAnimatedImageFrames);
-
- if (!imageFrames || CFArrayGetCount(imageFrames) < 1) {
- return NULL;
- }
-
- WebPAnimEncoderOptions enc_options;
- WebPAnimEncoderOptionsInit(&enc_options);
- if (loopCount) {
- CFNumberGetValue(loopCount, kCFNumberSInt32Type, &enc_options.anim_params.loop_count);
- }
-
- CGImageRef firstImage = (CGImageRef)CFArrayGetValueAtIndex(imageFrames, 0);
- WebPAnimEncoder *enc = WebPAnimEncoderNew((int)CGImageGetWidth(firstImage), (int)CGImageGetHeight(firstImage), &enc_options);
- if (!enc) {
- return NULL;
- }
-
- int frameDurationInMilliSec = 100;
- if (durationRef) {
- double totalDurationInSec;
- CFNumberGetValue(durationRef, kCFNumberDoubleType, &totalDurationInSec);
- frameDurationInMilliSec = (int)(totalDurationInSec * 1000 / CFArrayGetCount(imageFrames));
- }
-
- for (CFIndex i = 0; i < CFArrayGetCount(imageFrames); i ++) {
- WebPPicture frame;
- WebPPictureInit(&frame);
- if (WebPPictureImportCGImage(&frame, (CGImageRef)CFArrayGetValueAtIndex(imageFrames, i))) {
- WebPConfig config;
- WebPConfigInit(&config);
- if (isLossy) {
- WebPConfigPreset(&config, WEBP_PRESET_DEFAULT, quality);
- } else {
- WebPConfigLosslessPreset(&config, 0);
- }
- WebPAnimEncoderAdd(enc, &frame, (int)(frameDurationInMilliSec * i), &config);
- }
- WebPPictureFree(&frame);
- }
- WebPAnimEncoderAdd(enc, NULL, (int)(frameDurationInMilliSec * CFArrayGetCount(imageFrames)), NULL);
-
- WebPData webp_data;
- WebPDataInit(&webp_data);
- WebPAnimEncoderAssemble(enc, &webp_data);
- WebPAnimEncoderDelete(enc);
-
- CFDataRef data = CFDataCreate(kCFAllocatorDefault, webp_data.bytes, webp_data.size);
- WebPDataClear(&webp_data);
-
- return data;
- }
|