Skip to content

Commit b0848db

Browse files
committed
feat: Add ImageSpec::set_cicp functions and oiiotool --cicp option
The ImageSpec::set_cicp functions are convenience functions for setting the uint8[4] oiio:CICP attribute. The oiiotool `action_cicp` function works similarly to `action_iscolorspace`, in that it uses a custom OiiotoolOp to modify the top image's spec with the aforementioned set_cicp function. Upshot: oiiotool now has a `--cicp` option, which makes it easy to add, modify, or remove CICP metadata. Signed-off-by: Zach Lewis <zachcanbereached@gmail.com>
1 parent 8d84f27 commit b0848db

6 files changed

Lines changed: 182 additions & 5 deletions

File tree

src/doc/pythonbindings.rst

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,43 @@ Section :ref:`sec-ImageSpec`, is replicated for Python.
717717
data types, but not the arbitrary named metadata or channel names.
718718

719719

720+
.. py:method:: ImageSpec.set_cicp (pri: int, trc: int, mtx: int = 0, vfr: int = 1)
721+
ImageSpec.set_cicp (cicp: str)
722+
723+
Set CICP metadata for image formats that support it. The overloaded
724+
function accepts either a series of integers corresponding to the
725+
H.273 enumerations for primaries, transfer characteristic, color
726+
matrix, and video full range flag; or it'll accept a single string
727+
argument of up to 4 comma-separated values.
728+
729+
The third and fourth CICP values are optional, and are typically
730+
more pertinant to video formats. The default values assume OIIO
731+
buffers hold unscaled (i.e., full-range) RGB-encoded data (as
732+
opposed to Y'CbCr or ICtCp).
733+
734+
Examples:
735+
736+
.. code-block:: python
737+
738+
spec = ImageSpec(...)
739+
spec.set_cicp(1, 13, 1) # [1, 13, 1, 1]
740+
741+
# If only provided primaries and transfer characteristics,
742+
# the color matrix is "identity" (0)
743+
spec.set_cicp(1, 13) # [1, 13, 0, 1]
744+
745+
# Passing an empty string will erase the attribute.
746+
spec.set_cicp("")
747+
748+
# By default, OIIO assumes "full-range" scaling
749+
spec.set_cicp("9,16,9") # [9, 16, 9, 1]
750+
751+
# Use the following syntax to modify only certain values:
752+
spec.set_cicp(",,,0") # [9, 16, 9, 0]
753+
754+
This function was added in version 3.0.7.
755+
756+
720757
.. py:method:: bool ImageSpec.set_colorspace (name)
721758
722759
Set metadata to indicate the presumed color space `name`, or clear all

src/include/OpenImageIO/imageio.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,40 @@ class OIIO_API ImageSpec {
811811
deep = other.deep;
812812
}
813813

814+
/// Set the "oiio:CICP" attribute to the given values. The `pri`, `tc`, `mtx`,
815+
/// and `vfr` values are the CICP parameters, which are used to describe
816+
/// aspects of the color encoding of the image, as defined in ITU-T H.273.
817+
///
818+
/// @param pri
819+
/// An integer representing the color primaries.
820+
/// @param tc
821+
/// An integer representing the transfer characteristics.
822+
/// @param mtx
823+
/// An integer representing the matrix coefficients. RGB encodings
824+
/// typically use 0 (identity matrix).
825+
/// @param vfr
826+
/// An integer indicating whether the image is "full range" (Typically
827+
/// true of RGB encodings)
828+
///
829+
/// @version 3.0
830+
void set_cicp(int pri, int trc, int mtx=0, int vfr=1);
831+
/// Set the "oiio:CICP" attribute to the given values. The `cicp` string
832+
/// is a comma-delimeted list of integers representing the CICP parameters.
833+
/// The `cicp` string should be in the format "pri,tc,mtx,vfr", where
834+
/// `pri`, `trc`, `mtx`, and `vfr` are integers representing the color
835+
/// primaries, transfer characteristics, matrix coefficients, and
836+
/// video full range flag, respectively. If the `cicp` string is empty,
837+
/// the "oiio:CICP" attribute will be removed from the `ImageSpec`.
838+
/// The `cicp` string may also contain commas (",") without any
839+
/// integer value specified beforehand, in which case the
840+
/// corresponding CICP parameter will fall back to its current value,
841+
/// or to the default value (of {0, 0, 0, 1}) if there is no current value.
842+
/// For example, `",,,0"` would set the "video full range flag" to 0 to
843+
/// indicate a "narrow range" encoding, while keeping the other three
844+
/// parameters unchanged.
845+
/// @param cicp
846+
void set_cicp(string_view cicp);
847+
814848
/// Set the metadata to presume that color space is `name` (or to assume
815849
/// nothing about the color space if `name` is empty). The core operation
816850
/// is to set the "oiio:ColorSpace" attribute, but it also removes or
@@ -2157,6 +2191,9 @@ class OIIO_API ImageOutput {
21572191
/// Does this format allow 0x0 sized images, i.e. an image file
21582192
/// with metadata only and no pixels?
21592193
///
2194+
/// - `"cicp"` :
2195+
/// Does this format support embedding CICP metadata?
2196+
///
21602197
/// This list of queries may be extended in future releases. Since this
21612198
/// can be done simply by recognizing new query strings, and does not
21622199
/// require any new API entry points, addition of support for new
@@ -3366,6 +3403,22 @@ OIIO_API void set_colorspace(ImageSpec& spec, string_view name);
33663403
OIIO_API void set_colorspace_rec709_gamma(ImageSpec& spec, float gamma);
33673404

33683405

3406+
3407+
// /// Set the spec's metadata to reflect a custom set of CICP values.
3408+
// /// The core operation is to set the "oiio:CICP" attribute to an
3409+
// /// uint8[4] array. Currently, the CICP attribute must be
3410+
// /// manually maintained by the user; but OCIO-2.5 will offer
3411+
// /// a way for config authors to specify CICP values for a
3412+
// /// color space, and it will be possible to use this function
3413+
// /// in ImageBufAlgos that mutate "oiio:ColorSpace".
3414+
// ///
3415+
// /// @version 3.0
3416+
// OIIO_API void set_cicp(ImageSpec& spec, int transfer_characteristic,
3417+
// int color_primaries, int matrix_coefficients,
3418+
// int video_full_range_flag);
3419+
3420+
3421+
33693422
/// Are the two named color spaces equivalent, based on the default color
33703423
/// config in effect?
33713424
///

src/libOpenImageIO/formatspec.cpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ ImageSpec::find_attribute(string_view name, ParamValue& tmpparam,
470470
auto iter = extra_attribs.find(name, searchtype, casesensitive);
471471
if (iter != extra_attribs.end())
472472
return &(*iter);
473-
// Check named items in the ImageSpec structs, not in extra_attrubs
473+
// Check named items in the ImageSpec structs, not in extra_attrubs
474474
#define MATCH(n, t) \
475475
(((!casesensitive && Strutil::iequals(name, n)) \
476476
|| (casesensitive && name == n)) \
@@ -1268,6 +1268,38 @@ ImageSpec::set_colorspace(string_view colorspace)
12681268

12691269

12701270

1271+
void
1272+
ImageSpec::set_cicp(int pri, int trc, int mtx, int vfr)
1273+
{
1274+
uint8_t vals[4] = { uint8_t(std::clamp(pri, 0, 255)),
1275+
uint8_t(std::clamp(trc, 0, 255)),
1276+
uint8_t(std::clamp(mtx, 0, 255)),
1277+
uint8_t(std::clamp(vfr, 0, 255)) };
1278+
attribute("oiio:CICP", TypeDesc(TypeDesc::UINT8, 4), vals);
1279+
}
1280+
1281+
1282+
1283+
void
1284+
ImageSpec::set_cicp(string_view cicp)
1285+
{
1286+
// If the string is empty, erase the attribute.
1287+
if (cicp.empty()) {
1288+
erase_attribute("oiio:CICP");
1289+
return;
1290+
}
1291+
auto vals = Strutil::extract_from_list_string<int>("0,0,0,1", 4, 0);
1292+
auto p = find_attribute("oiio:CICP", TypeDesc(TypeDesc::UINT8, 4));
1293+
if (p) {
1294+
string_view existing_vals = metadata_val(*p);
1295+
Strutil::extract_from_list_string<int>(vals, existing_vals);
1296+
}
1297+
Strutil::extract_from_list_string<int>(vals, cicp);
1298+
set_cicp(vals[0], vals[1], vals[2], vals[3]);
1299+
}
1300+
1301+
1302+
12711303
template<>
12721304
size_t
12731305
pvt::heapsize<ImageSpec>(const ImageSpec& is)

src/oiiotool/oiiotool.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2219,6 +2219,47 @@ icc_read(Oiiotool& ot, cspan<const char*> argv)
22192219
}
22202220

22212221

2222+
// Special OiiotoolOp whose purpose is to set attributes on the top image.
2223+
class OpSetCICP final : public OiiotoolOp {
2224+
public:
2225+
OpSetCICP(Oiiotool& ot, string_view opname, cspan<const char*> argv)
2226+
: OiiotoolOp(ot, opname, argv, 1)
2227+
{
2228+
inplace(true); // This action operates in-place
2229+
cicp = args(1);
2230+
}
2231+
OpSetCICP(Oiiotool& ot, string_view opname, int argc,
2232+
const char* argv[])
2233+
: OpSetCICP(ot, opname, { argv, span_size_t(argc) })
2234+
{
2235+
}
2236+
bool setup() override
2237+
{
2238+
ir(0)->metadata_modified(true);
2239+
return true;
2240+
}
2241+
bool impl(span<ImageBuf*> img) override
2242+
{
2243+
// Because this is an in-place operation, img[0] is the same as
2244+
// img[1].
2245+
img[0]->specmod().set_cicp(cicp);
2246+
return true;
2247+
}
2248+
2249+
private:
2250+
string_view cicp;
2251+
};
2252+
2253+
2254+
// --cicp
2255+
static void
2256+
action_cicp(Oiiotool& ot, cspan<const char*> argv)
2257+
{
2258+
OpSetCICP op(ot, "cicp", argv);
2259+
op();
2260+
}
2261+
2262+
22222263

22232264
// --colorconfig
22242265
static void
@@ -7081,6 +7122,9 @@ Oiiotool::getargs(int argc, char* argv[])
70817122
ap.arg("--iccread %s:FILENAME")
70827123
.help("Add the contents of the file to the top image as its ICC profile")
70837124
.OTACTION(icc_read);
7125+
ap.arg("--cicp %s:CICP")
7126+
.help("Set CICP metadata for supporting output formats (e.g., '12,16,0,1')")
7127+
.OTACTION(action_cicp);
70847128
// clang-format on
70857129

70867130
if (ap.parse_args(argc, (const char**)argv) < 0) {

src/png.imageio/png_pvt.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -328,9 +328,9 @@ read_info(png_structp& sp, png_infop& ip, int& bit_depth, int& color_type,
328328

329329
#ifdef PNG_cICP_SUPPORTED
330330
{
331-
png_byte cp = 0, tf = 0, mc = 0, fr = 0;
332-
if (png_get_cICP(sp, ip, &cp, &tf, &mc, &fr)) {
333-
uint8_t cicp[4] = { cp, tf, mc, fr };
331+
png_byte pri = 0, trc = 0, mtx = 0, vfr = 0;
332+
if (png_get_cICP(sp, ip, &pri, &trc, &mtx, &vfr)) {
333+
uint8_t cicp[4] = { pri, trc, mtx, vfr };
334334
spec.attribute(CICP_ATTR, TypeDesc(TypeDesc::UINT8, 4), cicp);
335335
}
336336
}
@@ -715,7 +715,8 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec,
715715
const png_byte* vals = static_cast<const png_byte*>(p->data());
716716
if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp)
717717
return "Could not set PNG cICP chunk";
718-
png_set_cICP(sp, ip, vals[0], vals[1], vals[2], vals[3]);
718+
// libpng will only write the chunk if the third byte is 0
719+
png_set_cICP(sp, ip, vals[0], vals[1], (png_byte) 0, vals[3]);
719720
}
720721
#endif
721722

src/python/py_imagespec.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,16 @@ declare_imagespec(py::module& m)
278278
self.set_colorspace(cs);
279279
},
280280
"name"_a)
281+
.def(
282+
"set_cicp", [](ImageSpec& self, int pri, int trc,
283+
int mtx, int vfr) {
284+
self.set_cicp(pri, trc, mtx, vfr);
285+
},
286+
"pri"_a, "trc"_a, "mtx"_a = 0, "vfr"_a = 1)
287+
.def(
288+
"set_cicp",
289+
[](ImageSpec& self, const std::string& cicp) { self.set_cicp(cicp); },
290+
"cicp"_a)
281291
// __getitem__ is the dict-like `ImageSpec[key]` lookup
282292
.def("__getitem__",
283293
[](const ImageSpec& self, const std::string& key) {

0 commit comments

Comments
 (0)