pts 0.12.8 → 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.
- package/README.md +92 -80
- package/dist/index.d.mts +6208 -1254
- package/dist/index.d.mts.map +1 -0
- package/dist/index.d.ts +6208 -1254
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9314 -10680
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +9266 -10611
- package/dist/index.mjs.map +1 -0
- package/dist/pts.js +9446 -10777
- package/dist/pts.js.map +1 -0
- package/dist/pts.min.js +3 -5
- package/dist/pts.min.js.map +1 -0
- package/package.json +91 -31
- package/src/Canvas.ts +1642 -0
- package/src/Color.ts +1109 -0
- package/src/Create.ts +1547 -0
- package/src/Dom.ts +940 -0
- package/src/Form.ts +312 -0
- package/src/Image.ts +722 -0
- package/src/LinearAlgebra.ts +530 -0
- package/src/Num.ts +1091 -0
- package/src/Op.ts +2127 -0
- package/src/Physics.ts +1233 -0
- package/src/Play.ts +861 -0
- package/src/Pt.ts +1303 -0
- package/src/Space.ts +898 -0
- package/src/Svg.ts +1573 -0
- package/src/Types.ts +301 -0
- package/src/Typography.ts +228 -0
- package/src/UI.ts +757 -0
- package/src/Util.ts +454 -0
- package/src/_module.ts +18 -0
- package/src/_script.ts +94 -0
- package/src/_triangulate.ts +884 -0
- package/src/uheprng.ts +153 -0
package/src/Color.ts
ADDED
|
@@ -0,0 +1,1109 @@
|
|
|
1
|
+
/*! Pts.js is licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
|
|
2
|
+
|
|
3
|
+
import { Pt, Group } from "./Pt";
|
|
4
|
+
import { Util } from "./Util";
|
|
5
|
+
import { Num, Geom } from "./Num";
|
|
6
|
+
import { type PtLike, type ColorType } from "./Types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Color is a subclass of Pt. Since a color in a color space is analogous to a point or vector in a space, you can apply all Pt operations to colors too. The Color class provides support for many color spaces like HSL and LAB.
|
|
10
|
+
* Convert non-RGB colors to RGB before using `.hex`, `.rgb`, or `.rgba` for rendering. These getters format the channels; they don't convert between color spaces.
|
|
11
|
+
* @example
|
|
12
|
+
* ```
|
|
13
|
+
* const color = Color.hsl(268, 0.37, 0.51);
|
|
14
|
+
* form.fill(Color.HSLtoRGB(color).rgb);
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export class Color extends Pt {
|
|
18
|
+
// XYZ property for Standard Observer 2deg, Daylight/sRGB illuminant D65
|
|
19
|
+
private static D65: PtLike = new Pt(95.047, 100, 108.883, 1);
|
|
20
|
+
|
|
21
|
+
protected _mode: ColorType = "rgb";
|
|
22
|
+
private _isNorm: boolean = false;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Value range for each color space
|
|
26
|
+
*/
|
|
27
|
+
static ranges: { [name: string]: Group } = {
|
|
28
|
+
rgb: new Group(new Pt(0, 255), new Pt(0, 255), new Pt(0, 255)),
|
|
29
|
+
hsl: new Group(new Pt(0, 360), new Pt(0, 1), new Pt(0, 1)),
|
|
30
|
+
hsb: new Group(new Pt(0, 360), new Pt(0, 1), new Pt(0, 1)),
|
|
31
|
+
lab: new Group(new Pt(0, 100), new Pt(-128, 127), new Pt(-128, 127)),
|
|
32
|
+
lch: new Group(new Pt(0, 100), new Pt(0, 100), new Pt(0, 360)),
|
|
33
|
+
luv: new Group(new Pt(0, 100), new Pt(-134, 220), new Pt(-140, 122)),
|
|
34
|
+
xyz: new Group(new Pt(0, 100), new Pt(0, 100), new Pt(0, 100)),
|
|
35
|
+
oklab: new Group(new Pt(0, 1), new Pt(-0.4, 0.4), new Pt(-0.4, 0.4)),
|
|
36
|
+
oklch: new Group(new Pt(0, 1), new Pt(0, 0.4), new Pt(0, 360)),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Create a Color. Same as creating a Pt. Optionally you may use [`Color.from`](#link) to create a color.
|
|
41
|
+
* @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties
|
|
42
|
+
*/
|
|
43
|
+
constructor(...args: any[]) {
|
|
44
|
+
super(...args);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create a Color object with 4 default dimensional values (1,1,1,1).
|
|
49
|
+
* @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties
|
|
50
|
+
*/
|
|
51
|
+
static from(...args: any[]): Color {
|
|
52
|
+
const p = [1, 1, 1, 1];
|
|
53
|
+
const c = Util.getArgs(args);
|
|
54
|
+
for (let i = 0, len = p.length; i < len; i++) {
|
|
55
|
+
if (i < c.length) p[i] = c[i];
|
|
56
|
+
}
|
|
57
|
+
return new Color(p);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Convert a rgb hex string like `"#FF0000"` or `"#F00"` to a Color object.
|
|
62
|
+
* @param hex a hex string, with optional '#' prefix
|
|
63
|
+
*/
|
|
64
|
+
static fromHex(hex: string): Color {
|
|
65
|
+
if (hex[0] == "#") hex = hex.substr(1); // remove '#' if needed
|
|
66
|
+
if (hex.length <= 4) {
|
|
67
|
+
const fn = (i: number) => hex[i] || "F";
|
|
68
|
+
const shortAlpha = hex.length === 4 ? `${fn(3)}${fn(3)}` : ""; // #RGBA
|
|
69
|
+
hex = `${fn(0)}${fn(0)}${fn(1)}${fn(1)}${fn(2)}${fn(2)}${shortAlpha}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let alpha = 1;
|
|
73
|
+
if (hex.length === 8) {
|
|
74
|
+
alpha = parseInt(hex.substr(6, 2), 16) / 255;
|
|
75
|
+
hex = hex.substring(0, 6);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const hexVal = parseInt(hex, 16);
|
|
79
|
+
return new Color(hexVal >> 16, (hexVal >> 8) & 0xff, hexVal & 0xff, alpha);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create RGB Color. RGB color ranges are (0...255, 0...255, 0...255) respectively. You may use [`Color.normalize`](#link) to convert the ranges to 0...1.
|
|
84
|
+
* @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties.
|
|
85
|
+
*/
|
|
86
|
+
static rgb(...args: any[]): Color {
|
|
87
|
+
return Color.from(...args).toMode("rgb");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create HSL Color. HSL color ranges are (0...360, 0...1, 0...1) respectively. You may use [`Color.normalize`](#link) to convert the ranges to 0...1.
|
|
92
|
+
* @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties.
|
|
93
|
+
*/
|
|
94
|
+
static hsl(...args: any[]): Color {
|
|
95
|
+
return Color.from(...args).toMode("hsl");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Create HSB Color. HSB color ranges are (0...360, 0...1, 0...1) respectively. You may use [`Color.normalize`](#link) to convert the ranges to 0...1.
|
|
100
|
+
* @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties.
|
|
101
|
+
*/
|
|
102
|
+
static hsb(...args: any[]): Color {
|
|
103
|
+
return Color.from(...args).toMode("hsb");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Create LAB Color. LAB color ranges are (0...100, -128...127, -128...127) respectively. You may use [`Color.normalize`](#link) to convert the ranges to 0...1.
|
|
108
|
+
* @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties.
|
|
109
|
+
*/
|
|
110
|
+
static lab(...args: any[]): Color {
|
|
111
|
+
return Color.from(...args).toMode("lab");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Create LCH Color. LCH color ranges are (0...100, 0...100, 0...360) respectively. You may use [`Color.normalize`](#link) to convert the ranges to 0...1.
|
|
116
|
+
* @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties.
|
|
117
|
+
*/
|
|
118
|
+
static lch(...args: any[]): Color {
|
|
119
|
+
return Color.from(...args).toMode("lch");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Create LUV Color. LUV color ranges are (0...100, -134...220, -140...122) respectively. You may use [`Color.normalize`](#link) to convert the ranges to 0...1.
|
|
124
|
+
* @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties.
|
|
125
|
+
*/
|
|
126
|
+
static luv(...args: any[]): Color {
|
|
127
|
+
return Color.from(...args).toMode("luv");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Create XYZ Color. XYZ color ranges are (0...100, 0...100, 0...100) respectively. You may use [`Color.normalize`](#link) to convert the ranges to 0...1.
|
|
132
|
+
* @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties.
|
|
133
|
+
*/
|
|
134
|
+
static xyz(...args: any[]): Color {
|
|
135
|
+
return Color.from(...args).toMode("xyz");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Create OKLAB Color. OKLAB color ranges are (0...1, -0.4...0.4, -0.4...0.4) respectively. You may use [`Color.normalize`](#link) to convert the ranges to 0...1.
|
|
140
|
+
* @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties.
|
|
141
|
+
*/
|
|
142
|
+
static oklab(...args: any[]): Color {
|
|
143
|
+
return Color.from(...args).toMode("oklab");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Create OKLCH Color. OKLCH color ranges are (0...1, 0...0.4, 0...360) respectively. You may use [`Color.normalize`](#link) to convert the ranges to 0...1.
|
|
148
|
+
* @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties.
|
|
149
|
+
*/
|
|
150
|
+
static oklch(...args: any[]): Color {
|
|
151
|
+
return Color.from(...args).toMode("oklch");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get a Color object whose values are the maximum of its mode.
|
|
156
|
+
* @param mode a mode string such as "rgb" or "lab"
|
|
157
|
+
* @example Color.maxValue("rgb") will return a rgb Color object with values (255,255,255)
|
|
158
|
+
*/
|
|
159
|
+
static maxValues(mode: string): Pt {
|
|
160
|
+
return Color.ranges[mode].zipSlice(1).$take([0, 1, 2]);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get a hex string such as "#FF0000". Same as `toString("hex")`.
|
|
165
|
+
* For non-RGB colors, convert to RGB first.
|
|
166
|
+
*/
|
|
167
|
+
public get hex(): string {
|
|
168
|
+
return this.toString("hex");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get a rgb string such as "rgb(255,0,0)". Same as `toString("rgb")`.
|
|
173
|
+
* For non-RGB colors, convert to RGB first, eg `Color.HSLtoRGB(color).rgb`.
|
|
174
|
+
*/
|
|
175
|
+
public get rgb(): string {
|
|
176
|
+
return this.toString("rgb");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get a rgba string such as "rgba(255,0,0,0.5)". Same as `toString("rgba")`.
|
|
181
|
+
* For non-RGB colors, convert to RGB first.
|
|
182
|
+
*/
|
|
183
|
+
public get rgba(): string {
|
|
184
|
+
return this.toString("rgba");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Clone this Color.
|
|
189
|
+
*/
|
|
190
|
+
clone(): Color {
|
|
191
|
+
const c = new Color(this);
|
|
192
|
+
c.toMode(this._mode);
|
|
193
|
+
c._isNorm = this._isNorm;
|
|
194
|
+
return c;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get a denormalized copy of a color, trusting the caller's flag over the
|
|
199
|
+
* color's own normalized state so that explicit conversion arguments win.
|
|
200
|
+
* Inlines the range math — this sits on the hot path of every conversion
|
|
201
|
+
* that takes normalized input.
|
|
202
|
+
*/
|
|
203
|
+
private static _denorm(c: Color): Color {
|
|
204
|
+
const cc = c.clone();
|
|
205
|
+
const ranges = Color.ranges[cc._mode];
|
|
206
|
+
for (let i = 0; i < 3; i++) {
|
|
207
|
+
const r = ranges[i];
|
|
208
|
+
cc[i] = r[0] + cc[i] * (r[1] - r[0]);
|
|
209
|
+
}
|
|
210
|
+
cc._isNorm = false;
|
|
211
|
+
return cc;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Normalize a freshly computed full-range color in place and flag it.
|
|
216
|
+
* Conversion outputs are always full-range, so no state check is needed.
|
|
217
|
+
*/
|
|
218
|
+
private static _normOut(c: Color): Color {
|
|
219
|
+
const ranges = Color.ranges[c._mode];
|
|
220
|
+
for (let i = 0; i < 3; i++) {
|
|
221
|
+
const r = ranges[i];
|
|
222
|
+
c[i] = (c[i] - r[0]) / (r[1] - r[0]);
|
|
223
|
+
}
|
|
224
|
+
c._isNorm = true;
|
|
225
|
+
return c;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Convert this color from current color space to another color space.
|
|
230
|
+
* @param mode a ColorType string: "rgb" "hsl" "hsb" "lab" "lch" "luv" "xyz";
|
|
231
|
+
* @param convert if `true`, convert this Color to the new color space specified in `mode`. Default is `false`, which only sets the color mode without converting color values.
|
|
232
|
+
*/
|
|
233
|
+
toMode(mode: ColorType, convert: boolean = false): this {
|
|
234
|
+
if (convert && mode !== this._mode) {
|
|
235
|
+
const fname = this._mode.toUpperCase() + "to" + mode.toUpperCase();
|
|
236
|
+
if ((Color as any)[fname]) {
|
|
237
|
+
this.to((Color as any)[fname](this, this._isNorm, this._isNorm));
|
|
238
|
+
} else {
|
|
239
|
+
throw new Error("Cannot convert color with " + fname);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
this._mode = mode;
|
|
244
|
+
return this;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Get this Color's mode.
|
|
249
|
+
*/
|
|
250
|
+
get mode(): ColorType {
|
|
251
|
+
return this._mode;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// rgb
|
|
255
|
+
/**
|
|
256
|
+
* the `r` value in RGB color mode. Same as `x`.
|
|
257
|
+
*/
|
|
258
|
+
get r(): number {
|
|
259
|
+
return this[0];
|
|
260
|
+
}
|
|
261
|
+
set r(n: number) {
|
|
262
|
+
this[0] = n;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* the `g` value in RGB color mode. Same as `y`.
|
|
267
|
+
*/
|
|
268
|
+
get g(): number {
|
|
269
|
+
return this[1];
|
|
270
|
+
}
|
|
271
|
+
set g(n: number) {
|
|
272
|
+
this[1] = n;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* the `b` value in RGB/LAB/HSB color mode. Same as `z`.
|
|
277
|
+
*/
|
|
278
|
+
get b(): number {
|
|
279
|
+
return this[2];
|
|
280
|
+
}
|
|
281
|
+
set b(n: number) {
|
|
282
|
+
this[2] = n;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// hsl, hsb
|
|
286
|
+
/**
|
|
287
|
+
* the `h` value in HSL/HSB or LCH color mode. Same as either `x` or `z` depending on current color mode.
|
|
288
|
+
*/
|
|
289
|
+
get h(): number {
|
|
290
|
+
return this._mode == "lch" || this._mode == "oklch" ? this[2] : this[0];
|
|
291
|
+
}
|
|
292
|
+
set h(n: number) {
|
|
293
|
+
const i = this._mode == "lch" || this._mode == "oklch" ? 2 : 0;
|
|
294
|
+
this[i] = n;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* the `s` value in HSL/HSB color mode. Same as `y`.
|
|
299
|
+
*/
|
|
300
|
+
get s(): number {
|
|
301
|
+
return this[1];
|
|
302
|
+
}
|
|
303
|
+
set s(n: number) {
|
|
304
|
+
this[1] = n;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* the `l` value in HSL or LCH/LAB color mode. Same as either `x` or `z` depending on current color mode.
|
|
309
|
+
*/
|
|
310
|
+
get l(): number {
|
|
311
|
+
return this._mode == "hsl" ? this[2] : this[0];
|
|
312
|
+
}
|
|
313
|
+
set l(n: number) {
|
|
314
|
+
const i = this._mode == "hsl" ? 2 : 0;
|
|
315
|
+
this[i] = n;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// lab, lch, luv
|
|
319
|
+
/**
|
|
320
|
+
* the `a` value in LAB color mode. Same as `y`.
|
|
321
|
+
*/
|
|
322
|
+
get a(): number {
|
|
323
|
+
return this[1];
|
|
324
|
+
}
|
|
325
|
+
set a(n: number) {
|
|
326
|
+
this[1] = n;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* the `c` value in LCH color mode. Same as `y`.
|
|
331
|
+
*/
|
|
332
|
+
get c(): number {
|
|
333
|
+
return this[1];
|
|
334
|
+
}
|
|
335
|
+
set c(n: number) {
|
|
336
|
+
this[1] = n;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* the `u` value in LUV color mode. Same as `y`.
|
|
341
|
+
*/
|
|
342
|
+
get u(): number {
|
|
343
|
+
return this[1];
|
|
344
|
+
}
|
|
345
|
+
set u(n: number) {
|
|
346
|
+
this[1] = n;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* the `v` value in LUV color mode. Same as `z`.
|
|
351
|
+
*/
|
|
352
|
+
get v(): number {
|
|
353
|
+
return this[2];
|
|
354
|
+
}
|
|
355
|
+
set v(n: number) {
|
|
356
|
+
this[2] = n;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Get alpha value
|
|
361
|
+
*/
|
|
362
|
+
set alpha(n: number) {
|
|
363
|
+
if (this.length > 3) this[3] = n;
|
|
364
|
+
}
|
|
365
|
+
get alpha(): number {
|
|
366
|
+
return this.length > 3 ? this[3] : 1;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Check if color values are normalized (between 0 to 1). If conversion is needed, use [`Color.normalize`](#link) function.
|
|
371
|
+
*/
|
|
372
|
+
get normalized(): boolean {
|
|
373
|
+
return this._isNorm;
|
|
374
|
+
}
|
|
375
|
+
set normalized(b: boolean) {
|
|
376
|
+
this._isNorm = b;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Normalize the color values to between 0 to 1, or revert it back to the min/max values in current color mode.
|
|
381
|
+
* @param toNorm a boolean value specifying whether to normalize (`true`) or revert (`false`)
|
|
382
|
+
*/
|
|
383
|
+
normalize(toNorm: boolean = true): Color {
|
|
384
|
+
if (this._isNorm == toNorm) return this;
|
|
385
|
+
|
|
386
|
+
const ranges = Color.ranges[this._mode];
|
|
387
|
+
|
|
388
|
+
for (let i = 0; i < 3; i++) {
|
|
389
|
+
this[i] = !toNorm
|
|
390
|
+
? Num.mapToRange(this[i], 0, 1, ranges[i][0], ranges[i][1])
|
|
391
|
+
: Num.mapToRange(this[i], ranges[i][0], ranges[i][1], 0, 1);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
this._isNorm = toNorm;
|
|
395
|
+
|
|
396
|
+
return this;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Like `normalize()` but returns a new Color.
|
|
401
|
+
* @param toNorm a boolean value specifying whether to normalize (`true`) or revert (`false`)
|
|
402
|
+
* @returns new Color
|
|
403
|
+
*/
|
|
404
|
+
$normalize(toNorm: boolean = true): Color {
|
|
405
|
+
return this.clone().normalize(toNorm);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Convert this Color to a string. It can be used to get a hex or rgb string for use in rendering.
|
|
410
|
+
* This formats the current channels without converting color spaces. Convert to RGB before requesting "hex", "rgb", or "rgba". The default "mode" format is for inspecting values, not for CSS rendering.
|
|
411
|
+
* @param format "hex", "rgb", "rgba", or "mode" which means using current color mode label. Default is "mode".
|
|
412
|
+
*/
|
|
413
|
+
toString(format: "hex" | "rgb" | "rgba" | "mode" = "mode"): string {
|
|
414
|
+
// CSS formats need full-range values; a normalized color would floor to 0
|
|
415
|
+
const v: Color | this =
|
|
416
|
+
this._isNorm && format !== "mode" ? Color._denorm(this) : this;
|
|
417
|
+
if (format == "hex") {
|
|
418
|
+
const _hex = (n: number) => {
|
|
419
|
+
const s = Math.floor(n).toString(16);
|
|
420
|
+
return s.length < 2 ? "0" + s : s;
|
|
421
|
+
};
|
|
422
|
+
return `#${_hex(v[0])}${_hex(v[1])}${_hex(v[2])}`;
|
|
423
|
+
} else if (format == "rgba") {
|
|
424
|
+
return `rgba(${Math.floor(v[0])},${Math.floor(v[1])},${Math.floor(v[2])},${this.alpha})`;
|
|
425
|
+
} else if (format == "rgb") {
|
|
426
|
+
return `rgb(${Math.floor(v[0])},${Math.floor(v[1])},${Math.floor(v[2])})`;
|
|
427
|
+
} else {
|
|
428
|
+
return `${this._mode}(${this[0]},${this[1]},${this[2]},${this.alpha})`;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* A static function to convert RGB to HSL.
|
|
434
|
+
* @param rgb a RGB Color
|
|
435
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
436
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
437
|
+
* @returns a new HSL Color
|
|
438
|
+
*/
|
|
439
|
+
static RGBtoHSL(
|
|
440
|
+
rgb: Color,
|
|
441
|
+
normalizedInput: boolean = false,
|
|
442
|
+
normalizedOutput: boolean = false,
|
|
443
|
+
): Color {
|
|
444
|
+
const [r, g, b] = !normalizedInput ? rgb.$normalize() : rgb;
|
|
445
|
+
|
|
446
|
+
const max = Math.max(r, g, b);
|
|
447
|
+
const min = Math.min(r, g, b);
|
|
448
|
+
let h = (max + min) / 2;
|
|
449
|
+
let s = h;
|
|
450
|
+
const l = h;
|
|
451
|
+
|
|
452
|
+
if (max == min) {
|
|
453
|
+
h = 0;
|
|
454
|
+
s = 0; // achromatic
|
|
455
|
+
} else {
|
|
456
|
+
const d = max - min;
|
|
457
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
458
|
+
|
|
459
|
+
h = 0;
|
|
460
|
+
if (max === r) {
|
|
461
|
+
h = (g - b) / d + (g < b ? 6 : 0);
|
|
462
|
+
} else if (max === g) {
|
|
463
|
+
h = (b - r) / d + 2;
|
|
464
|
+
} else if (max === b) {
|
|
465
|
+
h = (r - g) / d + 4;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const cc = Color.hsl(normalizedOutput ? h / 6 : h * 60, s, l, rgb.alpha);
|
|
470
|
+
if (normalizedOutput) cc.normalized = true;
|
|
471
|
+
return cc;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* A static function to convert HSL to RGB.
|
|
476
|
+
* @param hsl a HSL Color
|
|
477
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
478
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
479
|
+
* @returns a new RGB Color
|
|
480
|
+
*/
|
|
481
|
+
static HSLtoRGB(
|
|
482
|
+
hsl: Color,
|
|
483
|
+
normalizedInput: boolean = false,
|
|
484
|
+
normalizedOutput: boolean = false,
|
|
485
|
+
): Color {
|
|
486
|
+
let [h, s, l] = hsl;
|
|
487
|
+
if (!normalizedInput) h = h / 360;
|
|
488
|
+
h = h - Math.floor(h); // wrap hue into [0, 1)
|
|
489
|
+
|
|
490
|
+
const sc = normalizedOutput ? 1 : 255;
|
|
491
|
+
if (s == 0) {
|
|
492
|
+
const gray = Color.rgb(sc * l, sc * l, sc * l, hsl.alpha);
|
|
493
|
+
if (normalizedOutput) gray.normalized = true;
|
|
494
|
+
return gray;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const q = l <= 0.5 ? l * (1 + s) : l + s - l * s;
|
|
498
|
+
const p = 2 * l - q;
|
|
499
|
+
|
|
500
|
+
const convert = (t: number) => {
|
|
501
|
+
t = t < 0 ? t + 1 : t > 1 ? t - 1 : t;
|
|
502
|
+
if (t * 6 < 1) {
|
|
503
|
+
return p + (q - p) * t * 6;
|
|
504
|
+
} else if (t * 2 < 1) {
|
|
505
|
+
return q;
|
|
506
|
+
} else if (t * 3 < 2) {
|
|
507
|
+
return p + (q - p) * (2 / 3 - t) * 6;
|
|
508
|
+
} else {
|
|
509
|
+
return p;
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
const cc = Color.rgb(
|
|
514
|
+
sc * convert(h + 1 / 3),
|
|
515
|
+
sc * convert(h),
|
|
516
|
+
sc * convert(h - 1 / 3),
|
|
517
|
+
hsl.alpha,
|
|
518
|
+
);
|
|
519
|
+
if (normalizedOutput) cc.normalized = true;
|
|
520
|
+
return cc;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* A static function to convert RGB to HSB.
|
|
525
|
+
* @param rgb a RGB Color
|
|
526
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
527
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
528
|
+
* @returns a new HSB Color
|
|
529
|
+
*/
|
|
530
|
+
static RGBtoHSB(
|
|
531
|
+
rgb: Color,
|
|
532
|
+
normalizedInput: boolean = false,
|
|
533
|
+
normalizedOutput: boolean = false,
|
|
534
|
+
): Color {
|
|
535
|
+
const [r, g, b] = !normalizedInput ? rgb.$normalize() : rgb;
|
|
536
|
+
|
|
537
|
+
const max = Math.max(r, g, b);
|
|
538
|
+
const min = Math.min(r, g, b);
|
|
539
|
+
const d = max - min;
|
|
540
|
+
let h = 0;
|
|
541
|
+
const s = max === 0 ? 0 : d / max;
|
|
542
|
+
const v = max;
|
|
543
|
+
|
|
544
|
+
if (max != min) {
|
|
545
|
+
if (max === r) {
|
|
546
|
+
h = (g - b) / d + (g < b ? 6 : 0);
|
|
547
|
+
} else if (max === g) {
|
|
548
|
+
h = (b - r) / d + 2;
|
|
549
|
+
} else if (max === b) {
|
|
550
|
+
h = (r - g) / d + 4;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const cc = Color.hsb(normalizedOutput ? h / 6 : h * 60, s, v, rgb.alpha);
|
|
555
|
+
if (normalizedOutput) cc.normalized = true;
|
|
556
|
+
return cc;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* A static function to convert HSB to RGB.
|
|
561
|
+
* @param hsb a HSB Color
|
|
562
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
563
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
564
|
+
* @returns a new RGB Color
|
|
565
|
+
*/
|
|
566
|
+
static HSBtoRGB(
|
|
567
|
+
hsb: Color,
|
|
568
|
+
normalizedInput: boolean = false,
|
|
569
|
+
normalizedOutput: boolean = false,
|
|
570
|
+
): Color {
|
|
571
|
+
let [h, s, v] = hsb;
|
|
572
|
+
if (!normalizedInput) h = h / 360;
|
|
573
|
+
h = h - Math.floor(h); // wrap hue into [0, 1)
|
|
574
|
+
|
|
575
|
+
const i = Math.floor(h * 6);
|
|
576
|
+
const f = h * 6 - i;
|
|
577
|
+
const p = v * (1 - s);
|
|
578
|
+
const q = v * (1 - f * s);
|
|
579
|
+
const t = v * (1 - (1 - f) * s);
|
|
580
|
+
|
|
581
|
+
const pick = [
|
|
582
|
+
[v, t, p],
|
|
583
|
+
[q, v, p],
|
|
584
|
+
[p, v, t],
|
|
585
|
+
[p, q, v],
|
|
586
|
+
[t, p, v],
|
|
587
|
+
[v, p, q],
|
|
588
|
+
];
|
|
589
|
+
const c = pick[i % 6];
|
|
590
|
+
|
|
591
|
+
const sc = normalizedOutput ? 1 : 255;
|
|
592
|
+
|
|
593
|
+
const cc = Color.rgb(sc * c[0], sc * c[1], sc * c[2], hsb.alpha);
|
|
594
|
+
if (normalizedOutput) cc.normalized = true;
|
|
595
|
+
return cc;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* A static function to convert RGB to LAB.
|
|
600
|
+
* @param rgb a RGB Color
|
|
601
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
602
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
603
|
+
* @returns a new LAB Color
|
|
604
|
+
*/
|
|
605
|
+
static RGBtoLAB(
|
|
606
|
+
rgb: Color,
|
|
607
|
+
normalizedInput: boolean = false,
|
|
608
|
+
normalizedOutput: boolean = false,
|
|
609
|
+
): Color {
|
|
610
|
+
const c = normalizedInput ? Color._denorm(rgb) : rgb;
|
|
611
|
+
return Color.XYZtoLAB(Color.RGBtoXYZ(c), false, normalizedOutput);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* A static function to convert LAB to RGB.
|
|
616
|
+
* @param lab a LAB Color
|
|
617
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
618
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
619
|
+
* @returns a new RGB Color
|
|
620
|
+
*/
|
|
621
|
+
static LABtoRGB(
|
|
622
|
+
lab: Color,
|
|
623
|
+
normalizedInput: boolean = false,
|
|
624
|
+
normalizedOutput: boolean = false,
|
|
625
|
+
): Color {
|
|
626
|
+
const c = normalizedInput ? Color._denorm(lab) : lab;
|
|
627
|
+
return Color.XYZtoRGB(Color.LABtoXYZ(c), false, normalizedOutput);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* A static function to convert RGB to LCH.
|
|
632
|
+
* @param rgb a RGB Color
|
|
633
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
634
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
635
|
+
* @returns a new LCH Color
|
|
636
|
+
*/
|
|
637
|
+
static RGBtoLCH(
|
|
638
|
+
rgb: Color,
|
|
639
|
+
normalizedInput: boolean = false,
|
|
640
|
+
normalizedOutput: boolean = false,
|
|
641
|
+
): Color {
|
|
642
|
+
const c = normalizedInput ? Color._denorm(rgb) : rgb;
|
|
643
|
+
return Color.LABtoLCH(Color.RGBtoLAB(c), false, normalizedOutput);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* A static function to convert LCH to RGB.
|
|
648
|
+
* @param lch a LCH Color
|
|
649
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
650
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
651
|
+
* @returns a new RGB Color
|
|
652
|
+
*/
|
|
653
|
+
static LCHtoRGB(
|
|
654
|
+
lch: Color,
|
|
655
|
+
normalizedInput: boolean = false,
|
|
656
|
+
normalizedOutput: boolean = false,
|
|
657
|
+
): Color {
|
|
658
|
+
const c = normalizedInput ? Color._denorm(lch) : lch;
|
|
659
|
+
return Color.LABtoRGB(Color.LCHtoLAB(c), false, normalizedOutput);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* A static function to convert RGB to LUV.
|
|
664
|
+
* @param rgb a RGB Color
|
|
665
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
666
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
667
|
+
* @returns a new LUV Color
|
|
668
|
+
*/
|
|
669
|
+
static RGBtoLUV(
|
|
670
|
+
rgb: Color,
|
|
671
|
+
normalizedInput: boolean = false,
|
|
672
|
+
normalizedOutput: boolean = false,
|
|
673
|
+
): Color {
|
|
674
|
+
const c = normalizedInput ? Color._denorm(rgb) : rgb;
|
|
675
|
+
return Color.XYZtoLUV(Color.RGBtoXYZ(c), false, normalizedOutput);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* A static function to convert LUV to RGB.
|
|
680
|
+
* @param luv a LUV Color
|
|
681
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
682
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
683
|
+
* @returns a new RGB Color
|
|
684
|
+
*/
|
|
685
|
+
static LUVtoRGB(
|
|
686
|
+
luv: Color,
|
|
687
|
+
normalizedInput: boolean = false,
|
|
688
|
+
normalizedOutput: boolean = false,
|
|
689
|
+
): Color {
|
|
690
|
+
const c = normalizedInput ? Color._denorm(luv) : luv;
|
|
691
|
+
return Color.XYZtoRGB(Color.LUVtoXYZ(c), false, normalizedOutput);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* A static function to convert RGB to XYZ.
|
|
696
|
+
* @param rgb a RGB Color
|
|
697
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
698
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
699
|
+
* @returns a new XYZ Color
|
|
700
|
+
*/
|
|
701
|
+
static RGBtoXYZ(
|
|
702
|
+
rgb: Color,
|
|
703
|
+
normalizedInput: boolean = false,
|
|
704
|
+
normalizedOutput: boolean = false,
|
|
705
|
+
): Color {
|
|
706
|
+
const c = !normalizedInput ? rgb.$normalize() : rgb.clone();
|
|
707
|
+
|
|
708
|
+
for (let i = 0; i < 3; i++) {
|
|
709
|
+
c[i] =
|
|
710
|
+
c[i] > 0.04045 ? Math.pow((c[i] + 0.055) / 1.055, 2.4) : c[i] / 12.92;
|
|
711
|
+
c[i] = c[i] * 100;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const cc = Color.xyz(
|
|
715
|
+
c[0] * 0.4124564 + c[1] * 0.3575761 + c[2] * 0.1804375,
|
|
716
|
+
c[0] * 0.2126729 + c[1] * 0.7151522 + c[2] * 0.072175,
|
|
717
|
+
c[0] * 0.0193339 + c[1] * 0.119192 + c[2] * 0.9503041,
|
|
718
|
+
rgb.alpha,
|
|
719
|
+
);
|
|
720
|
+
|
|
721
|
+
return normalizedOutput ? Color._normOut(cc) : cc;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* A static function to convert XYZ to RGB.
|
|
726
|
+
* @param xyz a XYZ Color
|
|
727
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
728
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
729
|
+
* @returns a new RGB Color
|
|
730
|
+
*/
|
|
731
|
+
static XYZtoRGB(
|
|
732
|
+
xyz: Color,
|
|
733
|
+
normalizedInput: boolean = false,
|
|
734
|
+
normalizedOutput: boolean = false,
|
|
735
|
+
): Color {
|
|
736
|
+
const [x, y, z] = !normalizedInput ? xyz.$normalize() : xyz;
|
|
737
|
+
|
|
738
|
+
const rgb = [
|
|
739
|
+
x * 3.2406254773200533 +
|
|
740
|
+
y * -1.5372079722103187 +
|
|
741
|
+
z * -0.4986285986982479,
|
|
742
|
+
x * -0.9689307147293197 +
|
|
743
|
+
y * 1.8757560608852415 +
|
|
744
|
+
z * 0.041517523842953964,
|
|
745
|
+
x * 0.055710120445510616 +
|
|
746
|
+
y * -0.2040210505984867 +
|
|
747
|
+
z * 1.0569959422543882,
|
|
748
|
+
];
|
|
749
|
+
|
|
750
|
+
// convert xyz to rgb. Note that not all colors are visible in rgb, so here we bound rgb between 0 to 1
|
|
751
|
+
for (let i = 0; i < 3; i++) {
|
|
752
|
+
rgb[i] =
|
|
753
|
+
rgb[i] > 0.0031308
|
|
754
|
+
? 1.055 * Math.pow(rgb[i], 1 / 2.4) - 0.055
|
|
755
|
+
: 12.92 * rgb[i];
|
|
756
|
+
rgb[i] = Math.max(0, Math.min(1, rgb[i]));
|
|
757
|
+
if (!normalizedOutput) rgb[i] = Math.round(rgb[i] * 255);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const cc = Color.rgb(rgb[0], rgb[1], rgb[2], xyz.alpha);
|
|
761
|
+
if (normalizedOutput) cc.normalized = true;
|
|
762
|
+
return cc;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* A static function to convert XYZ to LAB.
|
|
767
|
+
* @param xyz a XYZ Color
|
|
768
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
769
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
770
|
+
* @returns a new LAB Color
|
|
771
|
+
*/
|
|
772
|
+
static XYZtoLAB(
|
|
773
|
+
xyz: Color,
|
|
774
|
+
normalizedInput: boolean = false,
|
|
775
|
+
normalizedOutput: boolean = false,
|
|
776
|
+
): Color {
|
|
777
|
+
const c = normalizedInput ? Color._denorm(xyz) : xyz.clone();
|
|
778
|
+
const eps = 216 / 24389;
|
|
779
|
+
const kap = 24389 / 27;
|
|
780
|
+
|
|
781
|
+
// adjust for D65
|
|
782
|
+
c.divide(Color.D65);
|
|
783
|
+
|
|
784
|
+
const fn = (n: number) => (n > eps ? Math.cbrt(n) : (kap * n + 16) / 116);
|
|
785
|
+
const cy = fn(c[1]);
|
|
786
|
+
|
|
787
|
+
const cc = Color.lab(
|
|
788
|
+
116 * cy - 16,
|
|
789
|
+
500 * (fn(c[0]) - cy),
|
|
790
|
+
200 * (cy - fn(c[2])),
|
|
791
|
+
xyz.alpha,
|
|
792
|
+
);
|
|
793
|
+
return normalizedOutput ? Color._normOut(cc) : cc;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* A static function to convert LAB to XYZ.
|
|
798
|
+
* @param lab a LAB Color
|
|
799
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
800
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
801
|
+
* @returns a new XYZ Color
|
|
802
|
+
*/
|
|
803
|
+
static LABtoXYZ(
|
|
804
|
+
lab: Color,
|
|
805
|
+
normalizedInput: boolean = false,
|
|
806
|
+
normalizedOutput: boolean = false,
|
|
807
|
+
): Color {
|
|
808
|
+
const c = normalizedInput ? Color._denorm(lab) : lab;
|
|
809
|
+
const y = (c[0] + 16) / 116;
|
|
810
|
+
const x = c[1] / 500 + y;
|
|
811
|
+
const z = y - c[2] / 200;
|
|
812
|
+
const eps = 216 / 24389;
|
|
813
|
+
const kap = 24389 / 27;
|
|
814
|
+
|
|
815
|
+
const d = Color.D65;
|
|
816
|
+
const xxx = Math.pow(x, 3);
|
|
817
|
+
const zzz = Math.pow(z, 3);
|
|
818
|
+
|
|
819
|
+
// adjusted
|
|
820
|
+
const cc = Color.xyz(
|
|
821
|
+
d[0] * (xxx > eps ? xxx : (116 * x - 16) / kap),
|
|
822
|
+
d[1] * (c[0] > kap * eps ? Math.pow((c[0] + 16) / 116, 3) : c[0] / kap),
|
|
823
|
+
d[2] * (zzz > eps ? zzz : (116 * z - 16) / kap),
|
|
824
|
+
lab.alpha,
|
|
825
|
+
);
|
|
826
|
+
|
|
827
|
+
return normalizedOutput ? Color._normOut(cc) : cc;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* A static function to convert XYZ to LUV.
|
|
832
|
+
* @param xyz a XYZ Color
|
|
833
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
834
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
835
|
+
* @returns a new LUV Color
|
|
836
|
+
*/
|
|
837
|
+
static XYZtoLUV(
|
|
838
|
+
xyz: Color,
|
|
839
|
+
normalizedInput: boolean = false,
|
|
840
|
+
normalizedOutput: boolean = false,
|
|
841
|
+
): Color {
|
|
842
|
+
let [x, y, z] = normalizedInput ? Color._denorm(xyz) : xyz;
|
|
843
|
+
// black has no chromaticity, so pin u'v' to avoid 0/0
|
|
844
|
+
const den = x + 15 * y + 3 * z;
|
|
845
|
+
const u = den === 0 ? 0 : (4 * x) / den;
|
|
846
|
+
const v = den === 0 ? 0 : (9 * y) / den;
|
|
847
|
+
|
|
848
|
+
const eps = 216 / 24389;
|
|
849
|
+
const kap = 24389 / 27;
|
|
850
|
+
y = y / 100;
|
|
851
|
+
const L = y > eps ? 116 * Math.cbrt(y) - 16 : kap * y;
|
|
852
|
+
|
|
853
|
+
const refU =
|
|
854
|
+
(4 * Color.D65[0]) /
|
|
855
|
+
(Color.D65[0] + 15 * Color.D65[1] + 3 * Color.D65[2]);
|
|
856
|
+
const refV =
|
|
857
|
+
(9 * Color.D65[1]) /
|
|
858
|
+
(Color.D65[0] + 15 * Color.D65[1] + 3 * Color.D65[2]);
|
|
859
|
+
|
|
860
|
+
const cc = Color.luv(
|
|
861
|
+
L,
|
|
862
|
+
13 * L * (u - refU),
|
|
863
|
+
13 * L * (v - refV),
|
|
864
|
+
xyz.alpha,
|
|
865
|
+
);
|
|
866
|
+
return normalizedOutput ? Color._normOut(cc) : cc;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* A static function to convert LUV to XYZ.
|
|
871
|
+
* @param luv a LUV Color
|
|
872
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
873
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
874
|
+
* @returns a new XYZ Color
|
|
875
|
+
*/
|
|
876
|
+
static LUVtoXYZ(
|
|
877
|
+
luv: Color,
|
|
878
|
+
normalizedInput: boolean = false,
|
|
879
|
+
normalizedOutput: boolean = false,
|
|
880
|
+
): Color {
|
|
881
|
+
let [l, u, v] = normalizedInput ? Color._denorm(luv) : luv;
|
|
882
|
+
|
|
883
|
+
const eps = 216 / 24389;
|
|
884
|
+
const kap = 24389 / 27;
|
|
885
|
+
// black carries no chromaticity information
|
|
886
|
+
if (l === 0) {
|
|
887
|
+
const black = Color.xyz(0, 0, 0, luv.alpha);
|
|
888
|
+
return normalizedOutput ? Color._normOut(black) : black;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
const fy = (l + 16) / 116;
|
|
892
|
+
let y = l > kap * eps ? fy * fy * fy : l / kap;
|
|
893
|
+
|
|
894
|
+
const refU =
|
|
895
|
+
(4 * Color.D65[0]) /
|
|
896
|
+
(Color.D65[0] + 15 * Color.D65[1] + 3 * Color.D65[2]);
|
|
897
|
+
const refV =
|
|
898
|
+
(9 * Color.D65[1]) /
|
|
899
|
+
(Color.D65[0] + 15 * Color.D65[1] + 3 * Color.D65[2]);
|
|
900
|
+
|
|
901
|
+
u = u / (13 * l) + refU;
|
|
902
|
+
v = v / (13 * l) + refV;
|
|
903
|
+
|
|
904
|
+
y = y * 100;
|
|
905
|
+
const x = (-1 * (9 * y * u)) / ((u - 4) * v - u * v);
|
|
906
|
+
const z = (9 * y - 15 * v * y - v * x) / (3 * v);
|
|
907
|
+
|
|
908
|
+
const cc = Color.xyz(x, y, z, luv.alpha);
|
|
909
|
+
return normalizedOutput ? Color._normOut(cc) : cc;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* A static function to convert LAB to LCH.
|
|
914
|
+
* @param lab a LAB Color
|
|
915
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
916
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
917
|
+
* @returns a new LCH Color
|
|
918
|
+
*/
|
|
919
|
+
static LABtoLCH(
|
|
920
|
+
lab: Color,
|
|
921
|
+
normalizedInput: boolean = false,
|
|
922
|
+
normalizedOutput: boolean = false,
|
|
923
|
+
): Color {
|
|
924
|
+
const c = normalizedInput ? Color._denorm(lab) : lab;
|
|
925
|
+
const h = Geom.toDegree(Geom.boundRadian(Math.atan2(c[2], c[1]))); // 0 to 360 degrees
|
|
926
|
+
const cc = Color.lch(
|
|
927
|
+
c[0],
|
|
928
|
+
Math.sqrt(c[1] * c[1] + c[2] * c[2]),
|
|
929
|
+
h,
|
|
930
|
+
lab.alpha,
|
|
931
|
+
);
|
|
932
|
+
return normalizedOutput ? Color._normOut(cc) : cc;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* A static function to convert LCH to LAB.
|
|
937
|
+
* @param lch a LCH Color
|
|
938
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
939
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
940
|
+
* @returns a new LAB Color
|
|
941
|
+
*/
|
|
942
|
+
static LCHtoLAB(
|
|
943
|
+
lch: Color,
|
|
944
|
+
normalizedInput: boolean = false,
|
|
945
|
+
normalizedOutput: boolean = false,
|
|
946
|
+
): Color {
|
|
947
|
+
const c = normalizedInput ? Color._denorm(lch) : lch;
|
|
948
|
+
const rad = Geom.toRadian(c[2]);
|
|
949
|
+
const cc = Color.lab(
|
|
950
|
+
c[0],
|
|
951
|
+
Math.cos(rad) * c[1],
|
|
952
|
+
Math.sin(rad) * c[1],
|
|
953
|
+
lch.alpha,
|
|
954
|
+
);
|
|
955
|
+
return normalizedOutput ? Color._normOut(cc) : cc;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* A static function to convert RGB to OKLAB (Ottosson 2020, as specified in CSS Color 4). OKLAB improves on LAB's perceptual uniformity, especially hue stability in blues.
|
|
960
|
+
* @param rgb a RGB Color
|
|
961
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
962
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
963
|
+
* @returns a new OKLAB Color
|
|
964
|
+
*/
|
|
965
|
+
static RGBtoOKLAB(
|
|
966
|
+
rgb: Color,
|
|
967
|
+
normalizedInput: boolean = false,
|
|
968
|
+
normalizedOutput: boolean = false,
|
|
969
|
+
): Color {
|
|
970
|
+
const c = !normalizedInput ? rgb.$normalize() : rgb;
|
|
971
|
+
const lin = (v: number) =>
|
|
972
|
+
v > 0.04045 ? Math.pow((v + 0.055) / 1.055, 2.4) : v / 12.92;
|
|
973
|
+
const r = lin(c[0]);
|
|
974
|
+
const g = lin(c[1]);
|
|
975
|
+
const b = lin(c[2]);
|
|
976
|
+
|
|
977
|
+
// linear sRGB -> LMS cone response, then cube root
|
|
978
|
+
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
979
|
+
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
980
|
+
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
981
|
+
|
|
982
|
+
const cc = Color.oklab(
|
|
983
|
+
0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
|
|
984
|
+
1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
|
|
985
|
+
0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
|
|
986
|
+
rgb.alpha,
|
|
987
|
+
);
|
|
988
|
+
return normalizedOutput ? Color._normOut(cc) : cc;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/**
|
|
992
|
+
* A static function to convert OKLAB to RGB. Out-of-gamut results are clamped to the sRGB range.
|
|
993
|
+
* @param oklab an OKLAB Color
|
|
994
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
995
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
996
|
+
* @returns a new RGB Color
|
|
997
|
+
*/
|
|
998
|
+
static OKLABtoRGB(
|
|
999
|
+
oklab: Color,
|
|
1000
|
+
normalizedInput: boolean = false,
|
|
1001
|
+
normalizedOutput: boolean = false,
|
|
1002
|
+
): Color {
|
|
1003
|
+
const c = normalizedInput ? Color._denorm(oklab) : oklab;
|
|
1004
|
+
|
|
1005
|
+
const l_ = c[0] + 0.3963377774 * c[1] + 0.2158037573 * c[2];
|
|
1006
|
+
const m_ = c[0] - 0.1055613458 * c[1] - 0.0638541728 * c[2];
|
|
1007
|
+
const s_ = c[0] - 0.0894841775 * c[1] - 1.291485548 * c[2];
|
|
1008
|
+
const l = l_ * l_ * l_;
|
|
1009
|
+
const m = m_ * m_ * m_;
|
|
1010
|
+
const s = s_ * s_ * s_;
|
|
1011
|
+
|
|
1012
|
+
const rgb = [
|
|
1013
|
+
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
|
1014
|
+
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
|
1015
|
+
-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s,
|
|
1016
|
+
];
|
|
1017
|
+
|
|
1018
|
+
for (let i = 0; i < 3; i++) {
|
|
1019
|
+
rgb[i] =
|
|
1020
|
+
rgb[i] > 0.0031308
|
|
1021
|
+
? 1.055 * Math.pow(rgb[i], 1 / 2.4) - 0.055
|
|
1022
|
+
: 12.92 * rgb[i];
|
|
1023
|
+
rgb[i] = Math.max(0, Math.min(1, rgb[i]));
|
|
1024
|
+
if (!normalizedOutput) rgb[i] = Math.round(rgb[i] * 255);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
const cc = Color.rgb(rgb[0], rgb[1], rgb[2], oklab.alpha);
|
|
1028
|
+
if (normalizedOutput) cc.normalized = true;
|
|
1029
|
+
return cc;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
/**
|
|
1033
|
+
* A static function to convert RGB to OKLCH, the cylindrical form of OKLAB as specified in CSS Color 4.
|
|
1034
|
+
* @param rgb a RGB Color
|
|
1035
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
1036
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
1037
|
+
* @returns a new OKLCH Color
|
|
1038
|
+
*/
|
|
1039
|
+
static RGBtoOKLCH(
|
|
1040
|
+
rgb: Color,
|
|
1041
|
+
normalizedInput: boolean = false,
|
|
1042
|
+
normalizedOutput: boolean = false,
|
|
1043
|
+
): Color {
|
|
1044
|
+
const c = normalizedInput ? Color._denorm(rgb) : rgb;
|
|
1045
|
+
return Color.OKLABtoOKLCH(Color.RGBtoOKLAB(c), false, normalizedOutput);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* A static function to convert OKLCH to RGB. Out-of-gamut results are clamped to the sRGB range.
|
|
1050
|
+
* @param oklch an OKLCH Color
|
|
1051
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
1052
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
1053
|
+
* @returns a new RGB Color
|
|
1054
|
+
*/
|
|
1055
|
+
static OKLCHtoRGB(
|
|
1056
|
+
oklch: Color,
|
|
1057
|
+
normalizedInput: boolean = false,
|
|
1058
|
+
normalizedOutput: boolean = false,
|
|
1059
|
+
): Color {
|
|
1060
|
+
const c = normalizedInput ? Color._denorm(oklch) : oklch;
|
|
1061
|
+
return Color.OKLABtoRGB(Color.OKLCHtoOKLAB(c), false, normalizedOutput);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/**
|
|
1065
|
+
* A static function to convert OKLAB to OKLCH.
|
|
1066
|
+
* @param oklab an OKLAB Color
|
|
1067
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
1068
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
1069
|
+
* @returns a new OKLCH Color
|
|
1070
|
+
*/
|
|
1071
|
+
static OKLABtoOKLCH(
|
|
1072
|
+
oklab: Color,
|
|
1073
|
+
normalizedInput: boolean = false,
|
|
1074
|
+
normalizedOutput: boolean = false,
|
|
1075
|
+
): Color {
|
|
1076
|
+
const c = normalizedInput ? Color._denorm(oklab) : oklab;
|
|
1077
|
+
const h = Geom.toDegree(Geom.boundRadian(Math.atan2(c[2], c[1]))); // 0 to 360 degrees
|
|
1078
|
+
const cc = Color.oklch(
|
|
1079
|
+
c[0],
|
|
1080
|
+
Math.sqrt(c[1] * c[1] + c[2] * c[2]),
|
|
1081
|
+
h,
|
|
1082
|
+
oklab.alpha,
|
|
1083
|
+
);
|
|
1084
|
+
return normalizedOutput ? Color._normOut(cc) : cc;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* A static function to convert OKLCH to OKLAB.
|
|
1089
|
+
* @param oklch an OKLCH Color
|
|
1090
|
+
* @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`.
|
|
1091
|
+
* @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`.
|
|
1092
|
+
* @returns a new OKLAB Color
|
|
1093
|
+
*/
|
|
1094
|
+
static OKLCHtoOKLAB(
|
|
1095
|
+
oklch: Color,
|
|
1096
|
+
normalizedInput: boolean = false,
|
|
1097
|
+
normalizedOutput: boolean = false,
|
|
1098
|
+
): Color {
|
|
1099
|
+
const c = normalizedInput ? Color._denorm(oklch) : oklch;
|
|
1100
|
+
const rad = Geom.toRadian(c[2]);
|
|
1101
|
+
const cc = Color.oklab(
|
|
1102
|
+
c[0],
|
|
1103
|
+
Math.cos(rad) * c[1],
|
|
1104
|
+
Math.sin(rad) * c[1],
|
|
1105
|
+
oklch.alpha,
|
|
1106
|
+
);
|
|
1107
|
+
return normalizedOutput ? Color._normOut(cc) : cc;
|
|
1108
|
+
}
|
|
1109
|
+
}
|