Skip to content

Commit fda2e2f

Browse files
committed
Fix the case that libjxl only support RGB order (instead of BGR order, see example dice.jxl)
use vImage to convert to RGB order
1 parent a434de6 commit fda2e2f

File tree

1 file changed

+96
-13
lines changed

1 file changed

+96
-13
lines changed

SDWebImageJPEGXLCoder/Classes/SDImageJPEGXLCoder.m

Lines changed: 96 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -650,29 +650,110 @@ - (BOOL)sd_encodeFrameWithEnc:(JxlEncoder*)enc
650650
if (!imageRef) {
651651
return NO;
652652
}
653+
// bitmap info from CGImage
654+
__unused size_t width = CGImageGetWidth(imageRef);
655+
size_t height = CGImageGetHeight(imageRef);
656+
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
657+
size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef);
658+
size_t components = bitsPerPixel / bitsPerComponent;
659+
size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
660+
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
661+
CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
662+
CGImageByteOrderInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
663+
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
664+
alphaInfo == kCGImageAlphaNoneSkipFirst ||
665+
alphaInfo == kCGImageAlphaNoneSkipLast);
666+
BOOL byteOrderNormal = NO;
667+
switch (byteOrderInfo) {
668+
case kCGBitmapByteOrderDefault: {
669+
byteOrderNormal = YES;
670+
} break;
671+
case kCGBitmapByteOrder16Little:
672+
case kCGBitmapByteOrder32Little: {
673+
} break;
674+
case kCGBitmapByteOrder16Big:
675+
case kCGBitmapByteOrder32Big: {
676+
byteOrderNormal = YES;
677+
} break;
678+
default: break;
679+
}
680+
// We must prefer the input CGImage's color space, which may contains ICC profile
681+
CGColorSpaceRef colorSpace = CGImageGetColorSpace(imageRef);
682+
// We only supports RGB colorspace, filter the un-supported one (like Monochrome, CMYK, etc)
683+
if (CGColorSpaceGetModel(colorSpace) != kCGColorSpaceModelRGB) {
684+
// Ignore and convert, we don't know how to encode this colorspace directlly to WebP
685+
// This may cause little visible difference because of colorpsace conversion
686+
colorSpace = NULL;
687+
}
688+
CGColorRenderingIntent renderingIntent = CGImageGetRenderingIntent(imageRef);
653689

654-
// If we can not get bitmap buffer, early return
655-
CGDataProviderRef dataProvider = CGImageGetDataProvider(imageRef);
656-
if (!dataProvider) {
657-
return NO;
690+
// TODO: ugly code, libjxl supports RGBA order only, but input CGImage maybe BGRA, ARGB, etc
691+
// see: encode.h JxlDataType
692+
// * TODO(lode): support different channel orders if needed (RGB, BGR, ...)
693+
// Begin here vImage <---
694+
// RGB888/RGBA8888 (Non-premultiplied to works for libjxl)
695+
CGImageAlphaInfo destAlphaInfo;
696+
if (hasAlpha) {
697+
if (components == 4) {
698+
destAlphaInfo = kCGImageAlphaLast;
699+
} else if (components == 1) {
700+
destAlphaInfo = kCGImageAlphaOnly;
701+
} else {
702+
// Unsupported!
703+
destAlphaInfo = alphaInfo;
704+
}
705+
} else {
706+
if (components == 4) {
707+
destAlphaInfo = kCGImageAlphaNoneSkipLast;
708+
} else if (components == 3) {
709+
destAlphaInfo = kCGImageAlphaNone;
710+
} else {
711+
// Unsupported!
712+
destAlphaInfo = alphaInfo;
713+
}
714+
}
715+
CGImageByteOrderInfo destByteOrderInfo = byteOrderInfo;
716+
if (!byteOrderNormal) {
717+
// not RGB order, need reverse...
718+
if (byteOrderInfo == kCGImageByteOrder16Little) {
719+
destByteOrderInfo = kCGImageByteOrder16Big;
720+
} else if (byteOrderInfo == kCGImageByteOrder32Little) {
721+
destByteOrderInfo = kCGImageByteOrder32Big;
722+
}
723+
}
724+
CGBitmapInfo destBitmapInfo = (CGBitmapInfo)destAlphaInfo | (CGBitmapInfo)destByteOrderInfo;
725+
if (SD_OPTIONS_CONTAINS(bitmapInfo, kCGBitmapFloatComponents)) {
726+
destBitmapInfo |= kCGBitmapFloatComponents;
658727
}
659728

660-
NSData *buffer = (__bridge_transfer NSData *) CGDataProviderCopyData(dataProvider);
661-
if (!buffer) {
729+
uint8_t *rgba = NULL; // RGBA Buffer managed by CFData, don't call `free` on it, instead call `CFRelease` on `dataRef`
730+
vImage_CGImageFormat destFormat = {
731+
.bitsPerComponent = (uint32_t)bitsPerComponent,
732+
.bitsPerPixel = (uint32_t)bitsPerPixel,
733+
.colorSpace = colorSpace,
734+
.bitmapInfo = destBitmapInfo,
735+
.renderingIntent = renderingIntent
736+
};
737+
vImage_Buffer dest;
738+
// We could not assume that input CGImage's color mode is always RGB888/RGBA8888. Convert all other cases to target color mode using vImage
739+
// But vImageBuffer_InitWithCGImage will do convert automatically (unless you use `kvImageNoAllocate`), so no need to call `vImageConvert` by ourselves
740+
vImage_Error error = vImageBuffer_InitWithCGImage(&dest, &destFormat, NULL, imageRef, kvImageNoFlags);
741+
if (error != kvImageNoError) {
662742
return NO;
663743
}
664-
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
665-
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
666-
size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef);
667-
size_t components = bitsPerPixel / bitsPerComponent;
744+
rgba = dest.data;
745+
bytesPerRow = dest.rowBytes;
746+
size_t rgba_size = bytesPerRow * height;
747+
// End here vImage --->
668748

669749
JxlEncoderStatus jret = JXL_ENC_SUCCESS;
670750
JxlPixelFormat jxl_fmt;
671751

672752
/* Set the current frame pixel format */
673753
jxl_fmt.num_channels = (uint32_t)components;
674754
// CGImage has its own alignment, since we don't use vImage to re-align the input buffer, don't apply here
675-
jxl_fmt.align = 0;
755+
size_t alignment = (bitsPerComponent / 8) * components * 8;
756+
jxl_fmt.align = alignment;
676757
// default endian (little)
677758
jxl_fmt.endianness = JXL_NATIVE_ENDIAN;
678759
// floating point
@@ -702,8 +783,10 @@ - (BOOL)sd_encodeFrameWithEnc:(JxlEncoder*)enc
702783

703784
/* Add frame bitmap buffer */
704785
jret = JxlEncoderAddImageFrame(frame_settings, &jxl_fmt,
705-
buffer.bytes,
706-
buffer.length);
786+
rgba,
787+
rgba_size);
788+
// free the vImage allocated buffer
789+
free(rgba);
707790
if (jret != JXL_ENC_SUCCESS) {
708791
return NO;
709792
}

0 commit comments

Comments
 (0)