color-bits 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/parse.ts DELETED
@@ -1,392 +0,0 @@
1
- import { Color, newColor } from './core';
2
- import * as convert from './convert'
3
-
4
- const HASH = '#'.charCodeAt(0);
5
- const PERCENT = '%'.charCodeAt(0);
6
- const G = 'g'.charCodeAt(0);
7
- const N = 'n'.charCodeAt(0);
8
- const D = 'd'.charCodeAt(0);
9
- const E = 'e'.charCodeAt(0);
10
-
11
- /**
12
- * Approximative CSS colorspace string pattern, e.g. rgb(), color()
13
- */
14
- const PATTERN = (() => {
15
- const NAME = '(\\w+)'
16
- const SEPARATOR = '[\\s,\\/]'
17
- const VALUE = '([^\\s,\\/]+)'
18
- const SEPARATOR_THEN_VALUE = `(?:${SEPARATOR}+${VALUE})`
19
-
20
- return new RegExp(
21
- `${NAME}\\(
22
- ${SEPARATOR}*${VALUE}
23
- ${SEPARATOR_THEN_VALUE}
24
- ${SEPARATOR_THEN_VALUE}
25
- ${SEPARATOR_THEN_VALUE}?
26
- ${SEPARATOR_THEN_VALUE}?
27
- ${SEPARATOR}*
28
- \\)`.replace(/\s/g, '')
29
- )
30
- })();
31
-
32
-
33
- /**
34
- * Parse CSS color
35
- * @param color CSS color string: #xxx, #xxxxxx, #xxxxxxxx, rgb(), rgba(), hsl(), hsla(), color()
36
- */
37
- export function parse(color: string): Color {
38
- if (color.charCodeAt(0) === HASH) {
39
- return parseHex(color);
40
- } else {
41
- return parseColor(color);
42
- }
43
- }
44
-
45
- /**
46
- * Parse hexadecimal CSS color
47
- * @param color Hex color string: #xxx, #xxxxxx, #xxxxxxxx
48
- */
49
- export function parseHex(hex: string): Color {
50
- let r = 0x00;
51
- let g = 0x00;
52
- let b = 0x00;
53
- let a = 0xff;
54
-
55
- switch (hex.length) {
56
- // #59f
57
- case 4: {
58
- r = (hexValue(hex.charCodeAt(1)) << 4) + hexValue(hex.charCodeAt(1));
59
- g = (hexValue(hex.charCodeAt(2)) << 4) + hexValue(hex.charCodeAt(2));
60
- b = (hexValue(hex.charCodeAt(3)) << 4) + hexValue(hex.charCodeAt(3));
61
- break;
62
- }
63
- // #5599ff
64
- case 7: {
65
- r = (hexValue(hex.charCodeAt(1)) << 4) + hexValue(hex.charCodeAt(2));
66
- g = (hexValue(hex.charCodeAt(3)) << 4) + hexValue(hex.charCodeAt(4));
67
- b = (hexValue(hex.charCodeAt(5)) << 4) + hexValue(hex.charCodeAt(6));
68
- break;
69
- }
70
- // #5599ff88
71
- case 9: {
72
- r = (hexValue(hex.charCodeAt(1)) << 4) + hexValue(hex.charCodeAt(2));
73
- g = (hexValue(hex.charCodeAt(3)) << 4) + hexValue(hex.charCodeAt(4));
74
- b = (hexValue(hex.charCodeAt(5)) << 4) + hexValue(hex.charCodeAt(6));
75
- a = (hexValue(hex.charCodeAt(7)) << 4) + hexValue(hex.charCodeAt(8));
76
- break;
77
- }
78
- default: {
79
- break;
80
- }
81
- }
82
-
83
- return newColor(r, g, b, a)
84
- }
85
-
86
- // https://lemire.me/blog/2019/04/17/parsing-short-hexadecimal-strings-efficiently/
87
- function hexValue(c: number) {
88
- return (c & 0xF) + 9 * (c >> 6)
89
- }
90
-
91
-
92
- /**
93
- * Parse CSS color
94
- * https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
95
- * @param color CSS color string: rgb(), rgba(), hsl(), hsla(), color()
96
- */
97
- export function parseColor(color: string): Color {
98
- const match = PATTERN.exec(color);
99
- if (match === null) {
100
- throw new Error(`Color.parse(): invalid CSS color: "${color}"`);
101
- }
102
-
103
- const format = match[1];
104
- const p1 = match[2];
105
- const p2 = match[3];
106
- const p3 = match[4];
107
- const p4 = match[5];
108
- const p5 = match[6];
109
-
110
- switch (format) {
111
- case 'rgb':
112
- case 'rgba': {
113
- const r = parseColorChannel(p1);
114
- const g = parseColorChannel(p2);
115
- const b = parseColorChannel(p3);
116
- const a = p4 ? parseAlphaChannel(p4) : 255;
117
-
118
- return newColor(r, g, b, a);
119
- }
120
- case 'hsl':
121
- case 'hsla': {
122
- const h = parseAngle(p1);
123
- const s = parsePercentage(p2);
124
- const l = parsePercentage(p3);
125
- const a = p4 ? parseAlphaChannel(p4) : 255;
126
-
127
- // https://stackoverflow.com/a/9493060/3112706
128
- let r, g, b;
129
- if (s === 0) {
130
- r = g = b = Math.round(l * 255); // achromatic
131
- } else {
132
- const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
133
- const p = 2 * l - q;
134
- r = Math.round(hueToRGB(p, q, h + 1 / 3) * 255);
135
- g = Math.round(hueToRGB(p, q, h) * 255);
136
- b = Math.round(hueToRGB(p, q, h - 1 / 3) * 255);
137
- }
138
-
139
- return newColor(r, g, b, a);
140
- }
141
- case 'hwb': {
142
- const h = parseAngle(p1);
143
- const w = parsePercentage(p2);
144
- const bl = parsePercentage(p3);
145
- const a = p4 ? parseAlphaChannel(p4) : 255;
146
-
147
- /* https://drafts.csswg.org/css-color/#hwb-to-rgb */
148
- const s = 1.0;
149
- const l = 0.5;
150
-
151
- // Same as HSL to RGB
152
- const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
153
- const p = 2 * l - q;
154
- let r = Math.round(hueToRGB(p, q, h + 1 / 3) * 255);
155
- let g = Math.round(hueToRGB(p, q, h) * 255);
156
- let b = Math.round(hueToRGB(p, q, h - 1 / 3) * 255);
157
-
158
- // Then HWB
159
- r = hwbApply(r, w, bl);
160
- g = hwbApply(g, w, bl);
161
- b = hwbApply(b, w, bl);
162
-
163
- return newColor(r, g, b, a);
164
- }
165
- case 'lab': {
166
- const l = parsePercentageOrValue(p1);
167
- const aa = parsePercentageOrValue(p2);
168
- const b = parsePercentageOrValue(p3);
169
- const a = p4 ? parseAlphaChannel(p4) : 255;
170
- return newColorFromArray(a,
171
- convert.xyzd50ToSrgb(...convert.labToXyzd50(l, aa, b))
172
- )
173
- }
174
- case 'lch': {
175
- const l = parsePercentageOrValue(p1);
176
- const c = parsePercentageOrValue(p2);
177
- const h = parsePercentageOrValue(p3);
178
- const a = p4 ? parseAlphaChannel(p4) : 255;
179
- return newColorFromArray(a,
180
- convert.xyzd50ToSrgb(...convert.labToXyzd50(...convert.lchToLab(l, c, h)))
181
- )
182
- }
183
- case 'oklab': {
184
- const l = parsePercentageOrValue(p1);
185
- const aa = parsePercentageOrValue(p2);
186
- const b = parsePercentageOrValue(p3);
187
- const a = p4 ? parseAlphaChannel(p4) : 255;
188
- return newColorFromArray(a,
189
- convert.xyzd50ToSrgb(...convert.oklchToXyzd50(l, aa, b))
190
- )
191
- }
192
- case 'oklch': {
193
- const l = parsePercentageOrValue(p1);
194
- const c = parsePercentageOrValue(p2);
195
- const h = parsePercentageOrValue(p3);
196
- const a = p4 ? parseAlphaChannel(p4) : 255;
197
- return newColorFromArray(a,
198
- convert.xyzd50ToSrgb(...convert.oklchToXyzd50(l, c, h))
199
- )
200
- }
201
- case 'color': {
202
- // https://drafts.csswg.org/css-color-4/#color-function
203
-
204
- const colorspace = p1;
205
- const c1 = parsePercentageOrValue(p2);
206
- const c2 = parsePercentageOrValue(p3);
207
- const c3 = parsePercentageOrValue(p4);
208
- const a = p5 ? parseAlphaChannel(p5) : 255;
209
-
210
- switch (colorspace) {
211
- // RGB color spaces
212
- case 'srgb': {
213
- return newColorFromArray(a,
214
- [c1, c2, c3]
215
- )
216
- }
217
- case 'srgb-linear': {
218
- return newColorFromArray(a,
219
- convert.xyzd50ToSrgb(...convert.srgbLinearToXyzd50(c1, c2, c3))
220
- )
221
- }
222
- case 'display-p3': {
223
- return newColorFromArray(a,
224
- convert.xyzd50ToSrgb(...convert.displayP3ToXyzd50(c1, c2, c3))
225
- )
226
- }
227
- case 'a98-rgb': {
228
- return newColorFromArray(a,
229
- convert.xyzd50ToSrgb(...convert.adobeRGBToXyzd50(c1, c2, c3))
230
- )
231
- }
232
- case 'prophoto-rgb': {
233
- return newColorFromArray(a,
234
- convert.xyzd50ToSrgb(...convert.proPhotoToXyzd50(c1, c2, c3))
235
- )
236
- }
237
- case 'rec2020': {
238
- return newColorFromArray(a,
239
- convert.xyzd50ToSrgb(...convert.rec2020ToXyzd50(c1, c2, c3))
240
- )
241
- }
242
- // XYZ color spaces
243
- case 'xyz':
244
- case 'xyz-d65': {
245
- return newColorFromArray(a,
246
- convert.xyzd50ToSrgb(...convert.xyzd65ToD50(c1, c2, c3))
247
- )
248
- }
249
- case 'xyz-d50': {
250
- return newColorFromArray(a,
251
- convert.xyzd50ToSrgb(c1, c2, c3)
252
- )
253
- }
254
- default:
255
- }
256
- }
257
- default:
258
- }
259
- throw new Error(`Color.parse(): invalid CSS color: "${color}"`);
260
- }
261
-
262
- /**
263
- * Accepts: "50%", "128"
264
- * https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb#values
265
- * @returns a value in the 0 to 255 range
266
- */
267
- function parseColorChannel(channel: string): number {
268
- if (channel.charCodeAt(channel.length - 1) === PERCENT) {
269
- return Math.round((parseFloat(channel) / 100) * 255);
270
- }
271
- return Math.round(parseFloat(channel));
272
- }
273
-
274
- /**
275
- * Accepts: "50%", ".5", "0.5"
276
- * https://developer.mozilla.org/en-US/docs/Web/CSS/alpha-value
277
- * @returns a value in the [0, 255] range
278
- */
279
- function parseAlphaChannel(channel: string): number {
280
- return Math.round(parseAlphaValue(channel) * 255);
281
- }
282
-
283
- /**
284
- * Accepts: "50%", ".5", "0.5"
285
- * https://developer.mozilla.org/en-US/docs/Web/CSS/alpha-value
286
- * @returns a value in the [0, 1] range
287
- */
288
- function parseAlphaValue(channel: string): number {
289
- if (channel.charCodeAt(0) === N) {
290
- return 0;
291
- }
292
- if (channel.charCodeAt(channel.length - 1) === PERCENT) {
293
- return parseFloat(channel) / 100;
294
- }
295
- return parseFloat(channel);
296
- }
297
-
298
- /**
299
- * Accepts: "360", "360deg", "400grad", "6.28rad", "1turn", "none"
300
- * https://developer.mozilla.org/en-US/docs/Web/CSS/angle
301
- * @returns a value in the 0.0 to 1.0 range
302
- */
303
- function parseAngle(angle: string): number {
304
- let factor = 1;
305
- switch (angle.charCodeAt(angle.length - 1)) {
306
- case E: {
307
- // 'none'
308
- return 0;
309
- }
310
- case D: {
311
- // 'rad', 'grad'
312
- if (angle.charCodeAt(Math.max(0, angle.length - 4)) === G) {
313
- // 'grad'
314
- factor = 400;
315
- } else {
316
- // 'rad'
317
- factor = 2 * Math.PI; // TAU
318
- }
319
- break;
320
- }
321
- case N: {
322
- // 'turn'
323
- factor = 1;
324
- break;
325
- }
326
- // case G: // 'deg', but no need to check as it's also the default
327
- default: {
328
- factor = 360;
329
- }
330
- }
331
- return parseFloat(angle) / factor;
332
- }
333
-
334
- /**
335
- * Accepts: "100%", "none"
336
- * @returns a value in the 0.0 to 1.0 range
337
- */
338
- function parsePercentage(value: string): number {
339
- if (value.charCodeAt(0) === N) {
340
- return 0;
341
- }
342
- return parseFloat(value) / 100;
343
- }
344
-
345
- /**
346
- * Accepts: "1.0", "100%", "none"
347
- * @returns a value in the 0.0 to 1.0 range
348
- */
349
- function parsePercentageOrValue(value: string): number {
350
- if (value.charCodeAt(0) === N) {
351
- return 0;
352
- }
353
- if (value.charCodeAt(value.length - 1) === PERCENT) {
354
- return parseFloat(value) / 100;
355
- }
356
- return parseFloat(value);
357
- }
358
-
359
-
360
- // HSL functions
361
-
362
- function hueToRGB(p: number, q: number, t: number) {
363
- if (t < 0) { t += 1 };
364
- if (t > 1) { t -= 1 };
365
- if (t < 1 / 6) { return p + (q - p) * 6 * t };
366
- if (t < 1 / 2) { return q };
367
- if (t < 2 / 3) { return p + (q - p) * (2 / 3 - t) * 6 };
368
- { return p };
369
- }
370
-
371
- // HWB functions
372
-
373
- function hwbApply(channel: number, w: number, b: number) {
374
- let result = channel / 255
375
-
376
- result *= 1 - w - b
377
- result += w
378
-
379
- return Math.round(result * 255)
380
- }
381
-
382
-
383
- function clamp(value: number) {
384
- return Math.max(0, Math.min(255, value))
385
- }
386
-
387
- function newColorFromArray(a: number, rgb: [number, number, number]) {
388
- const r = clamp(Math.round(rgb[0] * 255))
389
- const g = clamp(Math.round(rgb[1] * 255))
390
- const b = clamp(Math.round(rgb[2] * 255))
391
- return newColor(r, g, b, a)
392
- }
package/src/transform.ts DELETED
@@ -1,58 +0,0 @@
1
- import {
2
- Color,
3
- getRed,
4
- getGreen,
5
- getBlue,
6
- getAlpha,
7
- setAlpha,
8
- newColor,
9
- } from './core';
10
-
11
- /**
12
- * Modifies color alpha channel.
13
- * @param color - Color
14
- * @param value - Value in the range [0, 1]
15
- */
16
- export function alpha(color: Color, value: number): Color {
17
- return setAlpha(color, Math.round(value * 255))
18
- }
19
-
20
- /**
21
- * Darkens a color.
22
- * @param color - Color
23
- * @param coefficient - Multiplier in the range [0, 1]
24
- */
25
- export function darken(color: Color, coefficient: number): Color {
26
- const r = getRed(color);
27
- const g = getGreen(color);
28
- const b = getBlue(color);
29
- const a = getAlpha(color);
30
-
31
- const factor = 1 - coefficient;
32
-
33
- return newColor(
34
- r * factor,
35
- g * factor,
36
- b * factor,
37
- a,
38
- )
39
- }
40
-
41
- /**
42
- * Lighten a color.
43
- * @param color - Color
44
- * @param coefficient - Multiplier in the range [0, 1]
45
- */
46
- export function lighten(color: Color, coefficient: number): Color {
47
- const r = getRed(color);
48
- const g = getGreen(color);
49
- const b = getBlue(color);
50
- const a = getAlpha(color);
51
-
52
- return newColor(
53
- r + (255 - r) * coefficient,
54
- g + (255 - g) * coefficient,
55
- b + (255 - b) * coefficient,
56
- a,
57
- )
58
- }
@@ -1,7 +0,0 @@
1
- import { from } from './core'
2
- import { parse } from './parse'
3
- import * as Transform from './transform'
4
-
5
- export function alpha(color: string, value: number) { return from(Transform.alpha(parse(color), value)) }
6
- export function darken(color: string, value: number) { return from(Transform.darken(parse(color), value)) }
7
- export function lighten(color: string, value: number) { return from(Transform.lighten(parse(color), value)) }