2

Problem

i got a similar problem like this one:

How to convert CIE color space into RGB or HEX color code in PHP

how to convert xy color to sRGB? I can't get the formular working xyY. What should i enter for Y?

Setup of the environment

i got an ikea light bulb which gives me a XY color value in the (CIE 1931 colorspace) I would like to convert it into RGB,(sRGB) or HEX.

The Phosconn app is sending the following xy values when setting the colors by full brighness and saturation.

    RED   [0.735, 0.265]
    GREEN [0.115, 0.826]
    BLUE  [0.157, 0.018]

i figured out that the lamp shows deeper colors when i send following values:

    RED   [1.0, 0.0]
    GREEN [0.0, 1.0]
    BLUE  [0.0, 0.0]

To be more precise here is an illustration what i try to achieve:

  1. Retrieve imformation from the bulb (xy color) via zigbee, convert it with javascript to RGB or HEX for the dashboard's color picker

  2. The other way around does already work. Retrieving information from dashboard's color picker (RGB,brightness,saturation) convert it with JS into XY color, brightness and saturation and send it with zigbee to the bulb. enter image description here

Current Implementation

It's based on the suggested cie-rgb-color-converter NPM module.

function xyBriToRgb(x, y, bri){
   // bri = bri/254*100
    node.warn("XYBRI: "+x+ " | "+y+" | "+bri)
    function getReversedGammaCorrectedValue(value) {
        return value <= 0.0031308 ? 12.92 * value : (1.0 + 0.055) * Math.pow(value, (1.0 / 2.4)) - 0.055;
    }

    let xy = {
        x: x,
        y: y
    };

    let z = 1.0 - xy.x - xy.y;
    let Y = bri / 255;
    let X = (Y / xy.y) * xy.x;
    let Z = (Y / xy.y) * z;
    let r = X * 1.656492 - Y * 0.354851 - Z * 0.255038;
    let g = -X * 0.707196 + Y * 1.655397 + Z * 0.036152;
    let b = X * 0.051713 - Y * 0.121364 + Z * 1.011530;

    r = getReversedGammaCorrectedValue(r);
    g = getReversedGammaCorrectedValue(g);
    b = getReversedGammaCorrectedValue(b);

    // Bring all negative components to zero
    r = Math.max(r, 0);
    g = Math.max(g, 0);
    b = Math.max(b, 0);

    // If one component is greater than 1, weight components by that value
    let max = Math.max(r, g, b);
    if (max > 1) {
        r = r / max;
        g = g / max;
        b = b / max;
    }

    return {
        r: Math.floor(r * 255),
        g: Math.floor(g * 255),
        b: Math.floor(b * 255),
    };
}
msg.payload = xyBriToRgb(msg.payload.xy[0], msg.payload.xy[1], msg.payload.bri);
node.warn("RGB: "+ JSON.stringify(msg.payload))
return msg;

Results

let rgb = ColorConverter.xyBriToRgb(0.157 ,0.018, 6);
// return {r: 64, g: 0, b: 255}

enter image description here

Research Material

With the help of the fantastic guys here i found some explanations in this Phillips HUE docs which was leading me to a Review of RGB color spaces

Meanwhile i discovered some bugs inside the phosconn api or its the firmware of the bulb, that the saturation can not be set via api.

I found zigbee2mqtt page which could fix all my problems with a page fitting 100% to the model of the ikea bulb Zigbee2MQTT IKEA LED1624G9

Im trying to setup zigbee2mqtt for this, because i got some problems with phosconn and the api not setting correctly brightness and stuff. Also the brightness is just the luminosity of the bulb and has here nothing to do with the color so i assume it's phosconn specific or bulb specific?

Devilsmaul
  • 23
  • 1
  • 5
  • Have you looked at this one: https://stackoverflow.com/questions/20283401/php-how-to-convert-rgb-color-to-cie-1931-color-specification – Terry Jan 06 '22 at 19:48
  • This is the other way around! i would like to convert XY to RGB – Devilsmaul Jan 06 '22 at 19:50
  • [This](https://gist.github.com/popcorn245/30afa0f98eea1c2fd34d) suggests that you should also have a brightness value. – Andrew Morton Jan 06 '22 at 19:53
  • @AndrewMorton i am also able to get a brightness value from 0-254. Im not sure if the hue algorithm will work but i will try it. Thanks – Devilsmaul Jan 06 '22 at 21:10
  • @AndrewMorton i tested my lamp with the given values inside the Document and they are different from the ones i get when setting the lamp with the phosconn app. So i searched the repo of phosconn and found -> [Xyz2Rgb](https://github.com/dresden-elektronik/deconz-rest-plugin/blob/master/colorspace.cpp#L735) Also interesting that there are 2 methods above Rgb2Xyz and Rgb2xy. I guess the Ikea bulb format is a bit different from CIE1931 which phillips is using. – Devilsmaul Jan 06 '22 at 21:43

2 Answers2

5

Short Answer

Okay, so what I am seeing there are the xy coordinates of the 1931 chromaticity diagram? From that we can generate a matrix, and thru the matrixes we can generate RGB or XYZ (and xyY in a simple further transform.)

Longer Answer

Those coordinates are "xy" not to be confused with XYZ (because what would color science be if it wasn't completely confusing?)

XYZ is a color space, and xyY is a derivative, with the xy being coordinates on the 1931 chromaticity diagram:

enter image description here

The Y, luminance (spectrally weighted light) is missing, but let's solve for all three primaries at 255, and let's assume that's white.

One thing I don't have is the "white point"... when you set the RGB values to 255,255,255 what is the color temperature of the white light? In order to create the matrix I made the assumption of D50, reasonable for lighting like this... but it could be lower or even higher.

The math for the matrixes can be seen at Bruce Lindbloom's site: http://brucelindbloom.com

Assuming that 255,255,255 creates a white light close to D50 then the matrix to go from XYZ to RGB is:

MATRIX XYZ TO RGB
X 1.4628522474616900 -0.1840680708796900 -0.2743691849401830 R
Y -0.5217863795540330 1.4472188263102400 0.0677218457168470   =   G
Z 0.0349375228787856 -0.0969021860977637 1.2885320438253000 B

But wait...

Where are you getting these xy values? It seems your controller is sending 255 to each of the lights... so, I'm a little confused as to the application?

Assuming white at a given color temperature when all three are at 255, then the relative Y is 1.0 (0.0 to 1.0 scale, though you can also use a 0 to 100 scale instead).

The next question is ... linearity. On a computer monitor, typically sRGB has an output gamma, a power curve of ^2.2 ... an LED bulb probably does not. Are you trying to match a computer monitor?

Assuming not, and assuming that your controller is also a gamma of 1, then 128,128,128 would be a Y of 0.5

If you want to get the whole XYZ from your RGB, that matrix is here:

For reference the RGB to XYZ and the reference whitepoint

MATRIX RGB TO XYZ
R 0.7160822783599350 0.1009309426243870 0.1471718784550240 X
G 0.2581793248508610 0.7249474661542950 0.0168732089948435   =   Y
B 0.0000000000000000 0.0517819618681639 0.7733554122636590 Z
CALCULATED REF WHITE POINT
WP 0.964185099439346 1.00 0.825137374131823

Matrix Sans Neo

Once the matrix is generated, it's fairly simple to process.

You multiply each number of each row with each row of the input channels, and then sum and that gives you the corresponding output.

so for the output to RGB, you go

   X * 1.46285  + Y * -0.184068 + Z * -0.274369  = R 
   X * 0.258179 + Y * 0.724947  + Z * 0.01687    = G 
   X * 0.0      + Y * 0.05178   + Z * 0.773355   = B

If I understood better what you were attempting to acheive, I might be able to be more helpful.

Recalcualted Matrixes

with the white point you gave:

recalculated matrixes

    FINAL RGB TO XYZ MATRIX         
R   0.6491852651246980  0.1034883891428110  0.1973263457324920  X
G   0.2340599935483600  0.7433166037561910  0.0226234026954449  Y
B   0.0000000000000000  0.0530940431254422  1.0369059568745600  Z

    CALCULATED REF WHITE POINT          
WP  0.950000000000001   1.00    1.090000000000000   

    MATRIX  INVERTED  — XYZ TO RGB          
X   1.6135957276619700  -0.2030358522440090 -0.3026422835182280 R
Y   -0.5088917855729640 1.4114545750797300  0.0660482763436608  G
Z   0.0260574473801223  -0.0722725427335469 0.9610256584609440  B

Myndex
  • 3,952
  • 1
  • 9
  • 24
  • 1
    As you said, we have `xy` as coordinates of the 1931 chromaticity diagram and want to convert it to RGB. let accept all assumptions about white color D50 and so on. But, I don't get it; how we can convert `xy` coordinates to RGB? – Esmaeil Jan 07 '22 at 20:19
  • 2
    Hi @Esmaeil xy alone is not enough. you need Y as in the relative luminance. The xy coordinates only indicate a hue/chroma on the chromaticity diagram, and those coordinates may not even be useable for some range of the luminance...... here's something, let's assume the OP always wants the brightest possible. Okay, so set up a loop and start with Y = 100, then step down by 1 until you get a set of RGB values where all three are no higher than 255. – Myndex Jan 08 '22 at 04:07
  • @Myndex you were asking for the white point which can be calculated with the color temperature. The Problem here is that the lamp is not giving me relyable color_temp values back. But if i sent 255,255,255 at least i got xy [0.3125,0.32894736842105265]. If i would guess its D55 The light seems to me cold . [Whitepoint info](https://de.mathworks.com/help/images/ref/whitepoint.html) – Devilsmaul Jan 10 '22 at 02:02
  • 1
    Hi @Devilsmaul That's essentially D65, same as an sRGB computer monitor or an HDTV.... Surprising as lighting is usually in the 5000 to 5500 range... it probebly seem very blusih for a light. I recalculated the matrixes, editing the answer to put them at the bottom (will leave the others) CHeers! – Myndex Jan 10 '22 at 11:02
  • 1
    soo after a long working week i got some time to test it. And your first matrix gives a warm light. and the second a really white colder light. i think both is nice if you can switch somehow between the temperatures. I still do not know how you calculated these matrices with the different white points. If i can get the missing clue i could write an NodeRed node doing it based on the whitepoint :) – Devilsmaul Jan 16 '22 at 04:00
  • Hi @Devilsmaul I had a link to the math in the post, though here it is again: http://brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html and he has a calculator in JS that does a lot of the heavy lifting... – Myndex Jan 16 '22 at 20:56
  • Also, I'm working on a library with matrix math... not ready for prime time though... – Myndex Jan 16 '22 at 20:58
2

You can use cie-rgb-color-converter NPM module.

let xy = ColorConverter.rgbToXy(255, 0, 0);
// It returns {x: 0.7350000508904126, y: 0.26499994910958735}

The number of xy is same as question example.

But if you want to convert these numbers back to RGB. You need to Brightness parameter.

let rgb = ColorConverter.xyBriToRgb(0.7350000508904126 ,0.26499994910958735 , Brightness);

If you set Brightness to 0, it is darkest light (no light, no color), and all number of RGB comes back as zero, because in zero light human eyes can not see anything.

Following example is nearest numbers:

let rgb = ColorConverter.xyBriToRgb(0.7350000508904126 ,0.26499994910958735, 70);
// return {r: 255, g: 0, b: 16}
    
let rgb = ColorConverter.xyBriToRgb(0.11500021676131911 ,0.8259995753701338, 200);
// return {r: 0, g: 255, b: 0}

let rgb = ColorConverter.xyBriToRgb(0.15700016726803506 ,0.01799963360335173, 6);
// return {r: 64, g: 0, b: 255}

Note: cie-rgb-color-converter has a simple problem, after install go to ColorConverter.js and change these lines:

    let red = parseInt(r * 255) > 255 ? 255: parseInt(r * 255);
    let green = parseInt(g * 255) > 255 ? 255: parseInt(g * 255);
    let blue = parseInt(b * 255) > 255 ? 255: parseInt(b * 255);

    red = Math.abs(red);
    green = Math.abs(green);
    blue = Math.abs(blue);

    return {r: red, g: green, b: blue};

to

// Bring all negative components to zero

    r = Math.max(r, 0);
    g = Math.max(g, 0);
    b = Math.max(b, 0);

    // If one component is greater than 1, weight components by that value

        let max = Math.max(r, g, b);
        if (max > 1) {
            r = r / max;
            g = g / max;
            b = b / max;
        }

        return {
            r: Math.floor(r * 255),
            g: Math.floor(g * 255),
            b: Math.floor(b * 255),
        };

@bokup PR it on GitHub.

Esmaeil
  • 558
  • 5
  • 11
  • 1
    this looks really promising. The rgbToXy is working fine! but i got stuck at xyBriToRgb conversion. After your change the RGB result values are between 0-255. But i think there is a problem with the brightness or somewhere else. if the brigthness its to high like 254 it just shows white (255,255,255) If i set it to 100 the colors does not make sense. Yellow instead of Green `"XYBRI: 0.157 | 0.018 | 254"` `"RGB: {"r":255,"g":255,"b":255}"` `"XYBRI: 0.735 | 0.265 | 100"` `"RGB: {"r":255,"g":255,"b":23}"` `"XYBRI: 0.735 | 0.265 | 1"` `"RGB: {"r":34,"g":3,"b":0}"` – Devilsmaul Jan 06 '22 at 22:12
  • 1
    I update the code, this new one, may be get more precise result. – Esmaeil Jan 07 '22 at 11:21
  • I will try your updated code asap and will give here feedback. Thanks! – Devilsmaul Jan 08 '22 at 23:58
  • As @Myndex mentioned: "xy alone is not enough. you need Y as in the relative luminance. The xy coordinates only indicate a hue/chroma on the chromaticity diagram, and those coordinates may not even be useable for some range of the luminance...... here's something, let's assume the OP always wants the brightest possible. Okay, so set up a loop and start with Y = 100, then step down by 1 until you get a set of RGB values where all three are no higher than 255." Then you can use original `cie-rgb-color-converter` - without my overwrite code, and try this new method as well. – Esmaeil Jan 09 '22 at 08:56