Authored by Bogdan Poplauschi
Committed by GitHub

Merge pull request #2318 from dreampiggy/bugfix_WebP_encode_color_mode

Fix WebP Encoding only works for RGBA8888 CGImage but not other color mode
@@ -23,6 +23,7 @@ @@ -23,6 +23,7 @@
23 #import "webp/demux.h" 23 #import "webp/demux.h"
24 #import "webp/mux.h" 24 #import "webp/mux.h"
25 #endif 25 #endif
  26 +#import <Accelerate/Accelerate.h>
26 27
27 @implementation SDWebImageWebPCoder { 28 @implementation SDWebImageWebPCoder {
28 WebPIDecoder *_idec; 29 WebPIDecoder *_idec;
@@ -393,18 +394,106 @@ @@ -393,18 +394,106 @@
393 } 394 }
394 395
395 size_t bytesPerRow = CGImageGetBytesPerRow(imageRef); 396 size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
  397 + CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
  398 + CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
  399 + CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
  400 + BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
  401 + alphaInfo == kCGImageAlphaNoneSkipFirst ||
  402 + alphaInfo == kCGImageAlphaNoneSkipLast);
  403 + BOOL byteOrderNormal = NO;
  404 + switch (byteOrderInfo) {
  405 + case kCGBitmapByteOrderDefault: {
  406 + byteOrderNormal = YES;
  407 + } break;
  408 + case kCGBitmapByteOrder32Little: {
  409 + } break;
  410 + case kCGBitmapByteOrder32Big: {
  411 + byteOrderNormal = YES;
  412 + } break;
  413 + default: break;
  414 + }
  415 + // If we can not get bitmap buffer, early return
396 CGDataProviderRef dataProvider = CGImageGetDataProvider(imageRef); 416 CGDataProviderRef dataProvider = CGImageGetDataProvider(imageRef);
397 if (!dataProvider) { 417 if (!dataProvider) {
398 return nil; 418 return nil;
399 } 419 }
400 CFDataRef dataRef = CGDataProviderCopyData(dataProvider); 420 CFDataRef dataRef = CGDataProviderCopyData(dataProvider);
401 - uint8_t *rgba = (uint8_t *)CFDataGetBytePtr(dataRef); 421 + if (!dataRef) {
  422 + return nil;
  423 + }
402 424
403 - uint8_t *data = NULL;  
404 - float quality = 100.0;  
405 - size_t size = WebPEncodeRGBA(rgba, (int)width, (int)height, (int)bytesPerRow, quality, &data);  
406 - CFRelease(dataRef);  
407 - rgba = NULL; 425 + uint8_t *rgba = NULL;
  426 + // We could not assume that input CGImage's color mode is always RGB888/RGBA8888. Convert all other cases to target color mode using vImage
  427 + if (byteOrderNormal && ((alphaInfo == kCGImageAlphaNone) || (alphaInfo == kCGImageAlphaLast))) {
  428 + // If the input CGImage is already RGB888/RGBA8888
  429 + rgba = (uint8_t *)CFDataGetBytePtr(dataRef);
  430 + } else {
  431 + // Convert all other cases to target color mode using vImage
  432 + vImageConverterRef convertor = NULL;
  433 + vImage_Error error = kvImageNoError;
  434 +
  435 + vImage_CGImageFormat srcFormat = {
  436 + .bitsPerComponent = (uint32_t)CGImageGetBitsPerComponent(imageRef),
  437 + .bitsPerPixel = (uint32_t)CGImageGetBitsPerPixel(imageRef),
  438 + .colorSpace = CGImageGetColorSpace(imageRef),
  439 + .bitmapInfo = bitmapInfo
  440 + };
  441 + vImage_CGImageFormat destFormat = {
  442 + .bitsPerComponent = 8,
  443 + .bitsPerPixel = hasAlpha ? 32 : 24,
  444 + .colorSpace = SDCGColorSpaceGetDeviceRGB(),
  445 + .bitmapInfo = hasAlpha ? kCGImageAlphaLast | kCGBitmapByteOrderDefault : kCGImageAlphaNone | kCGBitmapByteOrderDefault // RGB888/RGBA8888 (Non-premultiplied to works for libwebp)
  446 + };
  447 +
  448 + convertor = vImageConverter_CreateWithCGImageFormat(&srcFormat, &destFormat, NULL, kvImageNoFlags, &error);
  449 + if (error != kvImageNoError) {
  450 + CFRelease(dataRef);
  451 + return nil;
  452 + }
  453 +
  454 + vImage_Buffer src = {
  455 + .data = (uint8_t *)CFDataGetBytePtr(dataRef),
  456 + .width = width,
  457 + .height = height,
  458 + .rowBytes = bytesPerRow
  459 + };
  460 + vImage_Buffer dest;
  461 +
  462 + error = vImageBuffer_Init(&dest, height, width, destFormat.bitsPerPixel, kvImageNoFlags);
  463 + if (error != kvImageNoError) {
  464 + CFRelease(dataRef);
  465 + return nil;
  466 + }
  467 +
  468 + // Convert input color mode to RGB888/RGBA8888
  469 + error = vImageConvert_AnyToAny(convertor, &src, &dest, NULL, kvImageNoFlags);
  470 + if (error != kvImageNoError) {
  471 + CFRelease(dataRef);
  472 + return nil;
  473 + }
  474 +
  475 + rgba = dest.data; // Converted buffer
  476 + bytesPerRow = dest.rowBytes; // Converted bytePerRow
  477 + CFRelease(dataRef);
  478 + dataRef = NULL;
  479 + }
  480 +
  481 + uint8_t *data = NULL; // Output WebP data
  482 + float qualityFactor = 100; // WebP quality is 0-100
  483 + // Encode RGB888/RGBA8888 buffer to WebP data
  484 + size_t size;
  485 + if (hasAlpha) {
  486 + size = WebPEncodeRGBA(rgba, (int)width, (int)height, (int)bytesPerRow, qualityFactor, &data);
  487 + } else {
  488 + size = WebPEncodeRGB(rgba, (int)width, (int)height, (int)bytesPerRow, qualityFactor, &data);
  489 + }
  490 + if (dataRef) {
  491 + CFRelease(dataRef); // free non-converted rgba buffer
  492 + dataRef = NULL;
  493 + } else {
  494 + free(rgba); // free converted rgba buffer
  495 + rgba = NULL;
  496 + }
408 497
409 if (size) { 498 if (size) {
410 // success 499 // success