UNPKG

color-tf

Version:

RGB, HSL, HSV, HWB and more color models convertors

237 lines (187 loc) 7.24 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.colorTf = factory()); }(this, (function () { 'use strict'; var hsl2hsv = ((h, s, l) => { const t = s * (l < 0.5 ? l : 1 - l), V = l + t, S = l > 0 ? 2 * t / V : 0; return [h, S, V]; }); var hsl2rgb = ((h, s, l) => { if (s === 0) return [l, l, l]; // achromatic const q = l < 0.5 ? l * (1 + s) : l + s - l * s; const p = 2 * l - q; return [hue2rgb(p, q, h + 1 / 3), hue2rgb(p, q, h), hue2rgb(p, q, h - 1 / 3)]; }); function hue2rgb(p, q, t) { // private fn if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1 / 6) return p + (q - p) * 6 * t; if (t < 1 / 2) return q; if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; } var hsv2hsl = ((h, s, v) => { const L = (2 - s) * v / 2, S = s * v / (L < 0.5 ? L * 2 : 2 - L * 2); return [h, S || 0, L]; }); var hsv2hwb = ((h, s, v) => [h, (1 - s) * v, 1 - v]); var hsv2rgb = ((h, s, v) => { const i = Math.floor(h * 6); const f = h * 6 - i; const p = v * (1 - s); const q = v * (1 - f * s); const t = v * (1 - (1 - f) * s); switch (i) { case 6: case 0: return [v, t, p]; case 1: return [q, v, p]; case 2: return [p, v, t]; case 3: return [p, q, v]; case 4: return [t, p, v]; case 5: return [v, p, q]; } }); var hwb2hsv = ((h, w, b) => [h, b === 1 ? 0 : Math.max(0, 1 - w / (1 - b)), 1 - b]); var hwb2rgb = ((h, w, b) => { // could throw or warn, or normalize if w+b>=1 ? const v = 1 - b; const i = Math.floor(h * 6); const f = i & 1 ? 1 + i - h * 6 : h * 6 - i; // if i is odd const n = w + f * (v - w); // linear interpolation switch (i) { case 6: case 0: return [v, n, w]; case 1: return [n, v, w]; case 2: return [w, v, n]; case 3: return [w, n, v]; case 4: return [n, w, v]; case 5: return [v, w, n]; } }); var rgb2hsl = ((r, g, b) => { const max = Math.max(r, g, b), min = Math.min(r, g, b); const l = (max + min) / 2, d = max - min; if (d <= 0) return [0, 0, l]; // achromatic const s = l > 0.5 ? d / (2 - max - min) : d / (max + min); const h = max === r ? (g - b) / d + (g < b ? 6 : 0) : max === g ? (b - r) / d + 2 : (r - g) / d + 4; return [h / 6, s, l]; }); var rgb2hsv = ((r, g, b) => { const max = Math.max(r, g, b), min = Math.min(r, g, b); const v = max, d = max - min, s = max === 0 ? 0 : d / max; if (d <= 0) return [0, s, v]; // achromatic const h = max === r ? (g - b) / d + (g < b ? 6 : 0) : max === g ? (b - r) / d + 2 : (r - g) / d + 4; return [h / 6, s, v]; }); var rgb2hwb = ((R, G, B) => { const max = Math.max(R, G, B), min = Math.min(R, G, B); const b = 1 - max, d = max - min; if (d <= 0) return [0, min, b]; // achromatic const hue = min === R ? 3 - (G - B) / d : min === G ? 5 - (B - R) / d : 1 - (R - G) / d; // const [f, i] = min === R ? [G - B, 3 / 6] : min === G ? [B - R, 5 / 6] : [R - G, 1 / 6]; return [hue / 6, min, b]; }); var lib = /*#__PURE__*/Object.freeze({ hsl2hsv: hsl2hsv, hsl2rgb: hsl2rgb, hsv2hsl: hsv2hsl, hsv2hwb: hsv2hwb, hsv2rgb: hsv2rgb, hwb2hsv: hwb2hsv, hwb2rgb: hwb2rgb, rgb2hsl: rgb2hsl, rgb2hsv: rgb2hsv, rgb2hwb: rgb2hwb }); var rgbToHex = ((R, G, B, A, forceLongVersion) => { if (A < 1) { const alpha = Math.floor(A * 256); return !forceLongVersion && alpha % 17 === 0 && R % 17 === 0 && G % 17 === 0 && B % 17 === 0 // short version ? R.toString(16)[0] + G.toString(16)[0] + B.toString(16)[0] + alpha.toString(16)[0] : R.toString(16).padStart(2, 0) + G.toString(16).padStart(2, 0) + B.toString(16).padStart(2, 0) + alpha.toString(16).padStart(2, 0); } return !forceLongVersion && R % 17 === 0 && G % 17 === 0 && B % 17 === 0 // short version ? R.toString(16)[0] + G.toString(16)[0] + B.toString(16)[0] : R.toString(16).padStart(2, 0) + G.toString(16).padStart(2, 0) + B.toString(16).padStart(2, 0); }); var hexToRgb = (_hex => { const hex = _hex[0] === '#' ? _hex.slice(1) : _hex; const s = hex.length < 6 ? (hex[hex.length - 4] || '').repeat(2) + hex[hex.length - 3].repeat(2) + hex[hex.length - 2].repeat(2) + hex[hex.length - 1].repeat(2) : hex; return [parseInt(s.slice(0, 2), 16), parseInt(s.slice(2, 4), 16), parseInt(s.slice(4, 6), 16), ...(s.length === 6 ? [] : [Math.round(parseInt(s.slice(6, 8), 16) / 0.255) / 1000])]; }); /** * @return fns to compose between 2 keys in lib object e.g. fns.reduceRight((fn, f) => (...a) => f(...fn(...a))) */ var getFnPath = ((lib, fromKey, toKey) => { let nodes = [fromKey]; const visited = new Map(); // map node key => parent key while (nodes.length) { // search breadth-first const newNodes = []; for (const k of nodes) { if (lib[k + '2' + toKey]) { // done, we can stop const fns = [`${k}2${toKey}`]; for (let key = k; visited.has(key) && key !== fromKey; key = visited.get(key)) { fns.push(`${visited.get(key)}2${key}`); } return fns; } Object.keys(lib).filter(s => s.slice(0, 3) === k).map(s => s.slice(4)).filter(key => !visited.has(key)).forEach(key => { visited.set(key, k); newNodes.push(key); }); } nodes = newNodes; } }); const roundH = ([h, s, l]) => [Math.round(360 * h) % 360, Math.round(100 * s), Math.round(100 * l)]; /** * all functions available from a Proxy (to generate missing ones dynamically) * foo2bar for functions with input/output in [0, 1] * fooToBar for functions with natural inputs [0,255] for r,g,b, [0,360[ for hue, [0, 100] for the rest */ var proxy = new Proxy(new Map([...Object.entries(lib), ['rgbToHex', rgbToHex], ['hexToRgb', hexToRgb]]), { get: (map, key) => { if (typeof key !== 'string') return map; if (map.has(key)) return map.get(key); const fromKey = key.slice(0, 3); const toKey = key.slice(-3).toLowerCase(); const k = fromKey + '2' + toKey; let fn = lib[k]; if (!fn) { // todo check fromKey, toKey are in available keys, else getPath might be in infinite loop const fns = getFnPath(lib, fromKey, toKey).map(n => lib[n]); fn = fns.reduceRight((f, g) => (...a) => g(...f(...a))); map.set(k, fn); } if (key[3] === '2') return fn; const K = fromKey + 'To' + toKey[0].toUpperCase() + toKey.slice(1); const FN = fromKey === 'rgb' ? (r, g, b) => roundH(fn(r / 255, g / 255, b / 255)) : toKey === 'rgb' ? (h, x, y) => fn(h / 360, x / 100, y / 100).map(v => Math.round(v * 255)) : (h, x, y) => roundH(fn(h / 360, x / 100, y / 100)); map.set(K, FN); return FN; } }); return proxy; })));