Skip to content

Commit ff89654

Browse files
authored
feat(JXL): CICP read and write support for JPEG XL (#4968)
The JPEG XL color encoding metadata only supports a subset of CICP. So for example `srgb_p3d65_display` and `pq_rec2020_display` are supported, but `g26_xyzd65_display` is not. Custom primaries, custom white point and arbitrary gamma could be used to support more, but I didn't implement that. Tests for read and write were added. Signed-off-by: Brecht Van Lommel <[email protected]>
1 parent 693b09b commit ff89654

File tree

4 files changed

+111
-1
lines changed

4 files changed

+111
-1
lines changed

src/jpegxl.imageio/jxlinput.cpp

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <cassert>
1717
#include <cstdio>
1818

19+
#include <OpenImageIO/color.h>
1920
#include <OpenImageIO/filesystem.h>
2021
#include <OpenImageIO/fmath.h>
2122
#include <OpenImageIO/imageio.h>
@@ -54,7 +55,6 @@ class JxlInput final : public ImageInput {
5455
std::string m_filename;
5556
int m_next_scanline; // Which scanline is the next to read?
5657
uint32_t m_channels;
57-
JxlColorEncoding m_color_encoding;
5858
JxlDecoderPtr m_decoder;
5959
JxlResizableParallelRunnerPtr m_runner;
6060
std::unique_ptr<ImageSpec> m_config; // Saved copy of configuration spec
@@ -224,6 +224,8 @@ JxlInput::open(const std::string& name, ImageSpec& newspec)
224224
JxlDataType jxl_data_type;
225225
TypeDesc m_data_type;
226226
uint32_t bits = 0;
227+
JxlColorEncoding color_encoding {};
228+
bool have_color_encoding = false;
227229

228230
for (;;) {
229231
JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder.get());
@@ -300,6 +302,16 @@ JxlInput::open(const std::string& name, ImageSpec& newspec)
300302
errorfmt("JxlDecoderGetColorAsICCProfile failed\n");
301303
return false;
302304
}
305+
306+
// Get the color encoding of the pixel data
307+
// This will return JXL_DEC_ERR for a valid file without color
308+
// encoding information, so don't report an error.
309+
if (JXL_DEC_SUCCESS
310+
== JxlDecoderGetColorAsEncodedProfile(
311+
m_decoder.get(), JXL_COLOR_PROFILE_TARGET_DATA,
312+
&color_encoding)) {
313+
have_color_encoding = true;
314+
}
303315
} else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
304316
DBG std::cout << "JXL_DEC_NEED_IMAGE_OUT_BUFFER\n";
305317

@@ -348,6 +360,7 @@ JxlInput::open(const std::string& name, ImageSpec& newspec)
348360

349361
m_spec = ImageSpec(info.xsize, info.ysize, m_channels, m_data_type);
350362

363+
// Read ICC profile
351364
if (m_icc_profile.size() && m_icc_profile.data()) {
352365
m_spec.attribute("ICCProfile",
353366
TypeDesc(TypeDesc::UINT8, m_icc_profile.size()),
@@ -365,6 +378,21 @@ JxlInput::open(const std::string& name, ImageSpec& newspec)
365378
}
366379
}
367380

381+
// Read CICP from color encoding. Custom primaries, custom white point and
382+
// arbitrary gamma not supported currently.
383+
if (have_color_encoding && color_encoding.primaries != JXL_PRIMARIES_CUSTOM
384+
&& color_encoding.white_point != JXL_WHITE_POINT_CUSTOM
385+
&& color_encoding.transfer_function != JXL_TRANSFER_FUNCTION_GAMMA) {
386+
const int cicp[4] = { color_encoding.primaries,
387+
color_encoding.transfer_function, 0 /* RGB */,
388+
1 /* Full range */ };
389+
m_spec.attribute("CICP", TypeDesc(TypeDesc::INT, 4), cicp);
390+
const ColorConfig& colorconfig(ColorConfig::default_colorconfig());
391+
string_view interop_id = colorconfig.get_color_interop_id(cicp);
392+
if (!interop_id.empty())
393+
m_spec.attribute("oiio:ColorSpace", interop_id);
394+
}
395+
368396
newspec = m_spec;
369397
return true;
370398
}

src/jpegxl.imageio/jxloutput.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <cstdio>
77
#include <vector>
88

9+
#include <OpenImageIO/color.h>
910
#include <OpenImageIO/filesystem.h>
1011
#include <OpenImageIO/fmath.h>
1112
#include <OpenImageIO/imageio.h>
@@ -538,6 +539,8 @@ JxlOutput::save_image(const void* data)
538539
return false;
539540
}
540541

542+
bool wrote_colorspace = false;
543+
541544
// Write the ICC profile, if available
542545
const ParamValue* icc_profile_parameter = m_spec.find_attribute(
543546
"ICCProfile");
@@ -551,6 +554,59 @@ JxlOutput::save_image(const void* data)
551554
length)) {
552555
errorfmt("JxlEncoderSetICCProfile failed\n");
553556
}
557+
wrote_colorspace = true;
558+
}
559+
}
560+
561+
// Write CICP
562+
const ColorConfig& colorconfig(ColorConfig::default_colorconfig());
563+
const ParamValue* p = m_spec.find_attribute("CICP",
564+
TypeDesc(TypeDesc::INT, 4));
565+
string_view colorspace = m_spec.get_string_attribute("oiio:ColorSpace");
566+
cspan<int> cicp = (p) ? p->as_cspan<int>()
567+
: (!wrote_colorspace) ? colorconfig.get_cicp(colorspace)
568+
: cspan<int>();
569+
if (!cicp.empty()) {
570+
// JXL only has a subset of CICP, only write if supported. Custom
571+
// primaries and white point are not currently used but could help
572+
// support more CICP codes.
573+
JxlColorEncoding color_encoding {};
574+
color_encoding.primaries = JxlPrimaries(cicp[0]);
575+
color_encoding.transfer_function = JxlTransferFunction(cicp[1]);
576+
color_encoding.color_space = JXL_COLOR_SPACE_RGB;
577+
578+
bool supported_primaries = false;
579+
bool supported_transfer = false;
580+
581+
switch (color_encoding.primaries) {
582+
case JXL_PRIMARIES_SRGB:
583+
case JXL_PRIMARIES_2100:
584+
case JXL_PRIMARIES_P3:
585+
supported_primaries = true;
586+
color_encoding.white_point = JXL_WHITE_POINT_D65;
587+
break;
588+
case JXL_PRIMARIES_CUSTOM: // Not an actual CICP code in JXL
589+
break;
590+
}
591+
592+
switch (color_encoding.transfer_function) {
593+
case JXL_TRANSFER_FUNCTION_709:
594+
case JXL_TRANSFER_FUNCTION_UNKNOWN:
595+
case JXL_TRANSFER_FUNCTION_LINEAR:
596+
case JXL_TRANSFER_FUNCTION_SRGB:
597+
case JXL_TRANSFER_FUNCTION_PQ:
598+
case JXL_TRANSFER_FUNCTION_DCI:
599+
case JXL_TRANSFER_FUNCTION_HLG: supported_transfer = true; break;
600+
case JXL_TRANSFER_FUNCTION_GAMMA: // Not an actual CICP code
601+
break;
602+
}
603+
604+
if (supported_primaries && supported_transfer) {
605+
if (JXL_ENC_SUCCESS
606+
!= JxlEncoderSetColorEncoding(m_encoder.get(),
607+
&color_encoding)) {
608+
errorfmt("JxlEncoderSetColorEncoding failed\n");
609+
}
554610
}
555611
}
556612

testsuite/jxl/ref/out.txt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,26 @@ tahoe-icc.jxl : 128 x 96, 3 channel, uint8 jpegxl
1919
ICCProfile:profile_size: 560
2020
ICCProfile:profile_version: "2.1.0"
2121
ICCProfile:rendering_intent: "Perceptual"
22+
Reading tahoe-cicp-pq.jxl
23+
tahoe-cicp-pq.jxl : 128 x 96, 3 channel, uint8 jpegxl
24+
SHA-1: 069F1A3E5567349C2D34E535B29913029EF1B09C
25+
channel list: R, G, B
26+
CICP: 9, 16, 0, 1
27+
ICCProfile: 0, 0, 16, 248, 106, 120, 108, 32, 4, 64, 0, 0, 109, 110, 116, 114, ... [4344 x uint8]
28+
ICCProfile:attributes: "Reflective, Glossy, Positive, Color"
29+
ICCProfile:cmm_type: 1786276896
30+
ICCProfile:color_space: "RGB"
31+
ICCProfile:copyright: "CC0"
32+
ICCProfile:creation_date: "2019:12:01 00:00:00"
33+
ICCProfile:creator_signature: "6a786c20"
34+
ICCProfile:device_class: "Display device profile"
35+
ICCProfile:flags: "Not Embedded, Independent"
36+
ICCProfile:manufacturer: "0"
37+
ICCProfile:model: "0"
38+
ICCProfile:platform_signature: "Apple Computer, Inc."
39+
ICCProfile:profile_connection_space: "CIELAB"
40+
ICCProfile:profile_description: "RGB_D65_202_Per_PeQ"
41+
ICCProfile:profile_size: 4344
42+
ICCProfile:profile_version: "4.4.0"
43+
ICCProfile:rendering_intent: "Perceptual"
44+
oiio:ColorSpace: "pq_rec2020_display"

testsuite/jxl/run.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
command += info_command ("tahoe-icc.jxl", safematch=True)
1111
command += oiiotool ("tahoe-icc.jxl --iccwrite test-jxl.icc")
1212

13+
command += oiiotool ("../common/tahoe-tiny.tif --cicp \"9,16,9,1\" -o tahoe-cicp-pq.jxl")
14+
command += info_command ("tahoe-cicp-pq.jxl", safematch=True)
15+
1316
outputs = [
1417
"test-jxl.icc",
1518
"out.txt"

0 commit comments

Comments
 (0)