@texel/color 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -7
- package/package.json +3 -2
- package/test/bench-culori.js +115 -0
- package/test/bench-size.js +10 -1
package/README.md
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
A minimal and modern color library for JavaScript.
|
|
5
|
+
A minimal and modern color library for JavaScript. Especially useful for real-time applications, generative art, and graphics on the web.
|
|
6
6
|
|
|
7
7
|
- Features: fast color conversion, color difference, gamut mapping, and serialization
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
8
|
+
- Optimized for speed: approx 20-125 times faster than [Colorjs.io](https://colorjs.io/) (see [benchmarks](#benchmarks))
|
|
9
|
+
- Optimized for low memory and minimal allocations: no arrays or objects are created within conversion and gamut mapping functions
|
|
10
|
+
- Optimized for compact bundles: zero dependencies, and unused color spaces can be automatically tree-shaked away for small sizes (e.g. ~3.5kb minified if you only require OKLCH to sRGB conversion)
|
|
11
|
+
- Optimized for accuracy: [high precision](#accuracy) color space matrices
|
|
12
12
|
- Focused on a minimal and modern set of color spaces:
|
|
13
13
|
- xyz (D65), xyz-d50, oklab, oklch, okhsv, okhsl, srgb, srgb-linear, display-p3, display-p3-linear, rec2020, rec2020-linear, a98-rgb, a98-rgb-linear, prophoto-rgb, prophoto-rgb-linear
|
|
14
14
|
|
|
@@ -307,7 +307,9 @@ sRGB.toBase(in_sRGB, out_linear_sRGB); // linear to gamma transfer function
|
|
|
307
307
|
|
|
308
308
|
### Why another library?
|
|
309
309
|
|
|
310
|
-
[Colorjs](https://colorjs.io/) is fantastic and perhaps the current leading standard in JavaScript, but it's not very practical for creative coding and real-time web applications, where the requirements are often (1) leaner codebases, (2) highly optimized, and (3) minimal GC thrashing.
|
|
310
|
+
[Colorjs](https://colorjs.io/) is fantastic and perhaps the current leading standard in JavaScript, but it's not very practical for creative coding and real-time web applications, where the requirements are often (1) leaner codebases, (2) highly optimized, and (3) minimal GC thrashing.
|
|
311
|
+
|
|
312
|
+
Colorjs, and simialrly, [Culori](https://culorijs.org/)), are focused on matching CSS spec, which means it will very likely continue to grow in complexity over time, and performance will often be marred (for example, `@texel/color` cusp intersection gamut mapping is ~125 times faster than Colorjs and ~60 times faster than culori).
|
|
311
313
|
|
|
312
314
|
There are many other options such as [color-space](https://www.npmjs.com/package/color-space) or [color-convert](https://www.npmjs.com/package/color-convert), however, these do not support modern spacse such as OKLab and OKHSL, and/or have dubious levels of accuracy (many libraries, for example, do not distinguish between D50 and D65 whitepoints in XYZ).
|
|
313
315
|
|
|
@@ -337,6 +339,7 @@ If you think the matrices or accuracy could be improved, please open a PR.
|
|
|
337
339
|
There are a few benchmarks inside [test](./test):
|
|
338
340
|
|
|
339
341
|
- [bench-colorjs.js](./test/bench-colorjs.js) - run with `npm run bench` to compare against colorjs
|
|
342
|
+
- [bench-culori.js](./test/bench-colorjs.js) - run with node to compare against [culori](https://culorijs.org/)
|
|
340
343
|
- [bench-node.js](./test/bench-node.js) - run with `npm run bench:node` to get a node profile
|
|
341
344
|
- [bench-size.js](./test/bench-size.js) - run with `npm run bench:size` to get a small bundle size with esbuild
|
|
342
345
|
|
|
@@ -359,6 +362,21 @@ Ours: 82.04 ms
|
|
|
359
362
|
Speedup: 23.6x faster
|
|
360
363
|
```
|
|
361
364
|
|
|
365
|
+
And against culori:
|
|
366
|
+
|
|
367
|
+
```
|
|
368
|
+
Testing with input type: Random Samling in OKLab L Planes
|
|
369
|
+
Conversion OKLCH to P3 --
|
|
370
|
+
Culori: 43.30 ms
|
|
371
|
+
Ours: 12.83 ms
|
|
372
|
+
Speedup: 3.4x faster
|
|
373
|
+
|
|
374
|
+
Gamut Mapping OKLCH to P3 Gamut --
|
|
375
|
+
Culori: 1588.62 ms
|
|
376
|
+
Ours: 23.05 ms
|
|
377
|
+
Speedup: 68.9x faster
|
|
378
|
+
```
|
|
379
|
+
|
|
362
380
|
### Running Locally
|
|
363
381
|
|
|
364
382
|
Clone, `npm install`, then `npm run` to list the available scripts, or `npm t` to run the tests.
|
|
@@ -373,4 +391,4 @@ This library was made possible due to the excellent prior work by many developer
|
|
|
373
391
|
|
|
374
392
|
## License
|
|
375
393
|
|
|
376
|
-
MIT, see [LICENSE.md](
|
|
394
|
+
MIT, see [LICENSE.md](https://github.com/texel-org/color/blob/main/LICENSE.md) for details.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@texel/color",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "a minimal and modern color library",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"license": "MIT",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"canvas-sketch": "^0.7.7",
|
|
14
14
|
"canvas-sketch-cli": "^1.11.21",
|
|
15
15
|
"colorjs.io": "^0.5.2",
|
|
16
|
+
"culori": "^4.0.1",
|
|
16
17
|
"esbuild": "^0.23.0",
|
|
17
18
|
"faucet": "^0.0.4",
|
|
18
19
|
"pako": "^2.1.0",
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
convert,
|
|
3
|
+
OKLCH,
|
|
4
|
+
sRGB,
|
|
5
|
+
sRGBGamut,
|
|
6
|
+
listColorSpaces,
|
|
7
|
+
DisplayP3Gamut,
|
|
8
|
+
DisplayP3,
|
|
9
|
+
gamutMapOKLCH,
|
|
10
|
+
constrainAngle,
|
|
11
|
+
findCuspOKLCH,
|
|
12
|
+
degToRad,
|
|
13
|
+
MapToCuspL,
|
|
14
|
+
OKHSL,
|
|
15
|
+
OKLab,
|
|
16
|
+
OKHSLToOKLab,
|
|
17
|
+
} from "../src/index.js";
|
|
18
|
+
|
|
19
|
+
import { p3, toGamut, oklch, okhsl, converter } from "culori";
|
|
20
|
+
|
|
21
|
+
const N = 256 * 256;
|
|
22
|
+
|
|
23
|
+
// get N OKLCH in-gamut pixels by sampling uniformly from OKHSL cube
|
|
24
|
+
const oklchPixelsInGamut = Array(N)
|
|
25
|
+
.fill()
|
|
26
|
+
.map((_, i, lst) => {
|
|
27
|
+
const t = i / (lst.length - 1);
|
|
28
|
+
// note if we use the standard OKHSL space, it is bound to sRGB gamut...
|
|
29
|
+
// so instead we use the OKHSLToOKLab function and pass P3 gamut
|
|
30
|
+
const okhsl = [t, t, t * 360];
|
|
31
|
+
const oklab = OKHSLToOKLab(okhsl, DisplayP3Gamut);
|
|
32
|
+
return convert(oklab, OKLab, OKLCH);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// get N OKLCH pixels by sampling OKLab, many will be out of srgb gamut
|
|
36
|
+
const oklchPixelsRandom = Array(N)
|
|
37
|
+
.fill()
|
|
38
|
+
.map((_, i, lst) => {
|
|
39
|
+
const t = i / (lst.length - 1);
|
|
40
|
+
return convert([t, t * 2 - 1, t * 2 - 1], OKLab, OKLCH);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const toP3Gamut = toGamut("p3", "oklch");
|
|
44
|
+
// same perf as p3() it seems
|
|
45
|
+
// const p3Converter = converter("p3");
|
|
46
|
+
|
|
47
|
+
test(oklchPixelsInGamut, "Random Samling in P3 Gamut");
|
|
48
|
+
test(oklchPixelsRandom, "Random Samling in OKLab L Planes");
|
|
49
|
+
|
|
50
|
+
function test(inputPixelsOKLCH, label) {
|
|
51
|
+
console.log("Testing with input type: %s", label);
|
|
52
|
+
const culoriInputsOKLCH = oklchPixelsRandom.map(([l, c, h]) => {
|
|
53
|
+
return {
|
|
54
|
+
mode: "oklch",
|
|
55
|
+
l,
|
|
56
|
+
c,
|
|
57
|
+
h,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
let now, elapsedCulori, elapsedOurs;
|
|
62
|
+
let tmp = [0, 0, 0];
|
|
63
|
+
|
|
64
|
+
//// conversion
|
|
65
|
+
|
|
66
|
+
tmp = [0, 0, 0];
|
|
67
|
+
now = performance.now();
|
|
68
|
+
for (let oklch of inputPixelsOKLCH) {
|
|
69
|
+
convert(oklch, OKLCH, DisplayP3, tmp);
|
|
70
|
+
}
|
|
71
|
+
elapsedOurs = performance.now() - now;
|
|
72
|
+
|
|
73
|
+
now = performance.now();
|
|
74
|
+
for (let oklchColor of culoriInputsOKLCH) {
|
|
75
|
+
p3(oklchColor);
|
|
76
|
+
|
|
77
|
+
// same perf
|
|
78
|
+
// p3Converter(oklchColor);
|
|
79
|
+
}
|
|
80
|
+
elapsedCulori = performance.now() - now;
|
|
81
|
+
print("Conversion OKLCH to P3");
|
|
82
|
+
|
|
83
|
+
//// gamut
|
|
84
|
+
|
|
85
|
+
tmp = [0, 0, 0];
|
|
86
|
+
now = performance.now();
|
|
87
|
+
for (let oklch of inputPixelsOKLCH) {
|
|
88
|
+
gamutMapOKLCH(oklch, DisplayP3Gamut, DisplayP3, tmp);
|
|
89
|
+
}
|
|
90
|
+
elapsedOurs = performance.now() - now;
|
|
91
|
+
|
|
92
|
+
now = performance.now();
|
|
93
|
+
for (let oklchColor of culoriInputsOKLCH) {
|
|
94
|
+
toP3Gamut(oklchColor);
|
|
95
|
+
}
|
|
96
|
+
elapsedCulori = performance.now() - now;
|
|
97
|
+
print("Gamut Mapping OKLCH to P3 Gamut");
|
|
98
|
+
|
|
99
|
+
function print(label) {
|
|
100
|
+
console.log("%s --", label);
|
|
101
|
+
console.log("Culori: %s ms", elapsedCulori.toFixed(2));
|
|
102
|
+
console.log("Ours: %s ms", elapsedOurs.toFixed(2));
|
|
103
|
+
if (elapsedCulori > elapsedOurs)
|
|
104
|
+
console.log(
|
|
105
|
+
"Speedup: %sx faster",
|
|
106
|
+
(elapsedCulori / elapsedOurs).toFixed(1)
|
|
107
|
+
);
|
|
108
|
+
else
|
|
109
|
+
console.log(
|
|
110
|
+
"Slowdown: %sx slower",
|
|
111
|
+
(elapsedOurs / elapsedCulori).toFixed(1)
|
|
112
|
+
);
|
|
113
|
+
console.log();
|
|
114
|
+
}
|
|
115
|
+
}
|
package/test/bench-size.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
// To test @texel/color (~3.5 kb)
|
|
2
|
+
import * as colors from "../src/index.js";
|
|
2
3
|
const rgb = colors.convert([0.5, 0.15, 30], colors.OKLCH, colors.sRGB);
|
|
3
4
|
console.log(rgb);
|
|
5
|
+
|
|
6
|
+
// To test colorjs.io (~55.3 kb)
|
|
7
|
+
// import Color from "colorjs.io";
|
|
8
|
+
// console.log(new Color("oklch", [0.5, 0.15, 30]).to("srgb").coords);
|
|
9
|
+
|
|
10
|
+
// To test Culori (~43.2 kb)
|
|
11
|
+
// import { rgb } from "culori";
|
|
12
|
+
// console.log(rgb({ mode: "oklch", l: 0.5, c: 0.15, h: 30 }));
|