@texel/color 1.0.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.
@@ -0,0 +1,268 @@
1
+ /** This file is auto-generated by tools/print_matrices.py */
2
+
3
+ // OKLab to LMS matrices
4
+
5
+ export const OKLab_to_LMS_M = [
6
+ [1.0, 0.3963377773761749, 0.2158037573099136],
7
+ [1.0, -0.1055613458156586, -0.0638541728258133],
8
+ [1.0, -0.0894841775298119, -1.2914855480194092],
9
+ ];
10
+
11
+ export const LMS_to_OKLab_M = [
12
+ [0.210454268309314, 0.7936177747023054, -0.0040720430116193],
13
+ [1.9779985324311684, -2.42859224204858, 0.450593709617411],
14
+ [0.0259040424655478, 0.7827717124575296, -0.8086757549230774],
15
+ ];
16
+
17
+ export const XYZ_to_LMS_M = [
18
+ [0.819022437996703, 0.3619062600528904, -0.1288737815209879],
19
+ [0.0329836539323885, 0.9292868615863434, 0.0361446663506424],
20
+ [0.0481771893596242, 0.2642395317527308, 0.6335478284694309],
21
+ ];
22
+
23
+ export const LMS_to_XYZ_M = [
24
+ [1.2268798758459243, -0.5578149944602171, 0.2813910456659647],
25
+ [-0.0405757452148008, 1.112286803280317, -0.0717110580655164],
26
+ [-0.0763729366746601, -0.4214933324022432, 1.5869240198367816],
27
+ ];
28
+
29
+ // linear_sRGB space
30
+
31
+ // linear_sRGB to XYZ (D65) matrices
32
+
33
+ export const linear_sRGB_to_XYZ_M = [
34
+ [0.4123907992659595, 0.35758433938387796, 0.1804807884018343],
35
+ [0.21263900587151036, 0.7151686787677559, 0.07219231536073371],
36
+ [0.01933081871559185, 0.11919477979462599, 0.9505321522496606],
37
+ ];
38
+
39
+ export const XYZ_to_linear_sRGB_M = [
40
+ [3.2409699419045213, -1.5373831775700935, -0.4986107602930033],
41
+ [-0.9692436362808798, 1.8759675015077206, 0.04155505740717561],
42
+ [0.05563007969699361, -0.20397695888897657, 1.0569715142428786],
43
+ ];
44
+
45
+ // linear_sRGB to LMS matrices
46
+
47
+ export const linear_sRGB_to_LMS_M = [
48
+ [0.4122214694707629, 0.5363325372617349, 0.051445993267502196],
49
+ [0.2119034958178251, 0.6806995506452345, 0.10739695353694051],
50
+ [0.08830245919005637, 0.2817188391361215, 0.6299787016738223],
51
+ ];
52
+
53
+ export const LMS_to_linear_sRGB_M = [
54
+ [4.076741636075959, -3.307711539258062, 0.2309699031821041],
55
+ [-1.2684379732850313, 2.6097573492876878, -0.3413193760026569],
56
+ [-0.004196076138675526, -0.703418617935936, 1.7076146940746113],
57
+ ];
58
+
59
+ // linear_sRGB coefficients for OKLab gamut approximation
60
+
61
+ export const OKLab_to_linear_sRGB_coefficients = [
62
+ [
63
+ [-1.8817030993265873, -0.8093650129914302],
64
+ [1.19086277, 1.76576728, 0.59662641, 0.75515197, 0.56771245],
65
+ ],
66
+ [
67
+ [1.8144407988010998, -1.194452667805235],
68
+ [0.73956515, -0.45954404, 0.08285427, 0.12541073, -0.14503204],
69
+ ],
70
+ [
71
+ [0.13110757611180954, 1.813339709266608],
72
+ [1.35733652, -0.00915799, -1.1513021, -0.50559606, 0.00692167],
73
+ ],
74
+ ];
75
+
76
+ // linear_DisplayP3 space
77
+
78
+ // linear_DisplayP3 to XYZ (D65) matrices
79
+
80
+ export const linear_DisplayP3_to_XYZ_M = [
81
+ [0.48657094864821626, 0.26566769316909294, 0.1982172852343625],
82
+ [0.22897456406974884, 0.6917385218365062, 0.079286914093745],
83
+ [0.0, 0.045113381858902575, 1.0439443689009757],
84
+ ];
85
+
86
+ export const XYZ_to_linear_DisplayP3_M = [
87
+ [2.4934969119414245, -0.9313836179191236, -0.40271078445071684],
88
+ [-0.829488969561575, 1.7626640603183468, 0.02362468584194359],
89
+ [0.035845830243784335, -0.07617238926804171, 0.9568845240076873],
90
+ ];
91
+
92
+ // linear_DisplayP3 to LMS matrices
93
+
94
+ export const linear_DisplayP3_to_LMS_M = [
95
+ [0.48137985274995443, 0.4621183710113181, 0.05650177623872757],
96
+ [0.22883194181124475, 0.6532168193835676, 0.11795123880518778],
97
+ [0.08394575232299319, 0.22416527097756642, 0.6918889766994405],
98
+ ];
99
+
100
+ export const LMS_to_linear_DisplayP3_M = [
101
+ [3.1277689713618737, -2.2571357625916395, 0.1293667912297651],
102
+ [-1.091009018437798, 2.413331710306923, -0.3223226918691248],
103
+ [-0.02601080193857041, -0.5080413317041669, 1.5340521336427373],
104
+ ];
105
+
106
+ // linear_DisplayP3 coefficients for OKLab gamut approximation
107
+
108
+ export const OKLab_to_linear_DisplayP3_coefficients = [
109
+ [
110
+ [-1.772343927512981, -0.8207587433674072],
111
+ [
112
+ 1.1941401817282744, 1.7629811997119493, 0.5958599382477117,
113
+ 0.7575999740542505, 0.5681684967813678,
114
+ ],
115
+ ],
116
+ [
117
+ [1.8031987175305495, -1.1932813966558915],
118
+ [
119
+ 0.7395668192259771, -0.45954279991477065, 0.08285308768965816,
120
+ 0.1254116495192955, -0.14503290744357106,
121
+ ],
122
+ ],
123
+ [
124
+ [0.08970487824467532, 1.9032774657416107],
125
+ [
126
+ 1.3650944117698118, -0.013962295571040945, -1.1452305089885595,
127
+ -0.5025987876721942, 0.003174713114731378,
128
+ ],
129
+ ],
130
+ ];
131
+
132
+ // linear_Rec2020 space
133
+
134
+ // linear_Rec2020 to XYZ (D65) matrices
135
+
136
+ export const linear_Rec2020_to_XYZ_M = [
137
+ [0.6369580483012913, 0.14461690358620838, 0.16888097516417205],
138
+ [0.26270021201126703, 0.677998071518871, 0.059301716469861945],
139
+ [0.0, 0.028072693049087508, 1.0609850577107909],
140
+ ];
141
+
142
+ export const XYZ_to_linear_Rec2020_M = [
143
+ [1.7166511879712676, -0.3556707837763924, -0.2533662813736598],
144
+ [-0.666684351832489, 1.616481236634939, 0.01576854581391113],
145
+ [0.017639857445310915, -0.042770613257808655, 0.942103121235474],
146
+ ];
147
+
148
+ // linear_Rec2020 to LMS matrices
149
+
150
+ export const linear_Rec2020_to_LMS_M = [
151
+ [0.6167557848654444, 0.3601984012264634, 0.023045813908092266],
152
+ [0.26513305939263676, 0.6358393720678492, 0.09902756853951414],
153
+ [0.10010262952034828, 0.20390652261661452, 0.6959908478630372],
154
+ ];
155
+
156
+ export const LMS_to_linear_Rec2020_M = [
157
+ [2.1399067304346513, -1.246389493760618, 0.10648276332596689],
158
+ [-0.8847358357577675, 2.1632309383612007, -0.27849510260343363],
159
+ [-0.04857374640044396, -0.45450314971409633, 1.5030768961145404],
160
+ ];
161
+
162
+ // linear_Rec2020 coefficients for OKLab gamut approximation
163
+
164
+ export const OKLab_to_linear_Rec2020_coefficients = [
165
+ [
166
+ [-1.3683489920695084, -0.4666477292401165],
167
+ [
168
+ 1.2572444967331895, 1.715801757890085, 0.5648732965817461,
169
+ 0.7950731608663721, 0.5871636339819248,
170
+ ],
171
+ ],
172
+ [
173
+ [2.0115079619342833, -2.0379095965347],
174
+ [
175
+ 0.7408775472462948, -0.4586732968366297, 0.081829765825816,
176
+ 0.12598704592707602, -0.14570327455009213,
177
+ ],
178
+ ],
179
+ [
180
+ [0.06454093208719812, 2.2970933629671704],
181
+ [
182
+ 1.3692048443658147, -0.016466673486950332, -1.141978697647362,
183
+ -0.5010647675396565, 0.001199059854416378,
184
+ ],
185
+ ],
186
+ ];
187
+
188
+ // linear_A98RGB space
189
+
190
+ // linear_A98RGB to XYZ (D65) matrices
191
+
192
+ export const linear_A98RGB_to_XYZ_M = [
193
+ [0.5766690429101308, 0.18555823790654627, 0.18822864623499472],
194
+ [0.29734497525053616, 0.627363566255466, 0.07529145849399789],
195
+ [0.027031361386412378, 0.07068885253582714, 0.9913375368376389],
196
+ ];
197
+
198
+ export const XYZ_to_linear_A98RGB_M = [
199
+ [2.041587903810746, -0.5650069742788596, -0.3447313507783295],
200
+ [-0.9692436362808798, 1.8759675015077206, 0.04155505740717561],
201
+ [0.013444280632031024, -0.11836239223101824, 1.0151749943912054],
202
+ ];
203
+
204
+ // linear_A98RGB to LMS matrices
205
+
206
+ export const linear_A98RGB_to_LMS_M = [
207
+ [0.5764322596183941, 0.36991322261987963, 0.053654517761726306],
208
+ [0.29631647054222465, 0.5916761332521886, 0.11200739620558692],
209
+ [0.12347825101427762, 0.21949869837199862, 0.6570230506137239],
210
+ ];
211
+
212
+ export const LMS_to_linear_A98RGB_M = [
213
+ [2.554036838611556, -1.6219761806828696, 0.06793934207131354],
214
+ [-1.2684379732850315, 2.6097573492876887, -0.3413193760026572],
215
+ [-0.0562347359374939, -0.5670418395669057, 1.6232765755043994],
216
+ ];
217
+
218
+ // linear_A98RGB coefficients for OKLab gamut approximation
219
+
220
+ export const OKLab_to_linear_A98RGB_coefficients = [
221
+ [
222
+ [-1.591695414425798, -0.8395798483264373],
223
+ [
224
+ 1.215470987494823, 1.7445423850069868, 0.5911924333317312,
225
+ 0.774055974979685, 0.5710471573194968,
226
+ ],
227
+ ],
228
+ [
229
+ [1.8144407988011015, -1.1944526678052378],
230
+ [0.73956515, -0.45954404, 0.08285427, 0.12541073, -0.14503204],
231
+ ],
232
+ [
233
+ [-0.014529428934082126, 2.073564997814519],
234
+ [1.35733652, -0.00915799, -1.1513021, -0.50559606, 0.00692167],
235
+ ],
236
+ ];
237
+
238
+ // linear_ProPhotoRGB space
239
+
240
+ // linear_ProPhotoRGB to XYZ (D50) matrices
241
+
242
+ export const linear_ProPhotoRGB_to_XYZ_M = [
243
+ [0.7977666449006423, 0.13518129740053308, 0.0313477341283922],
244
+ [0.2880748288194013, 0.711835234241873, 8.993693872564e-5],
245
+ [0.0, 0.0, 0.8251046025104602],
246
+ ];
247
+
248
+ export const XYZ_to_linear_ProPhotoRGB_M = [
249
+ [1.3457868816471583, -0.25557208737979464, -0.05110186497554526],
250
+ [-0.5446307051249019, 1.5082477428451468, 0.02052744743642139],
251
+ [0.0, 0.0, 1.2119675456389452],
252
+ ];
253
+
254
+ // linear_ProPhotoRGB to LMS matrices
255
+
256
+ export const linear_ProPhotoRGB_to_LMS_M = [
257
+ [0.7247750802792337, 0.3523542757724655, -0.07712935605169913],
258
+ [0.2967127550253245, 0.6720629323218004, 0.031224312652875095],
259
+ [0.13744833201856482, 0.23349936027726578, 0.6290523077041692],
260
+ ];
261
+
262
+ export const LMS_to_linear_ProPhotoRGB_M = [
263
+ [1.7409200224411467, -1.004224451144535, 0.26330442870338805],
264
+ [-0.7641128642092264, 1.9548345194982568, -0.19072165528903018],
265
+ [-0.09675934345097237, -0.5061957965480232, 1.602955139998996],
266
+ ];
267
+
268
+ // linear_ProPhotoRGB does not yet support OKLab gamut approximation
package/src/core.js ADDED
@@ -0,0 +1,186 @@
1
+ import { floatToByte, vec3 } from "./util.js";
2
+ import { LMS_to_OKLab_M, OKLab_to_LMS_M } from "./conversion_matrices.js";
3
+ import { XYZ } from "./spaces.js";
4
+
5
+ const tmp3 = vec3();
6
+
7
+ const cubed3 = (lms) => {
8
+ const l = lms[0],
9
+ m = lms[1],
10
+ s = lms[2];
11
+ lms[0] = l * l * l;
12
+ lms[1] = m * m * m;
13
+ lms[2] = s * s * s;
14
+ };
15
+
16
+ const cbrt3 = (lms) => {
17
+ lms[0] = Math.cbrt(lms[0]);
18
+ lms[1] = Math.cbrt(lms[1]);
19
+ lms[2] = Math.cbrt(lms[2]);
20
+ };
21
+
22
+ const dot3 = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
23
+
24
+ export const OKLab_to = (OKLab, LMS_to_output, out = vec3()) => {
25
+ transform(OKLab, OKLab_to_LMS_M, out);
26
+ cubed3(out);
27
+ return transform(out, LMS_to_output, out);
28
+ };
29
+
30
+ export const OKLab_from = (input, input_to_LMS, out = vec3()) => {
31
+ transform(input, input_to_LMS, out);
32
+ cbrt3(out);
33
+ return transform(out, LMS_to_OKLab_M, out);
34
+ };
35
+
36
+ export const transform = (input, matrix, out = vec3()) => {
37
+ const x = dot3(input, matrix[0]);
38
+ const y = dot3(input, matrix[1]);
39
+ const z = dot3(input, matrix[2]);
40
+ out[0] = x;
41
+ out[1] = y;
42
+ out[2] = z;
43
+ return out;
44
+ };
45
+
46
+ const vec3Copy = (input, output) => {
47
+ output[0] = input[0];
48
+ output[1] = input[1];
49
+ output[2] = input[2];
50
+ };
51
+
52
+ export const serialize = (input, inputSpace, outputSpace = inputSpace) => {
53
+ if (!inputSpace) throw new Error(`must specify an input space`);
54
+ if (inputSpace !== outputSpace) {
55
+ convert(input, inputSpace, outputSpace, tmp3);
56
+ } else {
57
+ vec3Copy(input, tmp3);
58
+ }
59
+
60
+ const id = outputSpace.id;
61
+ if (id == "srgb") {
62
+ const r = floatToByte(tmp3[0]);
63
+ const g = floatToByte(tmp3[1]);
64
+ const b = floatToByte(tmp3[2]);
65
+ return `rgb(${r}, ${g}, ${b})`;
66
+ } else if (id == "oklab" || id == "oklch") {
67
+ return `${id}(${tmp3[0]} ${tmp3[1]} ${tmp3[2]})`;
68
+ } else {
69
+ return `color(${id} ${tmp3[0]} ${tmp3[1]} ${tmp3[2]})`;
70
+ }
71
+ };
72
+
73
+ export const convert = (input, fromSpace, toSpace, out = vec3()) => {
74
+ // place into output
75
+ vec3Copy(input, out);
76
+
77
+ if (!fromSpace) throw new Error(`must specify a fromSpace`);
78
+ if (!toSpace) throw new Error(`must specify a toSpace`);
79
+
80
+ // special case: no conversion needed
81
+ if (fromSpace == toSpace) {
82
+ return out;
83
+ }
84
+
85
+ // e.g. convert OKLCH -> OKLab or sRGB -> sRGBLinear
86
+ if (fromSpace.base) {
87
+ out = fromSpace.toBase(out, out);
88
+ fromSpace = fromSpace.base;
89
+ }
90
+
91
+ // now we have the base space like sRGBLinear or XYZ
92
+ let fromBaseSpace = fromSpace;
93
+
94
+ // and the base we want to get to, linear, OKLab, XYZ etc...
95
+ let toBaseSpace = toSpace.base ?? toSpace;
96
+
97
+ if (fromBaseSpace === toBaseSpace) {
98
+ // do nothing, spaces are the same
99
+ } else {
100
+ // [from space] -> (adaptation) -> [xyz] -> (adaptation) -> [to space]
101
+
102
+ // e.g. sRGB to ProPhotoLinear
103
+ // sRGB -> sRGBLinear -> XYZ(D65) -> XYZD65ToD50 -> ProPhotoLinear
104
+ // ProPhotoLinear -> XYZ(D50) -> XYZD50ToD65 -> sRGBLinear -> sRGB
105
+
106
+ let xyzIn = fromBaseSpace.id === "xyz";
107
+ let xyzOut = toBaseSpace.id === "xyz";
108
+ let throughXYZ = false;
109
+ let outputOklab = false;
110
+
111
+ // spaces are different
112
+ // check if we have a fast path
113
+ // this isn't supported for d50-based whitepoints
114
+ if (fromBaseSpace.id === "oklab") {
115
+ let mat = toBaseSpace.fromLMS_M;
116
+ if (!mat) {
117
+ // space doesn't support direct
118
+ // let's convert OKLab to XYZ and then use that
119
+ mat = XYZ.fromLMS_M;
120
+ throughXYZ = true;
121
+ xyzIn = true;
122
+ }
123
+ out = OKLab_to(out, mat, out);
124
+ } else if (toBaseSpace.id === "oklab") {
125
+ let mat = fromBaseSpace.toLMS_M;
126
+ if (!mat) {
127
+ // space doesn't support direct
128
+ throughXYZ = true;
129
+ outputOklab = true;
130
+ } else {
131
+ // direct from space to oklab
132
+ out = OKLab_from(out, mat, out);
133
+ }
134
+ } else {
135
+ throughXYZ = true;
136
+ }
137
+
138
+ if (throughXYZ) {
139
+ // First, convert to XYZ if we need to
140
+ if (!xyzIn) {
141
+ if (!fromBaseSpace.toXYZ_M)
142
+ throw new Error(`no toXYZ_M on ${fromBaseSpace.id}`);
143
+ out = transform(out, fromBaseSpace.toXYZ_M, out);
144
+ }
145
+
146
+ // Then, adapt D50 <-> D65 if we need to
147
+ if (fromBaseSpace.adapt) {
148
+ out = transform(out, fromBaseSpace.adapt.to, out);
149
+ } else if (toBaseSpace.adapt) {
150
+ out = transform(out, toBaseSpace.adapt.from, out);
151
+ }
152
+
153
+ // Now, convert XYZ to target if we need to
154
+ if (!xyzOut) {
155
+ if (outputOklab) {
156
+ out = OKLab_from(out, XYZ.toLMS_M, out);
157
+ } else {
158
+ if (!toBaseSpace.fromXYZ_M)
159
+ throw new Error(`no fromXYZ_M on ${toBaseSpace.id}`);
160
+ out = transform(out, toBaseSpace.fromXYZ_M, out);
161
+ }
162
+ }
163
+ }
164
+ }
165
+
166
+ // Now do the final transformation to the target space
167
+ // e.g. OKLab -> OKLCH or sRGBLinear -> sRGB
168
+ if (toBaseSpace !== toSpace) {
169
+ if (toSpace.fromBase) {
170
+ out = toSpace.fromBase(out, out);
171
+ } else {
172
+ throw new Error(`could not transform ${toBaseSpace.id} to ${toSpace.id}`);
173
+ }
174
+ }
175
+
176
+ return out;
177
+ };
178
+
179
+ // Calculate deltaE OK
180
+ // simple root sum of squares
181
+ export const deltaEOK = (oklab1, oklab2) => {
182
+ let dL = oklab1[0] - oklab2[0];
183
+ let da = oklab1[1] - oklab2[1];
184
+ let db = oklab1[2] - oklab2[2];
185
+ return Math.sqrt(dL * dL + da * da + db * db);
186
+ };