1 | import * as types from '../utils/types';
|
2 | import * as knownColors from './known-colors';
|
3 | import { Color } from '.';
|
4 | const SHARP = '#';
|
5 | const HEX_REGEX = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)|(^#[0-9A-F]{8}$)/i;
|
6 | export class ColorBase {
|
7 | constructor(...args) {
|
8 | if (args.length === 1) {
|
9 | const arg = args[0];
|
10 | if (types.isString(arg)) {
|
11 | const lowered = arg.toLowerCase();
|
12 | if (isRgbOrRgba(lowered)) {
|
13 | this._argb = argbFromRgbOrRgba(lowered);
|
14 | }
|
15 | else if (isHslOrHsla(lowered)) {
|
16 | this._argb = argbFromHslOrHsla(lowered);
|
17 | }
|
18 | else if (isHsvOrHsva(lowered)) {
|
19 | this._argb = argbFromHsvOrHsva(lowered);
|
20 | }
|
21 | else if (knownColors.isKnownName(lowered)) {
|
22 |
|
23 | const argb = knownColors.getKnownColor(lowered);
|
24 | this._name = arg;
|
25 | this._argb = argb;
|
26 | }
|
27 | else if (arg[0].charAt(0) === SHARP && (arg.length === 4 || arg.length === 7 || arg.length === 9)) {
|
28 |
|
29 |
|
30 |
|
31 | this._argb = this._argbFromString(arg);
|
32 | }
|
33 | else {
|
34 | throw new Error('Invalid color: ' + arg);
|
35 | }
|
36 | }
|
37 | else if (types.isNumber(arg)) {
|
38 |
|
39 |
|
40 | this._argb = arg >>> 0;
|
41 | }
|
42 | else if (arg && arg._argb) {
|
43 |
|
44 |
|
45 |
|
46 | this._argb = arg._argb >>> 0;
|
47 | }
|
48 | else {
|
49 | throw new Error('Expected 1 or 4 constructor parameters.');
|
50 | }
|
51 | }
|
52 | else if (args.length >= 4) {
|
53 | const a = args[0];
|
54 | switch (args[4]) {
|
55 | case 'hsl': {
|
56 | const { r, g, b } = hslToRgb(args[1], args[2], args[3]);
|
57 | this._argb = (a & 0xff) * 0x01000000 + (r & 0xff) * 0x00010000 + (g & 0xff) * 0x00000100 + (b & 0xff) * 0x00000001;
|
58 | break;
|
59 | }
|
60 | case 'hsv': {
|
61 | const { r, g, b } = hsvToRgb(args[1], args[2], args[3]);
|
62 | this._argb = (a & 0xff) * 0x01000000 + (r & 0xff) * 0x00010000 + (g & 0xff) * 0x00000100 + (b & 0xff) * 0x00000001;
|
63 | break;
|
64 | }
|
65 | default:
|
66 | this._argb = (a & 0xff) * 0x01000000 + (args[1] & 0xff) * 0x00010000 + (args[2] & 0xff) * 0x00000100 + (args[3] & 0xff) * 0x00000001;
|
67 | break;
|
68 | }
|
69 | }
|
70 | else {
|
71 | throw new Error('Expected 1 or 4 constructor parameters.');
|
72 | }
|
73 | }
|
74 | get a() {
|
75 | return (this._argb / 0x01000000) & 0xff;
|
76 | }
|
77 | get r() {
|
78 | return (this._argb / 0x00010000) & 0xff;
|
79 | }
|
80 | get g() {
|
81 | return (this._argb / 0x00000100) & 0xff;
|
82 | }
|
83 | get b() {
|
84 | return (this._argb / 0x00000001) & 0xff;
|
85 | }
|
86 | get argb() {
|
87 | return this._argb;
|
88 | }
|
89 | get hex() {
|
90 | let result = SHARP + ('000000' + (this._argb & 0xffffff).toString(16)).toUpperCase().slice(-6);
|
91 | if (this.a !== 0xff) {
|
92 | return (result += ('00' + this.a.toString(16).toUpperCase()).slice(-2));
|
93 | }
|
94 | return result;
|
95 | }
|
96 | get name() {
|
97 | return this._name;
|
98 | }
|
99 | get ios() {
|
100 | return undefined;
|
101 | }
|
102 | get android() {
|
103 | return undefined;
|
104 | }
|
105 | _argbFromString(hex) {
|
106 |
|
107 | hex = hex.substring(1);
|
108 | const length = hex.length;
|
109 |
|
110 | if (length === 3) {
|
111 | hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
112 | }
|
113 | else if (length === 4) {
|
114 | hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];
|
115 | }
|
116 | let intVal = parseInt(hex, 16);
|
117 | if (hex.length === 6) {
|
118 |
|
119 | intVal = (intVal & 0x00ffffff) + 0xff000000;
|
120 | }
|
121 | else {
|
122 |
|
123 |
|
124 | const a = (intVal / 0x00000001) & 0xff;
|
125 | intVal = (intVal >>> 8) + (a & 0xff) * 0x01000000;
|
126 | }
|
127 | return intVal;
|
128 | }
|
129 | equals(value) {
|
130 | return value && this.argb === value.argb;
|
131 | }
|
132 | static equals(value1, value2) {
|
133 |
|
134 | if (!value1 && !value2) {
|
135 | return true;
|
136 | }
|
137 |
|
138 | if (!value1 || !value2) {
|
139 | return false;
|
140 | }
|
141 | return value1.equals(value2);
|
142 | }
|
143 | static isValid(value) {
|
144 | if (value instanceof Color) {
|
145 | return true;
|
146 | }
|
147 | if (!types.isString(value)) {
|
148 | return false;
|
149 | }
|
150 | const lowered = value.toLowerCase();
|
151 | if (knownColors.isKnownName(lowered)) {
|
152 | return true;
|
153 | }
|
154 | return HEX_REGEX.test(value) || isRgbOrRgba(lowered) || isHslOrHsla(lowered);
|
155 | }
|
156 | static fromHSL(a, h, s, l) {
|
157 | return new Color(a, h, s, l, 'hsl');
|
158 | }
|
159 | static fromHSV(a, h, s, l) {
|
160 | return new Color(a, h, s, l, 'hsv');
|
161 | }
|
162 | toString() {
|
163 | return this.hex;
|
164 | }
|
165 | |
166 |
|
167 |
|
168 | static fromIosColor(value) {
|
169 | return undefined;
|
170 | }
|
171 | |
172 |
|
173 |
|
174 |
|
175 | isDark() {
|
176 | return this.getBrightness() < 128;
|
177 | }
|
178 | |
179 |
|
180 |
|
181 |
|
182 | isLight() {
|
183 | return !this.isDark();
|
184 | }
|
185 | |
186 |
|
187 |
|
188 |
|
189 | getBrightness() {
|
190 | return (this.r * 299 + this.g * 587 + this.b * 114) / 1000;
|
191 | }
|
192 | |
193 |
|
194 |
|
195 |
|
196 | getLuminance() {
|
197 | let R, G, B;
|
198 | const RsRGB = this.r / 255;
|
199 | const GsRGB = this.g / 255;
|
200 | const BsRGB = this.b / 255;
|
201 | if (RsRGB <= 0.03928) {
|
202 | R = RsRGB / 12.92;
|
203 | }
|
204 | else {
|
205 | R = Math.pow((RsRGB + 0.055) / 1.055, 2.4);
|
206 | }
|
207 | if (GsRGB <= 0.03928) {
|
208 | G = GsRGB / 12.92;
|
209 | }
|
210 | else {
|
211 | G = Math.pow((GsRGB + 0.055) / 1.055, 2.4);
|
212 | }
|
213 | if (BsRGB <= 0.03928) {
|
214 | B = BsRGB / 12.92;
|
215 | }
|
216 | else {
|
217 | B = Math.pow((BsRGB + 0.055) / 1.055, 2.4);
|
218 | }
|
219 | return 0.2126 * R + 0.7152 * G + 0.0722 * B;
|
220 | }
|
221 | |
222 |
|
223 |
|
224 |
|
225 |
|
226 | setAlpha(a) {
|
227 | return new Color(a, this.r, this.g, this.b);
|
228 | }
|
229 | |
230 |
|
231 |
|
232 |
|
233 | toHsl() {
|
234 | return { ...rgbToHsl(this.r, this.g, this.b), a: this.a };
|
235 | }
|
236 | |
237 |
|
238 |
|
239 |
|
240 | toHslString() {
|
241 | const hsl = rgbToHsl(this.r, this.g, this.b);
|
242 | const h = Math.round(hsl.h), s = Math.round(hsl.s), l = Math.round(hsl.l);
|
243 | const a = this.a;
|
244 | return a == 255 ? 'hsl(' + h + ', ' + s + '%, ' + l + '%)' : 'hsla(' + h + ', ' + s + '%, ' + l + '%, ' + (a / 255).toFixed(2) + ')';
|
245 | }
|
246 | |
247 |
|
248 |
|
249 |
|
250 | toHsv() {
|
251 | return { ...rgbToHsv(this.r, this.g, this.b), a: this.a };
|
252 | }
|
253 | |
254 |
|
255 |
|
256 |
|
257 | toHsvString() {
|
258 | const hsv = rgbToHsv(this.r, this.g, this.b);
|
259 | const h = Math.round(hsv.h * 360), s = Math.round(hsv.s * 100), v = Math.round(hsv.v * 100);
|
260 | const a = this.a;
|
261 | return a == 255 ? 'hsv(' + h + ', ' + s + '%, ' + v + '%)' : 'hsva(' + h + ', ' + s + '%, ' + v + '%, ' + (a / 255).toFixed(2) + ')';
|
262 | }
|
263 | |
264 |
|
265 |
|
266 |
|
267 | toRgbString() {
|
268 | const a = this.a;
|
269 | return a == 1 ? 'rgb(' + Math.round(this.r) + ', ' + Math.round(this.g) + ', ' + Math.round(this.b) + ')' : 'rgba(' + Math.round(this.r) + ', ' + Math.round(this.g) + ', ' + Math.round(this.b) + ', ' + (a / 255).toFixed(2) + ')';
|
270 | }
|
271 | |
272 |
|
273 |
|
274 |
|
275 |
|
276 | desaturate(amount) {
|
277 | amount = amount === 0 ? 0 : amount || 10;
|
278 | const hsl = rgbToHsl(this.r, this.g, this.b);
|
279 | return Color.fromHSL(this.a, hsl.h, Math.min(100, Math.max(0, hsl.s - amount)), hsl.l);
|
280 | }
|
281 | |
282 |
|
283 |
|
284 |
|
285 |
|
286 | saturate(amount) {
|
287 | amount = amount === 0 ? 0 : amount || 10;
|
288 | const hsl = rgbToHsl(this.r, this.g, this.b);
|
289 | return Color.fromHSL(this.a, hsl.h, Math.min(100, Math.max(0, hsl.s + amount)), hsl.l);
|
290 | }
|
291 | |
292 |
|
293 |
|
294 |
|
295 | greyscale() {
|
296 | return this.desaturate(100);
|
297 | }
|
298 | |
299 |
|
300 |
|
301 |
|
302 |
|
303 | lighten(amount) {
|
304 | amount = amount === 0 ? 0 : amount || 10;
|
305 | const hsl = rgbToHsl(this.r, this.g, this.b);
|
306 | return Color.fromHSL(this.a, hsl.h, hsl.s, Math.min(100, Math.max(0, hsl.l + amount)));
|
307 | }
|
308 | |
309 |
|
310 |
|
311 |
|
312 |
|
313 | brighten(amount) {
|
314 | amount = amount === 0 ? 0 : amount || 10;
|
315 | const r = Math.max(0, Math.min(255, this.r - Math.round(255 * -(amount / 100))));
|
316 | const g = Math.max(0, Math.min(255, this.g - Math.round(255 * -(amount / 100))));
|
317 | const b = Math.max(0, Math.min(255, this.b - Math.round(255 * -(amount / 100))));
|
318 | return new Color(this.a, r, g, b);
|
319 | }
|
320 | |
321 |
|
322 |
|
323 |
|
324 |
|
325 | darken(amount) {
|
326 | amount = amount === 0 ? 0 : amount || 10;
|
327 | const hsl = rgbToHsl(this.r, this.g, this.b);
|
328 | return Color.fromHSL(this.a, hsl.h, hsl.s, Math.min(100, Math.max(0, hsl.l - amount)));
|
329 | }
|
330 | |
331 |
|
332 |
|
333 |
|
334 |
|
335 | spin(amount) {
|
336 | const hsl = this.toHsl();
|
337 | const hue = (hsl.h + amount) % 360;
|
338 | hsl.h = hue < 0 ? 360 + hue : hue;
|
339 | return Color.fromHSL(this.a, hsl.h, hsl.s, hsl.l);
|
340 | }
|
341 | |
342 |
|
343 |
|
344 |
|
345 | complement() {
|
346 | const hsl = this.toHsl();
|
347 | hsl.h = (hsl.h + 180) % 360;
|
348 | return Color.fromHSL(this.a, hsl.h, hsl.s, hsl.l);
|
349 | }
|
350 | static mix(color1, color2, amount = 50) {
|
351 | const p = amount / 100;
|
352 | const rgba = {
|
353 | r: (color2.r - color1.r) * p + color1.r,
|
354 | g: (color2.g - color1.g) * p + color1.g,
|
355 | b: (color2.b - color1.b) * p + color1.b,
|
356 | a: (color2.a - color1.a) * p + color1.a,
|
357 | };
|
358 | return new Color(rgba.a, rgba.r, rgba.g, rgba.b);
|
359 | }
|
360 | }
|
361 | function isRgbOrRgba(value) {
|
362 | return (value.startsWith('rgb(') || value.startsWith('rgba(')) && value.endsWith(')');
|
363 | }
|
364 | function isHslOrHsla(value) {
|
365 | return (value.startsWith('hsl') || value.startsWith('hsla(')) && value.endsWith(')');
|
366 | }
|
367 | function isHsvOrHsva(value) {
|
368 | return (value.startsWith('hsv') || value.startsWith('hsva(')) && value.endsWith(')');
|
369 | }
|
370 | function parseColorWithAlpha(value) {
|
371 | const separator = value.indexOf(',') !== -1 ? ',' : ' ';
|
372 | const parts = value
|
373 | .replace(/(rgb|hsl|hsv)a?\(/, '')
|
374 | .replace(')', '')
|
375 | .replace(/\//, ' ')
|
376 | .replace(/%/g, '')
|
377 | .split(separator)
|
378 | .filter((part) => Boolean(part.length));
|
379 | let f = 255;
|
380 | let s = 255;
|
381 | let t = 255;
|
382 | let a = 255;
|
383 | if (parts[0]) {
|
384 | f = parseFloat(parts[0].trim());
|
385 | }
|
386 | if (parts[1]) {
|
387 | s = parseFloat(parts[1].trim());
|
388 | }
|
389 | if (parts[2]) {
|
390 | t = parseFloat(parts[2].trim());
|
391 | }
|
392 | if (parts[3]) {
|
393 | a = Math.round(parseFloat(parts[3].trim()) * 255);
|
394 | }
|
395 | return { f, s, t, a };
|
396 | }
|
397 | function argbFromRgbOrRgba(value) {
|
398 | const { f: r, s: g, t: b, a } = parseColorWithAlpha(value);
|
399 | return (a & 0xff) * 0x01000000 + (r & 0xff) * 0x00010000 + (g & 0xff) * 0x00000100 + (b & 0xff);
|
400 | }
|
401 | function argbFromHslOrHsla(value) {
|
402 | const { f: h, s: s, t: l, a } = parseColorWithAlpha(value);
|
403 | const { r, g, b } = hslToRgb(h, s, l);
|
404 | return (a & 0xff) * 0x01000000 + (r & 0xff) * 0x00010000 + (g & 0xff) * 0x00000100 + (b & 0xff);
|
405 | }
|
406 | function argbFromHsvOrHsva(value) {
|
407 | const { f: h, s: s, t: v, a } = parseColorWithAlpha(value);
|
408 | const { r, g, b } = hsvToRgb(h, s, v);
|
409 | return (a & 0xff) * 0x01000000 + (r & 0xff) * 0x00010000 + (g & 0xff) * 0x00000100 + (b & 0xff);
|
410 | }
|
411 |
|
412 |
|
413 |
|
414 |
|
415 | function rgbToHsl(r, g, b) {
|
416 | r /= 255;
|
417 | g /= 255;
|
418 | b /= 255;
|
419 | const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
420 | let h, s;
|
421 | const l = (max + min) / 2;
|
422 | if (max == min) {
|
423 | h = s = 0;
|
424 | }
|
425 | else {
|
426 | const d = max - min;
|
427 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
428 | switch (max) {
|
429 | case r:
|
430 | h = (g - b) / d + (g < b ? 6 : 0);
|
431 | break;
|
432 | case g:
|
433 | h = (b - r) / d + 2;
|
434 | break;
|
435 | case b:
|
436 | h = (r - g) / d + 4;
|
437 | break;
|
438 | }
|
439 | h /= 6;
|
440 | }
|
441 | return { h: h * 360, s: s * 100, l: l * 100 };
|
442 | }
|
443 | function hue2rgb(p, q, t) {
|
444 | if (t < 0)
|
445 | t += 1;
|
446 | if (t > 1)
|
447 | t -= 1;
|
448 | if (t < 1 / 6)
|
449 | return p + (q - p) * 6 * t;
|
450 | if (t < 1 / 2)
|
451 | return q;
|
452 | if (t < 2 / 3)
|
453 | return p + (q - p) * (2 / 3 - t) * 6;
|
454 | return p;
|
455 | }
|
456 |
|
457 |
|
458 |
|
459 |
|
460 | function hslToRgb(h1, s1, l1) {
|
461 | const h = (h1 % 360) / 360;
|
462 | const s = s1 / 100;
|
463 | const l = l1 / 100;
|
464 | let r, g, b;
|
465 | if (s === 0) {
|
466 | r = g = b = l;
|
467 | }
|
468 | else {
|
469 | const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
470 | const p = 2 * l - q;
|
471 | r = hue2rgb(p, q, h + 1 / 3);
|
472 | g = hue2rgb(p, q, h);
|
473 | b = hue2rgb(p, q, h - 1 / 3);
|
474 | }
|
475 | return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) };
|
476 | }
|
477 |
|
478 |
|
479 |
|
480 |
|
481 | function rgbToHsv(r, g, b) {
|
482 | r /= 255;
|
483 | g /= 255;
|
484 | b /= 255;
|
485 | const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
486 | let h;
|
487 | const v = max;
|
488 | const d = max - min;
|
489 | const s = max === 0 ? 0 : d / max;
|
490 | if (max == min) {
|
491 | h = 0;
|
492 | }
|
493 | else {
|
494 | switch (max) {
|
495 | case r:
|
496 | h = (g - b) / d + (g < b ? 6 : 0);
|
497 | break;
|
498 | case g:
|
499 | h = (b - r) / d + 2;
|
500 | break;
|
501 | case b:
|
502 | h = (r - g) / d + 4;
|
503 | break;
|
504 | }
|
505 | h /= 6;
|
506 | }
|
507 | return { h: h * 360, s: s * 100, v: v * 100 };
|
508 | }
|
509 |
|
510 |
|
511 |
|
512 |
|
513 | function hsvToRgb(h1, s1, v1) {
|
514 | const h = ((h1 % 360) / 360) * 6;
|
515 | const s = s1 / 100;
|
516 | const v = v1 / 100;
|
517 | var i = Math.floor(h), f = h - i, p = v * (1 - s), q = v * (1 - f * s), t = v * (1 - (1 - f) * s), mod = i % 6, r = [v, q, p, p, t, v][mod], g = [t, v, v, q, p, p][mod], b = [p, p, t, v, v, q][mod];
|
518 | return { r: r * 255, g: g * 255, b: b * 255 };
|
519 | }
|
520 |
|
\ | No newline at end of file |