The Animated WebP should not use the scaled canvas size, which will cause the draw frame contains Jagged and buggy. Instead, use the full pixels canvas to draw, scale down each frame after drawn (sadlly)

This commit is contained in:
DreamPiggy 2020-01-18 00:36:47 +08:00
parent 72d1968d42
commit f6ff82be42
1 changed files with 33 additions and 52 deletions

View File

@ -96,8 +96,8 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio
BOOL _hasAnimation; BOOL _hasAnimation;
BOOL _hasAlpha; BOOL _hasAlpha;
BOOL _finished; BOOL _finished;
CGFloat _canvasWidth; // Full Size without thumbnail scale CGFloat _canvasWidth;
CGFloat _canvasHeight; // Full Size without thumbnail scale CGFloat _canvasHeight;
dispatch_semaphore_t _lock; dispatch_semaphore_t _lock;
NSUInteger _currentBlendIndex; NSUInteger _currentBlendIndex;
BOOL _preserveAspectRatio; BOOL _preserveAspectRatio;
@ -192,10 +192,14 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio
return nil; return nil;
} }
CGColorSpaceRef colorSpace = [self sd_createColorSpaceWithDemuxer:demuxer]; CGColorSpaceRef colorSpace = [self sd_createColorSpaceWithDemuxer:demuxer];
int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
if (!hasAnimation || decodeFirstFrame) { if (!hasAnimation || decodeFirstFrame) {
// first frame for animated webp image // first frame for animated webp image
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:colorSpace preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize]; 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); CGColorSpaceRelease(colorSpace);
#if SD_UIKIT || SD_WATCH #if SD_UIKIT || SD_WATCH
UIImage *firstFrameImage = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp]; UIImage *firstFrameImage = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
@ -209,15 +213,10 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio
return firstFrameImage; return firstFrameImage;
} }
int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
BOOL hasAlpha = flags & ALPHA_FLAG; BOOL hasAlpha = flags & ALPHA_FLAG;
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host; CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst; bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
CGContextRef canvas = CGBitmapContextCreate(NULL, canvasWidth, canvasHeight, 8, 0, [SDImageCoderHelper colorSpaceGetDeviceRGB], bitmapInfo);
CGSize canvasFullSize = CGSizeMake(canvasWidth, canvasHeight);
CGSize canvasSize = SDCalculateThumbnailSize(canvasFullSize, preserveAspectRatio, thumbnailSize);
CGContextRef canvas = CGBitmapContextCreate(NULL, canvasSize.width, canvasSize.height, 8, 0, [SDImageCoderHelper colorSpaceGetDeviceRGB], bitmapInfo);
if (!canvas) { if (!canvas) {
WebPDemuxDelete(demuxer); WebPDemuxDelete(demuxer);
CGColorSpaceRelease(colorSpace); CGColorSpaceRelease(colorSpace);
@ -229,7 +228,7 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio
do { do {
@autoreleasepool { @autoreleasepool {
CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas:canvas iterator:iter colorSpace:colorSpace preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize canvasFullSize:canvasFullSize]; CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas:canvas iterator:iter colorSpace:colorSpace];
if (!imageRef) { if (!imageRef) {
continue; continue;
} }
@ -377,22 +376,16 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio
return image; return image;
} }
- (void)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter colorSpace:(nonnull CGColorSpaceRef)colorSpaceRef preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize canvasFullSize:(CGSize)canvasFullSize { - (void)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter colorSpace:(nonnull CGColorSpaceRef)colorSpaceRef {
size_t canvasWidth = CGBitmapContextGetWidth(canvas);
size_t canvasHeight = CGBitmapContextGetHeight(canvas); size_t canvasHeight = CGBitmapContextGetHeight(canvas);
CGFloat xScale = canvasWidth / canvasFullSize.width; CGFloat tmpX = iter.x_offset;
CGFloat yScale = canvasHeight / canvasFullSize.height; CGFloat tmpY = canvasHeight - iter.height - iter.y_offset;
CGRect imageRect = CGRectMake(tmpX, tmpY, iter.width, iter.height);
CGFloat tmpX = iter.x_offset * xScale;
CGFloat tmpY = (canvasFullSize.height - iter.height - iter.y_offset) * yScale;
CGFloat tmpWidth = iter.width * xScale;
CGFloat tmpHeight = iter.height * yScale;
CGRect imageRect = CGRectMake(tmpX, tmpY, tmpWidth, tmpHeight);
if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) { if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
CGContextClearRect(canvas, imageRect); CGContextClearRect(canvas, imageRect);
} else { } else {
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:colorSpaceRef preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize]; CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:colorSpaceRef scaledSize:CGSizeZero];
if (!imageRef) { if (!imageRef) {
return; return;
} }
@ -406,22 +399,17 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio
} }
} }
- (nullable CGImageRef)sd_drawnWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter colorSpace:(nonnull CGColorSpaceRef)colorSpaceRef preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize canvasFullSize:(CGSize)canvasFullSize CF_RETURNS_RETAINED { - (nullable CGImageRef)sd_drawnWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter colorSpace:(nonnull CGColorSpaceRef)colorSpaceRef CF_RETURNS_RETAINED {
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:colorSpaceRef preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize]; CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:colorSpaceRef scaledSize:CGSizeZero];
if (!imageRef) { if (!imageRef) {
return nil; return nil;
} }
size_t canvasWidth = CGBitmapContextGetWidth(canvas);
size_t canvasHeight = CGBitmapContextGetHeight(canvas); size_t canvasHeight = CGBitmapContextGetHeight(canvas);
CGFloat xScale = canvasWidth / canvasFullSize.width; CGFloat tmpX = iter.x_offset;
CGFloat yScale = canvasHeight / canvasFullSize.height; CGFloat tmpY = canvasHeight - iter.height - iter.y_offset;
CGRect imageRect = CGRectMake(tmpX, tmpY, iter.width, iter.height);
CGFloat tmpX = iter.x_offset * xScale;
CGFloat tmpY = (canvasFullSize.height - iter.height - iter.y_offset) * yScale;
CGFloat tmpWidth = iter.width * xScale;
CGFloat tmpHeight = iter.height * yScale;
CGRect imageRect = CGRectMake(tmpX, tmpY, tmpWidth, tmpHeight);
BOOL shouldBlend = iter.blend_method == WEBP_MUX_BLEND; BOOL shouldBlend = iter.blend_method == WEBP_MUX_BLEND;
// If not blend, cover the target image rect. (firstly clear then draw) // If not blend, cover the target image rect. (firstly clear then draw)
@ -432,7 +420,7 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio
CGImageRef newImageRef = CGBitmapContextCreateImage(canvas); CGImageRef newImageRef = CGBitmapContextCreateImage(canvas);
CGImageRelease(imageRef); CGImageRelease(imageRef);
if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) { if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
CGContextClearRect(canvas, imageRect); CGContextClearRect(canvas, imageRect);
} }
@ -440,7 +428,7 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio
return newImageRef; return newImageRef;
} }
- (nullable CGImageRef)sd_createWebpImageWithData:(WebPData)webpData colorSpace:(nonnull CGColorSpaceRef)colorSpaceRef preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize CF_RETURNS_RETAINED { - (nullable CGImageRef)sd_createWebpImageWithData:(WebPData)webpData colorSpace:(nonnull CGColorSpaceRef)colorSpaceRef scaledSize:(CGSize)scaledSize CF_RETURNS_RETAINED {
WebPDecoderConfig config; WebPDecoderConfig config;
if (!WebPInitDecoderConfig(&config)) { if (!WebPInitDecoderConfig(&config)) {
return nil; return nil;
@ -458,14 +446,11 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio
config.options.use_threads = 1; config.options.use_threads = 1;
config.output.colorspace = MODE_bgrA; config.output.colorspace = MODE_bgrA;
int width = config.input.width; // Use scaling for thumbnail
int height = config.input.height; if (scaledSize.width != 0 && scaledSize.height != 0) {
CGSize resultSize = SDCalculateThumbnailSize(CGSizeMake(width, height), preserveAspectRatio, thumbnailSize);
if (resultSize.width != width || resultSize.height != height) {
// Use scaling
config.options.use_scaling = 1; config.options.use_scaling = 1;
config.options.scaled_width = resultSize.width; config.options.scaled_width = scaledSize.width;
config.options.scaled_height = resultSize.height; config.options.scaled_height = scaledSize.height;
} }
// Decode the WebP image data into a RGBA value array // Decode the WebP image data into a RGBA value array
@ -473,19 +458,16 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio
return nil; return nil;
} }
if (config.options.use_scaling) {
width = config.options.scaled_width;
height = config.options.scaled_height;
}
// Construct a UIImage from the decoded RGBA value array // Construct a UIImage from the decoded RGBA value array
CGDataProviderRef provider = CGDataProviderRef provider =
CGDataProviderCreateWithData(NULL, config.output.u.RGBA.rgba, config.output.u.RGBA.size, FreeImageData); CGDataProviderCreateWithData(NULL, config.output.u.RGBA.rgba, config.output.u.RGBA.size, FreeImageData);
size_t bitsPerComponent = 8; size_t bitsPerComponent = 8;
size_t bitsPerPixel = 32; size_t bitsPerPixel = 32;
size_t bytesPerRow = config.output.u.RGBA.stride; size_t bytesPerRow = config.output.u.RGBA.stride;
size_t width = config.output.width;
size_t height = config.output.height;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
CGImageRef imageRef = CGImageCreate(resultSize.width, resultSize.height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent); CGImageRef imageRef = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);
CGDataProviderRelease(provider); CGDataProviderRelease(provider);
@ -917,7 +899,8 @@ static void FreeImageData(void *info, const void *data, size_t size) {
WebPDemuxReleaseIterator(&iter); WebPDemuxReleaseIterator(&iter);
return nil; return nil;
} }
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:_colorSpace preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize]; CGSize scaledSize = SDCalculateThumbnailSize(CGSizeMake(_canvasWidth, _canvasHeight), _preserveAspectRatio, _thumbnailSize);
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:_colorSpace scaledSize:scaledSize];
if (!imageRef) { if (!imageRef) {
return nil; return nil;
} }
@ -935,12 +918,10 @@ static void FreeImageData(void *info, const void *data, size_t size) {
if (!_colorSpace) { if (!_colorSpace) {
_colorSpace = [self sd_createColorSpaceWithDemuxer:_demux]; _colorSpace = [self sd_createColorSpaceWithDemuxer:_demux];
} }
CGSize canvasFullSize = CGSizeMake(_canvasWidth, _canvasHeight);
if (!_canvas) { if (!_canvas) {
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host; CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= _hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst; bitmapInfo |= _hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
CGSize canvasSize = SDCalculateThumbnailSize(canvasFullSize, _preserveAspectRatio, _thumbnailSize); CGContextRef canvas = CGBitmapContextCreate(NULL, _canvasWidth, _canvasHeight, 8, 0, [SDImageCoderHelper colorSpaceGetDeviceRGB], bitmapInfo);
CGContextRef canvas = CGBitmapContextCreate(NULL, canvasSize.width, canvasSize.height, 8, 0, [SDImageCoderHelper colorSpaceGetDeviceRGB], bitmapInfo);
if (!canvas) { if (!canvas) {
return nil; return nil;
} }
@ -981,7 +962,7 @@ static void FreeImageData(void *info, const void *data, size_t size) {
if (endIndex > startIndex) { if (endIndex > startIndex) {
do { do {
@autoreleasepool { @autoreleasepool {
[self sd_blendWebpImageWithCanvas:_canvas iterator:iter colorSpace:_colorSpace preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize canvasFullSize:canvasFullSize]; [self sd_blendWebpImageWithCanvas:_canvas iterator:iter colorSpace:_colorSpace];
} }
} while ((size_t)iter.frame_num < endIndex && WebPDemuxNextFrame(&iter)); } while ((size_t)iter.frame_num < endIndex && WebPDemuxNextFrame(&iter));
} }
@ -994,7 +975,7 @@ static void FreeImageData(void *info, const void *data, size_t size) {
_currentBlendIndex = index; _currentBlendIndex = index;
// Now the canvas is ready, which respects of dispose method behavior. Just do normal decoding and produce image. // 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 preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize canvasFullSize:canvasFullSize]; CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas:_canvas iterator:iter colorSpace:_colorSpace];
if (!imageRef) { if (!imageRef) {
return nil; return nil;
} }