What are the LCh and JCh values for the sRGB blue primary?

This question arose in an email discussion in which there was some question as to the actual “sRGB blue” LCh and JCh values.

Here are ArgyllCMS xicclu (xicclu) adapted and unadapted LCh values for the sRGB blue primary (that is, (0,0,255), (0.0, 0.0, 1.0), etc, depending on the bit depth)):

relative (adapted to D50): $ xicclu -ir -pL sRGB.icm

absolute (unadapted): $ xicclu -ia -pL sRGB.icm

The above adapted values are also what you get in GIMP, PhotoFlow, RawTherapee, though in RawTherapee you get LAB values, but the LAB values agree with GIMP LAB values.

Apparently https://www.colour-science.org/ calculations come up with a different set of LCh values for sRGB blue, compared to ArgyllCMS values. The discrepancies are worse for JCh values. The other party to the email discussion should be coming along soon to post the colour-science values, I hope :slight_smile: .

In the meantime, these three websites: http://davidjohnstone.net/pages/lch-lab-colour-gradient-picker , Convert Rgb to Lch , Convert | EasyRGB all come up with values of approximately:


So the above three websites come up with values that are in agreement with each other but only “somewhat close” to ArgyllCMS “xicclu” unadapted LCh values.

I suspect all three websites might have gotten their D65 and D50 white points from brucelindbloom.com (Welcome to Bruce Lindbloom's Web Site), which uses ASTM values instead of the D65 values from the sRGB specs and the D50 values from the ICC specs, which might account for the discrepancies for the LCh values.

For JCh, there are a bunch of other parameters to consider beside the white point values, and I don’t know what default values ArgyllCMS xicclu actually uses for these other parameters. But for comparison (again, assuming the other party to the email discussion does post colour-science values :slight_smile: ) here are the ArgyllCMS xicclu values:

adapted: $ xicclu -ir -pJ sRGB.icm

unadapted: $ xicclu -ia -pJ sRGB.icm

edit: in the lines immediately above, “L” of course should be “J”, sorry! I should have just copy-pasted the xicclu output instead of trying to make the lines look pretty. Sigh.

1 Like

To put some context to the question of the actual JCh and LCh values for sRGB blue, the question came up in a discussion of questions like these:

  • On a scale from “green-blue to blue to purple-blue to purple”, just how purple is sRGB blue?

  • If there were an actual “real world” paint pigment with the same JCh or LCh hue angle as sRGB blue and a reasonably close Lightness value - but obviously with a lower Chroma - how saturated a green could be made by mixing this paint pigment with various real world yellow paint pigments, such as Hansa yellow, which seems somewhat close to sRGB yellow.

Some colors to go along with the questions:

And here are the handprint.com CIELAB and CIECAM color wheels with paint pigments:

CIECAM: https://www.handprint.com/HP/WCL/cwheel06.pdf

CIELAB: https://www.handprint.com/HP/WCL/CIELAB.pdf

Hi Elle,

Are you familiar with the US company www.goldenpaints.com? They have an impressive tech info area on their site.

Have fun!
Claes in Lund, Sweden

Golden makes great paint! If you’re into that kind of thing :wink:

Here are the JCh hue angles using default parameters or the example from the API ref

sRGB blue 0,0,1
xicclu: 272-273
colorspacious: 257
colour.CIECAM02: 257
colour.CAM16: 282

I have no idea what’s up with these discrepancies.

Hi @briend - do you also happen to have the J and the C values? And maybe the command to get the JCh values out of the colour-science software?

Thanks for picking this up. My previous LCh / JCh thread had some loose cords. :slight_smile: I am glad that other people are interested in this topic as well. @Elle, do you get a lot of emails on colour, etc.? When we are done with sRGB, maybe discuss other colour spaces and gammas… :sunny:

Here are some procedures for for CIECAM02 and CIECAM16:


import colorspacious
>CIECAM02Space([95.047, 100.0, 108.883], 20.0, 4.074366543152521) 
>CIECAM02Surround(F=1.0, c=0.69, N_c=1.0)

We'll use these conditions for colour and colorspacious (default)

colorspacious.cspace_convert([0,0,1], "sRGB1", "XYZ100")
>array([18.04926474,  7.22004986, 95.04971251])

colorspacious.cspace_convert([0,0,1], "sRGB1", "JCh")
>array([ 21.074306  ,  90.51466637, 257.9160105 ])

import numpy as np
XYZ_w = np.array([95.05, 100.00, 108.88])
Y_b = 20.0
L_A = 4.074366543152521

import colour
XYZ = colour.sRGB_to_XYZ([0,0,1])*100
>array([ 18.05,   7.22,  95.05])

surround = colour.CAM16_VIEWING_CONDITIONS['Average']
colour.XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround)
>CAM16_Specification(J=25.068115589506046, C=86.379113936277193, h=282.76257939117284, s=100.40868283750517, Q=61.938226980555086, M=62.445523290163273, H=325.31102432728488, HC=None)

surround = colour.CIECAM02_VIEWING_CONDITIONS['Average']
colour.XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround)
>CIECAM02_Specification(J=21.074779199999309, C=90.514152237132848, h=257.91985314918975, s=107.34536676212075, Q=56.786152486934995, M=65.434841179118308, H=310.71293484022056, HC=None)

@briend - how do you actually get the colour commands to do anything?

When I type “import colour” the only thing that happens is the cursor changes shape. If I move the mouse I can draw a rectangle on the screen, that disappears as soon as I release the mouse, at which point I’m back at the command prompt.

Ah, you’re in bash. You should run python first and hopefully installed colour and/or colorspacious via pip or the websites, or package manager.

I was really confused but “import” is a shell command:
import - saves any visible window on an X server and outputs it as an
image file. You can capture a single window, the entire screen, or any
rectangular portion of the screen.

So to get it installed might issue:
pip install colour
pip install colorspacious
(then the commands from above to use the libraries)


I have not read the thread properly and will do tonight at home, but quickly checking: @Elle, are you comparing LCh from CIE Lab to LCh from CIECAM02? If that is the case I would not really be surprised to see discrepancies. CIE Lab is very different from CIECAM02, while the former is technically a CAM it is by far much simpler than CIECAM02 (or CAM16).



Did a test for LChab while cooking:

>>> import colour
>>> colour.Lab_to_LCHab(colour.XYZ_to_Lab(colour.sRGB_to_XYZ([0, 0, 1]), colour.ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65']))
array([  32.30258667,  133.80596077,  306.2910681 ])

Values Bruce Lindbloom get:

My apologies, I’ve never heard the phrase "LCh from CIECAM02, but I’m guessing that would be the same as JCh from CIECAM02, yes?

No, I’m not comparing LCh from CIELAB to JCh from CIECAM02, and indeed JCh from CIECAM02 and LCh from CIELAB are different from each other.

The problem is that the colour science JCh and LCh values for sRGB blue are rather different from ArgyllCMS xicclu JCh and LCh values for sRGB blue.

Quoting from xicclu documentation (xicclu)

 -i intent      a = absolute, r = relative colorimetric,
                p = perceptual, s = saturation, A = disp. abs. measurements
 -p oride       x = XYZ_PCS, X = XYZ * 100, l = Lab_PCS, L = LCh, y = Yxy,
                j = CIECAM02 Appearance Jab, J = CIECAM02 Appearance JCh

Here are xicclu JCh values using absolute colorimetric intent:

$ xicclu -ia -pJ sRGB.icm
0 0 1
0.000000 0.000000 1.000000 [RGB] -> MatrixFwd -> 
32.115896 100.702873 272.724696 [JCh]

Looking at your LCh (from CIELAB) values, that do also match LCh values that you show from Lindbloom’s nice little calculation page, this does cohere with a suggestion I made in my initial post to this thread, that various calculators might be using ASTM values for D65, just as Lindbloom does. Is this the case?

The problem with Lindbloom’s ASTM values for D65 (and D50) is that these values aren’t the values given in the sRGB color space specs for D65 (and also not the values given in the ICC specs for D50).

Is there a way to ask colour-science at the command line to use a different value for D65?

Here are ArgyllCMS xicclu values for sRGB blue for LCh (CIELAB) using absolute colorimetric intent:

$ xicclu -ia -pL sRGB.icm
0 0 1
0.000000 0.000000 1.000000 [RGB] -> MatrixFwd -> 
32.299974 148.440107 301.621299 [LCh]

Different values for D65 when making the calculations would mean different output values for LCh, JCh, etc. So I’m guessing that all the online calculators that produce values that match color-science values and Lindbloom calculator values, are using ASTM white point values for D65, instead of the white point values given in the sRGB specs.

ArgyllCMS xicclu uses the white point values given in the sRGB and ICC specs.

Thanks! for linking to Lindbloom’s color calculator - I didn’t realize that page actually still worked, but checking more carefully, you have to click on the row label after entering the values (note to self: try reading the instructions).

OK, “run python first” literally means drop to the command line and type “python”, at which point one can enter commands. Thanks! In case anyone tries this and hasn’t done this before, the way to exit python is to type “exit()” or else “Ctrl-D” .

@briend - what is the difference between colour and colorspacious?

Here’s a way to get LCh values out of colour-science that are a very close match to the values that ArgyllCMS xicclu produces:

  1. Use xicclu to convert sRGB blue from sRGB to XYZ. I’m using the ArgyllCMS version of sRGB “sRGB.icm” in the ArgyllCMS “ref” foloder:

    $ xicclu -ia -px sRGB.icm
    0 0 1
    0.000000 0.000000 1.000000 [RGB] → MatrixFwd →
    0.180482 0.072188 0.950536 [XYZ]

  2. Feed these XYZ values to colour:
    import colour
    colour.XYZ_to_Lab([0.180482, 0.072188, 0.950536])
    array([ 32.29991048, 77.81854307, -126.38474329])

  3. Use colour to convert from LAB to LCh:
    colour.Lab_to_LCHab([ 32.29991048, 77.81854307, -126.38474329])
    array([ 32.29991048, 148.4211204 , 301.62174339])

  4. Compare to xicclu values:
    $ xicclu -ia -pL sRGB.icm
    0 0 1
    0.000000 0.000000 1.000000 [RGB] → MatrixFwd →
    32.299974 148.440107 301.621299 [LCh]

Starting from the xicclu XYZ values for sRGB.icm (using absolute intent to get to XYZ), and giving these XYZ values to colour-science, L, C, and h all match to at least one decimal place.

For easy comparison, here are the colour-science results that @KelSolaar provided, starting from the color-science built-in sRGB instead of from the ArgyllCMS XYZ values for the sRGB blue primary - L is close, C differs by 15, and h differs by 5:

Colorspacious is just another python color library, for comparison. It’s much more limited than colour but it uses a graph model to traverse the conversions for you.

If this is the first step to get colour and xicclu to agree on LCh, then maybe we need to look at the discrepancy in the XYZ?

I think the problem is exactly in the conversion from sRGB to XYZ, which seems to be rounded to four decimal places in the colour calculations.

We are heading out the door and won’t be back for a couple of hours. But when we get back, I’ll check to see if the rounding to four places of the XYZ values really does account for the discrepancy.

ASTM D65 values instead of the sRGB specs D65 values only makes a small difference in calculation of LCh values.

Right! :slight_smile:

The conversions are not rounded at four decimal places, they should actually be happening at double precision everywhere which is about 15 digits on my platform. However because we use the common [0.3127, 0.3290] as D65 whitepoint, everything you get after that will not be comparable with a whitepoint definition having more digits, e.g. computed from XYZ. You can pass the CIE xyY value if you need in Colour or a value with more precision.

For display purpose you can increase the print precision:

import numpy as np
np.set_printoptions(formatter={‘float’: ‘{:0.15f}’.format}, threshold=np.nan)

Hmm, no, you are right, it’s not from rounding :slight_smile: . Even feeding just four digits per sRGB blue XYZ values still results in colour calculating LCh values that are very close to ArgyllCMS xicclu results:

>>> XYZ_blue=[ 0.1805,  0.0722,  0.9505]
>>> colour.XYZ_to_Lab(XYZ_blue)
array([  32.30258667,   77.81651558, -126.37748228])
>>> colour.Lab_to_LCHab([32.30258667,   77.81651558, -126.37748228])
array([  32.30258667,  148.41387443,  301.6225466 ])

$ xicclu -ia -pL sRGB.icm
0 0 1
0.000000 0.000000 1.000000 [RGB] -> MatrixFwd -> 
32.299974 148.440107 301.621299 [LCh]

But using colour to evaluate this expression does produce LCh values that are fairly well off from xicclu results:

>>> colour.Lab_to_LCHab(colour.XYZ_to_Lab(colour.sRGB_to_XYZ([0, 0, 1]), colour.ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65']))
array([  32.30258667,  133.80596077,  306.2910681 ])

Again the Chromas are apart by 15 and the hues by 5.

How do you evaluate the “extra” part, that being “colour.ILLUMINANTS[‘CIE 1931 2 Degree Standard Observer’][‘D65’])”?