6868#endif
6969#endif
7070
71- static inline CGImageRef __nullable CGBitmapContextCreateScaledImage (cg_nullable CGContextRef canvas, CGSize scaledSize) CF_RETURNS_RETAINED {
72- if (!canvas) return NULL ;
73- CGContextSaveGState (canvas);
74- CGContextScaleCTM (canvas, scaledSize.width , scaledSize.height );
75- CGContextRestoreGState (canvas);
76- return CGBitmapContextCreateImage (canvas);
71+ // / Used for animated WebP, which need a canvas for decoding (rendering), possible apply a scale transform for thumbnail decoding (avoiding post-rescale using vImage)
72+ // / See more in #73
73+ static inline CGContextRef _Nullable CreateWebPCanvas (BOOL hasAlpha, CGSize canvasSize, CGSize thumbnailSize, BOOL preserveAspectRatio) {
74+ CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host ;
75+ bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst ;
76+ // Check whether we need to use thumbnail
77+ CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize: CGSizeMake (canvasSize.width, canvasSize.height) scaleSize: thumbnailSize preserveAspectRatio: preserveAspectRatio shouldScaleUp: NO ];
78+ CGContextRef canvas = CGBitmapContextCreate (NULL , scaledSize.width , scaledSize.height , 8 , 0 , [SDImageCoderHelper colorSpaceGetDeviceRGB ], bitmapInfo);
79+ if (!canvas) {
80+ return nil ;
81+ }
82+ // Check whether we need to use thumbnail
83+ if (!CGSizeEqualToSize (canvasSize, scaledSize)) {
84+ CGFloat sx = scaledSize.width / canvasSize.width ;
85+ CGFloat sy = scaledSize.height / canvasSize.height ;
86+ CGContextScaleCTM (canvas, sx, sy);
87+ }
88+ return canvas;
7789}
7890
7991@interface SDWebPCoderFrame : NSObject
@@ -226,9 +238,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
226238 }
227239
228240 BOOL hasAlpha = flags & ALPHA_FLAG;
229- CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host ;
230- bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst ;
231- CGContextRef canvas = CGBitmapContextCreate (NULL , canvasWidth, canvasHeight, 8 , 0 , [SDImageCoderHelper colorSpaceGetDeviceRGB ], bitmapInfo);
241+ CGContextRef canvas = CreateWebPCanvas (hasAlpha, CGSizeMake (canvasWidth, canvasHeight), thumbnailSize, preserveAspectRatio);
232242 if (!canvas) {
233243 WebPDemuxDelete (demuxer);
234244 CGColorSpaceRelease (colorSpace);
@@ -240,7 +250,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
240250
241251 do {
242252 @autoreleasepool {
243- CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas: canvas iterator: iter colorSpace: colorSpace scaledSize: scaledSize ];
253+ CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas: canvas demuxer: demuxer iterator: iter colorSpace: colorSpace];
244254 if (!imageRef) {
245255 continue ;
246256 }
@@ -389,22 +399,15 @@ - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
389399 return nil ;
390400 }
391401
392- CGContextRef canvas = CGBitmapContextCreate ( NULL , width, height, 8 , 0 , [SDImageCoderHelper colorSpaceGetDeviceRGB ], bitmapInfo );
402+ CGContextRef canvas = CreateWebPCanvas ( YES , CGSizeMake ( width, height), _thumbnailSize, _preserveAspectRatio );
393403 if (!canvas) {
394404 CGImageRelease (imageRef);
395405 return nil ;
396406 }
397407
398408 // Only draw the last_y image height, keep remains transparent, in Core Graphics coordinate system
399409 CGContextDrawImage (canvas, CGRectMake (0 , height - last_y, width, last_y), imageRef);
400- // Check whether we need to use thumbnail
401- CGImageRef newImageRef;
402- CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize: CGSizeMake (width, height) scaleSize: _thumbnailSize preserveAspectRatio: _preserveAspectRatio shouldScaleUp: NO ];
403- if (!CGSizeEqualToSize (CGSizeMake (width, height), scaledSize)) {
404- newImageRef = CGBitmapContextCreateScaledImage (canvas, scaledSize);
405- } else {
406- newImageRef = CGBitmapContextCreateImage (canvas);
407- }
410+ CGImageRef newImageRef = CGBitmapContextCreateImage (canvas);
408411 CGImageRelease (imageRef);
409412 if (!newImageRef) {
410413 CGContextRelease (canvas);
@@ -433,8 +436,8 @@ - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
433436 return image;
434437}
435438
436- - (void )sd_blendWebpImageWithCanvas : (CGContextRef)canvas iterator : (WebPIterator)iter colorSpace : (nonnull CGColorSpaceRef)colorSpaceRef {
437- size_t canvasHeight = CGBitmapContextGetHeight (canvas );
439+ - (void )sd_blendWebpImageWithCanvas : (CGContextRef)canvas demuxer : (nonnull WebPDemuxer *) demuxer iterator : (WebPIterator)iter colorSpace : (nonnull CGColorSpaceRef)colorSpaceRef {
440+ int canvasHeight = WebPDemuxGetI (demuxer, WEBP_FF_CANVAS_HEIGHT );
438441 CGFloat tmpX = iter.x_offset ;
439442 CGFloat tmpY = canvasHeight - iter.height - iter.y_offset ;
440443 CGRect imageRect = CGRectMake (tmpX, tmpY, iter.width , iter.height );
@@ -456,14 +459,13 @@ - (void)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)
456459 }
457460}
458461
459- - (nullable CGImageRef)sd_drawnWebpImageWithCanvas : (CGContextRef)canvas iterator : (WebPIterator)iter colorSpace : (nonnull CGColorSpaceRef)colorSpaceRef scaledSize : (CGSize) scaledSize CF_RETURNS_RETAINED {
462+ - (nullable CGImageRef)sd_drawnWebpImageWithCanvas : (CGContextRef)canvas demuxer : (nonnull WebPDemuxer *) demuxer iterator : (WebPIterator)iter colorSpace : (nonnull CGColorSpaceRef)colorSpaceRef CF_RETURNS_RETAINED {
460463 CGImageRef imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: colorSpaceRef scaledSize: CGSizeZero];
461464 if (!imageRef) {
462465 return nil ;
463466 }
464467
465- size_t canvasWidth = CGBitmapContextGetWidth (canvas);
466- size_t canvasHeight = CGBitmapContextGetHeight (canvas);
468+ int canvasHeight = WebPDemuxGetI (demuxer, WEBP_FF_CANVAS_HEIGHT);
467469 CGFloat tmpX = iter.x_offset ;
468470 CGFloat tmpY = canvasHeight - iter.height - iter.y_offset ;
469471 CGRect imageRect = CGRectMake (tmpX, tmpY, iter.width , iter.height );
@@ -474,17 +476,9 @@ - (nullable CGImageRef)sd_drawnWebpImageWithCanvas:(CGContextRef)canvas iterator
474476 if (!shouldBlend) {
475477 CGContextClearRect (canvas, imageRect);
476478 }
477- CGContextDrawImage (canvas, imageRect, imageRef);
478-
479- CGImageRef newImageRef;
480- // Check whether we need to use thumbnail
481- if (!CGSizeEqualToSize (CGSizeMake (canvasWidth, canvasHeight), scaledSize)) {
482- // Use CoreGraphics canvas to scale down, no need extra allocation
483- newImageRef = CGBitmapContextCreateScaledImage (canvas, scaledSize);
484- } else {
485- newImageRef = CGBitmapContextCreateImage (canvas);
486- }
487479
480+ CGContextDrawImage (canvas, imageRect, imageRef);
481+ CGImageRef newImageRef = CGBitmapContextCreateImage (canvas);
488482 CGImageRelease (imageRef);
489483
490484 if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
@@ -741,74 +735,22 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
741735 if (!dataProvider) {
742736 return nil ;
743737 }
744- // Check colorSpace is RGB/RGBA
745- CGColorSpaceRef colorSpace = CGImageGetColorSpace (imageRef);
746- BOOL isRGB = CGColorSpaceGetModel (colorSpace) == kCGColorSpaceModelRGB ;
747738
748- CFDataRef dataRef;
749739 uint8_t *rgba = NULL ; // RGBA Buffer managed by CFData, don't call `free` on it, instead call `CFRelease` on `dataRef`
750740 // We could not assume that input CGImage's color mode is always RGB888/RGBA8888. Convert all other cases to target color mode using vImage
751- BOOL isRGB888 = isRGB && byteOrderNormal && alphaInfo == kCGImageAlphaNone && components == 3 ;
752- BOOL isRGBA8888 = isRGB && byteOrderNormal && alphaInfo == kCGImageAlphaLast && components == 4 ;
753- if (isRGB888 || isRGBA8888) {
754- // If the input CGImage is already RGB888/RGBA8888
755- dataRef = CGDataProviderCopyData (dataProvider);
756- if (!dataRef) {
757- return nil ;
758- }
759- rgba = (uint8_t *)CFDataGetBytePtr (dataRef);
760- } else {
761- // Convert all other cases to target color mode using vImage
762- vImageConverterRef convertor = NULL ;
763- vImage_Error error = kvImageNoError;
764-
765- vImage_CGImageFormat srcFormat = {
766- .bitsPerComponent = (uint32_t )bitsPerComponent,
767- .bitsPerPixel = (uint32_t )bitsPerPixel,
768- .colorSpace = colorSpace,
769- .bitmapInfo = bitmapInfo,
770- .renderingIntent = CGImageGetRenderingIntent (imageRef)
771- };
772- vImage_CGImageFormat destFormat = {
773- .bitsPerComponent = 8 ,
774- .bitsPerPixel = hasAlpha ? 32 : 24 ,
775- .colorSpace = [SDImageCoderHelper colorSpaceGetDeviceRGB ],
776- .bitmapInfo = hasAlpha ? kCGImageAlphaLast | kCGBitmapByteOrderDefault : kCGImageAlphaNone | kCGBitmapByteOrderDefault // RGB888/RGBA8888 (Non-premultiplied to works for libwebp)
777- };
778-
779- convertor = vImageConverter_CreateWithCGImageFormat (&srcFormat, &destFormat, NULL , kvImageNoFlags, &error);
780- if (error != kvImageNoError) {
781- return nil ;
782- }
783-
784- vImage_Buffer src;
785- error = vImageBuffer_InitWithCGImage (&src, &srcFormat, nil , imageRef, kvImageNoAllocate);
786- if (error != kvImageNoError) {
787- vImageConverter_Release (convertor);
788- return nil ;
789- }
790-
791- vImage_Buffer dest;
792- error = vImageBuffer_Init (&dest, height, width, destFormat.bitsPerPixel , kvImageNoFlags);
793- if (error != kvImageNoError) {
794- vImageConverter_Release (convertor);
795- return nil ;
796- }
797-
798- // Convert input color mode to RGB888/RGBA8888
799- error = vImageConvert_AnyToAny (convertor, &src, &dest, NULL , kvImageNoFlags);
800-
801- // Free the buffer
802- vImageConverter_Release (convertor);
803- if (error != kvImageNoError) {
804- free (dest.data );
805- return nil ;
806- }
807-
808- rgba = dest.data ; // Converted buffer
809- bytesPerRow = dest.rowBytes ; // Converted bytePerRow
810- dataRef = CFDataCreateWithBytesNoCopy (kCFAllocatorDefault , rgba, bytesPerRow * height, kCFAllocatorDefault );
741+ vImage_CGImageFormat destFormat = {
742+ .bitsPerComponent = 8 ,
743+ .bitsPerPixel = hasAlpha ? 32 : 24 ,
744+ .colorSpace = [SDImageCoderHelper colorSpaceGetDeviceRGB ],
745+ .bitmapInfo = hasAlpha ? kCGImageAlphaLast | kCGBitmapByteOrderDefault : kCGImageAlphaNone | kCGBitmapByteOrderDefault // RGB888/RGBA8888 (Non-premultiplied to works for libwebp)
746+ };
747+ vImage_Buffer dest;
748+ vImage_Error error = vImageBuffer_InitWithCGImage (&dest, &destFormat, NULL , imageRef, kvImageNoFlags);
749+ if (error != kvImageNoError) {
750+ return nil ;
811751 }
752+ rgba = dest.data ;
753+ bytesPerRow = dest.rowBytes ;
812754
813755 float qualityFactor = quality * 100 ; // WebP quality is 0-100
814756 // Encode RGB888/RGBA8888 buffer to WebP data
@@ -820,7 +762,8 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
820762 if (!WebPConfigPreset (&config, WEBP_PRESET_DEFAULT, qualityFactor) ||
821763 !WebPPictureInit (&picture)) {
822764 // shouldn't happen, except if system installation is broken
823- CFRelease (dataRef);
765+ free (dest.data );
766+ // CFRelease(dataRef);
824767 return nil ;
825768 }
826769
@@ -840,7 +783,7 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
840783 }
841784 if (!result) {
842785 WebPMemoryWriterClear (&writer);
843- CFRelease (dataRef );
786+ free (dest. data );
844787 return nil ;
845788 }
846789
@@ -851,14 +794,14 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
851794 if (!result) {
852795 WebPMemoryWriterClear (&writer);
853796 WebPPictureFree (&picture);
854- CFRelease (dataRef );
797+ free (dest. data );
855798 return nil ;
856799 }
857800 }
858801
859802 result = WebPEncode (&config, &picture);
860803 WebPPictureFree (&picture);
861- CFRelease (dataRef); // Free bitmap buffer
804+ free (dest. data );
862805
863806 if (result) {
864807 // success
@@ -1140,16 +1083,13 @@ - (UIImage *)safeStaticImageFrame {
11401083 if (_hasAnimation) {
11411084 // If have animation, we still need to allocate a CGContext, because the poster frame may be smaller than canvas
11421085 if (!_canvas) {
1143- CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host ;
1144- bitmapInfo |= _hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst ;
1145- CGContextRef canvas = CGBitmapContextCreate (NULL , _canvasWidth, _canvasHeight, 8 , 0 , [SDImageCoderHelper colorSpaceGetDeviceRGB ], bitmapInfo);
1086+ CGContextRef canvas = CreateWebPCanvas (_hasAlpha, CGSizeMake (_canvasWidth, _canvasHeight), _thumbnailSize, _preserveAspectRatio);
11461087 if (!canvas) {
11471088 return nil ;
11481089 }
11491090 _canvas = canvas;
11501091 }
1151- CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize: CGSizeMake (_canvasWidth, _canvasHeight) scaleSize: _thumbnailSize preserveAspectRatio: _preserveAspectRatio shouldScaleUp: NO ];
1152- imageRef = [self sd_drawnWebpImageWithCanvas: _canvas iterator: iter colorSpace: _colorSpace scaledSize: scaledSize];
1092+ imageRef = [self sd_drawnWebpImageWithCanvas: _canvas demuxer: _demux iterator: iter colorSpace: _colorSpace];
11531093 } else {
11541094 CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize: CGSizeMake (iter.width, iter.height) scaleSize: _thumbnailSize preserveAspectRatio: _preserveAspectRatio shouldScaleUp: NO ];
11551095 imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: _colorSpace scaledSize: scaledSize];
@@ -1169,9 +1109,7 @@ - (UIImage *)safeStaticImageFrame {
11691109
11701110- (UIImage *)safeAnimatedImageFrameAtIndex : (NSUInteger )index {
11711111 if (!_canvas) {
1172- CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host ;
1173- bitmapInfo |= _hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst ;
1174- CGContextRef canvas = CGBitmapContextCreate (NULL , _canvasWidth, _canvasHeight, 8 , 0 , [SDImageCoderHelper colorSpaceGetDeviceRGB ], bitmapInfo);
1112+ CGContextRef canvas = CreateWebPCanvas (_hasAlpha, CGSizeMake (_canvasWidth, _canvasHeight), _thumbnailSize, _preserveAspectRatio);
11751113 if (!canvas) {
11761114 return nil ;
11771115 }
@@ -1215,7 +1153,7 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
12151153 if (endIndex > startIndex) {
12161154 do {
12171155 @autoreleasepool {
1218- [self sd_blendWebpImageWithCanvas: _canvas iterator: iter colorSpace: _colorSpace];
1156+ [self sd_blendWebpImageWithCanvas: _canvas demuxer: _demux iterator: iter colorSpace: _colorSpace];
12191157 }
12201158 } while ((size_t )iter.frame_num < endIndex && WebPDemuxNextFrame (&iter));
12211159 }
@@ -1228,9 +1166,7 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
12281166 _currentBlendIndex = index;
12291167
12301168 // Now the canvas is ready, which respects of dispose method behavior. Just do normal decoding and produce image.
1231- // Check whether we need to use thumbnail
1232- CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize: CGSizeMake (_canvasWidth, _canvasHeight) scaleSize: _thumbnailSize preserveAspectRatio: _preserveAspectRatio shouldScaleUp: NO ];
1233- CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas: _canvas iterator: iter colorSpace: _colorSpace scaledSize: scaledSize];
1169+ CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas: _canvas demuxer: _demux iterator: iter colorSpace: _colorSpace];
12341170 if (!imageRef) {
12351171 return nil ;
12361172 }
0 commit comments