Repository navigation
Conversation
Nitpick, but this overall idea didn't come from me, it was part of your existing plans? For my use cases I was actually fine only supporting known and specified interop IDs, but I can see this being useful. |
| } | ||
| if (cs && interop_id.empty() && OIIO::pvt::colorconfig_analyze_transforms) { | ||
| // No known ID, so derive what the space's transforms establish. | ||
| result = derived(*cs); |
There was a problem hiding this comment.
When the colorspace has an interop_id: specified in the config, no chromaticities or gamma is derived. If it's an interop ID that is not known to OIIO (like some custom namespaced interop ID), I think the intent is to derive it?
There was a problem hiding this comment.
Right, fixed in e25bd19. A declared ID that OpenImageIO's table doesn't know now gets its primaries from OpenImageIO's built-in definition of it, if it has one (ocio:adx10_apd_scene, say), and otherwise from the space's own transforms, as a custom acme: ID does. The ID only ever gives the primaries; whether the space is linear comes from its own encoding or transforms.
| namespace { | ||
|
|
||
| using Mat3 = std::array<double, 9>; // Row-major, applied to column vectors | ||
| using Vec3 = std::array<double, 3>; |
There was a problem hiding this comment.
Imath is already a required OIIO dependency, any reason its types are not used instead of this?
There was a problem hiding this comment.
Good point, done in e25bd19: it uses Imath's M33d and V3d now.
| decode = config.getProcessor(cs.getName(), role); | ||
| // No approximate powers and no fused operations, so a power curve | ||
| // reads back as its exponent. | ||
| cpu = decode->getOptimizedCPUProcessor(OCIO::OPTIMIZATION_NONE); |
There was a problem hiding this comment.
Calling derive for every colorspace can be very slow on large OCIO config, as it needs to e.g. potentially load many large LUTs.
For example computing the chromaticities for an unknown colorspace when writing out a JXL file should be OK, as it's just one colorspace. But on file read, we probably wouldn't want to loop over all colorspaces to find the one that corresponds to given chromaticities.
We already discussed this before, and this PR doesn't do any of that so it's not an immediate concern. Just noting that if that is considered in the future, this could be optimized to e.g. skip any transform involving LUTs.
There was a problem hiding this comment.
Agreed. Readers never call this, and it only analyzes the one color space asked about, once per config. I also switched to OpenColorIO's lossless optimization, which reads the same matrices without composing LUTs: the first pass over a 52-space studio config now takes 7.6 ms, against 3.8 ms without the analysis.
When matching a file's chromaticities against a config comes up, I'd only consider color spaces whose transforms are simple: no 3D LUTs, CDLs, looks or views, and no file LUTs other than 1D or matrix ones. That is the same rule OpenColorIO's own IdentifyBuiltinColorSpace() uses. I'd also cache the results per config.
ColorConfig::get_chromaticities() and get_transfer_function_gamma() now analyze the transforms of a config color space with no known Color Interop ID. If its conversion to the config's scene or display interchange role is one per-channel curve shared by R, G and B followed by a matrix, they report the matrix's primaries and white, named as one of the published sets in the ColorInteropID table (reached with no chromatic adaptation, Bradford or CAT02), and a pure-power or linear transfer function, as an exponent the transforms state. Anything else, such as a 3D LUT, establishes nothing. A declared linear encoding still reports gamma 1.0. Each color space is analyzed once per ColorConfig. The new global attribute "colorconfig:analyze_transforms" (default 1) turns the analysis off, so the queries answer only from what the config declares and the names OpenImageIO knows. The analysis lives in its own file, color_derive.cpp. Assisted-by: Claude Code / Claude Opus 5.5 Signed-off-by: Zach Lewis <zachcanbereached@gmail.com>
Rework how a color space's primaries are derived: - They come from the matrices that lead the conversion from the interchange space, read from OpenColorIO's optimized processor: clamps are skipped and the first curve ends them. Sampling the decoder instead failed for ACEScc, whose decoder clamps negatives after its matrix. - The matrices are named as a published gamut when they match one under Bradford, CAT02 or no chromatic adaptation, within 1e-5. Otherwise the primaries are read under the usual white points (D65, D60, DCI, D50) and adaptations, keeping the reading with the most exact coordinates, so gamuts OpenImageIO doesn't list are reported too. - The ColorInteropID table gains OpenColorIO's camera IDs (ARRI, Blackmagic, DaVinci, Canon, DJI, Panasonic, RED, Sony, Apple) with their published primaries. P3-D60, P3-DCI and Rec.601 are recognized as well. - A color space whose transforms aren't simple, but end in another color space, answers as that one. - An ID gives the primaries even when the encoding contradicts its transfer function. An ID the table lacks takes them from OpenImageIO's built-in definition of it, and one it doesn't define either (a custom namespaced ID, say) has them derived. - The transfer function is the space's own. A declared encoding is linear only for the space's own reference type; without one, a space the table doesn't describe is linear if OpenColorIO's isColorSpaceLinear() says so. A pure-power gamma is no longer derived; that waits for matching transfer functions against known curves. - Imath types replace the hand-written matrices. Assisted-by: Claude Code / Claude Opus 5.5 Signed-off-by: Zach Lewis <zachcanbereached@gmail.com>
54a83a2 to
e25bd19
Compare
|
You're right, sorry, I misattributed that: deriving properties was already in my plans, and on #5521 you suggested how to describe it. I've corrected the description. I've also reworked the derivation since your review; the last paragraph of the description lists what changed. |
|
Thanks, overall this now works the way I would expect. I'm not immediately sure what the right way of doing this is. But my local AI review is pointing out a bunch of plausible issues, and I won't have time to properly review again this week. So pasting it here in case it's helpful. AI reviewMEDIUM: a space whose chain starts with a curve gets the reference gamut's primaries
MEDIUM: fallback to the trailing ColorSpaceTransform only looks at
|
|
Thanks, Brecht! I'm on vacation until mid-next week, so I won't get a chance to address some of the feedback your robot generated, but at first glance, there are some concerning bits I need to address... thanks for checking! |
ColorConfig::get_chromaticities()andget_transfer_function_gamma()(#5521,#5557) report the primaries and transfer function of a color space with a known
Color Interop ID. This derives the primaries of a color space without one, and
whether it is linear, by analyzing the transforms in the OpenColorIO config.
Primaries
For a color space whose ID OpenImageIO has no primaries for:
role for the space's reference type (
aces_interchangeorcie_xyz_d65_interchange), from OpenColorIO's optimized processor: clampsare skipped and the first curve ends them. ACEScc, whose decoder clamps
negative values after its matrix, reads as AP1.
chromatic adaptation, within 1e-5, it reports that gamut's primaries and
white. A D65 space authored against ACES2065-1 through an adapted matrix
reports P3-D65, not adapted coordinates and a D60 white.
DCI, D50) and adaptations, and keeps the reading with the most exact
coordinates.
in another color space, answers as that one.
The
ColorInteropIDtable gains OpenColorIO's camera IDs (ARRI, Blackmagic,DaVinci, Canon, DJI, Panasonic, RED, Sony and Apple) with the primaries their
makers publish. With the table's other gamuts, plus P3-D60, P3-DCI and
Rec.601, those are the gamuts named above, so RED Wide Gamut reports RED's
published primaries and D65 white.
An ID gives the primaries: a space declaring
ocio:lin_awg3_scenereports ARRIWide Gamut 3 whatever its transforms do. An ID the table lacks takes them from
OpenImageIO's built-in definition of it (#5547), and one neither knows, such
as a custom namespaced ID, has them derived.
Transfer function
The transfer function is the color space's own, never its ID's. A declared
encoding is linear only for the space's own reference type, so a display space
tagged
scene-linearis not linear. Without one, a space the table doesn'tdescribe is linear if OpenColorIO's
isColorSpaceLinear()says so. A gammaother than 1.0 still comes only from a known ID; matching a space's transfer
function against known curves is for a later PR.
Cost
Each color space is analyzed once per
ColorConfig, the first time it isqueried; image readers never analyze anything. Over the 52 spaces of OCIO's
studio-config-v2.1.0, which declares no IDs, querying both properties ofevery space takes 7.6 ms the first time instead of 3.8 ms, and 2.2 ms after
that, as before (median of 15 processes).
Turning it off
A new global attribute,
colorconfig:analyze_transforms(default 1), turns theanalysis off:
get_chromaticities()andget_transfer_function_gamma()thenanswer only from what the config declares and the names OpenImageIO knows.
Recognizing IDs by their transforms, a later PR from #5518, will follow the
same attribute.
This is new public API, so I'd value your view on its form. A global attribute
matches
imageinput:strict, needs no new ABI, and can be set without codechanges through
OPENIMAGEIO_OPTIONS=colorconfig:analyze_transforms=0. Aper-
ColorConfigoption would also work. Are the attribute and its name right?Changed behavior
get_chromaticities()can report primaries for a config space with no knownID, or with an ID OpenImageIO has no primaries for, and
get_transfer_function_gamma()reports 1.0 for such a space when it islinear.
transfer function.
scene-linear, or a scene space declaringdisplay-linear, is no longer reported as linear.get_color_interop_id()answers OpenColorIO's camera IDs for a space namedor aliased by one.
colorconfig:analyze_transforms.On OCIO's built-in studio configs, every color space but data and the display
CIE XYZ space now reports primaries: 51 of the 52 in
studio-config-v2.1.0,where 23 did before. Every camera space reports its maker's published
primaries.
Validation
unit_colorchecks eighteen spaces written with OCIO's built-in transforms:AP0, AP1 linear, with a 2.2 power and as ACEScct and ACEScc; P3-D65 through
Bradford adaptation; RED Log3G10; primaries no published gamut has; a CDL
ahead of a ColorSpaceTransform; a curve after the matrix; a 3D LUT; a
declared linear encoding; display CIE XYZ, Rec.1886, Display P3, P3-D60 and
P3-DCI; and a display space tagged
scene-linear. With the attribute at 0nothing is derived. With OCIO 2.5, a declared camera ID, an ID taken from
OpenImageIO's built-in definition, and an unknown namespaced ID.
2.3.0:
unit_color,python-colorconfig,python-imageinput-colorconfigand
oiiotool-colorpass. gcc 11 compilescolor_ocio.cppandcolor_derive.cppas one unit, as the CI unity build does, with-Werror.Since the first version of this PR, the primaries are read from the matrices
instead of sampling the decoder, which failed for ACEScc; gamuts the table
didn't list are reported; camera gamuts joined the table; Imath types replace
the hand-written matrices; and an ID OpenImageIO doesn't know no longer stops
the analysis, as Brecht pointed out.
Split out of #5518.
Assisted-by: Claude Code / Claude Opus 5.5