Fix the issue that animated image (which use a canvas) should also scale the canvas size

This commit is contained in:
DreamPiggy 2020-01-17 16:12:27 +08:00
parent 9db3358eb0
commit 50136be56b
1 changed files with 63 additions and 41 deletions

View File

@ -24,6 +24,38 @@
#import <Accelerate/Accelerate.h> #import <Accelerate/Accelerate.h>
/// Calculate the actual thumnail pixel size
static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio, CGSize thumbnailSize) {
CGFloat width = fullSize.width;
CGFloat height = fullSize.height;
CGFloat resultWidth;
CGFloat resultHeight;
if (width == 0 || height == 0 || thumbnailSize.width == 0 || thumbnailSize.height == 0 || (width <= thumbnailSize.width && height <= thumbnailSize.height)) {
// Full Pixel
resultWidth = width;
resultHeight = height;
} else {
// Thumbnail
if (preserveAspectRatio) {
CGFloat pixelRatio = width / height;
CGFloat thumbnailRatio = thumbnailSize.width / thumbnailSize.height;
if (pixelRatio > thumbnailRatio) {
resultWidth = thumbnailSize.width;
resultHeight = ceil(thumbnailSize.width / pixelRatio);
} else {
resultHeight = thumbnailSize.height;
resultWidth = ceil(thumbnailSize.height * pixelRatio);
}
} else {
resultWidth = thumbnailSize.width;
resultHeight = thumbnailSize.height;
}
}
return CGSizeMake(resultWidth, resultHeight);
}
#ifndef SD_LOCK #ifndef SD_LOCK
#define SD_LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); #define SD_LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
#endif #endif
@ -64,8 +96,6 @@
BOOL _hasAnimation; BOOL _hasAnimation;
BOOL _hasAlpha; BOOL _hasAlpha;
BOOL _finished; BOOL _finished;
CGFloat _canvasWidth;
CGFloat _canvasHeight;
dispatch_semaphore_t _lock; dispatch_semaphore_t _lock;
NSUInteger _currentBlendIndex; NSUInteger _currentBlendIndex;
BOOL _preserveAspectRatio; BOOL _preserveAspectRatio;
@ -159,7 +189,7 @@
WebPDemuxDelete(demuxer); WebPDemuxDelete(demuxer);
return nil; return nil;
} }
CGColorSpaceRef colorSpace = [self sd_colorSpaceWithDemuxer:demuxer]; CGColorSpaceRef colorSpace = [self sd_createColorSpaceWithDemuxer:demuxer];
if (!hasAnimation || decodeFirstFrame) { if (!hasAnimation || decodeFirstFrame) {
// first frame for animated webp image // first frame for animated webp image
@ -177,19 +207,14 @@
return firstFrameImage; return firstFrameImage;
} }
int loopCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT); CGContextRef canvas = [self sd_createCanvasWithDemuxer:demuxer colorSpace:colorSpace preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize];
int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
BOOL hasAlpha = flags & ALPHA_FLAG;
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
CGContextRef canvas = CGBitmapContextCreate(NULL, canvasWidth, canvasHeight, 8, 0, [SDImageCoderHelper colorSpaceGetDeviceRGB], bitmapInfo);
if (!canvas) { if (!canvas) {
WebPDemuxDelete(demuxer); WebPDemuxDelete(demuxer);
CGColorSpaceRelease(colorSpace); CGColorSpaceRelease(colorSpace);
return nil; return nil;
} }
int loopCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT);
NSMutableArray<SDImageFrame *> *frames = [NSMutableArray array]; NSMutableArray<SDImageFrame *> *frames = [NSMutableArray array];
do { do {
@ -305,7 +330,7 @@
return nil; return nil;
} }
CGContextRef canvas = CGBitmapContextCreate(NULL, width, height, 8, 0, [SDImageCoderHelper colorSpaceGetDeviceRGB], bitmapInfo); CGContextRef canvas = [self sd_createCanvasWithDemuxer:_demux colorSpace:colorSpaceRef preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize];
if (!canvas) { if (!canvas) {
CGImageRelease(imageRef); CGImageRelease(imageRef);
return nil; return nil;
@ -413,25 +438,12 @@
int width = config.input.width; int width = config.input.width;
int height = config.input.height; int height = config.input.height;
if (width == 0 || height == 0 || thumbnailSize.width == 0 || thumbnailSize.height == 0 || (width <= thumbnailSize.width && height <= thumbnailSize.height)) { CGSize resultSize = SDCalculateThumbnailSize(CGSizeMake(width, height), preserveAspectRatio, thumbnailSize);
// Full Pixel if (resultSize.width != width || resultSize.height != height) {
} else { // Use scaling
// Thumbnail
config.options.use_scaling = 1; config.options.use_scaling = 1;
if (preserveAspectRatio) { config.options.scaled_width = resultSize.width;
CGFloat pixelRatio = (CGFloat)width / (CGFloat)height; config.options.scaled_height = resultSize.height;
CGFloat thumbnailRatio = thumbnailSize.width / thumbnailSize.height;
if (pixelRatio > thumbnailRatio) {
config.options.scaled_width = thumbnailSize.width;
config.options.scaled_height = thumbnailSize.width / pixelRatio;
} else {
config.options.scaled_height = thumbnailSize.height;
config.options.scaled_width = thumbnailSize.height * pixelRatio;
}
} else {
config.options.scaled_width = thumbnailSize.width;
config.options.scaled_height = thumbnailSize.height;
}
} }
// Decode the WebP image data into a RGBA value array // Decode the WebP image data into a RGBA value array
@ -451,7 +463,7 @@
size_t bitsPerPixel = 32; size_t bitsPerPixel = 32;
size_t bytesPerRow = config.output.u.RGBA.stride; size_t bytesPerRow = config.output.u.RGBA.stride;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
CGImageRef imageRef = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent); CGImageRef imageRef = CGImageCreate(resultSize.width, resultSize.height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);
CGDataProviderRelease(provider); CGDataProviderRelease(provider);
@ -469,7 +481,7 @@
} }
// Create and return the correct colorspace by checking the ICC Profile // Create and return the correct colorspace by checking the ICC Profile
- (nonnull CGColorSpaceRef)sd_colorSpaceWithDemuxer:(nonnull WebPDemuxer *)demuxer CF_RETURNS_RETAINED { - (nonnull CGColorSpaceRef)sd_createColorSpaceWithDemuxer:(nonnull WebPDemuxer *)demuxer CF_RETURNS_RETAINED {
// WebP contains ICC Profile should use the desired colorspace, instead of default device colorspace // WebP contains ICC Profile should use the desired colorspace, instead of default device colorspace
// See: https://developers.google.com/speed/webp/docs/riff_container#color_profile // See: https://developers.google.com/speed/webp/docs/riff_container#color_profile
@ -508,6 +520,20 @@
return colorSpaceRef; return colorSpaceRef;
} }
- (CGContextRef)sd_createCanvasWithDemuxer:(nonnull WebPDemuxer *)demuxer colorSpace:(nonnull CGColorSpaceRef)colorSpace preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize CF_RETURNS_RETAINED {
uint32_t flags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS);
int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
BOOL hasAlpha = flags & ALPHA_FLAG;
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
CGSize canvasSize = SDCalculateThumbnailSize(CGSizeMake(canvasWidth, canvasHeight), preserveAspectRatio, thumbnailSize);
CGContextRef canvas = CGBitmapContextCreate(NULL, canvasSize.width, canvasSize.height, 8, 0, colorSpace, bitmapInfo);
return canvas;
}
#pragma mark - Encode #pragma mark - Encode
- (BOOL)canEncodeToFormat:(SDImageFormat)format { - (BOOL)canEncodeToFormat:(SDImageFormat)format {
return (format == SDImageFormatWebP); return (format == SDImageFormatWebP);
@ -784,8 +810,6 @@ static void FreeImageData(void *info, const void *data, size_t size) {
_hasAnimation = hasAnimation; _hasAnimation = hasAnimation;
_hasAlpha = hasAlpha; _hasAlpha = hasAlpha;
_canvasWidth = canvasWidth;
_canvasHeight = canvasHeight;
_frameCount = frameCount; _frameCount = frameCount;
_loopCount = loopCount; _loopCount = loopCount;
@ -875,7 +899,7 @@ static void FreeImageData(void *info, const void *data, size_t size) {
- (UIImage *)safeStaticImageFrame { - (UIImage *)safeStaticImageFrame {
UIImage *image; UIImage *image;
if (!_colorSpace) { if (!_colorSpace) {
_colorSpace = [self sd_colorSpaceWithDemuxer:_demux]; _colorSpace = [self sd_createColorSpaceWithDemuxer:_demux];
} }
// Static WebP image // Static WebP image
WebPIterator iter; WebPIterator iter;
@ -898,18 +922,16 @@ static void FreeImageData(void *info, const void *data, size_t size) {
} }
- (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index { - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
if (!_colorSpace) {
_colorSpace = [self sd_createColorSpaceWithDemuxer:_demux];
}
if (!_canvas) { if (!_canvas) {
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host; CGContextRef canvas = [self sd_createCanvasWithDemuxer:_demux colorSpace:_colorSpace preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize];
bitmapInfo |= _hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
CGContextRef canvas = CGBitmapContextCreate(NULL, _canvasWidth, _canvasHeight, 8, 0, [SDImageCoderHelper colorSpaceGetDeviceRGB], bitmapInfo);
if (!canvas) { if (!canvas) {
return nil; return nil;
} }
_canvas = canvas; _canvas = canvas;
} }
if (!_colorSpace) {
_colorSpace = [self sd_colorSpaceWithDemuxer:_demux];
}
SDWebPCoderFrame *frame = _frames[index]; SDWebPCoderFrame *frame = _frames[index];
UIImage *image; UIImage *image;
@ -929,7 +951,7 @@ static void FreeImageData(void *info, const void *data, size_t size) {
} else { } 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. // 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) { if (_currentBlendIndex != NSNotFound) {
CGContextClearRect(_canvas, CGRectMake(0, 0, _canvasWidth, _canvasHeight)); CGContextClearRect(_canvas, CGRectMake(0, 0, CGBitmapContextGetWidth(_canvas), CGBitmapContextGetHeight(_canvas)));
} }
// Then, loop from the blend from index, draw each of previous frames on the canvas. // Then, loop from the blend from index, draw each of previous frames on the canvas.