gka / chroma.js

JavaScript library for all kinds of color manipulations
https://vis4.net/chromajs/
Other
9.92k stars 543 forks source link

LCH 🠖 RGB mismatch compared to other libraries #278

Open adrian5 opened 2 years ago

adrian5 commented 2 years ago

This library looks wonderfully feature rich, but playing with the demo page I noticed that it produces a different RGB color than other libraries, given LCH input.

Input: { l: 59.6, c: 61.3, h: 24.3 }

Output:

Is this due to differences in the transformation stages, or maybe LCHab vs LCHuv? I have very limited knowledge of this topic, so I couldn't say.

notsidney commented 2 years ago

I noticed this as well when converting from RGB to LCH.

Input: #421AFD

Output:

Most noticeable is the +5 to the hue, which skews the colour closer to purple

meodai commented 2 years ago

as far as I understand it. the conversion depends on the white-point.

CIELAB is calculated relative to a reference white, for which the CIE recommends the use of CIE Standard Illuminant D65.[1] D65 is used in the vast majority industries and applications, with the notable exception being the printing industry which uses D50

https://en.wikipedia.org/wiki/CIELAB_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC

So you would have to know what white-point each library is using to be able to compare its results.

notsidney commented 2 years ago

It looks like colord uses W3C sample code for making the conversion from RGB → LAB: https://github.com/omgovich/colord/blob/master/src/colorModels/lab.ts#L45 https://www.w3.org/TR/css-color-4/#color-conversion-code

The W3C draft specifies the D50 whitepoint:

Convert from a D65 whitepoint (used by sRGB) to the D50 whitepoint used in Lab

https://www.w3.org/TR/css-color-4/#rgb-to-lab

d3-color uses their own algorithm and also uses D50 as the whitepoint: https://github.com/d3/d3-color/blob/main/src/lab.js#L5 https://observablehq.com/@mbostock/lab-and-rgb

And chroma.js uses D65 here: https://github.com/gka/chroma.js/blob/master/src/io/lab/lab-constants.js#L6

So this looks to be the reason for the difference

Arkkimaagi commented 2 years ago

https://css.land/lch seems to use https://drafts.csswg.org/css-color-4/utilities.js for converting between sRGB and LCH, which document the steps like this:

function sRGB_to_LCH(RGB) {
    // convert an array of gamma-corrected sRGB values
    // in the 0.0 to 1.0 range
    // to linear-light sRGB, then to CIE XYZ,
    // then adapt from D65 to D50,
    // then convert XYZ to CIE Lab
    // and finally, convert to CIE LCH
function LCH_to_sRGB(LCH) {
    // convert an array of CIE LCH values
    // to CIE Lab, and then to XYZ,
    // adapt from D50 to D65,
    // then convert XYZ to linear-light sRGB
    // and finally to gamma corrected sRGB
    // for in-gamut colors, components are in the 0.0 to 1.0 range
    // out of gamut colors may have negative components
    // or components greater than 1.0
    // so check for that :)

I do not know what steps chroma.js uses, but it would be interesting to know and hear the reasons for the difference in steps taken.