color-tf
Version:
RGB, HSL, HSV, HWB and more color models convertors
237 lines (187 loc) • 7.24 kB
JavaScript
(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;
})));