Skip to content

Commit f80a0dc

Browse files
committed
[css-color-4] Remove math.js dependency from color conversion functions, add tests
1 parent a64d38a commit f80a0dc

File tree

5 files changed

+901
-54
lines changed

5 files changed

+901
-54
lines changed

css-color-4/conversions.js

Lines changed: 49 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Sample code for color conversions
22
// Conversion can also be done using ICC profiles and a Color Management System
3-
// For clarity, a library is used for matrix manipulations
3+
// For clarity, a library is used for matrix multiplication (multiply-matrices.js)
44

55
// sRGB-related functions
66

@@ -36,24 +36,24 @@ function lin_sRGB_to_XYZ(rgb) {
3636
// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
3737
// also
3838
// https://www.image-engineering.de/library/technotes/958-how-to-convert-between-srgb-and-ciexyz
39-
var M = math.matrix([
39+
var M = [
4040
[0.4124564, 0.3575761, 0.1804375],
4141
[0.2126729, 0.7151522, 0.0721750],
4242
[0.0193339, 0.1191920, 0.9503041]
43-
]);
43+
];
4444

45-
return math.multiply(M, rgb).valueOf();
45+
return multiplyMatrices(M, rgb);
4646
}
4747

4848
function XYZ_to_lin_sRGB(XYZ) {
4949
// convert XYZ to linear-light sRGB
50-
var M = math.matrix([
50+
var M = [
5151
[ 3.2404542, -1.5371385, -0.4985314],
5252
[-0.9692660, 1.8760108, 0.0415560],
5353
[ 0.0556434, -0.2040259, 1.0572252]
54-
]);
54+
];
5555

56-
return math.multiply(M, XYZ).valueOf();
56+
return multiplyMatrices(M, XYZ);
5757
}
5858

5959
// image-p3-related functions
@@ -77,25 +77,25 @@ function lin_P3_to_XYZ(rgb) {
7777
// convert an array of linear-light display-p3 values to CIE XYZ
7878
// using D65 (no chromatic adaptation)
7979
// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
80-
var M = math.matrix([
80+
var M = [
8181
[0.4865709486482162, 0.26566769316909306, 0.1982172852343625],
8282
[0.2289745640697488, 0.6917385218365064, 0.079286914093745],
8383
[0.0000000000000000, 0.04511338185890264, 1.043944368900976]
84-
]);
84+
];
8585
// 0 was computed as -3.972075516933488e-17
8686

87-
return math.multiply(M, rgb).valueOf();
87+
return multiplyMatrices(M, rgb);
8888
}
8989

9090
function XYZ_to_lin_P3(XYZ) {
9191
// convert XYZ to linear-light P3
92-
var M = math.matrix([
92+
var M = [
9393
[ 2.493496911941425, -0.9313836179191239, -0.40271078445071684],
9494
[-0.8294889695615747, 1.7626640603183463, 0.023624685841943577],
9595
[ 0.03584583024378447, -0.07617238926804182, 0.9568845240076872]
96-
]);
96+
];
9797

98-
return math.multiply(M, XYZ).valueOf();
98+
return multiplyMatrices(M, XYZ);
9999
}
100100

101101
// prophoto-rgb functions
@@ -130,24 +130,24 @@ function lin_ProPhoto_to_XYZ(rgb) {
130130
// convert an array of linear-light prophoto-rgb values to CIE XYZ
131131
// using D50 (so no chromatic adaptation needed afterwards)
132132
// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
133-
var M = math.matrix([
134-
[ 0.7977604896723027, 0.13518583717574031, 0.0313493495815248 ],
135-
[ 0.2880711282292934, 0.7118432178101014, 0.00008565396060525902 ],
136-
[ 0.0, 0.0, 0.8251046025104601 ]
137-
]);
133+
var M = [
134+
[ 0.7977604896723027, 0.13518583717574031, 0.0313493495815248 ],
135+
[ 0.2880711282292934, 0.7118432178101014, 0.00008565396060525902 ],
136+
[ 0.0, 0.0, 0.8251046025104601 ]
137+
];
138138

139-
return math.multiply(M, rgb).valueOf();
139+
return multiplyMatrices(M, rgb);
140140
}
141141

142142
function XYZ_to_lin_ProPhoto(XYZ) {
143143
// convert XYZ to linear-light prophoto-rgb
144-
var M = math.matrix([
145-
[ 1.3457989731028281, -0.25558010007997534, -0.05110628506753401 ],
146-
[ -0.5446224939028347, 1.5082327413132781, 0.02053603239147973 ],
147-
[ 0.0, 0.0, 1.2119675456389454 ]
148-
]);
144+
var M = [
145+
[ 1.3457989731028281, -0.25558010007997534, -0.05110628506753401 ],
146+
[ -0.5446224939028347, 1.5082327413132781, 0.02053603239147973 ],
147+
[ 0.0, 0.0, 1.2119675456389454 ]
148+
];
149149

150-
return math.multiply(M, XYZ).valueOf();
150+
return multiplyMatrices(M, XYZ);
151151
}
152152

153153
// a98-rgb functions
@@ -176,24 +176,24 @@ function lin_a98rgb_to_XYZ(rgb) {
176176
// but the vaues below were calculated from first principles
177177
// from the chromaticity coordinates of R G B W
178178
// see matrixmaker.html
179-
var M = math.matrix([
180-
[ 0.5766690429101305, 0.1855582379065463, 0.1882286462349947 ],
181-
[ 0.29734497525053605, 0.6273635662554661, 0.07529145849399788 ],
182-
[ 0.02703136138641234, 0.07068885253582723, 0.9913375368376388 ]
183-
]);
179+
var M = [
180+
[ 0.5766690429101305, 0.1855582379065463, 0.1882286462349947 ],
181+
[ 0.29734497525053605, 0.6273635662554661, 0.07529145849399788 ],
182+
[ 0.02703136138641234, 0.07068885253582723, 0.9913375368376388 ]
183+
];
184184

185-
return math.multiply(M, rgb).valueOf();
185+
return multiplyMatrices(M, rgb);
186186
}
187187

188188
function XYZ_to_lin_a98rgb(XYZ) {
189189
// convert XYZ to linear-light a98-rgb
190-
var M = math.matrix([
191-
[ 2.0415879038107465, -0.5650069742788596, -0.34473135077832956 ],
192-
[ -0.9692436362808795, 1.8759675015077202, 0.04155505740717557 ],
193-
[ 0.013444280632031142, -0.11836239223101838, 1.0151749943912054 ]
194-
]);
190+
var M = [
191+
[ 2.0415879038107465, -0.5650069742788596, -0.34473135077832956 ],
192+
[ -0.9692436362808795, 1.8759675015077202, 0.04155505740717557 ],
193+
[ 0.013444280632031142, -0.11836239223101838, 1.0151749943912054 ]
194+
];
195195

196-
return math.multiply(M, XYZ).valueOf();
196+
return multiplyMatrices(M, XYZ);
197197
}
198198

199199
//Rec. 2020-related functions
@@ -233,25 +233,25 @@ function lin_2020_to_XYZ(rgb) {
233233
// convert an array of linear-light rec2020 values to CIE XYZ
234234
// using D65 (no chromatic adaptation)
235235
// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
236-
var M = math.matrix([
236+
var M = [
237237
[0.6369580483012914, 0.14461690358620832, 0.1688809751641721],
238238
[0.2627002120112671, 0.6779980715188708, 0.05930171646986196],
239239
[0.000000000000000, 0.028072693049087428, 1.060985057710791]
240-
]);
240+
];
241241
// 0 is actually calculated as 4.994106574466076e-17
242242

243-
return math.multiply(M, rgb).valueOf();
243+
return multiplyMatrices(M, rgb);
244244
}
245245

246246
function XYZ_to_lin_2020(XYZ) {
247247
// convert XYZ to linear-light rec2020
248-
var M = math.matrix([
248+
var M = [
249249
[1.7166511879712674, -0.35567078377639233, -0.25336628137365974],
250250
[-0.6666843518324892, 1.6164812366349395, 0.01576854581391113],
251251
[0.017639857445310783, -0.042770613257808524, 0.9421031212354738]
252-
]);
252+
];
253253

254-
return math.multiply(M, XYZ).valueOf();
254+
return multiplyMatrices(M, XYZ);
255255
}
256256

257257
// Chromatic adaptation
@@ -263,24 +263,24 @@ function D65_to_D50(XYZ) {
263263
// - scale components from one reference white to another
264264
// - convert back to XYZ
265265
// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
266-
var M = math.matrix([
266+
var M = [
267267
[ 1.0478112, 0.0228866, -0.0501270],
268268
[ 0.0295424, 0.9904844, -0.0170491],
269269
[-0.0092345, 0.0150436, 0.7521316]
270-
]);
270+
];
271271

272-
return math.multiply(M, XYZ).valueOf();
272+
return multiplyMatrices(M, XYZ);
273273
}
274274

275275
function D50_to_D65(XYZ) {
276276
// Bradford chromatic adaptation from D50 to D65
277-
var M = math.matrix([
277+
var M = [
278278
[ 0.9555766, -0.0230393, 0.0631636],
279279
[-0.0282895, 1.0099416, 0.0210077],
280280
[ 0.0122982, -0.0204830, 1.3299098]
281-
]);
281+
];
282282

283-
return math.multiply(M, XYZ).valueOf();
283+
return multiplyMatrices(M, XYZ);
284284
}
285285

286286
// Lab and LCH
@@ -347,5 +347,3 @@ function LCH_to_Lab(LCH) {
347347
LCH[1] * Math.sin(LCH[2] * Math.PI / 180) // b
348348
];
349349
}
350-
351-

css-color-4/multiply-matrices.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Simple matrix (and vector) multiplication
3+
* Warning: No error handling for incompatible dimensions!
4+
* @author Lea Verou 2020 MIT License
5+
*/
6+
// A is m x n. B is n x p. product is m x p.
7+
function multiplyMatrices(A, B) {
8+
let m = A.length;
9+
10+
if (!Array.isArray(A[0])) {
11+
// A is vector, convert to [[a, b, c, ...]]
12+
A = [A];
13+
}
14+
15+
if (!Array.isArray(B[0])) {
16+
// B is vector, convert to [[a], [b], [c], ...]]
17+
B = B.map(x => [x]);
18+
}
19+
20+
let p = B[0].length;
21+
let B_cols = B[0].map((_, i) => B.map(x => x[i])); // transpose B
22+
let product = A.map(row => B_cols.map(col => {
23+
if (!Array.isArray(row)) {
24+
return col.reduce((a, c) => a + c * row, 0);
25+
}
26+
27+
return row.reduce((a, c, i) => a + c * (col[i] || 0), 0);
28+
}));
29+
30+
if (m === 1) {
31+
product = product[0]; // Avoid [[a, b, c, ...]]
32+
}
33+
34+
if (p === 1) {
35+
return product.map(x => x[0]); // Avoid [[a], [b], [c], ...]]
36+
}
37+
38+
return product;
39+
}

css-color-4/tests.html

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
5+
<meta charset="UTF-8">
6+
<title>Color Conversion Tests</title>
7+
<link rel="stylesheet" href="https://test.mavo.io/style.css" />
8+
<style>
9+
.matrix {
10+
white-space: pre;
11+
font-family: Consolas, Monaco, monospace;
12+
}
13+
</style>
14+
<script src="https://blissfuljs.com/bliss.shy.js"></script>
15+
<script src="https://test.mavo.io/test.js"></script>
16+
17+
<!-- <script src="math.js"></script> -->
18+
<script src="conversions.js"></script>
19+
<script src="utilities.js"></script>
20+
<script src="multiply-matrices.js"></script>
21+
<script src="tests.js"></script>
22+
<script>
23+
function reftest(title, conversion, args, result) {
24+
let ref, test;
25+
26+
try {
27+
ref = `<pre>${conversion(...args)}</pre>`;
28+
}
29+
catch (e) {
30+
ref = e;
31+
throw e;
32+
}
33+
34+
document.currentScript.after(
35+
$.create("tr", {
36+
title,
37+
contents: [
38+
{tag: "td", innerHTML: ref },
39+
{tag: "td", innerHTML: `<pre>${result}</pre>`}
40+
]
41+
})
42+
);
43+
}
44+
</script>
45+
46+
</head>
47+
<body>
48+
49+
<h1>Color Conversion Tests</h1>
50+
51+
<section>
52+
<h1>sRGB_to_LCH</h1>
53+
<table class="reftest">
54+
<tbody>
55+
<script>
56+
57+
58+
for (let test of tests) {
59+
reftest(`sRGB ${test.RGB} to LCH`, sRGB_to_LCH, [test.RGB], test.LCH);
60+
reftest(`sRGB ${test.RGB} to XYZ`, a => lin_sRGB_to_XYZ(lin_sRGB(a)), [test.RGB], test.XYZ);
61+
reftest(`P3 ${test.RGB} to LCH`, P3_to_LCH, [test.RGB], test.P3_to_LCH);
62+
reftest(`P3 ${test.RGB} to XYZ`, a => lin_P3_to_XYZ(lin_P3(a)), [test.RGB], test.P3_to_XYZ);
63+
}
64+
</script>
65+
<!-- <script>
66+
reftest("sRGB 0,0,0 to LCH", sRGB_to_LCH, [[0, 0, 0]], [0, 0, 0])
67+
</script> -->
68+
</tbody>
69+
70+
71+
</table>
72+
</section>
73+
74+
75+
</body>
76+
</html>

0 commit comments

Comments
 (0)