Search code examples
javascriptrgbcolor-space

how to convert Ikea light bulb color XY ( CIE 1931 colorspace ) to RGB


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?


Solution

  • 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