Skip to content

Commit bea3ab8

Browse files
committed
Fix the HDR encoding bug after we use vImage convert
Only do vImage convert for non-HDR input image
1 parent e852994 commit bea3ab8

File tree

1 file changed

+91
-104
lines changed

1 file changed

+91
-104
lines changed

SDWebImageJPEGXLCoder/Classes/SDImageJPEGXLCoder.m

+91-104
Original file line numberDiff line numberDiff line change
@@ -246,29 +246,33 @@ - (nullable CGImageRef)sd_createJXLImageWithDec:(JxlDecoder *)dec info:(JxlBasic
246246
BOOL hasAlpha = info.alpha_bits != 0;
247247
BOOL premultiplied = info.alpha_premultiplied;
248248
SDImagePixelFormat pixelFormat = [SDImageCoderHelper preferredPixelFormat:hasAlpha];
249-
JxlDataType dataType;
250249

251250
// 16 bit or 8 bit, HDR ?
252-
CGBitmapInfo bitmapInfo = pixelFormat.bitmapInfo;
253-
CGImageByteOrderInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
251+
JxlDataType data_type;
252+
CGBitmapInfo bitmapInfo = 0;
254253
size_t alignment = pixelFormat.alignment;
255-
size_t components = hasAlpha ? 4 : 3;
256-
size_t bitsPerComponent;
257-
if (bitmapInfo & kCGBitmapFloatComponents) {
258-
// float16 HDR
259-
dataType = JXL_TYPE_FLOAT16;
260-
bitsPerComponent = 16;
261-
bitmapInfo = kCGBitmapByteOrderDefault | kCGBitmapFloatComponents;
262-
} else if (byteOrderInfo == kCGBitmapByteOrder16Big || byteOrderInfo == kCGBitmapByteOrder16Little) {
263-
// uint16 HDR
264-
dataType = JXL_TYPE_UINT16;
265-
bitsPerComponent = 16;
266-
bitmapInfo = kCGBitmapByteOrder16Host;
254+
size_t components = info.num_color_channels + info.num_extra_channels;
255+
size_t bitsPerComponent = info.bits_per_sample;
256+
if (info.exponent_bits_per_sample > 0 || info.alpha_exponent_bits > 0) {
257+
// float HDR
258+
data_type = bitsPerComponent > 16 ? JXL_TYPE_FLOAT : JXL_TYPE_FLOAT16;
259+
bitmapInfo |= kCGBitmapFloatComponents;
267260
} else {
268-
// uint8 SDR
269-
dataType = JXL_TYPE_UINT8;
270-
bitsPerComponent = 8;
271-
bitmapInfo = kCGBitmapByteOrderDefault;
261+
// uint
262+
data_type = bitsPerComponent <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16;
263+
}
264+
265+
switch (data_type) {
266+
case JXL_TYPE_FLOAT:
267+
bitmapInfo |= kCGBitmapByteOrder32Host;
268+
break;
269+
case JXL_TYPE_FLOAT16:
270+
case JXL_TYPE_UINT16:
271+
bitmapInfo |= kCGBitmapByteOrder16Host;
272+
break;
273+
case JXL_TYPE_UINT8:
274+
bitmapInfo |= kCGBitmapByteOrderDefault;
275+
break;
272276
}
273277
// libjxl now always prefer RGB / RGBA order
274278
if (hasAlpha) {
@@ -282,7 +286,7 @@ - (nullable CGImageRef)sd_createJXLImageWithDec:(JxlDecoder *)dec info:(JxlBasic
282286
}
283287
JxlPixelFormat format = {
284288
.num_channels = (uint32_t)components,
285-
.data_type = dataType,
289+
.data_type = data_type,
286290
.endianness = JXL_NATIVE_ENDIAN,
287291
.align = alignment
288292
};
@@ -403,7 +407,7 @@ - (NSData *)encodedDataWithFrames:(NSArray<SDImageFrame *> *)frames loopCount:(N
403407

404408
if (!hasAnimation) {
405409
// for static single jxl
406-
success = [self sd_encodeFrameWithEnc:enc frameSettings:frame_settings frame:imageRef orientation:orientation duration:0 options:options output:output];
410+
success = [self sd_encodeFrameWithEnc:enc frameSettings:frame_settings frame:imageRef orientation:orientation duration:0];
407411
if (!success) {
408412
JxlEncoderDestroy(enc);
409413
JxlThreadParallelRunnerDestroy(runner);
@@ -420,7 +424,7 @@ - (NSData *)encodedDataWithFrames:(NSArray<SDImageFrame *> *)frames loopCount:(N
420424
UIImage *currentImage = currentFrame.image;
421425
double duration = currentFrame.duration;
422426

423-
success = [self sd_encodeFrameWithEnc:enc frameSettings:frame_settings frame:currentImage.CGImage orientation:orientation duration:duration options:options output:output];
427+
success = [self sd_encodeFrameWithEnc:enc frameSettings:frame_settings frame:currentImage.CGImage orientation:orientation duration:duration];
424428
// earily break
425429
if (!success) {
426430
JxlEncoderDestroy(enc);
@@ -467,7 +471,6 @@ static JxlEncoderStatus EncodeWithEncoder(JxlEncoder* enc, NSMutableData *compre
467471
next_out = (uint8_t *)compressed.mutableBytes + offset;
468472
avail_out = compressed.length - offset;
469473
} else if (process_result == JXL_ENC_ERROR){
470-
NSLog(@"Failed to encode JXL");
471474
return process_result;
472475
}
473476
}
@@ -487,23 +490,6 @@ static JxlEncoderStatus SetupEncoderForPrimaryImage(JxlEncoder *enc, JxlEncoderF
487490
size_t components = bitsPerPixel / bitsPerComponent;
488491
__unused size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
489492
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
490-
CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
491-
CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
492-
__unused BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
493-
alphaInfo == kCGImageAlphaNoneSkipFirst ||
494-
alphaInfo == kCGImageAlphaNoneSkipLast);
495-
BOOL byteOrderNormal = NO;
496-
switch (byteOrderInfo) {
497-
case kCGBitmapByteOrderDefault: {
498-
byteOrderNormal = YES;
499-
} break;
500-
case kCGBitmapByteOrder32Little: {
501-
} break;
502-
case kCGBitmapByteOrder32Big: {
503-
byteOrderNormal = YES;
504-
} break;
505-
default: break;
506-
}
507493
// We must prefer the input CGImage's color space, which may contains ICC profile
508494
CGColorSpaceRef colorSpace = CGImageGetColorSpace(imageRef);
509495
// We only supports RGB colorspace, filter the un-supported one (like Monochrome, CMYK, etc)
@@ -522,10 +508,9 @@ static JxlEncoderStatus SetupEncoderForPrimaryImage(JxlEncoder *enc, JxlEncoderF
522508
BOOL loseless = options[SDImageCoderEncodeJXLLoseless] ? [options[SDImageCoderEncodeJXLLoseless] boolValue] : NO;
523509
int codeStreamLevel = options[SDImageCoderEncodeJXLCodeStreamLevel] ? [options[SDImageCoderEncodeJXLCodeStreamLevel] intValue] : -1;
524510

511+
/* Calculate the basic info only when primary image provided */
525512
__block JxlEncoderStatus jret = JXL_ENC_SUCCESS;
526513
JxlBasicInfo info;
527-
/* Calculate the basic info only when primary image provided */
528-
JxlColorEncoding jxl_color;
529514

530515
/* populate the basic info settings */
531516
JxlEncoderInitBasicInfo(&info);
@@ -577,32 +562,29 @@ static JxlEncoderStatus SetupEncoderForPrimaryImage(JxlEncoder *enc, JxlEncoderF
577562
render_indent = JXL_RENDERING_INTENT_SATURATION;
578563
break;
579564
}
580-
jxl_color.rendering_intent = render_indent;
581565

582566
jret = JxlEncoderSetBasicInfo(enc, &info);
583567
if (jret != JXL_ENC_SUCCESS) {
584568
return jret;
585569
}
586570

587-
jxl_color.color_space = JXL_COLOR_SPACE_RGB;
588571
// ICC Profile
589572
NSData *iccProfile = (__bridge_transfer NSData *)CGColorSpaceCopyICCProfile(colorSpace);
590573
jret = JxlEncoderSetICCProfile(enc, iccProfile.bytes, iccProfile.length);
591574
if (jret != JXL_ENC_SUCCESS) {
592575
return jret;
593576
}
594577

578+
/* This needs to be set each time the encoder is reset */
579+
jret &= JxlEncoderSetFrameDistance(frame_settings, distance);
580+
/* Set lossless */
581+
jret &= JxlEncoderSetFrameLossless(frame_settings, loseless ? 1 : 0);
595582
/* Set code steram level */
596583
jret = JxlEncoderSetCodestreamLevel(enc, codeStreamLevel);
597584
if (jret != JXL_ENC_SUCCESS) {
598585
return jret;
599586
}
600587

601-
/* This needs to be set each time the encoder is reset */
602-
jret &= JxlEncoderSetFrameDistance(frame_settings, distance);
603-
/* Set lossless */
604-
jret &= JxlEncoderSetFrameLossless(frame_settings, loseless ? 1 : 0);
605-
606588
/* Set extra frame setting */
607589
[frameSetting enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, NSNumber * _Nonnull value, BOOL * _Nonnull stop) {
608590
JxlEncoderFrameSettingId frame_key = key.unsignedIntValue;
@@ -628,31 +610,13 @@ static JxlEncoderStatus SetupEncoderForPrimaryImage(JxlEncoder *enc, JxlEncoderF
628610
return jret;
629611
}
630612

631-
// Encode single frame (shared by static/animated jxl encoding)
632-
- (BOOL)sd_encodeFrameWithEnc:(JxlEncoder*)enc
633-
frameSettings:(JxlEncoderFrameSettings *)frame_settings
634-
frame:(nullable CGImageRef)imageRef
635-
orientation:(CGImagePropertyOrientation)orientation /*useless*/
636-
duration:(double)duration
637-
options:(NSDictionary *)options
638-
output:(NSMutableData *)output
639-
{
640-
if (!imageRef) {
641-
return NO;
642-
}
613+
static vImage_Error ConvertToRGBABuffer(CGImageRef imageRef, vImage_Buffer *dest) {
643614
// bitmap info from CGImage
644-
__unused size_t width = CGImageGetWidth(imageRef);
645-
size_t height = CGImageGetHeight(imageRef);
646615
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
647616
size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef);
648-
size_t components = bitsPerPixel / bitsPerComponent;
649-
size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
650617
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
651618
CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
652619
CGImageByteOrderInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
653-
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
654-
alphaInfo == kCGImageAlphaNoneSkipFirst ||
655-
alphaInfo == kCGImageAlphaNoneSkipLast);
656620
BOOL byteOrderNormal = NO;
657621
switch (byteOrderInfo) {
658622
case kCGBitmapByteOrderDefault: {
@@ -677,30 +641,16 @@ - (BOOL)sd_encodeFrameWithEnc:(JxlEncoder*)enc
677641
}
678642
CGColorRenderingIntent renderingIntent = CGImageGetRenderingIntent(imageRef);
679643

680-
// TODO: ugly code, libjxl supports RGBA order only, but input CGImage maybe BGRA, ARGB, etc
681-
// see: encode.h JxlDataType
682-
// * TODO(lode): support different channel orders if needed (RGB, BGR, ...)
683644
// Begin here vImage <---
684-
// RGB888/RGBA8888 (Non-premultiplied to works for libjxl)
685-
CGImageAlphaInfo destAlphaInfo;
686-
if (hasAlpha) {
687-
if (components == 4) {
688-
destAlphaInfo = kCGImageAlphaLast;
689-
} else if (components == 1) {
690-
destAlphaInfo = kCGImageAlphaOnly;
691-
} else {
692-
// Unsupported!
693-
destAlphaInfo = alphaInfo;
694-
}
645+
CGImageAlphaInfo destAlphaInfo = alphaInfo;
646+
if (alphaInfo == kCGImageAlphaNoneSkipLast || alphaInfo == kCGImageAlphaNoneSkipFirst) {
647+
destAlphaInfo = kCGImageAlphaNoneSkipLast;
648+
} else if (alphaInfo == kCGImageAlphaLast || alphaInfo == kCGImageAlphaFirst) {
649+
destAlphaInfo = kCGImageAlphaLast;
650+
} else if (alphaInfo == kCGImageAlphaPremultipliedLast || alphaInfo == kCGImageAlphaPremultipliedFirst) {
651+
destAlphaInfo = kCGImageAlphaLast;
695652
} else {
696-
if (components == 4) {
697-
destAlphaInfo = kCGImageAlphaNoneSkipLast;
698-
} else if (components == 3) {
699-
destAlphaInfo = kCGImageAlphaNone;
700-
} else {
701-
// Unsupported!
702-
destAlphaInfo = alphaInfo;
703-
}
653+
destAlphaInfo = alphaInfo;
704654
}
705655
CGImageByteOrderInfo destByteOrderInfo = byteOrderInfo;
706656
if (!byteOrderNormal) {
@@ -716,33 +666,70 @@ - (BOOL)sd_encodeFrameWithEnc:(JxlEncoder*)enc
716666
destBitmapInfo |= kCGBitmapFloatComponents;
717667
}
718668

719-
uint8_t *rgba = NULL; // RGBA Buffer managed by CFData, don't call `free` on it, instead call `CFRelease` on `dataRef`
720669
vImage_CGImageFormat destFormat = {
721670
.bitsPerComponent = (uint32_t)bitsPerComponent,
722671
.bitsPerPixel = (uint32_t)bitsPerPixel,
723672
.colorSpace = colorSpace,
724673
.bitmapInfo = destBitmapInfo,
725674
.renderingIntent = renderingIntent
726675
};
727-
vImage_Buffer dest;
728676
// We could not assume that input CGImage's color mode is always RGB888/RGBA8888. Convert all other cases to target color mode using vImage
729677
// But vImageBuffer_InitWithCGImage will do convert automatically (unless you use `kvImageNoAllocate`), so no need to call `vImageConvert` by ourselves
730-
vImage_Error error = vImageBuffer_InitWithCGImage(&dest, &destFormat, NULL, imageRef, kvImageNoFlags);
731-
if (error != kvImageNoError) {
678+
vImage_Error error = vImageBuffer_InitWithCGImage(dest, &destFormat, NULL, imageRef, kvImageNoFlags);
679+
return error;
680+
// End here vImage --->
681+
}
682+
683+
// Encode single frame (shared by static/animated jxl encoding)
684+
- (BOOL)sd_encodeFrameWithEnc:(JxlEncoder*)enc
685+
frameSettings:(JxlEncoderFrameSettings *)frame_settings
686+
frame:(nullable CGImageRef)imageRef
687+
orientation:(CGImagePropertyOrientation)orientation /*useless*/
688+
duration:(double)duration
689+
{
690+
if (!imageRef) {
732691
return NO;
733692
}
734-
rgba = dest.data;
735-
bytesPerRow = dest.rowBytes;
736-
size_t rgba_size = bytesPerRow * height;
737-
// End here vImage --->
693+
694+
size_t width = CGImageGetWidth(imageRef);
695+
size_t height = CGImageGetHeight(imageRef);
696+
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
697+
size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef);
698+
size_t components = bitsPerPixel / bitsPerComponent;
699+
size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
700+
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
701+
702+
CFDataRef buffer;
703+
// is HDR or not ?
704+
if (bitsPerComponent < 16) {
705+
// TODO: ugly code, libjxl supports RGBA order only, but input CGImage maybe BGRA, ARGB, etc
706+
// see: encode.h JxlDataType
707+
// * TODO(lode): support different channel orders if needed (RGB, BGR, ...)
708+
vImage_Buffer dest;
709+
vImage_Error error = ConvertToRGBABuffer(imageRef, &dest);
710+
if (error != kvImageNoError) {
711+
return NO;
712+
}
713+
bytesPerRow = dest.rowBytes;
714+
size_t length = bytesPerRow * height;
715+
buffer = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, dest.data, length, kCFAllocatorDefault);
716+
} else {
717+
// directlly use the CGImage's buffer, preserve HDR
718+
CGDataProviderRef provider = CGImageGetDataProvider(imageRef);
719+
if (!provider) {
720+
return NO;
721+
}
722+
buffer = CGDataProviderCopyData(provider);
723+
CGDataProviderRelease(provider);
724+
}
738725

739726
JxlEncoderStatus jret = JXL_ENC_SUCCESS;
740727
JxlPixelFormat jxl_fmt;
741728

742729
/* Set the current frame pixel format */
743730
jxl_fmt.num_channels = (uint32_t)components;
744-
// CGImage has its own alignment, since we don't use vImage to re-align the input buffer, don't apply here
745-
size_t alignment = (bitsPerComponent / 8) * components * 8;
731+
// TODO: we use vImage, so the align should re-calculate
732+
size_t alignment = bytesPerRow - (width * bitsPerPixel / 8);
746733
jxl_fmt.align = alignment;
747734
// default endian (little)
748735
jxl_fmt.endianness = JXL_NATIVE_ENDIAN;
@@ -773,10 +760,10 @@ - (BOOL)sd_encodeFrameWithEnc:(JxlEncoder*)enc
773760

774761
/* Add frame bitmap buffer */
775762
jret = JxlEncoderAddImageFrame(frame_settings, &jxl_fmt,
776-
rgba,
777-
rgba_size);
778-
// free the vImage allocated buffer
779-
free(rgba);
763+
CFDataGetBytePtr(buffer),
764+
CFDataGetLength(buffer));
765+
// free the allocated buffer
766+
CFRelease(buffer);
780767
if (jret != JXL_ENC_SUCCESS) {
781768
return NO;
782769
}

0 commit comments

Comments
 (0)