1

Basically I want two mix two colours color1 and color2. Since simple calculation's bring up stuff like blue+yellow = grey ((color1.r + color2.r)/2 etc) i did some research and found that apparently mixing colors in order for the mixed color to look like we expect it too (e.g. blue+yellow = green) isn't that straight forward.

What another stackoverflow post taught me was that in order two achieve the mixture correctly i'd have to use the Lab* space / CIELAB and linked to the wikipedia page about this topic.

I found it informative but i couldn't really understand how to convert RGB to (sRGB and than to) Lab* - how to mix the obtained colors and how to convert back

I hope somebody here can help me

Thanks,

Samuel

Samuel
  • 18,286
  • 18
  • 52
  • 88

2 Answers2

0

1) convert sRGB to RGB. From GEGL:

  

static inline double
linear_to_gamma_2_2 (double value)
{
  if (value > 0.0030402477F)
    return 1.055F * pow (value, (1.0F/2.4F)) - 0.055F;
  return 12.92F * value;
}


static inline double
gamma_2_2_to_linear (double value)
{
  if (value > 0.03928F)
    return pow ((value + 0.055F) / 1.055F, 2.4F);
  return value / 12.92F;
}

2) RGB to CIELAB. Look in OpenCV source [/src/cv/cvcolor.cpp]. There are functions for color space conversions [icvBGRx2Lab_32f_CnC3R]

3) mix color channels.

4) make all the color conversions back.

Ross
  • 2,079
  • 2
  • 22
  • 26
  • Ross, can you please be more specific about "mix coor channels"? Do you mean to do just math averages of the each L* a* b* values? Please clarify. – Ωmega Jun 30 '12 at 13:38
0

To interpolate between two RGB colours in the LAB colour space, you first need to convert each colour to LAB via XYZ (RGB -> XYZ -> LAB).

function RGBtoXYZ([R, G, B]) {
    const [var_R, var_G, var_B] = [R, G, B]
        .map(x => x / 255)
        .map(x => x > 0.04045
            ? Math.pow(((x + 0.055) / 1.055), 2.4)
            : x / 12.92)
        .map(x => x * 100)

    // Observer. = 2°, Illuminant = D65
    X = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805
    Y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722
    Z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505
    return [X, Y, Z]
}

function XYZtoRGB([X, Y, Z]) {
    //X, Y and Z input refer to a D65/2° standard illuminant.
    //sR, sG and sB (standard RGB) output range = 0 ÷ 255

    let var_X = X / 100
    let var_Y = Y / 100
    let var_Z = Z / 100

    var_R = var_X *  3.2406 + var_Y * -1.5372 + var_Z * -0.4986
    var_G = var_X * -0.9689 + var_Y *  1.8758 + var_Z *  0.0415
    var_B = var_X *  0.0557 + var_Y * -0.2040 + var_Z *  1.0570

    return [var_R, var_G, var_B]
        .map(n => n > 0.0031308
            ? 1.055 * Math.pow(n, (1 / 2.4)) - 0.055
            : 12.92 * n)
        .map(n => n * 255)
}

You may use this function to interpolate between two LAB colours

const sum = (a, b) => a.map((_, i) => a[i] + b[i])

const interpolate = (a, b, p) => {
    return sum(
        a.map(x => x * p),
        b.map(x => x * (1 - p)),
    )
}

const colour1 = [...]
const colour2 = [...]
interpolate(colour1, colour2, 0.2) // take 20% of colour1 and 80% of colour2

Finally, convert the result back from LAB to RGB

function XYZtoLAB([x, y, z]) {
    const [ var_X, var_Y, var_Z ] = [ x / ref_X, y / ref_Y, z / ref_Z ]
        .map(a => a > 0.008856
            ? Math.pow(a, 1 / 3)
            : (7.787 * a) + (16 / 116))

    CIE_L = (116 * var_Y) - 16
    CIE_a = 500 * (var_X - var_Y)
    CIE_b = 200 * (var_Y - var_Z)

    return [CIE_L, CIE_a, CIE_b]
}

function LABtoXYZ([l, a, b]) {

    const var_Y = (l + 16) / 116
    const var_X = a / 500 + var_Y
    const var_Z = var_Y - b / 200

    const [X, Y, Z] = [var_X, var_Y, var_Z]
        .map(n => Math.pow(n, 3) > 0.008856
            ? Math.pow(n, 3)
            : (n - 16 / 116) / 7.787)

    return [X * ref_X, Y * ref_Y, Z * ref_Z]
}

Some other helpful functions

const parseRGB = s => s.substring(s.indexOf('(') + 1, s.length - 1).split(',')
    .map(ss => parseInt(ss.trim()))

const RGBtoString = ([r, g, b]) => `rgb(${r}, ${g}, ${b})`

All together:

document.body.style.backgroundColor = RGBtoString(XYZtoRGB(LABtoXYZ(interpolate(
    XYZtoLAB(RGBtoXYZ(parseRGB('rgb(255, 0, 0)'))),
    XYZtoLAB(RGBtoXYZ(parseRGB('rgb(0, 255, 0)'))),
    0.2,
))))

Reference for colour space conversions: http://www.easyrgb.com/en/math.php

Marcel
  • 1,034
  • 1
  • 17
  • 35