Authored by DreamPiggy

Add animated WebP loop count and fix duration zero issue

Animated WebP image support loop count. Set this value to UIImage decoded with WebP data.
Frame duration set to 100ms if is zero
... ... @@ -8,6 +8,8 @@
#import "SDWebImageCompat.h"
#import "objc/runtime.h"
#if !__has_feature(objc_arc)
#error SDWebImage is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
#endif
... ... @@ -26,8 +28,18 @@ inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullabl
for (UIImage *tempImage in image.images) {
[scaledImages addObject:SDScaledImageForKey(key, tempImage)];
}
return [UIImage animatedImageWithImages:scaledImages duration:image.duration];
UIImage *animatedImage = [UIImage animatedImageWithImages:scaledImages duration:image.duration];
#ifdef SD_WEBP
if (animatedImage) {
SEL sd_webpLoopCount = NSSelectorFromString(@"sd_webpLoopCount");
NSNumber *value = objc_getAssociatedObject(image, sd_webpLoopCount);
NSInteger loopCount = value.integerValue;
if (loopCount) {
objc_setAssociatedObject(animatedImage, sd_webpLoopCount, @(loopCount), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}
#endif
return animatedImage;
}
else {
#if SD_WATCH
... ...
... ... @@ -12,6 +12,16 @@
@interface UIImage (WebP)
/**
* Get the current WebP image loop count, the default value is 0.
* For static WebP image, the value is 0.
* For animated WebP image, 0 means repeat the animation indefinitely.
* Note that because of the limitations of categories this property can get out of sync
* if you create another instance with CGImage or other methods.
* @return WebP image loop count
*/
- (NSInteger)sd_webpLoopCount;
+ (nullable UIImage *)sd_imageWithWebPData:(nullable NSData *)data;
@end
... ...
... ... @@ -14,6 +14,8 @@
#import "webp/demux.h"
#import "NSImage+WebCache.h"
#import "objc/runtime.h"
// Callback for CGDataProviderRelease
static void FreeImageData(void *info, const void *data, size_t size) {
free((void *)data);
... ... @@ -21,6 +23,12 @@ static void FreeImageData(void *info, const void *data, size_t size) {
@implementation UIImage (WebP)
- (NSInteger)sd_webpLoopCount
{
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_webpLoopCount));
return value.integerValue;
}
+ (nullable UIImage *)sd_imageWithWebPData:(nullable NSData *)data {
if (!data) {
return nil;
... ... @@ -51,6 +59,7 @@ static void FreeImageData(void *info, const void *data, size_t size) {
}
int frameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT);
int loopCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT);
int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
CGBitmapInfo bitmapInfo;
... ... @@ -62,7 +71,7 @@ static void FreeImageData(void *info, const void *data, size_t size) {
CGContextRef canvas = CGBitmapContextCreate(NULL, canvasWidth, canvasHeight, 8, 0, SDCGColorSpaceGetDeviceRGB(), bitmapInfo);
NSMutableArray<UIImage *> *images = [NSMutableArray array];
NSTimeInterval duration = 0;
NSTimeInterval totalDuration = 0;
int durations[frameCount];
do {
... ... @@ -78,10 +87,16 @@ static void FreeImageData(void *info, const void *data, size_t size) {
}
[images addObject:image];
duration += iter.duration;
int duration = iter.duration;
if (!duration) {
// WebP standard says duration for 0 is used for canvas updating but not showing image, but actually Chrome set this to the default 100ms duration.
// Some animated WebP images also create without duration, we should keep compatibility
duration = 100;
}
totalDuration += duration;
size_t count = images.count;
if (count) {
durations[count - 1] = iter.duration;
durations[count - 1] = duration;
}
} while (WebPDemuxNextFrame(&iter));
... ... @@ -92,8 +107,11 @@ static void FreeImageData(void *info, const void *data, size_t size) {
UIImage *finalImage = nil;
#if SD_UIKIT || SD_WATCH
NSArray<UIImage *> *animatedImages = [self sd_animatedImagesWithImages:images durations:durations totalDuration:duration];
finalImage = [UIImage animatedImageWithImages:animatedImages duration:duration / 1000.0];
NSArray<UIImage *> *animatedImages = [self sd_animatedImagesWithImages:images durations:durations totalDuration:totalDuration];
finalImage = [UIImage animatedImageWithImages:animatedImages duration:totalDuration / 1000.0];
if (finalImage) {
objc_setAssociatedObject(finalImage, @selector(sd_webpLoopCount), @(loopCount), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#elif SD_MAC
finalImage = images.firstObject;
#endif
... ... @@ -230,7 +248,12 @@ static void FreeImageData(void *info, const void *data, size_t size) {
NSMutableArray<UIImage *> *animatedImages = [NSMutableArray arrayWithCapacity:count];
[images enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
int duration = durations[idx];
int repeatCount = duration / gcd;
int repeatCount;
if (gcd) {
repeatCount = duration / gcd;
} else {
repeatCount = 1;
}
for (int i = 0; i < repeatCount; ++i) {
[animatedImages addObject:image];
}
... ...