1414@import libjxl;
1515#endif
1616
17- #define SD_TWO_CC (c1,c2 ) ((uint16_t )(((c2) << 8 ) | (c1)))
18- #define SD_FOUR_CC (c1,c2,c3,c4 ) ((uint32_t )(((c4) << 24 ) | ((c3) << 16 ) | ((c2) << 8 ) | (c1)))
17+ typedef void (^sd_cleanupBlock_t)(void );
18+
19+ #if defined(__cplusplus)
20+ extern " C" {
21+ #endif
22+ void sd_executeCleanupBlock (__strong sd_cleanupBlock_t *block);
23+ #if defined(__cplusplus)
24+ }
25+ #endif
26+
27+ // #define SD_TWO_CC(c1,c2) ((uint16_t)(((c2) << 8) | (c1)))
28+ // #define SD_FOUR_CC(c1,c2,c3,c4) ((uint32_t)(((c4) << 24) | ((c3) << 16) | ((c2) << 8) | (c1)))
1929
2030static void FreeImageData (void *info, const void *data, size_t size) {
2131 free ((void *)data);
@@ -108,6 +118,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *)
108118 if (!data) {
109119 return nil ;
110120 }
121+ BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue ];
111122 CGFloat scale = 1 ;
112123 if ([options valueForKey: SDImageCoderDecodeScaleFactor]) {
113124 scale = [[options valueForKey: SDImageCoderDecodeScaleFactor] doubleValue ];
@@ -132,25 +143,20 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *)
132143 preserveAspectRatio = preserveAspectRatioValue.boolValue ;
133144 }
134145
135- CGImageRef imageRef = [self sd_createJXLImageWithData: data thumbnailSize: thumbnailSize preserveAspectRatio: preserveAspectRatio];
136- if (!imageRef) {
137- return nil ;
138- }
139-
140- #if SD_MAC
141- UIImage *image = [[UIImage alloc ] initWithCGImage: imageRef scale: scale orientation: kCGImagePropertyOrientationUp ];
142- #else
143- UIImage *image = [[UIImage alloc ] initWithCGImage: imageRef scale: scale orientation: UIImageOrientationUp];
144- #endif
145- CGImageRelease (imageRef);
146-
147- return image;
148- }
149-
150- - (nullable CGImageRef)sd_createJXLImageWithData : (NSData *)data thumbnailSize : (CGSize)thumbnailSize preserveAspectRatio : (BOOL )preserveAspectRatio CF_RETURNS_RETAINED {
151- // Learn example to render on screen, see: libjxl/tools/viewer/load_jxl.cc
146+ // cleanup
147+ __block JxlDecoder *dec;
148+ __block CGColorSpaceRef colorSpaceRef;
149+ __strong void (^cleanupBlock)(void ) __attribute__ ((cleanup (sd_executeCleanupBlock), unused)) = ^{
150+ if (colorSpaceRef) {
151+ CGColorSpaceRelease (colorSpaceRef);
152+ }
153+ if (dec) {
154+ JxlDecoderDestroy (dec);
155+ }
156+ };
152157
153- JxlDecoder *dec = JxlDecoderCreate (NULL );
158+ // Get basic info
159+ dec = JxlDecoderCreate (NULL );
154160 if (!dec) return nil ;
155161
156162 // feed data
@@ -160,7 +166,7 @@ - (nullable CGImageRef)sd_createJXLImageWithData:(NSData *)data thumbnailSize:(C
160166 // note: when using `JxlDecoderSubscribeEvents` libjxl behaves likes incremental decoding
161167 // which need event loop to get latest status via `JxlDecoderProcessInput`
162168 // each status reports your next steps's info
163- status = JxlDecoderSubscribeEvents (dec, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE);
169+ status = JxlDecoderSubscribeEvents (dec, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE);
164170 if (status != JXL_DEC_SUCCESS) return nil ;
165171
166172 // decode it
@@ -171,9 +177,9 @@ - (nullable CGImageRef)sd_createJXLImageWithData:(NSData *)data thumbnailSize:(C
171177 JxlBasicInfo info;
172178 status = JxlDecoderGetBasicInfo (dec, &info);
173179 if (status != JXL_DEC_SUCCESS) return nil ;
180+ CGImagePropertyOrientation exifOrientation = (CGImagePropertyOrientation)info.orientation ;
174181
175182 // colorspace
176- CGColorSpaceRef colorSpaceRef;
177183 size_t profileSize;
178184 status = JxlDecoderProcessInput (dec);
179185 if (status != JXL_DEC_COLOR_ENCODING) return nil ;
@@ -196,6 +202,76 @@ - (nullable CGImageRef)sd_createJXLImageWithData:(NSData *)data thumbnailSize:(C
196202 CGColorSpaceRetain (colorSpaceRef);
197203 }
198204
205+ // animation check
206+ BOOL hasAnimation = info.have_animation ;
207+ if (!hasAnimation || decodeFirstFrame) {
208+ status = JxlDecoderProcessInput (dec);
209+ if (status != JXL_DEC_FRAME) return nil ;
210+ CGImageRef imageRef = [self sd_createJXLImageWithDec: dec info: info colorSpace: colorSpaceRef thumbnailSize: thumbnailSize preserveAspectRatio: preserveAspectRatio];
211+ if (!imageRef) {
212+ return nil ;
213+ }
214+ #if SD_MAC
215+ UIImage *image = [[UIImage alloc ] initWithCGImage: imageRef scale: scale orientation: exifOrientation];
216+ #else
217+ UIImageOrientation orientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation: exifOrientation];
218+ UIImage *image = [[UIImage alloc ] initWithCGImage: imageRef scale: scale orientation: orientation];
219+ #endif
220+ CGImageRelease (imageRef);
221+
222+ return image;
223+ }
224+ // loop frame
225+ NSUInteger loopCount = info.animation .num_loops ;
226+ NSMutableArray <SDImageFrame *> *frames = [NSMutableArray array ];
227+ JxlFrameHeader header;
228+ do {
229+ @autoreleasepool {
230+ status = JxlDecoderProcessInput (dec);
231+ if (status != JXL_DEC_FRAME) break ;
232+ status = JxlDecoderGetFrameHeader (dec, &header);
233+ if (status != JXL_DEC_SUCCESS) continue ;
234+
235+ // frame decode
236+ NSTimeInterval duration = [self sd_frameDurationWithInfo: info header: header];
237+ CGImageRef imageRef = [self sd_createJXLImageWithDec: dec info: info colorSpace: colorSpaceRef thumbnailSize: thumbnailSize preserveAspectRatio: preserveAspectRatio];
238+ if (!imageRef) continue ;
239+ #if SD_MAC
240+ UIImage *image = [[UIImage alloc ] initWithCGImage: imageRef scale: scale orientation: exifOrientation];
241+ #else
242+ UIImageOrientation orientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation: exifOrientation];
243+ UIImage *image = [[UIImage alloc ] initWithCGImage: imageRef scale: scale orientation: orientation];
244+ #endif
245+ CGImageRelease (imageRef);
246+
247+ // Assemble frame
248+ SDImageFrame *frame = [SDImageFrame frameWithImage: image duration: duration];
249+ [frames addObject: frame];
250+ }
251+ } while (!header.is_last );
252+
253+ UIImage *animatedImage = [SDImageCoderHelper animatedImageWithFrames: frames];
254+ animatedImage.sd_imageLoopCount = loopCount;
255+ animatedImage.sd_imageFormat = SDImageFormatJPEGXL;
256+
257+ return animatedImage;
258+ }
259+
260+ - (NSTimeInterval )sd_frameDurationWithInfo : (JxlBasicInfo)info header : (JxlFrameHeader)header {
261+ // Calculate duration, this is `tick`
262+ // We need tps (tick per second) to calculate
263+ NSTimeInterval duration = (double )header.duration * info.animation .tps_denominator / info.animation .tps_numerator ;
264+ if (duration < 0.1 ) {
265+ // Should we still try to keep broswer behavior to limit 100ms ?
266+ // Like GIF/WebP ?
267+ return 0.1 ;
268+ }
269+ return duration;
270+ }
271+
272+ - (nullable CGImageRef)sd_createJXLImageWithDec : (JxlDecoder *)dec info : (JxlBasicInfo)info colorSpace : (CGColorSpaceRef)colorSpace thumbnailSize : (CGSize)thumbnailSize preserveAspectRatio : (BOOL )preserveAspectRatio CF_RETURNS_RETAINED {
273+ JxlDecoderStatus status;
274+
199275 // bitmap format
200276 BOOL hasAlpha = info.alpha_bits != 0 ;
201277 BOOL premultiplied = info.alpha_premultiplied ;
@@ -255,13 +331,13 @@ - (nullable CGImageRef)sd_createJXLImageWithData:(NSData *)data thumbnailSize:(C
255331 status = JxlDecoderProcessInput (dec);
256332 if (status != JXL_DEC_FULL_IMAGE) return nil ; // Final status
257333
258- JxlDecoderDestroy (dec);
259-
260334 // create CGImage
261335 CGDataProviderRef provider = CGDataProviderCreateWithData (NULL , buffer, bufferSize, FreeImageData);
262336 CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault ;
263337 BOOL shouldInterpolate = YES ;
264- CGImageRef imageRef = CGImageCreate (width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL , shouldInterpolate, renderingIntent);
338+ CGImageRef imageRef = CGImageCreate (width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpace, bitmapInfo, provider, NULL , shouldInterpolate, renderingIntent);
339+ CGDataProviderRelease (provider);
340+
265341 if (!imageRef) {
266342 return nil ;
267343 }
0 commit comments