culori
culori copied to clipboard
`inGamut` wrong result for white in OKLCH
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).
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?
A similar issue is discussed here.
To address these, maybe it would be a good idea to round the coordinates to fewer decimal places?
It will be great.
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]
I fixed this issue by https://github.com/evilmartians/oklch-picker/commit/3ec093142cffc5f00992f2c0f6336e836b7b23b6
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 :-)
Available in [email protected].
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 🙂.