7171// / Used for animated WebP, which need a canvas for decoding (rendering), possible apply a scale transform for thumbnail decoding (avoiding post-rescale using vImage)
7272// / See more in #73
7373static 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 ;
7676 // Check whether we need to use thumbnail
7777 CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize: CGSizeMake (canvasSize.width, canvasSize.height) scaleSize: thumbnailSize preserveAspectRatio: preserveAspectRatio shouldScaleUp: NO ];
7878 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
8888 return canvas;
8989}
9090
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;
103172}
104173
105174@interface SDWebPCoderFrame : NSObject
@@ -245,7 +314,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
245314 if (limitBytes > 0 ) {
246315 // Hack 32 BitsPerPixel
247316 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] ;
249318 // Override thumbnail size
250319 thumbnailSize = framePixelSize;
251320 preserveAspectRatio = YES ;
@@ -317,8 +386,8 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
317386- (instancetype )initIncrementalWithOptions : (nullable SDImageCoderOptions *)options {
318387 self = [super init ];
319388 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 );
322391 CGFloat scale = 1 ;
323392 NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
324393 if (scaleFactor != nil ) {
@@ -394,7 +463,7 @@ - (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
394463 if (_limitBytes > 0 ) {
395464 // Hack 32 BitsPerPixel
396465 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] ;
398467 // Override thumbnail size
399468 _thumbnailSize = framePixelSize;
400469 _preserveAspectRatio = YES ;
@@ -428,17 +497,18 @@ - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
428497 CGDataProviderRef provider =
429498 CGDataProviderCreateWithData (NULL , rgba, rgbaSize, NULL );
430499 CGColorSpaceRef colorSpaceRef = [SDImageCoderHelper colorSpaceGetDeviceRGB ];
431-
432- CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst ;
500+ // Because _idec use MODE_rgbA
501+ CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast ;
433502 size_t components = 4 ;
503+ BOOL shouldInterpolate = YES ;
434504 CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault ;
435505 // Why to use last_y for image height is because of libwebp's bug (https://bugs.chromium.org/p/webp/issues/detail?id=362)
436506 // It will not keep memory barrier safe on x86 architechure (macOS & iPhone simulator) but on ARM architecture (iPhone & iPad & tv & watch) it works great
437507 // If different threads use WebPIDecGetRGB to grab rgba bitmap, it will contain the previous decoded bitmap data
438508 // So this will cause our drawed image looks strange(above is the current part but below is the previous part)
439509 // We only grab the last_y height and draw the last_y height instead of total height image
440510 // 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);
442512
443513 CGDataProviderRelease (provider);
444514
@@ -546,20 +616,46 @@ - (nullable CGImageRef)sd_createWebpImageWithData:(WebPData)webpData colorSpace:
546616 }
547617
548618 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;
553628 config.options .use_threads = 1 ;
554- config. output . colorspace = MODE_bgrA;
629+
555630
556631 // Use scaling for thumbnail
632+ size_t width = config.input .width ;
633+ size_t height = config.input .height ;
557634 if (scaledSize.width != 0 && scaledSize.height != 0 ) {
558635 config.options .use_scaling = 1 ;
559636 config.options .scaled_width = scaledSize.width ;
560637 config.options .scaled_height = scaledSize.height ;
638+ width = scaledSize.width ;
639+ height = scaledSize.height ;
561640 }
562641
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+
563659 // Decode the WebP image data into a RGBA value array
564660 if (WebPDecode (webpData.bytes , webpData.size , &config) != VP8_STATUS_OK) {
565661 return nil ;
@@ -568,13 +664,9 @@ - (nullable CGImageRef)sd_createWebpImageWithData:(WebPData)webpData colorSpace:
568664 // Construct a UIImage from the decoded RGBA value array
569665 CGDataProviderRef provider =
570666 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 ;
576668 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);
578670
579671 CGDataProviderRelease (provider);
580672
@@ -756,9 +848,6 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
756848 }
757849
758850 size_t bytesPerRow = CGImageGetBytesPerRow (imageRef);
759- size_t bitsPerComponent = CGImageGetBitsPerComponent (imageRef);
760- size_t bitsPerPixel = CGImageGetBitsPerPixel (imageRef);
761- size_t components = bitsPerPixel / bitsPerComponent;
762851 CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo (imageRef);
763852 CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask ;
764853 CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask ;
@@ -891,7 +980,7 @@ - (void) updateWebPOptionsToConfig:(WebPConfig * _Nonnull)config
891980}
892981
893982static void FreeImageData (void *info, const void *data, size_t size) {
894- free ((void *)data);
983+ WebPFree ((void *)data);
895984}
896985
897986static int GetIntValueForKey (NSDictionary * _Nonnull dictionary, NSString * _Nonnull key, int defaultValue) {
@@ -968,7 +1057,7 @@ - (instancetype)initWithAnimatedImageData:(NSData *)data options:(nullable SDIma
9681057 if (_limitBytes > 0 ) {
9691058 // Hack 32 BitsPerPixel
9701059 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] ;
9721061 // Override thumbnail size
9731062 _thumbnailSize = framePixelSize;
9741063 _preserveAspectRatio = YES ;
@@ -1236,6 +1325,7 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
12361325#else
12371326 image = [[UIImage alloc ] initWithCGImage: imageRef scale: _scale orientation: kCGImagePropertyOrientationUp ];
12381327#endif
1328+ image.sd_imageFormat = SDImageFormatWebP;
12391329 CGImageRelease (imageRef);
12401330
12411331 WebPDemuxReleaseIterator (&iter);
0 commit comments