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 preferredBitmapInfo: hasAlpha] ;
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,6 +88,89 @@ static inline CGContextRef _Nullable CreateWebPCanvas(BOOL hasAlpha, CGSize canv
8888 return canvas;
8989}
9090
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;
172+ }
173+
91174// 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`)
92175static inline CGSize SDCalculateScaleDownPixelSize (NSUInteger limitBytes, CGSize originalSize, NSUInteger frameCount, NSUInteger bytesPerPixel) {
93176 if (CGSizeEqualToSize (originalSize, CGSizeZero)) return CGSizeMake (1 , 1 );
@@ -264,6 +347,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
264347 UIImage *firstFrameImage = [[UIImage alloc ] initWithCGImage: imageRef scale: scale orientation: kCGImagePropertyOrientationUp ];
265348#endif
266349 firstFrameImage.sd_imageFormat = SDImageFormatWebP;
350+ firstFrameImage.sd_isDecoded = YES ; // We handle byte alignment and alloc bitmap buffer
267351 CGImageRelease (imageRef);
268352 WebPDemuxReleaseIterator (&iter);
269353 WebPDemuxDelete (demuxer);
@@ -317,8 +401,8 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
317401- (instancetype )initIncrementalWithOptions : (nullable SDImageCoderOptions *)options {
318402 self = [super init ];
319403 if (self) {
320- // Progressive images need transparent, so always use premultiplied BGRA
321- _idec = WebPINewRGB (MODE_bgrA , NULL , 0 , 0 );
404+ // Progressive images need transparent, so always use premultiplied RGBA
405+ _idec = WebPINewRGB (MODE_rgbA , NULL , 0 , 0 );
322406 CGFloat scale = 1 ;
323407 NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
324408 if (scaleFactor != nil ) {
@@ -428,17 +512,18 @@ - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
428512 CGDataProviderRef provider =
429513 CGDataProviderCreateWithData (NULL , rgba, rgbaSize, NULL );
430514 CGColorSpaceRef colorSpaceRef = [SDImageCoderHelper colorSpaceGetDeviceRGB ];
431-
432- CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst ;
515+ // Because _idec use MODE_rgbA
516+ CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast ;
433517 size_t components = 4 ;
518+ BOOL shouldInterpolate = YES ;
434519 CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault ;
435520 // Why to use last_y for image height is because of libwebp's bug (https://bugs.chromium.org/p/webp/issues/detail?id=362)
436521 // It will not keep memory barrier safe on x86 architechure (macOS & iPhone simulator) but on ARM architecture (iPhone & iPad & tv & watch) it works great
437522 // If different threads use WebPIDecGetRGB to grab rgba bitmap, it will contain the previous decoded bitmap data
438523 // So this will cause our drawed image looks strange(above is the current part but below is the previous part)
439524 // We only grab the last_y height and draw the last_y height instead of total height image
440525 // 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);
526+ CGImageRef imageRef = CGImageCreate (width, last_y, 8 , components * 8 , components * width, colorSpaceRef, bitmapInfo, provider, NULL , shouldInterpolate , renderingIntent);
442527
443528 CGDataProviderRelease (provider);
444529
@@ -546,20 +631,44 @@ - (nullable CGImageRef)sd_createWebpImageWithData:(WebPData)webpData colorSpace:
546631 }
547632
548633 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 ;
634+ // From SDWebImage v5.17.0, use runtime detection of bitmap info instead of hardcode.
635+ CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredBitmapInfo: hasAlpha];
636+ WEBP_CSP_MODE mode = ConvertCSPMode (bitmapInfo);
637+ if (mode == MODE_LAST) {
638+ NSAssert (NO , @" Unsupported libwebp preferred CGBitmapInfo: %d " , bitmapInfo);
639+ return nil ;
640+ }
641+ config.output .colorspace = mode;
553642 config.options .use_threads = 1 ;
554- config. output . colorspace = MODE_bgrA;
643+
555644
556645 // Use scaling for thumbnail
646+ size_t width = config.input .width ;
647+ size_t height = config.input .height ;
557648 if (scaledSize.width != 0 && scaledSize.height != 0 ) {
558649 config.options .use_scaling = 1 ;
559650 config.options .scaled_width = scaledSize.width ;
560651 config.options .scaled_height = scaledSize.height ;
652+ width = scaledSize.width ;
653+ height = scaledSize.height ;
561654 }
562655
656+ // 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`
657+ size_t bitsPerComponent = 8 ;
658+ size_t components = (mode == MODE_RGB || mode == MODE_BGR) ? 3 : 4 ; // Actually always 4
659+ size_t bitsPerPixel = bitsPerComponent * components;
660+ // Read: https://github.com/path/FastImageCache#byte-alignment
661+ // A properly aligned bytes-per-row value must be a multiple of 8 pixels × bytes per pixel
662+ // For a typical ARGB image, the aligned bytes-per-row value is a multiple of 64.
663+ size_t alignment = [SDImageCoderHelper preferredByteAlignment ];
664+ size_t bytesPerRow = SDByteAlign (width * (bitsPerPixel / 8 ), alignment);
665+
666+ void *rgba = WebPMalloc (bytesPerRow * height);
667+ config.output .is_external_memory = 1 ;
668+ config.output .u .RGBA .rgba = rgba;
669+ config.output .u .RGBA .stride = (int )bytesPerRow;
670+ config.output .u .RGBA .size = height * bytesPerRow;
671+
563672 // Decode the WebP image data into a RGBA value array
564673 if (WebPDecode (webpData.bytes , webpData.size , &config) != VP8_STATUS_OK) {
565674 return nil ;
@@ -568,13 +677,9 @@ - (nullable CGImageRef)sd_createWebpImageWithData:(WebPData)webpData colorSpace:
568677 // Construct a UIImage from the decoded RGBA value array
569678 CGDataProviderRef provider =
570679 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 ;
680+ BOOL shouldInterpolate = YES ;
576681 CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault ;
577- CGImageRef imageRef = CGImageCreate (width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL , NO , renderingIntent);
682+ CGImageRef imageRef = CGImageCreate (width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL , shouldInterpolate , renderingIntent);
578683
579684 CGDataProviderRelease (provider);
580685
@@ -756,9 +861,6 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
756861 }
757862
758863 size_t bytesPerRow = CGImageGetBytesPerRow (imageRef);
759- size_t bitsPerComponent = CGImageGetBitsPerComponent (imageRef);
760- size_t bitsPerPixel = CGImageGetBitsPerPixel (imageRef);
761- size_t components = bitsPerPixel / bitsPerComponent;
762864 CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo (imageRef);
763865 CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask ;
764866 CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask ;
@@ -891,7 +993,7 @@ - (void) updateWebPOptionsToConfig:(WebPConfig * _Nonnull)config
891993}
892994
893995static void FreeImageData (void *info, const void *data, size_t size) {
894- free ((void *)data);
996+ WebPFree ((void *)data);
895997}
896998
897999static int GetIntValueForKey (NSDictionary * _Nonnull dictionary, NSString * _Nonnull key, int defaultValue) {
@@ -1236,6 +1338,8 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
12361338#else
12371339 image = [[UIImage alloc ] initWithCGImage: imageRef scale: _scale orientation: kCGImagePropertyOrientationUp ];
12381340#endif
1341+ image.sd_imageFormat = SDImageFormatWebP;
1342+ image.sd_isDecoded = YES ; // We handle byte alignment and alloc bitmap buffer
12391343 CGImageRelease (imageRef);
12401344
12411345 WebPDemuxReleaseIterator (&iter);
0 commit comments