Skip to content

Commit d15b424

Browse files
authored
Merge pull request #120 from KYHyeon/feature/icc-profile-embedding
Add ICC Profile Embedding Support for WebP Encoding
2 parents f534cfe + aa92e07 commit d15b424

File tree

4 files changed

+129
-8
lines changed

4 files changed

+129
-8
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, 0) == WEBP_MUX_OK) {
976+
WebPData icc_chunk = {
977+
.bytes = CFDataGetBytePtr(iccData),
978+
.size = CFDataGetLength(iccData)
979+
};
980+
981+
if (WebPMuxSetChunk(mux, "ICCP", &icc_chunk, 0) == 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/Images/TestDisplayP3.png

76.2 KB
Loading

Tests/SDWebImageWebPCoderTests.m

Lines changed: 69 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,74 @@ - (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+
438+
- (void)testWebPEncodingDisplayP3 {
439+
// Test Display P3 wide color gamut encoding with ICC profile
440+
NSString *pngPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestDisplayP3" ofType:@"png"];
441+
if (!pngPath) {
442+
return;
443+
}
444+
445+
NSData *pngData = [NSData dataWithContentsOfFile:pngPath];
446+
UIImage *p3Image = [[UIImage alloc] initWithData:pngData];
447+
expect(p3Image).notTo.beNil();
448+
449+
NSData *webpData = [[SDImageWebPCoder sharedCoder] encodedDataWithImage:p3Image
450+
format:SDImageFormatWebP
451+
options:@{SDImageCoderEncodeCompressionQuality: @0.95}];
452+
expect(webpData).notTo.beNil();
453+
454+
// Check for ICCP chunk
455+
WebPData webp_data;
456+
WebPDataInit(&webp_data);
457+
webp_data.bytes = webpData.bytes;
458+
webp_data.size = webpData.length;
459+
460+
WebPDemuxer *demuxer = WebPDemux(&webp_data);
461+
expect(demuxer).notTo.beNil();
462+
463+
uint32_t flags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS);
464+
expect(flags & ICCP_FLAG).notTo.equal(0);
465+
466+
WebPChunkIterator chunk_iter;
467+
int result = WebPDemuxGetChunk(demuxer, "ICCP", 1, &chunk_iter);
468+
expect(result).notTo.equal(0);
469+
expect(chunk_iter.chunk.size).to.beGreaterThan(0);
470+
471+
WebPDemuxReleaseChunkIterator(&chunk_iter);
472+
WebPDemuxDelete(demuxer);
473+
}
474+
407475
@end
408476

409477
@implementation SDWebImageWebPCoderTests (Helpers)

Tests/SDWebImageWebPCoderTests.xcodeproj/project.pbxproj

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 51;
6+
objectVersion = 54;
77
objects = {
88

99
/* Begin PBXBuildFile section */
@@ -23,6 +23,8 @@
2323
808C918E213FD131004B0F7C /* SDWebImageWebPCoderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 808C918D213FD131004B0F7C /* SDWebImageWebPCoderTests.m */; };
2424
808C919C213FD2B2004B0F7C /* TestImageStatic.webp in Resources */ = {isa = PBXBuildFile; fileRef = 808C919A213FD2B2004B0F7C /* TestImageStatic.webp */; };
2525
808C919D213FD2B2004B0F7C /* TestImageAnimated.webp in Resources */ = {isa = PBXBuildFile; fileRef = 808C919B213FD2B2004B0F7C /* TestImageAnimated.webp */; };
26+
B359E6032EB3ACBE0064933A /* TestDisplayP3.png in Resources */ = {isa = PBXBuildFile; fileRef = B359E6022EB3ACBE0064933A /* TestDisplayP3.png */; };
27+
B359E6042EB3ACBE0064933A /* TestDisplayP3.png in Resources */ = {isa = PBXBuildFile; fileRef = B359E6022EB3ACBE0064933A /* TestDisplayP3.png */; };
2628
/* End PBXBuildFile section */
2729

2830
/* Begin PBXFileReference section */
@@ -40,6 +42,7 @@
4042
808C918F213FD131004B0F7C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
4143
808C919A213FD2B2004B0F7C /* TestImageStatic.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestImageStatic.webp; sourceTree = "<group>"; };
4244
808C919B213FD2B2004B0F7C /* TestImageAnimated.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestImageAnimated.webp; sourceTree = "<group>"; };
45+
B359E6022EB3ACBE0064933A /* TestDisplayP3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = TestDisplayP3.png; sourceTree = "<group>"; };
4346
D92E6791BF088D1A101E670E /* Pods-SDWebImageWebPCoderTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SDWebImageWebPCoderTests.release.xcconfig"; path = "../Pods/Target Support Files/Pods-SDWebImageWebPCoderTests/Pods-SDWebImageWebPCoderTests.release.xcconfig"; sourceTree = "<group>"; };
4447
F121CFAEBEFA209D335C5C6D /* Pods-SDWebImageWebPCoderTests-macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SDWebImageWebPCoderTests-macOS.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-SDWebImageWebPCoderTests-macOS/Pods-SDWebImageWebPCoderTests-macOS.debug.xcconfig"; sourceTree = "<group>"; };
4548
/* End PBXFileReference section */
@@ -107,6 +110,7 @@
107110
808C9199213FD2B2004B0F7C /* Images */ = {
108111
isa = PBXGroup;
109112
children = (
113+
B359E6022EB3ACBE0064933A /* TestDisplayP3.png */,
110114
32B4C7912AFB959E003A4BC7 /* TestColorspaceBefore.jpeg */,
111115
326420302A5D53E300EE3E46 /* TestColorspaceStatic.webp */,
112116
325E268D25C82BE1000B807B /* TestImageGrayscale.jpg */,
@@ -211,6 +215,7 @@
211215
32B4C78C2AFB954C003A4BC7 /* TestColorspaceStatic.webp in Resources */,
212216
32B4C78D2AFB954C003A4BC7 /* TestImageBlendAnimated.webp in Resources */,
213217
32B4C7932AFB959E003A4BC7 /* TestColorspaceBefore.jpeg in Resources */,
218+
B359E6042EB3ACBE0064933A /* TestDisplayP3.png in Resources */,
214219
32B4C78B2AFB954C003A4BC7 /* TestImageGrayscale.jpg in Resources */,
215220
32B4C78F2AFB954C003A4BC7 /* TestImageStatic.webp in Resources */,
216221
);
@@ -224,6 +229,7 @@
224229
808C919D213FD2B2004B0F7C /* TestImageAnimated.webp in Resources */,
225230
808C919C213FD2B2004B0F7C /* TestImageStatic.webp in Resources */,
226231
32B4C7922AFB959E003A4BC7 /* TestColorspaceBefore.jpeg in Resources */,
232+
B359E6032EB3ACBE0064933A /* TestDisplayP3.png in Resources */,
227233
326420312A5D53E300EE3E46 /* TestColorspaceStatic.webp in Resources */,
228234
325E268E25C82BE1000B807B /* TestImageGrayscale.jpg in Resources */,
229235
);
@@ -262,10 +268,14 @@
262268
inputFileListPaths = (
263269
"${PODS_ROOT}/Target Support Files/Pods-SDWebImageWebPCoderTests/Pods-SDWebImageWebPCoderTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
264270
);
271+
inputPaths = (
272+
);
265273
name = "[CP] Embed Pods Frameworks";
266274
outputFileListPaths = (
267275
"${PODS_ROOT}/Target Support Files/Pods-SDWebImageWebPCoderTests/Pods-SDWebImageWebPCoderTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
268276
);
277+
outputPaths = (
278+
);
269279
runOnlyForDeploymentPostprocessing = 0;
270280
shellPath = /bin/sh;
271281
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SDWebImageWebPCoderTests/Pods-SDWebImageWebPCoderTests-frameworks.sh\"\n";
@@ -297,10 +307,14 @@
297307
inputFileListPaths = (
298308
"${PODS_ROOT}/Target Support Files/Pods-SDWebImageWebPCoderTests-macOS/Pods-SDWebImageWebPCoderTests-macOS-frameworks-${CONFIGURATION}-input-files.xcfilelist",
299309
);
310+
inputPaths = (
311+
);
300312
name = "[CP] Embed Pods Frameworks";
301313
outputFileListPaths = (
302314
"${PODS_ROOT}/Target Support Files/Pods-SDWebImageWebPCoderTests-macOS/Pods-SDWebImageWebPCoderTests-macOS-frameworks-${CONFIGURATION}-output-files.xcfilelist",
303315
);
316+
outputPaths = (
317+
);
304318
runOnlyForDeploymentPostprocessing = 0;
305319
shellPath = /bin/sh;
306320
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SDWebImageWebPCoderTests-macOS/Pods-SDWebImageWebPCoderTests-macOS-frameworks.sh\"\n";

0 commit comments

Comments
 (0)