@@ -64,6 +64,7 @@ @implementation SDImageWebPCoder {
6464 NSUInteger _frameCount;
6565 NSArray <SDWebPCoderFrame *> *_frames;
6666 CGContextRef _canvas;
67+ CGColorSpaceRef _colorSpace;
6768 BOOL _hasAnimation;
6869 BOOL _hasAlpha;
6970 BOOL _finished;
@@ -86,6 +87,10 @@ - (void)dealloc {
8687 CGContextRelease (_canvas);
8788 _canvas = NULL ;
8889 }
90+ if (_colorSpace) {
91+ CGColorSpaceRelease (_colorSpace);
92+ _colorSpace = NULL ;
93+ }
8994}
9095
9196+ (instancetype )sharedCoder {
@@ -131,21 +136,6 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
131136 scale = 1 ;
132137 }
133138 }
134- if (!hasAnimation) {
135- // for static single webp image
136- CGImageRef imageRef = [self sd_createWebpImageWithData: webpData];
137- if (!imageRef) {
138- return nil ;
139- }
140- #if SD_UIKIT || SD_WATCH
141- UIImage *staticImage = [[UIImage alloc ] initWithCGImage: imageRef scale: scale orientation: UIImageOrientationUp];
142- #else
143- UIImage *staticImage = [[UIImage alloc ] initWithCGImage: imageRef scale: scale orientation: kCGImagePropertyOrientationUp ];
144- #endif
145- CGImageRelease (imageRef);
146- WebPDemuxDelete (demuxer);
147- return staticImage;
148- }
149139
150140 // for animated webp image
151141 WebPIterator iter;
@@ -155,10 +145,12 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
155145 WebPDemuxDelete (demuxer);
156146 return nil ;
157147 }
148+ CGColorSpaceRef colorSpace = [self sd_colorSpaceWithDemuxer: demuxer];
158149
159- if (decodeFirstFrame) {
150+ if (!hasAnimation || decodeFirstFrame) {
160151 // first frame for animated webp image
161- CGImageRef imageRef = [self sd_createWebpImageWithData: iter.fragment];
152+ CGImageRef imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: colorSpace];
153+ CGColorSpaceRelease (colorSpace);
162154#if SD_UIKIT || SD_WATCH
163155 UIImage *firstFrameImage = [[UIImage alloc ] initWithCGImage: imageRef scale: scale orientation: UIImageOrientationUp];
164156#else
@@ -180,14 +172,15 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
180172 CGContextRef canvas = CGBitmapContextCreate (NULL , canvasWidth, canvasHeight, 8 , 0 , [SDImageCoderHelper colorSpaceGetDeviceRGB ], bitmapInfo);
181173 if (!canvas) {
182174 WebPDemuxDelete (demuxer);
175+ CGColorSpaceRelease (colorSpace);
183176 return nil ;
184177 }
185178
186179 NSMutableArray <SDImageFrame *> *frames = [NSMutableArray array ];
187180
188181 do {
189182 @autoreleasepool {
190- CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas: canvas iterator: iter];
183+ CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas: canvas iterator: iter colorSpace: colorSpace ];
191184 if (!imageRef) {
192185 continue ;
193186 }
@@ -208,6 +201,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
208201 WebPDemuxReleaseIterator (&iter);
209202 WebPDemuxDelete (demuxer);
210203 CGContextRelease (canvas);
204+ CGColorSpaceRelease (colorSpace);
211205
212206 UIImage *animatedImage = [SDImageCoderHelper animatedImageWithFrames: frames];
213207 animatedImage.sd_imageLoopCount = loopCount;
@@ -318,7 +312,7 @@ - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
318312 return image;
319313}
320314
321- - (void )sd_blendWebpImageWithCanvas : (CGContextRef)canvas iterator : (WebPIterator)iter {
315+ - (void )sd_blendWebpImageWithCanvas : (CGContextRef)canvas iterator : (WebPIterator)iter colorSpace : (nonnull CGColorSpaceRef) colorSpaceRef {
322316 size_t canvasHeight = CGBitmapContextGetHeight (canvas);
323317 CGFloat tmpX = iter.x_offset ;
324318 CGFloat tmpY = canvasHeight - iter.height - iter.y_offset ;
@@ -327,7 +321,7 @@ - (void)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)
327321 if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
328322 CGContextClearRect (canvas, imageRect);
329323 } else {
330- CGImageRef imageRef = [self sd_createWebpImageWithData: iter.fragment];
324+ CGImageRef imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: colorSpaceRef ];
331325 if (!imageRef) {
332326 return ;
333327 }
@@ -341,8 +335,8 @@ - (void)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)
341335 }
342336}
343337
344- - (nullable CGImageRef)sd_drawnWebpImageWithCanvas : (CGContextRef)canvas iterator : (WebPIterator)iter CF_RETURNS_RETAINED {
345- CGImageRef imageRef = [self sd_createWebpImageWithData: iter.fragment];
338+ - (nullable CGImageRef)sd_drawnWebpImageWithCanvas : (CGContextRef)canvas iterator : (WebPIterator)iter colorSpace : (nonnull CGColorSpaceRef) colorSpaceRef CF_RETURNS_RETAINED {
339+ CGImageRef imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: colorSpaceRef ];
346340 if (!imageRef) {
347341 return nil ;
348342 }
@@ -369,7 +363,7 @@ - (nullable CGImageRef)sd_drawnWebpImageWithCanvas:(CGContextRef)canvas iterator
369363 return newImageRef;
370364}
371365
372- - (nullable CGImageRef)sd_createWebpImageWithData : (WebPData)webpData CF_RETURNS_RETAINED {
366+ - (nullable CGImageRef)sd_createWebpImageWithData : (WebPData)webpData colorSpace : (nonnull CGColorSpaceRef) colorSpaceRef CF_RETURNS_RETAINED {
373367 WebPDecoderConfig config;
374368 if (!WebPInitDecoderConfig (&config)) {
375369 return nil ;
@@ -382,11 +376,10 @@ - (nullable CGImageRef)sd_createWebpImageWithData:(WebPData)webpData CF_RETURNS_
382376 BOOL hasAlpha = config.input .has_alpha ;
383377 // iOS prefer BGRA8888 (premultiplied) or BGRX8888 bitmapInfo for screen rendering, which is same as `UIGraphicsBeginImageContext()` or `- [CALayer drawInContext:]`
384378 // 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)
385- WEBP_CSP_MODE colorspace = MODE_bgrA;
386379 CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host ;
387380 bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst ;
388381 config.options .use_threads = 1 ;
389- config.output .colorspace = colorspace ;
382+ config.output .colorspace = MODE_bgrA ;
390383
391384 // Decode the WebP image data into a RGBA value array
392385 if (WebPDecode (webpData.bytes , webpData.size , &config) != VP8_STATUS_OK) {
@@ -406,7 +399,6 @@ - (nullable CGImageRef)sd_createWebpImageWithData:(WebPData)webpData CF_RETURNS_
406399 size_t bitsPerComponent = 8 ;
407400 size_t bitsPerPixel = 32 ;
408401 size_t bytesPerRow = config.output .u .RGBA .stride ;
409- CGColorSpaceRef colorSpaceRef = [SDImageCoderHelper colorSpaceGetDeviceRGB ];
410402 CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault ;
411403 CGImageRef imageRef = CGImageCreate (width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL , NO , renderingIntent);
412404
@@ -425,6 +417,28 @@ - (NSTimeInterval)sd_frameDurationWithIterator:(WebPIterator)iter {
425417 return duration / 1000.0 ;
426418}
427419
420+ // Create and return the correct colorspace by checking the ICC Profile
421+ - (nonnull CGColorSpaceRef)sd_colorSpaceWithDemuxer : (nonnull WebPDemuxer *)demuxer CF_RETURNS_RETAINED {
422+ // WebP contains ICC Profile should use the desired colorspace, instead of default device colorspace
423+ // See: https://developers.google.com/speed/webp/docs/riff_container#color_profile
424+
425+ WebPChunkIterator chunk_iter;
426+ CGColorSpaceRef colorSpaceRef = NULL ;
427+
428+ int result = WebPDemuxGetChunk (demuxer, " ICCP" , 1 , &chunk_iter);
429+ if (result) {
430+ NSData *profileData = [NSData dataWithBytes: chunk_iter.chunk.bytes length: chunk_iter.chunk.size];
431+ colorSpaceRef = CGColorSpaceCreateWithICCProfile ((__bridge CFDataRef)profileData);
432+ }
433+
434+ if (!colorSpaceRef) {
435+ colorSpaceRef = [SDImageCoderHelper colorSpaceGetDeviceRGB ];
436+ CGColorSpaceRetain (colorSpaceRef);
437+ }
438+
439+ return colorSpaceRef;
440+ }
441+
428442#pragma mark - Encode
429443- (BOOL )canEncodeToFormat : (SDImageFormat)format {
430444 return (format == SDImageFormatWebP);
@@ -770,6 +784,9 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
770784 }
771785 _canvas = canvas;
772786 }
787+ if (!_colorSpace) {
788+ _colorSpace = [self sd_colorSpaceWithDemuxer: _demux];
789+ }
773790
774791 SDWebPCoderFrame *frame = _frames[index];
775792 UIImage *image;
@@ -782,7 +799,7 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
782799 WebPDemuxReleaseIterator (&iter);
783800 return nil ;
784801 }
785- CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas: _canvas iterator: iter];
802+ CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas: _canvas iterator: iter colorSpace: _colorSpace ];
786803 if (!imageRef) {
787804 return nil ;
788805 }
@@ -810,9 +827,9 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
810827 do {
811828 @autoreleasepool {
812829 if ((size_t )iter.frame_num == endIndex) {
813- [self sd_blendWebpImageWithCanvas: _canvas iterator: iter];
830+ [self sd_blendWebpImageWithCanvas: _canvas iterator: iter colorSpace: _colorSpace ];
814831 } else {
815- CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas: _canvas iterator: iter];
832+ CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas: _canvas iterator: iter colorSpace: _colorSpace ];
816833 if (!imageRef) {
817834 return nil ;
818835 }
0 commit comments