const hsv2hsl = function (hue, sat, val) { return [hue, (sat * val) / ((hue = (2 - sat) * val) < 1 ? hue : 2 - hue) || 0, hue / 2]; }; 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; }; const bound01 = function (value, max) { if (isOnePointZero(value)) value = '100%'; const processPercent = isPercentage(value); value = Math.min(max, Math.max(0, parseFloat(`${value}`))); if (processPercent) { value = parseInt(`${value * max}`, 10) / 100; } if (Math.abs(value - max) < 0.000001) { return 1; } return (value % max) / parseFloat(max); }; const INT_HEX_MAP = {10: 'A', 11: 'B', 12: 'C', 13: 'D', 14: 'E', 15: 'F'}; 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}`; }; const toHex = function ({r, g, b}) { 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; const v = (light + sat) / 2; const sv = light === 0 ? (2 * smin) / (lmin + smin) : (2 * sat) / (light + sat); return { h: hue, s: sv * 100, v: v * 100, }; }; 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; const v = max; const d = max - min; const 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}; }; 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), }; }; function Color(options) { this._hue = 0; this._saturation = 0; this._value = 100; this._alpha = 100; this.enableAlpha = false; this.format = 'hex'; this.value = ''; options = options || {}; this.enableAlpha = options.enableAlpha || false; this.format = (options.format || 'hex').toLowerCase(); } Color.prototype.set = function (prop, value) { if (arguments.length === 1 && typeof prop === 'object') { for (const p in prop) { if (Object.hasOwn(prop, p)) { this.set(p, prop[p]); } } return; } this[`_${prop}`] = value; this.doOnChange(); }; Color.prototype.get = function (prop) { if (prop === 'alpha') { return Math.floor(this[`_${prop}`]); } return this[`_${prop}`]; }; Color.prototype.toRgb = function () { return hsv2rgb(this._hue, this._saturation, this._value); }; Color.prototype.fromString = function (value) { if (!value) { this._hue = 0; this._saturation = 0; this._value = 100; this._alpha = 100; this.value = ''; return this.value; } 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 = 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 = 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 = 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}$|^[0-9a-fA-F]{6}$|^[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 = (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) } /* if (!value) { this._hue = 0; this._saturation = 0; this._value = 100; this._alpha = 100; this.value = ''; return this.value; } const fromHSV = (h, s, v) => { this._hue = parseInt(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 = 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 = 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 = 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}$|^[0-9a-fA-F]{6}$|^[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 = (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); }*/ return this.value; }; Color.prototype.compare = function (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 ); }; Color.prototype.doOnChange = function () { 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)}%, ${this.get('alpha') / 100})`; break; } case 'hsv': { this.value = `hsva(${_hue}, ${Math.round(_saturation)}%, ${Math.round(_value)}%, ${this.get('alpha') / 100})`; break; } case 'hex': { this.value = `${toHex(hsv2rgb(_hue, _saturation, _value))}${hexOne((_alpha * 255) / 100 )}`; break; } default: { const {r, g, b} = hsv2rgb(_hue, _saturation, _value); this.value = `rgba(${r}, ${g}, ${b}, ${this.get('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)); } } } }; export default Color;