231 lines
5.5 KiB
JavaScript
231 lines
5.5 KiB
JavaScript
import colorNames from 'color-name';
|
|
|
|
const reverseNames = Object.create(null);
|
|
|
|
// Create a list of reverse color names
|
|
for (const name in colorNames) {
|
|
if (Object.hasOwn(colorNames, name)) {
|
|
reverseNames[colorNames[name]] = name;
|
|
}
|
|
}
|
|
|
|
const cs = {
|
|
to: {},
|
|
get: {},
|
|
};
|
|
|
|
cs.get = function (string) {
|
|
const prefix = string.slice(0, 3).toLowerCase();
|
|
let value;
|
|
let model;
|
|
switch (prefix) {
|
|
case 'hsl': {
|
|
value = cs.get.hsl(string);
|
|
model = 'hsl';
|
|
break;
|
|
}
|
|
|
|
case 'hwb': {
|
|
value = cs.get.hwb(string);
|
|
model = 'hwb';
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
value = cs.get.rgb(string);
|
|
model = 'rgb';
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
|
|
return {model, value};
|
|
};
|
|
|
|
cs.get.rgb = function (string) {
|
|
if (!string) {
|
|
return null;
|
|
}
|
|
|
|
const abbr = /^#([a-f\d]{3,4})$/i;
|
|
const hex = /^#([a-f\d]{6})([a-f\d]{2})?$/i;
|
|
const rgba = /^rgba?\(\s*([+-]?\d+)(?=[\s,])\s*(?:,\s*)?([+-]?\d+)(?=[\s,])\s*(?:,\s*)?([+-]?\d+)\s*(?:[\s,|/]\s*([+-]?[\d.]+)(%?)\s*)?\)$/;
|
|
const per = /^rgba?\(\s*([+-]?[\d.]+)%\s*,?\s*([+-]?[\d.]+)%\s*,?\s*([+-]?[\d.]+)%\s*(?:[\s,|/]\s*([+-]?[\d.]+)(%?)\s*)?\)$/;
|
|
const keyword = /^(\w+)$/;
|
|
|
|
let rgb = [0, 0, 0, 1];
|
|
let match;
|
|
let i;
|
|
let hexAlpha;
|
|
|
|
if (match = string.match(hex)) {
|
|
hexAlpha = match[2];
|
|
match = match[1];
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
// https://jsperf.com/slice-vs-substr-vs-substring-methods-long-string/19
|
|
const i2 = i * 2;
|
|
rgb[i] = Number.parseInt(match.slice(i2, i2 + 2), 16);
|
|
}
|
|
|
|
if (hexAlpha) {
|
|
rgb[3] = Number.parseInt(hexAlpha, 16) / 255;
|
|
}
|
|
} else if (match = string.match(abbr)) {
|
|
match = match[1];
|
|
hexAlpha = match[3];
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
rgb[i] = Number.parseInt(match[i] + match[i], 16);
|
|
}
|
|
|
|
if (hexAlpha) {
|
|
rgb[3] = Number.parseInt(hexAlpha + hexAlpha, 16) / 255;
|
|
}
|
|
} else if (match = string.match(rgba)) {
|
|
for (i = 0; i < 3; i++) {
|
|
rgb[i] = Number.parseInt(match[i + 1], 10);
|
|
}
|
|
|
|
if (match[4]) {
|
|
rgb[3] = match[5] ? Number.parseFloat(match[4]) * 0.01 : Number.parseFloat(match[4]);
|
|
}
|
|
} else if (match = string.match(per)) {
|
|
for (i = 0; i < 3; i++) {
|
|
rgb[i] = Math.round(Number.parseFloat(match[i + 1]) * 2.55);
|
|
}
|
|
|
|
if (match[4]) {
|
|
rgb[3] = match[5] ? Number.parseFloat(match[4]) * 0.01 : Number.parseFloat(match[4]);
|
|
}
|
|
} else if (match = string.match(keyword)) {
|
|
if (match[1] === 'transparent') {
|
|
return [0, 0, 0, 0];
|
|
}
|
|
|
|
if (!Object.hasOwn(colorNames, match[1])) {
|
|
return null;
|
|
}
|
|
|
|
rgb = colorNames[match[1]];
|
|
rgb[3] = 1;
|
|
|
|
return rgb;
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
rgb[i] = clamp(rgb[i], 0, 255);
|
|
}
|
|
|
|
rgb[3] = clamp(rgb[3], 0, 1);
|
|
|
|
return rgb;
|
|
};
|
|
|
|
cs.get.hsl = function (string) {
|
|
if (!string) {
|
|
return null;
|
|
}
|
|
|
|
const hsl = /^hsla?\(\s*([+-]?(?:\d{0,3}\.)?\d+)(?:deg)?\s*,?\s*([+-]?[\d.]+)%\s*,?\s*([+-]?[\d.]+)%\s*(?:[,|/]\s*([+-]?(?=\.\d|\d)(?:0|[1-9]\d*)?(?:\.\d*)?(?:[eE][+-]?\d+)?)\s*)?\)$/;
|
|
const match = string.match(hsl);
|
|
|
|
if (match) {
|
|
const alpha = Number.parseFloat(match[4]);
|
|
const h = ((Number.parseFloat(match[1]) % 360) + 360) % 360;
|
|
const s = clamp(Number.parseFloat(match[2]), 0, 100);
|
|
const l = clamp(Number.parseFloat(match[3]), 0, 100);
|
|
const a = clamp(Number.isNaN(alpha) ? 1 : alpha, 0, 1);
|
|
|
|
return [h, s, l, a];
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
cs.get.hwb = function (string) {
|
|
if (!string) {
|
|
return null;
|
|
}
|
|
|
|
const hwb = /^hwb\(\s*([+-]?\d{0,3}(?:\.\d+)?)(?:deg)?\s*[\s,]\s*([+-]?[\d.]+)%\s*[\s,]\s*([+-]?[\d.]+)%\s*(?:[\s,]\s*([+-]?(?=\.\d|\d)(?:0|[1-9]\d*)?(?:\.\d*)?(?:[eE][+-]?\d+)?)\s*)?\)$/;
|
|
const match = string.match(hwb);
|
|
|
|
if (match) {
|
|
const alpha = Number.parseFloat(match[4]);
|
|
const h = ((Number.parseFloat(match[1]) % 360) + 360) % 360;
|
|
const w = clamp(Number.parseFloat(match[2]), 0, 100);
|
|
const b = clamp(Number.parseFloat(match[3]), 0, 100);
|
|
const a = clamp(Number.isNaN(alpha) ? 1 : alpha, 0, 1);
|
|
return [h, w, b, a];
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
cs.to.hex = function (...rgba) {
|
|
return (
|
|
'#' +
|
|
hexDouble(rgba[0]) +
|
|
hexDouble(rgba[1]) +
|
|
hexDouble(rgba[2]) +
|
|
(rgba[3] < 1
|
|
? (hexDouble(Math.round(rgba[3] * 255)))
|
|
: '')
|
|
);
|
|
};
|
|
|
|
cs.to.rgb = function (...rgba) {
|
|
return rgba.length < 4 || rgba[3] === 1
|
|
? 'rgb(' + Math.round(rgba[0]) + ', ' + Math.round(rgba[1]) + ', ' + Math.round(rgba[2]) + ')'
|
|
: 'rgba(' + Math.round(rgba[0]) + ', ' + Math.round(rgba[1]) + ', ' + Math.round(rgba[2]) + ', ' + rgba[3] + ')';
|
|
};
|
|
|
|
cs.to.rgb.percent = function (...rgba) {
|
|
const r = Math.round(rgba[0] / 255 * 100);
|
|
const g = Math.round(rgba[1] / 255 * 100);
|
|
const b = Math.round(rgba[2] / 255 * 100);
|
|
|
|
return rgba.length < 4 || rgba[3] === 1
|
|
? 'rgb(' + r + '%, ' + g + '%, ' + b + '%)'
|
|
: 'rgba(' + r + '%, ' + g + '%, ' + b + '%, ' + rgba[3] + ')';
|
|
};
|
|
|
|
cs.to.hsl = function (...hsla) {
|
|
return hsla.length < 4 || hsla[3] === 1
|
|
? 'hsl(' + hsla[0] + ', ' + hsla[1] + '%, ' + hsla[2] + '%)'
|
|
: 'hsla(' + hsla[0] + ', ' + hsla[1] + '%, ' + hsla[2] + '%, ' + hsla[3] + ')';
|
|
};
|
|
|
|
// Hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
|
|
// (hwb have alpha optional & 1 is default value)
|
|
cs.to.hwb = function (...hwba) {
|
|
let a = '';
|
|
if (hwba.length >= 4 && hwba[3] !== 1) {
|
|
a = ', ' + hwba[3];
|
|
}
|
|
|
|
return 'hwb(' + hwba[0] + ', ' + hwba[1] + '%, ' + hwba[2] + '%' + a + ')';
|
|
};
|
|
|
|
cs.to.keyword = function (...rgb) {
|
|
return reverseNames[rgb.slice(0, 3)];
|
|
};
|
|
|
|
// Helpers
|
|
function clamp(number_, min, max) {
|
|
return Math.min(Math.max(min, number_), max);
|
|
}
|
|
|
|
function hexDouble(number_) {
|
|
const string_ = Math.round(number_).toString(16).toUpperCase();
|
|
return (string_.length < 2) ? '0' + string_ : string_;
|
|
}
|
|
|
|
export default cs;
|