UNPKG

17.7 kBJavaScriptView Raw
1import * as types from '../utils/types';
2import * as knownColors from './known-colors';
3import { Color } from '.';
4const SHARP = '#';
5const HEX_REGEX = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)|(^#[0-9A-F]{8}$)/i;
6export 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 // The parameter is a known color name
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 // we dont use the regexp as it is quite slow. Instead we expect it to be a valid hex format
29 // strange that it would not be. And if it is not a thrown error seems best
30 // The parameter is a "#RRGGBBAA" formatted string
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 // The parameter is a 32-bit unsigned integer where each 8 bits specify a color component
39 // In case a 32-bit signed int (Android, Java has no unsigned types) was provided - convert to unsigned by applyint >>> 0
40 this._argb = arg >>> 0;
41 }
42 else if (arg && arg._argb) {
43 // we would go there if a color was passed as an argument (or an object which is why we dont do instanceof)
44 // The parameter is a 32-bit unsigned integer where each 8 bits specify a color component
45 // In case a 32-bit signed int (Android, Java has no unsigned types) was provided - convert to unsigned by applyint >>> 0
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 // always called as SHARP as first char
107 hex = hex.substring(1);
108 const length = hex.length;
109 // first we normalize
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 // add the alpha component since the provided string is RRGGBB
119 intVal = (intVal & 0x00ffffff) + 0xff000000;
120 }
121 else {
122 // the new format is #RRGGBBAA
123 // we need to shift the alpha value to 0x01000000 position
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 // both values are falsy
134 if (!value1 && !value2) {
135 return true;
136 }
137 // only one is falsy
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 * @param {UIColor} value
167 */
168 static fromIosColor(value) {
169 return undefined;
170 }
171 /**
172 * return true if brightness < 128
173 *
174 */
175 isDark() {
176 return this.getBrightness() < 128;
177 }
178 /**
179 * return true if brightness >= 128
180 *
181 */
182 isLight() {
183 return !this.isDark();
184 }
185 /**
186 * return the [brightness](http://www.w3.org/TR/AERT#color-contrast)
187 *
188 */
189 getBrightness() {
190 return (this.r * 299 + this.g * 587 + this.b * 114) / 1000;
191 }
192 /**
193 * return the [luminance](http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef)
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 * Return this color (as a new Color instance) with the provided alpha
223 *
224 * @param alpha (between 0 and 255)
225 */
226 setAlpha(a) {
227 return new Color(a, this.r, this.g, this.b);
228 }
229 /**
230 * return the hsl representation of the color
231 *
232 */
233 toHsl() {
234 return { ...rgbToHsl(this.r, this.g, this.b), a: this.a };
235 }
236 /**
237 * return the [CSS hsv](https://www.w3schools.com/Css/css_colors_hsl.asp) representation of the color
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 * return the hsv representation of the color
248 *
249 */
250 toHsv() {
251 return { ...rgbToHsv(this.r, this.g, this.b), a: this.a };
252 }
253 /**
254 * return the [CSS hsv](https://www.w3schools.com/Css/css_colors_rgb.asp) representation of the color
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 * return the [CSS rgb](https://www.w3schools.com/Css/css_colors_rgb.asp) representation of the color
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 * Desaturate the color a given amount, from 0 to 100. Providing 100 will is the same as calling greyscale.
273 *
274 * @param amount (between 0 and 100)
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 * Saturate the color a given amount, from 0 to 100.
283 *
284 * @param amount (between 0 and 100)
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 * Completely desaturates a color into greyscale. Same as calling desaturate(100).
293 *
294 */
295 greyscale() {
296 return this.desaturate(100);
297 }
298 /**
299 * Lighten the color a given amount, from 0 to 100. Providing 100 will always return white.
300 *
301 * @param amount (between 0 and 100)
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 * Brighten the color a given amount, from 0 to 100.
310 *
311 * @param amount (between 0 and 100)
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 * Darken the color a given amount, from 0 to 100. Providing 100 will always return black.
322 *
323 * @param amount (between 0 and 100)
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 * Spin the hue a given amount, from -360 to 360. Calling with 0, 360, or -360 will do nothing (since it sets the hue back to what it was before).
332 *
333 * @param amount (between -360 and 360)
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 * returns the color complement
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}
361function isRgbOrRgba(value) {
362 return (value.startsWith('rgb(') || value.startsWith('rgba(')) && value.endsWith(')');
363}
364function isHslOrHsla(value) {
365 return (value.startsWith('hsl') || value.startsWith('hsla(')) && value.endsWith(')');
366}
367function isHsvOrHsva(value) {
368 return (value.startsWith('hsv') || value.startsWith('hsva(')) && value.endsWith(')');
369}
370function 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}
397function 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}
401function 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}
406function 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// `rgbToHsl`
412// Converts an RGB color value to HSL.
413// *Assumes:* r, g, and b are contained in [0, 255]
414// *Returns:* { h, s, l } in [0,360] and [0,100]
415function 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; // achromatic
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}
443function 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// `hslToRgb`
457// Converts an HSL color value to RGB.
458// *Assumes:* h is contained in [0, 360] and s and l are contained [0, 100]
459// *Returns:* { r, g, b } in the set [0, 255]
460function 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; // achromatic
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// `rgbToHsv`
478// Converts an RGB color value to HSV
479// *Assumes:* r, g, and b are contained in the set [0, 255]
480// *Returns:* { h, s, v } in [0,360] and [0,100]
481function 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; // achromatic
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// `hsvToRgb`
510// Converts an HSV color value to RGB.
511// *Assumes:* h is contained in [0, 360] and s and v are contained [0, 100]
512// *Returns:* { r, g, b } in the set [0, 255]
513function 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//# sourceMappingURL=color-common.js.map
\No newline at end of file