@texel/color 1.1.3 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@texel/color",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "a minimal and modern color library",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -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
@@ -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
- }
@@ -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
- }
@@ -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");
@@ -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 }));
@@ -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);