@auaust/jaune 0.0.0 → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,34 @@
1
+ export {
2
+ fallbackColor,
3
+ isAlphaChannel,
4
+ isColorChannels,
5
+ isRgbChannel,
6
+ toAlphaChannel,
7
+ toColorChannels,
8
+ toRgbChannel,
9
+ } from "~/utils/channels";
10
+ export { isColor, parseColor, type } from "~/utils/colors";
11
+ export { distance } from "~/utils/distance";
12
+ export { isHex, parseHex, toHex } from "~/utils/hex";
13
+ export {
14
+ brightness,
15
+ contrast,
16
+ grayscale,
17
+ isBright,
18
+ isDark,
19
+ linearTosRGB,
20
+ luminance,
21
+ sRGBtoLinear,
22
+ } from "~/utils/luminance";
23
+ export {
24
+ closestNamedColor,
25
+ isAliasToNamedColor,
26
+ isNamedColor,
27
+ namedColorAliases,
28
+ namedColorChannels,
29
+ namedColorsMap,
30
+ namedColorToHex,
31
+ parseNamedColor,
32
+ } from "~/utils/namedColors";
33
+ export { isOpaque, isTranslucent, isTransparent } from "~/utils/opacity";
34
+ export { couldBeRgb, isRgb, parseRgb, toRgb } from "~/utils/rgb";
@@ -0,0 +1,92 @@
1
+ import { N } from "@auaust/primitive-kit";
2
+ import type { ColorChannels } from "~/types";
3
+ import { toColorChannels } from "./channels";
4
+
5
+ /**
6
+ * Returns the linearized value of a sRGB channel.
7
+ *
8
+ * @see https://stackoverflow.com/a/56678483
9
+ */
10
+ export function sRGBtoLinear(channel: number) {
11
+ if (channel <= 0.04045) {
12
+ return channel / 12.92;
13
+ }
14
+
15
+ return Math.pow((channel + 0.055) / 1.055, 2.4);
16
+ }
17
+
18
+ /**
19
+ * Returns the gamma corrected sRGB value of a linear channel.
20
+ *
21
+ * @see sRGBtoLinear
22
+ */
23
+ export function linearTosRGB(channel: number) {
24
+ if (channel <= 0.04045 / 12.92) {
25
+ return channel * 12.92;
26
+ }
27
+
28
+ return 1.055 * Math.pow(channel, 1 / 2.4) - 0.055;
29
+ }
30
+
31
+ /**
32
+ * Returns the luminance of a color normalized to the range [0, 1].
33
+ *
34
+ * @see https://stackoverflow.com/a/56678483
35
+ * @see https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
36
+ */
37
+ export function luminance(channels: ColorChannels) {
38
+ let { r, g, b } = channels;
39
+
40
+ [r, g, b] = [r, g, b].map((v) => sRGBtoLinear(v / 255));
41
+
42
+ return r * 0.2126 + g * 0.7152 + b * 0.0722;
43
+ }
44
+
45
+ /**
46
+ * Returns the perceived brightness of a color normalized to the range [0, 1].
47
+ *
48
+ * @see https://stackoverflow.com/a/56678483
49
+ */
50
+ export function brightness(channels: ColorChannels): number {
51
+ const l = luminance(channels);
52
+
53
+ if (l <= 216 / 24389) {
54
+ return l * (24389 / 27);
55
+ }
56
+
57
+ return (Math.pow(l, 1 / 3) * 116 - 16) / 100;
58
+ }
59
+
60
+ /** Returns whether the color is bright, as in its brightness is greater than 0.5 or the provided threshold. */
61
+ export function isBright(channels: ColorChannels, threshold = 0.5): boolean {
62
+ return brightness(channels) > threshold;
63
+ }
64
+
65
+ /** Returns whether the color is dark, as in its brightness is less than or equal to 0.5 or the provided threshold. */
66
+ export function isDark(channels: ColorChannels, threshold = 0.5): boolean {
67
+ return brightness(channels) <= threshold;
68
+ }
69
+
70
+ /**
71
+ * Returns the contrast ratio between two colors.
72
+ *
73
+ * @see https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
74
+ */
75
+ export function contrast(channelsA: ColorChannels, channelsB: ColorChannels) {
76
+ const [darkest, brightest] = N.minMax(
77
+ luminance(channelsA),
78
+ luminance(channelsB)
79
+ );
80
+
81
+ return (brightest + 0.05) / (darkest + 0.05);
82
+ }
83
+
84
+ /**
85
+ * Returns the grayscale equivalent of a color as a new color channels object.
86
+ */
87
+ export function grayscale(channels: ColorChannels): ColorChannels {
88
+ // Reverse gamma correction from luminance
89
+ const gray = linearTosRGB(luminance(channels)) * 255;
90
+
91
+ return toColorChannels(gray, gray, gray, channels.a);
92
+ }
@@ -0,0 +1,266 @@
1
+ import { O, S } from "@auaust/primitive-kit";
2
+ import type { ColorChannels, MaybeNamedColor, NamedColor } from "~/types";
3
+ import { distance, fallbackColor, parseHex } from "~/utils";
4
+
5
+ /**
6
+ * A map of named colors and their HEX values.
7
+ *
8
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/named-color
9
+ */
10
+ export const namedColorsMap = O.freeze({
11
+ aliceblue: "#f0f8ffff",
12
+ antiquewhite: "#faebd7ff",
13
+ aqua: "#00ffffff",
14
+ aquamarine: "#7fffd4ff",
15
+ azure: "#f0ffffff",
16
+ beige: "#f5f5dcff",
17
+ bisque: "#ffe4c4ff",
18
+ black: "#000000ff",
19
+ blanchedalmond: "#ffebcdff",
20
+ blue: "#0000ffff",
21
+ blueviolet: "#8a2be2ff",
22
+ brown: "#a52a2aff",
23
+ burlywood: "#deb887ff",
24
+ cadetblue: "#5f9ea0ff",
25
+ chartreuse: "#7fff00ff",
26
+ chocolate: "#d2691eff",
27
+ coral: "#ff7f50ff",
28
+ cornflowerblue: "#6495edff",
29
+ cornsilk: "#fff8dcff",
30
+ crimson: "#dc143cff",
31
+ cyan: "#00ffffff",
32
+ darkblue: "#00008bff",
33
+ darkcyan: "#008b8bff",
34
+ darkgoldenrod: "#b8860bff",
35
+ darkgray: "#a9a9a9ff",
36
+ darkgreen: "#006400ff",
37
+ darkgrey: "#a9a9a9ff",
38
+ darkkhaki: "#bdb76bff",
39
+ darkmagenta: "#8b008bff",
40
+ darkolivegreen: "#556b2fff",
41
+ darkorange: "#ff8c00ff",
42
+ darkorchid: "#9932ccff",
43
+ darkred: "#8b0000ff",
44
+ darksalmon: "#e9967aff",
45
+ darkseagreen: "#8fbc8fff",
46
+ darkslateblue: "#483d8bff",
47
+ darkslategray: "#2f4f4fff",
48
+ darkslategrey: "#2f4f4fff",
49
+ darkturquoise: "#00ced1ff",
50
+ darkviolet: "#9400d3ff",
51
+ deeppink: "#ff1493ff",
52
+ deepskyblue: "#00bfffff",
53
+ dimgray: "#696969ff",
54
+ dimgrey: "#696969ff",
55
+ dodgerblue: "#1e90ffff",
56
+ firebrick: "#b22222ff",
57
+ floralwhite: "#fffaf0ff",
58
+ forestgreen: "#228b22ff",
59
+ fuchsia: "#ff00ffff",
60
+ gainsboro: "#dcdcdcff",
61
+ ghostwhite: "#f8f8ffff",
62
+ gold: "#ffd700ff",
63
+ goldenrod: "#daa520ff",
64
+ gray: "#808080ff",
65
+ green: "#008000ff",
66
+ greenyellow: "#adff2fff",
67
+ grey: "#808080ff",
68
+ honeydew: "#f0fff0ff",
69
+ hotpink: "#ff69b4ff",
70
+ indianred: "#cd5c5cff",
71
+ indigo: "#4b0082ff",
72
+ ivory: "#fffff0ff",
73
+ khaki: "#f0e68cff",
74
+ lavender: "#e6e6faff",
75
+ lavenderblush: "#fff0f5ff",
76
+ lawngreen: "#7cfc00ff",
77
+ lemonchiffon: "#fffacdff",
78
+ lightblue: "#add8e6ff",
79
+ lightcoral: "#f08080ff",
80
+ lightcyan: "#e0ffffff",
81
+ lightgoldenrodyellow: "#fafad2ff",
82
+ lightgray: "#d3d3d3ff",
83
+ lightgreen: "#90ee90ff",
84
+ lightgrey: "#d3d3d3ff",
85
+ lightpink: "#ffb6c1ff",
86
+ lightsalmon: "#ffa07aff",
87
+ lightseagreen: "#20b2aaff",
88
+ lightskyblue: "#87cefaff",
89
+ lightslategray: "#778899ff",
90
+ lightslategrey: "#778899ff",
91
+ lightsteelblue: "#b0c4deff",
92
+ lightyellow: "#ffffe0ff",
93
+ lime: "#00ff00ff",
94
+ limegreen: "#32cd32ff",
95
+ linen: "#faf0e6ff",
96
+ magenta: "#ff00ffff",
97
+ maroon: "#800000ff",
98
+ mediumaquamarine: "#66cdaaff",
99
+ mediumblue: "#0000cdff",
100
+ mediumorchid: "#ba55d3ff",
101
+ mediumpurple: "#9370dbff",
102
+ mediumseagreen: "#3cb371ff",
103
+ mediumslateblue: "#7b68eeff",
104
+ mediumspringgreen: "#00fa9aff",
105
+ mediumturquoise: "#48d1ccff",
106
+ mediumvioletred: "#c71585ff",
107
+ midnightblue: "#191970ff",
108
+ mintcream: "#f5fffaff",
109
+ mistyrose: "#ffe4e1ff",
110
+ moccasin: "#ffe4b5ff",
111
+ navajowhite: "#ffdeadff",
112
+ navy: "#000080ff",
113
+ oldlace: "#fdf5e6ff",
114
+ olive: "#808000ff",
115
+ olivedrab: "#6b8e23ff",
116
+ orange: "#ffa500ff",
117
+ orangered: "#ff4500ff",
118
+ orchid: "#da70d6ff",
119
+ palegoldenrod: "#eee8aaff",
120
+ palegreen: "#98fb98ff",
121
+ paleturquoise: "#afeeeeff",
122
+ palevioletred: "#db7093ff",
123
+ papayawhip: "#ffefd5ff",
124
+ peachpuff: "#ffdab9ff",
125
+ peru: "#cd853fff",
126
+ pink: "#ffc0cbff",
127
+ plum: "#dda0ddff",
128
+ powderblue: "#b0e0e6ff",
129
+ purple: "#800080ff",
130
+ rebeccapurple: "#663399ff",
131
+ red: "#ff0000ff",
132
+ rosybrown: "#bc8f8fff",
133
+ royalblue: "#4169e1ff",
134
+ saddlebrown: "#8b4513ff",
135
+ salmon: "#fa8072ff",
136
+ sandybrown: "#f4a460ff",
137
+ seagreen: "#2e8b57ff",
138
+ seashell: "#fff5eeff",
139
+ sienna: "#a0522dff",
140
+ silver: "#c0c0c0ff",
141
+ skyblue: "#87ceebff",
142
+ slateblue: "#6a5acdff",
143
+ slategray: "#708090ff",
144
+ slategrey: "#708090ff",
145
+ snow: "#fffafaff",
146
+ springgreen: "#00ff7fff",
147
+ steelblue: "#4682b4ff",
148
+ tan: "#d2b48cff",
149
+ teal: "#008080ff",
150
+ thistle: "#d8bfd8ff",
151
+ tomato: "#ff6347ff",
152
+ transparent: "#00000000",
153
+ turquoise: "#40e0d0ff",
154
+ violet: "#ee82eeff",
155
+ wheat: "#f5deb3ff",
156
+ white: "#ffffffff",
157
+ whitesmoke: "#f5f5f5ff",
158
+ yellow: "#ffff00ff",
159
+ yellowgreen: "#9acd32ff",
160
+ });
161
+
162
+ const namedColors = new Set<NamedColor>(O.keys(namedColorsMap));
163
+
164
+ const namedColorsChannelsCache: Partial<Record<NamedColor, ColorChannels>> = {};
165
+
166
+ const namedColorsAliasesCache: Partial<
167
+ Record<NamedColor, readonly NamedColor[]>
168
+ > = {};
169
+
170
+ /**
171
+ * Whether the input is a valid named color.
172
+ *
173
+ * The check is case-sensitive.
174
+ */
175
+ export function isNamedColor(value: unknown): value is NamedColor {
176
+ return S.is(value) && namedColors.has(<NamedColor>value.toLowerCase());
177
+ }
178
+
179
+ /**
180
+ * Returns the color channels from a named color. Must be a valid named color, already lowercased.
181
+ *
182
+ * @internal
183
+ */
184
+ export function namedColorChannels(name: NamedColor): ColorChannels {
185
+ return (namedColorsChannelsCache[name] ??= parseHex(namedColorToHex(name)!));
186
+ }
187
+
188
+ /**
189
+ * Returns the color channels from a named color.
190
+ */
191
+ export function parseNamedColor(name: MaybeNamedColor): ColorChannels {
192
+ if (!isNamedColor(name)) {
193
+ return fallbackColor;
194
+ }
195
+
196
+ return namedColorChannels(<NamedColor>name.toLowerCase());
197
+ }
198
+
199
+ /**
200
+ * Returns the corresponding HEX value of a named color.
201
+ */
202
+ export function namedColorToHex(name: NamedColor): string;
203
+ export function namedColorToHex(name: MaybeNamedColor): string | undefined;
204
+ export function namedColorToHex(name: MaybeNamedColor): string | undefined {
205
+ return (
206
+ namedColorsMap[<keyof typeof namedColorsMap>name.toLowerCase()] || undefined
207
+ );
208
+ }
209
+
210
+ /** Returns the closest named color to the passed color channels. */
211
+ export function closestNamedColor(channels: ColorChannels): NamedColor {
212
+ let closest: NamedColor | undefined;
213
+ let smallestDistance = Infinity;
214
+
215
+ for (const name of namedColors) {
216
+ const value = namedColorChannels(name);
217
+ const d = distance(
218
+ value,
219
+ channels,
220
+ value.a === 1 // Ignore alpha channel only if it's 1 -> allows to match transparent/black correctly
221
+ );
222
+
223
+ if (d < smallestDistance) {
224
+ closest = name;
225
+ smallestDistance = d;
226
+ }
227
+ }
228
+
229
+ return closest!;
230
+ }
231
+
232
+ /**
233
+ * Returns all the aliases of a named color.
234
+ */
235
+ export function namedColorAliases(name: NamedColor): readonly NamedColor[] {
236
+ name = <NamedColor>name.toLowerCase();
237
+
238
+ if (!isNamedColor(name)) {
239
+ return [];
240
+ }
241
+
242
+ if (namedColorsAliasesCache[name]) {
243
+ return namedColorsAliasesCache[name]!;
244
+ }
245
+
246
+ const aliases: NamedColor[] = [];
247
+ const targetHex = namedColorToHex(name);
248
+
249
+ for (const [name, hex] of O.entries(namedColorsMap)) {
250
+ if (hex === targetHex) {
251
+ aliases.push(name);
252
+ }
253
+ }
254
+
255
+ return (namedColorsAliasesCache[name] = Object.freeze(aliases));
256
+ }
257
+
258
+ /**
259
+ * Returns a boolean indicating whether two named colors are aliases.
260
+ */
261
+ export function isAliasToNamedColor(
262
+ name: NamedColor,
263
+ alias: NamedColor
264
+ ): boolean {
265
+ return namedColorAliases(name).includes(<NamedColor>alias.toLowerCase());
266
+ }
@@ -0,0 +1,16 @@
1
+ import type { ColorChannels } from "~/types";
2
+
3
+ /** Checks if the color is fully opaque. */
4
+ export function isOpaque(channels: ColorChannels) {
5
+ return channels.a === 1 || channels.a == undefined;
6
+ }
7
+
8
+ /** Checks if the color is fully transparent. */
9
+ export function isTransparent(channels: ColorChannels) {
10
+ return channels.a === 0;
11
+ }
12
+
13
+ /** Checks if the color is at least partially transparent. */
14
+ export function isTranslucent(channels: ColorChannels) {
15
+ return !isOpaque(channels);
16
+ }
@@ -0,0 +1,51 @@
1
+ import { A, N } from "@auaust/primitive-kit";
2
+ import type { ColorChannels, Rgb } from "~/types";
3
+ import {
4
+ fallbackColor,
5
+ isAlphaChannel,
6
+ isRgbChannel,
7
+ toAlphaChannel,
8
+ toColorChannels,
9
+ toRgbChannel,
10
+ } from "./channels";
11
+
12
+ /**
13
+ * Whether the input is a valid RGB color.
14
+ */
15
+ export function isRgb(value: unknown): value is Rgb {
16
+ return (
17
+ A.is(value) &&
18
+ N.isBetween(value.length, 3, 4) &&
19
+ value.every((n, i) => (i === 3 ? isAlphaChannel(n) : isRgbChannel(n)))
20
+ );
21
+ }
22
+
23
+ /**
24
+ * Whether the input could be a valid RGB color.
25
+ * As in, it checks if the input is an array of numbers without validating the values range.
26
+ */
27
+ export function couldBeRgb(value: unknown): value is Rgb {
28
+ return A.is(value) && N.isBetween(value.length, 3, 4) && value.every(N.is);
29
+ }
30
+
31
+ /**
32
+ * Parses a RGB tuple into an object of color channels.
33
+ *
34
+ * The input must already be a valid RGB tuple, otherwise the result will be unexpected.
35
+ */
36
+ export function parseRgb(value: Rgb): ColorChannels {
37
+ return A.is(value)
38
+ ? toColorChannels(value[0], value[1], value[2], value[3]) // Don't spread to avoid mistakenly forwarding `isTransformed` and `isFallback`
39
+ : fallbackColor;
40
+ }
41
+
42
+ /**
43
+ * Returns the corresponding RGB tuple of a color channels object.
44
+ */
45
+ export function toRgb(channels: ColorChannels): Rgb {
46
+ const { r, g, b, a } = channels;
47
+
48
+ return Array.from([r, g, b, a], (value, i) =>
49
+ i === 3 ? toAlphaChannel(value) : toRgbChannel(value)
50
+ );
51
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Used to privately store the cached channels of a color.
3
+ */
4
+ export const channels = Symbol("Color.channels");
5
+
6
+ /**
7
+ * Used to privately store the cached data of a color.
8
+ */
9
+ export const cache = Symbol("Color.cache");