77
88#import " SDImageAVIFCoder.h"
99#import < Accelerate/Accelerate.h>
10+ #import < os/lock.h>
11+ #import < libkern/OSAtomic.h>
1012#if __has_include(<libavif/avif.h>)
1113#import < libavif/avif.h>
1214#import < libavif/internal.h>
1719
1820#import " Private/Conversion.h"
1921
20- @implementation SDImageAVIFCoder
22+ #define SD_USE_OS_UNFAIR_LOCK TARGET_OS_MACCATALYST ||\
23+ (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_10_0) ||\
24+ (__MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_12) ||\
25+ (__TV_OS_VERSION_MIN_REQUIRED >= __TVOS_10_0) ||\
26+ (__WATCH_OS_VERSION_MIN_REQUIRED >= __WATCHOS_3_0)
27+
28+ #ifndef SD_LOCK_DECLARE
29+ #if SD_USE_OS_UNFAIR_LOCK
30+ #define SD_LOCK_DECLARE (lock ) os_unfair_lock lock
31+ #else
32+ #define SD_LOCK_DECLARE (lock ) os_unfair_lock lock API_AVAILABLE (ios(10.0 ), tvos(10 ), watchos(3 ), macos(10.12 )); \
33+ OSSpinLock lock##_deprecated;
34+ #endif
35+ #endif
36+
37+ #ifndef SD_LOCK_INIT
38+ #if SD_USE_OS_UNFAIR_LOCK
39+ #define SD_LOCK_INIT (lock ) lock = OS_UNFAIR_LOCK_INIT
40+ #else
41+ #define SD_LOCK_INIT (lock ) if (@available(iOS 10 , tvOS 10 , watchOS 3 , macOS 10.12 , *)) lock = OS_UNFAIR_LOCK_INIT; \
42+ else lock##_deprecated = OS_SPINLOCK_INIT;
43+ #endif
44+ #endif
45+
46+ #ifndef SD_LOCK
47+ #if SD_USE_OS_UNFAIR_LOCK
48+ #define SD_LOCK (lock ) os_unfair_lock_lock(&lock)
49+ #else
50+ #define SD_LOCK (lock ) if (@available(iOS 10 , tvOS 10 , watchOS 3 , macOS 10.12 , *)) os_unfair_lock_lock(&lock); \
51+ else OSSpinLockLock(&lock##_deprecated);
52+ #endif
53+ #endif
54+
55+ #ifndef SD_UNLOCK
56+ #if SD_USE_OS_UNFAIR_LOCK
57+ #define SD_UNLOCK (lock ) os_unfair_lock_unlock(&lock)
58+ #else
59+ #define SD_UNLOCK (lock ) if (@available(iOS 10 , tvOS 10 , watchOS 3 , macOS 10.12 , *)) os_unfair_lock_unlock(&lock); \
60+ else OSSpinLockUnlock(&lock##_deprecated);
61+ #endif
62+ #endif
63+
64+ @implementation SDImageAVIFCoder {
65+ avifDecoder *_decoder;
66+ NSData *_imageData;
67+ CGFloat _scale;
68+ NSUInteger _loopCount;
69+ NSUInteger _frameCount;
70+ SD_LOCK_DECLARE (_lock);
71+ }
72+
73+ - (void )dealloc {
74+ if (_decoder) {
75+ avifDecoderDestroy (_decoder);
76+ }
77+ }
2178
2279+ (instancetype )sharedCoder {
2380 static SDImageAVIFCoder *coder;
@@ -44,23 +101,6 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *)
44101 }
45102 }
46103
47- // Currently only support primary image :)
48- CGImageRef imageRef = [self sd_createAVIFImageWithData: data];
49- if (!imageRef) {
50- return nil ;
51- }
52-
53- #if SD_MAC
54- UIImage *image = [[UIImage alloc ] initWithCGImage: imageRef scale: scale orientation: kCGImagePropertyOrientationUp ];
55- #else
56- UIImage *image = [[UIImage alloc ] initWithCGImage: imageRef scale: scale orientation: UIImageOrientationUp];
57- #endif
58- CGImageRelease (imageRef);
59-
60- return image;
61- }
62-
63- - (nullable CGImageRef)sd_createAVIFImageWithData : (nonnull NSData *)data CF_RETURNS_RETAINED {
64104 // Decode it
65105 avifDecoder * decoder = avifDecoderCreate ();
66106 avifDecoderSetIOMemory (decoder, data.bytes , data.length );
@@ -72,15 +112,54 @@ - (nullable CGImageRef)sd_createAVIFImageWithData:(nonnull NSData *)data CF_RETU
72112 avifDecoderDestroy (decoder);
73113 return nil ;
74114 }
75- avifResult nextImageResult = avifDecoderNextImage (decoder);
76- if (nextImageResult != AVIF_RESULT_OK || nextImageResult == AVIF_RESULT_NO_IMAGES_REMAINING) {
77- NSLog (@" Failed to decode image: %s " , avifResultToString (nextImageResult));
78- avifDecoderDestroy (decoder);
79- return nil ;
115+
116+ // Static image
117+ if (decoder->imageCount <= 1 ) {
118+ avifResult nextImageResult = avifDecoderNextImage (decoder);
119+ if (nextImageResult != AVIF_RESULT_OK) {
120+ NSLog (@" Failed to decode image: %s " , avifResultToString (nextImageResult));
121+ avifDecoderDestroy (decoder);
122+ return nil ;
123+ }
124+ CGImageRef imageRef = SDCreateCGImageFromAVIF (decoder->image );
125+ if (!imageRef) {
126+ return nil ;
127+ }
128+ #if SD_MAC
129+ UIImage *image = [[UIImage alloc ] initWithCGImage: imageRef scale: scale orientation: kCGImagePropertyOrientationUp ];
130+ #else
131+ UIImage *image = [[UIImage alloc ] initWithCGImage: imageRef scale: scale orientation: UIImageOrientationUp];
132+ #endif
133+ CGImageRelease (imageRef);
134+ return image;
135+ }
136+
137+ // Animated image
138+ NSMutableArray <SDImageFrame *> *frames = [NSMutableArray array ];
139+ while (avifDecoderNextImage (decoder) == AVIF_RESULT_OK) {
140+ @autoreleasepool {
141+ CGImageRef imageRef = SDCreateCGImageFromAVIF (decoder->image );
142+ if (!imageRef) {
143+ continue ;
144+ }
145+ #if SD_MAC
146+ UIImage *image = [[UIImage alloc ] initWithCGImage: imageRef scale: scale orientation: kCGImagePropertyOrientationUp ];
147+ #else
148+ UIImage *image = [[UIImage alloc ] initWithCGImage: imageRef scale: scale orientation: UIImageOrientationUp];
149+ #endif
150+ NSTimeInterval duration = decoder->imageTiming .duration ; // Should use `decoder->imageTiming`, not the `decoder->duration`, see libavif source code
151+ SDImageFrame *frame = [SDImageFrame frameWithImage: image duration: duration];
152+ [frames addObject: frame];
153+ }
80154 }
81- CGImageRef const image = SDCreateCGImageFromAVIF (decoder-> image );
155+
82156 avifDecoderDestroy (decoder);
83- return image;
157+
158+ UIImage *animatedImage = [SDImageCoderHelper animatedImageWithFrames: frames];
159+ animatedImage.sd_imageLoopCount = 0 ;
160+ animatedImage.sd_imageFormat = SDImageFormatAVIF;
161+
162+ return animatedImage;
84163}
85164
86165// The AVIF encoding seems slow at the current time, but at least works
@@ -195,6 +274,91 @@ - (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDIm
195274 return imageData;
196275}
197276
277+ #pragma mark - Animation
278+ - (instancetype )initWithAnimatedImageData : (NSData *)data options : (SDImageCoderOptions *)options {
279+ self = [super init ];
280+ if (self) {
281+ avifDecoder *decoder = avifDecoderCreate ();
282+ avifDecoderSetIOMemory (decoder, data.bytes , data.length );
283+ // Disable strict mode to keep some AVIF image compatible
284+ decoder->strictFlags = AVIF_STRICT_DISABLED;
285+ avifResult decodeResult = avifDecoderParse (decoder);
286+ if (decodeResult != AVIF_RESULT_OK) {
287+ avifDecoderDestroy (decoder);
288+ NSLog (@" Failed to decode image: %s " , avifResultToString (decodeResult));
289+ return nil ;
290+ }
291+ // TODO: Optimize the performance like WebPCoder (frame meta cache, etc)
292+ _frameCount = decoder->imageCount ;
293+ _loopCount = 0 ;
294+ CGFloat scale = 1 ;
295+ NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
296+ if (scaleFactor != nil ) {
297+ scale = [scaleFactor doubleValue ];
298+ if (scale < 1 ) {
299+ scale = 1 ;
300+ }
301+ }
302+ _scale = scale;
303+ _decoder = decoder;
304+ _imageData = data;
305+ SD_LOCK_INIT (_lock);
306+ }
307+ return self;
308+ }
309+
310+ - (NSData *)animatedImageData {
311+ return _imageData;
312+ }
313+
314+ - (NSUInteger )animatedImageLoopCount {
315+ return _loopCount;
316+ }
317+
318+ - (NSUInteger )animatedImageFrameCount {
319+ return _frameCount;
320+ }
321+
322+ - (NSTimeInterval )animatedImageDurationAtIndex : (NSUInteger )index {
323+ if (index >= _frameCount) {
324+ return 0 ;
325+ }
326+ if (_frameCount <= 1 ) {
327+ return 0 ;
328+ }
329+ SD_LOCK (_lock);
330+ avifImageTiming timing;
331+ avifResult decodeResult = avifDecoderNthImageTiming (_decoder, (uint32_t )index, &timing);
332+ SD_UNLOCK (_lock);
333+ if (decodeResult != AVIF_RESULT_OK) {
334+ return 0 ;
335+ }
336+ return timing.duration ;
337+ }
338+
339+ - (UIImage *)animatedImageFrameAtIndex : (NSUInteger )index {
340+ if (index >= _frameCount) {
341+ return nil ;
342+ }
343+ SD_LOCK (_lock);
344+ avifResult decodeResult = avifDecoderNthImage (_decoder, (uint32_t )index);
345+ if (decodeResult != AVIF_RESULT_OK) {
346+ return nil ;
347+ }
348+ CGImageRef imageRef = SDCreateCGImageFromAVIF (_decoder->image );
349+ SD_UNLOCK (_lock);
350+ if (!imageRef) {
351+ return nil ;
352+ }
353+ #if SD_MAC
354+ UIImage *image = [[UIImage alloc ] initWithCGImage: imageRef scale: _scale orientation: kCGImagePropertyOrientationUp ];
355+ #else
356+ UIImage *image = [[UIImage alloc ] initWithCGImage: imageRef scale: _scale orientation: UIImageOrientationUp];
357+ #endif
358+ CGImageRelease (imageRef);
359+ return image;
360+ }
361+
198362
199363#pragma mark - Helper
200364+ (BOOL )isAVIFFormatForData : (NSData *)data
0 commit comments