123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 |
- const hsv2hsl = function(hue, sat, val) {
- return [
- hue,
- (sat * val / ((hue = (2 - sat) * val) < 1 ? hue : 2 - hue)) || 0,
- hue / 2
- ];
- };
- // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
- // <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
- const isOnePointZero = function(n) {
- return typeof n === 'string' && n.indexOf('.') !== -1 && parseFloat(n) === 1;
- };
- const isPercentage = function(n) {
- return typeof n === 'string' && n.indexOf('%') !== -1;
- };
- // Take input from [0, n] and return it as [0, 1]
- const bound01 = function(value, max) {
- if (isOnePointZero(value)) value = '100%';
- const processPercent = isPercentage(value);
- value = Math.min(max, Math.max(0, parseFloat(value)));
- // Automatically convert percentage into number
- if (processPercent) {
- value = parseInt(value * max, 10) / 100;
- }
- // Handle floating point rounding errors
- if ((Math.abs(value - max) < 0.000001)) {
- return 1;
- }
- // Convert into [0, 1] range if it isn't already
- return (value % max) / parseFloat(max);
- };
- const INT_HEX_MAP = { 10: 'A', 11: 'B', 12: 'C', 13: 'D', 14: 'E', 15: 'F' };
- const toHex = function({ r, g, b }) {
- const hexOne = function(value) {
- value = Math.min(Math.round(value), 255);
- const high = Math.floor(value / 16);
- const low = value % 16;
- return '' + (INT_HEX_MAP[high] || high) + (INT_HEX_MAP[low] || low);
- };
- if (isNaN(r) || isNaN(g) || isNaN(b)) return '';
- return '#' + hexOne(r) + hexOne(g) + hexOne(b);
- };
- const HEX_INT_MAP = { A: 10, B: 11, C: 12, D: 13, E: 14, F: 15 };
- const parseHexChannel = function(hex) {
- if (hex.length === 2) {
- return (HEX_INT_MAP[hex[0].toUpperCase()] || +hex[0]) * 16 + (HEX_INT_MAP[hex[1].toUpperCase()] || +hex[1]);
- }
- return HEX_INT_MAP[hex[1].toUpperCase()] || +hex[1];
- };
- const hsl2hsv = function(hue, sat, light) {
- sat = sat / 100;
- light = light / 100;
- let smin = sat;
- const lmin = Math.max(light, 0.01);
- let sv;
- let v;
- light *= 2;
- sat *= (light <= 1) ? light : 2 - light;
- smin *= lmin <= 1 ? lmin : 2 - lmin;
- v = (light + sat) / 2;
- sv = light === 0 ? (2 * smin) / (lmin + smin) : (2 * sat) / (light + sat);
- return {
- h: hue,
- s: sv * 100,
- v: v * 100
- };
- };
- // `rgbToHsv`
- // Converts an RGB color value to HSV
- // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
- // *Returns:* { h, s, v } in [0,1]
- const rgb2hsv = function(r, g, b) {
- r = bound01(r, 255);
- g = bound01(g, 255);
- b = bound01(b, 255);
- const max = Math.max(r, g, b);
- const min = Math.min(r, g, b);
- let h, s;
- let v = max;
- const d = max - min;
- s = max === 0 ? 0 : d / max;
- if (max === min) {
- h = 0; // achromatic
- } else {
- switch (max) {
- case r:
- h = (g - b) / d + (g < b ? 6 : 0);
- break;
- case g:
- h = (b - r) / d + 2;
- break;
- case b:
- h = (r - g) / d + 4;
- break;
- }
- h /= 6;
- }
- return { h: h * 360, s: s * 100, v: v * 100 };
- };
- // `hsvToRgb`
- // Converts an HSV color value to RGB.
- // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
- // *Returns:* { r, g, b } in the set [0, 255]
- const hsv2rgb = function(h, s, v) {
- h = bound01(h, 360) * 6;
- s = bound01(s, 100);
- v = bound01(v, 100);
- const i = Math.floor(h);
- const f = h - i;
- const p = v * (1 - s);
- const q = v * (1 - f * s);
- const t = v * (1 - (1 - f) * s);
- const mod = i % 6;
- const r = [v, q, p, p, t, v][mod];
- const g = [t, v, v, q, p, p][mod];
- const b = [p, p, t, v, v, q][mod];
- return {
- r: Math.round(r * 255),
- g: Math.round(g * 255),
- b: Math.round(b * 255)
- };
- };
- export default class Color {
- constructor(options) {
- this._hue = 0;
- this._saturation = 100;
- this._value = 100;
- this._alpha = 100;
- this.enableAlpha = false;
- this.format = 'hex';
- this.value = '';
- options = options || {};
- for (let option in options) {
- if (options.hasOwnProperty(option)) {
- this[option] = options[option];
- }
- }
- this.doOnChange();
- }
- set(prop, value) {
- if (arguments.length === 1 && typeof prop === 'object') {
- for (let p in prop) {
- if (prop.hasOwnProperty(p)) {
- this.set(p, prop[p]);
- }
- }
- return;
- }
- this['_' + prop] = value;
- this.doOnChange();
- }
- get(prop) {
- return this['_' + prop];
- }
- toRgb() {
- return hsv2rgb(this._hue, this._saturation, this._value);
- }
- fromString(value) {
- if (!value) {
- this._hue = 0;
- this._saturation = 100;
- this._value = 100;
- this.doOnChange();
- return;
- }
- const fromHSV = (h, s, v) => {
- this._hue = Math.max(0, Math.min(360, h));
- this._saturation = Math.max(0, Math.min(100, s));
- this._value = Math.max(0, Math.min(100, v));
- this.doOnChange();
- };
- if (value.indexOf('hsl') !== -1) {
- const parts = value.replace(/hsla|hsl|\(|\)/gm, '')
- .split(/\s|,/g).filter((val) => val !== '').map((val, index) => index > 2 ? parseFloat(val) : parseInt(val, 10));
- if (parts.length === 4) {
- this._alpha = Math.floor(parseFloat(parts[3]) * 100);
- } else if (parts.length === 3) {
- this._alpha = 100;
- }
- if (parts.length >= 3) {
- const { h, s, v } = hsl2hsv(parts[0], parts[1], parts[2]);
- fromHSV(h, s, v);
- }
- } else if (value.indexOf('hsv') !== -1) {
- const parts = value.replace(/hsva|hsv|\(|\)/gm, '')
- .split(/\s|,/g).filter((val) => val !== '').map((val, index) => index > 2 ? parseFloat(val) : parseInt(val, 10));
- if (parts.length === 4) {
- this._alpha = Math.floor(parseFloat(parts[3]) * 100);
- } else if (parts.length === 3) {
- this._alpha = 100;
- }
- if (parts.length >= 3) {
- fromHSV(parts[0], parts[1], parts[2]);
- }
- } else if (value.indexOf('rgb') !== -1) {
- const parts = value.replace(/rgba|rgb|\(|\)/gm, '')
- .split(/\s|,/g).filter((val) => val !== '').map((val, index) => index > 2 ? parseFloat(val) : parseInt(val, 10));
- if (parts.length === 4) {
- this._alpha = Math.floor(parseFloat(parts[3]) * 100);
- } else if (parts.length === 3) {
- this._alpha = 100;
- }
- if (parts.length >= 3) {
- const { h, s, v } = rgb2hsv(parts[0], parts[1], parts[2]);
- fromHSV(h, s, v);
- }
- } else if (value.indexOf('#') !== -1) {
- const hex = value.replace('#', '').trim();
- if (!/^(?:[0-9a-fA-F]{3}){1,2}|[0-9a-fA-F]{8}$/.test(hex)) return;
- let r, g, b;
- if (hex.length === 3) {
- r = parseHexChannel(hex[0] + hex[0]);
- g = parseHexChannel(hex[1] + hex[1]);
- b = parseHexChannel(hex[2] + hex[2]);
- } else if (hex.length === 6 || hex.length === 8) {
- r = parseHexChannel(hex.substring(0, 2));
- g = parseHexChannel(hex.substring(2, 4));
- b = parseHexChannel(hex.substring(4, 6));
- }
- if (hex.length === 8) {
- this._alpha = Math.floor(parseHexChannel(hex.substring(6)) / 255 * 100);
- } else if (hex.length === 3 || hex.length === 6) {
- this._alpha = 100;
- }
- const { h, s, v } = rgb2hsv(r, g, b);
- fromHSV(h, s, v);
- }
- }
- compare(color) {
- return Math.abs(color._hue - this._hue) < 2 &&
- Math.abs(color._saturation - this._saturation) < 1 &&
- Math.abs(color._value - this._value) < 1 &&
- Math.abs(color._alpha - this._alpha) < 1;
- }
- doOnChange() {
- const { _hue, _saturation, _value, _alpha, format } = this;
- if (this.enableAlpha) {
- switch (format) {
- case 'hsl':
- const hsl = hsv2hsl(_hue, _saturation / 100, _value / 100);
- this.value = `hsla(${ _hue }, ${ Math.round(hsl[1] * 100) }%, ${ Math.round(hsl[2] * 100) }%, ${ _alpha / 100})`;
- break;
- case 'hsv':
- this.value = `hsva(${ _hue }, ${ Math.round(_saturation) }%, ${ Math.round(_value) }%, ${ _alpha / 100})`;
- break;
- default:
- const { r, g, b } = hsv2rgb(_hue, _saturation, _value);
- this.value = `rgba(${r}, ${g}, ${b}, ${ _alpha / 100 })`;
- }
- } else {
- switch (format) {
- case 'hsl':
- const hsl = hsv2hsl(_hue, _saturation / 100, _value / 100);
- this.value = `hsl(${ _hue }, ${ Math.round(hsl[1] * 100) }%, ${ Math.round(hsl[2] * 100) }%)`;
- break;
- case 'hsv':
- this.value = `hsv(${ _hue }, ${ Math.round(_saturation) }%, ${ Math.round(_value) }%)`;
- break;
- case 'rgb':
- const { r, g, b } = hsv2rgb(_hue, _saturation, _value);
- this.value = `rgb(${r}, ${g}, ${b})`;
- break;
- default:
- this.value = toHex(hsv2rgb(_hue, _saturation, _value));
- }
- }
- }
- };
|