culori icon indicating copy to clipboard operation
culori copied to clipboard

`inGamut` wrong result for white in OKLCH

Open ai opened this issue 1 year ago • 5 comments

const inRGB = inGamut('rgb')

inRGB({ mode: 'oklch', l: 1, c: 0, h: 0 }) // => false
// white is in sRGB and return should be true

We found this issue in OKLCH color picker.

I can’t remember this issue previously. It could be related to some recent changes, but it is hard to say when exactly (we are using culori version 4.0.1).

ai avatar Nov 22 '24 13:11 ai

oklch(100% 0 0) is ever so slightly out of gamut in sRGB:

culori.rgb({ mode: 'oklch', l: 1, c: 0, h: 0 })
Object { mode: "rgb", r: 0.9999999694343974, g: 1.000000008665371, b: 1.0000001148563589 }

This may be due to the matrices we’re using for the conversion. There’s an open issue about updating the matrices to match the latest CSS 4 spec (#237), but if I remember correctly color.js uses the new matrices and the color is still, technically, very slightly out of gamut:

new Color('oklch(100% 0 0)').to('srgb').coords
Array(3) [ 1.0000000000000007, 0.9999999999999994, 0.9999999999999999 ]

To address these, maybe it would be a good idea to round the coordinates to fewer decimal places?

danburzo avatar Nov 27 '24 14:11 danburzo

A similar issue is discussed here.

danburzo avatar Nov 27 '24 15:11 danburzo

To address these, maybe it would be a good idea to round the coordinates to fewer decimal places?

It will be great.

ai avatar Nov 27 '24 23:11 ai

This may be due to the matrices we’re using for the conversion. There’s an open issue about updating the matrices to match the latest CSS 4 spec (https://github.com/Evercoder/culori/issues/237), but if I remember correctly color.js uses the new matrices and the color is still, technically, very slightly out of gamut:

This is simply due to floating point math. It can depend on how some of the calculations are done (matrix inversion etc.) , the consistency of values being used, the precision of those values, etc. Due to the complexity and type of operations being performed, getting a perfect conversion without some floating point error is impossible unless you get really lucky with the rounding.

I believe at this point the CSS issue is closed and new matrices have been applied.

The reality of the issue is that the original Oklab matrices were only provided in 32 bit precision. People always used the forward transform of the LMS to Oklab translation and generated the reverse transform in 64 bit, and assumed that was the best way to go, but the 32 bit values provided a poor translation from LMS 1, 1, 1 to Oklab 1, 0, 0 in 64 bit. In 32 bit operations it was fine. In 64 bit operations you would see a lightness that only aligned to approximately 32 bit accuracy and an LCh chroma that would drift further from zero for achromatic values as lightness approached or exceeded 1.

Taking the inverse transform (Oklab -> LMS) and converting back from Oklab 1, 0, 0 to LMS actually gave something very close, if not exact to 1, 1, 1 in LMS. Essentially, to get a closer, cleaner relation between LMS 1, 1, 1 and Oklab 1, 0, 0, it was better to use the 32 bit reverse transform and generate the forward transform by inverting the reverse transform. Generally, this reduces a lot of the noise and provides a better relationship between white and Oklab 1, 0, 0:

>>> Color('white').convert('oklch').coords()
[1.0, 5.117875266520903e-16, nan]
>>> Color('oklch', [1, 0, 0]).convert('srgb').coords()
[1.0000000000000004, 0.9999999999999997, 0.9999999999999997]

facelessuser avatar Nov 30 '24 17:11 facelessuser

I fixed this issue by https://github.com/evilmartians/oklch-picker/commit/3ec093142cffc5f00992f2c0f6336e836b7b23b6

ai avatar Feb 10 '25 23:02 ai

I’ve updated the matrices for Oklab conversion to match the css-color-4 spec (e43d12ad3344477b0cc043269911191c4b8c45b0). Incidentally, due to our slightly different LRGB to XYZ D65 matrix, oklch(100% 0 0) is now in the sRGB gamut :-)

danburzo avatar Jun 27 '25 15:06 danburzo

Available in [email protected].

danburzo avatar Jun 27 '25 16:06 danburzo

I’ve updated the matrices for Oklab conversion to match the css-color-4 spec (e43d12a). Incidentally, due to our slightly different LRGB to XYZ D65 matrix, oklch(100% 0 0) is now in the sRGB gamut :-)

Yep, sometimes you get lucky with the floating point rounding 🙂.

facelessuser avatar Jun 27 '25 17:06 facelessuser