@sphinxxxx/color-conversion
Version:
Convert colors between RGBA/HSLA/Hex
320 lines (246 loc) • 15.5 kB
JavaScript
/*global String*/
/*global atob*/
String.prototype.startsWith = (String.prototype.startsWith || function(needle) { return (this.indexOf(needle) === 0); });
String.prototype.padStart = (String.prototype.padStart || function(len, pad) { let str = this; while(str.length < len) { str = pad + str; }; return str; });
/*
Code golfing:
This is a compacted list of all 148 CSS color names (from https://github.com/bahamas10/css-color-names/blob/master/css-color-names.json).
Every seven characters in this array is a name and its RGB value.
//3 chars name hash (rudimentary, but just enough to separate all unique colors),
//based on https://stackoverflow.com/a/15710692/1869660
hash = [].reduce.call(name.replace('ey','ay'), (h, c) => (h << 2) + c.charCodeAt(0), 0)
.toString(36).slice(-3);
//4 chars base64 color value. Split the hex into [R, G, B], cast to bytes and base64-encode the byte array:
hex64 = btoa( hex.match(/../g).map(x => String.fromCharCode(parseInt(x, 16))).join('') );
const colorNames = '735AACA770//Xub218Pj/mo5+uvX6mdAP//gtpf//Ur258P//q1d9fXcxop/+TEq9zAAAAqfg/+vN6m1AAD/ngoiiviqt6pSoqzyo3riHxvdX56grk1f/8Aax10mkeqts/39QxbtZJXttkb//jcyxm3BQ86rmAP//wl5AACLwqqAIuL3y8uIYLwv1qampniqAGQAns5vbdrmohiwCLw5uVWsvsdd/4wAsegmTLMqagiwAAsqi6ZZ6uz6j7yPxtzSD2Lxk3L09PudbAM7RwsolADT0kz/xSTfuhAL//vfhaWlpyuxHpD/43rsiIiwn9//rw39uIosi9bp/wD/6w73Nzc9s5+Pj/6v8/9cA3b42qUg6vxgICArmaAIAAtdfrf8vf9n8P/wek3/2m0xnczVxc3bvSwCCsdt///wrvp8OaMs5i5ub6iyk//D1e8ifPwAoui//rNpyxrdjmw9c8ICAq4i4P//mx9+vrSq8t09PTx1ukO6Qqlv/7bBuuy/6B690uILKqpfdh876sd9d4iZnehsMTe0dv///g71lAP8A4nmMs0ys9u+vDmg9d/wD/4pmgAAAcurZs2qzllAADN4lkulXT6txk3Db66qPLNxozre2juokuAPqalj3SNHMgdkxxWF60pGRlwxfl9f/6hr5/+Thx6q/+S1m85/96tutd/fXmszxgIAAe4ma44j8rl/6UAmu0/0UA8so2nDWji87uiqumqmPuY9xbr+7u4rs23CTsb8/+/V95a/9q577xzYU/78z/8DL7b53aDdsu1sODmb11gACAy5nZjOZ1so/wAAlvevI+Pn09QWnhm7ui0UT94q+oBy7ei9KRg5aqLotXad5oFItasmwMDAaihh87r9fdalrN9p9cICQ7gz//r6k5uAP9/4qhRoK01te0rSM7cwAICA91x2L/Yclr/2NHcw1QODQd6w7oLuua09d6zudh////t359fX1enn//8Ao0ims0y';
let colorNamesDeser;
*/
//https://github.com/bahamas10/css-color-names/blob/master/css-color-names.json
// const colorNames = { aliceblue:'#f0f8ff', antiquewhite:'#faebd7', aqua:'#00ffff', aquamarine:'#7fffd4', azure:'#f0ffff', beige:'#f5f5dc', bisque:'#ffe4c4', black:'#000000', blanchedalmond:'#ffebcd', blue:'#0000ff', blueviolet:'#8a2be2', brown:'#a52a2a', burlywood:'#deb887', cadetblue:'#5f9ea0', chartreuse:'#7fff00', chocolate:'#d2691e', coral:'#ff7f50', cornflowerblue:'#6495ed', cornsilk:'#fff8dc', crimson:'#dc143c', cyan:'#00ffff', darkblue:'#00008b', darkcyan:'#008b8b', darkgoldenrod:'#b8860b', darkgray:'#a9a9a9', darkgreen:'#006400', darkgrey:'#a9a9a9', darkkhaki:'#bdb76b', darkmagenta:'#8b008b', darkolivegreen:'#556b2f', darkorange:'#ff8c00', darkorchid:'#9932cc', darkred:'#8b0000', darksalmon:'#e9967a', darkseagreen:'#8fbc8f', darkslateblue:'#483d8b', darkslategray:'#2f4f4f', darkslategrey:'#2f4f4f', darkturquoise:'#00ced1', darkviolet:'#9400d3', deeppink:'#ff1493', deepskyblue:'#00bfff', dimgray:'#696969', dimgrey:'#696969', dodgerblue:'#1e90ff', firebrick:'#b22222', floralwhite:'#fffaf0', forestgreen:'#228b22', fuchsia:'#ff00ff', gainsboro:'#dcdcdc', ghostwhite:'#f8f8ff', gold:'#ffd700', goldenrod:'#daa520', gray:'#808080', green:'#008000', greenyellow:'#adff2f', grey:'#808080', honeydew:'#f0fff0', hotpink:'#ff69b4', indianred:'#cd5c5c', indigo:'#4b0082', ivory:'#fffff0', khaki:'#f0e68c', lavender:'#e6e6fa', lavenderblush:'#fff0f5', lawngreen:'#7cfc00', lemonchiffon:'#fffacd', lightblue:'#add8e6', lightcoral:'#f08080', lightcyan:'#e0ffff', lightgoldenrodyellow:'#fafad2', lightgray:'#d3d3d3', lightgreen:'#90ee90', lightgrey:'#d3d3d3', lightpink:'#ffb6c1', lightsalmon:'#ffa07a', lightseagreen:'#20b2aa', lightskyblue:'#87cefa', lightslategray:'#778899', lightslategrey:'#778899', lightsteelblue:'#b0c4de', lightyellow:'#ffffe0', lime:'#00ff00', limegreen:'#32cd32', linen:'#faf0e6', magenta:'#ff00ff', maroon:'#800000', mediumaquamarine:'#66cdaa', mediumblue:'#0000cd', mediumorchid:'#ba55d3', mediumpurple:'#9370db', mediumseagreen:'#3cb371', mediumslateblue:'#7b68ee', mediumspringgreen:'#00fa9a', mediumturquoise:'#48d1cc', mediumvioletred:'#c71585', midnightblue:'#191970', mintcream:'#f5fffa', mistyrose:'#ffe4e1', moccasin:'#ffe4b5', navajowhite:'#ffdead', navy:'#000080', oldlace:'#fdf5e6', olive:'#808000', olivedrab:'#6b8e23', orange:'#ffa500', orangered:'#ff4500', orchid:'#da70d6', palegoldenrod:'#eee8aa', palegreen:'#98fb98', paleturquoise:'#afeeee', palevioletred:'#db7093', papayawhip:'#ffefd5', peachpuff:'#ffdab9', peru:'#cd853f', pink:'#ffc0cb', plum:'#dda0dd', powderblue:'#b0e0e6', purple:'#800080', rebeccapurple:'#663399', red:'#ff0000', rosybrown:'#bc8f8f', royalblue:'#4169e1', saddlebrown:'#8b4513', salmon:'#fa8072', sandybrown:'#f4a460', seagreen:'#2e8b57', seashell:'#fff5ee', sienna:'#a0522d', silver:'#c0c0c0', skyblue:'#87ceeb', slateblue:'#6a5acd', slategray:'#708090', slategrey:'#708090', snow:'#fffafa', springgreen:'#00ff7f', steelblue:'#4682b4', tan:'#d2b48c', teal:'#008080', thistle:'#d8bfd8', tomato:'#ff6347', turquoise:'#40e0d0', violet:'#ee82ee', wheat:'#f5deb3', white:'#ffffff', whitesmoke:'#f5f5f5', yellow:'#ffff00', yellowgreen:'#9acd32' };
//
//More golfing, just because.. Simple hashing of the names - see nameToRgb()
const colorNames = {cb:'0f8ff',tqw:'aebd7',q:'-ffff',qmrn:'7fffd4',zr:'0ffff',bg:'5f5dc',bsq:'e4c4',bck:'---',nch:'ebcd',b:'--ff',bvt:'8a2be2',brwn:'a52a2a',brw:'deb887',ctb:'5f9ea0',hrt:'7fff-',chcT:'d2691e',cr:'7f50',rnw:'6495ed',crns:'8dc',crms:'dc143c',cn:'-ffff',Db:'--8b',Dcn:'-8b8b',Dgnr:'b8860b',Dgr:'a9a9a9',Dgrn:'-64-',Dkhk:'bdb76b',Dmgn:'8b-8b',Dvgr:'556b2f',Drng:'8c-',Drch:'9932cc',Dr:'8b--',Dsmn:'e9967a',Dsgr:'8fbc8f',DsTb:'483d8b',DsTg:'2f4f4f',Dtrq:'-ced1',Dvt:'94-d3',ppnk:'1493',pskb:'-bfff',mgr:'696969',grb:'1e90ff',rbrc:'b22222',rwht:'af0',stg:'228b22',chs:'-ff',gnsb:'dcdcdc',st:'8f8ff',g:'d7-',gnr:'daa520',gr:'808080',grn:'-8-0',grnw:'adff2f',hnw:'0fff0',htpn:'69b4',nnr:'cd5c5c',ng:'4b-82',vr:'0',khk:'0e68c',vnr:'e6e6fa',nrb:'0f5',wngr:'7cfc-',mnch:'acd',Lb:'add8e6',Lcr:'08080',Lcn:'e0ffff',Lgnr:'afad2',Lgr:'d3d3d3',Lgrn:'90ee90',Lpnk:'b6c1',Lsmn:'a07a',Lsgr:'20b2aa',Lskb:'87cefa',LsTg:'778899',Lstb:'b0c4de',Lw:'e0',m:'-ff-',mgrn:'32cd32',nn:'af0e6',mgnt:'-ff',mrn:'8--0',mqm:'66cdaa',mmb:'--cd',mmrc:'ba55d3',mmpr:'9370db',msg:'3cb371',mmsT:'7b68ee','':'-fa9a',mtr:'48d1cc',mmvt:'c71585',mnLb:'191970',ntc:'5fffa',mstr:'e4e1',mccs:'e4b5',vjw:'dead',nv:'--80',c:'df5e6',v:'808-0',vrb:'6b8e23',rng:'a5-',rngr:'45-',rch:'da70d6',pgnr:'eee8aa',pgrn:'98fb98',ptrq:'afeeee',pvtr:'db7093',ppwh:'efd5',pchp:'dab9',pr:'cd853f',pnk:'c0cb',pm:'dda0dd',pwrb:'b0e0e6',prp:'8-080',cc:'663399',r:'--',sbr:'bc8f8f',rb:'4169e1',sbrw:'8b4513',smn:'a8072',nbr:'4a460',sgrn:'2e8b57',ssh:'5ee',snn:'a0522d',svr:'c0c0c0',skb:'87ceeb',sTb:'6a5acd',sTgr:'708090',snw:'afa',n:'-ff7f',stb:'4682b4',tn:'d2b48c',t:'-8080',thst:'d8bfd8',tmT:'6347',trqs:'40e0d0',vt:'ee82ee',whT:'5deb3',wht:'',hts:'5f5f5',w:'-',wgrn:'9acd32'};
function printNum(num, decs = 1) {
const str = (decs > 0) ? num.toFixed(decs).replace(/0+$/, '').replace(/\.$/, '')
: num.toString();
return str || '0';
}
class Color {
constructor(r, g, b, a) {
const that = this;
function parseString(input) {
//HSL string. Examples:
// hsl(120, 60%, 50%) or
// hsla(240, 100%, 50%, .7)
if( input.startsWith('hsl') ) {
let [h, s, l, a] = input.match(/([\-\d\.e]+)/g).map(Number);
if(a === undefined) { a = 1; }
h /= 360;
s /= 100;
l /= 100;
that.hsla = [h, s, l, a];
}
//RGB string. Examples:
// rgb(51, 170, 51)
// rgba(51, 170, 51, .7)
else if( input.startsWith('rgb') ) {
let [r, g, b, a] = input.match(/([\-\d\.e]+)/g).map(Number);
if(a === undefined) { a = 1; }
that.rgba = [r, g, b, a];
}
//Hex string or color name:
else {
if( input.startsWith('#') ) {
that.rgba = Color.hexToRgb(input);
}
else {
that.rgba = Color.nameToRgb(input) || Color.hexToRgb(input);
}
}
}
if( r === undefined ) {
//No color input - the color can be set later through .hsla/.rgba/.hex
}
//Single input - RGB(A) array
else if( Array.isArray(r) ) {
this.rgba = r;
}
//Single input - CSS string
else if( b === undefined ) {
const color = r && ('' + r);
if(color) {
parseString(color.toLowerCase());
}
}
else {
this.rgba = [r, g, b, (a === undefined) ? 1 : a];
}
}
/* RGBA representation */
get rgba() {
if(this._rgba) { return this._rgba; }
if(!this._hsla) { throw new Error('No color is set'); }
return (this._rgba = Color.hslToRgb(this._hsla));
}
set rgba(rgb) {
if(rgb.length === 3) { rgb[3] = 1; }
this._rgba = rgb;
this._hsla = null;
}
printRGB(alpha) {
const rgb = alpha ? this.rgba : this.rgba.slice(0, 3),
vals = rgb.map((x, i) => printNum(x, (i === 3) ? 3 : 0));
return alpha ? `rgba(${ vals })` : `rgb(${ vals })`;
}
get rgbString() { return this.printRGB(); }
get rgbaString() { return this.printRGB(true); }
/* HSLA representation */
get hsla() {
if(this._hsla) { return this._hsla; }
if(!this._rgba) { throw new Error('No color is set'); }
return (this._hsla = Color.rgbToHsl(this._rgba));
}
set hsla(hsl) {
if(hsl.length === 3) { hsl[3] = 1; }
this._hsla = hsl;
this._rgba = null;
}
printHSL(alpha) {
const mults = [360, 100, 100, 1],
suff = ['', '%', '%', ''];
const hsl = alpha ? this.hsla : this.hsla.slice(0, 3),
//in printNum(), use enough decimals to represent all RGB colors:
//https://gist.github.com/mjackson/5311256#gistcomment-2336011
vals = hsl.map((x, i) => printNum(x * mults[i], (i === 3) ? 3 : 1) + suff[i]);
return alpha ? `hsla(${ vals })` : `hsl(${ vals })`;
}
get hslString() { return this.printHSL(); }
get hslaString() { return this.printHSL(true); }
/* HEX representation */
get hex() {
const rgb = this.rgba,
hex = rgb.map((x, i) => (i < 3) ? x.toString(16)
: Math.round(x * 255).toString(16));
return '#' + hex.map(x => x.padStart(2, '0')).join('');
}
set hex(hex) {
this.rgba = Color.hexToRgb(hex);
}
printHex(alpha) {
const hex = this.hex;
return alpha ? hex : hex.substring(0, 7);
}
/* Conversion utils */
/**
* Splits a HEX string into its RGB(A) components
*/
static hexToRgb(input) {
//Normalize all hex codes (3/4/6/8 digits) to 8 digits RGBA
const hex = (input.startsWith('#') ? input.slice(1) : input)
.replace(/^(\w{3})$/, '$1F') //987 -> 987F
.replace(/^(\w)(\w)(\w)(\w)$/, '$1$1$2$2$3$3$4$4') //9876 -> 99887766
.replace(/^(\w{6})$/, '$1FF'); //987654 -> 987654FF
if(!hex.match(/^([0-9a-fA-F]{8})$/)) { throw new Error('Unknown hex color; ' + input); }
const rgba = hex
.match(/^(\w\w)(\w\w)(\w\w)(\w\w)$/).slice(1) //98765432 -> 98 76 54 32
.map(x => parseInt(x, 16)); //Hex to decimal
rgba[3] = rgba[3]/255;
return rgba;
}
/**
* Gets the RGB value from a CSS color name
*/
static nameToRgb(input) {
/* See comments on colorNames
if(!colorNamesDeser) {
colorNamesDeser = {};
colorNames.match(/.{7}/g).forEach(x =>
colorNamesDeser[x.slice(0, 3)] = atob(x.slice(-4)).split('').map(b => b.charCodeAt(0))
);
}
const hash = [].reduce.call(input.replace('ey', 'ay'), (h, c) => (h << 2) + c.charCodeAt(0), 0)
.toString(36).slice(-3);
return colorNamesDeser[hash];
*/
//const hex = colorNames[input];
//if(hex) {
// return Color.hexToRgb(hex);
//}
const hash = input.toLowerCase()
.replace('at', 'T')
.replace(/[aeiouyldf]/g, '')
.replace('ght', 'L')
.replace('rk', 'D')
.slice(-5, 4),
hex = colorNames[hash];
return (hex === undefined) ? hex
: Color.hexToRgb(hex.replace(/\-/g, '00').padStart(6, 'f'));
}
/**
* https://gist.github.com/mjackson/5311256
*
* Converts an RGB color value to HSL. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes r, g, and b are contained in the set [0, 255] and
* returns h, s, and l in the set [0, 1].
*/
static rgbToHsl([r, g, b, a]) {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b),
min = Math.min(r, g, b);
let h,
s,
l = (max + min) / 2;
if(max === min){
h = s = 0; // achromatic
}
else {
const d = max - min;
s = (l > 0.5) ? d / (2 - max - min)
: d / (max + min);
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, s, l, a];
}
/**
* https://gist.github.com/mjackson/5311256
*
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 255].
*/
static hslToRgb([h, s, l, a]) {
let r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
}
else {
const hue2rgb = function(p, q, t) {
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;
};
const q = (l < 0.5) ? l * (1 + s)
: l + s - (l * s),
p = (2 * l) - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
const rgba = [r * 255, g * 255, b * 255].map(Math.round);
rgba[3] = a;
return rgba;
}
}
export default Color;