Skip to content

Commit 91ebf46

Browse files
authored
Merge pull request #31 from SDWebImage/feature_support_avifs_animation
Feature support avifs animation
2 parents e4718c6 + d66afa7 commit 91ebf46

File tree

7 files changed

+207
-38
lines changed

7 files changed

+207
-38
lines changed

Example/SDWebImageAVIFCoder/SDViewController.m

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ - (void)viewDidLoad
2323
SDImageAVIFCoder *AVIFCoder = [SDImageAVIFCoder sharedCoder];
2424
[[SDImageCodersManager sharedManager] addCoder:AVIFCoder];
2525
NSURL *AVIFURL = [NSURL URLWithString:@"https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile0.8bpc.yuv420.avif"];
26-
NSURL *HDRAVIFURL = [NSURL URLWithString:@"https://raw.githubusercontent.com/link-u/avif-sample-images/master/hato.profile2.12bpc.yuv422.avif"];
26+
// NSURL *HDRAVIFURL = [NSURL URLWithString:@"https://raw.githubusercontent.com/link-u/avif-sample-images/master/hato.profile2.12bpc.yuv422.avif"];
27+
NSURL *animatedAVIFSURL = [NSURL URLWithString:@"https://raw.githubusercontent.com/link-u/avif-sample-images/master/star-12bpc-with-alpha.avifs"];
2728
CGSize screenSize = [UIScreen mainScreen].bounds.size;
2829

2930
UIImageView *imageView1 = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, screenSize.width, screenSize.height / 2)];
30-
UIImageView *imageView2 = [[UIImageView alloc] initWithFrame:CGRectMake(0, screenSize.height / 2, screenSize.width, screenSize.height / 2)];
31+
SDAnimatedImageView *imageView2 = [[SDAnimatedImageView alloc] initWithFrame:CGRectMake(0, screenSize.height / 2, screenSize.width, screenSize.height / 2)];
3132

3233
[self.view addSubview:imageView1];
3334
[self.view addSubview:imageView2];
@@ -43,10 +44,9 @@ - (void)viewDidLoad
4344
});
4445
}
4546
}];
46-
[imageView2 sd_setImageWithURL:HDRAVIFURL completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
47-
// 10-bit HDR
47+
[imageView2 sd_setImageWithURL:animatedAVIFSURL completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
4848
if (image) {
49-
NSLog(@"HDR AVIF load success");
49+
NSLog(@"Animated AVIFS load success");
5050
}
5151
}];
5252
// Do any additional setup after loading the view, typically from a nib.

Example/SDWebImageAVIFCoder_Example macOS/ViewController.m

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@ - (void)viewDidLoad {
1717

1818
SDImageAVIFCoder *AVIFCoder = [SDImageAVIFCoder sharedCoder];
1919
[[SDImageCodersManager sharedManager] addCoder:AVIFCoder];
20-
NSURL *AVIFURL = [NSURL URLWithString:@"https://raw.githubusercontent.com/AOMediaCodec/av1-avif/master/testFiles/Microsoft/kids_720p.avif"];
21-
NSURL *HDRAVIFURL = [NSURL URLWithString:@"https://raw.githubusercontent.com/AOMediaCodec/av1-avif/master/testFiles/Microsoft/Chimera_10bit_cropped_to_1920x1008.avif"];
20+
NSURL *AVIFURL = [NSURL URLWithString:@"https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile0.8bpc.yuv420.avif"];
21+
// NSURL *HDRAVIFURL = [NSURL URLWithString:@"https://raw.githubusercontent.com/link-u/avif-sample-images/master/hato.profile2.12bpc.yuv422.avif"];
22+
NSURL *animatedAVIFSURL = [NSURL URLWithString:@"https://raw.githubusercontent.com/link-u/avif-sample-images/master/star-12bpc-with-alpha.avifs"];
2223

2324
CGSize screenSize = self.view.bounds.size;
2425

2526
UIImageView *imageView1 = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, screenSize.width / 2, screenSize.height)];
2627
imageView1.imageScaling = NSImageScaleProportionallyUpOrDown;
2728

28-
UIImageView *imageView2 = [[UIImageView alloc] initWithFrame:CGRectMake(screenSize.width / 2, 0, screenSize.width / 2, screenSize.height)];
29+
SDAnimatedImageView *imageView2 = [[SDAnimatedImageView alloc] initWithFrame:CGRectMake(screenSize.width / 2, 0, screenSize.width / 2, screenSize.height)];
2930
imageView2.imageScaling = NSImageScaleProportionallyUpOrDown;
3031

3132
[self.view addSubview:imageView1];
@@ -42,10 +43,9 @@ - (void)viewDidLoad {
4243
});
4344
}
4445
}];
45-
[imageView2 sd_setImageWithURL:HDRAVIFURL completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
46-
// 10-bit HDR
46+
[imageView2 sd_setImageWithURL:animatedAVIFSURL completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
4747
if (image) {
48-
NSLog(@"HDR AVIF load success");
48+
NSLog(@"Animated AVIFS load success");
4949
}
5050
}];
5151
}

Example/Screenshot/AVIFDemo-iOS.png

-1.44 MB
Loading

Example/Screenshot/AVIFDemo-macOS.png

-326 KB
Loading

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@
1111

1212
This is a [SDWebImage](https://github.com/rs/SDWebImage) coder plugin to add [AV1 Image File Format (AVIF)](https://aomediacodec.github.io/av1-avif/) support. Which is built based on the open-sourced [libavif](https://github.com/AOMediaCodec/libavif) codec.
1313

14-
This AVIF coder plugin currently support AVIF still image **decoding**. Including alpha channel, as well as 10bit/12bit/16bit HDR images.
14+
This AVIF coder plugin support AVIF still image. Including alpha channel, as well as 10bit/12bit/16bit HDR images.
15+
16+
And, the new 0.9.0+ version add the support for AVIF sequence animated image! Including alpha channel, as well as 10bit/12bit/16bit HDR images.
1517

1618
The AVIF encoding is also supported now. Which always encode as 8-bit depth images.
1719

20+
See the demo for the more showcase.
21+
1822
## Note
1923

2024
AVIF image spec is still in evolve. And the current upstream AVIF codec is a simple implementation. The encoding time may be long for large images.

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)