cyberalien-color
Version:
Color library
1,314 lines (1,182 loc) • 59.3 kB
JavaScript
/**
* This file is part of the cyberalien-color package.
*
* (c) Vjacheslav Trushkin <cyberalien@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
"use strict";
(function (_global) {
var modules = {};
(function () {
/**
* List of color keywords
*/
/**
* List of base colors. From https://www.w3.org/TR/css3-color/
* @type Object
*/
const baseKeywords = {
silver: [192, 192, 192],
gray: [128, 128, 128],
white: [255, 255, 255],
maroon: [128, 0, 0],
red: [255, 0, 0],
purple: [128, 0, 128],
fuchsia: [255, 0, 255],
green: [0, 128, 0],
lime: [0, 255, 0],
olive: [128, 128, 0],
yellow: [255, 255, 0],
navy: [0, 0, 128],
blue: [0, 0, 255],
teal: [0, 128, 128],
aqua: [0, 255, 255]
};
/**
* List of extended colors. From https://www.w3.org/TR/css3-color/
* @type Object
*/
const extendedKeywords = {
antiquewhite: [250, 235, 215],
aqua: [0, 255, 255],
aquamarine: [127, 255, 212],
azure: [240, 255, 255],
beige: [245, 245, 220],
bisque: [255, 228, 196],
black: [0, 0, 0],
blanchedalmond: [255, 235, 205],
blue: [0, 0, 255],
blueviolet: [138, 43, 226],
brown: [165, 42, 42],
burlywood: [222, 184, 135],
cadetblue: [95, 158, 160],
chartreuse: [127, 255, 0],
chocolate: [210, 105, 30],
coral: [255, 127, 80],
cornflowerblue: [100, 149, 237],
cornsilk: [255, 248, 220],
crimson: [220, 20, 60],
cyan: [0, 255, 255],
darkblue: [0, 0, 139],
darkcyan: [0, 139, 139],
darkgoldenrod: [184, 134, 11],
darkgray: [169, 169, 169],
darkgreen: [0, 100, 0],
darkgrey: [169, 169, 169],
darkkhaki: [189, 183, 107],
darkmagenta: [139, 0, 139],
darkolivegreen: [85, 107, 47],
darkorange: [255, 140, 0],
darkorchid: [153, 50, 204],
darkred: [139, 0, 0],
darksalmon: [233, 150, 122],
darkseagreen: [143, 188, 143],
darkslateblue: [72, 61, 139],
darkslategray: [47, 79, 79],
darkslategrey: [47, 79, 79],
darkturquoise: [0, 206, 209],
darkviolet: [148, 0, 211],
deeppink: [255, 20, 147],
deepskyblue: [0, 191, 255],
dimgray: [105, 105, 105],
dimgrey: [105, 105, 105],
dodgerblue: [30, 144, 255],
firebrick: [178, 34, 34],
floralwhite: [255, 250, 240],
forestgreen: [34, 139, 34],
fuchsia: [255, 0, 255],
gainsboro: [220, 220, 220],
ghostwhite: [248, 248, 255],
gold: [255, 215, 0],
goldenrod: [218, 165, 32],
gray: [128, 128, 128],
green: [0, 128, 0],
greenyellow: [173, 255, 47],
grey: [128, 128, 128],
honeydew: [240, 255, 240],
hotpink: [255, 105, 180],
indianred: [205, 92, 92],
indigo: [75, 0, 130],
ivory: [255, 255, 240],
khaki: [240, 230, 140],
lavender: [230, 230, 250],
lavenderblush: [255, 240, 245],
lawngreen: [124, 252, 0],
lemonchiffon: [255, 250, 205],
lightblue: [173, 216, 230],
lightcoral: [240, 128, 128],
lightcyan: [224, 255, 255],
lightgoldenrodyellow: [250, 250, 210],
lightgray: [211, 211, 211],
lightgreen: [144, 238, 144],
lightgrey: [211, 211, 211],
lightpink: [255, 182, 193],
lightsalmon: [255, 160, 122],
lightseagreen: [32, 178, 170],
lightskyblue: [135, 206, 250],
lightslategray: [119, 136, 153],
lightslategrey: [119, 136, 153],
lightsteelblue: [176, 196, 222],
lightyellow: [255, 255, 224],
lime: [0, 255, 0],
limegreen: [50, 205, 50],
linen: [250, 240, 230],
magenta: [255, 0, 255],
maroon: [128, 0, 0],
mediumaquamarine: [102, 205, 170],
mediumblue: [0, 0, 205],
mediumorchid: [186, 85, 211],
mediumpurple: [147, 112, 219],
mediumseagreen: [60, 179, 113],
mediumslateblue: [123, 104, 238],
mediumspringgreen: [0, 250, 154],
mediumturquoise: [72, 209, 204],
mediumvioletred: [199, 21, 133],
midnightblue: [25, 25, 112],
mintcream: [245, 255, 250],
mistyrose: [255, 228, 225],
moccasin: [255, 228, 181],
navajowhite: [255, 222, 173],
navy: [0, 0, 128],
oldlace: [253, 245, 230],
olive: [128, 128, 0],
olivedrab: [107, 142, 35],
orange: [255, 165, 0],
orangered: [255, 69, 0],
orchid: [218, 112, 214],
palegoldenrod: [238, 232, 170],
palegreen: [152, 251, 152],
paleturquoise: [175, 238, 238],
palevioletred: [219, 112, 147],
papayawhip: [255, 239, 213],
peachpuff: [255, 218, 185],
peru: [205, 133, 63],
pink: [255, 192, 203],
plum: [221, 160, 221],
powderblue: [176, 224, 230],
purple: [128, 0, 128],
red: [255, 0, 0],
rosybrown: [188, 143, 143],
royalblue: [65, 105, 225],
saddlebrown: [139, 69, 19],
salmon: [250, 128, 114],
sandybrown: [244, 164, 96],
seagreen: [46, 139, 87],
seashell: [255, 245, 238],
sienna: [160, 82, 45],
silver: [192, 192, 192],
skyblue: [135, 206, 235],
slateblue: [106, 90, 205],
slategray: [112, 128, 144],
slategrey: [112, 128, 144],
snow: [255, 250, 250],
springgreen: [0, 255, 127],
steelblue: [70, 130, 180],
tan: [210, 180, 140],
teal: [0, 128, 128],
thistle: [216, 191, 216],
tomato: [255, 99, 71],
turquoise: [64, 224, 208],
violet: [238, 130, 238],
wheat: [245, 222, 179],
white: [255, 255, 255],
whitesmoke: [245, 245, 245],
yellow: [255, 255, 0],
yellowgreen: [154, 205, 50],
// Color module level 4
rebeccapurple: [102, 51, 153]
};
/**
* Object that lists color keywords
*/
const Keywords = {
base: baseKeywords,
extended: extendedKeywords,
all: Object.assign({}, baseKeywords, extendedKeywords)
};
modules['Keywords'] = Keywords;
})();
(function () {
const Keywords = modules['Keywords'];
/**
* Color class
*
* You can set and get color or components in HSL and RGB color spaces,
* import/export color from/to different commonly used formats, mix colors.
* Class automatically converts between color spaces when needed.
*
* Class automatically converts between color spaces when needed.
*/
class Color {
/**
* @ignore
*/
constructor() {
// Reset color spaces
this._rgb = this._rgbRounded = this._hsl = this._hslRounded = this._luminance = null;
this._alpha = 1;
}
/**
* Get keywords object
*
* @returns {Object}
*/
static keywords() {
return Keywords;
}
/**
* Set values
*/
/**
* Set value in RGB color space.
* Setting RGB color resets alpha to 1
*
* @param {number} red Red color component in 0-255 range
* @param {number} green Green color component in 0-255 range
* @param {number} blue Blue color component in 0-255 range
* @param {boolean} [rounded] True if values are rounded.
* Used for performance to avoid rounding values when its not needed.
*
* @returns {Color} Current color instance for method chaining
*/
setRGB(red, green, blue, rounded) {
return this._setRGBArray([red, green, blue, 1], rounded === void 0 ? false : rounded);
}
/**
* Set value in RGB color space with alpha channel.
*
* @param {number} red Red color component in 0-255 range
* @param {number} green Green color component in 0-255 range
* @param {number} blue Blue color component in 0-255 range
* @param {number} alpha Alpha in 0-1 range
* @param {boolean} [rounded] True if values are rounded.
* Used for performance to avoid rounding values when its not needed.
*
* @returns {Color} Current color instance for method chaining
*/
setRGBA(red, green, blue, alpha, rounded) {
return this._setRGBArray([red, green, blue, alpha], rounded === void 0 ? false : rounded);
}
/**
* Set value as RGB array.
* If alpha channel is missing, alpha will be reset to 1
*
* @param {Array} color Array of color components
* @param {boolean|Array} [rounded] True if values are rounded or array of rounded values.
* Used for performance to avoid rounding values when its not needed.
*
* @returns {Color|null} Current color instance for method chaining, null on failure
*/
setRGBArray(color, rounded) {
this._setRGBArray(color.slice(0), typeof rounded === 'boolean' ? rounded : false);
if (rounded !== void 0 && rounded instanceof Array) {
this._rgbRounded = rounded.slice(0);
}
return this;
}
/**
* Set value in HSL color space.
* Setting HSL color resets alpha to 1
*
* @param {number} hue Hue color component in 0-360 range
* @param {number} saturation Saturation color component in 0-100 range
* @param {number} lightness Lightness color component in 0-100 range
* @param {boolean} [rounded] True if values are rounded.
* Used for performance to avoid rounding values when its not needed.
*
* @returns {Color} Current color instance for method chaining
*/
setHSL(hue, saturation, lightness, rounded) {
return this._setHSLArray([hue, saturation, lightness, 1], rounded === void 0 ? false : rounded);
}
/**
* Set value in HSL color space with alpha channel.
*
* @param {number} hue Hue color component in 0-360 range
* @param {number} saturation Saturation color component in 0-100 range
* @param {number} lightness Lightness color component in 0-100 range
* @param {number} alpha Alpha in 0-1 range
* @param {boolean} [rounded] True if values are rounded.
* Used for performance to avoid rounding values when its not needed.
*
* @returns {Color} Current color instance for method chaining
*/
setHSLA(hue, saturation, lightness, alpha, rounded) {
return this._setHSLArray([hue, saturation, lightness, alpha], rounded === void 0 ? false : rounded);
}
/**
* Set value as HSL array.
* If alpha channel is missing, alpha will be reset to 1
*
* @param {Array} color Array of color components
* @param {boolean|Array} [rounded] True if values are rounded or array of rounded values.
* Used for performance to avoid rounding values when its not needed.
*
* @returns {Color|null} Current color instance for method chaining, null on failure
*/
setHSLArray(color, rounded) {
this._setHSLArray(color.slice(0), typeof rounded === 'boolean' ? rounded : false);
if (rounded !== void 0 && rounded instanceof Array) {
this._hslRounded = rounded.slice(0);
}
return this;
}
/**
* Set alpha channel value
*
* @param {number} value Alpha value in 0-1 range
*
* @returns {Color} Current color instance for method chaining
*/
setAlpha(value) {
this._alpha = value;
return this;
}
/**
* Set red color component
*
* @param {number} value Red color component in 0-255 range
* @param {boolean} [rounded] True if value is rounded.
*
* @returns {Color} Current color instance for method chaining
*/
setRed(value, rounded) {
return this._setRGB(0, value, rounded === void 0 ? false : rounded);
}
/**
* Set green color component
*
* @param {number} value Green color component in 0-255 range
* @param {boolean} [rounded] True if value is rounded.
*
* @returns {Color} Current color instance for method chaining
*/
setGreen(value, rounded) {
return this._setRGB(1, value, rounded === void 0 ? false : rounded);
}
/**
* Set blue color component
*
* @param {number} value Blue color component in 0-255 range
* @param {boolean} [rounded] True if value is rounded.
*
* @returns {Color} Current color instance for method chaining
*/
setBlue(value, rounded) {
return this._setRGB(2, value, rounded === void 0 ? false : rounded);
}
/**
* Set hue color component
*
* @param {number} value Hue color component in 0-360 range
* @param {boolean} [rounded] True if value is rounded.
*
* @returns {Color} Current color instance for method chaining
*/
setHue(value, rounded) {
return this._setHSL(0, value, rounded === void 0 ? false : rounded);
}
/**
* Set saturation color component
*
* @param {number} value Saturation color component in 0-100 range
* @param {boolean} [rounded] True if value is rounded.
*
* @returns {Color} Current color instance for method chaining
*/
setSaturation(value, rounded) {
return this._setHSL(1, value, rounded === void 0 ? false : rounded);
}
/**
* Set lightness color component
*
* @param {number} value Lightness color component in 0-100 range
* @param {boolean} [rounded] True if value is rounded.
*
* @returns {Color} Current color instance for method chaining
*/
setLightness(value, rounded) {
return this._setHSL(2, value, rounded === void 0 ? false : rounded);
}
/**
* Mix with another color
*
* @param {Color} color Color to mix this color with
* @param {number} [weight] Percentage of mixed color to be included in mix
*
* @returns {Color} Current color instance for method chaining
*/
mix(color, weight) {
weight = weight === void 0 ? 50 : weight;
if (weight <= 0) {
return this;
}
if (weight >= 100) {
this.reset();
this._setRGBArray(color.getRGBA());
return this;
}
if (this._rgb === null) {
this._convertToRGB();
}
let rgb = color.getRGBA(),
mix2 = weight / 100,
// weight of another color
mix1 = 1 - mix2; // weight of this color
return this._setRGBArray([this._rgb[0] * mix1 + rgb[0] * mix2, this._rgb[1] * mix1 + rgb[1] * mix2, this._rgb[2] * mix1 + rgb[2] * mix2, this._alpha * mix1 + rgb[3] * mix2]);
}
/*
* Get values
*/
/**
* Get RGB value as array
*
* @param {boolean} [round] True if result should be rounded
*
* @return {Array} Array of color values
*/
getRGB(round) {
if (this._rgb === null) {
this._convertToRGB();
}
if (round && this._rgbRounded === null) {
this._roundRGB();
}
return round ? this._rgbRounded.slice(0) : this._rgb.slice(0);
}
/**
* Get RGB value as array with alpha channel
*
* @param {boolean} [round] True if result should be rounded
*
* @return {Array} Array of color values
*/
getRGBA(round) {
let result = this.getRGB(round);
result.push(this._alpha);
return result;
}
/**
* Get HSL value as array
*
* @param {boolean} [round] True if result should be rounded
*
* @return {Array} Array of color values
*/
getHSL(round) {
if (this._hsl === null) {
this._convertToHSL();
}
if (round && this._hslRounded === null) {
this._roundHSL();
}
return round ? this._hslRounded.slice(0) : this._hsl.slice(0);
}
/**
* Get HSL value as array with alpha channel
*
* @param {boolean} [round] True if result should be rounded
*
* @return {Array} Array of color values
*/
getHSLA(round) {
let result = this.getHSL(round);
result.push(this._alpha);
return result;
}
/**
* Get alpha value
*
* @returns {number} Alpha value in 0-1 range
*/
getAlpha() {
return this._alpha;
}
/**
* Get red color component
*
* @param {boolean} [round] True if result should be rounded
*
* @returns {number} Color component in 0-255 range
*/
getRed(round) {
return this._getRGB(0, round);
}
/**
* Get green color component
*
* @param {boolean} [round] True if result should be rounded
*
* @returns {number} Color component in 0-255 range
*/
getGreen(round) {
return this._getRGB(1, round);
}
/**
* Get blue color component
*
* @param {boolean} [round] True if result should be rounded
*
* @returns {number} Color component in 0-255 range
*/
getBlue(round) {
return this._getRGB(2, round);
}
/**
* Get hue color component
*
* @param {boolean} [round] True if result should be rounded
*
* @returns {number} Color component in 0-360 range
*/
getHue(round) {
return this._getHSL(0, round);
}
/**
* Get saturation color component
*
* @param {boolean} [round] True if result should be rounded
*
* @returns {number} Color component in 0-100 range
*/
getSaturation(round) {
return this._getHSL(1, round);
}
/**
* Get lightness color component
*
* @param {boolean} [round] True if result should be rounded
*
* @returns {number} Color component in 0-100 range
*/
getLightness(round) {
return this._getHSL(2, round);
}
/**
* Get luminance
*
* @returns {number}
*/
getLuminance() {
if (this._luminance !== null) {
return this._luminance;
}
let values = [];
if (this._rgb === null) {
this._convertToRGB();
}
for (let i = 0; i < 3; i++) {
let value = this._rgb[i] / 255;
values[i] = value < .03928 ? value / 12.92 : Math.pow((value + .055) / 1.055, 2.4);
}
this._luminance = values[0] * .2126 + values[1] * .7152 + values[2] * 0.0722;
return this._luminance;
}
/**
* Calculate contrast between this and another color
*
* @param {Color|number} color Color or color's luminance value
*
* @returns {number} Contrast in 1-21 range
*/
getContrast(color) {
let lum1 = this.getLuminance() + 0.05,
lum2 = (typeof color === 'number' ? color : color.getLuminance()) + 0.05;
return lum1 > lum2 ? lum1 / lum2 : lum2 / lum1;
}
/*
* Misc functions
*/
/**
* Reset values.
*
* @returns {Color} Current color instance for method chaining
*/
reset() {
this._rgb = this._rgbRounded = this._hsl = this._hslRounded = this._luminance = null;
this._alpha = 1;
return this;
}
/**
* Normalize all values.
*
* @returns {Color} Current color instance for method chaining
*/
normalize() {
// Normalize RGB color space
if (this._rgb !== null) {
this._rgb = [this._rgb[0] < 0 ? 0 : this._rgb[0] > 255 ? 255 : this._rgb[0], this._rgb[1] < 0 ? 0 : this._rgb[1] > 255 ? 255 : this._rgb[1], this._rgb[2] < 0 ? 0 : this._rgb[2] > 255 ? 255 : this._rgb[2]];
if (this._rgbRounded !== null) {
this._rgbRounded = [this._rgbRounded[0] < 0 ? 0 : this._rgbRounded[0] > 255 ? 255 : this._rgbRounded[0], this._rgbRounded[1] < 0 ? 0 : this._rgbRounded[1] > 255 ? 255 : this._rgbRounded[1], this._rgbRounded[2] < 0 ? 0 : this._rgbRounded[2] > 255 ? 255 : this._rgbRounded[2]];
}
}
// Normalize HSL color space
if (this._hsl !== null) {
this._hsl = [this._hsl[0] < 0 ? this._hsl[0] % 360 + 360 : this._hsl[0] >= 360 ? this._hsl[0] % 360 : this._hsl[0], this._hsl[1] < 0 ? 0 : this._hsl[1] > 100 ? 100 : this._hsl[1], this._hsl[2] < 0 ? 0 : this._hsl[2] > 100 ? 100 : this._hsl[2]];
if (this._hslRounded !== null) {
this._hslRounded = [this._hslRounded[0] < 0 ? this._hslRounded[0] % 360 + 360 : this._hslRounded[0] >= 360 ? this._hslRounded[0] % 360 : this._hslRounded[0], this._hslRounded[1] < 0 ? 0 : this._hslRounded[1] > 100 ? 100 : this._hslRounded[1], this._hslRounded[2] < 0 ? 0 : this._hslRounded[2] > 100 ? 100 : this._hslRounded[2]];
}
}
// Normalize alpha
this._alpha = this._alpha < 0 ? 0 : this._alpha > 1 ? 1 : this._alpha;
// Reset luminance cache
this._luminance = null;
return this;
}
/**
* Make a clone of color object
*
* @returns {Color} New color instance
*/
clone() {
let color = new Color();
if (this._rgb !== null) {
color.setRGBArray(this._rgb, this._rgbRounded);
} else {
color.setHSLArray(this._hsl, this._hslRounded);
}
if (this._alpha < 1) {
color.setAlpha(this._alpha);
}
return color;
}
/**
* Create keyword from color object
*
* @param {boolean} [findClosest] True if function should find closest keyword, false if exact match is required
* @param {boolean} [useExtended] True if extended keywords list should be used
*
* @returns {string|boolean} Keyword, false on error
*/
toKeyword(findClosest, useExtended) {
let color, keywords, match, margin, componentMargin, rgb, keyword, diff, componentDiff, maxComponentDiff, i;
findClosest = findClosest === void 0 ? true : findClosest;
useExtended = useExtended === void 0 ? true : useExtended;
// Check for transparent color
if (this._alpha === 0) {
return 'transparent';
}
// Get keywords and rgb color
if (useExtended) {
keywords = Keywords.all;
} else {
keywords = Keywords.base;
}
if (this._rgb === null) {
this._convertToRGB();
}
color = this._rgb;
match = false;
margin = findClosest ? 1000 : 1;
componentMargin = findClosest ? 256 : 1;
// Check each component
for (keyword in keywords) {
//noinspection JSUnfilteredForInLoop
rgb = keywords[keyword];
diff = 0;
maxComponentDiff = 0;
for (i = 0; i < 3; i++) {
componentDiff = Math.abs(rgb[i] - color[i]);
diff += componentDiff;
if (diff > margin) {
break;
}
maxComponentDiff = Math.max(maxComponentDiff, componentDiff);
}
// Check for exact match
if (diff === 0) {
//noinspection JSUnfilteredForInLoop
return keyword;
}
// Compare to previous results
if (findClosest && diff < margin) {
match = keyword;
margin = diff;
componentMargin = maxComponentDiff;
} else if (findClosest && diff === margin && maxComponentDiff < componentMargin) {
// Same overall difference, but each component difference is smaller
match = keyword;
componentMargin = maxComponentDiff;
}
}
return match;
}
/**
* Get hex string
*
* @param {boolean} compress True if color should be compressed (such as #123 instead of #112233)
*
* @returns {string}
*/
toHex(compress) {
return this._getHexValue(compress === void 0 ? false : compress, false);
}
/**
* Get hex string with alpha channel
*
* @param {boolean} [compress] True if color should be compressed (such as #f123 instead of #ff112233)
*
* @returns {string}
*/
toIEHex(compress) {
return this._getHexValue(compress === void 0 ? false : compress, true);
}
/**
* Get color as rgb or rgba string
*
* @param {boolean} [compress] True if string should be compressed
* @param {boolean} [ignoreAlpha] True if alpha channel should be ignored. Returns rgb() string
* @param {number} [alphaPrecision] Number of digits after dot in alpha. Default = 2
*
* @returns {string}
*/
toRGBString(compress, ignoreAlpha, alphaPrecision) {
return this.toString({
format: 'rgb',
compress: compress === void 0 ? false : compress,
ignoreAlpha: ignoreAlpha === void 0 ? false : ignoreAlpha,
alphaPrecision: alphaPrecision === void 0 ? 2 : alphaPrecision
});
}
/**
* Get color as hsl or hsla string
*
* @param {boolean} [compress] True if string should be compressed
* @param {boolean} [ignoreAlpha] True if alpha channel should be ignored. Returns rgb() string
* @param {number} [alphaPrecision] Number of digits after dot in alpha. Default = 2
* @param {number} [roundPrecision] Number of digits after dot in color components. Default = 2
*
* @returns {string}
*/
toHSLString(compress, ignoreAlpha, alphaPrecision, roundPrecision) {
return this.toString({
format: 'hsl',
compress: compress === void 0 ? false : compress,
ignoreAlpha: ignoreAlpha === void 0 ? false : ignoreAlpha,
alphaPrecision: alphaPrecision === void 0 ? 2 : alphaPrecision,
roundPrecision: roundPrecision === void 0 ? 2 : roundPrecision
});
}
/**
* Get value as string
*
* @param {object} options List of options. Possible options:
* format: color format. possible values:
* auto (default): set automatically.
* If RGB color is rounded or roundPrecision is set to 0, result color
* will be in hex (if alpha == 1 or ignored) or rgba format. Otherwise
* result will be in hsl or hsla format.
* rgb: rgb(r, g, b) or rgba(r, g, b, a)
* rgba: rgba(r, g, b, a)
* hsl: hsl(h, s, l) or hsla(h, s, l, a)
* hsla: hsla(h, s, l, a)
* hex: hex string
* iehex: hex string with alpha channel
* ignoreAlpha: true if alpha channel should be ignored. default = false
* This option is ignored when format is set to 'hex' or 'iehex'
* roundPrecision: number of digits after dot in floating numbers. default = 2
* Floating numbers are allowed only in hsl() and hsla() colors
* alphaPrecision: number of digits after dot in alpha channel. default = 2
* If set to 0 alpha channel is ignored. This option is ignored if alpha value
* is 1 or ignoreAlpha is set or if selected format doesn't support alpha channel.
* compress: true if result string should be as short as possible. Examples:
* compressed:
* rgba(1,2,3,.4)
* #123
* not compressed:
* rgba(1, 2, 3, 0.4)
* #112233
*
* @returns {string}
*/
toString(options) {
let rgb,
hsl,
result,
op = options === void 0 ? {} : options,
format = op.format === void 0 ? 'auto' : op.format,
ignoreAlpha = op.ignoreAlpha === void 0 ? false : op.ignoreAlpha,
roundPrecision = op.roundPrecision === void 0 ? 2 : op.roundPrecision,
alphaPrecision = op.alphaPrecision === void 0 ? 2 : op.alphaPrecision,
compress = op.compress === void 0 ? false : op.compress,
alpha = ignoreAlpha ? 1 : Color._round(this._alpha, alphaPrecision),
comma = compress ? ',' : ', ';
switch (format) {
case 'auto':
// Try hex or rgba format
if (this._rgb === null) {
this._convertToRGB();
}
if (this._rgbRounded === null) {
this._roundRGB();
}
// Check if components are rounded
if (roundPrecision > 0 && !Color._equalColors(this._rgb, this._rgbRounded)) {
format = 'hsl';
break;
}
// Rounded or precision == 0
if (alpha === 1) {
return this._getHexValue(compress, false);
}
result = 'rgba(' + this._rgbRounded[0] + comma + this._rgbRounded[1] + comma + this._rgbRounded[2] + comma + alpha + ')';
return compress ? Color._compressString(result) : result;
case 'rgb':
case 'rgba':
if (this._rgb === null) {
this._convertToRGB();
}
if (this._rgbRounded === null) {
this._roundRGB();
}
result = (alpha === 1 && format !== 'rgba' ? 'rgb(' : 'rgba(') + this._rgbRounded[0] + comma + this._rgbRounded[1] + comma + this._rgbRounded[2] + (alpha === 1 && format !== 'rgba' ? '' : comma + alpha) + ')';
return compress ? Color._compressString(result) : result;
case 'hex':
return this._getHexValue(compress, false);
case 'iehex':
return this._getHexValue(compress, true);
}
// Only HSL format left
if (this._hsl === null) {
this._convertToHSL();
}
result = (alpha === 1 && format !== 'hsla' ? 'hsl(' : 'hsla(') + Color._round(this._hsl[0], roundPrecision) + comma + Color._round(this._hsl[1], roundPrecision) + '%' + comma + Color._round(this._hsl[2], roundPrecision) + '%' + (alpha === 1 && format !== 'hsla' ? '' : comma + alpha) + ')';
return compress ? Color._compressString(result) : result;
}
/**
* Create new color object from keyword
*
* @param {string} keyword Color value
* @param {boolean} [useExtended] True if extended keywords list should be used
*
* @returns {Color|null} Color object on success, null on failure
*/
static fromKeyword(keyword, useExtended) {
useExtended = useExtended === void 0 ? true : useExtended;
keyword = keyword.toLowerCase();
if (keyword === 'transparent') {
return new Color().setRGBA(0, 0, 0, 0, true);
}
let keywords = useExtended ? Keywords.all : Keywords.base;
if (keywords[keyword] === void 0) {
return null;
}
return new Color().setRGBArray(keywords[keyword], true);
}
/**
* Create new color object from HEX string
*
* @param {string} color Color string
*
* @returns {Color|null} Color object on success, null on failure
*/
static fromHex(color) {
let red,
green,
blue,
alpha = false,
start = 0;
if (color.slice(0, 1) === '#') {
color = color.slice(1);
}
if (!/^[\da-f]+$/i.test(color)) {
return null;
}
//noinspection FallThroughInSwitchStatementJS
switch (color.length) {
case 4:
alpha = color.slice(0, 1);
alpha += alpha;
start++;
// no break
case 3:
red = color.slice(start, ++start);
green = color.slice(start, ++start);
blue = color.slice(start, ++start);
red += red;
green += green;
blue += blue;
break;
case 8:
alpha = color.slice(0, 2);
start += 2;
// no break
case 6:
red = color.slice(start++, ++start);
green = color.slice(start++, ++start);
blue = color.slice(start++, ++start);
break;
default:
return null;
}
return new Color().setRGBA(parseInt(red, 16), parseInt(green, 16), parseInt(blue, 16), alpha === false ? 1 : parseInt(alpha, 16) / 255, true);
}
/**
* Create new color object from string
*
* @param {string} color Color string
*
* @returns {Color|null} Color object on success, null on failure
*/
static fromString(color) {
let parts, keyword, colors, alpha, result, r, g, b, h, s, l, rounded;
if (color.indexOf('(') === -1) {
// Missing required character. Test for hex string and keyword
result = Color.fromHex(color);
return result === null ? Color.fromKeyword(color) : result;
}
// Remove whitespace and change to lower case
color = color.toLowerCase().replace(/\s+/g, '');
if (color.slice(-1) !== ')') {
return null;
}
color = color.slice(0, color.length - 1);
parts = color.split('(', 2);
if (parts.length !== 2 || /[^\d.,%-]/.test(parts[1])) {
return null;
}
keyword = parts[0];
colors = parts[1].split(',');
alpha = 1;
if (keyword.slice(-1) === 'a') {
// with alpha
if (colors.length !== 4) {
return null;
}
alpha = parseFloat(colors.pop());
if (isNaN(alpha)) {
alpha = 0;
} else {
alpha = alpha < 0 ? 0 : alpha > 1 ? 1 : alpha;
}
} else if (colors.length !== 3) {
return null;
}
switch (keyword) {
case 'rgb':
case 'rgba':
if (colors[0].slice(-1) === '%') {
// All components must be percentages
if (colors[1].slice(-1) !== '%' || colors[2].slice(-1) !== '%') {
return null;
}
// Convert to numbers and normalize colors
r = parseFloat(colors[0]);
g = parseFloat(colors[1]);
b = parseFloat(colors[2]);
return new Color().setRGBA(isNaN(r) || r < 0 ? 0 : r > 100 ? 255 : r * 2.55, isNaN(g) || g < 0 ? 0 : g > 100 ? 255 : g * 2.55, isNaN(b) || b < 0 ? 0 : b > 100 ? 255 : b * 2.55, alpha);
}
// None of components must be percentages
if (parts[1].indexOf('%') !== -1) {
return null;
}
// Double values are not allowed in rgb()
r = parseInt(colors[0]);
g = parseInt(colors[1]);
b = parseInt(colors[2]);
return new Color().setRGBA(isNaN(r) || r < 0 ? 0 : r > 255 ? 255 : r, isNaN(g) || g < 0 ? 0 : g > 255 ? 255 : g, isNaN(b) || b < 0 ? 0 : b > 255 ? 255 : b, alpha, true);
case 'hsl':
case 'hsla':
if (colors[0].indexOf('%') !== -1 || colors[1].slice(-1) !== '%' || colors[2].slice(-1) !== '%') {
// Hue cannot be percentage, saturation and lightness must be percentage
return null;
}
// All values could be double numbers
h = parseFloat(colors[0]);
s = parseFloat(colors[1]);
l = parseFloat(colors[2]);
rounded = parts[1].indexOf('.') === -1;
// Create new object, assign normalized values and return color
return new Color().setHSLA(isNaN(h) ? 0 : h < 0 ? h % 360 + 360 : h >= 360 ? h % 360 : h, isNaN(s) || s < 0 ? 0 : s > 100 ? 100 : s, isNaN(l) || l < 0 ? 0 : l > 100 ? 100 : l, alpha, rounded);
}
return null;
}
/*
* Private functions
*
* Do not call these functions directly
*/
/**
* Set value as RGB array
*
* @param {Array} color Array of color components
* @param {boolean} [rounded] True if values are rounded.
* Used for performance to avoid rounding values when its not needed.
*
* @returns {Color|null} Current color instance for method chaining, null on failure
*
* @private
*/
_setRGBArray(color, rounded) {
if (color.length === 4) {
this._alpha = color.pop();
} else if (color.length !== 3) {
return null;
} else {
this._alpha = 1;
}
this._resetSpaces('rgb');
this._rgb = color;
this._rgbRounded = rounded ? color.slice(0) : null;
return this;
}
/**
* Set value as HSL array
*
* @param {Array} color Array of color components
* @param {boolean} [rounded] True if values are rounded.
* Used for performance to avoid rounding values when its not needed.
*
* @returns {Color|null} Current color instance for method chaining, null on failure
*
* @private
*/
_setHSLArray(color, rounded) {
if (color.length === 4) {
this._alpha = color.pop();
} else if (color.length !== 3) {
return null;
} else {
this._alpha = 1;
}
this._resetSpaces('hsl');
this._hsl = color;
this._hslRounded = rounded ? color.slice(0) : null;
return this;
}
/**
* Internal function used by setRed() and similar functions
*
* @param {number} index
* @param {number} value
* @param {boolean} rounded
*
* @returns {Color}
*
* @private
*/
_setRGB(index, value, rounded) {
if (this._rgb === null) {
this._convertToRGB();
}
// Set value
this._resetSpaces('rgb');
this._rgb[index] = value;
// Set rounded value
if (this._rgbRounded !== null) {
if (rounded || Math.round(value) === value) {
this._rgbRounded[index] = value;
} else {
this._rgbRounded = null;
}
}
return this;
}
/**
* Internal function used by setHue() and similar functions
*
* @param {number} index
* @param {number} value
* @param {boolean} rounded
*
* @returns {Color}
*
* @private
*/
_setHSL(index, value, rounded) {
if (this._hsl === null) {
this._convertToHSL();
}
// Set value
this._resetSpaces('hsl');
this._hsl[index] = value;
// Set rounded value
if (this._hslRounded !== null) {
if (rounded || Math.round(value) === value) {
this._hslRounded[index] = value;
} else {
this._hslRounded = null;
}
}
return this;
}
/**
* Internal function used by getRed() and similar functions
*
* @param {number} index
* @param {boolean} [round]
*
* @returns {*}
*
* @private
*/
_getRGB(index, round) {
if (this._rgb === null) {
this._convertToRGB();
}
if (round && this._rgbRounded === null) {
this._roundRGB();
}
return round ? this._rgbRounded[index] : this._rgb[index];
}
/**
* Internal function used by getHue() and similar functions
*
* @param {number} index
* @param {boolean} [round]
*
* @returns {*}
*
* @private
*/
_getHSL(index, round) {
if (this._hsl === null) {
this._convertToHSL();
}
if (round && this._hslRounded === null) {
this._roundHSL();
}
return round ? this._hslRounded[index] : this._hsl[index];
}
/**
* Round RGB colors
*
* @private
*/
_roundRGB() {
let r = Math.round(this._rgb[0]),
g = Math.round(this._rgb[1]),
b = Math.round(this._rgb[2]);
this._rgbRounded = [r < 0 ? 0 : r > 255 ? 255 : r, g < 0 ? 0 : g > 255 ? 255 : g, b < 0 ? 0 : b > 255 ? 255 : b];
}
/**
* Round HSL colors
*
* @private
*/
_roundHSL() {
let h = Math.round(this._hsl[0]),
s = Math.round(this._hsl[1]),
l = Math.round(this._hsl[2]);
this._hslRounded = [h < 0 ? h % 360 + 360 : h >= 360 ? h % 360 : h, s < 0 ? 0 : s > 100 ? 100 : s, l < 0 ? 0 : l > 100 ? 100 : l];
}
/**
* Normalize values
*
* @param {number} value Value to normalize
* @param {number} [max] Maximum value, default = 100
*
* @returns {number}
*
* @private
*/
static _normalize(value, max) {
max = max === void 0 ? 100 : max;
return value < 0 ? 0 : value > max ? max : value;
}
/**
* Normalize hue value
*
* @param {number}