diff --git a/js/term/colors.js b/js/term/colors.js new file mode 100644 index 0000000..6d0cb89 --- /dev/null +++ b/js/term/colors.js @@ -0,0 +1,530 @@ +/** + * 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 name, i, c, nc; + + 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 { + name = exports.ncolors[bg]; + if (name) { + for (i = 0; i < exports.ncolors.length; i++) { + if (name === exports.ncolors[i] && i !== bg) { + c = exports.vcolors[bg]; + 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 { + name = exports.ncolors[fg]; + if (name) { + for (i = 0; i < exports.ncolors.length; i++) { + if (name === exports.ncolors[i] && i !== fg) { + c = exports.vcolors[fg]; + 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]; +});