Skip to content

Conversation

@brechtvl
Copy link
Contributor

@brechtvl brechtvl commented Nov 27, 2025

Description

When reading image files with CICP metadata, automatically set the corresponding "oiio:ColorSpace". When writing files that support CICP and no other colorspace metadata can represent "oiio:ColorSpace", automatically write CICP metadata.

Setting "oiio:ColorSpace" on read prefers scene referred over display referred color spaces, changing existing behavior as little as possible. The alternative would have been to interpret the presence of CICP metadata as an indication that the image is likely display referred, which might be reasonable too. Either way it's a guess.

There is no automatic mapping from g22_rec709_display to CICP currently to keep behavior unchanged, as this is often not what you want and further discussion is needed to decided on the right behavior.

Also add new ColorConfig get_cicp and get_color_interop_id API functions to share logic between file formats.

Tests

Tests were updated, the auto detected color space name appears in the output.

Commands like these should also do the right thing automatically.

oiiotool in.exr --ociodisplay "Rec.2100-PQ - Display" "ACES 2.0 - HDR 1000 nits (P3 D65)" -o out.avif
oiiotool out.avif --autocc -o out.exr

Checklist:

  • I have read the contribution guidelines.
  • I have updated the documentation, if applicable. (Check if there is no
    need to update the documentation, for example if this is a bug fix that
    doesn't change the API.)
  • I have ensured that the change is tested somewhere in the testsuite
    (adding new test cases if necessary).
  • If I added or modified a C++ API call, I have also amended the
    corresponding Python bindings (and if altering ImageBufAlgo functions, also
    exposed the new functionality as oiiotool options).
  • My code follows the prevailing code style of this project. If I haven't
    already run clang-format before submitting, I definitely will look at the CI
    test that runs clang-format and fix anything that it highlights as being
    nonconforming.

When reading image files with CICP metadata, automatically set the
corresponding "oiio:ColorSpace". When writing files that support CICP and no
other colorspace metadata can represent "oiio:ColorSpace", automatically write
CICP metadata.

Setting "oiio:ColorSpace" on read prefers scene referred over display referred
color spaces, changing existing behavior as little as possible. The alternative
would have been to interpret the presence of CICP metadata as an indication
that the image is likely display referred, which might be reasonable too.

Also add new ColorConfig set_colorspace_cicp and get_colorspace_cicp API
functions to share logic between file formats.

Signed-off-by: Brecht Van Lommel <[email protected]>
@brechtvl brechtvl marked this pull request as draft November 27, 2025 12:49
@brechtvl
Copy link
Contributor Author

brechtvl commented Nov 27, 2025

Still a draft because:

  • The display interop ID recommendations are not finalized yet.
  • Python API and docs have not been updated yet.

Relevant discussion: #4787

Signed-off-by: Brecht Van Lommel <[email protected]>
@zachlewis
Copy link
Collaborator

Oh hells yes! Brecht, this is fantastic! I've been slowly working on my own p.o.c. (in python) for managing the relationship between CICP <--> colorInteropID <--> OCIO, and what you've put together aligns pretty closely with what I've been tinkering with. I really appreciate the initiative, this is exactly the direction we should be heading towards!

I have a few suggestions + ideas, for your consideration:

  • Convert CICP values to and from an intermediate string colorInteropID attribute, instead of directly embedding a CIF interop ID token as a oiio:ColorSpace attribute.
    • I think we should have a layer of abstraction between external non-OCIO color metadata attributes as wrangled by format readers and writers, and OIIO's internal OCIO-config-specific accounting of color and image state
    • oiio:ColorSpace is an internal (non-serialized) attribute whose value always refers to a specific OCIO config that OIIO knows about.
    • colorInteropID is an external (serializable) attribute whose value reliably informs and reflects other representations of the data's image state / color encoding. It functions sort of like a proxy, or a bridge between different color-managed frameworks.
  • I think we should have dedicated functions for reconciling, interpreting, and translating between "external" non-OCIO-specific metadata constructs (e.g., CICP, oiio:InputFilePath, acesImageContainerFlag, MDCV, etc...) and OCIO-config-specific metadata (primarily, oiio:ColorSpace), with colorInteropID serving as a means for translating between them, where appropriate.
    • This would allow us to defer the colorInteropID <--> oiio:ColorSpace resolution as late as possible; while still allowing users to make meaningful edits to incoming or outgoing CICP (and MDCV) metadata.
    • It would also provide natural junctures for updating / synchronizing / managing the relationship between the internal accounting of the image state, and the external metadata we persist / blast / adjust).
      • This makes room for us to add more nuanced CICP (and MDCV) read/write behavior in the future without API/ABI-breaking changes
        • E.g., maybe users want all CICP-supporting format readers to immediately set the colorInteropID attribute for CICP data signalling HDR encodings, but would otherwise like to exhaust OCIO file rules first for SDR encodings, or otherwise manually override individual CICP values before applying a color-aware IBA. I'm thinking of users trying to troubleshoot format / encoding / application specific CICP behavior experienced outside of OIIO.

Damn, I have more to add, of course, but I've got a bus to catch!

@brechtvl
Copy link
Contributor Author

brechtvl commented Nov 27, 2025

The existing OIIO 3.1 implementation for OpenEXR and PNG is already setting oiio:ColorSpace immediately, and it will also set oiio:ColorSpace to interop IDs even if they are unknown to the current OCIO config.

My inclination would be to ensure colorInteropID, acesImageContainerFlag, CICP and similar are all in the ImageSpec for full control, but still immediately set oiio:ColorSpace to a good default guess based on them.

To me deferring seems like it could be confusing in oiiotool and make the API harder to use for simple cases. If all the relevant attributes are preserved, there could still be a way to re-guess oiio:ColorSpace after editing those attributes, for more advanced use cases.

Though in either the immediate or deferred case, it's not clear to me when to drop colorspace related attributes that make no sense to write into another file (contradictory or not).

@lgritz
Copy link
Collaborator

lgritz commented Nov 27, 2025

This is looking great to me!

I agree with what Zach wrote about where we eventually want to get to. But I think what you have here is still a positive incremental step that we should integrate when the two of you think it's ready.

@zachlewis
Copy link
Collaborator

zachlewis commented Nov 27, 2025

To be honest, I don't really have a problem with setting oiio:ColorSpace as early as possible; I just wonder if it's necessary, and if doing so imposes complexity of trying to keep CICP and oiio:ColorSpace attributes in sync. Either way, we need a mechanism for divining valid oiio:ColorSpace values just in time for color-aware IBAs that are otherwise missing and needing the attribute.

My thinking is, if a user isn't using a color-aware IBA, it's probably safe to persist existing CICP primaries and trc data for as long as possible. But as soon as a color-aware IBA is encountered, incoming CICP data is potentially invalidated, so it may as well be removed. This would permit users to manually edit CICP metadata (in whole or in part) either before applying color-aware IBAs or just before writing to supported formats, allowing folks to pretty effortlessly preview the effects of modifying existing CICP data without having to worry about whether modifications to CICP metadata reflect the current state of the oiio:ColorSpace attribute, or vice versa.

Conversely, once we're working with color-aware IBAs, we only care about keeping the value of oiio:ColorSpace up to date. If we blast away the CICP attribute every time we set oiio:ColorSpace, then when it comes time to setting CICP metadata for format writers, there's no ambiguity in what to do:

  • If CICP is set, it means either the buffer never encountered any IBAs that would affect color, or it means the user has manually set or edited the value intentionally -- either way, use as-is.
  • If CICP is unset and oiio:ColorSpace is set, use either the corresponding color space's interop_id attribute to inform the primaries and transfer function CICP values; or otherwise try to infer CICP values by attempting to match oiio:ColorSpace's definition to one of the canonical CIF interop_ids that unambiguously map to CICP pri + trc values.
    (Crucially, this would allow config authors to control, for example, whether a Gamma 2.2-based Display/View transform elicits an HEIF tagged as 1-13-1)

Likewise, if we defer the automatic derivation of "oiio:ColorSpace" to the last possible moment, that means that if the value happens to be set, either the user has set it manually, OR it was set by an upstream IBA applied by the user. If the user is only interested in setting or modifying CICP data, I don't think we should create conditions that would potentially compromise the validity of an oiio:ColorSpace attribute initially set on read.

On the other hand, we certainly could endeavor to always update oiio:ColorSpace whenever CICP is updated. But given that color-aware IBAs might specify custom configs, I think it makes more sense to couple CICP mutations with corresponding changes to the config-agnostic "colorInteropID" attribute, interpreting the value as necessary for the given IBA / OCIO config.

As for the behavior of the colorInteropID attribute itself, I think it should disappear and reappear as necessary, just like the CICP does, as described above.

I'm not totally against allowing users to use CIF strings directly as oiio:ColorSpace values -- I think it would be a nice convenience, personally -- but I think it might be a slippery slope into compounded complexity down the road, when we want to use the colorInteropID to convey "namespaced" color spaces, which might require searching for a config, and then searching for a color space in that config, and then either finding an equivalent color space in the active config, or otherwise creating an ad-hoc transform / Processor into the current config.

In other words, given the limited scope and use of the "unnamespaced" / reserved set of CIF strings, I think treating them as if they were known OCIO color spaces is more or less fine; but given the other applications of the colorInteropID attribute, we do need to treat it as a first-class citizen.

Does this make sense?

@zachlewis
Copy link
Collaborator

This is looking great to me!

I agree with what Zach wrote about where we eventually want to get to. But I think what you have here is still a positive incremental step that we should integrate when the two of you think it's ready.

Also, yeah, to echo what Larry says, I have absolutely no problem integrating what you're currently doing in this PR as is, since I very much agree that it's on the right track!

@lgritz
Copy link
Collaborator

lgritz commented Nov 27, 2025

I was thinking that the reader is primarily responsible for setting the attribs that directly come from the file (like "CICP" from these files, or "colorInteropID" for exr, etc.). Optionally, it can also set "oiio:ColorSpace" if it knows for sure what it should be set to, like if the CICP exactly corresponds to a standard interop ID value that will be available in any default config, but this is not necessarily required.

Then, we want a global deduce_color_space() (or name TBD) that takes a spec and config and is tasked with setting "oiio:ColorSpace" (if not already set, or perhaps unconditionally if a flag parameter says so) based on... the full set of what's in the spec -- colorInteropID, CICD, filename, any other hints. This utility can embody the centralized logic that expresses our best ideas for how to adjudicate the possibly under- or over-specified or contradictory information that might be in the spec.

The few functions that really need to know what color space the data is in can call deduce_color_space() before doing any critical color operations.

I think that if we can figure out that there is an interopid that corresponds to whatever this image is, then oiio:ColorSpace should always be that. But I don't think it necessarily needs to be restricted to interop id names in cases where something a file supports isn't expressible as an OCIO color space name. In those cases, of course, we wouldn't expect an OCIO-based color transform to be able to deal with it, but we (OIIO) may internally handle other cases.

I do agree that oiio:ColorSpace is non-serialized -- it's never written or read from any file, it's just an internal marker for our best inference (based on what was in the file or other info) about what the data is.

@brechtvl
Copy link
Contributor Author

I changed the public API functions to be getColorInteropID(cicp) and getCICP(colorspace). This way they don't include logic about how to resolve colorspaces from attributes, since it looks like that's going to change in the future. At the cost of a small amount of duplicated code for each file format.

Also added Python bindings for those.

Signed-off-by: Brecht Van Lommel <[email protected]>
@brechtvl
Copy link
Contributor Author

I realized the ACES configs were already released with display interop IDs. So I guess if the recommendation is final enough for those, then it should be ok for OpenImageIO too.

@zachlewis
Copy link
Collaborator

Yes, the display interop IDs are good to go.

We'll also want to support OCIO's internal color space interop IDs as well -- i.e., all the color spaces in ocio://studio-config-latest with interop_id values prefixed with the "ocio:" namespace... something like ~60-65 spaces in total, I think.

@zachlewis
Copy link
Collaborator

Oh, I just realized this PR is limited to CICP <--> ColorSpace stuff. We don't have to worry about the "ocio:" prefixed namespaces here, necessarily, but the mechanism for handling the CIF tokens and "ocio:"-prefixed namespaces is pretty much the same.

I'll write about it in another thread.

@lgritz
Copy link
Collaborator

lgritz commented Nov 29, 2025

Needs the stubs file to be updated (you can download it as an artifact from the failed wheel job).

Signed-off-by: Larry Gritz <[email protected]>
@lgritz
Copy link
Collaborator

lgritz commented Nov 29, 2025

I took the liberty of pushing a fix with the missing stub file changes.

Unfortunately, something unrelated has happened to the sonar action and lots of jobs are failing. I'm inclined to just wait an hour an poke it again, some kind of temporary outate.

@brechtvl
Copy link
Contributor Author

It may be good to wait a bit longer to merge this given the g22_rec709_display discussion in #4787, and other missing display color space interop ID support. Probably we'd want to make sure all those changes land in the same release.

@lgritz
Copy link
Collaborator

lgritz commented Nov 30, 2025

OK, whatever you guys decide here is fine with me. The last several rounds of this discussion has been on esoteric points that are totally over my head. I trust you to make the right decisions, just let me know when you both agree that what we have here is ready to merge.

I've already got the Dec 1's scheduled 3.0 and 3.1 releases staged and ready to go, none of this is going to make that deadline, so whatever we merge here is going to get tried out in main for a few weeks before being backported to become a tagged release, so I think the stakes are low even if you merge something that you decide to revise reasonably soon after.

@lgritz
Copy link
Collaborator

lgritz commented Nov 30, 2025

BTW, @brechtvl, the comments in the C++ header form the documentation for the C++ API, but you also added python bindings which still need to be mentioned briefly in pythonbindings.rst

This is unlikely meant to be written with Gamma 2.2 color metadata,
so keep behavior unchanged until there is decision on what to do.

Signed-off-by: Brecht Van Lommel <[email protected]>
The ColorConfig class was not documented yet, this only documents
the new methods.

Signed-off-by: Brecht Van Lommel <[email protected]>
@brechtvl
Copy link
Contributor Author

brechtvl commented Dec 1, 2025

Some more changes:

  • Take into account the OCIO 2.5 config interop_id attributes for mapping colorspace to CICP on write.
  • Add get_color_interop_id(colorspace) and more complete color interop ID table, needed for the above.
  • Disable writing CICP for g22_rec709_display for now, keeping behavior of that case unchanged. It is unlikely to be what you want for a Gamma 2.2 display, and further discussion is needed to decide on the right behavior.
  • The ColorConfig class was not documented in pythonbindings.rst yet. I added documentation just for the new methods and left a TODO comment about this being incomplete.

Signed-off-by: Brecht Van Lommel <[email protected]>
@zachlewis
Copy link
Collaborator

I've already got the Dec 1's scheduled 3.0 and 3.1 releases staged and ready to go, none of this is going to make that deadline, so whatever we merge here is going to get tried out in main for a few weeks before being backported to become a tagged release, so I think the stakes are low even if you merge something that you decide to revise reasonably soon after.

I think, in general, throughout the 3.1 release cycle, we should caveat these color management features as "experimental" and subject to change. I think there's a good chance we won't be able to effectively solicit enough meaningful feedback in some areas without a concrete implementation for folks to mess around with; and I know the OpenColorIO TSC is waiting to see what we end up doing here with stuff like CICP before providing more "official" guidance. Also, I expect that we'll make incremental improvements and incorporate additional features along the way that are outside the scope of what we're immediately trying to achieve. So, it would be cool if we could "reserve the right" to adjust behavior between OIIO patch versions. This would also serve to keep the relevant PRs less painful to review and more pleasurable to merge.

@zachlewis
Copy link
Collaborator

Also, I feel it's important to note that what we're currently doing with writing interop_id strings directly to oiio:ColorSpace isn't really a viable solution (or, at least, not without intervention elsewhere). Doing so presupposes that the ColorConfig used with any given IBA already contains ColorSpace with a name or alias that matches that string, and that assumption really only holds when using one of the newer (OCIO-2.5) builtin OCIO configs.

Given what I wrote above, given the scope of this PR, and given what we're doing elsewhere, I have no problem with continuing to populate oiio:ColorSpace with CIF token values for now, if that helps move things forward; but this would mean backtracking a little bit in a future PR, where we more appropriately handle the semantics of config-specific ColorSpaces and config-agnostic interop_ids.

As part of that effort, we're also going to need to vastly improve our ColorConfig / ColorSpace equivalency testing. We've got a handful of clever tricks going on in our current ColorConfig.equivalent method, but we need something far more robust, something that actually tests for definitional equivalence within and across ColorConfigs; and we need a mechanism for handling cases where the ColorSpace we're referencing doesn't actually exist in the ColorConfig we're using for an IBA.

Fortunately, the OCIO API offers some methods to make this fairly trivial. I'll write about these mechanisms in the color-stuff-discussion-thread.

@lgritz
Copy link
Collaborator

lgritz commented Dec 1, 2025

I think, in general, throughout the 3.1 release cycle, we should caveat these color management features as "experimental" and subject to change.

That sounds reasonable.

Though I think that when we're talking about behavior, and not API/ABI compatibility that will cause downstream software to not build correctly, we're allowed to "fix bugs" even if it changes behavior by a reasonable amount, which gives us some leeway naturally to say "this behavior might be changing, but it was wrong before, and we're improving it" at a patch release.

Nevertheless, if you think this area is likely to have repeated churn and changes in direction (not just monotonically increasing the set of color scenarios we recognize and handle), then we can add a line to future release notes saying that we're expecting extra change.

@lgritz
Copy link
Collaborator

lgritz commented Dec 1, 2025

Also, I feel it's important to note that what we're currently doing with writing interop_id strings directly to oiio:ColorSpace isn't really a viable solution (or, at least, not without intervention elsewhere). Doing so presupposes that the ColorConfig used with any given IBA already contains ColorSpace with a name or alias that matches that string, and that assumption really only holds when using one of the newer (OCIO-2.5) builtin OCIO configs.

Yeah, but in some sense... so what? Isn't the worst that happens that you might ask it to do a color transformation that depends on OCIO, and it's an error because your config doesn't contain the color space that the image says it is? Seems like that caveat has been in effect forever.

We do have the option, I suppose, of requiring OCIO 2.5 minimum and rely on the auto-build to make a baked-in static OCIO if that's newer than what the user has installed?

Given what I wrote above, given the scope of this PR, and given what we're doing elsewhere, I have no problem with continuing to populate oiio:ColorSpace with CIF token values for now, if that helps move things forward; but this would mean backtracking a little bit in a future PR, where we more appropriately handle the semantics of config-specific ColorSpaces and config-agnostic interop_ids.

I'm fine with that.

@zachlewis
Copy link
Collaborator

zachlewis commented Dec 1, 2025 via email

@lgritz
Copy link
Collaborator

lgritz commented Dec 1, 2025

There are several other PRs in flight now that are drafts because they depend on this being merged first. So I want to put a premium on expeditiously getting this first one acceptable to merge, even if we need to come back and fine tune afterwards. The 3.1 patches for this month are out, so it's fine to repeatedly revise this in main until the end of the month, and even then, we can always just decide to delay backporting any of these color PRs to dev-3.1 until we are fully satisfied with its state.

@brechtvl
Copy link
Contributor Author

brechtvl commented Dec 1, 2025

I submitted two more PRs intended to be wrap up loose ends left by this PR: compatibility with older OCIO configs and handling display color space interop IDs throughout the I/O code.

The implementation choices can be debated and changed, it's just my proposal.

@zachlewis
Copy link
Collaborator

So I want to put a premium on expeditiously getting this first one acceptable to merge, even if we need to come back and fine tune afterwards.

Agreed! Wherever we land in the end, this PR provides a fantastic basis for moving forward.

constexpr ColorInteropID color_interop_ids[] = {
// Scene referred interop IDs first so they are the default in automatic
// conversion from CICP to interop ID. Some are not display color spaces
// at all, but can be represented by CICP anyway.
Copy link
Contributor

Choose a reason for hiding this comment

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

Since CICP is primarily used in video files, I think it would be more appropriate to prefer the display-referred mapping. The one exception might be for CICPTransfer::Linear, but what file formats would contain that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, as mentioned in the description I think that's reasonable too.

There are some potentially negative consequences:

  • OIIO currently reads sRGB images as srgb_rec709_scene. If the presence of CICP would change that to srgb_rec709_display, some applications integrating OIIO would need to be updated (again) to handle both cases.
  • If you use srgb_p3d65_scene for textures, then it would help to write CICP to make them open and display correctly in other software. But then opening them again in OIIO they would be srgb_p3d65_display.
  • Other software for painting and editing textures may write CICP (maybe more so in the future if AVIF, JPEG XL gain more adoption).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Welp, you both make very good points.

I've given this a lot of thought, and I've come up with a potential way forward that I believe addresses Doug's concerns about preferring to interpret CICP values as display-referred color spaces, as well as Brecht's concerns about reading and writing metadata that OIIO can interpret reliably.

Upshot: by default, interpret CICP as display-referred; and leverage other color metadata to convey scene-referred encodings.

#4787 (comment)

This said, I think, if you guys are okay with it, for the sake of the other dependent PRs, we should leave this behavior as-is for the immediate future, and continue with implementing a more robust strategy in a forthcoming PR, after the rest of Brecht's related work has made its way in safely.

Copy link
Collaborator

Choose a reason for hiding this comment

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

we should leave this behavior as-is for the immediate future, and continue with implementing a more robust strategy in a forthcoming PR

Fine by me. We have two gates: one on individual PRs, but another on when we backport a batch of PRs to 3.1. It's ok to noodle with the specifics from PR to PR as long as we have things reasonably settled down for the backporting steps.

@brechtvl
Copy link
Contributor Author

brechtvl commented Dec 1, 2025

There are several other PRs in flight now that are drafts because they depend on this being merged first. So I want to put a premium on expeditiously getting this first one acceptable to merge, even if we need to come back and fine tune afterwards.

For this PR, the tricky decisions are how to write g22_rec709_display and how to guess scene referred or display referred from CICP. In both cases I've left the existing behavior unchanged (to the extent possible), to avoid approval being blocked by a decision for those.

@zachlewis
Copy link
Collaborator

zachlewis commented Dec 2, 2025 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants