/** * colors.js - color-related functions for blessed. * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). * https://github.com/chjj/blessed */ exports.match = function(r1, g1, b1) { if (typeof r1 === 'string') { var hex = r1; if (hex[0] !== '#') { return -1; } hex = exports.hexToRGB(hex); r1 = hex[0], g1 = hex[1], b1 = hex[2]; } else if (Array.isArray(r1)) { b1 = r1[2], g1 = r1[1], r1 = r1[0]; } var hash = (r1 << 16) | (g1 << 8) | b1; if (exports._cache[hash] != null) { return exports._cache[hash]; } var 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]; diff = colorDistance(r1, g1, b1, r2, g2, b2); if (diff === 0) { li = i; break; } if (diff < ldiff) { ldiff = diff; li = i; } } return exports._cache[hash] = li; }; exports.RGBToHex = function(r, g, b) { if (Array.isArray(r)) { b = r[2], g = r[1], r = r[0]; } function hex(n) { n = n.toString(16); if (n.length < 2) n = '0' + n; return n; } return '#' + hex(r) + hex(g) + hex(b); }; exports.hexToRGB = function(hex) { if (hex.length === 4) { hex = hex[0] + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3]; } var col = parseInt(hex.substring(1), 16) , r = (col >> 16) & 0xff , g = (col >> 8) & 0xff , b = col & 0xff; return [r, g, b]; }; // 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[1]. Maybe someone better at math can // propose a superior solution. // [1] http://stackoverflow.com/questions/1633828 function colorDistance(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); } // This might work well enough for a terminal's colors: treat RGB as XYZ in a // 3-dimensional space and go midway between the two points. exports.mixColors = function(c1, c2, alpha) { // if (c1 === 0x1ff) return c1; // if (c2 === 0x1ff) return c1; if (c1 === 0x1ff) c1 = 0; if (c2 === 0x1ff) c2 = 0; if (alpha == null) alpha = 0.5; c1 = exports.vcolors[c1]; var r1 = c1[0]; var g1 = c1[1]; var b1 = c1[2]; c2 = exports.vcolors[c2]; var r2 = c2[0]; var g2 = c2[1]; var b2 = c2[2]; r1 += (r2 - r1) * alpha | 0; g1 += (g2 - g1) * alpha | 0; b1 += (b2 - b1) * alpha | 0; return exports.match([r1, g1, b1]); }; exports.blend = function blend(attr, attr2, alpha) { var bg = attr & 0x1ff; if (attr2 != null) { var bg2 = attr2 & 0x1ff; if (bg === 0x1ff) bg = 0; if (bg2 === 0x1ff) bg2 = 0; bg = exports.mixColors(bg, bg2, alpha); } else { if (blend._cache[bg] != null) { bg = blend._cache[bg]; // } else if (bg < 8) { // bg += 8; } else if (bg >= 8 && bg <= 15) { bg -= 8; } else { var name = exports.ncolors[bg]; if (name) { for (var i = 0; i < exports.ncolors.length; i++) { if (name === exports.ncolors[i] && i !== bg) { var c = exports.vcolors[bg]; var nc = exports.vcolors[i]; if (nc[0] + nc[1] + nc[2] < c[0] + c[1] + c[2]) { blend._cache[bg] = i; bg = i; break; } } } } } } attr &= ~0x1ff; attr |= bg; var fg = (attr >> 9) & 0x1ff; if (attr2 != null) { var fg2 = (attr2 >> 9) & 0x1ff; // 0, 7, 188, 231, 251 if (fg === 0x1ff) { // XXX workaround fg = 248; } else { if (fg === 0x1ff) fg = 7; if (fg2 === 0x1ff) fg2 = 7; fg = exports.mixColors(fg, fg2, alpha); } } else { if (blend._cache[fg] != null) { fg = blend._cache[fg]; // } else if (fg < 8) { // fg += 8; } else if (fg >= 8 && fg <= 15) { fg -= 8; } else { var name = exports.ncolors[fg]; if (name) { for (var i = 0; i < exports.ncolors.length; i++) { if (name === exports.ncolors[i] && i !== fg) { var c = exports.vcolors[fg]; var nc = exports.vcolors[i]; if (nc[0] + nc[1] + nc[2] < c[0] + c[1] + c[2]) { blend._cache[fg] = i; fg = i; break; } } } } } } attr &= ~(0x1ff << 9); attr |= fg << 9; return attr; }; exports.blend._cache = {}; exports._cache = {}; exports.reduce = function(color, total) { if (color >= 16 && total <= 16) { color = exports.ccolors[color]; } else if (color >= 8 && total <= 8) { color -= 8; } else if (color >= 2 && total <= 2) { color %= 2; } return color; }; // 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.match); exports.colors = cols; exports.vcolors = _cols; exports.ccolors = out; return out; })(); var colorNames = exports.colorNames = { // special default: -1, normal: -1, bg: -1, fg: -1, // normal black: 0, red: 1, green: 2, yellow: 3, blue: 4, magenta: 5, cyan: 6, white: 7, // light lightblack: 8, lightred: 9, lightgreen: 10, lightyellow: 11, lightblue: 12, lightmagenta: 13, lightcyan: 14, lightwhite: 15, // bright brightblack: 8, brightred: 9, brightgreen: 10, brightyellow: 11, brightblue: 12, brightmagenta: 13, brightcyan: 14, brightwhite: 15, // alternate spellings grey: 8, gray: 8, lightgrey: 7, lightgray: 7, brightgrey: 7, brightgray: 7 }; exports.convert = function(color) { if (typeof color === 'number') { ; } else if (typeof color === 'string') { color = color.replace(/[\- ]/g, ''); if (colorNames[color] != null) { color = colorNames[color]; } else { color = exports.match(color); } } else if (Array.isArray(color)) { color = exports.match(color); } else { color = -1; } return color !== -1 ? color : 0x1ff; }; // 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] ] }; exports.ncolors = []; Object.keys(exports.ccolors).forEach(function(name) { exports.ccolors[name].forEach(function(offset) { if (typeof offset === 'number') { exports.ncolors[offset] = name; exports.ccolors[offset] = exports.colorNames[name]; return; } for (var i = offset[0], l = offset[1]; i <= l; i++) { exports.ncolors[i] = name; exports.ccolors[i] = exports.colorNames[name]; } }); delete exports.ccolors[name]; });