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) {
return nil;
}
NSMutableArray *images = [NSMutableArray array];
NSTimeInterval duration = 0;
int frameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT);
int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
CGContextRef canvas = CGBitmapContextCreate(NULL, canvasWidth, canvasHeight, 8, 0, sd_CGColorSpaceGetDeviceRGB(), kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast);
CGBitmapInfo bitmapInfo;
if (!(flags & ALPHA_FLAG)) {
bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast;
} else {
bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;
}
CGContextRef canvas = CGBitmapContextCreate(NULL, canvasWidth, canvasHeight, 8, 0, SDCGColorSpaceGetDeviceRGB(), bitmapInfo);
NSMutableArray<UIImage *> *images = [NSMutableArray array];
NSTimeInterval duration = 0;
int durations[frameCount];
do {
UIImage *image;
... ... @@ -70,7 +78,11 @@ static void FreeImageData(void *info, const void *data, size_t size) {
}
[images addObject:image];
duration += iter.duration / 1000.0f;
duration += iter.duration;
size_t count = images.count;
if (count) {
durations[count - 1] = iter.duration;
}
} while (WebPDemuxNextFrame(&iter));
... ... @@ -80,11 +92,10 @@ static void FreeImageData(void *info, const void *data, size_t size) {
UIImage *finalImage = nil;
#if SD_UIKIT || SD_WATCH
finalImage = [UIImage animatedImageWithImages:images duration:duration];
NSArray<UIImage *> *animatedImages = [self sd_animatedImagesWithImages:images durations:durations totalDuration:duration];
finalImage = [UIImage animatedImageWithImages:animatedImages duration:duration / 1000.0];
#elif SD_MAC
if ([images count] > 0) {
finalImage = images[0];
}
finalImage = images.firstObject;
#endif
return finalImage;
}
... ... @@ -185,7 +196,7 @@ static void FreeImageData(void *info, const void *data, size_t size) {
// Construct a UIImage from the decoded RGBA value array.
CGDataProviderRef provider =
CGDataProviderCreateWithData(NULL, config.output.u.RGBA.rgba, config.output.u.RGBA.size, FreeImageData);
CGColorSpaceRef colorSpaceRef = sd_CGColorSpaceGetDeviceRGB();
CGColorSpaceRef colorSpaceRef = SDCGColorSpaceGetDeviceRGB();
CGBitmapInfo bitmapInfo = config.input.has_alpha ? kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast : kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast;
size_t components = config.input.has_alpha ? 4 : 3;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
... ... @@ -203,7 +214,32 @@ static void FreeImageData(void *info, const void *data, size_t size) {
return image;
}
static CGColorSpaceRef sd_CGColorSpaceGetDeviceRGB() {
+ (NSArray<UIImage *> *)sd_animatedImagesWithImages:(NSArray<UIImage *> *)images durations:(int const * const)durations totalDuration:(NSTimeInterval)totalDuration
{
// [UIImage animatedImageWithImages:duration:] only use the average duration for per frame
// divide the total duration to implement per frame duration for animated WebP
NSUInteger count = images.count;
if (!count) {
return nil;
}
if (count == 1) {
return images;
}
int const gcd = gcdArray(count, durations);
NSMutableArray<UIImage *> *animatedImages = [NSMutableArray arrayWithCapacity:count];
[images enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
int duration = durations[idx];
int repeatCount = duration / gcd;
for (int i = 0; i < repeatCount; ++i) {
[animatedImages addObject:image];
}
}];
return animatedImages;
}
static CGColorSpaceRef SDCGColorSpaceGetDeviceRGB() {
static CGColorSpaceRef space;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
... ... @@ -212,6 +248,24 @@ static CGColorSpaceRef sd_CGColorSpaceGetDeviceRGB() {
return space;
}
static int gcdArray(size_t const count, int const * const values) {
int result = values[0];
for (size_t i = 1; i < count; ++i) {
result = gcd(values[i], result);
}
return result;
}
static int gcd(int a,int b) {
int c;
while (a != 0) {
c = a;
a = b % a;
b = c;
}
return b;
}
@end
#endif
... ...