6868#endif
6969#endif
7070
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;
89+ }
90+
7191@interface SDWebPCoderFrame : NSObject
7292
7393@property (nonatomic , assign ) NSUInteger index; // Frame index (zero based)
@@ -218,9 +238,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
218238 }
219239
220240 BOOL hasAlpha = flags & ALPHA_FLAG;
221- CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host ;
222- bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst ;
223- CGContextRef canvas = CGBitmapContextCreate (NULL , canvasWidth, canvasHeight, 8 , 0 , [SDImageCoderHelper colorSpaceGetDeviceRGB ], bitmapInfo);
241+ CGContextRef canvas = CreateWebPCanvas (hasAlpha, CGSizeMake (canvasWidth, canvasHeight), thumbnailSize, preserveAspectRatio);
224242 if (!canvas) {
225243 WebPDemuxDelete (demuxer);
226244 CGColorSpaceRelease (colorSpace);
@@ -232,7 +250,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
232250
233251 do {
234252 @autoreleasepool {
235- 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];
236254 if (!imageRef) {
237255 continue ;
238256 }
@@ -381,7 +399,7 @@ - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
381399 return nil ;
382400 }
383401
384- CGContextRef canvas = CGBitmapContextCreate ( NULL , width, height, 8 , 0 , [SDImageCoderHelper colorSpaceGetDeviceRGB ], bitmapInfo );
402+ CGContextRef canvas = CreateWebPCanvas ( YES , CGSizeMake ( width, height), _thumbnailSize, _preserveAspectRatio );
385403 if (!canvas) {
386404 CGImageRelease (imageRef);
387405 return nil ;
@@ -403,13 +421,6 @@ - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
403421 scale = 1 ;
404422 }
405423 }
406- CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize: CGSizeMake (width, height) scaleSize: _thumbnailSize preserveAspectRatio: _preserveAspectRatio shouldScaleUp: NO ];
407- // Check whether we need to use thumbnail
408- if (!CGSizeEqualToSize (CGSizeMake (width, height), scaledSize)) {
409- CGImageRef scaledImageRef = [SDImageCoderHelper CGImageCreateScaled: newImageRef size: scaledSize];
410- CGImageRelease (newImageRef);
411- newImageRef = scaledImageRef;
412- }
413424
414425#if SD_UIKIT || SD_WATCH
415426 image = [[UIImage alloc ] initWithCGImage: newImageRef scale: scale orientation: UIImageOrientationUp];
@@ -425,8 +436,8 @@ - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
425436 return image;
426437}
427438
428- - (void )sd_blendWebpImageWithCanvas : (CGContextRef)canvas iterator : (WebPIterator)iter colorSpace : (nonnull CGColorSpaceRef)colorSpaceRef {
429- 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 );
430441 CGFloat tmpX = iter.x_offset ;
431442 CGFloat tmpY = canvasHeight - iter.height - iter.y_offset ;
432443 CGRect imageRect = CGRectMake (tmpX, tmpY, iter.width , iter.height );
@@ -448,14 +459,13 @@ - (void)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)
448459 }
449460}
450461
451- - (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 {
452463 CGImageRef imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: colorSpaceRef scaledSize: CGSizeZero];
453464 if (!imageRef) {
454465 return nil ;
455466 }
456467
457- size_t canvasWidth = CGBitmapContextGetWidth (canvas);
458- size_t canvasHeight = CGBitmapContextGetHeight (canvas);
468+ int canvasHeight = WebPDemuxGetI (demuxer, WEBP_FF_CANVAS_HEIGHT);
459469 CGFloat tmpX = iter.x_offset ;
460470 CGFloat tmpY = canvasHeight - iter.height - iter.y_offset ;
461471 CGRect imageRect = CGRectMake (tmpX, tmpY, iter.width , iter.height );
@@ -466,26 +476,15 @@ - (nullable CGImageRef)sd_drawnWebpImageWithCanvas:(CGContextRef)canvas iterator
466476 if (!shouldBlend) {
467477 CGContextClearRect (canvas, imageRect);
468478 }
479+
469480 CGContextDrawImage (canvas, imageRect, imageRef);
470481 CGImageRef newImageRef = CGBitmapContextCreateImage (canvas);
471-
472482 CGImageRelease (imageRef);
473483
474484 if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
475485 CGContextClearRect (canvas, imageRect);
476486 }
477487
478- // Check whether we need to use thumbnail
479- if (!CGSizeEqualToSize (CGSizeMake (canvasWidth, canvasHeight), scaledSize)) {
480- // Important: For Animated WebP thumbnail generation, we can not just use a scaled small canvas and draw each thumbnail frame
481- // 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
482- // Because of this reason, even each frame contains small zigzag, the final animation contains visible glitch, this is not we want.
483- // 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
484- CGImageRef scaledImageRef = [SDImageCoderHelper CGImageCreateScaled: newImageRef size: scaledSize];
485- CGImageRelease (newImageRef);
486- newImageRef = scaledImageRef;
487- }
488-
489488 return newImageRef;
490489}
491490
@@ -736,76 +735,22 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
736735 if (!dataProvider) {
737736 return nil ;
738737 }
739- // Check colorSpace is RGB/RGBA
740- CGColorSpaceRef colorSpace = CGImageGetColorSpace (imageRef);
741- BOOL isRGB = CGColorSpaceGetModel (colorSpace) == kCGColorSpaceModelRGB ;
742738
743- CFDataRef dataRef;
744739 uint8_t *rgba = NULL ; // RGBA Buffer managed by CFData, don't call `free` on it, instead call `CFRelease` on `dataRef`
745740 // We could not assume that input CGImage's color mode is always RGB888/RGBA8888. Convert all other cases to target color mode using vImage
746- BOOL isRGB888 = isRGB && byteOrderNormal && alphaInfo == kCGImageAlphaNone && components == 3 ;
747- BOOL isRGBA8888 = isRGB && byteOrderNormal && alphaInfo == kCGImageAlphaLast && components == 4 ;
748- if (isRGB888 || isRGBA8888) {
749- // If the input CGImage is already RGB888/RGBA8888
750- dataRef = CGDataProviderCopyData (dataProvider);
751- if (!dataRef) {
752- return nil ;
753- }
754- rgba = (uint8_t *)CFDataGetBytePtr (dataRef);
755- } else {
756- // Convert all other cases to target color mode using vImage
757- vImageConverterRef convertor = NULL ;
758- vImage_Error error = kvImageNoError;
759-
760- vImage_CGImageFormat srcFormat = {
761- .bitsPerComponent = (uint32_t )bitsPerComponent,
762- .bitsPerPixel = (uint32_t )bitsPerPixel,
763- .colorSpace = colorSpace,
764- .bitmapInfo = bitmapInfo,
765- .renderingIntent = CGImageGetRenderingIntent (imageRef)
766- };
767- vImage_CGImageFormat destFormat = {
768- .bitsPerComponent = 8 ,
769- .bitsPerPixel = hasAlpha ? 32 : 24 ,
770- .colorSpace = [SDImageCoderHelper colorSpaceGetDeviceRGB ],
771- .bitmapInfo = hasAlpha ? kCGImageAlphaLast | kCGBitmapByteOrderDefault : kCGImageAlphaNone | kCGBitmapByteOrderDefault // RGB888/RGBA8888 (Non-premultiplied to works for libwebp)
772- };
773-
774- convertor = vImageConverter_CreateWithCGImageFormat (&srcFormat, &destFormat, NULL , kvImageNoFlags, &error);
775- if (error != kvImageNoError) {
776- return nil ;
777- }
778-
779- vImage_Buffer src;
780- error = vImageBuffer_InitWithCGImage (&src, &srcFormat, nil , imageRef, kvImageNoFlags);
781- if (error != kvImageNoError) {
782- vImageConverter_Release (convertor);
783- return nil ;
784- }
785-
786- vImage_Buffer dest;
787- error = vImageBuffer_Init (&dest, height, width, destFormat.bitsPerPixel , kvImageNoFlags);
788- if (error != kvImageNoError) {
789- vImageConverter_Release (convertor);
790- free (src.data );
791- return nil ;
792- }
793-
794- // Convert input color mode to RGB888/RGBA8888
795- error = vImageConvert_AnyToAny (convertor, &src, &dest, NULL , kvImageNoFlags);
796-
797- // Free the buffer
798- free (src.data );
799- vImageConverter_Release (convertor);
800- if (error != kvImageNoError) {
801- free (dest.data );
802- return nil ;
803- }
804-
805- rgba = dest.data ; // Converted buffer
806- bytesPerRow = dest.rowBytes ; // Converted bytePerRow
807- 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 ;
808751 }
752+ rgba = dest.data ;
753+ bytesPerRow = dest.rowBytes ;
809754
810755 float qualityFactor = quality * 100 ; // WebP quality is 0-100
811756 // Encode RGB888/RGBA8888 buffer to WebP data
@@ -817,7 +762,8 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
817762 if (!WebPConfigPreset (&config, WEBP_PRESET_DEFAULT, qualityFactor) ||
818763 !WebPPictureInit (&picture)) {
819764 // shouldn't happen, except if system installation is broken
820- CFRelease (dataRef);
765+ free (dest.data );
766+ // CFRelease(dataRef);
821767 return nil ;
822768 }
823769
@@ -837,7 +783,7 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
837783 }
838784 if (!result) {
839785 WebPMemoryWriterClear (&writer);
840- CFRelease (dataRef );
786+ free (dest. data );
841787 return nil ;
842788 }
843789
@@ -848,14 +794,14 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
848794 if (!result) {
849795 WebPMemoryWriterClear (&writer);
850796 WebPPictureFree (&picture);
851- CFRelease (dataRef );
797+ free (dest. data );
852798 return nil ;
853799 }
854800 }
855801
856802 result = WebPEncode (&config, &picture);
857803 WebPPictureFree (&picture);
858- CFRelease (dataRef); // Free bitmap buffer
804+ free (dest. data );
859805
860806 if (result) {
861807 // success
@@ -1137,16 +1083,13 @@ - (UIImage *)safeStaticImageFrame {
11371083 if (_hasAnimation) {
11381084 // If have animation, we still need to allocate a CGContext, because the poster frame may be smaller than canvas
11391085 if (!_canvas) {
1140- CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host ;
1141- bitmapInfo |= _hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst ;
1142- CGContextRef canvas = CGBitmapContextCreate (NULL , _canvasWidth, _canvasHeight, 8 , 0 , [SDImageCoderHelper colorSpaceGetDeviceRGB ], bitmapInfo);
1086+ CGContextRef canvas = CreateWebPCanvas (_hasAlpha, CGSizeMake (_canvasWidth, _canvasHeight), _thumbnailSize, _preserveAspectRatio);
11431087 if (!canvas) {
11441088 return nil ;
11451089 }
11461090 _canvas = canvas;
11471091 }
1148- CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize: CGSizeMake (_canvasWidth, _canvasHeight) scaleSize: _thumbnailSize preserveAspectRatio: _preserveAspectRatio shouldScaleUp: NO ];
1149- imageRef = [self sd_drawnWebpImageWithCanvas: _canvas iterator: iter colorSpace: _colorSpace scaledSize: scaledSize];
1092+ imageRef = [self sd_drawnWebpImageWithCanvas: _canvas demuxer: _demux iterator: iter colorSpace: _colorSpace];
11501093 } else {
11511094 CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize: CGSizeMake (iter.width, iter.height) scaleSize: _thumbnailSize preserveAspectRatio: _preserveAspectRatio shouldScaleUp: NO ];
11521095 imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: _colorSpace scaledSize: scaledSize];
@@ -1166,9 +1109,7 @@ - (UIImage *)safeStaticImageFrame {
11661109
11671110- (UIImage *)safeAnimatedImageFrameAtIndex : (NSUInteger )index {
11681111 if (!_canvas) {
1169- CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host ;
1170- bitmapInfo |= _hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst ;
1171- CGContextRef canvas = CGBitmapContextCreate (NULL , _canvasWidth, _canvasHeight, 8 , 0 , [SDImageCoderHelper colorSpaceGetDeviceRGB ], bitmapInfo);
1112+ CGContextRef canvas = CreateWebPCanvas (_hasAlpha, CGSizeMake (_canvasWidth, _canvasHeight), _thumbnailSize, _preserveAspectRatio);
11721113 if (!canvas) {
11731114 return nil ;
11741115 }
@@ -1212,7 +1153,7 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
12121153 if (endIndex > startIndex) {
12131154 do {
12141155 @autoreleasepool {
1215- [self sd_blendWebpImageWithCanvas: _canvas iterator: iter colorSpace: _colorSpace];
1156+ [self sd_blendWebpImageWithCanvas: _canvas demuxer: _demux iterator: iter colorSpace: _colorSpace];
12161157 }
12171158 } while ((size_t )iter.frame_num < endIndex && WebPDemuxNextFrame (&iter));
12181159 }
@@ -1225,9 +1166,7 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
12251166 _currentBlendIndex = index;
12261167
12271168 // Now the canvas is ready, which respects of dispose method behavior. Just do normal decoding and produce image.
1228- // Check whether we need to use thumbnail
1229- CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize: CGSizeMake (_canvasWidth, _canvasHeight) scaleSize: _thumbnailSize preserveAspectRatio: _preserveAspectRatio shouldScaleUp: NO ];
1230- 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];
12311170 if (!imageRef) {
12321171 return nil ;
12331172 }
0 commit comments