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