@@ -246,29 +246,33 @@ - (nullable CGImageRef)sd_createJXLImageWithDec:(JxlDecoder *)dec info:(JxlBasic
246
246
BOOL hasAlpha = info.alpha_bits != 0 ;
247
247
BOOL premultiplied = info.alpha_premultiplied ;
248
248
SDImagePixelFormat pixelFormat = [SDImageCoderHelper preferredPixelFormat: hasAlpha];
249
- JxlDataType dataType;
250
249
251
250
// 16 bit or 8 bit, HDR ?
252
- CGBitmapInfo bitmapInfo = pixelFormat. bitmapInfo ;
253
- CGImageByteOrderInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask ;
251
+ JxlDataType data_type ;
252
+ CGBitmapInfo bitmapInfo = 0 ;
254
253
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 ;
267
260
} 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 ;
272
276
}
273
277
// libjxl now always prefer RGB / RGBA order
274
278
if (hasAlpha) {
@@ -282,7 +286,7 @@ - (nullable CGImageRef)sd_createJXLImageWithDec:(JxlDecoder *)dec info:(JxlBasic
282
286
}
283
287
JxlPixelFormat format = {
284
288
.num_channels = (uint32_t )components,
285
- .data_type = dataType ,
289
+ .data_type = data_type ,
286
290
.endianness = JXL_NATIVE_ENDIAN,
287
291
.align = alignment
288
292
};
@@ -403,7 +407,7 @@ - (NSData *)encodedDataWithFrames:(NSArray<SDImageFrame *> *)frames loopCount:(N
403
407
404
408
if (!hasAnimation) {
405
409
// 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 ];
407
411
if (!success) {
408
412
JxlEncoderDestroy (enc);
409
413
JxlThreadParallelRunnerDestroy (runner);
@@ -420,7 +424,7 @@ - (NSData *)encodedDataWithFrames:(NSArray<SDImageFrame *> *)frames loopCount:(N
420
424
UIImage *currentImage = currentFrame.image ;
421
425
double duration = currentFrame.duration ;
422
426
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];
424
428
// earily break
425
429
if (!success) {
426
430
JxlEncoderDestroy (enc);
@@ -467,7 +471,6 @@ static JxlEncoderStatus EncodeWithEncoder(JxlEncoder* enc, NSMutableData *compre
467
471
next_out = (uint8_t *)compressed.mutableBytes + offset;
468
472
avail_out = compressed.length - offset;
469
473
} else if (process_result == JXL_ENC_ERROR){
470
- NSLog (@" Failed to encode JXL" );
471
474
return process_result;
472
475
}
473
476
}
@@ -487,23 +490,6 @@ static JxlEncoderStatus SetupEncoderForPrimaryImage(JxlEncoder *enc, JxlEncoderF
487
490
size_t components = bitsPerPixel / bitsPerComponent;
488
491
__unused size_t bytesPerRow = CGImageGetBytesPerRow (imageRef);
489
492
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
- }
507
493
// We must prefer the input CGImage's color space, which may contains ICC profile
508
494
CGColorSpaceRef colorSpace = CGImageGetColorSpace (imageRef);
509
495
// We only supports RGB colorspace, filter the un-supported one (like Monochrome, CMYK, etc)
@@ -522,10 +508,9 @@ static JxlEncoderStatus SetupEncoderForPrimaryImage(JxlEncoder *enc, JxlEncoderF
522
508
BOOL loseless = options[SDImageCoderEncodeJXLLoseless] ? [options[SDImageCoderEncodeJXLLoseless] boolValue ] : NO ;
523
509
int codeStreamLevel = options[SDImageCoderEncodeJXLCodeStreamLevel] ? [options[SDImageCoderEncodeJXLCodeStreamLevel] intValue ] : -1 ;
524
510
511
+ /* Calculate the basic info only when primary image provided */
525
512
__block JxlEncoderStatus jret = JXL_ENC_SUCCESS;
526
513
JxlBasicInfo info;
527
- /* Calculate the basic info only when primary image provided */
528
- JxlColorEncoding jxl_color;
529
514
530
515
/* populate the basic info settings */
531
516
JxlEncoderInitBasicInfo (&info);
@@ -577,32 +562,29 @@ static JxlEncoderStatus SetupEncoderForPrimaryImage(JxlEncoder *enc, JxlEncoderF
577
562
render_indent = JXL_RENDERING_INTENT_SATURATION;
578
563
break ;
579
564
}
580
- jxl_color.rendering_intent = render_indent;
581
565
582
566
jret = JxlEncoderSetBasicInfo (enc, &info);
583
567
if (jret != JXL_ENC_SUCCESS) {
584
568
return jret;
585
569
}
586
570
587
- jxl_color.color_space = JXL_COLOR_SPACE_RGB;
588
571
// ICC Profile
589
572
NSData *iccProfile = (__bridge_transfer NSData *)CGColorSpaceCopyICCProfile (colorSpace);
590
573
jret = JxlEncoderSetICCProfile (enc, iccProfile.bytes , iccProfile.length );
591
574
if (jret != JXL_ENC_SUCCESS) {
592
575
return jret;
593
576
}
594
577
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 );
595
582
/* Set code steram level */
596
583
jret = JxlEncoderSetCodestreamLevel (enc, codeStreamLevel);
597
584
if (jret != JXL_ENC_SUCCESS) {
598
585
return jret;
599
586
}
600
587
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
-
606
588
/* Set extra frame setting */
607
589
[frameSetting enumerateKeysAndObjectsUsingBlock: ^(NSNumber * _Nonnull key, NSNumber * _Nonnull value, BOOL * _Nonnull stop) {
608
590
JxlEncoderFrameSettingId frame_key = key.unsignedIntValue ;
@@ -628,31 +610,13 @@ static JxlEncoderStatus SetupEncoderForPrimaryImage(JxlEncoder *enc, JxlEncoderF
628
610
return jret;
629
611
}
630
612
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) {
643
614
// bitmap info from CGImage
644
- __unused size_t width = CGImageGetWidth (imageRef);
645
- size_t height = CGImageGetHeight (imageRef);
646
615
size_t bitsPerComponent = CGImageGetBitsPerComponent (imageRef);
647
616
size_t bitsPerPixel = CGImageGetBitsPerPixel (imageRef);
648
- size_t components = bitsPerPixel / bitsPerComponent;
649
- size_t bytesPerRow = CGImageGetBytesPerRow (imageRef);
650
617
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo (imageRef);
651
618
CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask ;
652
619
CGImageByteOrderInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask ;
653
- BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
654
- alphaInfo == kCGImageAlphaNoneSkipFirst ||
655
- alphaInfo == kCGImageAlphaNoneSkipLast );
656
620
BOOL byteOrderNormal = NO ;
657
621
switch (byteOrderInfo) {
658
622
case kCGBitmapByteOrderDefault : {
@@ -677,30 +641,16 @@ - (BOOL)sd_encodeFrameWithEnc:(JxlEncoder*)enc
677
641
}
678
642
CGColorRenderingIntent renderingIntent = CGImageGetRenderingIntent (imageRef);
679
643
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, ...)
683
644
// 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 ;
695
652
} 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;
704
654
}
705
655
CGImageByteOrderInfo destByteOrderInfo = byteOrderInfo;
706
656
if (!byteOrderNormal) {
@@ -716,33 +666,70 @@ - (BOOL)sd_encodeFrameWithEnc:(JxlEncoder*)enc
716
666
destBitmapInfo |= kCGBitmapFloatComponents ;
717
667
}
718
668
719
- uint8_t *rgba = NULL ; // RGBA Buffer managed by CFData, don't call `free` on it, instead call `CFRelease` on `dataRef`
720
669
vImage_CGImageFormat destFormat = {
721
670
.bitsPerComponent = (uint32_t )bitsPerComponent,
722
671
.bitsPerPixel = (uint32_t )bitsPerPixel,
723
672
.colorSpace = colorSpace,
724
673
.bitmapInfo = destBitmapInfo,
725
674
.renderingIntent = renderingIntent
726
675
};
727
- vImage_Buffer dest;
728
676
// We could not assume that input CGImage's color mode is always RGB888/RGBA8888. Convert all other cases to target color mode using vImage
729
677
// 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) {
732
691
return NO ;
733
692
}
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
+ }
738
725
739
726
JxlEncoderStatus jret = JXL_ENC_SUCCESS;
740
727
JxlPixelFormat jxl_fmt;
741
728
742
729
/* Set the current frame pixel format */
743
730
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 ) ;
746
733
jxl_fmt.align = alignment;
747
734
// default endian (little)
748
735
jxl_fmt.endianness = JXL_NATIVE_ENDIAN;
@@ -773,10 +760,10 @@ - (BOOL)sd_encodeFrameWithEnc:(JxlEncoder*)enc
773
760
774
761
/* Add frame bitmap buffer */
775
762
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 );
780
767
if (jret != JXL_ENC_SUCCESS) {
781
768
return NO ;
782
769
}
0 commit comments