Skip to content

Commit 7a84897

Browse files
committed
[css-color] Sample code for color conversions
sRGB <-> lin sRGB <-> XYZ <-> chromatic adaptation <-> Lab <-> LCH Uses a library for matrix manipulations
1 parent 3ac11a3 commit 7a84897

2 files changed

Lines changed: 51399 additions & 0 deletions

File tree

css-color/conversions.js

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// sRGB-related functions
2+
3+
function lin_sRGB(RGB) {
4+
// convert an array of sRGB values in the range 0.0 - 1.0
5+
// to linear light (un-companded) form.
6+
// https://en.wikipedia.org/wiki/SRGB
7+
return RGB.map(function (val) {
8+
if (val < 0.04045) {
9+
return val / 12.92;
10+
}
11+
return Math.pow((val + 0.055) / 1.055, 2.4);
12+
} )
13+
}
14+
15+
function gam_sRGB(RGB) {
16+
// convert an array of linear-light sRGB values in the range 0.0-1.0
17+
// to gamma corrected form
18+
// https://en.wikipedia.org/wiki/SRGB
19+
return RGB.map(function (val) {
20+
if (val > 0.0031308) {
21+
return 1.055 * Math.pow(val, 1/2.4) - 0.055;
22+
}
23+
return 12.92 * val;
24+
})
25+
}
26+
27+
function lin_sRGB_to_XYZ(rgb) {
28+
// convert an array of linear-light sRGB values to CIE XYZ
29+
// using sRGB's own white, D65 (no chromatic adaptation)
30+
// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
31+
var M = math.matrix([
32+
[0.4124564, 0.3575761, 0.1804375],
33+
[0.2126729, 0.7151522, 0.0721750],
34+
[0.0193339, 0.1191920, 0.9503041]
35+
]);
36+
return math.multiply(M, rgb).valueOf();
37+
}
38+
39+
function XYZ_to_lin_sRGB(XYZ) {
40+
// convert XYZ to linear-light sRGB
41+
var M = math.matrix([
42+
[ 3.2404542, -1.5371385, -0.4985314],
43+
[-0.9692660, 1.8760108, 0.0415560],
44+
[ 0.0556434, -0.2040259, 1.0572252]
45+
]);
46+
return math.multiply(M, XYZ).valueOf();
47+
}
48+
49+
// Chromatic adaptation
50+
51+
function D65_to_D50(XYZ) {
52+
// Bradford chromatic adaptation from D65 to D50
53+
// The matrix below is the result of three operations:
54+
// - convert from XYZ to retinal cone domain
55+
// - scale components from one reference white to another
56+
// - convert back to XYZ
57+
// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
58+
var M = math.matrix(
59+
[[ 1.0478112, 0.0228866, -0.0501270],
60+
[ 0.0295424, 0.9904844, -0.0170491],
61+
[-0.0092345, 0.0150436, 0.7521316]]);
62+
return math.multiply(M, XYZ).valueOf();
63+
}
64+
65+
function D50_to_D65(XYZ) {
66+
// Bradford chromatic adaptation from D50 to D65
67+
var M = math.matrix(
68+
[[ 0.9555766, -0.0230393, 0.0631636],
69+
[-0.0282895, 1.0099416, 0.0210077],
70+
[ 0.0122982, -0.0204830, 1.3299098]]);
71+
return math.multiply(M, XYZ).valueOf();
72+
}
73+
74+
// Lab and LCH
75+
76+
function XYZ_to_Lab(XYZ) {
77+
// Assuming XYZ is relative to D50, convert to CIE Lab
78+
// from CIE standard, which now defines these as a rational fraction
79+
var ε = 216/24389; // 6^3/29^3
80+
var κ = 24389/27; // 29^3/3^3
81+
var white = [0.9642, 1.0000, 0.8249]; // D50 reference white
82+
var f = [];
83+
var xyz = [];
84+
var result = [];
85+
// compute xyz, which is XYZ scaled relative to reference white
86+
XYZ.forEach(function (value, index){
87+
xyz[index] = XYZ[index]/white[index];
88+
});
89+
// now compute f
90+
xyz.forEach(function (value, index) {
91+
if (value > ε) {
92+
f[index] = Math.cbrt(value);
93+
} else {
94+
f[index] = (κ * value + 16)/116
95+
}
96+
});
97+
// compute L
98+
result[0] = (116 * f[1]) - 16;
99+
//compute a
100+
result[1] = 500 * (f[0] - f[1]);
101+
// and lastly b
102+
result[2] = 200 * (f[1] - f[2]);
103+
return result;
104+
}
105+
106+
function Lab_to_XYZ(Lab) {
107+
// Convert Lab to D50-adapted XYZ
108+
var ε = 216/24389; // 6^3/29^3
109+
var κ = 24389/27; // 29^3/3^3
110+
var white = [0.9642, 1.0000, 0.8249]; // D50 reference white
111+
var f = [];
112+
var xyz = [];
113+
var result = [];
114+
// compute f, starting with the luminance-related term
115+
f[1] = (Lab[0] + 16)/116;
116+
f[0] = Lab[1]/500 + f[1];
117+
f[2] = f[1] - Lab[2]/200;
118+
// compute xyz
119+
if (f[0] > ε) {
120+
xyz[0] = Math.pow(f[0],3);
121+
} else {
122+
xyz[0] = (116*f[0]-16)/κ;
123+
}
124+
if (Lab[0] > κ * ε) {
125+
xyz[1] = Math.pow((Lab[0]+16)/116,3);
126+
} else {
127+
xyz[1] = Lab[0]/κ;
128+
}
129+
if (f[2] > ε) {
130+
xyz[2] = Math.pow(f[2],3);
131+
} else {
132+
xyz[2] = (116*f[2]-16)/κ;
133+
}
134+
// Compute XYZ by scaling xyz by reference white
135+
xyz.forEach(function (value, index) {
136+
result[index] = value * white[index];
137+
});
138+
return result;
139+
}
140+
141+
function Lab_to_LCH(Lab) {
142+
// Convert to polar form
143+
var result = [];
144+
result[0] = Lab[0]; // L is still L
145+
// Chroma
146+
result[1] = Math.sqrt(Math.pow(Lab[1],2) + Math.pow(Lab[2],2));
147+
// Hue, in degrees
148+
result[2] = Math.atan2(Lab[2], Lab[1]) * 180 / Math.PI;
149+
return result;
150+
}
151+
152+
function LCH_to_Lab(LCH) {
153+
// Convert from polar form
154+
var result = [];
155+
result[0] = LCH[0]; // L is still L
156+
// a and b
157+
result[1] = LCH[1] * Math.cos(LCH[2] * Math.PI / 180);
158+
result[2] = LCH[1] * Math.sin(LCH[2] * Math.PI / 180);
159+
return result;
160+
}
161+
162+
// DCI P3 functions
163+

0 commit comments

Comments
 (0)