/** * Colors */ // Try to match a hex code to a terminal color as best as possible. // This entire function assumes the terminal user is using the // default xterm colors. exports.matchColor = function(hex) { if (hex[0] !== '#') { return hex; } if (hex.length === 4) { hex = hex[0] + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3]; } if (exports._cache[hex] != null) { return exports._cache[hex]; } var col = parseInt(hex.substring(1), 16) , r1 = (col >> 16) & 0xff , g1 = (col >> 8) & 0xff , b1 = col & 0xff , ldiff = Infinity , li = -1 , i = 0 , c , r2 , g2 , b2 , diff; for (; i < exports.vcolors.length; i++) { c = exports.vcolors[i]; r2 = c[0]; g2 = c[1]; b2 = c[2]; if (r1 === r2 && g1 === g2 && b1 === b2) { diff = 0; } else { // diff = colorDistance3d(r1, g1, b1, r2, g2, b2); diff = colorDistance3dWeight(r1, g1, b1, r2, g2, b2); // diff = colorDistance3dWeightSqrt(r1, g1, b1, r2, g2, b2); } if (diff === 0) { li = i; break; } if (diff < ldiff) { ldiff = diff; li = i; } } return exports._cache[hex] = li; }; // As it happens, comparing how similar two colors are is really hard. Here is // one of the simplest solutions, which doesn't require conversion to another // color space, posted on stackoverflow. Maybe someone better at math can // propose a superior solution. // http://stackoverflow.com/questions/9018016 function colorDistance3d(r1, g1, b1, r2, g2, b2) { var d; d = Math.sqrt( Math.pow(r2 - r1, 2) + Math.pow(g2 - g1, 2) + Math.pow(b2 - b1, 2)); d /= Math.sqrt( Math.pow(255, 2) + Math.pow(255, 2) + Math.pow(255, 2)); return d; } // http://stackoverflow.com/questions/1633828 function colorDistance3dWeight(r1, g1, b1, r2, g2, b2) { return Math.pow(30 * (r1 - r2), 2) + Math.pow(59 * (g1 - g2), 2) + Math.pow(11 * (b1 - b2), 2); } function colorDistance3dWeightSqrt(r1, g1, b1, r2, g2, b2) { var d; d = Math.sqrt( Math.pow(30 * (r2 - r1), 2) + Math.pow(59 * (g2 - g1), 2) + Math.pow(11 * (b2 - b1), 2)); d /= Math.sqrt( Math.pow(255, 2) + Math.pow(255, 2) + Math.pow(255, 2)); return d; } exports._cache = {}; // XTerm Colors // These were actually tough to track down. The xterm source only uses color // keywords. The X11 source needed to be examined to find the actual values. // They then had to be mapped to rgb values and then converted to hex values. exports.xterm = [ '#000000', // black '#cd0000', // red3 '#00cd00', // green3 '#cdcd00', // yellow3 '#0000ee', // blue2 '#cd00cd', // magenta3 '#00cdcd', // cyan3 '#e5e5e5', // gray90 '#7f7f7f', // gray50 '#ff0000', // red '#00ff00', // green '#ffff00', // yellow '#5c5cff', // rgb:5c/5c/ff '#ff00ff', // magenta '#00ffff', // cyan '#ffffff' // white ]; // Seed all 256 colors. Assume xterm defaults. // Ported from the xterm color generation script. exports.colors = (function() { var cols = exports.colors = [] , _cols = exports.vcolors = [] , r , g , b , i , l; function hex(n) { n = n.toString(16); if (n.length < 2) n = '0' + n; return n; } function push(i, r, g, b) { cols[i] = '#' + hex(r) + hex(g) + hex(b); _cols[i] = [r, g, b]; } // 0 - 15 exports.xterm.forEach(function(c, i) { c = parseInt(c.substring(1), 16); push(i, (c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff); }); // 16 - 231 for (r = 0; r < 6; r++) { for (g = 0; g < 6; g++) { for (b = 0; b < 6; b++) { i = 16 + (r * 36) + (g * 6) + b; push(i, r ? (r * 40 + 55) : 0, g ? (g * 40 + 55) : 0, b ? (b * 40 + 55) : 0); } } } // 232 - 255 are grey. for (g = 0; g < 24; g++) { l = (g * 10) + 8; i = 232 + g; push(i, l, l, l); } return cols; })(); // Map higher colors to the first 8 colors. // This allows translation of high colors to low colors on 8-color terminals. exports.ccolors = (function() { var _cols = exports.vcolors.slice() , cols = exports.colors.slice() , out; exports.vcolors = exports.vcolors.slice(0, 8); exports.colors = exports.colors.slice(0, 8); out = cols.map(exports.matchColor); exports.colors = cols; exports.vcolors = _cols; exports.ccolors = out; return out; })(); var colorNames = exports.colorNames = { default: -1, normal: -1, bg: -1, fg: -1, black: 0, red: 1, green: 2, yellow: 3, blue: 4, magenta: 5, cyan: 6, white: 7, lightblack: 8, lightred: 9, lightgreen: 10, lightyellow: 11, lightblue: 12, lightmagenta: 13, lightcyan: 14, lightwhite: 15 }; //Object.keys(colorNames).forEach(function(name) { // colorNames[colorNames[name]] = name; //}); exports.convert = function(color) { if (typeof color === 'string') { color = color.replace(/[\- ]/g, ''); } var val = colorNames[color]; if (val == null) val = color; if (val == null) val = -1; if (typeof val === 'string') { val = val[0] === '#' ? exports.matchColor(val) : -1; } if (val === -1) return 0x1ff; return val; }; // Map higher colors to the first 8 colors. // This allows translation of high colors to low colors on 8-color terminals. // Why the hell did I do this by hand? exports.ccolors = { blue: [ 4, 12, [17, 21], [24, 27], [31, 33], [38, 39], 45, [54, 57], [60, 63], [67, 69], [74, 75], 81, [91, 93], [97, 99], [103, 105], [110, 111], 117, [128, 129], [134, 135], [140, 141], [146, 147], 153, 165, 171, 177, 183, 189 ], green: [ 2, 10, 22, [28, 29], [34, 36], [40, 43], [46, 50], [64, 65], [70, 72], [76, 79], [82, 86], [106, 108], [112, 115], [118, 122], [148, 151], [154, 158], [190, 194] ], cyan: [ 6, 14, 23, 30, 37, 44, 51, 66, 73, 80, 87, 109, 116, 123, 152, 159, 195 ], red: [ 1, 9, 52, [88, 89], [94, 95], [124, 126], [130, 132], [136, 138], [160, 163], [166, 169], [172, 175], [178, 181], [196, 200], [202, 206], [208, 212], [214, 218], [220, 224] ], magenta: [ 5, 13, 53, 90, 96, 127, 133, 139, 164, 170, 176, 182, 201, 207, 213, 219, 225 ], yellow: [ 3, 11, 58, [100, 101], [142, 144], [184, 187], [226, 230] ], black: [ 0, 8, 16, 59, 102, [232, 243] ], white: [ 7, 15, 145, 188, 231, [244, 255] ] }; Object.keys(exports.ccolors).forEach(function(name) { exports.ccolors[name].forEach(function(offset) { if (typeof offset === 'number') { exports.ccolors[offset] = exports.colorNames[name]; return; } for (var i = offset[0], l = offset[1]; i <= l; i++) { exports.ccolors[i] = exports.colorNames[name]; } }); delete exports.ccolors[name]; });