@sardine/colour 3.0.0 → 4.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/dist/convertHextoCSSRGB.d.ts +1 -1
- package/dist/convertHextoRGB.d.ts +2 -2
- package/dist/getContrastRatioFromHex.d.ts +1 -1
- package/dist/getContrastRatioFromNamedCSSColour.d.ts +1 -1
- package/dist/getSRGBLuminanceFromHex.d.ts +1 -1
- package/dist/index.cjs +984 -646
- package/dist/index.min.js +1 -1
- package/dist/index.mjs +986 -683
- package/dist/pickHexColourContrast.d.ts +1 -1
- package/dist/sortHexColours.d.ts +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -1,743 +1,1081 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
2
|
+
//#region src/util/namedCSSColours.ts
|
|
3
|
+
/**
|
|
4
|
+
* Named list from https://developer.mozilla.org/en-US/docs/Web/CSS/named-color
|
|
5
|
+
*/
|
|
6
|
+
var namedCSSColours = new Map([
|
|
7
|
+
["aliceblue", "#f0f8ff"],
|
|
8
|
+
["antiquewhite", "#faebd7"],
|
|
9
|
+
["aqua", "#00ffff"],
|
|
10
|
+
["aquamarine", "#7fffd4"],
|
|
11
|
+
["azure", "#f0ffff"],
|
|
12
|
+
["beige", "#f5f5dc"],
|
|
13
|
+
["bisque", "#ffe4c4"],
|
|
14
|
+
["black", "#000000"],
|
|
15
|
+
["blanchedalmond", "#ffebcd"],
|
|
16
|
+
["blue", "#0000ff"],
|
|
17
|
+
["blueviolet", "#8a2be2"],
|
|
18
|
+
["brown", "#a52a2a"],
|
|
19
|
+
["burlywood", "#deb887"],
|
|
20
|
+
["cadetblue", "#5f9ea0"],
|
|
21
|
+
["chartreuse", "#7fff00"],
|
|
22
|
+
["chocolate", "#d2691e"],
|
|
23
|
+
["coral", "#ff7f50"],
|
|
24
|
+
["cornflowerblue", "#6495ed"],
|
|
25
|
+
["cornsilk", "#fff8dc"],
|
|
26
|
+
["crimson", "#dc143c"],
|
|
27
|
+
["cyan", "#00ffff"],
|
|
28
|
+
["darkblue", "#00008b"],
|
|
29
|
+
["darkcyan", "#008b8b"],
|
|
30
|
+
["darkgoldenrod", "#b8860b"],
|
|
31
|
+
["darkgray", "#a9a9a9"],
|
|
32
|
+
["darkgreen", "#006400"],
|
|
33
|
+
["darkgrey", "#a9a9a9"],
|
|
34
|
+
["darkkhaki", "#bdb76b"],
|
|
35
|
+
["darkmagenta", "#8b008b"],
|
|
36
|
+
["darkolivegreen", "#556b2f"],
|
|
37
|
+
["darkorange", "#ff8c00"],
|
|
38
|
+
["darkorchid", "#9932cc"],
|
|
39
|
+
["darkred", "#8b0000"],
|
|
40
|
+
["darksalmon", "#e9967a"],
|
|
41
|
+
["darkseagreen", "#8fbc8f"],
|
|
42
|
+
["darkslateblue", "#483d8b"],
|
|
43
|
+
["darkslategray", "#2f4f4f"],
|
|
44
|
+
["darkslategrey", "#2f4f4f"],
|
|
45
|
+
["darkturquoise", "#00ced1"],
|
|
46
|
+
["darkviolet", "#9400d3"],
|
|
47
|
+
["deeppink", "#ff1493"],
|
|
48
|
+
["deepskyblue", "#00bfff"],
|
|
49
|
+
["dimgray", "#696969"],
|
|
50
|
+
["dimgrey", "#696969"],
|
|
51
|
+
["dodgerblue", "#1e90ff"],
|
|
52
|
+
["firebrick", "#b22222"],
|
|
53
|
+
["floralwhite", "#fffaf0"],
|
|
54
|
+
["forestgreen", "#228b22"],
|
|
55
|
+
["fuchsia", "#ff00ff"],
|
|
56
|
+
["gainsboro", "#dcdcdc"],
|
|
57
|
+
["ghostwhite", "#f8f8ff"],
|
|
58
|
+
["gold", "#ffd700"],
|
|
59
|
+
["goldenrod", "#daa520"],
|
|
60
|
+
["gray", "#808080"],
|
|
61
|
+
["green", "#008000"],
|
|
62
|
+
["greenyellow", "#adff2f"],
|
|
63
|
+
["grey", "#808080"],
|
|
64
|
+
["honeydew", "#f0fff0"],
|
|
65
|
+
["hotpink", "#ff69b4"],
|
|
66
|
+
["indianred", "#cd5c5c"],
|
|
67
|
+
["indigo", "#4b0082"],
|
|
68
|
+
["ivory", "#fffff0"],
|
|
69
|
+
["khaki", "#f0e68c"],
|
|
70
|
+
["lavender", "#e6e6fa"],
|
|
71
|
+
["lavenderblush", "#fff0f5"],
|
|
72
|
+
["lawngreen", "#7cfc00"],
|
|
73
|
+
["lemonchiffon", "#fffacd"],
|
|
74
|
+
["lightblue", "#add8e6"],
|
|
75
|
+
["lightcoral", "#f08080"],
|
|
76
|
+
["lightcyan", "#e0ffff"],
|
|
77
|
+
["lightgoldenrodyellow", "#fafad2"],
|
|
78
|
+
["lightgray", "#d3d3d3"],
|
|
79
|
+
["lightgreen", "#90ee90"],
|
|
80
|
+
["lightgrey", "#d3d3d3"],
|
|
81
|
+
["lightpink", "#ffb6c1"],
|
|
82
|
+
["lightsalmon", "#ffa07a"],
|
|
83
|
+
["lightseagreen", "#20b2aa"],
|
|
84
|
+
["lightskyblue", "#87cefa"],
|
|
85
|
+
["lightslategray", "#778899"],
|
|
86
|
+
["lightslategrey", "#778899"],
|
|
87
|
+
["lightsteelblue", "#b0c4de"],
|
|
88
|
+
["lightyellow", "#ffffe0"],
|
|
89
|
+
["lime", "#00ff00"],
|
|
90
|
+
["limegreen", "#32cd32"],
|
|
91
|
+
["linen", "#faf0e6"],
|
|
92
|
+
["magenta", "#ff00ff"],
|
|
93
|
+
["maroon", "#800000"],
|
|
94
|
+
["mediumaquamarine", "#66cdaa"],
|
|
95
|
+
["mediumblue", "#0000cd"],
|
|
96
|
+
["mediumorchid", "#ba55d3"],
|
|
97
|
+
["mediumpurple", "#9370db"],
|
|
98
|
+
["mediumseagreen", "#3cb371"],
|
|
99
|
+
["mediumslateblue", "#7b68ee"],
|
|
100
|
+
["mediumspringgreen", "#00fa9a"],
|
|
101
|
+
["mediumturquoise", "#48d1cc"],
|
|
102
|
+
["mediumvioletred", "#c71585"],
|
|
103
|
+
["midnightblue", "#191970"],
|
|
104
|
+
["mintcream", "#f5fffa"],
|
|
105
|
+
["mistyrose", "#ffe4e1"],
|
|
106
|
+
["moccasin", "#ffe4b5"],
|
|
107
|
+
["navajowhite", "#ffdead"],
|
|
108
|
+
["navy", "#000080"],
|
|
109
|
+
["oldlace", "#fdf5e6"],
|
|
110
|
+
["olive", "#808000"],
|
|
111
|
+
["olivedrab", "#6b8e23"],
|
|
112
|
+
["orange", "#ffa500"],
|
|
113
|
+
["orangered", "#ff4500"],
|
|
114
|
+
["orchid", "#da70d6"],
|
|
115
|
+
["palegoldenrod", "#eee8aa"],
|
|
116
|
+
["palegreen", "#98fb98"],
|
|
117
|
+
["paleturquoise", "#afeeee"],
|
|
118
|
+
["palevioletred", "#db7093"],
|
|
119
|
+
["papayawhip", "#ffefd5"],
|
|
120
|
+
["peachpuff", "#ffdab9"],
|
|
121
|
+
["peru", "#cd853f"],
|
|
122
|
+
["pink", "#ffc0cb"],
|
|
123
|
+
["plum", "#dda0dd"],
|
|
124
|
+
["powderblue", "#b0e0e6"],
|
|
125
|
+
["purple", "#800080"],
|
|
126
|
+
["rebeccapurple", "#663399"],
|
|
127
|
+
["red", "#ff0000"],
|
|
128
|
+
["rosybrown", "#bc8f8f"],
|
|
129
|
+
["royalblue", "#4169e1"],
|
|
130
|
+
["saddlebrown", "#8b4513"],
|
|
131
|
+
["salmon", "#fa8072"],
|
|
132
|
+
["sandybrown", "#f4a460"],
|
|
133
|
+
["seagreen", "#2e8b57"],
|
|
134
|
+
["seashell", "#fff5ee"],
|
|
135
|
+
["sienna", "#a0522d"],
|
|
136
|
+
["silver", "#c0c0c0"],
|
|
137
|
+
["skyblue", "#87ceeb"],
|
|
138
|
+
["slateblue", "#6a5acd"],
|
|
139
|
+
["slategray", "#708090"],
|
|
140
|
+
["slategrey", "#708090"],
|
|
141
|
+
["snow", "#fffafa"],
|
|
142
|
+
["springgreen", "#00ff7f"],
|
|
143
|
+
["steelblue", "#4682b4"],
|
|
144
|
+
["tan", "#d2b48c"],
|
|
145
|
+
["teal", "#008080"],
|
|
146
|
+
["thistle", "#d8bfd8"],
|
|
147
|
+
["tomato", "#ff6347"],
|
|
148
|
+
["transparent", "#00000000"],
|
|
149
|
+
["turquoise", "#40e0d0"],
|
|
150
|
+
["violet", "#ee82ee"],
|
|
151
|
+
["wheat", "#f5deb3"],
|
|
152
|
+
["white", "#ffffff"],
|
|
153
|
+
["whitesmoke", "#f5f5f5"],
|
|
154
|
+
["yellow", "#ffff00"],
|
|
155
|
+
["yellowgreen", "#9acd32"]
|
|
153
156
|
]);
|
|
154
|
-
|
|
155
|
-
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/util/regexers.ts
|
|
159
|
+
/** Any valid hexadecimal colour: 3, 4, 6, or 8 hex digits after `#` */
|
|
160
|
+
var hexAnyRegex = /^#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{4}|[0-9a-fA-F]{3})$/;
|
|
161
|
+
/**
|
|
162
|
+
* Captures the following CSS RGB formats:
|
|
163
|
+
* - `rgb(0,0,0)`
|
|
164
|
+
* - `rgba(0, 0, 0, 0.4)`
|
|
165
|
+
* - `rgba(0,0,0,50%)`
|
|
166
|
+
* - `rgb(0 0 0)`
|
|
167
|
+
* - `rgba(0 0 0 / 0.4)`
|
|
168
|
+
* - `rgb(0 0 0 / 0.5)`
|
|
169
|
+
* - `rgb(0 0 0 / 50%)`
|
|
170
|
+
* - `rgb(50%, 25%, 100%)`
|
|
171
|
+
* - `rgba(50%, 25%, 100%, 0.8)`
|
|
172
|
+
* - `rgba(50%, 25%, 100%, 80%)`
|
|
173
|
+
*/
|
|
174
|
+
var cssRGBARegex = /^rgba?\(\s*([-+]?\d*\.?\d+%?)\s*[,\s]\s*([-+]?\d*\.?\d+%?)\s*[,\s]\s*([-+]?\d*\.?\d+%?)\s*(?:[,/]\s*([-+]?\d*\.?\d+%?))?\s*\)$/i;
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/assertions.ts
|
|
177
|
+
/**
|
|
178
|
+
* Determines whether a string represents a valid CSS RGB or RGBA colour value.
|
|
179
|
+
*
|
|
180
|
+
* Captures the following CSS RGB formats:
|
|
181
|
+
* - `rgb(0,0,0)`
|
|
182
|
+
* - `rgba(0, 0, 0, 0.4)`
|
|
183
|
+
* - `rgba(0,0,0,50%)`
|
|
184
|
+
* - `rgb(0 0 0)`
|
|
185
|
+
* - `rgba(0 0 0 / 0.4)`
|
|
186
|
+
* - `rgb(0 0 0 / 0.5)`
|
|
187
|
+
* - `rgb(0 0 0 / 50%)`
|
|
188
|
+
* - `rgb(50%, 25%, 100%)`
|
|
189
|
+
* - `rgba(50%, 25%, 100%, 0.8)`
|
|
190
|
+
* - `rgba(50%, 25%, 100%, 80%)`
|
|
191
|
+
*
|
|
192
|
+
* @param {string} colour - The string to test.
|
|
193
|
+
* @returns {boolean} `true` if the string represents a valid CSS RGB or RGBA colour value, `false` otherwise.
|
|
194
|
+
*/
|
|
156
195
|
function isCSSRGBColour(colour) {
|
|
157
|
-
|
|
196
|
+
return cssRGBARegex.test(colour);
|
|
158
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Determines whether a string represents a valid hexadecimal colour value.
|
|
200
|
+
*
|
|
201
|
+
* @param {string} colour - The string to test.
|
|
202
|
+
* @returns {boolean} True if the string represents a valid hexadecimal colour value, false otherwise.
|
|
203
|
+
*/
|
|
159
204
|
function isHexColour(colour) {
|
|
160
|
-
|
|
205
|
+
return hexAnyRegex.test(colour);
|
|
161
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Determines whether a string represents a valid named CSS colour.
|
|
209
|
+
*
|
|
210
|
+
* @param {NamedCSSColour} colour - The string to test.
|
|
211
|
+
* @returns {boolean} `true` if the string represents a valid named CSS colour, `false` otherwise.
|
|
212
|
+
*/
|
|
162
213
|
function isNamedCSSColour(colour) {
|
|
163
|
-
|
|
214
|
+
return namedCSSColours.has(colour);
|
|
164
215
|
}
|
|
216
|
+
//#endregion
|
|
217
|
+
//#region src/util/index.ts
|
|
218
|
+
/**
|
|
219
|
+
* Calculates the Hue derivative
|
|
220
|
+
* @param x A numeric expression representing the cartesian x-coordinate.
|
|
221
|
+
* @param y A numeric expression representing the cartesian y-coordinate.
|
|
222
|
+
*/
|
|
165
223
|
function hue_d(x, y) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return angle;
|
|
172
|
-
}
|
|
173
|
-
return angle + 360;
|
|
224
|
+
if (x === 0 && y === 0) return 0;
|
|
225
|
+
/** The angle in degrees */
|
|
226
|
+
const angle = Math.atan2(x, y) * (180 / Math.PI);
|
|
227
|
+
if (angle >= 0) return angle;
|
|
228
|
+
return angle + 360;
|
|
174
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Calculates the difference between two Hue derivatives
|
|
232
|
+
* @param HueHelper param
|
|
233
|
+
*/
|
|
175
234
|
function deltaHue_d({ C1, C2, h1_d, h2_d }) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return delta;
|
|
182
|
-
}
|
|
183
|
-
if (delta > 180) {
|
|
184
|
-
return delta - 360;
|
|
185
|
-
}
|
|
186
|
-
return delta + 360;
|
|
235
|
+
if (C1 * C2 === 0) return 0;
|
|
236
|
+
const delta = h2_d - h1_d;
|
|
237
|
+
if (Math.abs(delta) <= 180) return delta;
|
|
238
|
+
if (delta > 180) return delta - 360;
|
|
239
|
+
return delta + 360;
|
|
187
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Calculates the mean between two Hue derivatives
|
|
243
|
+
* @param HueHelper param
|
|
244
|
+
*/
|
|
188
245
|
function meanHue_d({ C1, C2, h1_d, h2_d }) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
246
|
+
if (C1 * C2 === 0) return h2_d + h1_d;
|
|
247
|
+
const delta = Math.abs(h1_d - h2_d);
|
|
248
|
+
if (delta <= 180) return (h2_d + h1_d) / 2;
|
|
249
|
+
if (delta > 180 && h1_d + h2_d < 360) return (h2_d + h1_d + 360) / 2;
|
|
250
|
+
return (h2_d + h1_d - 360) / 2;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Converts a number in degrees to radians
|
|
254
|
+
* @param n Number in degrees to be converted
|
|
255
|
+
*/
|
|
256
|
+
var DEG_TO_RAD = Math.PI / 180;
|
|
257
|
+
var toRadians = (n) => n * DEG_TO_RAD;
|
|
258
|
+
/**
|
|
259
|
+
* Calculates a recurring square root
|
|
260
|
+
* @param n Input number
|
|
261
|
+
*/
|
|
262
|
+
var POW_25_7 = 25 ** 7;
|
|
263
|
+
var bigSquare = (n) => Math.sqrt(n ** 7 / (n ** 7 + POW_25_7));
|
|
264
|
+
/**
|
|
265
|
+
* Normalise black and white colorimetry as specified in IEC 61966-2-1
|
|
266
|
+
* It takes a RGB channel in the range [0 - 255] and returns a value between 0 and 1
|
|
267
|
+
* @param rgbValue number to be normalised
|
|
268
|
+
*/
|
|
205
269
|
function linearRGB(rgbValue, WCAG21) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
270
|
+
const rgbRatio = rgbValue / 255;
|
|
271
|
+
const threshold = WCAG21 ? .03928 : .04045;
|
|
272
|
+
let linearValue;
|
|
273
|
+
if (rgbRatio > threshold) linearValue = ((rgbRatio + .055) / 1.055) ** 2.4;
|
|
274
|
+
else linearValue = rgbRatio / 12.92;
|
|
275
|
+
return linearValue;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* The division of the f function domain into two parts was done to prevent an infinite slope at n = 0
|
|
279
|
+
* @param n Number to be constrained
|
|
280
|
+
*/
|
|
281
|
+
var LAB_DELTA = 6 / 29;
|
|
282
|
+
var LAB_DELTA_CUBE = LAB_DELTA ** 3;
|
|
283
|
+
var LAB_DELTA_SQ_3 = 3 * LAB_DELTA ** 2;
|
|
219
284
|
function constrainLab(n) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
return n / LAB_DELTA_SQ_3 + 4 / 29;
|
|
285
|
+
if (n > LAB_DELTA_CUBE) return Math.cbrt(n);
|
|
286
|
+
return n / LAB_DELTA_SQ_3 + 4 / 29;
|
|
224
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* Clamps a number between two values
|
|
290
|
+
* @param {number} value - The value to be clamped
|
|
291
|
+
* @param {number} min - The minimum value
|
|
292
|
+
* @param {number} max - The maximum value
|
|
293
|
+
* @returns {number} - A clamped value
|
|
294
|
+
*/
|
|
225
295
|
function clamp(value, min, max) {
|
|
226
|
-
|
|
296
|
+
return Math.min(Math.max(value, min), max);
|
|
227
297
|
}
|
|
228
|
-
|
|
298
|
+
/** MDN reference for CSS named colours — shared to avoid duplicating the URL string in the bundle */
|
|
299
|
+
var NAMED_CSS_COLOUR_URL = "https://developer.mozilla.org/en-US/docs/Web/CSS/named-color";
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region src/CIEDE2000.ts
|
|
302
|
+
/**
|
|
303
|
+
* Measures the colour difference between two colours in the Lab space
|
|
304
|
+
*
|
|
305
|
+
* Math taken from:
|
|
306
|
+
*
|
|
307
|
+
* https://en.wikipedia.org/wiki/Color_difference#CIEDE2000
|
|
308
|
+
* http://www2.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf
|
|
309
|
+
* @param colour1 First colour to be compared
|
|
310
|
+
* @param colour2 First colour to be compared
|
|
311
|
+
*/
|
|
229
312
|
function ciede2000(colour1, colour2) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
313
|
+
/** Lightness for colour 1 */
|
|
314
|
+
const lightness1 = colour1.L;
|
|
315
|
+
/** green–red colour opponent for colour 1 */
|
|
316
|
+
const greenRed1 = colour1.a;
|
|
317
|
+
/** blue–yellow colour opponent for colour 1 */
|
|
318
|
+
const blueYellow1 = colour1.b;
|
|
319
|
+
/** Lightness for colour 2 */
|
|
320
|
+
const lightness2 = colour2.L;
|
|
321
|
+
/** green–red colour opponent for colour 2 */
|
|
322
|
+
const greenRed2 = colour2.a;
|
|
323
|
+
/** blue–yellow colour opponent for colour 2 */
|
|
324
|
+
const blueYellow2 = colour2.b;
|
|
325
|
+
/** Weighting factor for Luminance */
|
|
326
|
+
const luminanceWeight = 1;
|
|
327
|
+
/** Weighting factor for Chroma */
|
|
328
|
+
const chromaWeight = 1;
|
|
329
|
+
/** Weighting factor for Hue */
|
|
330
|
+
const hueWeight = 1;
|
|
331
|
+
/** Chroma for colour 1 */
|
|
332
|
+
const chroma1 = Math.sqrt(greenRed1 ** 2 + blueYellow1 ** 2);
|
|
333
|
+
/** Chroma for colour 2 */
|
|
334
|
+
const chroma2 = Math.sqrt(greenRed2 ** 2 + blueYellow2 ** 2);
|
|
335
|
+
/** Derivative of the Lightness difference */
|
|
336
|
+
const deltaLightness = lightness2 - lightness1;
|
|
337
|
+
const G = .5 * (1 - bigSquare((chroma1 + chroma2) / 2));
|
|
338
|
+
/** Derivative of greenRed1 */
|
|
339
|
+
const greenRed1Prime = greenRed1 * (1 + G);
|
|
340
|
+
/** Derivative of greenRed2 */
|
|
341
|
+
const greenRed2Prime = greenRed2 * (1 + G);
|
|
342
|
+
/** Derivative of chroma1 */
|
|
343
|
+
const chroma1Prime = Math.sqrt(greenRed1Prime ** 2 + blueYellow1 ** 2);
|
|
344
|
+
/** Derivative of chroma2 */
|
|
345
|
+
const chroma2Prime = Math.sqrt(greenRed2Prime ** 2 + blueYellow2 ** 2);
|
|
346
|
+
/** Derivative of Chroma mean */
|
|
347
|
+
const meanChromaPrime = (chroma1Prime + chroma2Prime) / 2;
|
|
348
|
+
/** Derivative of the mean difference of Chroma */
|
|
349
|
+
const deltaChromaPrime = chroma2Prime - chroma1Prime;
|
|
350
|
+
/** Derivative of colour 1 Hue */
|
|
351
|
+
const hue1Prime = hue_d(blueYellow1, greenRed1Prime);
|
|
352
|
+
/** Derivative of colour 2 Hue */
|
|
353
|
+
const hue2Prime = hue_d(blueYellow2, greenRed2Prime);
|
|
354
|
+
/** Hue difference */
|
|
355
|
+
const deltaHuePrime = deltaHue_d({
|
|
356
|
+
C1: chroma1,
|
|
357
|
+
C2: chroma2,
|
|
358
|
+
h1_d: hue1Prime,
|
|
359
|
+
h2_d: hue2Prime
|
|
360
|
+
});
|
|
361
|
+
const deltaHue = 2 * Math.sqrt(chroma1Prime * chroma2Prime) * Math.sin(toRadians(deltaHuePrime) / 2);
|
|
362
|
+
/** Derivative of mean hue */
|
|
363
|
+
const meanHuePrime = meanHue_d({
|
|
364
|
+
C1: chroma1,
|
|
365
|
+
C2: chroma2,
|
|
366
|
+
h1_d: hue1Prime,
|
|
367
|
+
h2_d: hue2Prime
|
|
368
|
+
});
|
|
369
|
+
/** Lightness Mean value*/
|
|
370
|
+
const meanLightness = (lightness1 + lightness2) / 2;
|
|
371
|
+
/** Compensation for neutral colours (the primed values in the L*C*h differences) */
|
|
372
|
+
const T = 1 - .17 * Math.cos(toRadians(meanHuePrime - 30)) + .24 * Math.cos(toRadians(2 * meanHuePrime)) + .32 * Math.cos(toRadians(3 * meanHuePrime + 6)) - .2 * Math.cos(toRadians(4 * meanHuePrime - 63));
|
|
373
|
+
/** Compensation for lightness */
|
|
374
|
+
const SL = 1 + .015 * (meanLightness - 50) ** 2 / Math.sqrt(20 + (meanLightness - 50) ** 2);
|
|
375
|
+
/** Compensation for chroma */
|
|
376
|
+
const SC = .045 * meanChromaPrime + 1;
|
|
377
|
+
/** Compensation for hue */
|
|
378
|
+
const SH = 1 + .015 * meanChromaPrime * T;
|
|
379
|
+
const rotation = 30 * Math.exp(-(((meanHuePrime - 275) / 25) ** 2));
|
|
380
|
+
/** A hue rotation term, to deal with the problematic blue region (hue angles in the neighborhood of 275°) */
|
|
381
|
+
const RT = -2 * bigSquare(meanChromaPrime) * Math.sin(toRadians(rotation * 2));
|
|
382
|
+
return Math.sqrt((deltaLightness / (luminanceWeight * SL)) ** 2 + (deltaChromaPrime / (chromaWeight * SC)) ** 2 + (deltaHue / (hueWeight * SH)) ** 2 + RT * (deltaChromaPrime / (chromaWeight * SC)) * (deltaHue / (hueWeight * SH)));
|
|
276
383
|
}
|
|
384
|
+
//#endregion
|
|
385
|
+
//#region src/convertCSSRGBtoRGB.ts
|
|
386
|
+
/**
|
|
387
|
+
* Converts a CSS RGB/Alpha value to its numeric equivalent
|
|
388
|
+
* @param value - The value string (e.g., "50%", "127", "0.5")
|
|
389
|
+
* @param isAlpha - Whether this is an alpha channel value
|
|
390
|
+
* @returns The numeric value
|
|
391
|
+
*/
|
|
277
392
|
function convertCssValueToNumber(value, isAlpha = false) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
return Number.parseFloat(value);
|
|
393
|
+
if (value.endsWith("%")) {
|
|
394
|
+
const percentage = Number.parseFloat(value.slice(0, -1));
|
|
395
|
+
if (isAlpha) return percentage / 100;
|
|
396
|
+
return Math.round(percentage / 100 * 255);
|
|
397
|
+
}
|
|
398
|
+
return Number.parseFloat(value);
|
|
286
399
|
}
|
|
287
400
|
function convertCssRgbChannelValue(value) {
|
|
288
|
-
|
|
289
|
-
|
|
401
|
+
if (!value) return void 0;
|
|
402
|
+
return convertCssValueToNumber(value);
|
|
290
403
|
}
|
|
291
404
|
function convertCssAlphaChannelValue(value) {
|
|
292
|
-
|
|
293
|
-
|
|
405
|
+
if (!value) return void 0;
|
|
406
|
+
return convertCssValueToNumber(value, true);
|
|
294
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* Converts CSS RGB colour format into RGB colour object.
|
|
410
|
+
* @param {string} colour - A CSS RGB colour in the format:
|
|
411
|
+
*
|
|
412
|
+
* - `rgb(0,0,0)`
|
|
413
|
+
* - `rgba(0,0,0,0.4)`
|
|
414
|
+
* - `rgb(0 0 0)`
|
|
415
|
+
* - `rgba(0 0 0 / 0.4)`
|
|
416
|
+
* - `rgb(50%, 25%, 100%)`
|
|
417
|
+
* - `rgba(50%, 25%, 100%, 0.8)`
|
|
418
|
+
* - `rgba(50%, 25%, 100%, 80%)`
|
|
419
|
+
*
|
|
420
|
+
* @returns {RGBColour} - RGB colour object.
|
|
421
|
+
*/
|
|
295
422
|
function convertCSSRGBtoRGB(colour) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
G: convertCssRgbChannelValue(match[2]),
|
|
305
|
-
B: convertCssRgbChannelValue(match[3]),
|
|
306
|
-
A: convertCssAlphaChannelValue(match[4])
|
|
307
|
-
};
|
|
423
|
+
const match = colour.match(cssRGBARegex);
|
|
424
|
+
if (!match) throw new Error(`convertCSSRGBtoRGB expects a valid CSS RGB string but got ${colour}`);
|
|
425
|
+
return {
|
|
426
|
+
R: convertCssRgbChannelValue(match[1]),
|
|
427
|
+
G: convertCssRgbChannelValue(match[2]),
|
|
428
|
+
B: convertCssRgbChannelValue(match[3]),
|
|
429
|
+
A: convertCssAlphaChannelValue(match[4])
|
|
430
|
+
};
|
|
308
431
|
}
|
|
432
|
+
//#endregion
|
|
433
|
+
//#region src/convertRGBtoHex.ts
|
|
434
|
+
/**
|
|
435
|
+
* Converts a colour in the RGB format to Hexadecimal.
|
|
436
|
+
* It accepts an option Alpha channel `A`
|
|
437
|
+
* @param {RGBColour} colour - An object representing RGB Colour.
|
|
438
|
+
* @param {number} colour.R - A number between 0 and 255 to describe the Red colour channel
|
|
439
|
+
* @param {number} colour.G - A number between 0 and 255 to describe the Green colour channel
|
|
440
|
+
* @param {number} colour.B - A number between 0 and 255 to describe the Blue colour channel
|
|
441
|
+
* @param {number} colour.A - An optional number between 0 and 1 to describe the Alpha colour channel
|
|
442
|
+
* @returns - An hexadecimal string
|
|
443
|
+
*/
|
|
309
444
|
function convertRGBtoHex({ R, G, B, A }) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
return `#${hex(R)}${hex(G)}${hex(B)}${A ? hex(Math.round(A * 255)) : ""}`;
|
|
445
|
+
const hex = (n) => {
|
|
446
|
+
return clamp(n, 0, 255).toString(16).padStart(2, "0");
|
|
447
|
+
};
|
|
448
|
+
return `#${hex(R)}${hex(G)}${hex(B)}${A ? hex(Math.round(A * 255)) : ""}`;
|
|
315
449
|
}
|
|
450
|
+
//#endregion
|
|
451
|
+
//#region src/convertCSSRGBtoHex.ts
|
|
452
|
+
/**
|
|
453
|
+
* Converts CSS RGB colour format into Hexadecimal.
|
|
454
|
+
* @param {string} colour - A CSS RGB colour in the format:
|
|
455
|
+
*
|
|
456
|
+
* - `rgb(0,0,0)`
|
|
457
|
+
* - `rgba(0,0,0,0.4)`
|
|
458
|
+
* - `rgb(0 0 0)`
|
|
459
|
+
* - `rgba(0 0 0 / 0.4)`
|
|
460
|
+
* - `rgb(50%, 25%, 100%)`
|
|
461
|
+
* - `rgba(50%, 25%, 100%, 0.8)`
|
|
462
|
+
* - `rgba(50%, 25%, 100%, 80%)`
|
|
463
|
+
*
|
|
464
|
+
* @returns {string} - An hexadecimal string
|
|
465
|
+
*/
|
|
316
466
|
function convertCSSRGBtoHex(colour) {
|
|
317
|
-
|
|
318
|
-
return convertRGBtoHex(rgb);
|
|
467
|
+
return convertRGBtoHex(convertCSSRGBtoRGB(colour));
|
|
319
468
|
}
|
|
469
|
+
//#endregion
|
|
470
|
+
//#region src/convertHextoRGB.ts
|
|
471
|
+
/**
|
|
472
|
+
* Converts an hexadecimal colour into RGB colour object.
|
|
473
|
+
* @param {string} hex - An hexadecimal colour in the format:
|
|
474
|
+
*
|
|
475
|
+
* - `#000`
|
|
476
|
+
* - `#102030`
|
|
477
|
+
* - `#ffff`
|
|
478
|
+
* - `#102030ff`
|
|
479
|
+
*
|
|
480
|
+
* @returns {RGBColour | null} RGB colour object, or `null` if the input is not a valid hexadecimal colour.
|
|
481
|
+
*/
|
|
320
482
|
function convertHextoRGB(hex) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
B: parseInt(hex.slice(5, 7), 16),
|
|
347
|
-
A: Math.round(parseInt(hex.slice(7, 9), 16) / 255 * 100) / 100
|
|
348
|
-
};
|
|
349
|
-
default:
|
|
350
|
-
return {
|
|
351
|
-
R: parseInt(hex.slice(1, 2).repeat(2), 16),
|
|
352
|
-
G: parseInt(hex.slice(2, 3).repeat(2), 16),
|
|
353
|
-
B: parseInt(hex.slice(3, 4).repeat(2), 16),
|
|
354
|
-
A: Math.round(parseInt(hex.slice(4, 5).repeat(2), 16) / 255 * 100) / 100
|
|
355
|
-
};
|
|
356
|
-
}
|
|
483
|
+
if (typeof hex !== "string" || !hexAnyRegex.test(hex)) return null;
|
|
484
|
+
switch (hex.length) {
|
|
485
|
+
case 7: return {
|
|
486
|
+
R: parseInt(hex.slice(1, 3), 16),
|
|
487
|
+
G: parseInt(hex.slice(3, 5), 16),
|
|
488
|
+
B: parseInt(hex.slice(5, 7), 16)
|
|
489
|
+
};
|
|
490
|
+
case 4: return {
|
|
491
|
+
R: parseInt(hex.slice(1, 2).repeat(2), 16),
|
|
492
|
+
G: parseInt(hex.slice(2, 3).repeat(2), 16),
|
|
493
|
+
B: parseInt(hex.slice(3, 4).repeat(2), 16)
|
|
494
|
+
};
|
|
495
|
+
case 9: return {
|
|
496
|
+
R: parseInt(hex.slice(1, 3), 16),
|
|
497
|
+
G: parseInt(hex.slice(3, 5), 16),
|
|
498
|
+
B: parseInt(hex.slice(5, 7), 16),
|
|
499
|
+
A: Math.round(parseInt(hex.slice(7, 9), 16) / 255 * 100) / 100
|
|
500
|
+
};
|
|
501
|
+
default: return {
|
|
502
|
+
R: parseInt(hex.slice(1, 2).repeat(2), 16),
|
|
503
|
+
G: parseInt(hex.slice(2, 3).repeat(2), 16),
|
|
504
|
+
B: parseInt(hex.slice(3, 4).repeat(2), 16),
|
|
505
|
+
A: Math.round(parseInt(hex.slice(4, 5).repeat(2), 16) / 255 * 100) / 100
|
|
506
|
+
};
|
|
507
|
+
}
|
|
357
508
|
}
|
|
509
|
+
//#endregion
|
|
510
|
+
//#region src/convertHextoCSSRGB.ts
|
|
511
|
+
/**
|
|
512
|
+
* Converts an hexadecimal colour into CSS RGB/A colour.
|
|
513
|
+
* @param {string} hex - An hexadecimal colour in the format:
|
|
514
|
+
*
|
|
515
|
+
* - `#000`
|
|
516
|
+
* - `#102030`
|
|
517
|
+
* - `#ffff`
|
|
518
|
+
* - `#102030ff`
|
|
519
|
+
*
|
|
520
|
+
* @returns {string} either a CSS RGB or CSS RGBA string.
|
|
521
|
+
*/
|
|
358
522
|
function convertHextoCSSRGB(hex) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
return `rgb(${rgb.R},${rgb.G},${rgb.B})`;
|
|
523
|
+
const rgb = convertHextoRGB(hex);
|
|
524
|
+
if (!rgb) return null;
|
|
525
|
+
if (rgb.A) return `rgba(${rgb.R},${rgb.G},${rgb.B},${rgb.A})`;
|
|
526
|
+
return `rgb(${rgb.R},${rgb.G},${rgb.B})`;
|
|
364
527
|
}
|
|
528
|
+
//#endregion
|
|
529
|
+
//#region src/convertHextoNamedCSSColour.ts
|
|
530
|
+
/**
|
|
531
|
+
* Converts a hexadecimal colour value to a named CSS colour.
|
|
532
|
+
*
|
|
533
|
+
* @param {string} colour - The hexadecimal colour value to convert.
|
|
534
|
+
* @returns {NamedCSSColour | undefined} The named CSS colour that corresponds to the given hexadecimal colour value, or undefined if no named CSS colour matches the given value.
|
|
535
|
+
*/
|
|
365
536
|
function convertHextoNamedCSSColour(colour) {
|
|
366
|
-
|
|
367
|
-
if (hex === colour) {
|
|
368
|
-
return name;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
return void 0;
|
|
537
|
+
for (const [name, hex] of namedCSSColours.entries()) if (hex === colour) return name;
|
|
372
538
|
}
|
|
539
|
+
//#endregion
|
|
540
|
+
//#region src/convertNamedCSSColourtoHex.ts
|
|
541
|
+
/**
|
|
542
|
+
* Converts a named CSS Colour in an hexadecimal one.
|
|
543
|
+
*
|
|
544
|
+
* List of colours sourced here:
|
|
545
|
+
* https://developer.mozilla.org/en-US/docs/Web/CSS/named-color
|
|
546
|
+
* @param {NamedCSSColour} name - A named CSS colour
|
|
547
|
+
* @returns {string} - An hexadecimal string
|
|
548
|
+
*/
|
|
373
549
|
function convertNamedCSSColourtoHex(name) {
|
|
374
|
-
|
|
550
|
+
return namedCSSColours.get(name);
|
|
375
551
|
}
|
|
552
|
+
//#endregion
|
|
553
|
+
//#region src/convertNamedCSSColourtoRGB.ts
|
|
554
|
+
/**
|
|
555
|
+
* Converts a named CSS colour to an RGB colour object.
|
|
556
|
+
*
|
|
557
|
+
* @param {NamedCSSColour} colour - The named CSS colour to convert.
|
|
558
|
+
* @returns {RGBColour | undefined} An RGB colour object representing the named CSS colour, or undefined if the named CSS colour is not recognized.
|
|
559
|
+
*/
|
|
376
560
|
function convertNamedCSSColourtoRGB(colour) {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
381
|
-
return convertHextoRGB(hex);
|
|
561
|
+
const hex = convertNamedCSSColourtoHex(colour);
|
|
562
|
+
if (!hex) return;
|
|
563
|
+
return convertHextoRGB(hex) || void 0;
|
|
382
564
|
}
|
|
565
|
+
//#endregion
|
|
566
|
+
//#region src/convertRGBtoCSSRGB.ts
|
|
567
|
+
/**
|
|
568
|
+
* Converts an RGB colour object to a CSS RGB colour string.
|
|
569
|
+
*
|
|
570
|
+
* @param {RGBColour} colour - The RGB colour object to convert.
|
|
571
|
+
* @returns {string} The CSS RGB colour string in the format `rgb(R G B)` or `rgb(R G B / A)` if the alpha channel is present.
|
|
572
|
+
*/
|
|
383
573
|
function convertRGBtoCSSRGB({ R, G, B, A }) {
|
|
384
|
-
|
|
574
|
+
return `rgb(${R} ${G} ${B}${A ? ` / ${A}` : ""})`;
|
|
385
575
|
}
|
|
576
|
+
//#endregion
|
|
577
|
+
//#region src/convertRGBtoXYZ.ts
|
|
578
|
+
/**
|
|
579
|
+
* Converts sRGB colour space to XYZ.
|
|
580
|
+
* Math comes from https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation
|
|
581
|
+
* @param {RGBColour} colour sRGB colour
|
|
582
|
+
* @return {XYZColour} XYZ colour
|
|
583
|
+
*/
|
|
386
584
|
function convertRGBtoXYZ(colour) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
585
|
+
const { R, G, B } = colour;
|
|
586
|
+
const _R = linearRGB(R) * 100;
|
|
587
|
+
const _G = linearRGB(G) * 100;
|
|
588
|
+
const _B = linearRGB(B) * 100;
|
|
589
|
+
return {
|
|
590
|
+
X: _R * .4124 + _G * .3576 + _B * .1805,
|
|
591
|
+
Y: _R * .2126 + _G * .7152 + _B * .0722,
|
|
592
|
+
Z: _R * .0193 + _G * .1192 + _B * .9505
|
|
593
|
+
};
|
|
395
594
|
}
|
|
595
|
+
//#endregion
|
|
596
|
+
//#region src/convertXYZtoLab.ts
|
|
597
|
+
/**
|
|
598
|
+
* Converts XYZ colour space to Lab
|
|
599
|
+
* Math comes from https://en.wikipedia.org/wiki/CIELAB_color_space#From_CIEXYZ_to_CIELAB[11]
|
|
600
|
+
* @param colour XYZ colour
|
|
601
|
+
* @return {LabColour} Lab colour
|
|
602
|
+
*/
|
|
396
603
|
function convertXYZtoLab(colour) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
604
|
+
const { X, Y, Z } = colour;
|
|
605
|
+
const _X = X / 95.047;
|
|
606
|
+
const _Y = Y / 100;
|
|
607
|
+
const _Z = Z / 108.883;
|
|
608
|
+
const fX = constrainLab(_X);
|
|
609
|
+
const fY = constrainLab(_Y);
|
|
610
|
+
const fZ = constrainLab(_Z);
|
|
611
|
+
return {
|
|
612
|
+
L: 116 * fY - 16,
|
|
613
|
+
a: 500 * (fX - fY),
|
|
614
|
+
b: 200 * (fY - fZ)
|
|
615
|
+
};
|
|
408
616
|
}
|
|
617
|
+
//#endregion
|
|
618
|
+
//#region src/convertRGBtoLab.ts
|
|
619
|
+
/**
|
|
620
|
+
* Indirectly converts RGB to Lab.
|
|
621
|
+
* First converts RGB to XYZ, then converts XYZ to Lab.
|
|
622
|
+
* @param {RGBColour} colour sRGB colour
|
|
623
|
+
* @return {LabColour} Lab colour
|
|
624
|
+
*/
|
|
409
625
|
function convertRGBtoLab(colour) {
|
|
410
|
-
|
|
411
|
-
return convertXYZtoLab(XYZColour);
|
|
626
|
+
return convertXYZtoLab(convertRGBtoXYZ(colour));
|
|
412
627
|
}
|
|
628
|
+
//#endregion
|
|
629
|
+
//#region src/convertRGBtoNamedCSSColour.ts
|
|
630
|
+
/**
|
|
631
|
+
* Converts an RGB colour object to a named CSS colour.
|
|
632
|
+
*
|
|
633
|
+
* @param {RGBColour} colour - The RGB colour object to convert.
|
|
634
|
+
* @returns {NamedCSSColour | undefined} The named CSS colour that corresponds to the given RGB colour, or undefined if no named CSS colour matches the given colour.
|
|
635
|
+
*/
|
|
413
636
|
function convertRGBtoNamedCSSColour(colour) {
|
|
414
|
-
|
|
415
|
-
return convertHextoNamedCSSColour(hex);
|
|
637
|
+
return convertHextoNamedCSSColour(convertRGBtoHex(colour));
|
|
416
638
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
639
|
+
//#endregion
|
|
640
|
+
//#region src/RGBdistance.ts
|
|
641
|
+
/**
|
|
642
|
+
* Calculate the distance between two RGB colours using the CIEDE2000 colour-difference formula.
|
|
643
|
+
*
|
|
644
|
+
* The CIEDE2000 colour-difference formula is a standard method for calculating the perceptual difference
|
|
645
|
+
* between two colours in the CIELAB colour space. It takes into account human visual perception and the
|
|
646
|
+
* non-linearities in how we perceive colour differences.
|
|
647
|
+
*
|
|
648
|
+
* @param colour1 - The first colour to compare.
|
|
649
|
+
* @param colour2 - The second colour to compare.
|
|
650
|
+
* @returns The distance between the two colours. Smaller numbers (minimum 0) mean the colours are more similar,
|
|
651
|
+
* larger numbers (typically not exceeding 100 for visible colours) indicate more dissimilar colours.
|
|
652
|
+
*/
|
|
653
|
+
var RGBdistance = (colour1, colour2) => {
|
|
654
|
+
return ciede2000(convertRGBtoLab(colour1), convertRGBtoLab(colour2));
|
|
421
655
|
};
|
|
656
|
+
//#endregion
|
|
657
|
+
//#region src/findNearestRGBColour.ts
|
|
658
|
+
/**
|
|
659
|
+
* Finds the nearest RGB colour in a palette to the given RGB colour.
|
|
660
|
+
*
|
|
661
|
+
* @param {RGBColour} colour - The RGB colour to find the nearest match for.
|
|
662
|
+
* @param {RGBColour[]} palette - An array of RGB colours to search for the nearest match in.
|
|
663
|
+
* @returns {RGBColour} The RGB colour in the palette that is closest to the given colour.
|
|
664
|
+
* If the palette has fewer than 2 colours, or if it is undefined or null, the original colour is returned.
|
|
665
|
+
*/
|
|
422
666
|
function findNearestRGBColour(colour, palette) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
}
|
|
435
|
-
return nearest;
|
|
667
|
+
if (!palette || palette.length < 2) return colour;
|
|
668
|
+
let nearest = colour;
|
|
669
|
+
let minDistance = Number.POSITIVE_INFINITY;
|
|
670
|
+
for (const paletteColour of palette) {
|
|
671
|
+
const distance = RGBdistance(colour, paletteColour);
|
|
672
|
+
if (distance < minDistance) {
|
|
673
|
+
minDistance = distance;
|
|
674
|
+
nearest = paletteColour;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return nearest;
|
|
436
678
|
}
|
|
679
|
+
//#endregion
|
|
680
|
+
//#region src/findNearestColour.ts
|
|
681
|
+
/**
|
|
682
|
+
* Finds the nearest colour in a palette to a given colour.
|
|
683
|
+
*
|
|
684
|
+
* @param {string | NamedCSSColour} colour - The colour to match. It can be a hexadecimal colour, a CSS RGB colour, or a named CSS colour.
|
|
685
|
+
* @param {string[] | NamedCSSColour[]} palette - The palette of colours to search.
|
|
686
|
+
* @returns {string} The nearest colour in the palette to the given colour.
|
|
687
|
+
*/
|
|
437
688
|
function findNearestColour(colour, palette) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
if (paletteRGB.length < 2) {
|
|
468
|
-
return colour;
|
|
469
|
-
}
|
|
470
|
-
const nearest = findNearestRGBColour(baseColour, paletteRGB);
|
|
471
|
-
if (colourType === "hex") {
|
|
472
|
-
return convertRGBtoHex(nearest);
|
|
473
|
-
}
|
|
474
|
-
if (colourType === "cssRGB") {
|
|
475
|
-
return convertRGBtoCSSRGB(nearest);
|
|
476
|
-
}
|
|
477
|
-
return convertRGBtoNamedCSSColour(nearest);
|
|
689
|
+
if (!palette || palette.length < 2) return colour;
|
|
690
|
+
let baseColour;
|
|
691
|
+
let colourType;
|
|
692
|
+
const paletteRGB = [];
|
|
693
|
+
if (isHexColour(colour)) {
|
|
694
|
+
baseColour = convertHextoRGB(colour);
|
|
695
|
+
colourType = "hex";
|
|
696
|
+
} else if (isCSSRGBColour(colour)) {
|
|
697
|
+
baseColour = convertCSSRGBtoRGB(colour);
|
|
698
|
+
colourType = "cssRGB";
|
|
699
|
+
} else if (isNamedCSSColour(colour)) {
|
|
700
|
+
baseColour = convertNamedCSSColourtoRGB(colour);
|
|
701
|
+
colourType = "namedCSS";
|
|
702
|
+
}
|
|
703
|
+
if (!baseColour) return;
|
|
704
|
+
for (const paletteColour of palette) if (isHexColour(paletteColour)) {
|
|
705
|
+
const rgb = convertHextoRGB(paletteColour);
|
|
706
|
+
if (rgb) paletteRGB.push(rgb);
|
|
707
|
+
} else if (isCSSRGBColour(paletteColour)) paletteRGB.push(convertCSSRGBtoRGB(paletteColour));
|
|
708
|
+
else if (isNamedCSSColour(paletteColour)) {
|
|
709
|
+
const rgb = convertNamedCSSColourtoRGB(paletteColour);
|
|
710
|
+
if (rgb) paletteRGB.push(rgb);
|
|
711
|
+
}
|
|
712
|
+
if (paletteRGB.length < 2) return colour;
|
|
713
|
+
const nearest = findNearestRGBColour(baseColour, paletteRGB);
|
|
714
|
+
if (colourType === "hex") return convertRGBtoHex(nearest);
|
|
715
|
+
if (colourType === "cssRGB") return convertRGBtoCSSRGB(nearest);
|
|
716
|
+
return convertRGBtoNamedCSSColour(nearest);
|
|
478
717
|
}
|
|
718
|
+
//#endregion
|
|
719
|
+
//#region src/findNearestCSSRGBColour.ts
|
|
720
|
+
/**
|
|
721
|
+
* Finds the nearest CSS RGB colour in a palette to a given CSS RGB colour.
|
|
722
|
+
*
|
|
723
|
+
* @param {string} colour - The CSS RGB colour to find the nearest match for. Alpha channel is ignored.
|
|
724
|
+
* @param {string[]} palette - An array of CSS RGB colours to search for the nearest match.
|
|
725
|
+
* @returns {string} The CSS RGB colour in the palette that is closest to the given colour.
|
|
726
|
+
* If the palette has fewer than 2 colours, or if it is undefined or null, the original colour is returned.
|
|
727
|
+
* The CSS RGB colour string is in the format `rgb(R G B)`.
|
|
728
|
+
*/
|
|
479
729
|
function findNearestCSSRGBColour(colour, palette) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
const baseRGBColour = convertCSSRGBtoRGB(colour);
|
|
484
|
-
const paletteRGBColours = palette.map(
|
|
485
|
-
(hexColour) => convertCSSRGBtoRGB(hexColour)
|
|
486
|
-
);
|
|
487
|
-
const nearestRGBColour = findNearestRGBColour(
|
|
488
|
-
baseRGBColour,
|
|
489
|
-
paletteRGBColours
|
|
490
|
-
);
|
|
491
|
-
return convertRGBtoCSSRGB(nearestRGBColour);
|
|
730
|
+
if (!palette || palette.length < 2) return colour;
|
|
731
|
+
return convertRGBtoCSSRGB(findNearestRGBColour(convertCSSRGBtoRGB(colour), palette.map((hexColour) => convertCSSRGBtoRGB(hexColour))));
|
|
492
732
|
}
|
|
733
|
+
//#endregion
|
|
734
|
+
//#region src/findNearestHexColour.ts
|
|
735
|
+
/**
|
|
736
|
+
* Finds the nearest hexadecimal colour in a palette to the given hex colour.
|
|
737
|
+
*
|
|
738
|
+
* @param {string} colour - The hexadecimal colour to find the nearest match for.
|
|
739
|
+
* @param {string[]} palette - An array of hexadecimal colours to search for the nearest match in.
|
|
740
|
+
* @returns {string} The hexadecimal colour in the palette that is closest to the given colour.
|
|
741
|
+
* If the palette has fewer than 2 colours, or if it is undefined or null, the original colour is returned.
|
|
742
|
+
*/
|
|
493
743
|
function findNearestHexColour(colour, palette) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
const paletteRGBColours = palette.map(
|
|
499
|
-
(hexColour) => convertHextoRGB(hexColour)
|
|
500
|
-
);
|
|
501
|
-
const nearestRGBColour = findNearestRGBColour(
|
|
502
|
-
baseRGBColour,
|
|
503
|
-
paletteRGBColours
|
|
504
|
-
);
|
|
505
|
-
return convertRGBtoHex(nearestRGBColour);
|
|
744
|
+
if (!palette || palette.length < 2) return colour;
|
|
745
|
+
const baseRGBColour = convertHextoRGB(colour);
|
|
746
|
+
if (!baseRGBColour) return colour;
|
|
747
|
+
return convertRGBtoHex(findNearestRGBColour(baseRGBColour, palette.map((hexColour) => convertHextoRGB(hexColour)).filter((rgb) => rgb !== null)));
|
|
506
748
|
}
|
|
749
|
+
//#endregion
|
|
750
|
+
//#region src/findNearestNamedCSSColour.ts
|
|
507
751
|
function findNearestNamedCSSColour(colour, palette) {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
(hexColour) => convertNamedCSSColourtoRGB(hexColour)
|
|
514
|
-
);
|
|
515
|
-
const filteredPaletteRGBColours = paletteRGBColours.filter(
|
|
516
|
-
(paletteRGBColour) => paletteRGBColour !== void 0
|
|
517
|
-
);
|
|
518
|
-
if (!baseRGBColour || filteredPaletteRGBColours.length < 2) {
|
|
519
|
-
return colour;
|
|
520
|
-
}
|
|
521
|
-
const nearestRGBColour = findNearestRGBColour(
|
|
522
|
-
baseRGBColour,
|
|
523
|
-
filteredPaletteRGBColours
|
|
524
|
-
);
|
|
525
|
-
return convertRGBtoNamedCSSColour(nearestRGBColour);
|
|
752
|
+
if (!palette || palette.length < 2) return colour;
|
|
753
|
+
const baseRGBColour = convertNamedCSSColourtoRGB(colour);
|
|
754
|
+
const filteredPaletteRGBColours = palette.map((hexColour) => convertNamedCSSColourtoRGB(hexColour)).filter((paletteRGBColour) => paletteRGBColour !== void 0);
|
|
755
|
+
if (!baseRGBColour || filteredPaletteRGBColours.length < 2) return colour;
|
|
756
|
+
return convertRGBtoNamedCSSColour(findNearestRGBColour(baseRGBColour, filteredPaletteRGBColours));
|
|
526
757
|
}
|
|
758
|
+
//#endregion
|
|
759
|
+
//#region src/getSRGBLuminanceFromRGB.ts
|
|
760
|
+
/**
|
|
761
|
+
* Returns the relative luminance of a colour in the sRGB space.
|
|
762
|
+
*
|
|
763
|
+
* The calculations are compatible with WCAG 3.0 as it aligns with the sRGB spec
|
|
764
|
+
* and difference to WCAG 2.1 is minimal in a 8 bit channel.
|
|
765
|
+
*
|
|
766
|
+
* https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
|
|
767
|
+
*
|
|
768
|
+
* https://www.w3.org/WAI/GL/wiki/Relative_luminance
|
|
769
|
+
* @param colour an hexadecimal colour
|
|
770
|
+
*/
|
|
527
771
|
function getSRGBLuminanceFromRGB({ R, G, B }, standard) {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
772
|
+
const isWCAG21 = standard === "WCAG2.1";
|
|
773
|
+
const r = linearRGB(R, isWCAG21);
|
|
774
|
+
const g = linearRGB(G, isWCAG21);
|
|
775
|
+
const b = linearRGB(B, isWCAG21);
|
|
776
|
+
return .2126 * r + .7152 * g + .0722 * b;
|
|
533
777
|
}
|
|
778
|
+
//#endregion
|
|
779
|
+
//#region src/getSRGBLuminanceFromHex.ts
|
|
780
|
+
/**
|
|
781
|
+
* Returns the relative luminance of a colour in the sRGB space.
|
|
782
|
+
*
|
|
783
|
+
* The calculations are compatible with WCAG 3.0 as it aligns with the sRGB spec
|
|
784
|
+
* and difference to WCAG 2.1 is minimal in a 8 bit channel.
|
|
785
|
+
*
|
|
786
|
+
* https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
|
|
787
|
+
*
|
|
788
|
+
* https://www.w3.org/WAI/GL/wiki/Relative_luminance
|
|
789
|
+
* @param colour an hexadecimal colour
|
|
790
|
+
*/
|
|
534
791
|
function getSRGBLuminanceFromHex(colour, standard) {
|
|
535
|
-
|
|
536
|
-
|
|
792
|
+
const rgbColor = convertHextoRGB(colour);
|
|
793
|
+
if (!rgbColor) return null;
|
|
794
|
+
return getSRGBLuminanceFromRGB(rgbColor, standard);
|
|
537
795
|
}
|
|
796
|
+
//#endregion
|
|
797
|
+
//#region src/util/calculateContrastRatio.ts
|
|
798
|
+
/**
|
|
799
|
+
* Calculate the contrast ratio between two colours
|
|
800
|
+
* @param luminance1 The luminance of the first colour
|
|
801
|
+
* @param luminance2 The luminance of the second colour
|
|
802
|
+
* @returns The contrast ratio between the two colours truncated to 3 decimal places
|
|
803
|
+
*/
|
|
538
804
|
function calculateContrastRatio(luminance1, luminance2) {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
805
|
+
const lighter = Math.max(luminance1, luminance2);
|
|
806
|
+
const darker = Math.min(luminance1, luminance2);
|
|
807
|
+
const ratio = (lighter + .05) / (darker + .05);
|
|
808
|
+
return Math.floor(ratio * 1e3) / 1e3;
|
|
543
809
|
}
|
|
810
|
+
//#endregion
|
|
811
|
+
//#region src/getContrastRatioFromHex.ts
|
|
812
|
+
/**
|
|
813
|
+
* Calculates the contrast ratio between two colours in hexadecimal format.
|
|
814
|
+
* @param colour1 The first colour in hexadecimal format
|
|
815
|
+
* @param colour2 The second colour in hexadecimal format
|
|
816
|
+
* @param standard The standard to evaluate the contrast ratio against, defaults to WCAG2.1
|
|
817
|
+
* @returns The contrast ratio between the two colours truncated to 3 decimal places
|
|
818
|
+
*/
|
|
544
819
|
function getContrastRatioFromHex(colour1, colour2, standard) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
820
|
+
const luminance1 = getSRGBLuminanceFromHex(colour1, standard);
|
|
821
|
+
const luminance2 = getSRGBLuminanceFromHex(colour2, standard);
|
|
822
|
+
if (luminance1 === null || luminance2 === null) return null;
|
|
823
|
+
return calculateContrastRatio(luminance1, luminance2);
|
|
548
824
|
}
|
|
825
|
+
//#endregion
|
|
826
|
+
//#region src/getContrastRatio.ts
|
|
827
|
+
/**
|
|
828
|
+
* Calculates the contrast ratio between two colours.
|
|
829
|
+
* Accepts CSS RGB colours (including percentage values), named CSS colours, or hexadecimal colours.
|
|
830
|
+
* @param colour1 The first colour (e.g., `#ff0000`, `rgb(255,0,0)`, `rgb(100%, 0%, 0%)`, or `red`)
|
|
831
|
+
* @param colour2 The second colour (e.g., `#00ff00`, `rgb(0,255,0)`, `rgb(0%, 100%, 0%)`, or `lime`)
|
|
832
|
+
* @param standard The standard to evaluate the contrast ratio against, defaults to WCAG2.1
|
|
833
|
+
* @returns The contrast ratio between the two colours truncated to 3 decimal places
|
|
834
|
+
*/
|
|
549
835
|
function getContrastRatio(colour1, colour2, standard) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
} else {
|
|
570
|
-
const _hexColour2 = convertNamedCSSColourtoHex(colour2);
|
|
571
|
-
if (_hexColour2 === void 0) {
|
|
572
|
-
throw new Error(
|
|
573
|
-
`getContrastRatio expects valid CSS named colours. ${colour2} is not a valid CSS named colour. See ${NAMED_CSS_COLOUR_URL}`
|
|
574
|
-
);
|
|
575
|
-
}
|
|
576
|
-
hexColour2 = _hexColour2;
|
|
577
|
-
}
|
|
578
|
-
return getContrastRatioFromHex(hexColour1, hexColour2, standard);
|
|
836
|
+
let hexColour1;
|
|
837
|
+
let hexColour2;
|
|
838
|
+
if (colour1.startsWith("#")) hexColour1 = colour1;
|
|
839
|
+
else if (colour1.startsWith("rgb")) hexColour1 = convertCSSRGBtoHex(colour1);
|
|
840
|
+
else {
|
|
841
|
+
const _hexColour1 = convertNamedCSSColourtoHex(colour1);
|
|
842
|
+
if (_hexColour1 === void 0) throw new Error(`getContrastRatio expects valid CSS named colours. ${colour1} is not a valid CSS named colour. See ${NAMED_CSS_COLOUR_URL}`);
|
|
843
|
+
hexColour1 = _hexColour1;
|
|
844
|
+
}
|
|
845
|
+
if (colour2.startsWith("#")) hexColour2 = colour2;
|
|
846
|
+
else if (colour2.startsWith("rgb")) hexColour2 = convertCSSRGBtoHex(colour2);
|
|
847
|
+
else {
|
|
848
|
+
const _hexColour2 = convertNamedCSSColourtoHex(colour2);
|
|
849
|
+
if (_hexColour2 === void 0) throw new Error(`getContrastRatio expects valid CSS named colours. ${colour2} is not a valid CSS named colour. See ${NAMED_CSS_COLOUR_URL}`);
|
|
850
|
+
hexColour2 = _hexColour2;
|
|
851
|
+
}
|
|
852
|
+
const result = getContrastRatioFromHex(hexColour1, hexColour2, standard);
|
|
853
|
+
if (result === null) throw new Error(`getContrastRatio expects valid hexadecimal colours but received ${hexColour1} or ${hexColour2}.`);
|
|
854
|
+
return result;
|
|
579
855
|
}
|
|
856
|
+
//#endregion
|
|
857
|
+
//#region src/getContrastRatioFromCSSRGB.ts
|
|
858
|
+
/**
|
|
859
|
+
* Calculates the contrast ratio between two colours in CSSRGB format.
|
|
860
|
+
* @param colour1 The first colour in CSSRGB format
|
|
861
|
+
* @param colour2 The second colour in CSSRGB format
|
|
862
|
+
* @param standard The standard to evaluate the contrast ratio against, defaults to WCAG2.1
|
|
863
|
+
* @returns The contrast ratio between the two colours truncated to 3 decimal places
|
|
864
|
+
*/
|
|
580
865
|
function getContrastRatioFromCSSRGB(colour1, colour2, standard) {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
const luminance2 = getSRGBLuminanceFromRGB(rgbColour2, standard);
|
|
585
|
-
return calculateContrastRatio(luminance1, luminance2);
|
|
866
|
+
const rgbColour1 = convertCSSRGBtoRGB(colour1);
|
|
867
|
+
const rgbColour2 = convertCSSRGBtoRGB(colour2);
|
|
868
|
+
return calculateContrastRatio(getSRGBLuminanceFromRGB(rgbColour1, standard), getSRGBLuminanceFromRGB(rgbColour2, standard));
|
|
586
869
|
}
|
|
870
|
+
//#endregion
|
|
871
|
+
//#region src/getContrastRatioFromNamedCSSColour.ts
|
|
872
|
+
/**
|
|
873
|
+
* Calculates the contrast ratio between two named CSS colours in.
|
|
874
|
+
*
|
|
875
|
+
* See list here https://developer.mozilla.org/en-US/docs/Web/CSS/named-color
|
|
876
|
+
* @param colour1 The first named CSS colour
|
|
877
|
+
* @param colour2 The second named CSS colour
|
|
878
|
+
* @param standard The standard to evaluate the contrast ratio against, defaults to WCAG2.1
|
|
879
|
+
* @returns The contrast ratio between the two colours truncated to 3 decimal places
|
|
880
|
+
*/
|
|
587
881
|
function getContrastRatioFromNamedCSSColour(colour1, colour2, standard) {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
`getContrastRatioFromNamedCSSColour expects valid CSS named colours. ${colour1} or ${colour2} are not valid CSS named colours. See ${NAMED_CSS_COLOUR_URL}`
|
|
593
|
-
);
|
|
594
|
-
}
|
|
595
|
-
return getContrastRatioFromHex(hexColour1, hexColour2, standard);
|
|
882
|
+
const hexColour1 = convertNamedCSSColourtoHex(colour1);
|
|
883
|
+
const hexColour2 = convertNamedCSSColourtoHex(colour2);
|
|
884
|
+
if (hexColour1 === void 0 || hexColour2 === void 0) throw new Error(`getContrastRatioFromNamedCSSColour expects valid CSS named colours. ${colour1} or ${colour2} are not valid CSS named colours. See ${NAMED_CSS_COLOUR_URL}`);
|
|
885
|
+
return getContrastRatioFromHex(hexColour1, hexColour2, standard);
|
|
596
886
|
}
|
|
887
|
+
//#endregion
|
|
888
|
+
//#region src/isHexDarkColour.ts
|
|
889
|
+
/**
|
|
890
|
+
* Evaluates if a colour is dark by measuring the contrast ratio against black and white
|
|
891
|
+
* @param {string} colour - A colour in the hexadecimal format
|
|
892
|
+
* @param {"WCAG2.1" | "WCAG3.0"} standard - Evaluate agains "WCAG2.1" or "WCAG3.0"
|
|
893
|
+
* @returns {boolean} Returns either `true` or `false`
|
|
894
|
+
*/
|
|
597
895
|
function isHexDarkColour(colour, standard) {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
896
|
+
const lum = getSRGBLuminanceFromHex(colour, standard);
|
|
897
|
+
if (lum === null) return false;
|
|
898
|
+
const colourLuminance = lum + .05;
|
|
899
|
+
return 1.05 / colourLuminance > colourLuminance / .05;
|
|
602
900
|
}
|
|
901
|
+
//#endregion
|
|
902
|
+
//#region src/isCSSNameDarkColour.ts
|
|
903
|
+
/**
|
|
904
|
+
* Evaluates if a named CSS colour is dark by measuring the contrast ratio against black and white
|
|
905
|
+
* @param {string} name - A named CSS colour, ie: `hotpink`
|
|
906
|
+
* @param {"WCAG2.1" | "WCAG3.0"} standard - Evaluate agains "WCAG2.1" or "WCAG3.0"
|
|
907
|
+
* @returns { boolean } Returns `true`, `false` or `undefined` if name is not a valid CSS named colour
|
|
908
|
+
*/
|
|
603
909
|
function isCSSNamedDarkColour(name, standard) {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
}
|
|
608
|
-
throw new Error(
|
|
609
|
-
`${name} is not a valid colour format. isCSSNamedDarkColour only accepts CSS named colours. See ${NAMED_CSS_COLOUR_URL}`
|
|
610
|
-
);
|
|
910
|
+
const hex = convertNamedCSSColourtoHex(name);
|
|
911
|
+
if (hex) return isHexDarkColour(hex, standard);
|
|
912
|
+
throw new Error(`${name} is not a valid colour format. isCSSNamedDarkColour only accepts CSS named colours. See ${NAMED_CSS_COLOUR_URL}`);
|
|
611
913
|
}
|
|
914
|
+
//#endregion
|
|
915
|
+
//#region src/isCSSRGBDarkColour.ts
|
|
612
916
|
function isCSSRGBDarkColour(colour, standard) {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
const whiteContrast = 1.05 / colourLuminance;
|
|
616
|
-
const blackContrast = colourLuminance / 0.05;
|
|
617
|
-
return whiteContrast > blackContrast;
|
|
917
|
+
const colourLuminance = getSRGBLuminanceFromRGB(convertCSSRGBtoRGB(colour), standard);
|
|
918
|
+
return 1.05 / colourLuminance > colourLuminance / .05;
|
|
618
919
|
}
|
|
920
|
+
//#endregion
|
|
921
|
+
//#region src/isDarkColour.ts
|
|
922
|
+
/**
|
|
923
|
+
* Evaluates if a colour is dark by measuring the contrast ratio against black and white.
|
|
924
|
+
* It accepts CSS RGB colours (including percentage values), named CSS or hexadecimal.
|
|
925
|
+
*
|
|
926
|
+
* If you know in advance the type of colour you want to evaluate consider using `isCSSNamedDarkColour`, `isCSSRGBDarkColour` or `isHexDarkColour` as they are smaller functions
|
|
927
|
+
* @param {string | NamedCSSColour} colour - A colour value, ie: `hotpink`, `#333222`, `rgb(12,34,45)`, or `rgb(50%, 25%, 100%)`
|
|
928
|
+
* @param {"WCAG2.1" | "WCAG3.0"} standard - Evaluate agains "WCAG2.1" or "WCAG3.0"
|
|
929
|
+
* @returns {boolean} Returns `true`, `false` or `undefined` if name is not a valid CSS named colour
|
|
930
|
+
*/
|
|
619
931
|
function isDarkColour(colour, standard) {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
}
|
|
634
|
-
const relativeContrast = (firstColour, secondColour) => {
|
|
635
|
-
if (firstColour > secondColour) {
|
|
636
|
-
return firstColour / secondColour;
|
|
637
|
-
}
|
|
638
|
-
return secondColour / firstColour;
|
|
932
|
+
try {
|
|
933
|
+
if (colour.startsWith("#")) return isHexDarkColour(colour, standard);
|
|
934
|
+
if (colour.startsWith("rgb")) return isCSSRGBDarkColour(colour, standard);
|
|
935
|
+
return isCSSNamedDarkColour(colour, standard);
|
|
936
|
+
} catch (_error) {
|
|
937
|
+
throw new Error(`${colour} is not a valid colour format. isDarkColour accepts CSS RGB formats, ie rgb(0,0,0), rgba(255, 255, 255, 0.4), rgb(50%, 25%, 100%), and rgba(50%, 25%, 100%, 80%), hexadecimal and CSS named colours.`);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
//#endregion
|
|
941
|
+
//#region src/pickHexColourContrast.ts
|
|
942
|
+
var relativeContrast = (firstColour, secondColour) => {
|
|
943
|
+
if (firstColour > secondColour) return firstColour / secondColour;
|
|
944
|
+
return secondColour / firstColour;
|
|
639
945
|
};
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
backgroundColourLuminance
|
|
651
|
-
);
|
|
652
|
-
if (optionOneContrast > optionTwoContrast) {
|
|
653
|
-
return optionOneColour;
|
|
654
|
-
}
|
|
655
|
-
return optionTwoColour;
|
|
946
|
+
var pickHexColourContrast = ({ backgroundColour, optionOneColour, optionTwoColour }, standard) => {
|
|
947
|
+
const bgLum = getSRGBLuminanceFromHex(backgroundColour, standard);
|
|
948
|
+
const opt1Lum = getSRGBLuminanceFromHex(optionOneColour, standard);
|
|
949
|
+
const opt2Lum = getSRGBLuminanceFromHex(optionTwoColour, standard);
|
|
950
|
+
if (bgLum === null || opt1Lum === null || opt2Lum === null) return null;
|
|
951
|
+
const backgroundColourLuminance = bgLum + .05;
|
|
952
|
+
const optionOneColourLuminance = opt1Lum + .05;
|
|
953
|
+
const optionTwoColourLuminance = opt2Lum + .05;
|
|
954
|
+
if (relativeContrast(optionOneColourLuminance, backgroundColourLuminance) > relativeContrast(optionTwoColourLuminance, backgroundColourLuminance)) return optionOneColour;
|
|
955
|
+
return optionTwoColour;
|
|
656
956
|
};
|
|
957
|
+
//#endregion
|
|
958
|
+
//#region src/convertRGBtoHSV.ts
|
|
959
|
+
/**
|
|
960
|
+
* Converts an RGB colour to HSV.
|
|
961
|
+
* Conversion formula is based on the RGB to HSV conversion algorithm:
|
|
962
|
+
* https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
|
|
963
|
+
*
|
|
964
|
+
* https://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
|
|
965
|
+
* - Hue (h) is calculated based on the maximum RGB value.
|
|
966
|
+
* - Saturation (s) is the chroma divided by the maximum RGB value.
|
|
967
|
+
* - Value (v) is the maximum RGB value.
|
|
968
|
+
* @param r Red channel (0-255)
|
|
969
|
+
* @param g Green channel (0-255)
|
|
970
|
+
* @param b Blue channel (0-255)
|
|
971
|
+
* @returns HSVColour object with h (hue), s (saturation), v (value)
|
|
972
|
+
*/
|
|
657
973
|
function convertRGBtoHSV(r, g, b) {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
if (hue < 0) {
|
|
682
|
-
hue = hue + 360;
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
return { h: hue, s: saturation, v: value };
|
|
974
|
+
const red = r / 255;
|
|
975
|
+
const green = g / 255;
|
|
976
|
+
const blue = b / 255;
|
|
977
|
+
const max = Math.max(red, green, blue);
|
|
978
|
+
const min = Math.min(red, green, blue);
|
|
979
|
+
let hue = 0;
|
|
980
|
+
let saturation = 0;
|
|
981
|
+
const value = max;
|
|
982
|
+
const delta = max - min;
|
|
983
|
+
if (max === 0) saturation = 0;
|
|
984
|
+
else saturation = delta / max;
|
|
985
|
+
if (delta !== 0) {
|
|
986
|
+
if (max === red) hue = (green - blue) / delta % 6;
|
|
987
|
+
else if (max === green) hue = (blue - red) / delta + 2;
|
|
988
|
+
else if (max === blue) hue = (red - green) / delta + 4;
|
|
989
|
+
hue = hue * 60;
|
|
990
|
+
if (hue < 0) hue = hue + 360;
|
|
991
|
+
}
|
|
992
|
+
return {
|
|
993
|
+
h: hue,
|
|
994
|
+
s: saturation,
|
|
995
|
+
v: value
|
|
996
|
+
};
|
|
686
997
|
}
|
|
998
|
+
//#endregion
|
|
999
|
+
//#region src/sortHexColours.ts
|
|
1000
|
+
/**
|
|
1001
|
+
* Helper to parse hex and get HSV + alpha
|
|
1002
|
+
* @param hex Hex color string
|
|
1003
|
+
* @returns Object with hue, saturation, value, and alpha
|
|
1004
|
+
*/
|
|
687
1005
|
function getColorInfo(hex) {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
1006
|
+
const rgb = convertHextoRGB(hex);
|
|
1007
|
+
if (!rgb) return null;
|
|
1008
|
+
const { R, G, B, A } = rgb;
|
|
1009
|
+
const a = typeof A === "number" ? A : 1;
|
|
1010
|
+
const { h, s, v } = convertRGBtoHSV(R, G, B);
|
|
1011
|
+
return {
|
|
1012
|
+
h,
|
|
1013
|
+
s,
|
|
1014
|
+
v,
|
|
1015
|
+
a
|
|
1016
|
+
};
|
|
692
1017
|
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Helper to get custom order for greyscale values
|
|
1020
|
+
* @param v HSV value (brightness) from 0 to 1
|
|
1021
|
+
* @returns Order priority: 0 for grey/other, 1 for black, 2 for white
|
|
1022
|
+
*/
|
|
693
1023
|
function getGreyscaleOrder(v) {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1024
|
+
if (v === 0) return 1;
|
|
1025
|
+
if (v === 1) return 2;
|
|
1026
|
+
return 0;
|
|
697
1027
|
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Sorts an array of hex colours by hue, then by saturation.
|
|
1030
|
+
* Colours with 0% saturation (greyscale) are shifted to the end.
|
|
1031
|
+
* Fully transparent colours (alpha = 0) are placed at the very end.
|
|
1032
|
+
* Accepts hex colours in #RRGGBB or #RRGGBBAA format.
|
|
1033
|
+
* @param hexColours Array of hex colour strings
|
|
1034
|
+
* @returns Sorted array of hex colour strings
|
|
1035
|
+
*/
|
|
698
1036
|
function sortHexColours(hexColours) {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
return [...normal, ...greyscale, ...transparent];
|
|
1037
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1038
|
+
for (const hex of hexColours) if (!cache.has(hex)) {
|
|
1039
|
+
const info = getColorInfo(hex);
|
|
1040
|
+
if (info) cache.set(hex, info);
|
|
1041
|
+
}
|
|
1042
|
+
const normal = [];
|
|
1043
|
+
const greyscale = [];
|
|
1044
|
+
const transparent = [];
|
|
1045
|
+
for (const hex of hexColours) {
|
|
1046
|
+
const info = cache.get(hex);
|
|
1047
|
+
if (!info) continue;
|
|
1048
|
+
const { s, a } = info;
|
|
1049
|
+
if (a === 0) transparent.push(hex);
|
|
1050
|
+
else if (s === 0) greyscale.push(hex);
|
|
1051
|
+
else normal.push(hex);
|
|
1052
|
+
}
|
|
1053
|
+
normal.sort((a, b) => {
|
|
1054
|
+
const ca = cache.get(a);
|
|
1055
|
+
const cb = cache.get(b);
|
|
1056
|
+
if (ca.h !== cb.h) return ca.h - cb.h;
|
|
1057
|
+
return cb.s - ca.s;
|
|
1058
|
+
});
|
|
1059
|
+
greyscale.sort((a, b) => {
|
|
1060
|
+
const ca = cache.get(a);
|
|
1061
|
+
const cb = cache.get(b);
|
|
1062
|
+
const orderA = getGreyscaleOrder(ca.v);
|
|
1063
|
+
const orderB = getGreyscaleOrder(cb.v);
|
|
1064
|
+
if (orderA !== orderB) return orderA - orderB;
|
|
1065
|
+
return ca.v - cb.v;
|
|
1066
|
+
});
|
|
1067
|
+
transparent.sort((a, b) => {
|
|
1068
|
+
const ca = cache.get(a);
|
|
1069
|
+
const cb = cache.get(b);
|
|
1070
|
+
return ca.v - cb.v;
|
|
1071
|
+
});
|
|
1072
|
+
return [
|
|
1073
|
+
...normal,
|
|
1074
|
+
...greyscale,
|
|
1075
|
+
...transparent
|
|
1076
|
+
];
|
|
740
1077
|
}
|
|
1078
|
+
//#endregion
|
|
741
1079
|
exports.RGBdistance = RGBdistance;
|
|
742
1080
|
exports.ciede2000 = ciede2000;
|
|
743
1081
|
exports.convertCSSRGBtoHex = convertCSSRGBtoHex;
|