Skip to content

Commit 38a4f94

Browse files
jessey-gitlgritz
authored andcommitted
feat(webp): Support reading/writing the ICCProfile attribute (#4878)
Enable ICCProfile attribute support for WEBP for both input and output. Like other formats this support is passive; pixel data will not be altered by the mere presence of the ICCProfile attribute (i.e. it functions like other informational metadata only). The WebP API is a bit odd when it comes to writing the ICCProfile. We must use the "mux" API set; the "advanced" API is apparently not advanced enough to add the metadata to the file. This wouldn't be too bad except that the mux API is at odds with how we write out the data. Before: When it comes time to do the file write, we would pass our own file writer into webp. The writes would go straight to disk. Now: If ICC profile data is necessary to write, we let webp use its own writer to write to memory. We then assemble the in-memory file and the ICC profile data together and pass that entire package to our writer as a final step. When no ICC profile data is used, the old write behavior is kept. Added `oiiotool` test variations which uses ICC profiles with WEBP. --------- Signed-off-by: Jesse Yurkovich <[email protected]>
1 parent 7cfdabf commit 38a4f94

File tree

8 files changed

+162
-35
lines changed

8 files changed

+162
-35
lines changed

src/doc/builtinplugins.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3034,6 +3034,10 @@ open standard for lossy-compressed images for use on the web.
30343034
* - ImageSpec Attribute
30353035
- Type
30363036
- WebP header data or explanation
3037+
* - ``ICCProfile``
3038+
- uint8[]
3039+
- The ICC color profile. A variety of other ``ICCProfile:*`` attributes
3040+
may also be present, extracted from the main profile.
30373041
* - ``oiio:Movie``
30383042
- int
30393043
- If nonzero, indicates that it's a multi-subimage file intended to

src/webp.imageio/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
if (WebP_FOUND)
66
add_oiio_plugin (webpinput.cpp webpoutput.cpp
7-
LINK_LIBRARIES WebP::webp WebP::webpdemux
7+
LINK_LIBRARIES WebP::webp WebP::webpdemux WebP::libwebpmux
88
DEFINITIONS "USE_WEBP=1")
99
else ()
1010
message (STATUS "WebP plugin will not be built")

src/webp.imageio/webpinput.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,20 @@ WebpInput::open(const std::string& name, ImageSpec& spec,
195195
}
196196
if (m_demux_flags & ICCP_FLAG
197197
&& WebPDemuxGetChunk(m_demux, "ICCP", 1, &chunk_iter)) {
198-
// FIXME: This is where we would extract an ICC profile. Come back
199-
// to this when I have found an example webp containing an ICC
200-
// profile that I can use as a test case, otherwise I'm just
201-
// guessing.
198+
cspan<uint8_t> icc_span(chunk_iter.chunk.bytes, chunk_iter.chunk.size);
199+
m_spec.attribute("ICCProfile",
200+
TypeDesc(TypeDesc::UINT8, icc_span.size_bytes()),
201+
icc_span.data());
202+
203+
std::string errormsg;
204+
const bool ok = decode_icc_profile(icc_span, m_spec, errormsg);
202205
WebPDemuxReleaseChunkIterator(&chunk_iter);
206+
207+
if (!ok && OIIO::get_int_attribute("imageinput:strict")) {
208+
errorfmt("Possible corrupt file, could not decode ICC profile: {}\n",
209+
errormsg);
210+
return false;
211+
}
203212
}
204213

205214
// Make space for the decoded image

src/webp.imageio/webpoutput.cpp

Lines changed: 96 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <cstdio>
66

77
#include <webp/encode.h>
8+
#include <webp/mux.h>
89

910
#include <OpenImageIO/filesystem.h>
1011
#include <OpenImageIO/imagebufalgo.h>
@@ -44,6 +45,8 @@ class WebpOutput final : public ImageOutput {
4445
m_scanline_size = 0;
4546
ioproxy_clear();
4647
}
48+
49+
bool write_complete_data();
4750
};
4851

4952

@@ -119,11 +122,9 @@ WebpOutput::open(const std::string& name, const ImageSpec& spec, OpenMode mode)
119122
// Lossless encoding (0=lossy(default), 1=lossless).
120123
m_webp_config.lossless = int(is_lossless);
121124

122-
m_webp_picture.use_argb = m_webp_config.lossless;
123-
m_webp_picture.width = m_spec.width;
124-
m_webp_picture.height = m_spec.height;
125-
m_webp_picture.writer = WebpImageWriter;
126-
m_webp_picture.custom_ptr = (void*)ioproxy();
125+
m_webp_picture.use_argb = m_webp_config.lossless;
126+
m_webp_picture.width = m_spec.width;
127+
m_webp_picture.height = m_spec.height;
127128

128129
// forcing UINT8 format
129130
m_spec.set_format(TypeDesc::UINT8);
@@ -137,6 +138,94 @@ WebpOutput::open(const std::string& name, const ImageSpec& spec, OpenMode mode)
137138
}
138139

139140

141+
bool
142+
WebpOutput::write_complete_data()
143+
{
144+
// Check if we have an optional ICC Profile to write.
145+
const unsigned char* icc_data = nullptr;
146+
uint32_t icc_data_length = 0;
147+
bool has_icc_data = false;
148+
const ParamValue* icc_profile_parameter = m_spec.find_attribute(
149+
"ICCProfile");
150+
if (icc_profile_parameter != nullptr) {
151+
icc_data = (const unsigned char*)icc_profile_parameter->data();
152+
icc_data_length = icc_profile_parameter->type().size();
153+
has_icc_data = (icc_data && icc_data_length > 0);
154+
}
155+
156+
// If we have ICC data, encode to memory first. This is required in order
157+
// to use the WebPMux assembly API below.
158+
WebPMemoryWriter wrt;
159+
if (has_icc_data) {
160+
WebPMemoryWriterInit(&wrt);
161+
m_webp_picture.writer = WebPMemoryWrite;
162+
m_webp_picture.custom_ptr = &wrt;
163+
} else {
164+
m_webp_picture.writer = WebpImageWriter;
165+
m_webp_picture.custom_ptr = (void*)ioproxy();
166+
}
167+
168+
if (m_spec.nchannels == 4) {
169+
if (m_convert_alpha) {
170+
// WebP requires unassociated alpha, and it's sRGB.
171+
// Handle this all by wrapping an IB around it.
172+
ImageSpec specwrap(m_spec.width, m_spec.height, 4, TypeUInt8);
173+
ImageBuf bufwrap(specwrap, cspan<uint8_t>(m_uncompressed_image));
174+
ROI rgbroi(0, m_spec.width, 0, m_spec.height, 0, 1, 0, 3);
175+
ImageBufAlgo::pow(bufwrap, bufwrap, 2.2f, rgbroi);
176+
ImageBufAlgo::unpremult(bufwrap, bufwrap);
177+
ImageBufAlgo::pow(bufwrap, bufwrap, 1.0f / 2.2f, rgbroi);
178+
}
179+
180+
WebPPictureImportRGBA(&m_webp_picture, m_uncompressed_image.data(),
181+
m_scanline_size);
182+
} else {
183+
WebPPictureImportRGB(&m_webp_picture, m_uncompressed_image.data(),
184+
m_scanline_size);
185+
}
186+
187+
if (!WebPEncode(&m_webp_config, &m_webp_picture)) {
188+
errorfmt("Failed to encode {} as WebP image", m_filename);
189+
if (has_icc_data) {
190+
WebPMemoryWriterClear(&wrt);
191+
}
192+
close();
193+
return false;
194+
}
195+
196+
// If there's no ICC data to write, we are done at this point.
197+
bool ok = true;
198+
199+
// Otherwise, assemble the final WebP package and write it out.
200+
if (has_icc_data) {
201+
WebPMux* mux = WebPMuxNew();
202+
203+
WebPData image_data = { wrt.mem, wrt.size };
204+
WebPMuxSetImage(mux, &image_data, false);
205+
206+
WebPData icc_chunk = { icc_data, size_t(icc_data_length) };
207+
WebPMuxSetChunk(mux, "ICCP", &icc_chunk, false);
208+
209+
WebPData assembly;
210+
if (WebPMuxAssemble(mux, &assembly) != WEBP_MUX_OK) {
211+
errorfmt("Failed to assemble {} as WebP image", m_filename);
212+
WebPMuxDelete(mux);
213+
WebPMemoryWriterClear(&wrt);
214+
return false;
215+
}
216+
217+
ok = ioproxy()->write(assembly.bytes, assembly.size) == assembly.size;
218+
219+
WebPDataClear(&assembly);
220+
WebPMuxDelete(mux);
221+
WebPMemoryWriterClear(&wrt);
222+
}
223+
224+
return ok;
225+
}
226+
227+
228+
140229
bool
141230
WebpOutput::write_scanline(int y, int z, TypeDesc format, const void* data,
142231
stride_t xstride)
@@ -150,32 +239,9 @@ WebpOutput::write_scanline(int y, int z, TypeDesc format, const void* data,
150239
data = to_native_scanline(format, data, xstride, scratch, m_dither, y, z);
151240
memcpy(&m_uncompressed_image[y * m_scanline_size], data, m_scanline_size);
152241

242+
/* If this was the final scanline, we are done. */
153243
if (y == m_spec.height - 1) {
154-
if (m_spec.nchannels == 4) {
155-
if (m_convert_alpha) {
156-
// WebP requires unassociated alpha, and it's sRGB.
157-
// Handle this all by wrapping an IB around it.
158-
ImageSpec specwrap(m_spec.width, m_spec.height, 4, TypeUInt8);
159-
ImageBuf bufwrap(specwrap,
160-
cspan<uint8_t>(m_uncompressed_image));
161-
ROI rgbroi(0, m_spec.width, 0, m_spec.height, 0, 1, 0, 3);
162-
ImageBufAlgo::pow(bufwrap, bufwrap, 2.2f, rgbroi);
163-
ImageBufAlgo::unpremult(bufwrap, bufwrap);
164-
ImageBufAlgo::pow(bufwrap, bufwrap, 1.0f / 2.2f, rgbroi);
165-
}
166-
167-
WebPPictureImportRGBA(&m_webp_picture, m_uncompressed_image.data(),
168-
m_scanline_size);
169-
} else {
170-
WebPPictureImportRGB(&m_webp_picture, m_uncompressed_image.data(),
171-
m_scanline_size);
172-
}
173-
174-
if (!WebPEncode(&m_webp_config, &m_webp_picture)) {
175-
errorfmt("Failed to encode {} as WebP image", m_filename);
176-
close();
177-
return false;
178-
}
244+
return write_complete_data();
179245
}
180246
return true;
181247
}

testsuite/oiiotool-attribs/ref/out-jpeg9d.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,25 @@ tahoe-icc.jpg : 128 x 96, 3 channel, uint8 jpeg
161161
ICCProfile:rendering_intent: "Perceptual"
162162
jpeg:subsampling: "4:2:0"
163163
oiio:ColorSpace: "sRGB"
164+
Reading tahoe-icc.webp
165+
tahoe-icc.webp : 128 x 96, 3 channel, uint8 webp
166+
SHA-1: 407D97F3D62F90C7F9A78AF85989ED3C06C59523
167+
channel list: R, G, B
168+
ICCProfile: 0, 0, 2, 48, 65, 68, 66, 69, 2, 16, 0, 0, 109, 110, 116, 114, ... [560 x uint8]
169+
ICCProfile:attributes: "Reflective, Glossy, Positive, Color"
170+
ICCProfile:cmm_type: 1094992453
171+
ICCProfile:color_space: "RGB"
172+
ICCProfile:copyright: "Copyright 1999 Adobe Systems Incorporated"
173+
ICCProfile:creation_date: "1999:06:03 00:00:00"
174+
ICCProfile:creator_signature: "41444245"
175+
ICCProfile:device_class: "Display device profile"
176+
ICCProfile:flags: "Not Embedded, Independent"
177+
ICCProfile:manufacturer: "6e6f6e65"
178+
ICCProfile:model: "0"
179+
ICCProfile:platform_signature: "Apple Computer, Inc."
180+
ICCProfile:profile_connection_space: "XYZ"
181+
ICCProfile:profile_description: "Adobe RGB (1998)"
182+
ICCProfile:profile_size: 560
183+
ICCProfile:profile_version: "2.1.0"
184+
ICCProfile:rendering_intent: "Perceptual"
185+
oiio:ColorSpace: "sRGB"

testsuite/oiiotool-attribs/ref/out.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,25 @@ tahoe-icc.jpg : 128 x 96, 3 channel, uint8 jpeg
161161
ICCProfile:rendering_intent: "Perceptual"
162162
jpeg:subsampling: "4:2:0"
163163
oiio:ColorSpace: "sRGB"
164+
Reading tahoe-icc.webp
165+
tahoe-icc.webp : 128 x 96, 3 channel, uint8 webp
166+
SHA-1: 407D97F3D62F90C7F9A78AF85989ED3C06C59523
167+
channel list: R, G, B
168+
ICCProfile: 0, 0, 2, 48, 65, 68, 66, 69, 2, 16, 0, 0, 109, 110, 116, 114, ... [560 x uint8]
169+
ICCProfile:attributes: "Reflective, Glossy, Positive, Color"
170+
ICCProfile:cmm_type: 1094992453
171+
ICCProfile:color_space: "RGB"
172+
ICCProfile:copyright: "Copyright 1999 Adobe Systems Incorporated"
173+
ICCProfile:creation_date: "1999:06:03 00:00:00"
174+
ICCProfile:creator_signature: "41444245"
175+
ICCProfile:device_class: "Display device profile"
176+
ICCProfile:flags: "Not Embedded, Independent"
177+
ICCProfile:manufacturer: "6e6f6e65"
178+
ICCProfile:model: "0"
179+
ICCProfile:platform_signature: "Apple Computer, Inc."
180+
ICCProfile:profile_connection_space: "XYZ"
181+
ICCProfile:profile_description: "Adobe RGB (1998)"
182+
ICCProfile:profile_size: 560
183+
ICCProfile:profile_version: "2.1.0"
184+
ICCProfile:rendering_intent: "Perceptual"
185+
oiio:ColorSpace: "sRGB"
560 Bytes
Binary file not shown.

testsuite/oiiotool-attribs/run.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,14 @@
5151

5252
# Test adding and extracting ICC profiles
5353
command += oiiotool ("../common/tahoe-tiny.tif --iccread ref/test.icc -o tahoe-icc.jpg")
54+
command += oiiotool ("../common/tahoe-tiny.tif --iccread ref/test-webp.icc -o tahoe-icc.webp")
5455
command += info_command ("tahoe-icc.jpg", safematch=True)
56+
command += info_command ("tahoe-icc.webp", safematch=True)
5557
command += oiiotool ("tahoe-icc.jpg --iccwrite test.icc")
58+
command += oiiotool ("tahoe-icc.webp --iccwrite test-webp.icc")
5659

5760
outputs = [
5861
"test.icc",
62+
"test-webp.icc",
5963
"out.txt"
6064
]

0 commit comments

Comments
 (0)