Authored by DreamPiggy

Fixed animated webp decoder issue when image has different duration per frame

Animated webp can set duration per frame but the UIImage animate array just use the average duration for each frame
@@ -50,12 +50,20 @@ static void FreeImageData(void *info, const void *data, size_t size) { @@ -50,12 +50,20 @@ static void FreeImageData(void *info, const void *data, size_t size) {
50 return nil; 50 return nil;
51 } 51 }
52 52
53 - NSMutableArray *images = [NSMutableArray array];  
54 - NSTimeInterval duration = 0;  
55 - 53 + int frameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT);
56 int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH); 54 int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
57 int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT); 55 int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
58 - CGContextRef canvas = CGBitmapContextCreate(NULL, canvasWidth, canvasHeight, 8, 0, sd_CGColorSpaceGetDeviceRGB(), kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast); 56 + CGBitmapInfo bitmapInfo;
  57 + if (!(flags & ALPHA_FLAG)) {
  58 + bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast;
  59 + } else {
  60 + bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;
  61 + }
  62 + CGContextRef canvas = CGBitmapContextCreate(NULL, canvasWidth, canvasHeight, 8, 0, SDCGColorSpaceGetDeviceRGB(), bitmapInfo);
  63 +
  64 + NSMutableArray<UIImage *> *images = [NSMutableArray array];
  65 + NSTimeInterval duration = 0;
  66 + int durations[frameCount];
59 67
60 do { 68 do {
61 UIImage *image; 69 UIImage *image;
@@ -70,7 +78,11 @@ static void FreeImageData(void *info, const void *data, size_t size) { @@ -70,7 +78,11 @@ static void FreeImageData(void *info, const void *data, size_t size) {
70 } 78 }
71 79
72 [images addObject:image]; 80 [images addObject:image];
73 - duration += iter.duration / 1000.0f; 81 + duration += iter.duration;
  82 + size_t count = images.count;
  83 + if (count) {
  84 + durations[count - 1] = iter.duration;
  85 + }
74 86
75 } while (WebPDemuxNextFrame(&iter)); 87 } while (WebPDemuxNextFrame(&iter));
76 88
@@ -80,11 +92,10 @@ static void FreeImageData(void *info, const void *data, size_t size) { @@ -80,11 +92,10 @@ static void FreeImageData(void *info, const void *data, size_t size) {
80 92
81 UIImage *finalImage = nil; 93 UIImage *finalImage = nil;
82 #if SD_UIKIT || SD_WATCH 94 #if SD_UIKIT || SD_WATCH
83 - finalImage = [UIImage animatedImageWithImages:images duration:duration]; 95 + NSArray<UIImage *> *animatedImages = [self sd_animatedImagesWithImages:images durations:durations totalDuration:duration];
  96 + finalImage = [UIImage animatedImageWithImages:animatedImages duration:duration / 1000.0];
84 #elif SD_MAC 97 #elif SD_MAC
85 - if ([images count] > 0) {  
86 - finalImage = images[0];  
87 - } 98 + finalImage = images.firstObject;
88 #endif 99 #endif
89 return finalImage; 100 return finalImage;
90 } 101 }
@@ -185,7 +196,7 @@ static void FreeImageData(void *info, const void *data, size_t size) { @@ -185,7 +196,7 @@ static void FreeImageData(void *info, const void *data, size_t size) {
185 // Construct a UIImage from the decoded RGBA value array. 196 // Construct a UIImage from the decoded RGBA value array.
186 CGDataProviderRef provider = 197 CGDataProviderRef provider =
187 CGDataProviderCreateWithData(NULL, config.output.u.RGBA.rgba, config.output.u.RGBA.size, FreeImageData); 198 CGDataProviderCreateWithData(NULL, config.output.u.RGBA.rgba, config.output.u.RGBA.size, FreeImageData);
188 - CGColorSpaceRef colorSpaceRef = sd_CGColorSpaceGetDeviceRGB(); 199 + CGColorSpaceRef colorSpaceRef = SDCGColorSpaceGetDeviceRGB();
189 CGBitmapInfo bitmapInfo = config.input.has_alpha ? kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast : kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast; 200 CGBitmapInfo bitmapInfo = config.input.has_alpha ? kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast : kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast;
190 size_t components = config.input.has_alpha ? 4 : 3; 201 size_t components = config.input.has_alpha ? 4 : 3;
191 CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; 202 CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
@@ -203,7 +214,32 @@ static void FreeImageData(void *info, const void *data, size_t size) { @@ -203,7 +214,32 @@ static void FreeImageData(void *info, const void *data, size_t size) {
203 return image; 214 return image;
204 } 215 }
205 216
206 -static CGColorSpaceRef sd_CGColorSpaceGetDeviceRGB() { 217 ++ (NSArray<UIImage *> *)sd_animatedImagesWithImages:(NSArray<UIImage *> *)images durations:(int const * const)durations totalDuration:(NSTimeInterval)totalDuration
  218 +{
  219 + // [UIImage animatedImageWithImages:duration:] only use the average duration for per frame
  220 + // divide the total duration to implement per frame duration for animated WebP
  221 + NSUInteger count = images.count;
  222 + if (!count) {
  223 + return nil;
  224 + }
  225 + if (count == 1) {
  226 + return images;
  227 + }
  228 +
  229 + int const gcd = gcdArray(count, durations);
  230 + NSMutableArray<UIImage *> *animatedImages = [NSMutableArray arrayWithCapacity:count];
  231 + [images enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
  232 + int duration = durations[idx];
  233 + int repeatCount = duration / gcd;
  234 + for (int i = 0; i < repeatCount; ++i) {
  235 + [animatedImages addObject:image];
  236 + }
  237 + }];
  238 +
  239 + return animatedImages;
  240 +}
  241 +
  242 +static CGColorSpaceRef SDCGColorSpaceGetDeviceRGB() {
207 static CGColorSpaceRef space; 243 static CGColorSpaceRef space;
208 static dispatch_once_t onceToken; 244 static dispatch_once_t onceToken;
209 dispatch_once(&onceToken, ^{ 245 dispatch_once(&onceToken, ^{
@@ -212,6 +248,24 @@ static CGColorSpaceRef sd_CGColorSpaceGetDeviceRGB() { @@ -212,6 +248,24 @@ static CGColorSpaceRef sd_CGColorSpaceGetDeviceRGB() {
212 return space; 248 return space;
213 } 249 }
214 250
  251 +static int gcdArray(size_t const count, int const * const values) {
  252 + int result = values[0];
  253 + for (size_t i = 1; i < count; ++i) {
  254 + result = gcd(values[i], result);
  255 + }
  256 + return result;
  257 +}
  258 +
  259 +static int gcd(int a,int b) {
  260 + int c;
  261 + while (a != 0) {
  262 + c = a;
  263 + a = b % a;
  264 + b = c;
  265 + }
  266 + return b;
  267 +}
  268 +
215 @end 269 @end
216 270
217 #endif 271 #endif