toosoon-utils 1.4.0 → 2.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 +27 -23
- package/package.json +8 -4
- package/tsconfig.json +1 -3
- package/lib/classes/_pool.d.ts +0 -56
- package/lib/classes/_pool.js +0 -92
- package/lib/classes/color-scale.d.ts +0 -52
- package/lib/classes/color-scale.js +0 -160
- package/lib/classes/frame-rate.d.ts +0 -25
- package/lib/classes/frame-rate.js +0 -48
- package/lib/colors.d.ts +0 -155
- package/lib/colors.js +0 -367
- package/lib/constants.d.ts +0 -162
- package/lib/constants.js +0 -170
- package/lib/dom.d.ts +0 -25
- package/lib/dom.js +0 -47
- package/lib/files.d.ts +0 -14
- package/lib/files.js +0 -38
- package/lib/functions.d.ts +0 -22
- package/lib/functions.js +0 -53
- package/lib/geometry.d.ts +0 -89
- package/lib/geometry.js +0 -128
- package/lib/index.d.ts +0 -10
- package/lib/index.js +0 -39
- package/lib/maths.d.ts +0 -161
- package/lib/maths.js +0 -219
- package/lib/now.d.ts +0 -5
- package/lib/now.js +0 -28
- package/lib/prng.d.ts +0 -124
- package/lib/prng.js +0 -234
- package/lib/random.d.ts +0 -91
- package/lib/random.js +0 -162
- package/lib/strings.d.ts +0 -14
- package/lib/strings.js +0 -18
- package/lib/tsconfig.tsbuildinfo +0 -1
- package/lib/types.d.ts +0 -18
- package/lib/types.js +0 -1
- package/src/classes/_pool.ts +0 -92
- package/src/classes/color-scale.ts +0 -181
- package/src/classes/frame-rate.ts +0 -49
- package/src/colors.ts +0 -389
- package/src/constants.ts +0 -172
- package/src/dom.ts +0 -50
- package/src/files.ts +0 -42
- package/src/functions.ts +0 -56
- package/src/geometry.ts +0 -160
- package/src/maths.ts +0 -241
- package/src/prng.ts +0 -250
- package/src/random.ts +0 -162
- package/src/strings.ts +0 -19
- package/src/types.ts +0 -33
package/src/classes/_pool.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
// *********************
|
|
2
|
-
// WIP
|
|
3
|
-
// *********************
|
|
4
|
-
|
|
5
|
-
export type PoolSettings = {
|
|
6
|
-
max?: number;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export const defaultSettings: Required<PoolSettings> = {
|
|
10
|
-
max: Infinity
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
interface PoolItem {
|
|
14
|
-
setup?: () => void;
|
|
15
|
-
reset?: () => void;
|
|
16
|
-
dispose?: () => void;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Abstract class for manipulating pool items
|
|
21
|
-
*
|
|
22
|
-
* @exports
|
|
23
|
-
* @class Pool
|
|
24
|
-
*/
|
|
25
|
-
export default abstract class Pool<I extends PoolItem> {
|
|
26
|
-
public items: I[] = [];
|
|
27
|
-
public pool: I[] = [];
|
|
28
|
-
|
|
29
|
-
settings: Required<PoolSettings> = { ...defaultSettings };
|
|
30
|
-
|
|
31
|
-
constructor(settings: PoolSettings = { ...defaultSettings }) {
|
|
32
|
-
this.settings = Object.assign(this.settings, settings);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Abstract method to implement custom item creation
|
|
37
|
-
*
|
|
38
|
-
* @returns {PoolItem}
|
|
39
|
-
*/
|
|
40
|
-
protected abstract create(): I;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Add an item to the active items
|
|
44
|
-
*
|
|
45
|
-
* @param {PoolItem} item Item to add to the active items
|
|
46
|
-
*/
|
|
47
|
-
protected add(item: I): void {
|
|
48
|
-
this.items.push(item);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Remove an item from the active items
|
|
53
|
-
*
|
|
54
|
-
* @param {PoolItem} item Item to remove from the active items
|
|
55
|
-
*/
|
|
56
|
-
protected remove(item: I): void {
|
|
57
|
-
this.items = this.items.filter((_item) => _item !== item);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Return an item from pool or create a new one
|
|
62
|
-
*
|
|
63
|
-
* @returns {PoolItem|undefined}
|
|
64
|
-
*/
|
|
65
|
-
public get(): I | undefined {
|
|
66
|
-
if (this.items.length >= this.settings.max) return;
|
|
67
|
-
const item = this.pool.pop() ?? this.create();
|
|
68
|
-
item.setup?.();
|
|
69
|
-
this.add(item);
|
|
70
|
-
return item;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Release an item from the active items and add it to the pool
|
|
75
|
-
*
|
|
76
|
-
* @param {PoolItem} item Item to release
|
|
77
|
-
*/
|
|
78
|
-
public release(item: I): void {
|
|
79
|
-
this.pool.push(item);
|
|
80
|
-
item.reset?.();
|
|
81
|
-
this.remove(item);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Dispose all items
|
|
86
|
-
*/
|
|
87
|
-
public dispose(): void {
|
|
88
|
-
[...this.items, ...this.pool].forEach((item: I) => item.dispose?.());
|
|
89
|
-
this.items = [];
|
|
90
|
-
this.pool = [];
|
|
91
|
-
}
|
|
92
|
-
}
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import { lerp, triLerp } from '../maths';
|
|
2
|
-
import { hclToRgb, hsbToRgb, hslToRgb, normalizeColor, rgbToHcl, rgbToHsb, rgbToHsl } from '../colors';
|
|
3
|
-
import { ColorRepresentation } from '../types';
|
|
4
|
-
|
|
5
|
-
export type ColorScaleSettings =
|
|
6
|
-
| {
|
|
7
|
-
colorSpace: 'rgb' | 'hsl' | 'hsb';
|
|
8
|
-
}
|
|
9
|
-
| {
|
|
10
|
-
colorSpace: 'hcl';
|
|
11
|
-
mode?: 'qualitative' | 'sequential' | 'diverging';
|
|
12
|
-
triangular?: number;
|
|
13
|
-
powerStrength?: number;
|
|
14
|
-
hueOffset?: number;
|
|
15
|
-
chromaOffset?: number;
|
|
16
|
-
luminanceOffset?: number;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export const defaultSettings: Required<ColorScaleSettings> = {
|
|
20
|
-
colorSpace: 'rgb'
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Utility class for generating color scales and interpolating between colors
|
|
25
|
-
*
|
|
26
|
-
* @exports
|
|
27
|
-
* @class ColorScale
|
|
28
|
-
*/
|
|
29
|
-
export default class ColorScale {
|
|
30
|
-
/**
|
|
31
|
-
* Array of colors composing the color scale
|
|
32
|
-
*/
|
|
33
|
-
public colors: Array<[number, number, number]> = [];
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @param {ColorRepresentation} input Input color representation
|
|
37
|
-
* @param {ColorRepresentation} target Target color representation
|
|
38
|
-
* @param {number} [length=5] Amount of colors composing the color scale
|
|
39
|
-
* @param {ColorScaleSettings} [settings] Color scale generation settings
|
|
40
|
-
*/
|
|
41
|
-
constructor(
|
|
42
|
-
input: ColorRepresentation,
|
|
43
|
-
target: ColorRepresentation,
|
|
44
|
-
length: number = 5,
|
|
45
|
-
settings: ColorScaleSettings = { ...defaultSettings }
|
|
46
|
-
) {
|
|
47
|
-
this.colors = ColorScale.generate(input, target, length, settings);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Static method for generating a color scale
|
|
52
|
-
*
|
|
53
|
-
* @param {ColorRepresentation} input Input color representation
|
|
54
|
-
* @param {ColorRepresentation} target Target color representation
|
|
55
|
-
* @param {number} length Amount of colors composing the color scale
|
|
56
|
-
* @param {ColorScaleSettings} [settings] Color scale generation settings
|
|
57
|
-
* @returns {Array<[number, number, number]>} Color scale colors
|
|
58
|
-
*/
|
|
59
|
-
static generate(
|
|
60
|
-
input: ColorRepresentation,
|
|
61
|
-
target: ColorRepresentation,
|
|
62
|
-
length: number,
|
|
63
|
-
settings: ColorScaleSettings = { ...defaultSettings }
|
|
64
|
-
): Array<[number, number, number]> {
|
|
65
|
-
const colors: Array<[number, number, number]> = [];
|
|
66
|
-
|
|
67
|
-
const inputColor = normalizeColor(input);
|
|
68
|
-
const targetColor = normalizeColor(target);
|
|
69
|
-
|
|
70
|
-
for (let i = 0; i < length; i++) {
|
|
71
|
-
const value = i / Math.floor(length);
|
|
72
|
-
colors.push(ColorScale.interpolate(inputColor, targetColor, value, settings));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return colors;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Static method for interpolating between colors
|
|
80
|
-
*
|
|
81
|
-
* @param {[number,number,number]} inputColor Input color
|
|
82
|
-
* @param {[number,number,number]} targetColor Target color
|
|
83
|
-
* @param {number} value Interpolation normalized value
|
|
84
|
-
* @param {ColorScaleSettings} [settings] Color scale settings
|
|
85
|
-
* @returns {[number,number,number]} Interpolated color
|
|
86
|
-
*/
|
|
87
|
-
static interpolate(
|
|
88
|
-
inputColor: [number, number, number],
|
|
89
|
-
targetColor: [number, number, number],
|
|
90
|
-
value: number,
|
|
91
|
-
settings: ColorScaleSettings = { ...defaultSettings }
|
|
92
|
-
): [number, number, number] {
|
|
93
|
-
switch (settings.colorSpace) {
|
|
94
|
-
case 'rgb': {
|
|
95
|
-
const r = lerp(value, inputColor[0], targetColor[0]);
|
|
96
|
-
const g = lerp(value, inputColor[1], targetColor[1]);
|
|
97
|
-
const b = lerp(value, inputColor[2], targetColor[2]);
|
|
98
|
-
return [r, g, b];
|
|
99
|
-
}
|
|
100
|
-
case 'hsl': {
|
|
101
|
-
const inputHsl = rgbToHsl(inputColor);
|
|
102
|
-
const targetHsl = rgbToHsl(targetColor);
|
|
103
|
-
const h = lerp(value, inputHsl[0], targetHsl[0]);
|
|
104
|
-
const s = lerp(value, inputHsl[1], targetHsl[1]);
|
|
105
|
-
const l = lerp(value, inputHsl[2], targetHsl[2]);
|
|
106
|
-
return hslToRgb([h, s, l]);
|
|
107
|
-
}
|
|
108
|
-
case 'hsb': {
|
|
109
|
-
const inputHsb = rgbToHsb(inputColor);
|
|
110
|
-
const targetHsb = rgbToHsb(targetColor);
|
|
111
|
-
const h = lerp(value, inputHsb[0], targetHsb[0]);
|
|
112
|
-
const s = lerp(value, inputHsb[1], targetHsb[1]);
|
|
113
|
-
const b = lerp(value, inputHsb[2], targetHsb[2]);
|
|
114
|
-
return hsbToRgb([h, s, b]);
|
|
115
|
-
}
|
|
116
|
-
case 'hcl':
|
|
117
|
-
const inputHcl = rgbToHcl(inputColor);
|
|
118
|
-
const targetHcl = rgbToHcl(targetColor);
|
|
119
|
-
const powerValue = Math.pow(value, settings.powerStrength ?? 1);
|
|
120
|
-
|
|
121
|
-
const h1 = inputHcl[0];
|
|
122
|
-
const c1 = inputHcl[1];
|
|
123
|
-
const l1 = inputHcl[2];
|
|
124
|
-
const h2 = targetHcl[0] + (settings.hueOffset ?? 0);
|
|
125
|
-
const c2 = targetHcl[1] + (settings.chromaOffset ?? 0);
|
|
126
|
-
const l2 = targetHcl[2] + (settings.luminanceOffset ?? 0);
|
|
127
|
-
|
|
128
|
-
let h, c, l;
|
|
129
|
-
|
|
130
|
-
// HCL color palettes
|
|
131
|
-
// -> https://colorspace.r-forge.r-project.org/articles/hcl_palettes.html
|
|
132
|
-
if (settings.mode === 'qualitative') {
|
|
133
|
-
/**
|
|
134
|
-
* Qualitative
|
|
135
|
-
* Designed for coding categorical information,
|
|
136
|
-
* where no particular ordering of categories is available
|
|
137
|
-
* and every color should receive the same perceptual weight.
|
|
138
|
-
*
|
|
139
|
-
* - Hue: Linear
|
|
140
|
-
* - Chroma: Constant
|
|
141
|
-
* - Luminance: Constant
|
|
142
|
-
*/
|
|
143
|
-
h = lerp(value, h1, h2);
|
|
144
|
-
c = c1;
|
|
145
|
-
l = l1;
|
|
146
|
-
} else if (settings.mode === 'sequential') {
|
|
147
|
-
/**
|
|
148
|
-
* Sequential
|
|
149
|
-
* Designed for coding ordered/numeric information,
|
|
150
|
-
* going from high to low (or vice versa).
|
|
151
|
-
*
|
|
152
|
-
* - Hue: Constant | Linear
|
|
153
|
-
* - Chroma: Linear (+power) | Triangular (+power)
|
|
154
|
-
* - Luminance: Linear (+power)
|
|
155
|
-
*/
|
|
156
|
-
h = lerp(value, h1, h2);
|
|
157
|
-
c = settings.triangular ? triLerp(powerValue, c1, c2, settings.triangular) : lerp(powerValue, c1, c2);
|
|
158
|
-
l = lerp(powerValue, l1, l2);
|
|
159
|
-
} else if (settings.mode === 'diverging') {
|
|
160
|
-
/**
|
|
161
|
-
* Diverging
|
|
162
|
-
* Designed for coding ordered/numeric information around a central neutral value,
|
|
163
|
-
* where colors diverge from neutral to two extremes.
|
|
164
|
-
*
|
|
165
|
-
* - Hue: Constants (x2)
|
|
166
|
-
* - Chroma: Linear (+power) | Triangular (+power)
|
|
167
|
-
* - Luminance: Linear (+power)
|
|
168
|
-
*/
|
|
169
|
-
h = value < 0.5 ? h1 : value > 0.5 ? h2 : lerp(0.5, h1, h2);
|
|
170
|
-
c = settings.triangular ? triLerp(powerValue, c1, c2, settings.triangular) : lerp(powerValue, c1, c2);
|
|
171
|
-
l = lerp(powerValue, l1, l2);
|
|
172
|
-
} else {
|
|
173
|
-
h = lerp(value, h1, h2);
|
|
174
|
-
c = lerp(value, c1, c2);
|
|
175
|
-
l = lerp(value, l1, l2);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return hclToRgb([h, c, l]);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { now } from '../functions';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Utility class for controlling FPS calls
|
|
5
|
-
*
|
|
6
|
-
* @exports
|
|
7
|
-
* @class FrameRate
|
|
8
|
-
*/
|
|
9
|
-
export default class FrameRate {
|
|
10
|
-
private _fps: number;
|
|
11
|
-
private interval = 0;
|
|
12
|
-
private time = 0;
|
|
13
|
-
private elapsedTime = 0;
|
|
14
|
-
private lastUpdate = 0;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @param {number} [fps=30] Frame per second limit
|
|
18
|
-
*/
|
|
19
|
-
constructor(fps: number = 30) {
|
|
20
|
-
this._fps = fps;
|
|
21
|
-
this.fps = fps;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Return true if elapsed time since last update is higher than current FPS
|
|
26
|
-
*
|
|
27
|
-
* @returns {boolean}
|
|
28
|
-
*/
|
|
29
|
-
update(): boolean {
|
|
30
|
-
this.time = now();
|
|
31
|
-
this.elapsedTime = this.time - this.lastUpdate;
|
|
32
|
-
|
|
33
|
-
if (this.elapsedTime < this.interval) {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
this.lastUpdate = this.time - (this.elapsedTime % this.interval);
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
get fps(): number {
|
|
42
|
-
return this._fps;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
set fps(fps: number) {
|
|
46
|
-
this._fps = fps;
|
|
47
|
-
this.interval = 1000 / fps;
|
|
48
|
-
}
|
|
49
|
-
}
|
package/src/colors.ts
DELETED
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
import { W3CX11 } from './constants';
|
|
2
|
-
import { toDegrees, toRadians } from './geometry';
|
|
3
|
-
import { clamp } from './maths';
|
|
4
|
-
import { ColorName, ColorRepresentation } from './types';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Normalize a color representation into RGB
|
|
8
|
-
*
|
|
9
|
-
* @param {ColorRepresentation} color Color representation
|
|
10
|
-
* @returns {[number,number,number]} Normalized RGB color
|
|
11
|
-
*/
|
|
12
|
-
export function normalizeColor(color: ColorRepresentation): [number, number, number] {
|
|
13
|
-
if (typeof color === 'string') {
|
|
14
|
-
return hexToRgb(W3CX11[color as ColorName] ?? color);
|
|
15
|
-
} else if (typeof color === 'number') {
|
|
16
|
-
return hexToRgb(color);
|
|
17
|
-
} else {
|
|
18
|
-
return color;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// ******************************************
|
|
23
|
-
// RGB & Hexadecimal color spaces
|
|
24
|
-
// ******************************************
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Normalize an hexadecimal string
|
|
28
|
-
*
|
|
29
|
-
* @param {string} hex Hexadecimal string
|
|
30
|
-
* @returns {string} Normalized hexadecimal string
|
|
31
|
-
*/
|
|
32
|
-
export function normalizeHexString(hex: string): string {
|
|
33
|
-
let match: RegExpMatchArray | null;
|
|
34
|
-
let result: string = '000000';
|
|
35
|
-
hex = hex.toLocaleLowerCase();
|
|
36
|
-
|
|
37
|
-
if ((match = hex.match(/(#|0x)?([a-f0-9]{6})/i))) {
|
|
38
|
-
result = match[2];
|
|
39
|
-
} else if ((match = hex.match(/^#?([a-f0-9])([a-f0-9])([a-f0-9])$/i))) {
|
|
40
|
-
result = match[1] + match[1] + match[2] + match[2] + match[3] + match[3];
|
|
41
|
-
} else if ((match = hex.match(/rgb\(\s*(\d*)\s*,\s*(\d*)\s*,\s*(\d*)\s*\)/))) {
|
|
42
|
-
result =
|
|
43
|
-
parseInt(match[1]).toString(16).padStart(2, '0') +
|
|
44
|
-
parseInt(match[2]).toString(16).padStart(2, '0') +
|
|
45
|
-
parseInt(match[3]).toString(16).padStart(2, '0');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return `#${result}`;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Convert RGB to hexadecimal
|
|
53
|
-
* Note: rgb values are contained in the interval [0, 1]
|
|
54
|
-
*
|
|
55
|
-
* @param {[number, number, number]} rgb RGB color
|
|
56
|
-
* @returns {number} Hexadecimal color
|
|
57
|
-
*/
|
|
58
|
-
export function rgbToHex([r, g, b]: [number, number, number]): number {
|
|
59
|
-
return ((r * 255) << 16) ^ ((g * 255) << 8) ^ ((b * 255) << 0);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Convert RGB to hexadecimal string
|
|
64
|
-
* Note: rgb values are contained in the interval [0, 1]
|
|
65
|
-
*
|
|
66
|
-
* @param {[number, number, number]} rgb RGB color
|
|
67
|
-
* @returns {string} Hexadecimal string
|
|
68
|
-
*/
|
|
69
|
-
export function rgbToHexString([r, g, b]: [number, number, number]): string {
|
|
70
|
-
r = clamp(Math.round(r * 255), 0, 255);
|
|
71
|
-
g = clamp(Math.round(g * 255), 0, 255);
|
|
72
|
-
b = clamp(Math.round(b * 255), 0, 255);
|
|
73
|
-
|
|
74
|
-
const result = (b | (g << 8) | (r << 16) | (1 << 24)).toString(16).slice(1);
|
|
75
|
-
return `#${result}`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Convert hexadecimal to RGB
|
|
80
|
-
* Note: rgb values are contained in the interval [0, 1]
|
|
81
|
-
*
|
|
82
|
-
* @param {(number|string)} hex Hexadecimal color
|
|
83
|
-
* @returns {[number, number, number]} RGB color
|
|
84
|
-
*/
|
|
85
|
-
export function hexToRgb(hex: number | string): [number, number, number] {
|
|
86
|
-
if (typeof hex === 'number') {
|
|
87
|
-
hex = Math.floor(hex);
|
|
88
|
-
} else if (typeof hex === 'string') {
|
|
89
|
-
hex = normalizeHexString(hex).replace(/^#/, '');
|
|
90
|
-
hex = parseInt(hex, 16);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const r = ((hex >> 16) & 255) / 255;
|
|
94
|
-
const g = ((hex >> 8) & 255) / 255;
|
|
95
|
-
const b = (hex & 255) / 255;
|
|
96
|
-
|
|
97
|
-
return [r, g, b];
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Lighten a color
|
|
102
|
-
*
|
|
103
|
-
* @param {string} hex Hexadecimal string
|
|
104
|
-
* @param {number} [amount=0] Amount of the color offset
|
|
105
|
-
* @returns {string} Computed hexadecimal
|
|
106
|
-
*/
|
|
107
|
-
export function lighten(hex: string, amount: number = 0): string {
|
|
108
|
-
let prefix = '';
|
|
109
|
-
|
|
110
|
-
if (hex[0] === '#') {
|
|
111
|
-
hex = hex.slice(1);
|
|
112
|
-
prefix = '#';
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const value = parseInt(hex, 16);
|
|
116
|
-
|
|
117
|
-
const r = clamp((value >> 16) + amount, 0, 255);
|
|
118
|
-
const b = clamp(((value >> 8) & 0x00ff) + amount, 0, 255);
|
|
119
|
-
const g = clamp((value & 0x0000ff) + amount, 0, 255);
|
|
120
|
-
|
|
121
|
-
let result: number | string = g | (b << 8) | (r << 16);
|
|
122
|
-
if (r === 0 && g === 0 && b === 0 && amount !== 0) {
|
|
123
|
-
result = '000000';
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return prefix + result.toString(16);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Darken a color
|
|
131
|
-
*
|
|
132
|
-
* @param {string} hex Hexadecimal string
|
|
133
|
-
* @param {number} [amount=0] Amount of the color offset
|
|
134
|
-
* @returns {string} Computed hexadecimal
|
|
135
|
-
*/
|
|
136
|
-
export function darken(hex: string, amount: number = 0): string {
|
|
137
|
-
return lighten(hex, -amount);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// ***************************************************
|
|
141
|
-
// RGB & Hue-Saturation-Lightness (HSL) color spaces
|
|
142
|
-
// ***************************************************
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Normalize an HSL string
|
|
146
|
-
* Note: hsl values are contained in the intervals H: [0, 360], S: [0, 1], L: [0, 1]
|
|
147
|
-
*
|
|
148
|
-
* @param {string} hsl HSL string (format: 'hsl(360, 100%, 100%)')
|
|
149
|
-
* @returns {[number, number, number]} Normalized HSL color
|
|
150
|
-
*/
|
|
151
|
-
export function normalizeHslString(hsl: string): [number, number, number] {
|
|
152
|
-
const [h, s, l] = hsl.match(/\d+/g)?.map(Number) ?? [0, 0, 0];
|
|
153
|
-
return [h, s / 100, l / 100];
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Convert RGB to HSL
|
|
158
|
-
* Notes:
|
|
159
|
-
* - rgb values are contained in the interval [0, 1]
|
|
160
|
-
* - hsl values are contained in the intervals H: [0, 360], S: [0, 1], L: [0, 1]
|
|
161
|
-
*
|
|
162
|
-
* @param {[number, number, number]} rgb RGB color
|
|
163
|
-
* @returns {[number, number, number]} HSL color
|
|
164
|
-
*/
|
|
165
|
-
export function rgbToHsl([r, g, b]: [number, number, number]): [number, number, number] {
|
|
166
|
-
const l = Math.max(r, g, b);
|
|
167
|
-
const s = l - Math.min(r, g, b);
|
|
168
|
-
const h = s ? (l === r ? (g - b) / s : l === g ? 2 + (b - r) / s : 4 + (r - g) / s) : 0;
|
|
169
|
-
|
|
170
|
-
return [
|
|
171
|
-
60 * h < 0 ? 60 * h + 360 : 60 * h,
|
|
172
|
-
s ? (l <= 0.5 ? s / (2 * l - s) : s / (2 - (2 * l - s))) : 0,
|
|
173
|
-
(2 * l - s) / 2
|
|
174
|
-
];
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Convert HSL to RGB
|
|
179
|
-
* Notes:
|
|
180
|
-
* - rgb values are contained in the interval [0, 1]
|
|
181
|
-
* - hsl values are contained in the intervals H: [0, 360], S: [0, 1], L: [0, 1]
|
|
182
|
-
*
|
|
183
|
-
* @param {[number, number, number]} hsl HSL color
|
|
184
|
-
* @returns {[number, number, number]} RGB color
|
|
185
|
-
*/
|
|
186
|
-
export function hslToRgb([h, s, l]: [number, number, number]): [number, number, number] {
|
|
187
|
-
const a = s * Math.min(l, 1 - l);
|
|
188
|
-
const k = (v: number) => (v + h / 30) % 12;
|
|
189
|
-
const f = (v: number) => l - a * Math.max(-1, Math.min(k(v) - 3, Math.min(9 - k(v), 1)));
|
|
190
|
-
return [f(0), f(8), f(4)];
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// ***************************************************
|
|
194
|
-
// RGB & Hue-Saturation-Brightness (HSB) color spaces
|
|
195
|
-
// ***************************************************
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Convert RGB to HSB
|
|
199
|
-
* Notes:
|
|
200
|
-
* - rgb values are contained in the interval [0, 1]
|
|
201
|
-
* - hsb values are contained in the intervals H: [0, 360], S: [0, 1], B: [0, 1]
|
|
202
|
-
*
|
|
203
|
-
* @param {[number, number, number]} rgb RGB color
|
|
204
|
-
* @returns {[number, number, number]} HSB color
|
|
205
|
-
*/
|
|
206
|
-
export function rgbToHsb([r, g, b]: [number, number, number]): [number, number, number] {
|
|
207
|
-
const max = Math.max(r, g, b);
|
|
208
|
-
const min = Math.min(r, g, b);
|
|
209
|
-
const delta = max - min;
|
|
210
|
-
const h =
|
|
211
|
-
delta === 0 ? 0 : delta && max === r ? (g - b) / delta : max === g ? 2 + (b - r) / delta : 4 + (r - g) / delta;
|
|
212
|
-
return [60 * (h < 0 ? h + 6 : h), max && delta / max, max];
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Convert HSB to RGB
|
|
217
|
-
* Notes:
|
|
218
|
-
* - rgb values are contained in the interval [0, 1]
|
|
219
|
-
* - hsb values are contained in the intervals H: [0, 360], S: [0, 1], B: [0, 1]
|
|
220
|
-
*
|
|
221
|
-
* @param {[number, number, number]} hsb HSB color
|
|
222
|
-
* @returns {[number, number, number]} RGB color
|
|
223
|
-
*/
|
|
224
|
-
export function hsbToRgb([h, s, b]: [number, number, number]): [number, number, number] {
|
|
225
|
-
const k = (v: number) => (v + h / 60) % 6;
|
|
226
|
-
const f = (v: number) => b * (1 - s * Math.max(0, Math.min(k(v), 4 - k(v), 1)));
|
|
227
|
-
return [f(5), f(3), f(1)];
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// *********************************************
|
|
231
|
-
// LAB & Hue-Chroma-Luminance (HCL) color spaces
|
|
232
|
-
// *********************************************
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Convert LAB to HCL
|
|
236
|
-
* -> http://www.brucelindbloom.com/index.html?Eqn_Lab_to_LCH.html
|
|
237
|
-
*
|
|
238
|
-
* @param {[number, number, number]} lab LAB color
|
|
239
|
-
* @returns {[number, number, number]} HCL color
|
|
240
|
-
*/
|
|
241
|
-
export function labToHcl([l, a, b]: [number, number, number]): [number, number, number] {
|
|
242
|
-
const c = Math.sqrt(a * a + b * b);
|
|
243
|
-
const h = abToHue(a, b);
|
|
244
|
-
return [h, c, l];
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Convert HCL to LAB
|
|
249
|
-
* -> http://www.brucelindbloom.com/index.html?Eqn_LCH_to_Lab.html
|
|
250
|
-
*
|
|
251
|
-
* @param {[number, number, number]} hcl HCL color
|
|
252
|
-
* @returns {[number, number, number]} LAB color space
|
|
253
|
-
*/
|
|
254
|
-
export function hclToLab([h, c, l]: [number, number, number]): [number, number, number] {
|
|
255
|
-
const a = c * Math.cos(toRadians(h));
|
|
256
|
-
const b = c * Math.sin(toRadians(h));
|
|
257
|
-
return [l, a, b];
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Convert A and B of LAB to Hue of LCH
|
|
262
|
-
* -> https://stackoverflow.com/questions/53733379/conversion-of-cielab-to-cielchab-not-yielding-correct-result
|
|
263
|
-
*
|
|
264
|
-
* @param {number} a A value of LAB color
|
|
265
|
-
* @param {number} b B value of LAB color
|
|
266
|
-
* @returns {number} Hue value
|
|
267
|
-
*/
|
|
268
|
-
function abToHue(a: number, b: number): number {
|
|
269
|
-
if (a >= 0 && b === 0) {
|
|
270
|
-
return 0;
|
|
271
|
-
}
|
|
272
|
-
if (a < 0 && b === 0) {
|
|
273
|
-
return 180;
|
|
274
|
-
}
|
|
275
|
-
if (a === 0 && b > 0) {
|
|
276
|
-
return 90;
|
|
277
|
-
}
|
|
278
|
-
if (a === 0 && b < 0) {
|
|
279
|
-
return 270;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
let xBias = 0;
|
|
283
|
-
if (a > 0 && b > 0) {
|
|
284
|
-
xBias = 0;
|
|
285
|
-
} else if (a < 0) {
|
|
286
|
-
xBias = 180;
|
|
287
|
-
} else if (a > 0 && b < 0) {
|
|
288
|
-
xBias = 360;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
return toDegrees(Math.atan(b / a)) + xBias;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// ******************************************
|
|
295
|
-
// LAB & RGB color spaces
|
|
296
|
-
// ******************************************
|
|
297
|
-
const f1 = (v: number) => (v * v * v > 0.008856 ? v * v * v : (v - 16 / 116) / 7.787);
|
|
298
|
-
const f2 = (v: number) => (v > 0.0031308 ? 1.055 * Math.pow(v, 1 / 2.4) - 0.055 : 12.92 * v);
|
|
299
|
-
const f3 = (v: number) => (v > 0.04045 ? Math.pow((v + 0.055) / 1.055, 2.4) : v / 12.92);
|
|
300
|
-
const f4 = (v: number) => (v > 0.008856 ? Math.pow(v, 1 / 3) : 7.787 * v + 16 / 116);
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Converts LAB to RGB
|
|
304
|
-
*
|
|
305
|
-
* @param {[number, number, number]} lab LAB color
|
|
306
|
-
* @returns {[number, number, number]} RGB color
|
|
307
|
-
*/
|
|
308
|
-
export function labToRgb([l, a, b]: [number, number, number]): [number, number, number] {
|
|
309
|
-
let y = (l + 16) / 116;
|
|
310
|
-
let x = a / 500 + y;
|
|
311
|
-
let z = y - b / 200;
|
|
312
|
-
|
|
313
|
-
x = 0.95047 * f1(x);
|
|
314
|
-
y = 1.0 * f1(y);
|
|
315
|
-
z = 1.08883 * f1(z);
|
|
316
|
-
|
|
317
|
-
return [
|
|
318
|
-
clamp(f2(x * 3.2406 + y * -1.5372 + z * -0.4986)),
|
|
319
|
-
clamp(f2(x * -0.9689 + y * 1.8758 + z * 0.0415)),
|
|
320
|
-
clamp(f2(x * 0.0557 + y * -0.204 + z * 1.057))
|
|
321
|
-
];
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Converts RGB to LAB
|
|
326
|
-
*
|
|
327
|
-
* @param {[number, number, number]} rgb RGB color
|
|
328
|
-
* @returns {[number, number, number]} LAB color
|
|
329
|
-
*/
|
|
330
|
-
export function rgbToLab([r, g, b]: [number, number, number]): [number, number, number] {
|
|
331
|
-
r = f3(r);
|
|
332
|
-
g = f3(g);
|
|
333
|
-
b = f3(b);
|
|
334
|
-
|
|
335
|
-
let x = f4((r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047);
|
|
336
|
-
let y = f4((r * 0.2126 + g * 0.7152 + b * 0.0722) / 1);
|
|
337
|
-
let z = f4((r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883);
|
|
338
|
-
|
|
339
|
-
return [116 * y - 16, 500 * (x - y), 200 * (y - z)];
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* Get the delta from two LAB colors
|
|
344
|
-
*
|
|
345
|
-
* @param {[number, number, number]} labA First LAB color
|
|
346
|
-
* @param {[number, number, number]} labB Second LAB color
|
|
347
|
-
* @returns {number} Delta
|
|
348
|
-
*/
|
|
349
|
-
export function deltaE(labA: [number, number, number], labB: [number, number, number]): number {
|
|
350
|
-
const deltaL = labA[0] - labB[0];
|
|
351
|
-
const deltaA = labA[1] - labB[1];
|
|
352
|
-
const deltaB = labA[2] - labB[2];
|
|
353
|
-
const c1 = Math.sqrt(labA[1] * labA[1] + labA[2] * labA[2]);
|
|
354
|
-
const c2 = Math.sqrt(labB[1] * labB[1] + labB[2] * labB[2]);
|
|
355
|
-
const deltaC = c1 - c2;
|
|
356
|
-
let deltaH = deltaA * deltaA + deltaB * deltaB - deltaC * deltaC;
|
|
357
|
-
deltaH = deltaH < 0 ? 0 : Math.sqrt(deltaH);
|
|
358
|
-
const sc = 1.0 + 0.045 * c1;
|
|
359
|
-
const sh = 1.0 + 0.015 * c1;
|
|
360
|
-
const deltaLKlsl = deltaL / 1;
|
|
361
|
-
const deltaCkcsc = deltaC / sc;
|
|
362
|
-
const deltaHkhsh = deltaH / sh;
|
|
363
|
-
const i = deltaLKlsl * deltaLKlsl + deltaCkcsc * deltaCkcsc + deltaHkhsh * deltaHkhsh;
|
|
364
|
-
return i < 0 ? 0 : Math.sqrt(i);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// *********************************************
|
|
368
|
-
// RGB & Hue-Chroma-Luminance (HCL) color spaces
|
|
369
|
-
// *********************************************
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* Convert RGB to HCL
|
|
373
|
-
*
|
|
374
|
-
* @param {[number, number, number]} rgb RGB color
|
|
375
|
-
* @returns {[number, number, number]} HCL color
|
|
376
|
-
*/
|
|
377
|
-
export function rgbToHcl([r, g, b]: [number, number, number]): [number, number, number] {
|
|
378
|
-
return labToHcl(rgbToLab([r, g, b]));
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Converts HCL to RGB
|
|
383
|
-
*
|
|
384
|
-
* @param {[number, number, number]} hcl RGB color
|
|
385
|
-
* @returns {[number, number, number]} RGB color
|
|
386
|
-
*/
|
|
387
|
-
export function hclToRgb([h, c, l]: [number, number, number]): [number, number, number] {
|
|
388
|
-
return labToRgb(hclToLab([h, c, l]));
|
|
389
|
-
}
|