Mix RGB colors (L*a*b*)
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 fo开发者_StackOverflow中文版r 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
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.
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
精彩评论