Skip to content

Commit 8b7b680

Browse files
committed
Add ICC profile embedding support for WebP encoding
- Extract ICC profile from input color space using CGColorSpaceCopyICCData - Use WebPMux API to embed ICCP chunk in WebP container with copy_data=1 - Automatically convert Simple Format (VP8) to Extended Format (VP8X) - Add iOS 10+ availability check for CGColorSpaceCopyICCData - Skip ICC profile when maxFileSize is set (size limit takes priority) - Add comprehensive test (testWebPEncodingEmbedICCProfile) This fixes color reproduction issues with wide color gamut images (Display P3, Adobe RGB, etc.) on platforms that assume sRGB when ICC profile is missing. Fixes #119
1 parent f534cfe commit 8b7b680

File tree

2 files changed

+77
-7
lines changed

2 files changed

+77
-7
lines changed

SDWebImageWebPCoder/Classes/SDImageWebPCoder.m

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,7 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
818818
maxFileSize:(NSUInteger)maxFileSize
819819
options:(nullable SDImageCoderOptions *)options
820820
{
821-
NSData *webpData;
821+
NSData *webpData = nil;
822822
if (!imageRef) {
823823
return nil;
824824
}
@@ -950,16 +950,55 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
950950
result = WebPEncode(&config, &picture);
951951
WebPPictureFree(&picture);
952952
free(dest.data);
953-
953+
954954
if (result) {
955-
// success
956-
webpData = [NSData dataWithBytes:writer.mem length:writer.size];
955+
// Add ICC profile if present
956+
// See: https://developers.google.com/speed/webp/docs/riff_container#color_profile
957+
// Skip ICC profile when maxFileSize is set, as meeting the size limit takes priority
958+
CFDataRef iccData = NULL;
959+
if (colorSpace && maxFileSize == 0) {
960+
if (@available(iOS 10, tvOS 10, macOS 10.12, watchOS 3, *)) {
961+
iccData = CGColorSpaceCopyICCData(colorSpace);
962+
}
963+
}
964+
965+
if (iccData && CFDataGetLength(iccData) > 0) {
966+
// Use WebPMux to add ICCP chunk
967+
// This automatically converts Simple Format to Extended Format (VP8X)
968+
WebPMux *mux = WebPMuxNew();
969+
if (mux) {
970+
WebPData webp_input = {
971+
.bytes = writer.mem,
972+
.size = writer.size
973+
};
974+
975+
if (WebPMuxSetImage(mux, &webp_input, 1) == WEBP_MUX_OK) {
976+
WebPData icc_chunk = {
977+
.bytes = CFDataGetBytePtr(iccData),
978+
.size = CFDataGetLength(iccData)
979+
};
980+
981+
if (WebPMuxSetChunk(mux, "ICCP", &icc_chunk, 1) == WEBP_MUX_OK) {
982+
WebPData output;
983+
if (WebPMuxAssemble(mux, &output) == WEBP_MUX_OK) {
984+
webpData = [NSData dataWithBytes:output.bytes length:output.size];
985+
WebPDataClear(&output);
986+
}
987+
}
988+
}
989+
WebPMuxDelete(mux);
990+
}
991+
CFRelease(iccData);
992+
}
993+
994+
if (!webpData) {
995+
webpData = [NSData dataWithBytes:writer.mem length:writer.size];
996+
}
957997
} else {
958-
// failed
959998
webpData = nil;
960999
}
9611000
WebPMemoryWriterClear(&writer);
962-
1001+
9631002
return webpData;
9641003
}
9651004

Tests/SDWebImageWebPCoderTests.m

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ - (void)testWebPEncodingWithICCProfile {
381381
NSString *jpegPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestColorspaceBefore" ofType:@"jpeg"];
382382
NSData *jpegData = [NSData dataWithContentsOfFile:jpegPath];
383383
UIImage *jpegImage = [[UIImage alloc] initWithData:jpegData];
384-
384+
385385
NSData *webpData = [[SDImageWebPCoder sharedCoder] encodedDataWithImage:jpegImage format:SDImageFormatWebP options:nil];
386386
// Re-decode to pick color
387387
UIImage *webpImage = [[SDImageWebPCoder sharedCoder] decodedImageWithData:webpData options:nil];
@@ -404,6 +404,37 @@ - (void)testWebPEncodingWithICCProfile {
404404
#endif
405405
}
406406

407+
- (void)testWebPEncodingEmbedICCProfile {
408+
// Test that ICC profile is embedded in WebP
409+
NSString *jpegPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestColorspaceBefore" ofType:@"jpeg"];
410+
NSData *jpegData = [NSData dataWithContentsOfFile:jpegPath];
411+
UIImage *jpegImage = [[UIImage alloc] initWithData:jpegData];
412+
expect(jpegImage).notTo.beNil();
413+
414+
NSData *webpData = [[SDImageWebPCoder sharedCoder] encodedDataWithImage:jpegImage format:SDImageFormatWebP options:nil];
415+
expect(webpData).notTo.beNil();
416+
417+
// Check for ICCP chunk
418+
WebPData webp_data;
419+
WebPDataInit(&webp_data);
420+
webp_data.bytes = webpData.bytes;
421+
webp_data.size = webpData.length;
422+
423+
WebPDemuxer *demuxer = WebPDemux(&webp_data);
424+
expect(demuxer).notTo.beNil();
425+
426+
uint32_t flags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS);
427+
expect(flags & ICCP_FLAG).notTo.equal(0);
428+
429+
WebPChunkIterator chunk_iter;
430+
int result = WebPDemuxGetChunk(demuxer, "ICCP", 1, &chunk_iter);
431+
expect(result).notTo.equal(0);
432+
expect(chunk_iter.chunk.size).to.beGreaterThan(0);
433+
434+
WebPDemuxReleaseChunkIterator(&chunk_iter);
435+
WebPDemuxDelete(demuxer);
436+
}
437+
407438
@end
408439

409440
@implementation SDWebImageWebPCoderTests (Helpers)

0 commit comments

Comments
 (0)