@@ -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