Skip to content

Commit df89239

Browse files
committed
Added the support for AVIF animation (avifs images).
Including the traditional UIAnimatedImage as well as SDAnimatedImage support
1 parent 7bba93f commit df89239

File tree

2 files changed

+191
-26
lines changed

2 files changed

+191
-26
lines changed

SDWebImageAVIFCoder/Classes/Public/SDImageAVIFCoder.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313

1414
static const SDImageFormat SDImageFormatAVIF = 15; // AV1-codec based HEIF
1515

16-
@interface SDImageAVIFCoder : NSObject <SDImageCoder>
16+
/// Supports AVIF static image and AVIFS animated image
17+
@interface SDImageAVIFCoder : NSObject <SDAnimatedImageCoder>
1718

1819
@property (nonatomic, class, readonly, nonnull) SDImageAVIFCoder *sharedCoder;
1920

SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m

Lines changed: 189 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
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>
@@ -17,7 +19,62 @@
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

Comments
 (0)