neo-neo-bblessed
Version:
A fork of neo-blessed (which is a fork of blessed) with bug fixes and maintenance.
511 lines (456 loc) • 11 kB
text/typescript
/**
* colors.js - color-related functions for blessed.
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
* https://github.com/chjj/blessed
*/
interface RGB {
0: number;
1: number;
2: number;
}
type ColorInput = string | number[] | RGB;
interface ColorModule {
match(r1: number | string | number[], g1?: number, b1?: number): number;
RGBToHex(r: number | number[], g?: number, b?: number): string;
hexToRGB(hex: string): number[];
mixColors(c1: number, c2: number, alpha?: number): number;
blend(attr: number, attr2?: number | null, alpha?: number): number;
reduce(color: number, total: number): number;
convert(color: ColorInput): number;
_cache: { [key: number]: number };
xterm: string[];
colors: string[];
vcolors: number[][];
colorNames: { [key: string]: number };
ccolors: { [key: string]: any };
ncolors: string[];
}
exports.match = function (
r1: number | string | number[],
g1?: number,
b1?: number
): number {
if (typeof r1 === 'string') {
var hex = r1;
if (hex[0] !== '#') {
return -1;
}
const hexRgb = exports.hexToRGB(hex);
r1 = hexRgb[0];
g1 = hexRgb[1];
b1 = hexRgb[2];
} else if (Array.isArray(r1)) {
b1 = r1[2];
g1 = r1[1];
r1 = r1[0];
}
var hash = ((r1 as number) << 16) | ((g1 as number) << 8) | (b1 as number);
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 as number, g1 as number, b1 as number, r2, g2, b2);
if (diff === 0) {
li = i;
break;
}
if (diff < ldiff) {
ldiff = diff;
li = i;
}
}
exports._cache[hash] = li;
return exports._cache[hash];
};
exports.RGBToHex = function (
r: number | number[],
g?: number,
b?: number
): string {
if (Array.isArray(r)) {
b = r[2];
g = r[1];
r = r[0];
}
function hex(n: number): string {
let hexStr = n.toString(16);
if (hexStr.length < 2) hexStr = '0' + hexStr;
return hexStr;
}
return '#' + hex(r) + hex(g) + hex(b);
};
exports.hexToRGB = function (hex: string): number[] {
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: number,
g1: number,
b1: number,
r2: number,
g2: number,
b2: number
): number {
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: number,
c2: number,
alpha?: number | null
): number {
// 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: number,
attr2?: number | null,
alpha?: number
): number {
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 as any)._cache[bg] !== null) {
bg = (blend as any)._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 as any)._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 as any)._cache[fg] !== null) {
fg = (blend as any)._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 as any)._cache[fg] = i;
fg = i;
break;
}
}
}
}
}
}
attr &= ~(0x1ff << 9);
attr |= fg << 9;
return attr;
};
exports.blend._cache = {};
exports._cache = {};
exports.reduce = function (color: number, total: number): number {
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: number,
g: number,
b: number,
i: number,
l: number;
function hex(n: number): string {
let hexStr = n.toString(16);
if (hexStr.length < 2) hexStr = '0' + hexStr;
return hexStr;
}
function push(i: number, r: number, g: number, b: number): void {
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;
})();
var colorNames: { [key: string]: number } = (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: ColorInput): number {
let result: number;
if (typeof color === 'number') {
result = color;
} else if (typeof color === 'string') {
let name = color.replace(/[\- ]/g, '');
if (colorNames[name] !== null) {
result = colorNames[name];
} else {
result = exports.match(color);
}
} else if (Array.isArray(color)) {
result = exports.match(color);
} else {
result = -1;
}
return result !== -1 ? result : 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,
[],
[],
[],
[],
45,
[],
[],
[],
[],
81,
[],
[],
[],
[],
117,
[],
[],
[],
[],
153,
165,
171,
177,
183,
189,
],
green: [
2,
10,
22,
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
],
cyan: [
6, 14, 23, 30, 37, 44, 51, 66, 73, 80, 87, 109, 116, 123, 152, 159, 195,
],
red: [
1,
9,
52,
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
],
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];
});