egra: even more comments
[iv.d.git] / termrgb.d
blobf659a257d753432c2a4f0b1e7b103de31039914b
1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 // 256-color terminal utilities
18 module iv.termrgb /*is aliced*/;
19 private:
21 //version = termrgb_weighted_colors;
22 //version = termrgb_disable_256_colors;
23 //version = termrgb_gamma_correct;
26 // ////////////////////////////////////////////////////////////////////////// //
27 /// Terminal type (yeah, i know alot of 'em)
28 public enum TermType {
29 other,
30 rxvt,
31 xterm,
32 linux, // linux console
36 public __gshared TermType termType = TermType.other; ///
37 __gshared bool isTermRedirected = true; ///
40 /// is TTY stdin or stdout redirected? note that stderr *can* be redirected.
41 public @property bool ttyIsRedirected () nothrow @trusted @nogc { pragma(inline, true); return isTermRedirected; }
44 shared static this () nothrow @trusted @nogc {
46 import core.stdc.stdlib : getenv;
47 import core.stdc.string : strcmp;
48 auto tt = getenv("TERM");
49 if (tt !is null) {
50 auto len = 0;
51 while (len < 5 && tt[len]) ++len;
52 if (len >= 4 && tt[0..4] == "rxvt") termType = TermType.rxvt;
53 else if (len >= 5 && tt[0..5] == "xterm") termType = TermType.xterm;
54 else if (len >= 5 && tt[0..5] == "linux") termType = TermType.linux;
58 import core.sys.posix.unistd : isatty, STDIN_FILENO, STDOUT_FILENO;
59 import core.sys.posix.termios : tcgetattr;
60 import core.sys.posix.termios : termios;
61 if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
62 termios origMode = void;
63 if (tcgetattr(STDIN_FILENO, &origMode) == 0) isTermRedirected = false;
69 // ////////////////////////////////////////////////////////////////////////// //
70 /// k8sterm color table, lol
71 static immutable uint[256] ttyRGBTable = {
72 uint[256] res;
73 // standard terminal colors
74 res[0] = 0x000000;
75 res[1] = 0xb21818;
76 res[2] = 0x18b218;
77 res[3] = 0xb26818;
78 res[4] = 0x1818b2;
79 res[5] = 0xb218b2;
80 res[6] = 0x18b2b2;
81 res[7] = 0xb2b2b2;
82 res[8] = 0x686868;
83 res[9] = 0xff5454;
84 res[10] = 0x54ff54;
85 res[11] = 0xffff54;
86 res[12] = 0x5454ff;
87 res[13] = 0xff54ff;
88 res[14] = 0x54ffff;
89 res[15] = 0xffffff;
90 // rgb colors [16..231]
91 int f = 16;
92 foreach (ubyte r; 0..6) {
93 foreach (ubyte g; 0..6) {
94 foreach (ubyte b; 0..6) {
95 uint cr = (r == 0 ? 0 : 0x37+0x28*r); assert(cr <= 255);
96 uint cg = (g == 0 ? 0 : 0x37+0x28*g); assert(cg <= 255);
97 uint cb = (b == 0 ? 0 : 0x37+0x28*b); assert(cb <= 255);
98 res[f++] = (cr<<16)|(cg<<8)|cb;
102 assert(f == 232);
103 // b/w shades [232..255]
104 foreach (ubyte n; 0..24) {
105 uint c = 0x08+0x0a*n; assert(c <= 255);
106 res[f++] = (c<<16)|(c<<8)|c;
108 assert(f == 256);
109 return res;
110 }();
113 static immutable uint[16] ttyRGB16 = {
114 uint[16] res;
115 // standard terminal colors
116 version(tty_linux_dumb) {
117 res[0] = 0x000000;
118 res[1] = 0x800000;
119 res[2] = 0x008000;
120 res[3] = 0x808000;
121 res[4] = 0x000080;
122 res[5] = 0x800080;
123 res[6] = 0x008080;
124 res[7] = 0xc0c0c0;
125 res[8] = 0x808080;
126 res[9] = 0xff0000;
127 res[10] = 0x00ff00;
128 res[11] = 0xffff00;
129 res[12] = 0x0000ff;
130 res[13] = 0xff00ff;
131 res[14] = 0x00ffff;
132 res[15] = 0xffffff;
133 } else version(tty_linux_hi) {
134 res[0] = 0x000000;
135 res[1] = 0xc00000;
136 res[2] = 0x00c000;
137 res[3] = 0xc0c000;
138 res[4] = 0x0000c0;
139 res[5] = 0xc000c0;
140 res[6] = 0x00c0c0;
141 res[7] = 0xc0c0c0;
142 res[8] = 0x808080;
143 res[9] = 0xff0000;
144 res[10] = 0x00ff00;
145 res[11] = 0xffff00;
146 res[12] = 0x0000ff;
147 res[13] = 0xff00ff;
148 res[14] = 0x00ffff;
149 res[15] = 0xffffff;
150 } else {
151 res[0] = 0x000000;
152 res[1] = 0xb21818;
153 res[2] = 0x18b218;
154 res[3] = 0xb26818;
155 res[4] = 0x1818b2;
156 res[5] = 0xb218b2;
157 res[6] = 0x18b2b2;
158 res[7] = 0xb2b2b2;
159 res[8] = 0x686868;
160 res[9] = 0xff5454;
161 res[10] = 0x54ff54;
162 res[11] = 0xffff54;
163 res[12] = 0x5454ff;
164 res[13] = 0xff54ff;
165 res[14] = 0x54ffff;
166 res[15] = 0xffffff;
168 return res;
169 }();
172 version(termrgb_gamma_correct) {
173 // color in sRGB space
174 struct SRGB {
175 float r=0, g=0, b=0; // [0..1]
176 //alias x = r, y = g, z = b;
177 this (float ar, float ag, float ab) pure nothrow @safe @nogc { r = ar; g = ag; b = ab; }
178 this() (in auto ref FXYZ c) pure nothrow @safe @nogc {
179 version(tty_XYZ) {
180 immutable float xs = c.x* 3.2406+c.y*-1.5372+c.z*-0.4986;
181 immutable float ys = c.x*-0.9689+c.y* 1.8758+c.z* 0.0415;
182 immutable float zs = c.x* 0.0557+c.y*-0.2040+c.z* 1.0570;
183 r = valueFromLinear(xs);
184 g = valueFromLinear(ys);
185 b = valueFromLinear(zs);
186 } else {
187 r = valueFromLinear(c.x);
188 g = valueFromLinear(c.y);
189 b = valueFromLinear(c.z);
193 // linear to gamma conversion
194 // value should be in [0..1] range
195 static T valueFromLinear(T : real) (T v) pure nothrow @safe @nogc {
196 import std.math : pow;
197 return (v > 0.0031308 ? 1.055*pow(v, (1.0/2.4))-0.055 : 12.92*v);
201 // color in linear space
202 struct FXYZ {
203 float x=0, y=0, z=0; // [0..1]
204 this (float ax, float ay, float az) pure nothrow @safe @nogc { x = ax; y = ay; z = az; }
205 this() (in auto ref SRGB c) pure nothrow @safe @nogc {
206 version(tty_XYZ) {
207 immutable float rl = valueFromGamma(c.r);
208 immutable float gl = valueFromGamma(c.g);
209 immutable float bl = valueFromGamma(c.b);
210 // observer. = 2degs, Illuminant = D65
211 x = rl*0.4124+gl*0.3576+bl*0.1805;
212 y = rl*0.2126+gl*0.7152+bl*0.0722;
213 z = rl*0.0193+gl*0.1192+bl*0.9505;
214 } else {
215 x = valueFromGamma(c.r);
216 y = valueFromGamma(c.g);
217 z = valueFromGamma(c.b);
221 // gamma to linear conversion
222 // value should be in [0..1] range
223 static T valueFromGamma(T : real) (T v) pure nothrow @safe @nogc {
224 import std.math : pow;
225 return (v > 0.04045 ? pow((v+0.055)/1.055, 2.4) : v/12.92);
231 /// Convert 256-color terminal color number to approximate rgb values
232 public void ttyColor2rgb (ubyte cnum, out ubyte r, out ubyte g, out ubyte b) pure nothrow @trusted @nogc {
233 pragma(inline, true);
234 r = cast(ubyte)(ttyRGBTable.ptr[cnum]>>16);
235 g = cast(ubyte)(ttyRGBTable.ptr[cnum]>>8);
236 b = cast(ubyte)(ttyRGBTable.ptr[cnum]);
238 if (cnum == 0) {
239 r = g = b = 0;
240 } else if (cnum == 8) {
241 r = g = b = 0x80;
242 } else if (cnum >= 0 && cnum < 16) {
243 r = (cnum&(1<<0) ? (cnum&(1<<3) ? 0xff : 0x80) : 0x00);
244 g = (cnum&(1<<1) ? (cnum&(1<<3) ? 0xff : 0x80) : 0x00);
245 b = (cnum&(1<<2) ? (cnum&(1<<3) ? 0xff : 0x80) : 0x00);
246 } else if (cnum >= 16 && cnum < 232) {
247 // [0..5] -> [0..255]
248 b = cast(ubyte)(((cnum-16)%6)*51);
249 g = cast(ubyte)((((cnum-16)/6)%6)*51);
250 r = cast(ubyte)((((cnum-16)/6/6)%6)*51);
251 } else if (cnum >= 232 && cnum <= 255) {
252 // [0..23] (0 is very dark gray; 23 is *almost* white)
253 b = g = r = cast(ubyte)(8+(cnum-232)*10);
258 /// Ditto.
259 public alias ttyColor2RGB = ttyColor2rgb;
261 /// Ditto.
262 public alias ttyColor2Rgb = ttyColor2rgb;
265 immutable static ubyte[256] tty256to16tbl = () {
266 ubyte[256] res;
267 foreach (ubyte idx; 0..256) {
268 immutable cc = ttyRGBTable[idx];
269 immutable r = (cc>>16)&0xff;
270 immutable g = (cc>>8)&0xff;
271 immutable b = cc&0xff;
272 res[idx] = ttyRGB!false(r, g, b);
274 foreach (ubyte idx; 0..16) res[idx] = idx;
275 return res;
276 }();
279 immutable static ubyte[256] tty256to8tbl = () {
280 ubyte[256] res;
281 foreach (ubyte idx; 0..256) {
282 immutable cc = ttyRGBTable[idx];
283 immutable r = (cc>>16)&0xff;
284 immutable g = (cc>>8)&0xff;
285 immutable b = cc&0xff;
286 res[idx] = ttyRGB!(false, true)(r, g, b);
288 foreach (ubyte idx; 0..8) { res[idx] = idx; res[idx+8] = idx; }
289 return res;
290 }();
293 /// convert 256-color code to 16-color Linux console code
294 public ubyte tty2linux (ubyte ttyc) nothrow @trusted @nogc {
295 pragma(inline, true);
296 return (termType != TermType.linux ? ttyc : tty256to16tbl[ttyc]);
300 /// convert 256-color code to 8-color Linux console code
301 public ubyte tty2linux8 (ubyte ttyc) nothrow @trusted @nogc {
302 pragma(inline, true);
303 return (termType != TermType.linux ? ttyc : tty256to8tbl[ttyc]);
307 /// Force CTFE
308 public enum TtyRGB(ubyte r, ubyte g, ubyte b, bool allow256=true) = ttyRGB!allow256(r, g, b);
310 /// Ditto.
311 public enum TtyRGB(string rgb, bool allow256=true) = ttyRGB!allow256(rgb);
313 public alias TtyRgb = TtyRGB; /// Ditto.
316 /// Convert rgb values to approximate 256-color (or 16-color) teminal color number
317 public ubyte ttyRGB(bool allow256=true, bool only8=false) (const(char)[] rgb) pure nothrow @trusted @nogc {
318 static int c2h (immutable char ch) pure nothrow @trusted @nogc {
319 if (ch >= '0' && ch <= '9') return ch-'0';
320 else if (ch >= 'A' && ch <= 'F') return ch-'A'+10;
321 else if (ch >= 'a' && ch <= 'f') return ch-'a'+10;
322 else return -1;
325 auto anchor = rgb;
326 while (rgb.length && (rgb[0] <= ' ' || rgb[0] == '#')) rgb = rgb[1..$];
327 while (rgb.length && rgb[$-1] <= ' ') rgb = rgb[0..$-1];
328 if (rgb.length == 3) {
329 foreach (immutable char ch; rgb) if (c2h(ch) < 0) return 7; // normal gray
330 return ttyRGB(
331 cast(ubyte)(255*c2h(rgb[0])/15),
332 cast(ubyte)(255*c2h(rgb[1])/15),
333 cast(ubyte)(255*c2h(rgb[2])/15),
335 } else if (rgb.length == 6) {
336 foreach (immutable char ch; rgb) if (c2h(ch) < 0) return 7; // normal gray
337 return ttyRGB(
338 cast(ubyte)(16*c2h(rgb[0])+c2h(rgb[1])),
339 cast(ubyte)(16*c2h(rgb[2])+c2h(rgb[3])),
340 cast(ubyte)(16*c2h(rgb[4])+c2h(rgb[5])),
342 } else {
343 return 7; // normal gray
348 /// Convert rgb values to approximate 256-color (or 16-color) teminal color number
349 public ubyte ttyRGB(bool allow256=true, bool only8=false) (ubyte r, ubyte g, ubyte b) pure nothrow @trusted @nogc {
350 // use standard (weighted) color distance function to find the closest match
351 // d = ((r2-r1)*0.30)^^2+((g2-g1)*0.59)^^2+((b2-b1)*0.11)^^2
352 version(termrgb_gamma_correct) {
353 static if (only8) { enum lastc = 8; alias rgbtbl = ttyRGB16; }
354 else {
355 version(termrgb_disable_256_colors) { enum lastc = 16; alias rgbtbl = ttyRGB16; }
356 else { static if (allow256) { enum lastc = 256; alias rgbtbl = ttyRGBTable;} else { enum lastc = 16; alias rgbtbl = ttyRGB16; } }
358 double dist = double.max;
359 ubyte resclr = 0;
360 immutable l0 = FXYZ(SRGB(r/255.0f, g/255.0f, b/255.0f));
361 foreach (immutable idx, uint cc; rgbtbl[0..lastc]) {
362 auto linear = FXYZ(SRGB(((cc>>16)&0xff)/255.0f, ((cc>>8)&0xff)/255.0f, (cc&0xff)/255.0f));
363 linear.x -= l0.x;
364 linear.y -= l0.y;
365 linear.z -= l0.z;
366 //double dd = linear.x*linear.x+linear.y*linear.y+linear.z*linear.z;
367 double dd = (linear.x*linear.x)*0.30+(linear.y*linear.y)*0.59+(linear.z*linear.z)*0.11;
368 if (dd < dist) {
369 resclr = cast(ubyte)idx;
370 dist = dd;
373 return resclr;
374 } else {
375 enum n = 16384; // scale
376 enum m0 = 4916; // 0.30*16384
377 enum m1 = 9666; // 0.59*16384
378 enum m2 = 1802; // 0.11*16384
379 long dist = long.max;
380 ubyte resclr = 0;
381 static if (only8) { enum lastc = 8; alias rgbtbl = ttyRGB16; }
382 else {
383 version(termrgb_disable_256_colors) { enum lastc = 16; alias rgbtbl = ttyRGB16; }
384 else { static if (allow256) { enum lastc = 256; alias rgbtbl = ttyRGBTable;} else { enum lastc = 16; alias rgbtbl = ttyRGB16; } }
386 foreach (immutable idx, uint cc; rgbtbl[0..lastc]) {
387 version(termrgb_weighted_colors) {
388 long dr = cast(int)((cc>>16)&0xff)-cast(int)r;
389 dr = ((dr*m0)*(dr*m0))/n;
390 assert(dr >= 0);
391 long dg = cast(int)((cc>>8)&0xff)-cast(int)g;
392 dg = ((dg*m1)*(dg*m1))/n;
393 assert(dg >= 0);
394 long db = cast(int)(cc&0xff)-cast(int)b;
395 db = ((db*m2)*(db*m2))/n;
396 assert(db >= 0);
397 long d = dr+dg+db;
398 assert(d >= 0);
399 } else {
400 long dr = cast(int)((cc>>16)&0xff)-cast(int)r;
401 dr = dr*dr;
402 assert(dr >= 0);
403 long dg = cast(int)((cc>>8)&0xff)-cast(int)g;
404 dg = dg*dg;
405 assert(dg >= 0);
406 long db = cast(int)(cc&0xff)-cast(int)b;
407 db = db*db;
408 assert(db >= 0);
409 long d = dr+dg+db;
410 assert(d >= 0);
412 if (d < dist) {
413 resclr = cast(ubyte)idx;
414 dist = d;
415 if (d == 0) break; // no better match is possible
418 return resclr;
422 public alias ttyRgb = ttyRGB; /// Ditto.