UNPKG

neo-neo-bblessed

Version:

A fork of neo-blessed (which is a fork of blessed) with bug fixes and maintenance.

538 lines (477 loc) 12.4 kB
/** * 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[]; } export function match( r1: number | string | number[], g1?: number, b1?: number ): number { if (typeof r1 === 'string') { var hex = r1; if (hex[0] !== '#') { return -1; } const hexRgb = 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 (_cache[hash] != null) { return _cache[hash]; } var ldiff = Infinity, li = -1, i = 0, c, r2, g2, b2, diff; for (; i < vcolors.length; i++) { c = 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; } } _cache[hash] = li; return _cache[hash]; } export function RGBToHex(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); } export function hexToRGB(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 export 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. export function mixColors( c1: number, c2: number, alpha?: number | null ): number { // Handle new default color marker if (c1 === 0xffffff) c1 = 0; if (c2 === 0xffffff) c2 = 0; if (alpha === null) alpha = 0.5; const c1Color = vcolors[c1]; var r1 = c1Color[0]; var g1 = c1Color[1]; var b1 = c1Color[2]; const c2Color = vcolors[c2]; var r2 = c2Color[0]; var g2 = c2Color[1]; var b2 = c2Color[2]; r1 += ((r2 - r1) * alpha) | 0; g1 += ((g2 - g1) * alpha) | 0; b1 += ((b2 - b1) * alpha) | 0; return match([r1, g1, b1]); } export function blend( attr: number, attr2?: number | null, alpha?: number ): number { var name, i, c, nc; // Extract colors from new format var bg = attr & 0xffffff; var fg = Math.floor(attr / 0x1000000) & 0xffffff; var flags = Math.floor(attr / 0x1000000000000); if (attr2 !== null) { var bg2 = attr2 & 0xffffff; var fg2 = Math.floor(attr2 / 0x1000000) & 0xffffff; // Handle default colors if (bg === 0xffffff) bg = 0; if (bg2 === 0xffffff) bg2 = 0; if (fg === 0xffffff) fg = 7; if (fg2 === 0xffffff) fg2 = 7; // For RGB colors (> 255), convert to palette for blending if (bg > 255) bg = match([(bg >> 16) & 0xff, (bg >> 8) & 0xff, bg & 0xff]); if (bg2 > 255) bg2 = match([(bg2 >> 16) & 0xff, (bg2 >> 8) & 0xff, bg2 & 0xff]); if (fg > 255) fg = match([(fg >> 16) & 0xff, (fg >> 8) & 0xff, fg & 0xff]); if (fg2 > 255) fg2 = match([(fg2 >> 16) & 0xff, (fg2 >> 8) & 0xff, fg2 & 0xff]); bg = mixColors(bg, bg2, alpha); fg = mixColors(fg, fg2, alpha); } else { // Handle single color processing if (bg === 0xffffff) bg = 0; if (fg === 0xffffff) fg = 7; // For RGB colors, convert to palette if (bg > 255) bg = match([(bg >> 16) & 0xff, (bg >> 8) & 0xff, bg & 0xff]); if (fg > 255) fg = match([(fg >> 16) & 0xff, (fg >> 8) & 0xff, fg & 0xff]); // Apply cache and brightness adjustments for palette colors if (bg <= 255) { if ((blend as any)._cache[bg] !== null) { bg = (blend as any)._cache[bg]; } else if (bg >= 8 && bg <= 15) { bg -= 8; } else { name = ncolors[bg]; if (name) { for (i = 0; i < ncolors.length; i++) { if (name === ncolors[i] && i !== bg) { c = vcolors[bg]; nc = vcolors[i]; if (nc[0] + nc[1] + nc[2] < c[0] + c[1] + c[2]) { (blend as any)._cache[bg] = i; bg = i; break; } } } } } } if (fg <= 255) { if ((blend as any)._cache[fg] !== null) { fg = (blend as any)._cache[fg]; } else if (fg >= 8 && fg <= 15) { fg -= 8; } else { name = ncolors[fg]; if (name) { for (i = 0; i < ncolors.length; i++) { if (name === ncolors[i] && i !== fg) { c = vcolors[fg]; nc = vcolors[i]; if (nc[0] + nc[1] + nc[2] < c[0] + c[1] + c[2]) { (blend as any)._cache[fg] = i; fg = i; break; } } } } } } } // Pack back into new format return bg + fg * 0x1000000 + flags * 0x1000000000000; } blend._cache = {}; export const _cache = {}; export function reduce(color: number, total: number): number { if (color >= 16 && total <= 16) { color = 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. export const 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. export const vcolors = [] as number[][]; export const colors = (function () { var cols = [] as string[], _cols = 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 xterm.forEach(function (c, i) { const cValue = parseInt(c.substring(1), 16); push(i, (cValue >> 16) & 0xff, (cValue >> 8) & 0xff, cValue & 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; })(); export const colorNames: { [key: string]: number } = { // 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, }; export function convert(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 if (color.startsWith('#')) { // Handle hex colors like #ff0000 const hex = color.slice(1); if (hex.length === 6) { const r = parseInt(hex.slice(0, 2), 16); const g = parseInt(hex.slice(2, 4), 16); const b = parseInt(hex.slice(4, 6), 16); result = (r << 16) | (g << 8) | b; // Return full RGB value } else { result = match(color); } } else { result = match(color); } } else if (Array.isArray(color) && color.length === 3) { // Handle RGB arrays like [255, 0, 0] const [r, g, b] = color; if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) { result = (r << 16) | (g << 8) | b; // Return full RGB value } else { result = match(color); } } else if (Array.isArray(color)) { result = match(color); } else { result = -1; } // Return default color marker for undefined/null/-1, otherwise return the color return result !== -1 ? result : 0xffffff; } // 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? export const ccolors: any = { 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]], }; export const ncolors: string[] = []; Object.keys(ccolors).forEach(function (name) { ccolors[name].forEach(function (offset: any) { if (typeof offset === 'number') { ncolors[offset] = name; ccolors[offset] = colorNames[name]; return; } for (var i = offset[0], l = offset[1]; i <= l; i++) { ncolors[i] = name; ccolors[i] = colorNames[name]; } }); delete ccolors[name]; });