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
Showing
1 changed file
with
65 additions
and
11 deletions
@@ -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 |
-
Please register or login to post a comment