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:
parent
72d1968d42
commit
f6ff82be42
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue