68
68
#endif
69
69
#endif
70
70
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;
77
89
}
78
90
79
91
@interface SDWebPCoderFrame : NSObject
@@ -226,9 +238,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
226
238
}
227
239
228
240
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);
232
242
if (!canvas) {
233
243
WebPDemuxDelete (demuxer);
234
244
CGColorSpaceRelease (colorSpace);
@@ -240,7 +250,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
240
250
241
251
do {
242
252
@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];
244
254
if (!imageRef) {
245
255
continue ;
246
256
}
@@ -389,22 +399,15 @@ - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
389
399
return nil ;
390
400
}
391
401
392
- CGContextRef canvas = CGBitmapContextCreate ( NULL , width, height, 8 , 0 , [SDImageCoderHelper colorSpaceGetDeviceRGB ], bitmapInfo );
402
+ CGContextRef canvas = CreateWebPCanvas ( YES , CGSizeMake ( width, height), _thumbnailSize, _preserveAspectRatio );
393
403
if (!canvas) {
394
404
CGImageRelease (imageRef);
395
405
return nil ;
396
406
}
397
407
398
408
// Only draw the last_y image height, keep remains transparent, in Core Graphics coordinate system
399
409
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);
408
411
CGImageRelease (imageRef);
409
412
if (!newImageRef) {
410
413
CGContextRelease (canvas);
@@ -433,8 +436,8 @@ - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
433
436
return image;
434
437
}
435
438
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 );
438
441
CGFloat tmpX = iter.x_offset ;
439
442
CGFloat tmpY = canvasHeight - iter.height - iter.y_offset ;
440
443
CGRect imageRect = CGRectMake (tmpX, tmpY, iter.width , iter.height );
@@ -456,14 +459,13 @@ - (void)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)
456
459
}
457
460
}
458
461
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 {
460
463
CGImageRef imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: colorSpaceRef scaledSize: CGSizeZero];
461
464
if (!imageRef) {
462
465
return nil ;
463
466
}
464
467
465
- size_t canvasWidth = CGBitmapContextGetWidth (canvas);
466
- size_t canvasHeight = CGBitmapContextGetHeight (canvas);
468
+ int canvasHeight = WebPDemuxGetI (demuxer, WEBP_FF_CANVAS_HEIGHT);
467
469
CGFloat tmpX = iter.x_offset ;
468
470
CGFloat tmpY = canvasHeight - iter.height - iter.y_offset ;
469
471
CGRect imageRect = CGRectMake (tmpX, tmpY, iter.width , iter.height );
@@ -474,17 +476,9 @@ - (nullable CGImageRef)sd_drawnWebpImageWithCanvas:(CGContextRef)canvas iterator
474
476
if (!shouldBlend) {
475
477
CGContextClearRect (canvas, imageRect);
476
478
}
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
- }
487
479
480
+ CGContextDrawImage (canvas, imageRect, imageRef);
481
+ CGImageRef newImageRef = CGBitmapContextCreateImage (canvas);
488
482
CGImageRelease (imageRef);
489
483
490
484
if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
@@ -741,74 +735,22 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
741
735
if (!dataProvider) {
742
736
return nil ;
743
737
}
744
- // Check colorSpace is RGB/RGBA
745
- CGColorSpaceRef colorSpace = CGImageGetColorSpace (imageRef);
746
- BOOL isRGB = CGColorSpaceGetModel (colorSpace) == kCGColorSpaceModelRGB ;
747
738
748
- CFDataRef dataRef;
749
739
uint8_t *rgba = NULL ; // RGBA Buffer managed by CFData, don't call `free` on it, instead call `CFRelease` on `dataRef`
750
740
// 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 ;
811
751
}
752
+ rgba = dest.data ;
753
+ bytesPerRow = dest.rowBytes ;
812
754
813
755
float qualityFactor = quality * 100 ; // WebP quality is 0-100
814
756
// Encode RGB888/RGBA8888 buffer to WebP data
@@ -820,7 +762,8 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
820
762
if (!WebPConfigPreset (&config, WEBP_PRESET_DEFAULT, qualityFactor) ||
821
763
!WebPPictureInit (&picture)) {
822
764
// shouldn't happen, except if system installation is broken
823
- CFRelease (dataRef);
765
+ free (dest.data );
766
+ // CFRelease(dataRef);
824
767
return nil ;
825
768
}
826
769
@@ -840,7 +783,7 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
840
783
}
841
784
if (!result) {
842
785
WebPMemoryWriterClear (&writer);
843
- CFRelease (dataRef );
786
+ free (dest. data );
844
787
return nil ;
845
788
}
846
789
@@ -851,14 +794,14 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
851
794
if (!result) {
852
795
WebPMemoryWriterClear (&writer);
853
796
WebPPictureFree (&picture);
854
- CFRelease (dataRef );
797
+ free (dest. data );
855
798
return nil ;
856
799
}
857
800
}
858
801
859
802
result = WebPEncode (&config, &picture);
860
803
WebPPictureFree (&picture);
861
- CFRelease (dataRef); // Free bitmap buffer
804
+ free (dest. data );
862
805
863
806
if (result) {
864
807
// success
@@ -1140,16 +1083,13 @@ - (UIImage *)safeStaticImageFrame {
1140
1083
if (_hasAnimation) {
1141
1084
// If have animation, we still need to allocate a CGContext, because the poster frame may be smaller than canvas
1142
1085
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);
1146
1087
if (!canvas) {
1147
1088
return nil ;
1148
1089
}
1149
1090
_canvas = canvas;
1150
1091
}
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];
1153
1093
} else {
1154
1094
CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize: CGSizeMake (iter.width, iter.height) scaleSize: _thumbnailSize preserveAspectRatio: _preserveAspectRatio shouldScaleUp: NO ];
1155
1095
imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: _colorSpace scaledSize: scaledSize];
@@ -1169,9 +1109,7 @@ - (UIImage *)safeStaticImageFrame {
1169
1109
1170
1110
- (UIImage *)safeAnimatedImageFrameAtIndex : (NSUInteger )index {
1171
1111
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);
1175
1113
if (!canvas) {
1176
1114
return nil ;
1177
1115
}
@@ -1215,7 +1153,7 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
1215
1153
if (endIndex > startIndex) {
1216
1154
do {
1217
1155
@autoreleasepool {
1218
- [self sd_blendWebpImageWithCanvas: _canvas iterator: iter colorSpace: _colorSpace];
1156
+ [self sd_blendWebpImageWithCanvas: _canvas demuxer: _demux iterator: iter colorSpace: _colorSpace];
1219
1157
}
1220
1158
} while ((size_t )iter.frame_num < endIndex && WebPDemuxNextFrame (&iter));
1221
1159
}
@@ -1228,9 +1166,7 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
1228
1166
_currentBlendIndex = index;
1229
1167
1230
1168
// 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];
1234
1170
if (!imageRef) {
1235
1171
return nil ;
1236
1172
}
0 commit comments