14
14
@import libjxl;
15
15
#endif
16
16
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)))
19
29
20
30
static void FreeImageData (void *info, const void *data, size_t size) {
21
31
free ((void *)data);
@@ -108,6 +118,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *)
108
118
if (!data) {
109
119
return nil ;
110
120
}
121
+ BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue ];
111
122
CGFloat scale = 1 ;
112
123
if ([options valueForKey: SDImageCoderDecodeScaleFactor]) {
113
124
scale = [[options valueForKey: SDImageCoderDecodeScaleFactor] doubleValue ];
@@ -132,25 +143,20 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *)
132
143
preserveAspectRatio = preserveAspectRatioValue.boolValue ;
133
144
}
134
145
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
+ };
152
157
153
- JxlDecoder *dec = JxlDecoderCreate (NULL );
158
+ // Get basic info
159
+ dec = JxlDecoderCreate (NULL );
154
160
if (!dec) return nil ;
155
161
156
162
// feed data
@@ -160,7 +166,7 @@ - (nullable CGImageRef)sd_createJXLImageWithData:(NSData *)data thumbnailSize:(C
160
166
// note: when using `JxlDecoderSubscribeEvents` libjxl behaves likes incremental decoding
161
167
// which need event loop to get latest status via `JxlDecoderProcessInput`
162
168
// 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);
164
170
if (status != JXL_DEC_SUCCESS) return nil ;
165
171
166
172
// decode it
@@ -171,9 +177,9 @@ - (nullable CGImageRef)sd_createJXLImageWithData:(NSData *)data thumbnailSize:(C
171
177
JxlBasicInfo info;
172
178
status = JxlDecoderGetBasicInfo (dec, &info);
173
179
if (status != JXL_DEC_SUCCESS) return nil ;
180
+ CGImagePropertyOrientation exifOrientation = (CGImagePropertyOrientation)info.orientation ;
174
181
175
182
// colorspace
176
- CGColorSpaceRef colorSpaceRef;
177
183
size_t profileSize;
178
184
status = JxlDecoderProcessInput (dec);
179
185
if (status != JXL_DEC_COLOR_ENCODING) return nil ;
@@ -196,6 +202,76 @@ - (nullable CGImageRef)sd_createJXLImageWithData:(NSData *)data thumbnailSize:(C
196
202
CGColorSpaceRetain (colorSpaceRef);
197
203
}
198
204
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
+
199
275
// bitmap format
200
276
BOOL hasAlpha = info.alpha_bits != 0 ;
201
277
BOOL premultiplied = info.alpha_premultiplied ;
@@ -255,13 +331,13 @@ - (nullable CGImageRef)sd_createJXLImageWithData:(NSData *)data thumbnailSize:(C
255
331
status = JxlDecoderProcessInput (dec);
256
332
if (status != JXL_DEC_FULL_IMAGE) return nil ; // Final status
257
333
258
- JxlDecoderDestroy (dec);
259
-
260
334
// create CGImage
261
335
CGDataProviderRef provider = CGDataProviderCreateWithData (NULL , buffer, bufferSize, FreeImageData);
262
336
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault ;
263
337
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
+
265
341
if (!imageRef) {
266
342
return nil ;
267
343
}
0 commit comments