Re-implements the animated webp's thumbnail decoding, now looks better

This commit is contained in:
DreamPiggy 2020-01-18 01:01:39 +08:00
parent f6ff82be42
commit 6726339690
1 changed files with 25 additions and 9 deletions

View File

@ -194,11 +194,11 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio
CGColorSpaceRef colorSpace = [self sd_createColorSpaceWithDemuxer:demuxer];
int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
// Check whether we need to use thumbnail
CGSize scaledSize = SDCalculateThumbnailSize(CGSizeMake(canvasWidth, canvasHeight), preserveAspectRatio, thumbnailSize);
if (!hasAnimation || decodeFirstFrame) {
// first frame for animated webp image
CGSize scaledSize = SDCalculateThumbnailSize(CGSizeMake(canvasWidth, canvasHeight), preserveAspectRatio, thumbnailSize);
// Create thumbnail if need
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:colorSpace scaledSize:scaledSize];
CGColorSpaceRelease(colorSpace);
#if SD_UIKIT || SD_WATCH
@ -228,10 +228,11 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio
do {
@autoreleasepool {
CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas:canvas iterator:iter colorSpace:colorSpace];
CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas:canvas iterator:iter colorSpace:colorSpace scaledSize:scaledSize];
if (!imageRef) {
continue;
}
#if SD_UIKIT || SD_WATCH
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
#else
@ -399,12 +400,13 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio
}
}
- (nullable CGImageRef)sd_drawnWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter colorSpace:(nonnull CGColorSpaceRef)colorSpaceRef CF_RETURNS_RETAINED {
- (nullable CGImageRef)sd_drawnWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter colorSpace:(nonnull CGColorSpaceRef)colorSpaceRef scaledSize:(CGSize)scaledSize CF_RETURNS_RETAINED {
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:colorSpaceRef scaledSize:CGSizeZero];
if (!imageRef) {
return nil;
}
size_t canvasWidth = CGBitmapContextGetWidth(canvas);
size_t canvasHeight = CGBitmapContextGetHeight(canvas);
CGFloat tmpX = iter.x_offset;
CGFloat tmpY = canvasHeight - iter.height - iter.y_offset;
@ -425,6 +427,17 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio
CGContextClearRect(canvas, imageRect);
}
// Check whether we need to use thumbnail
if (!CGSizeEqualToSize(CGSizeMake(canvasWidth, canvasHeight), scaledSize)) {
// Important: For Animated WebP thumbnail generation, we can not just use a scaled small canvas and draw each thumbnail frame
// 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
// Because of this reason, even each frame contains small zigzag, the final animation contains visible glitch, this is not we want.
// 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
CGImageRef scaledImageRef = [SDImageCoderHelper CGImageCreateScaled:newImageRef size:scaledSize];
CGImageRelease(newImageRef);
newImageRef = scaledImageRef;
}
return newImageRef;
}
@ -899,6 +912,7 @@ static void FreeImageData(void *info, const void *data, size_t size) {
WebPDemuxReleaseIterator(&iter);
return nil;
}
// Check whether we need to use thumbnail
CGSize scaledSize = SDCalculateThumbnailSize(CGSizeMake(_canvasWidth, _canvasHeight), _preserveAspectRatio, _thumbnailSize);
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:_colorSpace scaledSize:scaledSize];
if (!imageRef) {
@ -915,9 +929,6 @@ static void FreeImageData(void *info, const void *data, size_t size) {
}
- (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
if (!_colorSpace) {
_colorSpace = [self sd_createColorSpaceWithDemuxer:_demux];
}
if (!_canvas) {
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= _hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
@ -927,6 +938,9 @@ static void FreeImageData(void *info, const void *data, size_t size) {
}
_canvas = canvas;
}
if (!_colorSpace) {
_colorSpace = [self sd_createColorSpaceWithDemuxer:_demux];
}
SDWebPCoderFrame *frame = _frames[index];
UIImage *image;
@ -946,7 +960,7 @@ static void FreeImageData(void *info, const void *data, size_t size) {
} else {
// 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.
if (_currentBlendIndex != NSNotFound) {
CGContextClearRect(_canvas, CGRectMake(0, 0, CGBitmapContextGetWidth(_canvas), CGBitmapContextGetHeight(_canvas)));
CGContextClearRect(_canvas, CGRectMake(0, 0, _canvasWidth, _canvasHeight));
}
// Then, loop from the blend from index, draw each of previous frames on the canvas.
@ -975,7 +989,9 @@ static void FreeImageData(void *info, const void *data, size_t size) {
_currentBlendIndex = index;
// Now the canvas is ready, which respects of dispose method behavior. Just do normal decoding and produce image.
CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas:_canvas iterator:iter colorSpace:_colorSpace];
// Check whether we need to use thumbnail
CGSize scaledSize = SDCalculateThumbnailSize(CGSizeMake(_canvasWidth, _canvasHeight), _preserveAspectRatio, _thumbnailSize);
CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas:_canvas iterator:iter colorSpace:_colorSpace scaledSize:scaledSize];
if (!imageRef) {
return nil;
}