Skip to content
Merged
9 changes: 9 additions & 0 deletions src/doc/oiiotool.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4676,6 +4676,15 @@ will be printed with the command `oiiotool --colorconfiginfo`.

This was added to OpenImageIO 2.5.

.. option:: --cicp <pri>,<trc>,<mtx>,<vfr>

The `--cicp` command adds, modifies, or removes a `"CICP"` attribute
belonging to the top image, stored as an array of four integers.
The integers represent, in order, the color primaries, transfer
function, color matrix (for YUV colorspaces), and
video-full-range-flag.

This was added to OpenImageIO 3.1.

|

Expand Down
11 changes: 11 additions & 0 deletions src/doc/stdmetadata.rst
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,17 @@ Color information
window that are not overlapping the pixel data window. If not supplied,
the default is black (0 in all channels).

.. option:: "CICP" : int[4]

The CICP color space information, as defined by
`ITU-T H.273 <https://www.itu.int/rec/T-REC-H.273>`_. This is an array
of four integers, with the following meanings:

- `[0]` : color primaries
- `[1]` : transfer characteristics
- `[2]` : matrix coefficients
- `[3]` : full range flag

.. option:: "ICCProfile" : uint8[]
"ICCProfile:...various..." : ...various types...

Expand Down
6 changes: 6 additions & 0 deletions src/include/OpenImageIO/imageio.h
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,9 @@ class OIIO_API ImageInput {
/// - `"exif"` :
/// Can this format store Exif camera data?
///
/// - `"cicp"` :
/// Does this format support embedding CICP metadata?
///
/// - `"ioproxy"` :
/// Does this format reader support reading from an `IOProxy`?
///
Expand Down Expand Up @@ -2527,6 +2530,9 @@ class OIIO_API ImageOutput {
/// Does this format allow 0x0 sized images, i.e. an image file
/// with metadata only and no pixels?
///
/// - `"cicp"` :
/// Does this format support embedding CICP metadata?
///
Comment on lines +2533 to +2535
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does a similar comment need to go in ImageInput::supports so that it's documented for both?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sure does! On it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to make sure I've appropriately documented in oiiotool.rst and stdmetadata.rst as well.

/// This list of queries may be extended in future releases. Since this
/// can be done simply by recognizing new query strings, and does not
/// require any new API entry points, addition of support for new
Expand Down
2 changes: 2 additions & 0 deletions src/libOpenImageIO/formatspec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1295,6 +1295,8 @@ void
ImageSpec::set_colorspace(string_view colorspace)
{
ColorConfig::default_colorconfig().set_colorspace(*this, colorspace);
// Invalidate potentially contradictory metadata
erase_attribute("CICP");
}


Expand Down
57 changes: 57 additions & 0 deletions src/oiiotool/oiiotool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2248,6 +2248,60 @@ icc_read(Oiiotool& ot, cspan<const char*> argv)
}


// Set, modify, or remove the top image's CICP (ITU-T H.273) metadata.
class OpSetCICP final : public OiiotoolOp {
public:
OpSetCICP(Oiiotool& ot, string_view opname, cspan<const char*> argv)
: OiiotoolOp(ot, opname, argv, 1)
{
inplace(true); // This action operates in-place
cicp = args(1);
}
OpSetCICP(Oiiotool& ot, string_view opname, int argc, const char* argv[])
: OpSetCICP(ot, opname, { argv, span_size_t(argc) })
{
}
bool setup() override
{
ir(0)->metadata_modified(true);
return true;
}
bool impl(span<ImageBuf*> img) override
{
// Because this is an in-place operation, img[0] is the same as
// img[1].
if (cicp.empty()) {
img[0]->specmod().erase_attribute("CICP");
return true;
}
std::vector<int> vals { 0, 0, 0, 1 };
auto p = img[0]->spec().find_attribute("CICP",
TypeDesc(TypeDesc::INT, 4));
if (p) {
const int* existing = static_cast<const int*>(p->data());
for (int i = 0; i < 4; ++i)
vals[i] = existing[i];
}
Strutil::extract_from_list_string<int>(vals, cicp);
img[0]->specmod().attribute("CICP", TypeDesc(TypeDesc::INT, 4),
vals.data());
return true;
}

private:
string_view cicp;
};


// --cicp
static void
action_cicp(Oiiotool& ot, cspan<const char*> argv)
{
OpSetCICP op(ot, "cicp", argv);
op();
}



// --colorconfig
static void
Expand Down Expand Up @@ -7173,6 +7227,9 @@ Oiiotool::getargs(int argc, char* argv[])
ap.arg("--iccread %s:FILENAME")
.help("Add the contents of the file to the top image as its ICC profile")
.OTACTION(icc_read);
ap.arg("--cicp %s:CICP")
.help("Set or modifiy CICP metadata for supporting output formats (e.g., \"12,16,0,1\")") //; selectively persist existing values if not specified (e.g., \",,,0\")")
.OTACTION(action_cicp);
// clang-format on

if (ap.parse_args(argc, (const char**)argv) < 0) {
Expand Down
31 changes: 28 additions & 3 deletions src/png.imageio/png_pvt.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ For further information see the following mailing list threads:
OIIO_PLUGIN_NAMESPACE_BEGIN

#define ICC_PROFILE_ATTR "ICCProfile"

#define CICP_ATTR "CICP"

namespace PNG_pvt {

Expand Down Expand Up @@ -224,7 +224,7 @@ read_info(png_structp& sp, png_infop& ip, int& bit_depth, int& color_type,
int srgb_intent;
double gamma = 0.0;
if (png_get_sRGB(sp, ip, &srgb_intent)) {
spec.set_colorspace("srgb_rec709_scene");
spec.attribute("oiio:ColorSpace", "srgb_rec709_scene");
} else if (png_get_gAMA(sp, ip, &gamma) && gamma > 0.0) {
// Round gamma to the nearest hundredth to prevent stupid
// precision choices and make it easier for apps to make
Expand All @@ -235,7 +235,7 @@ read_info(png_structp& sp, png_infop& ip, int& bit_depth, int& color_type,
set_colorspace_rec709_gamma(spec, g);
} else {
// If there's no info at all, assume sRGB.
set_colorspace(spec, "srgb_rec709_scene");
spec.attribute("oiio:ColorSpace", "srgb_rec709_scene");
}

if (png_get_valid(sp, ip, PNG_INFO_iCCP)) {
Expand Down Expand Up @@ -326,6 +326,16 @@ read_info(png_structp& sp, png_infop& ip, int& bit_depth, int& color_type,

interlace_type = png_get_interlace_type(sp, ip);

#ifdef PNG_cICP_SUPPORTED
{
png_byte pri = 0, trc = 0, mtx = 0, vfr = 0;
if (png_get_cICP(sp, ip, &pri, &trc, &mtx, &vfr)) {
int cicp[4] = { pri, trc, mtx, vfr };
spec.attribute(CICP_ATTR, TypeDesc(TypeDesc::INT, 4), cicp);
}
}
#endif

#ifdef PNG_eXIf_SUPPORTED
// Recent version of PNG and libpng (>= 1.6.32, I think) have direct
// support for Exif chunks. Older versions don't support it, and I'm not
Expand Down Expand Up @@ -713,6 +723,21 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec,
(png_uint_32)(yres * scale), unittype);
}

#ifdef PNG_cICP_SUPPORTED
const ParamValue* p = spec.find_attribute(CICP_ATTR,
TypeDesc(TypeDesc::INT, 4));
if (p) {
const int* int_vals = static_cast<const int*>(p->data());
png_byte vals[4];
for (int i = 0; i < 4; ++i)
vals[i] = static_cast<png_byte>(int_vals[i]);
if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp)
return "Could not set PNG cICP chunk";
// libpng will only write the chunk if the third byte is 0
png_set_cICP(sp, ip, vals[0], vals[1], (png_byte)0, vals[3]);
}
#endif

#ifdef PNG_eXIf_SUPPORTED
std::vector<char> exifBlob;
encode_exif(spec, exifBlob, endian::big);
Expand Down
9 changes: 8 additions & 1 deletion src/png.imageio/pnginput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ class PNGInput final : public ImageInput {
const char* format_name(void) const override { return "png"; }
int supports(string_view feature) const override
{
return (feature == "ioproxy" || feature == "exif");
return (feature == "ioproxy"
#ifdef PNG_eXIf_SUPPORTED
|| feature == "exif"
#endif
#ifdef PNG_cICP_SUPPORTED
|| feature == "cicp"
#endif
);
}
bool valid_file(Filesystem::IOProxy* ioproxy) const override;
bool open(const std::string& name, ImageSpec& newspec) override;
Expand Down
3 changes: 3 additions & 0 deletions src/png.imageio/pngoutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ class PNGOutput final : public ImageOutput {
return (feature == "alpha" || feature == "ioproxy"
#ifdef PNG_eXIf_SUPPORTED
Comment thread
lgritz marked this conversation as resolved.
|| feature == "exif"
#endif
#ifdef PNG_cICP_SUPPORTED
|| feature == "cicp"
#endif
);
}
Expand Down
16 changes: 16 additions & 0 deletions testsuite/png/ref/out-libpng15.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ Reading exif.png
exif.png : 64 x 64, 3 channel, uint8 png
SHA-1: 7CB41FEA50720B48BE0C145E1473982B23E9AB77
channel list: R, G, B
Exif:ExifVersion: "0230"
Exif:FlashPixVersion: "0100"
Exif:FocalLength: 45.7 (45.7 mm)
Exif:WhiteBalance: 0 (auto)
oiio:ColorSpace: "srgb_rec709_scene"
alphagamma:
1 x 1, 4 channel, float png
Expand Down Expand Up @@ -86,5 +90,17 @@ gimp_gradient:
Monochrome: No
smallalpha.png : 1 x 1, 4 channel, uint8 png
Pixel (0, 0): 240 108 119 1 (0.94117653 0.42352945 0.4666667 0.003921569)
cicp:
16 x 16, 4 channel, float png
channel list: R, G, B, A
oiio:ColorSpace: "srgb_rec709_scene"
removed_cicp:
16 x 16, 4 channel, float png
channel list: R, G, B, A
oiio:ColorSpace: "srgb_rec709_scene"
remove_cicp_via_set_colorspace:
16 x 16, 4 channel, float png
channel list: R, G, B, A
oiio:ColorSpace: "g22_rec709_display"
Comparing "test16.png" and "ref/test16.png"
PASS
13 changes: 13 additions & 0 deletions testsuite/png/ref/out.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,18 @@ gimp_gradient:
Monochrome: No
smallalpha.png : 1 x 1, 4 channel, uint8 png
Pixel (0, 0): 240 108 119 1 (0.94117653 0.42352945 0.4666667 0.003921569)
cicp:
16 x 16, 4 channel, float png
channel list: R, G, B, A
CICP: 1, 13, 0, 1
oiio:ColorSpace: "srgb_rec709_scene"
Comment thread
lgritz marked this conversation as resolved.
removed_cicp:
16 x 16, 4 channel, float png
channel list: R, G, B, A
oiio:ColorSpace: "srgb_rec709_scene"
remove_cicp_via_set_colorspace:
16 x 16, 4 channel, float png
channel list: R, G, B, A
oiio:ColorSpace: "g22_rec709_display"
Comparing "test16.png" and "ref/test16.png"
PASS
Binary file modified testsuite/png/ref/test16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 11 additions & 1 deletion testsuite/png/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,15 @@
command += oiiotool ("--pattern fill:color=0.00235,0.00106,0.00117,0.0025 1x1 4 -d uint8 -o smallalpha.png")
command += oiiotool ("--no-autopremult --dumpdata smallalpha.png")

outputs = [ "test16.png", "out.txt" ]
# Test embedding CICP metadata in PNG files
command += oiiotool ("-i test16.png --cicp 1,13 -o test16.png")
command += oiiotool ("-echo cicp: test16.png --eraseattrib Software --printinfo")
# FIXME: The following test fails on Windows
# Workaround is to avoid partial modifications and always specify all CICP values
# command += oiiotool ("-echo modified_cicp: test16.png --eraseattrib Software --cicp \",,,0\" --printinfo")
command += oiiotool ("-echo removed_cicp: test16.png --eraseattrib Software --cicp \"\" --printinfo")

# Test that "set_colorspace" removes CICP metadata
command += oiiotool ("-echo remove_cicp_via_set_colorspace: test16.png --eraseattrib Software --cicp 1,13 --iscolorspace g22_rec709_display --printinfo")

outputs = [ "test16.png", "out.txt" ]
13 changes: 7 additions & 6 deletions testsuite/python-imagespec/ref/out.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,20 +74,20 @@ channelformat(1) = uint8
get_int_attribute('foo_int') retrieves 14
get_int_attribute('foo_int',21) with default retrieves 14
get_int_attribute('foo_no',23) retrieves 23
get_float_attribute('foo_float') retrieves 3.1400001049
get_float_attribute('foo_float') retrieves 3.140000104904175
get_float_attribute('foo_float_no') retrieves 0.0
get_float_attribute('foo_float_no',2.7) retrieves 2.70000004768
get_float_attribute('foo_float_no',2.7) retrieves 2.700000047683716
Comment on lines +77 to +79
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did these change?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect this is an artifact of the weirdo merge, maybe pulling in some things from other branches that you didn't intend.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmmm.... unfortunately, I made these changes four months ago, and I forgot to say why exactly in the commit :(
I believe one or more of the MacOS runners were choking on the precision differences; but it's definitely possible the tests were only failing on my local machine, and these were changes I hadn't intended to commit...

Should I revert these changes and see if stuff breaks?

get_string_attribute('foo_str') retrieves blah
get_string_attribute('foo_str_no') retrieves
get_string_attribute('foo_str_no','xx') retrieves xx

getattribute('foo_int') retrieves 14
getattribute('foo_float') retrieves 3.1400001049
getattribute('foo_float') retrieves 3.140000104904175
getattribute('foo_str') retrieves blah
getattribute('foo_vector') retrieves (1.0, 0.0, 11.0)
getattribute('foo_matrix') retrieves (1.0, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 2.0, 3.0, 1.0)
getattribute('foo_no') retrieves None
getattribute('smpte:TimeCode') retrieves (18356486L, 4294967295L)
getattribute('smpte:TimeCode') retrieves (18356486, 4294967295)
getattribute('ucarr') retrieves [49 50 51 0 0 97 98 99 1 88]
getattribute('unknown') retrieves None
s.get('foo_int') = 14
Expand All @@ -107,13 +107,13 @@ extra_attribs size is 10
"blah"
1 foo_int int 14
14
2 foo_float float 3.1400001049
2 foo_float float 3.140000104904175
3.14
3 foo_vector vector (1.0, 0.0, 11.0)
1, 0, 11
4 foo_matrix matrix (1.0, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 2.0, 3.0, 1.0)
1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 1, 2, 3, 1
5 smpte:TimeCode timecode (18356486L, 4294967295L)
5 smpte:TimeCode timecode (18356486, 4294967295)
01:18:19:06
6 ucarr uint8[10] [49 50 51 0 0 97 98 99 1 88]
49, 50, 51, 0, 0, 97, 98, 99, 1, 88
Expand Down Expand Up @@ -206,6 +206,7 @@ Testing set_colorspace:
after set_colorspace('sRGB'): sRGB
after set_colorspace(''):


Testing global attribute store/retrieve:
get_string_attribute plugin_searchpath : perfect
get_int_attribute plugin_searchpath : 0
Expand Down
Loading