Repository navigation
Conversation
When J is at the limit (exactly 100.0 at 100 nits, which passes the J > limit_J_max
guard), solve_J_intersect can return J_intersect a float step above limit_J_max. The
upper-hull base J_max - J_intersect_source in find_gamut_boundary_intersection is then
slightly negative, powf returns NaN, and the gamut_boundary_M <= 0 guard does not catch
it. Through the studio config the range clamp turns the NaN into 0, so some very bright
pixels render black.
Clamp the base at 0 on the CPU and in the generated shader text. The boundary M is then
0 and the existing guard returns {J, 0, h}, as the J > limit_J_max branch does.
Tests: forward and inverse gamut compression for J at the limit and the float steps
below it over all hues, and an output-transform input that reached the limit.
Signed-off-by: Kazuaki Matsumura <ezulfilm@gmail.com>
|
|
|
Hi, thank you for OpenColorIO. I hit this while comparing ACES 2.0 renders: a few very bright pixels came out black while their neighbours were white. What I checked:
If you'd prefer a different fix, I'm happy to change it: clamping the result of This is separate from #2132 (the divide by zero in |
The ACES 2.0 gamut compression returns NaN for some very bright colours.
Example:
Through the built-in studio config, the range clamp after the output transform turns the NaN into 0, so these pixels render black among white neighbours. For example, ACEScct
[1.02502234, 0.90455188, 0.68137096]gives (0, 0, 0) on "sRGB - Display" / "ACES 2.0 - SDR 100 nits (Rec.709)".In this code, J is the lightness in the JMh space the transform works in, and its upper limit
limit_J_maxis exactly 100.0 at 100 nits. A colour with J = 100 passes theJ > limit_J_maxearly-out.solve_J_intersectthen returnsJ_intersect_sourcea float step above the limit (100.0000076), because J = limit_J_max is an exact root of its quadratic. Infind_gamut_boundary_intersection,J_max - J_intersect_sourceis slightly negative,powfof it is NaN, and thegamut_boundary_M <= 0.fguard does not catch the NaN.Fix: clamp that value at 0, on the CPU and in the generated shader text. The boundary M is then 0, and the existing guard returns
{J, 0, h}, the same as for J above the limit (white).Tests:
aces_gamut_comp_20_limit_J: forward and inverse, J at the limit and the 3 float steps below it, all hues in 0.5° steps, 4 values of M; all outputs finite.aces_output_transform_20_limit_J: an AP0 input that reached the limit inside the output transform now gives display white instead of NaN.