@texel/color 1.1.2 → 1.1.4
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 +1 -1
- package/package.json +1 -1
- package/src/gamut.js +4 -2
- package/test/almost-equal.js +0 -15
- package/test/banner.png +0 -0
- package/test/bench-colorjs.js +0 -227
- package/test/bench-culori.js +0 -148
- package/test/bench-node.js +0 -51
- package/test/bench-size.js +0 -12
- package/test/canvas-graph.js +0 -212
- package/test/check-gamut-epsilon.js +0 -136
- package/test/colorjs-fn.js +0 -34
- package/test/example-interpolation.js +0 -107
- package/test/logo.js +0 -112
- package/test/logo.png +0 -0
- package/test/profiles/DisplayP3.icc +0 -0
- package/test/spaces/hsl.js +0 -87
- package/test/spaces/lab.js +0 -60
- package/test/test-colorjs.js +0 -86
- package/test/test-other-spaces.js +0 -115
- package/test/test.js +0 -451
- package/tools/__pycache__/calc_oklab_matrices.cpython-311.pyc +0 -0
- package/tools/calc_oklab_matrices.py +0 -233
- package/tools/print_matrices.py +0 -509
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ The return value is the new coordinates in the destination space; such as `[r,g,
|
|
|
85
85
|
|
|
86
86
|
#### `output = gamutMapOKLCH(oklch, gamut = sRGBGamut, targetSpace = gamut.space, out = [0, 0, 0], mapping = MapToCuspL, [cusp])`
|
|
87
87
|
|
|
88
|
-
Performs fast gamut mapping in OKLCH as [described by Björn
|
|
88
|
+
Performs fast gamut mapping in OKLCH as [described by Björn Ottosson](https://bottosson.github.io/posts/gamutclipping/) (2021). This takes an input `[l,c,h]` coords in OKLCH space, and ensures the final result will lie within the specified color `gamut` (default `sRGBGamut`). You can further specify a different target space (which default's to the gamut's space), for example to get a linear-light sRGB and avoid the transfer function, or to keep the result in OKLCH:
|
|
89
89
|
|
|
90
90
|
```js
|
|
91
91
|
import { gamutMapOKLCH, sRGBGamut, sRGBLinear, OKLCH } from "@texel/color";
|
package/package.json
CHANGED
package/src/gamut.js
CHANGED
|
@@ -182,13 +182,15 @@ export const findGamutIntersectionOKLCH = (a, b, l1, c1, l0, cusp, gamut) => {
|
|
|
182
182
|
|
|
183
183
|
// Find the intersection for upper and lower half separately
|
|
184
184
|
if ((l1 - l0) * cusp[1] - (cusp[0] - l0) * c1 <= 0.0) {
|
|
185
|
+
const denom = (c1 * cusp[0] + cusp[1] * (l0 - l1));
|
|
185
186
|
// Lower half
|
|
186
|
-
t =
|
|
187
|
+
t = denom === 0 ? 0 : (cusp[1] * l0) / denom;
|
|
187
188
|
} else {
|
|
188
189
|
// Upper half
|
|
189
190
|
|
|
190
191
|
// First intersect with triangle
|
|
191
|
-
|
|
192
|
+
const denom = (c1 * (cusp[0] - 1.0) + cusp[1] * (l0 - l1));
|
|
193
|
+
t = denom === 0 ? 0 : (cusp[1] * (l0 - 1.0)) / denom;
|
|
192
194
|
|
|
193
195
|
// Then one step Halley's method
|
|
194
196
|
let dl = l1 - l0;
|
package/test/almost-equal.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
const EPSILON = Math.pow(2, -33); // ~= 0.0000000001
|
|
2
|
-
|
|
3
|
-
export default function arrayAlmostEqual(a, b, tolerance = EPSILON) {
|
|
4
|
-
if (!a || !b) return false;
|
|
5
|
-
if (a.length !== b.length) return false;
|
|
6
|
-
for (let i = 0; i < a.length; i++) {
|
|
7
|
-
const p0 = a[i];
|
|
8
|
-
const p1 = b[i];
|
|
9
|
-
if (p0 !== p1) {
|
|
10
|
-
const diff = Math.abs(p1 - p0);
|
|
11
|
-
if (diff > tolerance) return false;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
return true;
|
|
15
|
-
}
|
package/test/banner.png
DELETED
|
Binary file
|
package/test/bench-colorjs.js
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import Color from "colorjs.io";
|
|
2
|
-
import {
|
|
3
|
-
sRGB as sRGB_ColorJS,
|
|
4
|
-
OKLCH as OKLCH_ColorJS,
|
|
5
|
-
P3 as DisplayP3_ColorJS,
|
|
6
|
-
toGamut,
|
|
7
|
-
} from "colorjs.io/fn";
|
|
8
|
-
import {
|
|
9
|
-
convert,
|
|
10
|
-
OKLCH,
|
|
11
|
-
OKLab,
|
|
12
|
-
sRGB,
|
|
13
|
-
sRGBGamut,
|
|
14
|
-
DisplayP3Gamut,
|
|
15
|
-
DisplayP3,
|
|
16
|
-
gamutMapOKLCH,
|
|
17
|
-
constrainAngle,
|
|
18
|
-
findCuspOKLCH,
|
|
19
|
-
degToRad,
|
|
20
|
-
MapToCuspL,
|
|
21
|
-
} from "../src/index.js";
|
|
22
|
-
|
|
23
|
-
import { getSupportedColorJSSpaces } from "./colorjs-fn.js";
|
|
24
|
-
|
|
25
|
-
const supportedSpaces = getSupportedColorJSSpaces();
|
|
26
|
-
|
|
27
|
-
// @texel/color space interfaces
|
|
28
|
-
const spaces = supportedSpaces.map((s) => s.space);
|
|
29
|
-
|
|
30
|
-
// Colorjs.io space interfaces & IDs
|
|
31
|
-
const colorJSSpaces = supportedSpaces.map((s) => s.colorJSSpace);
|
|
32
|
-
const colorJSSpaceIDs = supportedSpaces.map((s) => s.colorJSSpace.id);
|
|
33
|
-
|
|
34
|
-
const N = 128 * 128;
|
|
35
|
-
|
|
36
|
-
// sampling variables within OKLab and converting to OKLCH
|
|
37
|
-
const vecs = Array(N)
|
|
38
|
-
.fill()
|
|
39
|
-
.map((_, i, lst) => {
|
|
40
|
-
const t = i / (lst.length - 1);
|
|
41
|
-
return convert([t, t * 2 - 1, t * 2 - 1], OKLab, OKLCH);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const tmp = [0, 0, 0];
|
|
45
|
-
|
|
46
|
-
let now, elapsedColorjs, elapsedOurs;
|
|
47
|
-
|
|
48
|
-
//// OKLCH to sRGB with gamut mapping (direct path)
|
|
49
|
-
|
|
50
|
-
const hueCusps = Array(360).fill(null);
|
|
51
|
-
const oklchVecs = Array(512 * 256)
|
|
52
|
-
.fill()
|
|
53
|
-
.map((_, i, lst) => {
|
|
54
|
-
const t0 = i / (lst.length - 1);
|
|
55
|
-
const t1 = i / lst.length;
|
|
56
|
-
const H = constrainAngle(Math.round(t1 * 360));
|
|
57
|
-
if (!hueCusps[H]) {
|
|
58
|
-
const Hr = degToRad(H);
|
|
59
|
-
const a = Math.cos(Hr);
|
|
60
|
-
const b = Math.sin(Hr);
|
|
61
|
-
hueCusps[H] = findCuspOKLCH(a, b, sRGBGamut);
|
|
62
|
-
}
|
|
63
|
-
return [t0, t0, H];
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
compare(
|
|
67
|
-
"conversion (Colorjs.io procedural API)",
|
|
68
|
-
() => {
|
|
69
|
-
for (let vec of vecs) {
|
|
70
|
-
for (let i = 0; i < colorJSSpaces.length; i++) {
|
|
71
|
-
for (let j = 0; j < colorJSSpaces.length; j++) {
|
|
72
|
-
const a = colorJSSpaces[i];
|
|
73
|
-
const b = colorJSSpaces[j];
|
|
74
|
-
const ret = b.from(a, vec);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
() => {
|
|
80
|
-
for (let vec of vecs) {
|
|
81
|
-
for (let i = 0; i < spaces.length; i++) {
|
|
82
|
-
for (let j = 0; j < spaces.length; j++) {
|
|
83
|
-
const a = spaces[i];
|
|
84
|
-
const b = spaces[j];
|
|
85
|
-
convert(vec, a, b, tmp);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
compare(
|
|
93
|
-
"conversion (Colorjs.io main API)",
|
|
94
|
-
() => {
|
|
95
|
-
for (let vec of vecs) {
|
|
96
|
-
for (let i = 0; i < colorJSSpaceIDs.length; i++) {
|
|
97
|
-
for (let j = 0; j < colorJSSpaceIDs.length; j++) {
|
|
98
|
-
const a = colorJSSpaceIDs[i];
|
|
99
|
-
const b = colorJSSpaceIDs[j];
|
|
100
|
-
new Color(a, vec).to(b);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
|
-
() => {
|
|
106
|
-
for (let vec of vecs) {
|
|
107
|
-
for (let i = 0; i < spaces.length; i++) {
|
|
108
|
-
for (let j = 0; j < spaces.length; j++) {
|
|
109
|
-
const a = spaces[i];
|
|
110
|
-
const b = spaces[j];
|
|
111
|
-
convert(vec, a, b, tmp);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
compare(
|
|
119
|
-
"gamut mapping OKLCH - sRGB (Colorjs.io procedural API)",
|
|
120
|
-
() => {
|
|
121
|
-
let tmpColor = { space: sRGB_ColorJS, coords: [0, 0, 0], alpha: 1 };
|
|
122
|
-
for (let vec of oklchVecs) {
|
|
123
|
-
tmpColor.coords = sRGB_ColorJS.from(OKLCH_ColorJS, vec);
|
|
124
|
-
toGamut(tmpColor);
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
() => {
|
|
128
|
-
for (let vec of oklchVecs) {
|
|
129
|
-
// you can omit the cusp and it will be found on the fly,
|
|
130
|
-
// however the test will run slightly slower
|
|
131
|
-
const cusp = hueCusps[vec[2]];
|
|
132
|
-
gamutMapOKLCH(vec, sRGBGamut, sRGB, tmp, MapToCuspL, cusp);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
compare(
|
|
138
|
-
"gamut mapping OKLCH - sRGB (Colorjs.io main API)",
|
|
139
|
-
() => {
|
|
140
|
-
for (let vec of oklchVecs) {
|
|
141
|
-
new Color("oklch", vec)
|
|
142
|
-
.to("srgb")
|
|
143
|
-
.toGamut({ space: "srgb", method: "css" });
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
() => {
|
|
147
|
-
for (let vec of oklchVecs) {
|
|
148
|
-
// you can omit the cusp and it will be found on the fly,
|
|
149
|
-
// however the test will run slightly slower
|
|
150
|
-
const cusp = hueCusps[vec[2]];
|
|
151
|
-
gamutMapOKLCH(vec, sRGBGamut, sRGB, tmp, MapToCuspL, cusp);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
compare(
|
|
157
|
-
"gamut mapping all spaces to P3 (Colorjs.io procedural API)",
|
|
158
|
-
() => {
|
|
159
|
-
let tmpColor = { space: DisplayP3_ColorJS, coords: [0, 0, 0], alpha: 1 };
|
|
160
|
-
for (let vec of vecs) {
|
|
161
|
-
for (let i = 0; i < colorJSSpaces.length; i++) {
|
|
162
|
-
const a = colorJSSpaces[i];
|
|
163
|
-
tmpColor.coords = DisplayP3_ColorJS.from(a, vec);
|
|
164
|
-
toGamut(tmpColor);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
},
|
|
168
|
-
() => {
|
|
169
|
-
for (let vec of vecs) {
|
|
170
|
-
for (let i = 0; i < spaces.length; i++) {
|
|
171
|
-
const a = spaces[i];
|
|
172
|
-
convert(vec, a, OKLCH, tmp);
|
|
173
|
-
gamutMapOKLCH(tmp, DisplayP3Gamut, DisplayP3, tmp);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
compare(
|
|
180
|
-
"gamut mapping all spaces to P3 (Colorjs.io main API)",
|
|
181
|
-
() => {
|
|
182
|
-
for (let vec of vecs) {
|
|
183
|
-
for (let i = 0; i < colorJSSpaceIDs.length; i++) {
|
|
184
|
-
const a = colorJSSpaceIDs[i];
|
|
185
|
-
new Color(a, vec).to("p3").toGamut({ space: "p3", method: "css" });
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
() => {
|
|
190
|
-
for (let vec of vecs) {
|
|
191
|
-
for (let i = 0; i < spaces.length; i++) {
|
|
192
|
-
const a = spaces[i];
|
|
193
|
-
convert(vec, a, OKLCH, tmp);
|
|
194
|
-
gamutMapOKLCH(tmp, DisplayP3Gamut, DisplayP3, tmp);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
function print(label) {
|
|
201
|
-
console.log("%s --", label);
|
|
202
|
-
console.log("Colorjs.io: %s ms", elapsedColorjs.toFixed(2));
|
|
203
|
-
console.log("Ours: %s ms", elapsedOurs.toFixed(2));
|
|
204
|
-
if (elapsedColorjs > elapsedOurs)
|
|
205
|
-
console.log(
|
|
206
|
-
"Speedup: %sx faster",
|
|
207
|
-
(elapsedColorjs / elapsedOurs).toFixed(1)
|
|
208
|
-
);
|
|
209
|
-
else
|
|
210
|
-
console.log(
|
|
211
|
-
"Slowdown: %sx slower",
|
|
212
|
-
(elapsedOurs / elapsedColorjs).toFixed(1)
|
|
213
|
-
);
|
|
214
|
-
console.log();
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function compare(label, colorJSFn, ourFn) {
|
|
218
|
-
now = performance.now();
|
|
219
|
-
colorJSFn();
|
|
220
|
-
elapsedColorjs = performance.now() - now;
|
|
221
|
-
|
|
222
|
-
now = performance.now();
|
|
223
|
-
ourFn();
|
|
224
|
-
elapsedOurs = performance.now() - now;
|
|
225
|
-
|
|
226
|
-
print(label);
|
|
227
|
-
}
|
package/test/bench-culori.js
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
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
|
-
const gamut = DisplayP3Gamut;
|
|
23
|
-
const target = gamut.space;
|
|
24
|
-
|
|
25
|
-
// get N OKLCH in-gamut pixels by sampling uniformly from OKHSL cube
|
|
26
|
-
const oklchPixelsInGamut = Array(N)
|
|
27
|
-
.fill()
|
|
28
|
-
.map((_, i, lst) => {
|
|
29
|
-
const t = i / (lst.length - 1);
|
|
30
|
-
// note if we use the standard OKHSL space, it is bound to sRGB gamut...
|
|
31
|
-
// so instead we use the OKHSLToOKLab function and pass P3 gamut
|
|
32
|
-
const okhsl = [t * 360, t, t];
|
|
33
|
-
const oklab = OKHSLToOKLab(okhsl, gamut);
|
|
34
|
-
return convert(oklab, OKLab, OKLCH);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// get N OKLCH pixels by sampling OKLab, many will be out of srgb gamut
|
|
38
|
-
const oklchPixelsRandom = Array(N)
|
|
39
|
-
.fill()
|
|
40
|
-
.map((_, i, lst) => {
|
|
41
|
-
const t = i / (lst.length - 1);
|
|
42
|
-
return convert([t, t * 2 - 1, t * 2 - 1], OKLab, OKLCH);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const toP3Gamut = toGamut("p3", "oklch");
|
|
46
|
-
// same perf as p3() it seems
|
|
47
|
-
// const p3Converter = converter("p3");
|
|
48
|
-
|
|
49
|
-
test(oklchPixelsInGamut, "Random Sampling in P3 Gamut");
|
|
50
|
-
test(oklchPixelsRandom, "Random Sampling in OKLab L Planes");
|
|
51
|
-
test(oklchPixelsRandom, "Random Sampling in OKLab L Planes", true);
|
|
52
|
-
|
|
53
|
-
function test(inputPixelsOKLCH, label, fixedCusp) {
|
|
54
|
-
let cuspMap;
|
|
55
|
-
if (fixedCusp) {
|
|
56
|
-
cuspMap = new Array(360).fill(null);
|
|
57
|
-
inputPixelsOKLCH = inputPixelsOKLCH.map((oklch) => {
|
|
58
|
-
const H = constrainAngle(Math.round(oklch[2]));
|
|
59
|
-
oklch = oklch.slice();
|
|
60
|
-
oklch[2] = H;
|
|
61
|
-
if (!cuspMap[H]) {
|
|
62
|
-
const Hr = degToRad(H);
|
|
63
|
-
const a = Math.cos(Hr);
|
|
64
|
-
const b = Math.sin(Hr);
|
|
65
|
-
cuspMap[H] = findCuspOKLCH(a, b, gamut);
|
|
66
|
-
}
|
|
67
|
-
return oklch;
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
console.log(
|
|
72
|
-
"Testing with input type: %s%s",
|
|
73
|
-
label,
|
|
74
|
-
fixedCusp ? " (Fixed Cusp)" : ""
|
|
75
|
-
);
|
|
76
|
-
const culoriInputsOKLCH = inputPixelsOKLCH.map(([l, c, h]) => {
|
|
77
|
-
return {
|
|
78
|
-
mode: "oklch",
|
|
79
|
-
l,
|
|
80
|
-
c,
|
|
81
|
-
h,
|
|
82
|
-
};
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
let now, elapsedCulori, elapsedOurs;
|
|
86
|
-
let tmp = [0, 0, 0];
|
|
87
|
-
|
|
88
|
-
//// conversion
|
|
89
|
-
|
|
90
|
-
tmp = [0, 0, 0];
|
|
91
|
-
now = performance.now();
|
|
92
|
-
for (let oklch of inputPixelsOKLCH) {
|
|
93
|
-
convert(oklch, OKLCH, target, tmp);
|
|
94
|
-
}
|
|
95
|
-
elapsedOurs = performance.now() - now;
|
|
96
|
-
|
|
97
|
-
now = performance.now();
|
|
98
|
-
for (let oklchColor of culoriInputsOKLCH) {
|
|
99
|
-
p3(oklchColor);
|
|
100
|
-
|
|
101
|
-
// same perf ?
|
|
102
|
-
// p3Converter(oklchColor);
|
|
103
|
-
}
|
|
104
|
-
elapsedCulori = performance.now() - now;
|
|
105
|
-
print("Conversion OKLCH to P3");
|
|
106
|
-
|
|
107
|
-
//// gamut
|
|
108
|
-
|
|
109
|
-
tmp = [0, 0, 0];
|
|
110
|
-
if (fixedCusp && cuspMap) {
|
|
111
|
-
now = performance.now();
|
|
112
|
-
for (let oklch of inputPixelsOKLCH) {
|
|
113
|
-
const cusp = cuspMap[oklch[2]];
|
|
114
|
-
gamutMapOKLCH(oklch, gamut, target, tmp, undefined, cusp);
|
|
115
|
-
}
|
|
116
|
-
elapsedOurs = performance.now() - now;
|
|
117
|
-
} else {
|
|
118
|
-
now = performance.now();
|
|
119
|
-
for (let oklch of inputPixelsOKLCH) {
|
|
120
|
-
gamutMapOKLCH(oklch, gamut, target, tmp);
|
|
121
|
-
}
|
|
122
|
-
elapsedOurs = performance.now() - now;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
now = performance.now();
|
|
126
|
-
for (let oklchColor of culoriInputsOKLCH) {
|
|
127
|
-
toP3Gamut(oklchColor);
|
|
128
|
-
}
|
|
129
|
-
elapsedCulori = performance.now() - now;
|
|
130
|
-
print("Gamut Mapping OKLCH to P3 Gamut");
|
|
131
|
-
|
|
132
|
-
function print(label) {
|
|
133
|
-
console.log("%s --", label);
|
|
134
|
-
console.log("Culori: %s ms", elapsedCulori.toFixed(2));
|
|
135
|
-
console.log("Ours: %s ms", elapsedOurs.toFixed(2));
|
|
136
|
-
if (elapsedCulori > elapsedOurs)
|
|
137
|
-
console.log(
|
|
138
|
-
"Speedup: %sx faster",
|
|
139
|
-
(elapsedCulori / elapsedOurs).toFixed(1)
|
|
140
|
-
);
|
|
141
|
-
else
|
|
142
|
-
console.log(
|
|
143
|
-
"Slowdown: %sx slower",
|
|
144
|
-
(elapsedOurs / elapsedCulori).toFixed(1)
|
|
145
|
-
);
|
|
146
|
-
console.log();
|
|
147
|
-
}
|
|
148
|
-
}
|
package/test/bench-node.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
convert,
|
|
3
|
-
OKLCH,
|
|
4
|
-
sRGB,
|
|
5
|
-
sRGBGamut,
|
|
6
|
-
listColorSpaces,
|
|
7
|
-
gamutMapOKLCH,
|
|
8
|
-
} from "../src/index.js";
|
|
9
|
-
|
|
10
|
-
const spaces = listColorSpaces().filter((f) => !/ok(hsv|hsl)/i.test(f.id));
|
|
11
|
-
|
|
12
|
-
const vecs = Array(128 * 128)
|
|
13
|
-
.fill()
|
|
14
|
-
.map((_, i, lst) => {
|
|
15
|
-
const t = i / (lst.length - 1);
|
|
16
|
-
return (
|
|
17
|
-
Array(3)
|
|
18
|
-
.fill()
|
|
19
|
-
// -0.5 .. 1.5
|
|
20
|
-
.map(() => t + (t * 2 - 1) * 0.5)
|
|
21
|
-
);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const tmp = [0, 0, 0];
|
|
25
|
-
|
|
26
|
-
// console.time("bench");
|
|
27
|
-
|
|
28
|
-
for (let vec of vecs) {
|
|
29
|
-
for (let i = 0; i < spaces.length; i++) {
|
|
30
|
-
for (let j = 0; j < spaces.length; j++) {
|
|
31
|
-
const a = spaces[i];
|
|
32
|
-
const b = spaces[j];
|
|
33
|
-
|
|
34
|
-
// convert A to B
|
|
35
|
-
convert(vec, a, b, tmp);
|
|
36
|
-
// convert B to OKLCH
|
|
37
|
-
convert(tmp, b, OKLCH, tmp);
|
|
38
|
-
// gamut map OKLCH
|
|
39
|
-
gamutMapOKLCH(tmp, sRGBGamut, sRGB, tmp);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// benchmark for EOK
|
|
45
|
-
// for (let i = 0; i < 1000; i++) {
|
|
46
|
-
// for (let vec of vecs) {
|
|
47
|
-
// deltaEOK(vec, [0, 0.25, 1]);
|
|
48
|
-
// }
|
|
49
|
-
// }
|
|
50
|
-
|
|
51
|
-
// console.timeEnd("bench");
|
package/test/bench-size.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// To test @texel/color (~3.5 kb)
|
|
2
|
-
import * as colors from "../src/index.js";
|
|
3
|
-
const rgb = colors.convert([0.5, 0.15, 30], colors.OKLCH, colors.sRGB);
|
|
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 }));
|
package/test/canvas-graph.js
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import canvasSketch from "canvas-sketch";
|
|
2
|
-
import {
|
|
3
|
-
findCuspOKLCH,
|
|
4
|
-
gamutMapOKLCH,
|
|
5
|
-
MapToAdaptiveCuspL,
|
|
6
|
-
A98RGBGamut,
|
|
7
|
-
convert,
|
|
8
|
-
DisplayP3Gamut,
|
|
9
|
-
OKLCH,
|
|
10
|
-
Rec2020Gamut,
|
|
11
|
-
sRGBGamut,
|
|
12
|
-
degToRad,
|
|
13
|
-
constrainAngle,
|
|
14
|
-
floatToByte,
|
|
15
|
-
isRGBInGamut,
|
|
16
|
-
clampedRGB,
|
|
17
|
-
} from "../src/index.js";
|
|
18
|
-
import arrayAlmostEqual from "./almost-equal.js";
|
|
19
|
-
|
|
20
|
-
const settings = {
|
|
21
|
-
dimensions: [768, 768],
|
|
22
|
-
animate: false,
|
|
23
|
-
playbackRate: "throttle",
|
|
24
|
-
fps: 2,
|
|
25
|
-
attributes: {
|
|
26
|
-
colorSpace: "display-p3",
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const sketch = ({ width, height }) => {
|
|
31
|
-
return ({ context, width, height, frame, playhead }) => {
|
|
32
|
-
const { colorSpace = "srgb" } = context.getContextAttributes();
|
|
33
|
-
const gamut = colorSpace === "srgb" ? sRGBGamut : DisplayP3Gamut;
|
|
34
|
-
const mapping = MapToAdaptiveCuspL;
|
|
35
|
-
|
|
36
|
-
context.fillStyle = "gray";
|
|
37
|
-
context.fillRect(0, 0, width, height);
|
|
38
|
-
const H = 264.1;
|
|
39
|
-
// const H = constrainAngle((frame * 45) / 2);
|
|
40
|
-
|
|
41
|
-
// console.time("map");
|
|
42
|
-
// console.profile("map");
|
|
43
|
-
const tmp = [0, 0, 0];
|
|
44
|
-
const pixels = new Uint8ClampedArray(width * height * 4).fill(0xff);
|
|
45
|
-
for (let y = 0; y < height; y++) {
|
|
46
|
-
for (let x = 0; x < width; x++) {
|
|
47
|
-
const u = x / width;
|
|
48
|
-
const v = y / height;
|
|
49
|
-
|
|
50
|
-
const [L, C] = UVtoLC([u, v]);
|
|
51
|
-
|
|
52
|
-
let oklch = [L, C, H];
|
|
53
|
-
let rgb = convert(oklch, OKLCH, gamut.space, tmp);
|
|
54
|
-
if (!isRGBInGamut(rgb, 0)) {
|
|
55
|
-
rgb[0] = 0.25;
|
|
56
|
-
rgb[1] = 0.25;
|
|
57
|
-
rgb[2] = 0.25;
|
|
58
|
-
// if we wanted to fill the whole space with mapped colors
|
|
59
|
-
// rgb = gamutMapOKLCH(oklch, gamut, sRGB, tmp, mapping);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const idx = x + y * width;
|
|
63
|
-
pixels[idx * 4 + 0] = floatToByte(rgb[0]);
|
|
64
|
-
pixels[idx * 4 + 1] = floatToByte(rgb[1]);
|
|
65
|
-
pixels[idx * 4 + 2] = floatToByte(rgb[2]);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
context.putImageData(
|
|
69
|
-
new ImageData(pixels, width, height, { colorSpace }),
|
|
70
|
-
0,
|
|
71
|
-
0
|
|
72
|
-
);
|
|
73
|
-
// console.profileEnd("map");
|
|
74
|
-
// console.timeEnd("map");
|
|
75
|
-
|
|
76
|
-
const A = [1, 0];
|
|
77
|
-
const B = [0, 0];
|
|
78
|
-
const lineWidth = width * 0.003;
|
|
79
|
-
context.lineWidth = lineWidth;
|
|
80
|
-
|
|
81
|
-
const gamuts = [
|
|
82
|
-
{ defaultColor: "yellow", gamut: sRGBGamut },
|
|
83
|
-
{ defaultColor: "palegreen", gamut: DisplayP3Gamut },
|
|
84
|
-
{ defaultColor: "red", gamut: Rec2020Gamut },
|
|
85
|
-
{ defaultColor: "pink", gamut: A98RGBGamut },
|
|
86
|
-
];
|
|
87
|
-
|
|
88
|
-
const hueAngle = degToRad(H);
|
|
89
|
-
const a = Math.cos(hueAngle);
|
|
90
|
-
const b = Math.sin(hueAngle);
|
|
91
|
-
|
|
92
|
-
for (let { gamut: dispGamut, defaultColor } of gamuts) {
|
|
93
|
-
const gamutCusp = findCuspOKLCH(a, b, dispGamut);
|
|
94
|
-
const gamutTri = [A, gamutCusp, B];
|
|
95
|
-
drawLCTriangle(
|
|
96
|
-
context,
|
|
97
|
-
gamutTri,
|
|
98
|
-
gamut === dispGamut ? "white" : defaultColor
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
context.strokeStyle = "white";
|
|
103
|
-
|
|
104
|
-
const steps = 64;
|
|
105
|
-
for (let i = 0; i < steps; i++) {
|
|
106
|
-
// get some LC point that is very likely to be out of gamut
|
|
107
|
-
const ox = 0.5;
|
|
108
|
-
const oy = 0.5;
|
|
109
|
-
const r = 1;
|
|
110
|
-
const t = (i / steps) * degToRad(360) + degToRad(-180);
|
|
111
|
-
const xy = [Math.cos(t) * r + ox, Math.sin(t) * r + oy];
|
|
112
|
-
const [L, C] = UVtoLC(xy);
|
|
113
|
-
const oklch = [L, C, H];
|
|
114
|
-
const lc = oklch.slice(0, 2);
|
|
115
|
-
|
|
116
|
-
const mapped = gamutMapOKLCH(oklch, gamut, OKLCH, tmp, mapping);
|
|
117
|
-
|
|
118
|
-
const radius = width * 0.01;
|
|
119
|
-
const didChange = !arrayAlmostEqual(mapped, oklch);
|
|
120
|
-
if (didChange) {
|
|
121
|
-
context.globalAlpha = 0.5;
|
|
122
|
-
drawLCPoint(context, lc, radius / 2, "white");
|
|
123
|
-
context.beginPath();
|
|
124
|
-
context.lineTo(...LCtoXY(lc));
|
|
125
|
-
context.lineTo(...LCtoXY(mapped));
|
|
126
|
-
context.stroke();
|
|
127
|
-
context.globalAlpha = 1;
|
|
128
|
-
drawLCPoint(context, mapped.slice(0, 2), radius / 4, "white");
|
|
129
|
-
} else {
|
|
130
|
-
drawLCPoint(context, lc, radius);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const fontSize = width * 0.03;
|
|
135
|
-
const boxHeight = fontSize * gamuts.length;
|
|
136
|
-
const pad = width * 0.05;
|
|
137
|
-
const padleft = width * 0.1;
|
|
138
|
-
context.fillStyle = "black";
|
|
139
|
-
|
|
140
|
-
for (let i = 0; i < gamuts.length; i++) {
|
|
141
|
-
const { gamut: dispGamut, defaultColor } = gamuts[i];
|
|
142
|
-
const curColor = dispGamut === gamut ? "white" : defaultColor;
|
|
143
|
-
|
|
144
|
-
context.font = `${fontSize}px monospace`;
|
|
145
|
-
context.textAlign = "right";
|
|
146
|
-
context.textBaseline = "top";
|
|
147
|
-
context.fillStyle = curColor;
|
|
148
|
-
const x = width - pad - padleft;
|
|
149
|
-
const y = height - boxHeight + i * fontSize - pad;
|
|
150
|
-
context.fillText(dispGamut.space.id, x, y);
|
|
151
|
-
context.beginPath();
|
|
152
|
-
context.lineTo(x + fontSize / 2, y + fontSize * 0.4);
|
|
153
|
-
context.lineTo(x + padleft, y + fontSize * 0.4);
|
|
154
|
-
context.strokeStyle = curColor;
|
|
155
|
-
context.stroke();
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
context.fillStyle = "white";
|
|
159
|
-
context.fillText(
|
|
160
|
-
`Hue: ${H.toFixed(0)}º`,
|
|
161
|
-
width - pad,
|
|
162
|
-
height - pad - boxHeight - fontSize * 2
|
|
163
|
-
);
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
function LCtoXY(okLC) {
|
|
167
|
-
const x = (okLC[1] / 1) * width;
|
|
168
|
-
const y = (1 - okLC[0]) * height;
|
|
169
|
-
return [x, y];
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function XYtoLC(xy) {
|
|
173
|
-
return UVtoLC([xy[0] / width, xy[1] / height]);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function UVtoLC(xy) {
|
|
177
|
-
const L = 1 - xy[1];
|
|
178
|
-
const C = xy[0] * 1;
|
|
179
|
-
return [L, C];
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function drawLCTriangle(context, triangle, color = "white") {
|
|
183
|
-
context.beginPath();
|
|
184
|
-
triangle.forEach((oklch) => {
|
|
185
|
-
const [x, y] = LCtoXY(oklch);
|
|
186
|
-
context.lineTo(x, y);
|
|
187
|
-
});
|
|
188
|
-
context.closePath();
|
|
189
|
-
context.strokeStyle = color;
|
|
190
|
-
context.stroke();
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function drawLCPoint(
|
|
194
|
-
context,
|
|
195
|
-
okLC,
|
|
196
|
-
radius = width * 0.01,
|
|
197
|
-
color = "white",
|
|
198
|
-
fill = true
|
|
199
|
-
) {
|
|
200
|
-
context.beginPath();
|
|
201
|
-
context.arc(...LCtoXY(okLC), radius, 0, Math.PI * 2);
|
|
202
|
-
if (fill) {
|
|
203
|
-
context.fillStyle = color;
|
|
204
|
-
context.fill();
|
|
205
|
-
} else {
|
|
206
|
-
context.strokeStyle = color;
|
|
207
|
-
context.stroke();
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
canvasSketch(sketch, settings);
|