Skip to content

Commit 5b8045f

Browse files
committed
Fix that WebP image with ICC Profile does not show color correctly, should use the profile colorspace instead of device colorspace
1 parent 2a73606 commit 5b8045f

File tree

1 file changed

+46
-29
lines changed

1 file changed

+46
-29
lines changed

SDWebImageWebPCoder/Classes/SDImageWebPCoder.m

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)