71
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
72
// / See more in #73
73
73
static inline CGContextRef _Nullable CreateWebPCanvas (BOOL hasAlpha, CGSize canvasSize, CGSize thumbnailSize, BOOL preserveAspectRatio) {
74
- CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host ;
75
- bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst ;
74
+ // From SDWebImage v5.17.0, use runtime detection of bitmap info instead of hardcode.
75
+ CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredPixelFormat: hasAlpha]. bitmapInfo ;
76
76
// Check whether we need to use thumbnail
77
77
CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize: CGSizeMake (canvasSize.width, canvasSize.height) scaleSize: thumbnailSize preserveAspectRatio: preserveAspectRatio shouldScaleUp: NO ];
78
78
CGContextRef canvas = CGBitmapContextCreate (NULL , scaledSize.width , scaledSize.height , 8 , 0 , [SDImageCoderHelper colorSpaceGetDeviceRGB ], bitmapInfo);
@@ -88,18 +88,87 @@ static inline CGContextRef _Nullable CreateWebPCanvas(BOOL hasAlpha, CGSize canv
88
88
return canvas;
89
89
}
90
90
91
- // TODO, share this logic for multiple coders, or do refactory in v6.0 (The coder plugin should provide image information back to Core, like `CGImageSourceCopyPropertiesAtIndex`)
92
- static inline CGSize SDCalculateScaleDownPixelSize (NSUInteger limitBytes, CGSize originalSize, NSUInteger frameCount, NSUInteger bytesPerPixel) {
93
- if (CGSizeEqualToSize (originalSize, CGSizeZero )) return CGSizeMake (1 , 1 );
94
- NSUInteger totalFramePixelSize = limitBytes / bytesPerPixel / (frameCount ?: 1 );
95
- CGFloat ratio = originalSize.height / originalSize.width ;
96
- CGFloat width = sqrt (totalFramePixelSize / ratio);
97
- CGFloat height = width * ratio;
98
- width = MAX (1 , floor (width));
99
- height = MAX (1 , floor (height));
100
- CGSize size = CGSizeMake (width, height);
101
-
102
- return size;
91
+ WEBP_CSP_MODE ConvertCSPMode (CGBitmapInfo bitmapInfo) {
92
+ // Get alpha info, byteOrder info
93
+ CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask ;
94
+ CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask ;
95
+ BOOL byteOrderNormal = NO ;
96
+ switch (byteOrderInfo) {
97
+ case kCGBitmapByteOrderDefault : {
98
+ byteOrderNormal = YES ;
99
+ } break ;
100
+ case kCGBitmapByteOrder32Little : {
101
+ } break ;
102
+ case kCGBitmapByteOrder32Big : {
103
+ byteOrderNormal = YES ;
104
+ } break ;
105
+ default : break ;
106
+ }
107
+ switch (alphaInfo) {
108
+ case kCGImageAlphaPremultipliedFirst : {
109
+ if (byteOrderNormal) {
110
+ // ARGB8888, premultiplied
111
+ return MODE_Argb;
112
+ } else {
113
+ // BGRA8888, premultiplied
114
+ return MODE_bgrA;
115
+ }
116
+ }
117
+ break ;
118
+ case kCGImageAlphaPremultipliedLast : {
119
+ if (byteOrderNormal) {
120
+ // RGBA8888, premultiplied
121
+ return MODE_rgbA;
122
+ } else {
123
+ // ABGR8888, premultiplied
124
+ // Unsupported!
125
+ return MODE_LAST;
126
+ }
127
+ }
128
+ break ;
129
+ case kCGImageAlphaNone : {
130
+ if (byteOrderNormal) {
131
+ // RGB
132
+ return MODE_RGB;
133
+ } else {
134
+ // BGR
135
+ return MODE_BGR;
136
+ }
137
+ }
138
+ break ;
139
+ case kCGImageAlphaLast :
140
+ case kCGImageAlphaNoneSkipLast : {
141
+ if (byteOrderNormal) {
142
+ // RGBA or RGBX
143
+ return MODE_RGBA;
144
+ } else {
145
+ // ABGR or XBGR
146
+ // Unsupported!
147
+ return MODE_LAST;
148
+ }
149
+ }
150
+ break ;
151
+ case kCGImageAlphaFirst :
152
+ case kCGImageAlphaNoneSkipFirst : {
153
+ if (byteOrderNormal) {
154
+ // ARGB or XRGB
155
+ return MODE_ARGB;
156
+ } else {
157
+ // BGRA or BGRX
158
+ return MODE_BGRA;
159
+ }
160
+ }
161
+ break ;
162
+ case kCGImageAlphaOnly : {
163
+ // A
164
+ // Unsupported
165
+ return MODE_LAST;
166
+ }
167
+ break ;
168
+ default :
169
+ break ;
170
+ }
171
+ return MODE_LAST;
103
172
}
104
173
105
174
@interface SDWebPCoderFrame : NSObject
@@ -245,7 +314,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
245
314
if (limitBytes > 0 ) {
246
315
// Hack 32 BitsPerPixel
247
316
CGSize imageSize = CGSizeMake (canvasWidth, canvasHeight);
248
- CGSize framePixelSize = SDCalculateScaleDownPixelSize (limitBytes, imageSize, frameCount, 4 ) ;
317
+ CGSize framePixelSize = [SDImageCoderHelper scaledSizeWithImageSize: imageSize limitBytes: limitBytes bytesPerPixel: 4 frameCount: frameCount] ;
249
318
// Override thumbnail size
250
319
thumbnailSize = framePixelSize;
251
320
preserveAspectRatio = YES ;
@@ -317,8 +386,8 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
317
386
- (instancetype )initIncrementalWithOptions : (nullable SDImageCoderOptions *)options {
318
387
self = [super init ];
319
388
if (self) {
320
- // Progressive images need transparent, so always use premultiplied BGRA
321
- _idec = WebPINewRGB (MODE_bgrA , NULL , 0 , 0 );
389
+ // Progressive images need transparent, so always use premultiplied RGBA
390
+ _idec = WebPINewRGB (MODE_rgbA , NULL , 0 , 0 );
322
391
CGFloat scale = 1 ;
323
392
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
324
393
if (scaleFactor != nil ) {
@@ -394,7 +463,7 @@ - (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
394
463
if (_limitBytes > 0 ) {
395
464
// Hack 32 BitsPerPixel
396
465
CGSize imageSize = CGSizeMake (_canvasWidth, _canvasHeight);
397
- CGSize framePixelSize = SDCalculateScaleDownPixelSize (_limitBytes, imageSize, _frameCount, 4 ) ;
466
+ CGSize framePixelSize = [SDImageCoderHelper scaledSizeWithImageSize: imageSize limitBytes: _limitBytes bytesPerPixel: 4 frameCount: _frameCount] ;
398
467
// Override thumbnail size
399
468
_thumbnailSize = framePixelSize;
400
469
_preserveAspectRatio = YES ;
@@ -428,17 +497,18 @@ - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
428
497
CGDataProviderRef provider =
429
498
CGDataProviderCreateWithData (NULL , rgba, rgbaSize, NULL );
430
499
CGColorSpaceRef colorSpaceRef = [SDImageCoderHelper colorSpaceGetDeviceRGB ];
431
-
432
- CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst ;
500
+ // Because _idec use MODE_rgbA
501
+ CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast ;
433
502
size_t components = 4 ;
503
+ BOOL shouldInterpolate = YES ;
434
504
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault ;
435
505
// Why to use last_y for image height is because of libwebp's bug (https://bugs.chromium.org/p/webp/issues/detail?id=362)
436
506
// It will not keep memory barrier safe on x86 architechure (macOS & iPhone simulator) but on ARM architecture (iPhone & iPad & tv & watch) it works great
437
507
// If different threads use WebPIDecGetRGB to grab rgba bitmap, it will contain the previous decoded bitmap data
438
508
// So this will cause our drawed image looks strange(above is the current part but below is the previous part)
439
509
// We only grab the last_y height and draw the last_y height instead of total height image
440
510
// Besides fix, this can enhance performance since we do not need to create extra bitmap
441
- CGImageRef imageRef = CGImageCreate (width, last_y, 8 , components * 8 , components * width, colorSpaceRef, bitmapInfo, provider, NULL , NO , renderingIntent);
511
+ CGImageRef imageRef = CGImageCreate (width, last_y, 8 , components * 8 , components * width, colorSpaceRef, bitmapInfo, provider, NULL , shouldInterpolate , renderingIntent);
442
512
443
513
CGDataProviderRelease (provider);
444
514
@@ -546,20 +616,46 @@ - (nullable CGImageRef)sd_createWebpImageWithData:(WebPData)webpData colorSpace:
546
616
}
547
617
548
618
BOOL hasAlpha = config.input .has_alpha ;
549
- // iOS prefer BGRA8888 (premultiplied) or BGRX8888 bitmapInfo for screen rendering, which is same as `UIGraphicsBeginImageContext()` or `- [CALayer drawInContext:]`
550
- // use this bitmapInfo, combined with right colorspace, even without decode, can still avoid extra CA::Render::copy_image(which marked `Color Copied Images` from Instruments)
551
- CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host ;
552
- bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst ;
619
+ // From SDWebImage v5.17.0, use runtime detection of bitmap info instead of hardcode.
620
+ SDImagePixelFormat pixelFormat = [SDImageCoderHelper preferredPixelFormat: hasAlpha];
621
+ CGBitmapInfo bitmapInfo = pixelFormat.bitmapInfo ;
622
+ WEBP_CSP_MODE mode = ConvertCSPMode (bitmapInfo);
623
+ if (mode == MODE_LAST) {
624
+ NSAssert (NO , @" Unsupported libwebp preferred CGBitmapInfo: %d " , bitmapInfo);
625
+ return nil ;
626
+ }
627
+ config.output .colorspace = mode;
553
628
config.options .use_threads = 1 ;
554
- config. output . colorspace = MODE_bgrA;
629
+
555
630
556
631
// Use scaling for thumbnail
632
+ size_t width = config.input .width ;
633
+ size_t height = config.input .height ;
557
634
if (scaledSize.width != 0 && scaledSize.height != 0 ) {
558
635
config.options .use_scaling = 1 ;
559
636
config.options .scaled_width = scaledSize.width ;
560
637
config.options .scaled_height = scaledSize.height ;
638
+ width = scaledSize.width ;
639
+ height = scaledSize.height ;
561
640
}
562
641
642
+ // We alloc the buffer and do byte alignment by ourself. libwebp defaults does not byte alignment to `bitsPerPixel`, which cause the CoreAnimation unhappy and always trigger the `CA::Render::copy_image`
643
+ size_t bitsPerComponent = 8 ;
644
+ size_t components = (mode == MODE_RGB || mode == MODE_BGR) ? 3 : 4 ; // Actually always 4
645
+ size_t bitsPerPixel = bitsPerComponent * components;
646
+ // Read: https://github.com/path/FastImageCache#byte-alignment
647
+ // A properly aligned bytes-per-row value must be a multiple of 8 pixels × bytes per pixel
648
+ // For a typical ARGB image, the aligned bytes-per-row value is a multiple of 64.
649
+ size_t alignment = pixelFormat.alignment ;
650
+ size_t bytesPerRow = SDByteAlign (width * (bitsPerPixel / 8 ), alignment);
651
+ // size_t bytesPerRow = 6688;
652
+
653
+ void *rgba = WebPMalloc (bytesPerRow * height);
654
+ config.output .is_external_memory = 1 ;
655
+ config.output .u .RGBA .rgba = rgba;
656
+ config.output .u .RGBA .stride = (int )bytesPerRow;
657
+ config.output .u .RGBA .size = height * bytesPerRow;
658
+
563
659
// Decode the WebP image data into a RGBA value array
564
660
if (WebPDecode (webpData.bytes , webpData.size , &config) != VP8_STATUS_OK) {
565
661
return nil ;
@@ -568,13 +664,9 @@ - (nullable CGImageRef)sd_createWebpImageWithData:(WebPData)webpData colorSpace:
568
664
// Construct a UIImage from the decoded RGBA value array
569
665
CGDataProviderRef provider =
570
666
CGDataProviderCreateWithData (NULL , config.output .u .RGBA .rgba , config.output .u .RGBA .size , FreeImageData);
571
- size_t bitsPerComponent = 8 ;
572
- size_t bitsPerPixel = 32 ;
573
- size_t bytesPerRow = config.output .u .RGBA .stride ;
574
- size_t width = config.output .width ;
575
- size_t height = config.output .height ;
667
+ BOOL shouldInterpolate = YES ;
576
668
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault ;
577
- CGImageRef imageRef = CGImageCreate (width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL , NO , renderingIntent);
669
+ CGImageRef imageRef = CGImageCreate (width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL , shouldInterpolate , renderingIntent);
578
670
579
671
CGDataProviderRelease (provider);
580
672
@@ -756,9 +848,6 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
756
848
}
757
849
758
850
size_t bytesPerRow = CGImageGetBytesPerRow (imageRef);
759
- size_t bitsPerComponent = CGImageGetBitsPerComponent (imageRef);
760
- size_t bitsPerPixel = CGImageGetBitsPerPixel (imageRef);
761
- size_t components = bitsPerPixel / bitsPerComponent;
762
851
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo (imageRef);
763
852
CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask ;
764
853
CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask ;
@@ -891,7 +980,7 @@ - (void) updateWebPOptionsToConfig:(WebPConfig * _Nonnull)config
891
980
}
892
981
893
982
static void FreeImageData (void *info, const void *data, size_t size) {
894
- free ((void *)data);
983
+ WebPFree ((void *)data);
895
984
}
896
985
897
986
static int GetIntValueForKey (NSDictionary * _Nonnull dictionary, NSString * _Nonnull key, int defaultValue) {
@@ -968,7 +1057,7 @@ - (instancetype)initWithAnimatedImageData:(NSData *)data options:(nullable SDIma
968
1057
if (_limitBytes > 0 ) {
969
1058
// Hack 32 BitsPerPixel
970
1059
CGSize imageSize = CGSizeMake (_canvasWidth, _canvasHeight);
971
- CGSize framePixelSize = SDCalculateScaleDownPixelSize (_limitBytes, imageSize, _frameCount, 4 ) ;
1060
+ CGSize framePixelSize = [SDImageCoderHelper scaledSizeWithImageSize: imageSize limitBytes: _limitBytes bytesPerPixel: 4 frameCount: _frameCount] ;
972
1061
// Override thumbnail size
973
1062
_thumbnailSize = framePixelSize;
974
1063
_preserveAspectRatio = YES ;
@@ -1236,6 +1325,7 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
1236
1325
#else
1237
1326
image = [[UIImage alloc ] initWithCGImage: imageRef scale: _scale orientation: kCGImagePropertyOrientationUp ];
1238
1327
#endif
1328
+ image.sd_imageFormat = SDImageFormatWebP;
1239
1329
CGImageRelease (imageRef);
1240
1330
1241
1331
WebPDemuxReleaseIterator (&iter);
0 commit comments