diff --git a/SDWebImage/SDWebImageCompat.m b/SDWebImage/SDWebImageCompat.m index ade04be1..74f1aa64 100644 --- a/SDWebImage/SDWebImageCompat.m +++ b/SDWebImage/SDWebImageCompat.m @@ -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 diff --git a/SDWebImage/UIImage+WebP.h b/SDWebImage/UIImage+WebP.h index cd9f27b1..6d6ba89a 100644 --- a/SDWebImage/UIImage+WebP.h +++ b/SDWebImage/UIImage+WebP.h @@ -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 diff --git a/SDWebImage/UIImage+WebP.m b/SDWebImage/UIImage+WebP.m index e91c672c..89a8489c 100644 --- a/SDWebImage/UIImage+WebP.m +++ b/SDWebImage/UIImage+WebP.m @@ -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 *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 *animatedImages = [self sd_animatedImagesWithImages:images durations:durations totalDuration:duration]; - finalImage = [UIImage animatedImageWithImages:animatedImages duration:duration / 1000.0]; + NSArray *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 *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]; }