@texel/color 1.0.6 → 1.1.0
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 +53 -18
- package/package.json +1 -1
- package/src/core.js +26 -10
- package/src/spaces/prophoto-rgb.js +1 -1
- package/src/spaces/xyz.js +6 -0
- package/src/util.js +5 -12
- package/test/bench-colorjs.js +175 -86
- package/test/colorjs-fn.js +34 -0
- package/test/example-interpolation.js +107 -0
- package/test/spaces/hsl.js +82 -0
- package/test/spaces/lab.js +58 -0
- package/test/test-colorjs.js +12 -13
- package/test/test-other-spaces.js +115 -0
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
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
|
-
- Optimized for speed: approx
|
|
8
|
+
- Optimized for speed: approx 5-125 times faster than [Colorjs.io](https://colorjs.io/) (see [benchmarks](#benchmarks))
|
|
9
9
|
- Optimized for low memory and minimal allocations: no arrays or objects are created within conversion and gamut mapping functions
|
|
10
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
11
|
- Optimized for accuracy: [high precision](#accuracy) color space matrices
|
|
@@ -265,6 +265,10 @@ Converts the xyY coordinates to XYZ form, storing the results in `out` if specif
|
|
|
265
265
|
|
|
266
266
|
Performs linear interpolation between min and max with the factor `t`.
|
|
267
267
|
|
|
268
|
+
#### `v = lerpAngle(min, max, t)`
|
|
269
|
+
|
|
270
|
+
Performs circular linear interpolation between min and max with the factor `t`, but where the min and max are considered to be angles (in degrees) allowing the value to wrap around within 0 to 360, interpolating to create the shortest arc.
|
|
271
|
+
|
|
268
272
|
#### `c = clamp(value, min, max)`
|
|
269
273
|
|
|
270
274
|
Clamps the `value` between min and max and returns the result.
|
|
@@ -326,6 +330,14 @@ sRGB.toBase(in_sRGB, out_linear_sRGB); // linear to gamma transfer function
|
|
|
326
330
|
OKHSLToOKLab([h, s, l], DisplayP3Gamut, optionalOutVec);
|
|
327
331
|
```
|
|
328
332
|
|
|
333
|
+
## Interpolation
|
|
334
|
+
|
|
335
|
+
The library currently only exposes `{ lerp, lerpAngle }` functions. To interpolate colors, you will need to build some additional logic, for example see the [example-interpolation.js](./test/example-interpolation.js) script which creates a color ramp in Canvas2D.
|
|
336
|
+
|
|
337
|
+
## Custom Color Spaces
|
|
338
|
+
|
|
339
|
+
You can build custom color space objects to extend this library, such as adding support for CIELab and HSL. See [test/spaces/lab.js](./test/spaces/lab.js) and [test/spaces/hsl.js](./test/spaces/hsl.js) for examples of this. Some of these spaces may be added to the library at a later point, although the current focus is on "modern" spaces (such as OKLab that has largely made CIELab and HSL obsolete). Documentaiton on custom color spaces is WIP.
|
|
340
|
+
|
|
329
341
|
## Notes
|
|
330
342
|
|
|
331
343
|
### Why another library?
|
|
@@ -338,7 +350,7 @@ There are many other options such as [color-space](https://www.npmjs.com/package
|
|
|
338
350
|
|
|
339
351
|
### Supported Spaces
|
|
340
352
|
|
|
341
|
-
This library does not aim to target every color space; it only focuses on a limited "modern" set, i.e. OKLab, OKHSL and DeltaEOK have replaced CIELab, HSL, and CIEDE2000 for many practical purposes, allowing this library to be simpler and slimmer.
|
|
353
|
+
This library does not aim to target every color space; it only focuses on a limited "modern" set, i.e. OKLab, OKHSL and DeltaEOK have replaced CIELab, HSL, and CIEDE2000 for many practical purposes, allowing this library to be simpler and slimmer. Note that other spaces like CIELab and HSL are supported through 'custom color spaces'.
|
|
342
354
|
|
|
343
355
|
### Improvements & Techniques
|
|
344
356
|
|
|
@@ -366,26 +378,47 @@ There are a few benchmarks inside [test](./test):
|
|
|
366
378
|
- [bench-node.js](./test/bench-node.js) - run with `npm run bench:node` to get a node profile
|
|
367
379
|
- [bench-size.js](./test/bench-size.js) - run with `npm run bench:size` to get a small bundle size with esbuild
|
|
368
380
|
|
|
369
|
-
|
|
381
|
+
Results below, based on MacBook Air M2. Note that Colorjs performance depends on which API you use (the default class-based API is much slower than the procedural API).
|
|
382
|
+
|
|
383
|
+
<details>
|
|
384
|
+
<summary>Benchmark Against Colorjs.io</summary>
|
|
370
385
|
|
|
371
386
|
```
|
|
372
|
-
|
|
373
|
-
Colorjs:
|
|
374
|
-
Ours:
|
|
375
|
-
Speedup:
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
Colorjs:
|
|
379
|
-
Ours:
|
|
380
|
-
Speedup:
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
Colorjs:
|
|
384
|
-
Ours:
|
|
385
|
-
Speedup:
|
|
387
|
+
conversion (Colorjs.io procedural API) --
|
|
388
|
+
Colorjs.io: 2955.88 ms
|
|
389
|
+
Ours: 457.86 ms
|
|
390
|
+
Speedup: 6.5x faster
|
|
391
|
+
|
|
392
|
+
conversion (Colorjs.io main API) --
|
|
393
|
+
Colorjs.io: 10034.38 ms
|
|
394
|
+
Ours: 452.11 ms
|
|
395
|
+
Speedup: 22.2x faster
|
|
396
|
+
|
|
397
|
+
gamut mapping OKLCH - sRGB (Colorjs.io procedural API) --
|
|
398
|
+
Colorjs.io: 5602.46 ms
|
|
399
|
+
Ours: 49.10 ms
|
|
400
|
+
Speedup: 114.1x faster
|
|
401
|
+
|
|
402
|
+
gamut mapping OKLCH - sRGB (Colorjs.io main API) --
|
|
403
|
+
Colorjs.io: 5913.80 ms
|
|
404
|
+
Ours: 44.91 ms
|
|
405
|
+
Speedup: 131.7x faster
|
|
406
|
+
|
|
407
|
+
gamut mapping all spaces to P3 (Colorjs.io procedural API) --
|
|
408
|
+
Colorjs.io: 4693.43 ms
|
|
409
|
+
Ours: 150.16 ms
|
|
410
|
+
Speedup: 31.3x faster
|
|
411
|
+
|
|
412
|
+
gamut mapping all spaces to P3 (Colorjs.io main API) --
|
|
413
|
+
Colorjs.io: 5478.16 ms
|
|
414
|
+
Ours: 145.88 ms
|
|
415
|
+
Speedup: 37.6x faster
|
|
386
416
|
```
|
|
387
417
|
|
|
388
|
-
|
|
418
|
+
</details>
|
|
419
|
+
|
|
420
|
+
<details>
|
|
421
|
+
<summary>Benchmark Against Culori</summary>
|
|
389
422
|
|
|
390
423
|
```
|
|
391
424
|
Testing with input type: Random Samling in OKLab L Planes
|
|
@@ -400,6 +433,8 @@ Ours: 23.05 ms
|
|
|
400
433
|
Speedup: 68.9x faster
|
|
401
434
|
```
|
|
402
435
|
|
|
436
|
+
</details>
|
|
437
|
+
|
|
403
438
|
### Running Locally
|
|
404
439
|
|
|
405
440
|
Clone, `npm install`, then `npm run` to list the available scripts, or `npm t` to run the tests.
|
package/package.json
CHANGED
package/src/core.js
CHANGED
|
@@ -200,6 +200,12 @@ export const convert = (input, fromSpace, toSpace, out = vec3()) => {
|
|
|
200
200
|
// and the base we want to get to, linear, OKLab, XYZ etc...
|
|
201
201
|
let toBaseSpace = toSpace.base ?? toSpace;
|
|
202
202
|
|
|
203
|
+
// this is something we may support in future, if there is a nice
|
|
204
|
+
// zero-allocation way of achieving it
|
|
205
|
+
if (fromSpace.base || toBaseSpace.base) {
|
|
206
|
+
throw new Error(`Currently only base of depth=1 is supported`);
|
|
207
|
+
}
|
|
208
|
+
|
|
203
209
|
if (fromBaseSpace === toBaseSpace) {
|
|
204
210
|
// do nothing, spaces are the same
|
|
205
211
|
} else {
|
|
@@ -220,39 +226,47 @@ export const convert = (input, fromSpace, toSpace, out = vec3()) => {
|
|
|
220
226
|
if (fromBaseSpace.id === "oklab") {
|
|
221
227
|
let mat = toBaseSpace.fromLMS_M;
|
|
222
228
|
if (!mat) {
|
|
223
|
-
// space doesn't support direct
|
|
229
|
+
// space doesn't support direct from OKLAB
|
|
224
230
|
// let's convert OKLab to XYZ and then use that
|
|
225
231
|
mat = XYZ.fromLMS_M;
|
|
226
232
|
throughXYZ = true;
|
|
227
233
|
xyzIn = true;
|
|
228
234
|
}
|
|
235
|
+
// convert OKLAB to output (other space, or xyz)
|
|
229
236
|
out = OKLab_to(out, mat, out);
|
|
230
237
|
} else if (toBaseSpace.id === "oklab") {
|
|
231
238
|
let mat = fromBaseSpace.toLMS_M;
|
|
232
239
|
if (!mat) {
|
|
233
|
-
// space doesn't support direct
|
|
240
|
+
// space doesn't support direct to OKLAB
|
|
241
|
+
// we will need to use XYZ as connection, then convert to OKLAB
|
|
234
242
|
throughXYZ = true;
|
|
235
243
|
outputOklab = true;
|
|
236
244
|
} else {
|
|
237
|
-
// direct from space to
|
|
245
|
+
// direct from space to OKLAB
|
|
238
246
|
out = OKLab_from(out, mat, out);
|
|
239
247
|
}
|
|
240
248
|
} else {
|
|
249
|
+
// any other spaces, we use XYZ D65 as a connection
|
|
241
250
|
throughXYZ = true;
|
|
242
251
|
}
|
|
243
252
|
|
|
244
253
|
if (throughXYZ) {
|
|
245
254
|
// First, convert to XYZ if we need to
|
|
246
255
|
if (!xyzIn) {
|
|
247
|
-
if (
|
|
248
|
-
|
|
249
|
-
|
|
256
|
+
if (fromBaseSpace.toXYZ) {
|
|
257
|
+
out = fromBaseSpace.toXYZ(out, out);
|
|
258
|
+
} else if (fromBaseSpace.toXYZ_M) {
|
|
259
|
+
out = transform(out, fromBaseSpace.toXYZ_M, out);
|
|
260
|
+
} else {
|
|
261
|
+
throw new Error(`no toXYZ or toXYZ_M on ${fromBaseSpace.id}`);
|
|
262
|
+
}
|
|
250
263
|
}
|
|
251
264
|
|
|
252
265
|
// Then, adapt D50 <-> D65 if we need to
|
|
253
266
|
if (fromBaseSpace.adapt) {
|
|
254
267
|
out = transform(out, fromBaseSpace.adapt.to, out);
|
|
255
|
-
}
|
|
268
|
+
}
|
|
269
|
+
if (toBaseSpace.adapt) {
|
|
256
270
|
out = transform(out, toBaseSpace.adapt.from, out);
|
|
257
271
|
}
|
|
258
272
|
|
|
@@ -260,10 +274,12 @@ export const convert = (input, fromSpace, toSpace, out = vec3()) => {
|
|
|
260
274
|
if (!xyzOut) {
|
|
261
275
|
if (outputOklab) {
|
|
262
276
|
out = OKLab_from(out, XYZ.toLMS_M, out);
|
|
263
|
-
} else {
|
|
264
|
-
|
|
265
|
-
|
|
277
|
+
} else if (toBaseSpace.fromXYZ) {
|
|
278
|
+
out = toBaseSpace.fromXYZ(out, out);
|
|
279
|
+
} else if (toBaseSpace.fromXYZ_M) {
|
|
266
280
|
out = transform(out, toBaseSpace.fromXYZ_M, out);
|
|
281
|
+
} else {
|
|
282
|
+
throw new Error(`no fromXYZ or fromXYZ_M on ${toBaseSpace.id}`);
|
|
267
283
|
}
|
|
268
284
|
}
|
|
269
285
|
}
|
|
@@ -14,7 +14,7 @@ const ProPhotoRGBToGamma = (v) => (v >= Et ? v ** (1 / 1.8) : 16 * v);
|
|
|
14
14
|
|
|
15
15
|
// Note: below is a possibly improved transfer function proposed by CSS Module 4 spec
|
|
16
16
|
// It is currently not matching the outputs of Colorjs.io when dealing with particular
|
|
17
|
-
// negative values, such as convert([1,1,1],
|
|
17
|
+
// negative values, such as convert([1,1,1], OKLab, ProPhotoRGB)
|
|
18
18
|
|
|
19
19
|
// const ProPhotoRGBToLinear = (val) => {
|
|
20
20
|
// // convert an array of prophoto-rgb values
|
package/src/spaces/xyz.js
CHANGED
|
@@ -2,6 +2,12 @@ import { vec3 } from "../util.js";
|
|
|
2
2
|
import { transform } from "../core.js";
|
|
3
3
|
import { LMS_to_XYZ_M, XYZ_to_LMS_M } from "../conversion_matrices.js";
|
|
4
4
|
|
|
5
|
+
// Note: for the time being, these are not exported
|
|
6
|
+
// It may be exported in a future version
|
|
7
|
+
// for compatibility, the four-digit chromaticity-derived ones everyone else uses
|
|
8
|
+
// const D50 = [0.3457 / 0.3585, 1.0, (1.0 - 0.3457 - 0.3585) / 0.3585];
|
|
9
|
+
// const D65 = [0.3127 / 0.329, 1.0, (1.0 - 0.3127 - 0.329) / 0.329];
|
|
10
|
+
|
|
5
11
|
// Bradford chromatic adaptation from D65 to D50
|
|
6
12
|
// The matrix below is the result of three operations:
|
|
7
13
|
// - convert from XYZ to retinal cone domain
|
package/src/util.js
CHANGED
|
@@ -107,16 +107,9 @@ export const vec3 = () => [0, 0, 0];
|
|
|
107
107
|
// return ((2 * da) % max) - da;
|
|
108
108
|
// }
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// }
|
|
115
|
-
|
|
116
|
-
// function lerpAngleRad(a0, a1, t) {
|
|
117
|
-
// return a0 + shortAngleDistRad(a0, a1) * t;
|
|
118
|
-
// }
|
|
110
|
+
export const deltaAngle = (a0, a1) => {
|
|
111
|
+
var da = (a1 - a0) % 360;
|
|
112
|
+
return ((2 * da) % 360) - da;
|
|
113
|
+
};
|
|
119
114
|
|
|
120
|
-
|
|
121
|
-
// return radToDeg(lerpAngleRad(degToRad(a0), degToRad(a1), t));
|
|
122
|
-
// }
|
|
115
|
+
export const lerpAngle = (a0, a1, t) => a0 + deltaAngle(a0, a1) * t;
|
package/test/bench-colorjs.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
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";
|
|
2
8
|
import {
|
|
3
9
|
convert,
|
|
4
10
|
OKLCH,
|
|
11
|
+
OKLab,
|
|
5
12
|
sRGB,
|
|
6
13
|
sRGBGamut,
|
|
7
|
-
listColorSpaces,
|
|
8
14
|
DisplayP3Gamut,
|
|
9
15
|
DisplayP3,
|
|
10
16
|
gamutMapOKLCH,
|
|
@@ -14,27 +20,25 @@ import {
|
|
|
14
20
|
MapToCuspL,
|
|
15
21
|
} from "../src/index.js";
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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);
|
|
23
33
|
|
|
24
|
-
|
|
25
|
-
const spaces = listColorSpaces().filter((f) => !/ok(hsv|hsl)/i.test(f.id));
|
|
26
|
-
const spacesForColorjs = spaces.map((s) => fixName(s.id));
|
|
34
|
+
const N = 128 * 128;
|
|
27
35
|
|
|
28
|
-
|
|
36
|
+
// sampling variables within OKLab and converting to OKLCH
|
|
37
|
+
const vecs = Array(N)
|
|
29
38
|
.fill()
|
|
30
39
|
.map((_, i, lst) => {
|
|
31
40
|
const t = i / (lst.length - 1);
|
|
32
|
-
return (
|
|
33
|
-
Array(3)
|
|
34
|
-
.fill()
|
|
35
|
-
// -0.5 .. 1.5
|
|
36
|
-
.map(() => t + (t * 2 - 1) * 0.5)
|
|
37
|
-
);
|
|
41
|
+
return convert([t, t * 2 - 1, t * 2 - 1], OKLab, OKLCH);
|
|
38
42
|
});
|
|
39
43
|
|
|
40
44
|
const tmp = [0, 0, 0];
|
|
@@ -59,80 +63,165 @@ const oklchVecs = Array(512 * 256)
|
|
|
59
63
|
return [t0, t0, H];
|
|
60
64
|
});
|
|
61
65
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
now = performance.now();
|
|
85
|
-
for (let vec of vecs) {
|
|
86
|
-
for (let i = 0; i < spacesForColorjs.length; i++) {
|
|
87
|
-
for (let j = 0; j < spacesForColorjs.length; j++) {
|
|
88
|
-
const a = spacesForColorjs[i];
|
|
89
|
-
const b = spacesForColorjs[j];
|
|
90
|
-
new Color(a, vec).to(b);
|
|
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
|
+
}
|
|
91
88
|
}
|
|
92
89
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
+
}
|
|
103
114
|
}
|
|
104
115
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
+
}
|
|
120
134
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
+
}
|
|
130
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();
|
|
131
215
|
}
|
|
132
|
-
elapsedOurs = performance.now() - now;
|
|
133
216
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as ColorJSFn from "colorjs.io/fn";
|
|
2
|
+
import { listColorSpaces } from "../src/index.js";
|
|
3
|
+
|
|
4
|
+
// Colorjs.io uses some different naming conventions than @texel/color
|
|
5
|
+
const getColorJSID = (name) => {
|
|
6
|
+
return name
|
|
7
|
+
.replace("display-", "")
|
|
8
|
+
.replace(/^xyz$/, "xyz-d65")
|
|
9
|
+
.replace("a98-rgb", "a98rgb")
|
|
10
|
+
.replace("prophoto-rgb", "prophoto");
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// returns a list of ColorJS.io space IDs that are supported by @texel/color
|
|
14
|
+
// okhsl/okhsv is skipped due to it not being included in this npm version of colorjs.io
|
|
15
|
+
export function getSupportedColorJSSpaces() {
|
|
16
|
+
const spaceFns = Object.values(ColorJSFn);
|
|
17
|
+
const spaces = listColorSpaces().filter((s) => !/ok(hsv|hsl)/i.test(s.id));
|
|
18
|
+
return spaces.map((space) => {
|
|
19
|
+
const cjsID = getColorJSID(space.id);
|
|
20
|
+
const colorJSSpace = spaceFns.find((f) => f.id === cjsID);
|
|
21
|
+
if (!colorJSSpace)
|
|
22
|
+
throw new Error(`expected ${cjsID} to exist in colorjs.io/fn`);
|
|
23
|
+
return {
|
|
24
|
+
space,
|
|
25
|
+
colorJSSpace,
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Register all spaces
|
|
31
|
+
const spaces = getSupportedColorJSSpaces().map((s) => s.colorJSSpace);
|
|
32
|
+
for (let space of spaces) {
|
|
33
|
+
ColorJSFn.ColorSpace.register(space);
|
|
34
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import canvasSketch from "canvas-sketch";
|
|
2
|
+
import {
|
|
3
|
+
convert,
|
|
4
|
+
DisplayP3Gamut,
|
|
5
|
+
gamutMapOKLCH,
|
|
6
|
+
lerp,
|
|
7
|
+
lerpAngle,
|
|
8
|
+
OKLab,
|
|
9
|
+
OKLCH,
|
|
10
|
+
serialize,
|
|
11
|
+
sRGB,
|
|
12
|
+
sRGBGamut,
|
|
13
|
+
} from "../src/index.js";
|
|
14
|
+
|
|
15
|
+
const settings = {
|
|
16
|
+
dimensions: [2048, 512],
|
|
17
|
+
attributes: {
|
|
18
|
+
// comment this out if you want sRGB output
|
|
19
|
+
colorSpace: "display-p3",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const mix = (() => {
|
|
24
|
+
const tmpA = [0, 0, 0];
|
|
25
|
+
const tmpB = [0, 0, 0];
|
|
26
|
+
|
|
27
|
+
// you can decide whether you'd like to interpolate in
|
|
28
|
+
// OKLab, OKLCH or another space (you may need to adjust interpolation
|
|
29
|
+
// if you use a custom space)
|
|
30
|
+
const interpolationSpace = OKLCH;
|
|
31
|
+
|
|
32
|
+
// e.g. mix({ space, coords }, { space, coords }, 0.5, sRGB)
|
|
33
|
+
return (a, b, t, outputSpace = sRGB, out = [0, 0, 0]) => {
|
|
34
|
+
// bring both spaces into the shared interpolation space
|
|
35
|
+
convert(a.coords, a.space, interpolationSpace, tmpA);
|
|
36
|
+
convert(b.coords, b.space, interpolationSpace, tmpB);
|
|
37
|
+
|
|
38
|
+
// now do interpolation
|
|
39
|
+
out[0] = lerp(tmpA[0], tmpB[0], t);
|
|
40
|
+
out[1] = lerp(tmpA[1], tmpB[1], t);
|
|
41
|
+
if (interpolationSpace.id === "oklch") {
|
|
42
|
+
// for cylindrical spaces, use a circular interpolation for Hue parameter
|
|
43
|
+
// note if you decide to use a custom space like HSL as your interpolation space,
|
|
44
|
+
// you'll have to use the first parameter instead...
|
|
45
|
+
out[2] = lerpAngle(tmpA[2], tmpB[2], t);
|
|
46
|
+
} else {
|
|
47
|
+
// otherwise can use a regular linear interpolation
|
|
48
|
+
out[2] = lerp(tmpA[2], tmpB[2], t);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// make sure we convert from interpolation space to the target output space
|
|
52
|
+
convert(out, interpolationSpace, outputSpace, out);
|
|
53
|
+
return out;
|
|
54
|
+
};
|
|
55
|
+
})();
|
|
56
|
+
|
|
57
|
+
// utility to create a ramp between two 'colors' as { space, coords }
|
|
58
|
+
function ramp(a, b, steps = 4, outputSpace = sRGB) {
|
|
59
|
+
return Array(steps)
|
|
60
|
+
.fill()
|
|
61
|
+
.map((_, i, lst) => {
|
|
62
|
+
const t = lst.length <= 1 ? 0 : i / (lst.length - 1);
|
|
63
|
+
return mix(a, b, t, outputSpace);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const sketch = ({ context }) => {
|
|
68
|
+
const { colorSpace = "srgb" } = context.getContextAttributes();
|
|
69
|
+
const gamut = colorSpace === "srgb" ? sRGBGamut : DisplayP3Gamut;
|
|
70
|
+
|
|
71
|
+
return ({ context, width, height }) => {
|
|
72
|
+
context.fillStyle = "white";
|
|
73
|
+
context.fillRect(0, 0, width, height);
|
|
74
|
+
|
|
75
|
+
const A = {
|
|
76
|
+
space: sRGB,
|
|
77
|
+
coords: [0, 0, 1],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const B = {
|
|
81
|
+
space: OKLCH,
|
|
82
|
+
coords: [0.55, 0.4, 30],
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const slices = 16;
|
|
86
|
+
const sliceWidth = width / slices;
|
|
87
|
+
|
|
88
|
+
// the output space is whatever the canvas expects (sRGB or DisplayP3)
|
|
89
|
+
const outputSpace = gamut.space;
|
|
90
|
+
|
|
91
|
+
// create a ramp of colors in OKLCH
|
|
92
|
+
// then gamut map them to the outputSpace
|
|
93
|
+
const colors = ramp(A, B, slices, OKLCH).map((oklch) =>
|
|
94
|
+
gamutMapOKLCH(oklch, gamut, outputSpace)
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
for (let i = 0; i < slices; i++) {
|
|
98
|
+
const color = colors[i];
|
|
99
|
+
|
|
100
|
+
// turn the color (now in outputSpace) into a context string
|
|
101
|
+
context.fillStyle = serialize(color, outputSpace);
|
|
102
|
+
context.fillRect(i * sliceWidth, 0, sliceWidth, height);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
canvasSketch(sketch, settings);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { sRGB, sRGBLinear } from "../../src/index.js";
|
|
2
|
+
|
|
3
|
+
export const HSL = {
|
|
4
|
+
id: "hsl",
|
|
5
|
+
// Note: @texel/color currently only supports 1-level depth for color spaces
|
|
6
|
+
// (for performance & memory reasons) - so our base must be one without another base space
|
|
7
|
+
base: sRGBLinear,
|
|
8
|
+
// Adapted from https://drafts.csswg.org/css-color-4/better-rgbToHsl.js
|
|
9
|
+
fromBase: (rgb, out = [0, 0, 0]) => {
|
|
10
|
+
// from sRGBLinear (this space's base) to sRGB (for HSL conversion)
|
|
11
|
+
sRGB.fromBase(rgb, out);
|
|
12
|
+
const r = out[0];
|
|
13
|
+
const g = out[1];
|
|
14
|
+
const b = out[2];
|
|
15
|
+
let max = Math.max(r, g, b);
|
|
16
|
+
let min = Math.min(r, g, b);
|
|
17
|
+
let h = 0,
|
|
18
|
+
s = 0,
|
|
19
|
+
l = (min + max) / 2;
|
|
20
|
+
let d = max - min;
|
|
21
|
+
|
|
22
|
+
if (d !== 0) {
|
|
23
|
+
s = l === 0 || l === 1 ? 0 : (max - l) / Math.min(l, 1 - l);
|
|
24
|
+
|
|
25
|
+
switch (max) {
|
|
26
|
+
case r:
|
|
27
|
+
h = (g - b) / d + (g < b ? 6 : 0);
|
|
28
|
+
break;
|
|
29
|
+
case g:
|
|
30
|
+
h = (b - r) / d + 2;
|
|
31
|
+
break;
|
|
32
|
+
case b:
|
|
33
|
+
h = (r - g) / d + 4;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
h = h * 60;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Very out of gamut colors can produce negative saturation
|
|
40
|
+
// If so, just rotate the hue by 180 and use a positive saturation
|
|
41
|
+
// see https://github.com/w3c/csswg-drafts/issues/9222
|
|
42
|
+
if (s < 0) {
|
|
43
|
+
h += 180;
|
|
44
|
+
s = Math.abs(s);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (h >= 360) {
|
|
48
|
+
h -= 360;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
out[0] = h;
|
|
52
|
+
out[1] = s * 100;
|
|
53
|
+
out[2] = l * 100;
|
|
54
|
+
return out;
|
|
55
|
+
},
|
|
56
|
+
// Adapted from https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB_alternative
|
|
57
|
+
toBase: (hsl, out = [0, 0, 0]) => {
|
|
58
|
+
let h = hsl[0];
|
|
59
|
+
let s = hsl[1];
|
|
60
|
+
let l = hsl[2];
|
|
61
|
+
h = h % 360;
|
|
62
|
+
if (h < 0) {
|
|
63
|
+
h += 360;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
s /= 100;
|
|
67
|
+
l /= 100;
|
|
68
|
+
|
|
69
|
+
const a = s * Math.min(l, 1 - l);
|
|
70
|
+
out[0] = f(h, l, a, 0);
|
|
71
|
+
out[1] = f(h, l, a, 8);
|
|
72
|
+
out[2] = f(h, l, a, 4);
|
|
73
|
+
// from sRGB to sRGBLinear (this space's base)
|
|
74
|
+
sRGB.toBase(out, out);
|
|
75
|
+
return out;
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
function f(h, l, a, n) {
|
|
80
|
+
let k = (n + h / 30) % 12;
|
|
81
|
+
return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
|
|
82
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Lab aka CIELAB aka L*a*b* (uses a D50 WHITE_D50 point and has to be adapted)
|
|
2
|
+
// refer to CSS Color Module Level 4 Spec for more details
|
|
3
|
+
// Source code reference: Colorjs.io
|
|
4
|
+
import { D50_to_D65_M, D65_to_D50_M } from "../../src/index.js";
|
|
5
|
+
|
|
6
|
+
// K * e = 2^3 = 8
|
|
7
|
+
const e = 216 / 24389; // 6^3/29^3 == (24/116)^3
|
|
8
|
+
const e3 = 24 / 116;
|
|
9
|
+
const K = 24389 / 27; // 29^3/3^3
|
|
10
|
+
const WHITE_D50 = [0.3457 / 0.3585, 1.0, (1.0 - 0.3457 - 0.3585) / 0.3585];
|
|
11
|
+
|
|
12
|
+
const fterm = (value) =>
|
|
13
|
+
value > e ? Math.cbrt(value) : (K * value + 16) / 116;
|
|
14
|
+
|
|
15
|
+
const inv = (value) =>
|
|
16
|
+
value > e3 ? Math.pow(value, 3) : (116 * value - 16) / K;
|
|
17
|
+
|
|
18
|
+
export const Lab = {
|
|
19
|
+
id: "lab",
|
|
20
|
+
adapt: {
|
|
21
|
+
// chromatic adaptation to and from D65
|
|
22
|
+
to: D50_to_D65_M,
|
|
23
|
+
from: D65_to_D50_M,
|
|
24
|
+
},
|
|
25
|
+
// Convert D50-adapted XYX to Lab
|
|
26
|
+
// CIE 15.3:2004 section 8.2.1.1
|
|
27
|
+
fromXYZ(xyz, out = [0, 0, 0]) {
|
|
28
|
+
// XYZ scaled relative to reference WHITE_D50, then modified
|
|
29
|
+
out[0] = fterm(xyz[0] / WHITE_D50[0]);
|
|
30
|
+
out[1] = fterm(xyz[1] / WHITE_D50[1]);
|
|
31
|
+
out[2] = fterm(xyz[2] / WHITE_D50[2]);
|
|
32
|
+
let L = 116 * out[1] - 16;
|
|
33
|
+
let a = 500 * (out[0] - out[1]);
|
|
34
|
+
let b = 200 * (out[1] - out[2]);
|
|
35
|
+
out[0] = L;
|
|
36
|
+
out[1] = a;
|
|
37
|
+
out[2] = b;
|
|
38
|
+
return out;
|
|
39
|
+
},
|
|
40
|
+
// Convert Lab to D50-adapted XYZ
|
|
41
|
+
// Same result as CIE 15.3:2004 Appendix D although the derivation is different
|
|
42
|
+
// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
|
|
43
|
+
toXYZ(Lab, out = [0, 0, 0]) {
|
|
44
|
+
// compute f, starting with the luminance-related term
|
|
45
|
+
const L = Lab[0];
|
|
46
|
+
const a = Lab[1];
|
|
47
|
+
const b = Lab[2];
|
|
48
|
+
out[1] = (L + 16) / 116;
|
|
49
|
+
out[0] = a / 500 + out[1];
|
|
50
|
+
out[2] = out[1] - b / 200;
|
|
51
|
+
|
|
52
|
+
// compute xyz and scale by WHITE_D50
|
|
53
|
+
out[0] = inv(out[0]) * WHITE_D50[0];
|
|
54
|
+
out[1] = (L > 8 ? Math.pow((L + 16) / 116, 3) : L / K) * WHITE_D50[1];
|
|
55
|
+
out[2] = inv(out[2]) * WHITE_D50[2];
|
|
56
|
+
return out;
|
|
57
|
+
},
|
|
58
|
+
};
|
package/test/test-colorjs.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import test from "tape";
|
|
2
2
|
import Color from "colorjs.io";
|
|
3
3
|
import arrayAlmostEqual from "./almost-equal.js";
|
|
4
|
-
import {
|
|
4
|
+
import { convert } from "../src/index.js";
|
|
5
|
+
import { getSupportedColorJSSpaces } from "./colorjs-fn.js";
|
|
5
6
|
|
|
6
7
|
test("should approximately match colorjs.io conversions", async (t) => {
|
|
7
8
|
// note: we skip okhsv/hsl as colorjs.io doesn't support in the current npm version
|
|
8
|
-
const spaces =
|
|
9
|
+
const spaces = getSupportedColorJSSpaces();
|
|
9
10
|
const vecs = [
|
|
10
11
|
[0.12341, 0.12001, 0.05212],
|
|
11
12
|
[1, 1, 1],
|
|
12
13
|
[1, 0, 0],
|
|
13
14
|
[0, 0, 0],
|
|
15
|
+
[-0.5, -0.5, -0.5],
|
|
14
16
|
// some other inputs
|
|
15
17
|
[0.95, 1, 1.089],
|
|
16
18
|
[0.45, 1.236, -0.019],
|
|
@@ -28,18 +30,15 @@ test("should approximately match colorjs.io conversions", async (t) => {
|
|
|
28
30
|
// Math.random() * 2 - 1,
|
|
29
31
|
// ]);
|
|
30
32
|
|
|
31
|
-
const fixName = (name) => {
|
|
32
|
-
return name
|
|
33
|
-
.replace("display-", "")
|
|
34
|
-
.replace("a98-rgb", "a98rgb")
|
|
35
|
-
.replace("prophoto-rgb", "prophoto");
|
|
36
|
-
};
|
|
37
|
-
|
|
38
33
|
for (let vec of vecs) {
|
|
39
34
|
for (let i = 0; i < spaces.length; i++) {
|
|
40
35
|
for (let j = 0; j < spaces.length; j++) {
|
|
41
|
-
const
|
|
42
|
-
const
|
|
36
|
+
const A = spaces[i];
|
|
37
|
+
const B = spaces[j];
|
|
38
|
+
|
|
39
|
+
// @texel/color spaces
|
|
40
|
+
const a = A.space;
|
|
41
|
+
const b = B.space;
|
|
43
42
|
const suffix = `${a.id}-to-${b.id}`;
|
|
44
43
|
|
|
45
44
|
console.log(suffix);
|
|
@@ -47,8 +46,8 @@ test("should approximately match colorjs.io conversions", async (t) => {
|
|
|
47
46
|
const tmp = vec.slice();
|
|
48
47
|
const expected1 = convert(vec, a, b, tmp);
|
|
49
48
|
|
|
50
|
-
const colorjsid_a =
|
|
51
|
-
const colorjsid_b =
|
|
49
|
+
const colorjsid_a = A.colorJSSpace.id;
|
|
50
|
+
const colorjsid_b = B.colorJSSpace.id;
|
|
52
51
|
t.deepEqual(expected0, tmp, `${suffix} copies into`);
|
|
53
52
|
t.deepEqual(expected0, expected1, `${suffix} copies into`);
|
|
54
53
|
t.equal(expected1, tmp, `${suffix} copies into and returns`);
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
convert,
|
|
3
|
+
XYZD50,
|
|
4
|
+
sRGB,
|
|
5
|
+
XYZ,
|
|
6
|
+
ProPhotoRGB,
|
|
7
|
+
OKLCH,
|
|
8
|
+
sRGBLinear,
|
|
9
|
+
} from "../src/index.js";
|
|
10
|
+
import { Lab } from "./spaces/lab.js";
|
|
11
|
+
import { HSL } from "./spaces/hsl.js";
|
|
12
|
+
import test from "tape";
|
|
13
|
+
import Color from "colorjs.io";
|
|
14
|
+
import arrayAlmostEqual from "./almost-equal.js";
|
|
15
|
+
|
|
16
|
+
test("should approximately match colorjs.io CIELAB", async (t) => {
|
|
17
|
+
let input = [0.5, 0.1243, -0.123];
|
|
18
|
+
let inputSpace = OKLCH;
|
|
19
|
+
let outputSpace = Lab;
|
|
20
|
+
let expected = new Color(inputSpace.id, input).to(outputSpace.id).coords;
|
|
21
|
+
let result = convert(input, inputSpace, outputSpace);
|
|
22
|
+
t.deepEqual(expected, result);
|
|
23
|
+
|
|
24
|
+
inputSpace = XYZD50;
|
|
25
|
+
expected = new Color(inputSpace.id, input).to(outputSpace.id).coords;
|
|
26
|
+
result = convert(input, inputSpace, outputSpace);
|
|
27
|
+
t.deepEqual(arrayAlmostEqual(expected, result), true);
|
|
28
|
+
|
|
29
|
+
inputSpace = XYZ;
|
|
30
|
+
expected = new Color(inputSpace.id, input).to(outputSpace.id).coords;
|
|
31
|
+
result = convert(input, inputSpace, outputSpace);
|
|
32
|
+
t.deepEqual(arrayAlmostEqual(expected, result), true);
|
|
33
|
+
|
|
34
|
+
inputSpace = sRGB;
|
|
35
|
+
expected = new Color(inputSpace.id, input).to(outputSpace.id).coords;
|
|
36
|
+
result = convert(input, inputSpace, outputSpace);
|
|
37
|
+
t.deepEqual(arrayAlmostEqual(expected, result), true);
|
|
38
|
+
|
|
39
|
+
inputSpace = ProPhotoRGB;
|
|
40
|
+
expected = new Color("prophoto", input).to(outputSpace.id).coords;
|
|
41
|
+
result = convert(input, inputSpace, outputSpace);
|
|
42
|
+
t.deepEqual(arrayAlmostEqual(expected, result), true);
|
|
43
|
+
|
|
44
|
+
inputSpace = Lab;
|
|
45
|
+
outputSpace = XYZ;
|
|
46
|
+
expected = new Color(inputSpace.id, input).to(outputSpace.id).coords;
|
|
47
|
+
result = convert(input, inputSpace, outputSpace);
|
|
48
|
+
t.deepEqual(arrayAlmostEqual(expected, result), true);
|
|
49
|
+
|
|
50
|
+
outputSpace = XYZD50;
|
|
51
|
+
expected = new Color(inputSpace.id, input).to(outputSpace.id).coords;
|
|
52
|
+
result = convert(input, inputSpace, outputSpace);
|
|
53
|
+
t.deepEqual(arrayAlmostEqual(expected, result), true);
|
|
54
|
+
|
|
55
|
+
outputSpace = Lab;
|
|
56
|
+
expected = new Color(inputSpace.id, input).to(outputSpace.id).coords;
|
|
57
|
+
result = convert(input, inputSpace, outputSpace);
|
|
58
|
+
t.deepEqual(arrayAlmostEqual(expected, result), true);
|
|
59
|
+
|
|
60
|
+
outputSpace = sRGBLinear;
|
|
61
|
+
expected = new Color(inputSpace.id, input).to(outputSpace.id).coords;
|
|
62
|
+
result = convert(input, inputSpace, outputSpace);
|
|
63
|
+
t.deepEqual(arrayAlmostEqual(expected, result), true);
|
|
64
|
+
|
|
65
|
+
outputSpace = sRGB;
|
|
66
|
+
expected = new Color(inputSpace.id, input).to(outputSpace.id).coords;
|
|
67
|
+
result = convert(input, inputSpace, outputSpace);
|
|
68
|
+
t.deepEqual(arrayAlmostEqual(expected, result), true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("should approximately match colorjs.io HSL", async (t) => {
|
|
72
|
+
let input = [30, 50, 50];
|
|
73
|
+
let inputSpace = HSL;
|
|
74
|
+
let outputSpace = OKLCH;
|
|
75
|
+
let expected = new Color(inputSpace.id, input).to(outputSpace.id).coords;
|
|
76
|
+
let result = convert(input, inputSpace, outputSpace);
|
|
77
|
+
t.deepEqual(arrayAlmostEqual(expected, result), true);
|
|
78
|
+
|
|
79
|
+
outputSpace = XYZD50;
|
|
80
|
+
expected = new Color(inputSpace.id, input).to(outputSpace.id).coords;
|
|
81
|
+
result = convert(input, inputSpace, outputSpace);
|
|
82
|
+
t.deepEqual(arrayAlmostEqual(expected, result), true);
|
|
83
|
+
|
|
84
|
+
outputSpace = sRGB;
|
|
85
|
+
expected = new Color(inputSpace.id, input).to(outputSpace.id).coords;
|
|
86
|
+
result = convert(input, inputSpace, outputSpace);
|
|
87
|
+
t.deepEqual(arrayAlmostEqual(expected, result), true);
|
|
88
|
+
|
|
89
|
+
outputSpace = sRGBLinear;
|
|
90
|
+
expected = new Color(inputSpace.id, input).to(outputSpace.id).coords;
|
|
91
|
+
result = convert(input, inputSpace, outputSpace);
|
|
92
|
+
t.deepEqual(arrayAlmostEqual(expected, result), true);
|
|
93
|
+
|
|
94
|
+
input = [0.5, 0.2, 30];
|
|
95
|
+
inputSpace = OKLCH;
|
|
96
|
+
outputSpace = HSL;
|
|
97
|
+
expected = new Color(inputSpace.id, input).to(outputSpace.id).coords;
|
|
98
|
+
result = convert(input, inputSpace, outputSpace);
|
|
99
|
+
t.deepEqual(arrayAlmostEqual(expected, result), true);
|
|
100
|
+
|
|
101
|
+
inputSpace = XYZ;
|
|
102
|
+
expected = new Color(inputSpace.id, input).to(outputSpace.id).coords;
|
|
103
|
+
result = convert(input, inputSpace, outputSpace);
|
|
104
|
+
t.deepEqual(arrayAlmostEqual(expected, result), true);
|
|
105
|
+
|
|
106
|
+
inputSpace = sRGB;
|
|
107
|
+
expected = new Color(inputSpace.id, input).to(outputSpace.id).coords;
|
|
108
|
+
result = convert(input, inputSpace, outputSpace);
|
|
109
|
+
t.deepEqual(arrayAlmostEqual(expected, result), true);
|
|
110
|
+
|
|
111
|
+
inputSpace = sRGBLinear;
|
|
112
|
+
expected = new Color(inputSpace.id, input).to(outputSpace.id).coords;
|
|
113
|
+
result = convert(input, inputSpace, outputSpace);
|
|
114
|
+
t.deepEqual(arrayAlmostEqual(expected, result), true);
|
|
115
|
+
});
|