Skip to content

Commit 6726339

Browse files
committed
Re-implements the animated webp's thumbnail decoding, now looks better
1 parent f6ff82b commit 6726339

File tree

1 file changed

+25
-9
lines changed

1 file changed

+25
-9
lines changed

SDWebImageWebPCoder/Classes/SDImageWebPCoder.m

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,11 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
194194
CGColorSpaceRef colorSpace = [self sd_createColorSpaceWithDemuxer:demuxer];
195195
int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
196196
int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
197+
// Check whether we need to use thumbnail
198+
CGSize scaledSize = SDCalculateThumbnailSize(CGSizeMake(canvasWidth, canvasHeight), preserveAspectRatio, thumbnailSize);
197199

198200
if (!hasAnimation || decodeFirstFrame) {
199201
// first frame for animated webp image
200-
CGSize scaledSize = SDCalculateThumbnailSize(CGSizeMake(canvasWidth, canvasHeight), preserveAspectRatio, thumbnailSize);
201-
// Create thumbnail if need
202202
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:colorSpace scaledSize:scaledSize];
203203
CGColorSpaceRelease(colorSpace);
204204
#if SD_UIKIT || SD_WATCH
@@ -228,10 +228,11 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
228228

229229
do {
230230
@autoreleasepool {
231-
CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas:canvas iterator:iter colorSpace:colorSpace];
231+
CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas:canvas iterator:iter colorSpace:colorSpace scaledSize:scaledSize];
232232
if (!imageRef) {
233233
continue;
234234
}
235+
235236
#if SD_UIKIT || SD_WATCH
236237
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
237238
#else
@@ -399,12 +400,13 @@ - (void)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)
399400
}
400401
}
401402

402-
- (nullable CGImageRef)sd_drawnWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter colorSpace:(nonnull CGColorSpaceRef)colorSpaceRef CF_RETURNS_RETAINED {
403+
- (nullable CGImageRef)sd_drawnWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter colorSpace:(nonnull CGColorSpaceRef)colorSpaceRef scaledSize:(CGSize)scaledSize CF_RETURNS_RETAINED {
403404
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:colorSpaceRef scaledSize:CGSizeZero];
404405
if (!imageRef) {
405406
return nil;
406407
}
407408

409+
size_t canvasWidth = CGBitmapContextGetWidth(canvas);
408410
size_t canvasHeight = CGBitmapContextGetHeight(canvas);
409411
CGFloat tmpX = iter.x_offset;
410412
CGFloat tmpY = canvasHeight - iter.height - iter.y_offset;
@@ -425,6 +427,17 @@ - (nullable CGImageRef)sd_drawnWebpImageWithCanvas:(CGContextRef)canvas iterator
425427
CGContextClearRect(canvas, imageRect);
426428
}
427429

430+
// Check whether we need to use thumbnail
431+
if (!CGSizeEqualToSize(CGSizeMake(canvasWidth, canvasHeight), scaledSize)) {
432+
// Important: For Animated WebP thumbnail generation, we can not just use a scaled small canvas and draw each thumbnail frame
433+
// This works **On Theory**. However, image scale down loss details. Animated WebP use the partial pixels with blend mode / dispose method with offset, to cover previous canvas status
434+
// Because of this reason, even each frame contains small zigzag, the final animation contains visible glitch, this is not we want.
435+
// So, always create the full pixels canvas (even though this consume more RAM), after drawn on the canvas, re-scale again with the final size
436+
CGImageRef scaledImageRef = [SDImageCoderHelper CGImageCreateScaled:newImageRef size:scaledSize];
437+
CGImageRelease(newImageRef);
438+
newImageRef = scaledImageRef;
439+
}
440+
428441
return newImageRef;
429442
}
430443

@@ -899,6 +912,7 @@ - (UIImage *)safeStaticImageFrame {
899912
WebPDemuxReleaseIterator(&iter);
900913
return nil;
901914
}
915+
// Check whether we need to use thumbnail
902916
CGSize scaledSize = SDCalculateThumbnailSize(CGSizeMake(_canvasWidth, _canvasHeight), _preserveAspectRatio, _thumbnailSize);
903917
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:_colorSpace scaledSize:scaledSize];
904918
if (!imageRef) {
@@ -915,9 +929,6 @@ - (UIImage *)safeStaticImageFrame {
915929
}
916930

917931
- (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
918-
if (!_colorSpace) {
919-
_colorSpace = [self sd_createColorSpaceWithDemuxer:_demux];
920-
}
921932
if (!_canvas) {
922933
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
923934
bitmapInfo |= _hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
@@ -927,6 +938,9 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
927938
}
928939
_canvas = canvas;
929940
}
941+
if (!_colorSpace) {
942+
_colorSpace = [self sd_createColorSpaceWithDemuxer:_demux];
943+
}
930944

931945
SDWebPCoderFrame *frame = _frames[index];
932946
UIImage *image;
@@ -946,7 +960,7 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
946960
} else {
947961
// Else, this can happen when one image set to different imageViews or one loop end. So we should clear the canvas. Then draw until the canvas is ready.
948962
if (_currentBlendIndex != NSNotFound) {
949-
CGContextClearRect(_canvas, CGRectMake(0, 0, CGBitmapContextGetWidth(_canvas), CGBitmapContextGetHeight(_canvas)));
963+
CGContextClearRect(_canvas, CGRectMake(0, 0, _canvasWidth, _canvasHeight));
950964
}
951965

952966
// Then, loop from the blend from index, draw each of previous frames on the canvas.
@@ -975,7 +989,9 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
975989
_currentBlendIndex = index;
976990

977991
// Now the canvas is ready, which respects of dispose method behavior. Just do normal decoding and produce image.
978-
CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas:_canvas iterator:iter colorSpace:_colorSpace];
992+
// Check whether we need to use thumbnail
993+
CGSize scaledSize = SDCalculateThumbnailSize(CGSizeMake(_canvasWidth, _canvasHeight), _preserveAspectRatio, _thumbnailSize);
994+
CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas:_canvas iterator:iter colorSpace:_colorSpace scaledSize:scaledSize];
979995
if (!imageRef) {
980996
return nil;
981997
}

0 commit comments

Comments
 (0)