68
68
#endif
69
69
#endif
70
70
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
+
71
91
@interface SDWebPCoderFrame : NSObject
72
92
73
93
@property (nonatomic , assign ) NSUInteger index; // Frame index (zero based)
@@ -218,9 +238,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
218
238
}
219
239
220
240
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);
224
242
if (!canvas) {
225
243
WebPDemuxDelete (demuxer);
226
244
CGColorSpaceRelease (colorSpace);
@@ -232,7 +250,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
232
250
233
251
do {
234
252
@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];
236
254
if (!imageRef) {
237
255
continue ;
238
256
}
@@ -381,7 +399,7 @@ - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
381
399
return nil ;
382
400
}
383
401
384
- CGContextRef canvas = CGBitmapContextCreate ( NULL , width, height, 8 , 0 , [SDImageCoderHelper colorSpaceGetDeviceRGB ], bitmapInfo );
402
+ CGContextRef canvas = CreateWebPCanvas ( YES , CGSizeMake ( width, height), _thumbnailSize, _preserveAspectRatio );
385
403
if (!canvas) {
386
404
CGImageRelease (imageRef);
387
405
return nil ;
@@ -403,13 +421,6 @@ - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
403
421
scale = 1 ;
404
422
}
405
423
}
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
- }
413
424
414
425
#if SD_UIKIT || SD_WATCH
415
426
image = [[UIImage alloc ] initWithCGImage: newImageRef scale: scale orientation: UIImageOrientationUp];
@@ -425,8 +436,8 @@ - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
425
436
return image;
426
437
}
427
438
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 );
430
441
CGFloat tmpX = iter.x_offset ;
431
442
CGFloat tmpY = canvasHeight - iter.height - iter.y_offset ;
432
443
CGRect imageRect = CGRectMake (tmpX, tmpY, iter.width , iter.height );
@@ -448,14 +459,13 @@ - (void)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)
448
459
}
449
460
}
450
461
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 {
452
463
CGImageRef imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: colorSpaceRef scaledSize: CGSizeZero ];
453
464
if (!imageRef) {
454
465
return nil ;
455
466
}
456
467
457
- size_t canvasWidth = CGBitmapContextGetWidth (canvas);
458
- size_t canvasHeight = CGBitmapContextGetHeight (canvas);
468
+ int canvasHeight = WebPDemuxGetI (demuxer, WEBP_FF_CANVAS_HEIGHT);
459
469
CGFloat tmpX = iter.x_offset ;
460
470
CGFloat tmpY = canvasHeight - iter.height - iter.y_offset ;
461
471
CGRect imageRect = CGRectMake (tmpX, tmpY, iter.width , iter.height );
@@ -466,26 +476,15 @@ - (nullable CGImageRef)sd_drawnWebpImageWithCanvas:(CGContextRef)canvas iterator
466
476
if (!shouldBlend) {
467
477
CGContextClearRect (canvas, imageRect);
468
478
}
479
+
469
480
CGContextDrawImage (canvas, imageRect, imageRef);
470
481
CGImageRef newImageRef = CGBitmapContextCreateImage (canvas);
471
-
472
482
CGImageRelease (imageRef);
473
483
474
484
if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
475
485
CGContextClearRect (canvas, imageRect);
476
486
}
477
487
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
-
489
488
return newImageRef;
490
489
}
491
490
@@ -736,76 +735,22 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
736
735
if (!dataProvider) {
737
736
return nil ;
738
737
}
739
- // Check colorSpace is RGB/RGBA
740
- CGColorSpaceRef colorSpace = CGImageGetColorSpace (imageRef);
741
- BOOL isRGB = CGColorSpaceGetModel (colorSpace) == kCGColorSpaceModelRGB ;
742
738
743
- CFDataRef dataRef;
744
739
uint8_t *rgba = NULL ; // RGBA Buffer managed by CFData, don't call `free` on it, instead call `CFRelease` on `dataRef`
745
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
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 ;
808
751
}
752
+ rgba = dest.data ;
753
+ bytesPerRow = dest.rowBytes ;
809
754
810
755
float qualityFactor = quality * 100 ; // WebP quality is 0-100
811
756
// Encode RGB888/RGBA8888 buffer to WebP data
@@ -817,7 +762,8 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
817
762
if (!WebPConfigPreset (&config, WEBP_PRESET_DEFAULT, qualityFactor) ||
818
763
!WebPPictureInit (&picture)) {
819
764
// shouldn't happen, except if system installation is broken
820
- CFRelease (dataRef);
765
+ free (dest.data );
766
+ // CFRelease(dataRef);
821
767
return nil ;
822
768
}
823
769
@@ -837,7 +783,7 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
837
783
}
838
784
if (!result) {
839
785
WebPMemoryWriterClear (&writer);
840
- CFRelease (dataRef );
786
+ free (dest. data );
841
787
return nil ;
842
788
}
843
789
@@ -848,14 +794,14 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
848
794
if (!result) {
849
795
WebPMemoryWriterClear (&writer);
850
796
WebPPictureFree (&picture);
851
- CFRelease (dataRef );
797
+ free (dest. data );
852
798
return nil ;
853
799
}
854
800
}
855
801
856
802
result = WebPEncode (&config, &picture);
857
803
WebPPictureFree (&picture);
858
- CFRelease (dataRef); // Free bitmap buffer
804
+ free (dest. data );
859
805
860
806
if (result) {
861
807
// success
@@ -1137,16 +1083,13 @@ - (UIImage *)safeStaticImageFrame {
1137
1083
if (_hasAnimation) {
1138
1084
// If have animation, we still need to allocate a CGContext, because the poster frame may be smaller than canvas
1139
1085
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);
1143
1087
if (!canvas) {
1144
1088
return nil ;
1145
1089
}
1146
1090
_canvas = canvas;
1147
1091
}
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];
1150
1093
} else {
1151
1094
CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize: CGSizeMake (iter.width, iter.height) scaleSize: _thumbnailSize preserveAspectRatio: _preserveAspectRatio shouldScaleUp: NO ];
1152
1095
imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: _colorSpace scaledSize: scaledSize];
@@ -1166,9 +1109,7 @@ - (UIImage *)safeStaticImageFrame {
1166
1109
1167
1110
- (UIImage *)safeAnimatedImageFrameAtIndex : (NSUInteger )index {
1168
1111
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);
1172
1113
if (!canvas) {
1173
1114
return nil ;
1174
1115
}
@@ -1212,7 +1153,7 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
1212
1153
if (endIndex > startIndex) {
1213
1154
do {
1214
1155
@autoreleasepool {
1215
- [self sd_blendWebpImageWithCanvas: _canvas iterator: iter colorSpace: _colorSpace];
1156
+ [self sd_blendWebpImageWithCanvas: _canvas demuxer: _demux iterator: iter colorSpace: _colorSpace];
1216
1157
}
1217
1158
} while ((size_t )iter.frame_num < endIndex && WebPDemuxNextFrame (&iter));
1218
1159
}
@@ -1225,9 +1166,7 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
1225
1166
_currentBlendIndex = index ;
1226
1167
1227
1168
// 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];
1231
1170
if (!imageRef) {
1232
1171
return nil ;
1233
1172
}
0 commit comments