Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 45 additions & 6 deletions SDWebImageWebPCoder/Classes/SDImageWebPCoder.m
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
maxFileSize:(NSUInteger)maxFileSize
options:(nullable SDImageCoderOptions *)options
{
NSData *webpData;
NSData *webpData = nil;
if (!imageRef) {
return nil;
}
Expand Down Expand Up @@ -950,16 +950,55 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef
result = WebPEncode(&config, &picture);
WebPPictureFree(&picture);
free(dest.data);

if (result) {
// success
webpData = [NSData dataWithBytes:writer.mem length:writer.size];
// Add ICC profile if present
// See: https://developers.google.com/speed/webp/docs/riff_container#color_profile
// Skip ICC profile when maxFileSize is set, as meeting the size limit takes priority
CFDataRef iccData = NULL;
if (colorSpace && maxFileSize == 0) {
if (@available(iOS 10, tvOS 10, macOS 10.12, watchOS 3, *)) {
iccData = CGColorSpaceCopyICCData(colorSpace);
}
}

if (iccData && CFDataGetLength(iccData) > 0) {
// Use WebPMux to add ICCP chunk
// This automatically converts Simple Format to Extended Format (VP8X)
WebPMux *mux = WebPMuxNew();
if (mux) {
WebPData webp_input = {
.bytes = writer.mem,
.size = writer.size
};

if (WebPMuxSetImage(mux, &webp_input, 0) == WEBP_MUX_OK) {
WebPData icc_chunk = {
.bytes = CFDataGetBytePtr(iccData),
.size = CFDataGetLength(iccData)
};

if (WebPMuxSetChunk(mux, "ICCP", &icc_chunk, 0) == WEBP_MUX_OK) {
WebPData output;
if (WebPMuxAssemble(mux, &output) == WEBP_MUX_OK) {
webpData = [NSData dataWithBytes:output.bytes length:output.size];
WebPDataClear(&output);
}
}
}
WebPMuxDelete(mux);
}
CFRelease(iccData);
}

if (!webpData) {
webpData = [NSData dataWithBytes:writer.mem length:writer.size];
}
} else {
// failed
webpData = nil;
}
WebPMemoryWriterClear(&writer);

return webpData;
}

Expand Down
Binary file added Tests/Images/TestDisplayP3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 69 additions & 1 deletion Tests/SDWebImageWebPCoderTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ - (void)testWebPEncodingWithICCProfile {
NSString *jpegPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestColorspaceBefore" ofType:@"jpeg"];
NSData *jpegData = [NSData dataWithContentsOfFile:jpegPath];
UIImage *jpegImage = [[UIImage alloc] initWithData:jpegData];

NSData *webpData = [[SDImageWebPCoder sharedCoder] encodedDataWithImage:jpegImage format:SDImageFormatWebP options:nil];
// Re-decode to pick color
UIImage *webpImage = [[SDImageWebPCoder sharedCoder] decodedImageWithData:webpData options:nil];
Expand All @@ -404,6 +404,74 @@ - (void)testWebPEncodingWithICCProfile {
#endif
}

- (void)testWebPEncodingEmbedICCProfile {
// Test that ICC profile is embedded in WebP
NSString *jpegPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestColorspaceBefore" ofType:@"jpeg"];
NSData *jpegData = [NSData dataWithContentsOfFile:jpegPath];
UIImage *jpegImage = [[UIImage alloc] initWithData:jpegData];
expect(jpegImage).notTo.beNil();

NSData *webpData = [[SDImageWebPCoder sharedCoder] encodedDataWithImage:jpegImage format:SDImageFormatWebP options:nil];
expect(webpData).notTo.beNil();

// Check for ICCP chunk
WebPData webp_data;
WebPDataInit(&webp_data);
webp_data.bytes = webpData.bytes;
webp_data.size = webpData.length;

WebPDemuxer *demuxer = WebPDemux(&webp_data);
expect(demuxer).notTo.beNil();

uint32_t flags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS);
expect(flags & ICCP_FLAG).notTo.equal(0);

WebPChunkIterator chunk_iter;
int result = WebPDemuxGetChunk(demuxer, "ICCP", 1, &chunk_iter);
expect(result).notTo.equal(0);
expect(chunk_iter.chunk.size).to.beGreaterThan(0);

WebPDemuxReleaseChunkIterator(&chunk_iter);
WebPDemuxDelete(demuxer);
}

- (void)testWebPEncodingDisplayP3 {
// Test Display P3 wide color gamut encoding with ICC profile
NSString *pngPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestDisplayP3" ofType:@"png"];
if (!pngPath) {
return;
}

NSData *pngData = [NSData dataWithContentsOfFile:pngPath];
UIImage *p3Image = [[UIImage alloc] initWithData:pngData];
expect(p3Image).notTo.beNil();

NSData *webpData = [[SDImageWebPCoder sharedCoder] encodedDataWithImage:p3Image
format:SDImageFormatWebP
options:@{SDImageCoderEncodeCompressionQuality: @0.95}];
expect(webpData).notTo.beNil();

// Check for ICCP chunk
WebPData webp_data;
WebPDataInit(&webp_data);
webp_data.bytes = webpData.bytes;
webp_data.size = webpData.length;

WebPDemuxer *demuxer = WebPDemux(&webp_data);
expect(demuxer).notTo.beNil();

uint32_t flags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS);
expect(flags & ICCP_FLAG).notTo.equal(0);

WebPChunkIterator chunk_iter;
int result = WebPDemuxGetChunk(demuxer, "ICCP", 1, &chunk_iter);
expect(result).notTo.equal(0);
expect(chunk_iter.chunk.size).to.beGreaterThan(0);

WebPDemuxReleaseChunkIterator(&chunk_iter);
WebPDemuxDelete(demuxer);
}

@end

@implementation SDWebImageWebPCoderTests (Helpers)
Expand Down
16 changes: 15 additions & 1 deletion Tests/SDWebImageWebPCoderTests.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objectVersion = 54;
objects = {

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

/* Begin PBXFileReference section */
Expand All @@ -40,6 +42,7 @@
808C918F213FD131004B0F7C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
808C919A213FD2B2004B0F7C /* TestImageStatic.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestImageStatic.webp; sourceTree = "<group>"; };
808C919B213FD2B2004B0F7C /* TestImageAnimated.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestImageAnimated.webp; sourceTree = "<group>"; };
B359E6022EB3ACBE0064933A /* TestDisplayP3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = TestDisplayP3.png; sourceTree = "<group>"; };
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>"; };
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>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -107,6 +110,7 @@
808C9199213FD2B2004B0F7C /* Images */ = {
isa = PBXGroup;
children = (
B359E6022EB3ACBE0064933A /* TestDisplayP3.png */,
32B4C7912AFB959E003A4BC7 /* TestColorspaceBefore.jpeg */,
326420302A5D53E300EE3E46 /* TestColorspaceStatic.webp */,
325E268D25C82BE1000B807B /* TestImageGrayscale.jpg */,
Expand Down Expand Up @@ -211,6 +215,7 @@
32B4C78C2AFB954C003A4BC7 /* TestColorspaceStatic.webp in Resources */,
32B4C78D2AFB954C003A4BC7 /* TestImageBlendAnimated.webp in Resources */,
32B4C7932AFB959E003A4BC7 /* TestColorspaceBefore.jpeg in Resources */,
B359E6042EB3ACBE0064933A /* TestDisplayP3.png in Resources */,
32B4C78B2AFB954C003A4BC7 /* TestImageGrayscale.jpg in Resources */,
32B4C78F2AFB954C003A4BC7 /* TestImageStatic.webp in Resources */,
);
Expand All @@ -224,6 +229,7 @@
808C919D213FD2B2004B0F7C /* TestImageAnimated.webp in Resources */,
808C919C213FD2B2004B0F7C /* TestImageStatic.webp in Resources */,
32B4C7922AFB959E003A4BC7 /* TestColorspaceBefore.jpeg in Resources */,
B359E6032EB3ACBE0064933A /* TestDisplayP3.png in Resources */,
326420312A5D53E300EE3E46 /* TestColorspaceStatic.webp in Resources */,
325E268E25C82BE1000B807B /* TestImageGrayscale.jpg in Resources */,
);
Expand Down Expand Up @@ -262,10 +268,14 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-SDWebImageWebPCoderTests/Pods-SDWebImageWebPCoderTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-SDWebImageWebPCoderTests/Pods-SDWebImageWebPCoderTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SDWebImageWebPCoderTests/Pods-SDWebImageWebPCoderTests-frameworks.sh\"\n";
Expand Down Expand Up @@ -297,10 +307,14 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-SDWebImageWebPCoderTests-macOS/Pods-SDWebImageWebPCoderTests-macOS-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-SDWebImageWebPCoderTests-macOS/Pods-SDWebImageWebPCoderTests-macOS-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SDWebImageWebPCoderTests-macOS/Pods-SDWebImageWebPCoderTests-macOS-frameworks.sh\"\n";
Expand Down
Loading