1 /** 
2 Module containing various color related utilities
3 */
4 module sily.color;
5 
6 import std.algorithm : canFind;
7 import std.algorithm.comparison;
8 import std.algorithm.comparison: compclamp = clamp;
9 import std.array : replace;
10 import std.conv;
11 import std.math;
12 import std.regex;
13 import std.stdio;
14 import std.string;
15 import std.traits;
16 import std.uni : toLower;
17 import std.traits: isNumeric;
18 
19 import sily.vector;
20 import sily.meta.swizzle;
21 import sily.array;
22 
23 /// GLSL style alias to Color
24 alias col = Color;
25 alias Color8 = color8;
26 
27 /// Constructs color from 8 bit (0-255) components
28 private Color color8(float R, float G, float B, float A = 255) {
29     import sily.color;
30     return Color(R / 255.0f, G / 255.0f, B / 255.0f, A / 255.0f);
31 }
32 
33 /// Color structure with data accesible with `[N]` or swizzling
34 struct Color {
35     /// Color data
36     public float[4] data = [ 1.0f ];
37 
38     /// Alias to allow easy `data` access
39     alias data this;
40     /// Alias to data
41     alias arrayof = data;
42 
43     /** 
44     Constructs Color from float components. If no components present
45     color will default to white ([1, 1, 1, 1])
46     Example:
47     ---
48     // You can explicitly define alpha or omit it
49     Color red = Color(1, 0, 0);
50     Color reda = Color(1, 0, 0, 1); 
51     // If presented with one or two components
52     // color will fill rgb portion of itself
53     // with only this component
54     // You can also omit/define alpha with it
55     Color gray = Color(0.5); 
56     Color graya = Color(0.5, 0.8);
57     // Also theres two aliases to help you
58     // GLSL style color as type
59     col c = col(0.2, 0.4, 1)
60     // And custom constructor that allows you
61     // to use 8 bit values (0-255)
62     Color webCol = Color8(255, 0, 255);
63     // Colors can be accessed with array slicing,
64     // by using color symbols or swizzling (rgba)
65     float rcomp = c.r;
66     float gcomp = c[1];
67     float bcomp = c.b;
68     float[] redblue = c.rb;
69     float[] redbluebluegreen = c.rbbg;
70     ---
71     */
72     this(in float val) {
73         foreach (i; 0 .. 3) { data[i] = val; }
74         data[3] = 1.0f;
75     }
76     /// Ditto
77     this(in float val, in float a) {
78         foreach (i; 0 .. 3) { data[i] = val; }
79         data[3] = a;
80     }
81     /// Ditto
82     this(in float[3] vals...) {
83         data = vals ~ [1.0f];
84     }
85     /// Ditto
86     this(in float[4] vals...) {
87         data = vals;
88     }
89 
90     /// Returns color component in 8 bit form
91     ubyte r8() { return cast(ubyte) (data[0] * 255u); }
92     /// Ditto
93     ubyte g8() { return cast(ubyte) (data[1] * 255u); }
94     /// Ditto
95     ubyte b8() { return cast(ubyte) (data[2] * 255u); }
96     /// Ditto
97     ubyte a8() { return cast(ubyte) (data[3] * 255u); }
98 
99     /* -------------------------------------------------------------------------- */
100     /*                         UNARY OPERATIONS OVERRIDES                         */
101     /* -------------------------------------------------------------------------- */
102     
103     /// opBinary x [+, -, *, /, %] y
104     auto opBinary(string op, R)(in Color!(R, N) b) const if ( isNumeric!R ) {
105         // assert(/* this !is null && */ b !is null, "\nOP::ERROR nullptr Color!" ~ 4.to!string ~ ".");
106         VecType ret = VecType();
107         foreach (i; 0 .. 4) { mixin( "ret.data[i] = data[i] " ~ op ~ " b.data[i];" ); }
108         return ret;
109     }
110 
111     /// Ditto
112     auto opBinaryRight(string op, R)(in Color!(R, N) b) const if ( isNumeric!R ) {
113         // assert(/* this !is null && */ b !is null, "\nOP::ERROR nullptr Color!" ~ 4.to!string ~ ".");
114         VecType ret = VecType();
115         foreach (i; 0 .. 4) { mixin( "ret[i] = b.data[i] " ~ op ~ " data[i];" ); }
116         return ret;
117     }
118 
119     /// Ditto
120     auto opBinary(string op, R)(in R b) const if ( isNumeric!R ) {
121         // assert(this !is null, "\nOP::ERROR nullptr Color!" ~ 4.to!string ~ ".");
122         VecType ret = VecType();
123         foreach (i; 0 .. 4) { mixin( "ret.data[i] = data[i] " ~ op ~ " b;" ); }
124         return ret;
125     }
126 
127     /// Ditto
128     auto opBinaryRight(string op, R)(in R b) const if ( isNumeric!R ) {
129         // assert(this !is null, "\nOP::ERROR nullptr Color!" ~ 4.to!string ~ ".");
130         VecType ret = VecType();
131         foreach (i; 0 .. 4) { mixin( "ret[i] = b " ~ op ~ " data[i];" ); }
132         return ret;
133     }
134 
135     /// opEquals x == y
136     bool opEquals(R)(in Color!(R, 4) b) const if ( isNumeric!R ) {
137         // assert(/* this !is null && */ b !is null, "\nOP::ERROR nullptr Color!" ~ 4.to!string ~ ".");
138         bool eq = true;
139         foreach (i; 0 .. 4) { 
140             eq = eq && data[i] == b.data[i]; 
141             if (!eq) break;
142         }
143         return eq;
144     }
145 
146     /// opCmp x [< > <= >=] y
147     int opCmp(R)(in Color!(R, N) b) const if ( isNumeric!R ) {
148         // assert(/* this !is null && */ b !is null, "\nOP::ERROR nullptr Color!" ~ 4.to!string ~ ".");
149         float al = length;
150         float bl = b.length;
151         if (al == bl) return 0;
152         if (al < bl) return -1;
153         return 1;
154     }
155 
156     /// opUnary [-, +, --, ++] x
157     auto opUnary(string op)() if(op == "-"){
158         // assert(this !is null, "\nOP::ERROR nullptr Color!" ~ 4.to!string ~ ".");
159         VecType ret = VecType();
160         if (op == "-")
161             foreach (i; 0 .. 4) { ret.data[i] = -data[i]; }
162         return ret;
163     }
164     
165     /// opOpAssign x [+, -, *, /, %]= y
166     auto opOpAssign(string op, R)( in Color!(R, N) b ) if ( isNumeric!R ) { 
167         // assert(/* this !is null && */ b !is null, "\nOP::ERROR nullptr Color!" ~ 4.to!string ~ ".");
168         foreach (i; 0 .. 4) { mixin( "data[i] = data[i] " ~ op ~ " b.data[i];" ); }
169         return this;
170     }
171     
172     /// Ditto
173     auto opOpAssign(string op, R)( in R b ) if ( isNumeric!R ) { 
174         // assert(this !is null, "\nOP::ERROR nullptr Color!" ~ 4.to!string ~ ".");
175         foreach (i; 0 .. 4) { mixin( "data[i] = data[i] " ~ op ~ " b;" ); }
176         return this;
177     }
178 
179     /// opCast cast(x) y
180     R opCast(R)() const if (isVector!R && (R.size == 3 || R.size == 4) && isFloatingPoint!(R.dataType)){
181         R ret;
182         foreach (i; 0 ..  R.size) {
183             ret[i] = cast(R.dataType) data[i];
184         }
185         return ret;
186     }
187     /// Ditto
188     R opCast(R)() const if (isVector!R && (R.size == 3 || R.size == 4) && !isFloatingPoint!(R.dataType)){
189         R ret;
190         foreach (i; 0 ..  R.size) {
191             ret[i] = cast(R.dataType) (data[i] * 255.0f);
192         }
193         return ret;
194     }
195     /// Ditto
196     bool opCast(T)() const if (is(T == bool)) {
197         float s = 0;
198         foreach (i; 0..4) {
199             s += data[i];
200         } 
201         return !s.isClose(0, float.epsilon);
202     }
203 
204 
205     /// Returns hash 
206     size_t toHash() const @safe nothrow {
207         return typeid(data).getHash(&data);
208     }
209     
210     // incredible magic from sily.meta
211     // idk how it works but it works awesome
212     // and im not going to touch it at all
213     enum AccessString = "r g b a"; // exclude from docs
214     mixin accessByString!(float, 4, "data", AccessString); // exclude from docs
215     
216     // /** 
217     // Returns color transformed to float vector.
218     // Also direct assign syntax is allowed:
219     // ---
220     // // Assigns rgba values
221     // Vector!(float, 4) v4 = Color(0.4, 1.0);
222     // // Only rgb values
223     // Vector!(float, 3) v3 = Color(0.7);
224     // ---
225     // */
226     // public vec4 asVector4f() {
227     //     return vec4(data);
228     // }
229     // /// Ditto
230     // public vec3 asVector3f() {
231     //     return vec3(data[0..3]);
232     // }
233 
234     /// Returns copy of color
235     public Color copyof() {
236         return Color(data);
237     }
238 
239     /// Returns string representation of color: `[1.00, 1.00, 1.00, 1.00]`
240     public string toString() const {
241         import std.conv : to;
242         string s;
243         s ~= "[";
244         foreach (i; 0 .. 4) {
245             s ~= format("%.2f", data[i]);
246             if (i != 4 - 1) s ~= ", ";
247         }
248         s ~= "]";
249         return s;
250     }
251 
252     /// Returns pointer to data
253     float* ptr() return {
254         return data.ptr;
255     }
256 
257     /* -------------------------------------------------------------------------- */
258     /*                                 PROPERTIES                                 */
259     /* -------------------------------------------------------------------------- */
260 
261     /// Inverts color
262     public void invert() {
263         data[0] = 1.0f - data[0];
264         data[1] = 1.0f - data[1];
265         data[2] = 1.0f - data[2];
266         data[3] = 1.0f - data[3];
267     }
268 
269     /// Returns the luminance of the color in the [0.0, 1.0] range
270     public float luminance() {
271         return compclamp(0.2126f * data[0] + 0.7152f * data[1] + 0.0722f * data[2], 0.0f, 1.0f);
272     } 
273 
274     /** 
275     Returns the linear interpolation with another color
276     Params:
277       p_to = Color to interpolate with
278       p_weight = Interpolation factor in [0.0, 1.0] range
279     */
280 	public void lerp(const Color p_to, float p_weight) {
281 		data[0] += (p_weight * (p_to.data[0] - data[0]));
282 		data[1] += (p_weight * (p_to.data[1] - data[1]));
283 		data[2] += (p_weight * (p_to.data[2] - data[2]));
284 		data[3] += (p_weight * (p_to.data[3] - data[3]));
285 	} 
286 
287     /** 
288     Darkens color by `p_amount`
289     Params: 
290       p_amount = Amount to darken
291     */
292 	public void darken(float p_amount) {
293 		data[0] = data[0] * (1.0f - p_amount);
294 		data[1] = data[1] * (1.0f - p_amount);
295 		data[2] = data[2] * (1.0f - p_amount);
296 	}
297 
298     /** 
299     Lightens color by `p_amount`
300     Params: 
301       p_amount = Amount to lighten
302     */
303 	public void lighten(float p_amount) {
304 		data[0] = data[0] + (1.0f - data[0]) * p_amount;
305 		data[1] = data[1] + (1.0f - data[1]) * p_amount;
306 		data[2] = data[2] + (1.0f - data[2]) * p_amount;
307 	}
308 
309     /** 
310     Clamps color values between `min` and `max`
311     Params: 
312       min = Minimal allowed value
313       max = Maximal allowed value
314     */
315     void clamp(float min = 0.0f, float max = 1.0f) {
316         data[0] = compclamp(data[0], min, max);
317         data[1] = compclamp(data[1], min, max);
318         data[2] = compclamp(data[2], min, max);
319         data[3] = compclamp(data[3], min, max);
320     }
321 
322     /* -------------------------------------------------------------------------- */
323     /*                                    HTML                                    */
324     /* -------------------------------------------------------------------------- */
325 
326     private uint tocolbit(uint c_bits, int[4] c_order ...) {
327         uint c_size = 2.pow(c_bits) - 1;
328         uint c_shift = c_bits;
329         uint c = (data[c_order[0]] * c_size).round.to!uint;
330         c <<= c_shift;
331         c |= (data[c_order[1]] * c_size).round.to!uint;
332         c <<= c_shift;
333         c |= (data[c_order[2]] * c_size).round.to!uint;
334         c <<= c_shift;
335         c |= (data[c_order[3]] * c_size).round.to!uint;
336         return c;
337     }
338 
339     // public uint toargb32() { return tocolbit(8, 3, 0, 1, 2); }
340     // public uint toabgr32() { return tocolbit(8, 3, 2, 1, 0); }
341     // public uint torgba32() { return tocolbit(8, 0, 1, 2, 3); }
342     // public uint toargb64() { return tocolbit(16, 3, 0, 1, 2); }
343     // public uint toabgr64() { return tocolbit(16, 3, 2, 1, 0); }
344     // public uint torgba64() { return tocolbit(16, 0, 1, 2, 3); }
345 
346     private string _to_hex(float p_val) const {
347         int v = (p_val * 255).round.to!int;
348         v = v.clamp(0, 255);
349         string ret;
350 
351         for (int i = 0; i < 2; i++) {
352             char[2] c = [ 0, 0 ];
353             int lv = v & 0xF;
354             if (lv < 10) {
355                 c[0] = ('0' + lv).to!char;
356             } else {
357                 c[0] = ('a' + lv - 10).to!char;
358             }
359 
360             v >>= 4;
361             string cs = c.to!string;
362             ret = cs ~ ret;
363         }
364 
365         return ret;
366     }
367 
368     /** 
369     Returns html representation of color in format `#RRGGBB`
370     If `p_alpha` is true returns color in format `#RRGGBBAA`
371     Params:
372       p_alpha = Include alpha?
373     Returns: Html string
374     */
375     public string toHtml(bool p_alpha = false) const {
376         string txt;
377         txt ~= _to_hex(data[0]);
378         txt ~= _to_hex(data[1]);
379         txt ~= _to_hex(data[2]);
380         if (p_alpha) {
381             txt ~= _to_hex(data[3]);
382         }
383         return txt;
384     }
385 
386     /** 
387     Returns hex representation of color in format `0xrrggbb`
388     Returns: uint hex
389     */
390     public uint toHex() {
391         int r = rint(data[0] * 255);
392         int g = rint(data[1] * 255);
393         int b = rint(data[2] * 255);
394         return ((r & 0xff) << 16) + ((g & 0xff) << 8) + (b & 0xff);
395     }
396 
397     /* -------------------------------------------------------------------------- */
398     /*                                   SETTERS                                  */
399     /* -------------------------------------------------------------------------- */
400 
401     private void setHex(uint p_hex, uint c_mask, uint c_bits, bool c_hasAlpha) {
402         float c_size = (2.pow(c_bits) - 1).to!float;
403         uint c_shift = c_bits;
404         float a = 0;
405         if (c_hasAlpha) {
406             a = (p_hex & c_mask) / c_size;
407             p_hex >>= c_shift;
408         }
409         float b = (p_hex & c_mask) / c_size;
410         p_hex >>= c_shift;
411         float g = (p_hex & c_mask) / c_size;
412         p_hex >>= c_shift;
413         float r = (p_hex & c_mask) / c_size;
414 
415         data = [r, g, b, a];
416     }
417 
418     /** 
419     Sets color to hexadecimal value
420     Params:
421       p_hex = uint hex value to set color to
422       p_hasAlpha = Does p_hex include alpha
423     */
424     public void setHex(uint p_hex, bool p_hasAlpha = false) { setHex(p_hex, 0xFF, 8, p_hasAlpha); }
425     // public void setHex64(uint p_hex, bool p_hasAlpha = false) { setHex(p_hex, 0xFFFF, 16, p_hasAlpha); }
426 
427     /** 
428     Sets color from hsv
429     Params:
430       p_h = hue
431       p_s = saturation
432       p_v = value
433       p_alpha = alpha
434     */
435     public void setHsv(float p_h, float p_s, float p_v, float p_alpha = 1.0f) {
436         int i;
437         float f, p, q, t;
438         data[3] = p_alpha;
439 
440         if (p_s == 0) {
441             // Achromatic (grey)
442             data[0] = data[1] = data[2] = p_v;
443             return;
444         }
445 
446         p_h *= 6.0f;
447         p_h = p_h.fmod(6);
448         i = p_h.floor().to!int;
449 
450         f = p_h - i;
451         p = p_v * (1 - p_s);
452         q = p_v * (1 - p_s * f);
453         t = p_v * (1 - p_s * (1 - f));
454 
455         switch (i) {
456             case 0: // Red is the dominant color
457                 data[0] = p_v;
458                 data[1] = t;
459                 data[2] = p;
460                 break;
461             case 1: // Green is the dominant color
462                 data[0] = q;
463                 data[1] = p_v;
464                 data[2] = p;
465                 break;
466             case 2:
467                 data[0] = p;
468                 data[1] = p_v;
469                 data[2] = t;
470                 break;
471             case 3: // Blue is the dominant color
472                 data[0] = p;
473                 data[1] = q;
474                 data[2] = p_v;
475                 break;
476             case 4:
477                 data[0] = t;
478                 data[1] = p;
479                 data[2] = p_v;
480                 break;
481             default: // (5) Red is the dominant color
482                 data[0] = p_v;
483                 data[1] = p;
484                 data[2] = q;
485                 break;
486         }
487     }
488 
489     /** 
490     Sets color from html string in format `#RRGGBB`
491     Params:
492       html = Color string
493     */
494     public void setHtml(string html) {
495         auto rg = regex(r"/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i");
496         auto m = matchAll(html, rg);
497         m.popFront();
498         float _r = to!int(m.front.hit, 16) / 255; m.popFront();
499         float _g = to!int(m.front.hit, 16) / 255; m.popFront();
500         float _b = to!int(m.front.hit, 16) / 255; m.popFront();
501         data = [to!float(_r), to!float(_g), to!float(_b), 1.0f];
502     }
503 
504     /* -------------------------------------------------------------------------- */
505     /*                               HSV PROPERTIES                               */
506     /* -------------------------------------------------------------------------- */
507 
508     /// Returns `h` component of hsv
509     public float hue() const {
510         float cmin = data[0].min(data[1]);
511         cmin = cmin.min(data[2]);
512         float cmax = data[0].max(data[1]);
513         cmax = cmax.min(data[2]);
514 
515         float delta = cmax - cmin;
516         if (delta == 0) { return 0; }
517 
518         float h;
519         if (data[0] == cmax) {
520             h = (data[1] - data[2]) / delta;
521         } else
522         if (data[1] == cmax) {
523             h = 2 + (data[2] - data[0]) / delta;
524         } else {
525             h = 4 + (data[2] - data[0]) / delta;
526         }
527 
528         h /= 6.0f;
529         if (h < 0) {
530             h += 1.0f;
531         }
532         return h;
533     }
534 
535     /// Returns `s` component of hsv
536     public float saturation() const {
537         float cmin = data[0].min(data[1]);
538         cmin = cmin.min(data[2]);
539         float cmax = data[0].max(data[1]);
540         cmax = cmax.min(data[2]);
541 
542         float delta = cmax - cmin;
543 
544         return (cmax != 0) ? (delta / cmax) : 0;
545     }
546 
547     /// Returns `v` component of hsv
548     public float value() const {
549         float cmax = data[0].max(data[1]);
550         cmax = cmax.max(data[2]);
551         return cmax;
552     }
553 
554     /* -------------------------------------------------------------------------- */
555     /*                                BASH GETTERS                                */
556     /* -------------------------------------------------------------------------- */
557 
558     /** 
559     Sets color from ANSI index
560     Params:
561       ansi = Color index
562     */
563     void setAnsi8(int ansi) {
564         if (ansi > 100) ansi -= 92;
565         if (ansi > 90) ansi -= 82;
566         if (ansi > 40) ansi -= 40;
567         if (ansi > 30) ansi -= 30;
568         if (ansi >= lowHEX.length) ansi = lowHEX.length.to!int - 1;
569         if (ansi < 0) ansi = 0;
570         // write(ansi);
571         setHex(lowHEX[ansi]);
572     }
573 
574     /** 
575     Sets color from ANSI256 index
576     Params:
577       ansi = Color index
578     */
579     void setAnsi(int ansi) {
580         if (ansi < 0 || ansi > 255) {
581             data = [0, 0, 0, 0];
582             return;
583         }
584         if (ansi < 16) {
585             setHex(Color.lowHEX[ansi]);
586             return;
587         }
588 
589         if (ansi > 231) {
590             const int s = (ansi - 232) * 10 + 8;
591             data = [s / 255.0f, s / 255.0f, s / 255.0f, 1];
592             return;
593         }
594 
595         const int n = ansi - 16;
596         int _b = n % 6;
597         int _g = (n - _b) / 6 % 6;
598         int _r = (n - _b - _g * 6) / 36 % 6;
599         _b = _b ? _b * 40 + 55 : 0;
600         _r = _r ? _r * 40 + 55 : 0;
601         _g = _g ? _g * 40 + 55 : 0;
602 
603         data = [_r / 255.0, _g / 255.0, _b / 255.0, 1];
604     }
605 
606     /* -------------------------------------------------------------------------- */
607     /*                                BASH SETTERS                                */
608     /* -------------------------------------------------------------------------- */
609     
610     private alias rint = (a) => to!int(round(a));
611 
612 
613     private float colorDistance(Color e1, Color e2) {
614         int rmean = ( cast(int) e1.r + cast(int) e2.r ) / 2;
615         int r = cast(int) e1.r - cast(int) e2.r;
616         int g = cast(int) e1.g - cast(int) e2.g;
617         int b = cast(int) e1.b - cast(int) e2.b;
618         // return sqrt(
619         //     (((512 + rmean) * r * r) >> 8) + 
620         //     4.0f * g * g + 
621         //     (((767 - rmean) * b * b) >> 8)
622         //     );
623         return sqrt((2 + rmean / 255) * r * r + 4 * g * g + (2 + (255 - rmean) / 255) * b * b + 0.0f);
624     }
625     
626     /** 
627     Returns closest ANSI8 color index 
628     Params:
629       isBackground = Is color a background
630     Returns: ANSI8 color
631     */
632     int toAnsi8(bool isBackground = false) {
633         /*
634         39 - default foreground, 49 - default backgound
635         Main colors are from 30 - 39, light variant is 90 - 97.
636         97 is white, 30 is black. to get background - add 10 to color code
637         goes like:
638         black, red, green, yellow, blue, magenta, cyan, lgray
639         then repeat with lighter variation
640         */
641 
642         int ri = rint(data[0] * 255);
643         int gi = rint(data[1] * 255);
644         int bi = rint(data[2] * 255);
645 
646         if (ri / 64 == gi / 64 && gi / 64 == bi / 64) {
647             // 0 128 192 255
648             if (ri <= 64) return 30 + (isBackground ? 10 : 0);
649             if (ri <= 160) return 90 + (isBackground ? 10 : 0);
650             if (ri <= 224) return 37 + (isBackground ? 10 : 0);
651             if (ri <= 255) return 97 + (isBackground ? 10 : 0);
652         }
653         
654         // int diff = 255 * 3;
655         float diff = 255 * 3;
656         int pos = 0;
657         // writeln(lowHEX[15] - hex);
658         for (int i = 0; i < 16; i ++) {
659             if (i == 0 || i == 7 || i == 8 || i == 15) continue;
660             int rh = lowRGB[i * 3];
661             int gh = lowRGB[i * 3 + 1];
662             int bh = lowRGB[i * 3 + 2];
663             // int rd = abs(rh - ri);
664             // int gd = abs(gh - gi);
665             // int bd = abs(bh - bi);
666             // int udiff = rd + gd + bd;
667             float udiff = colorDistance(col(ri, gi, bi), col(rh, gh, bh));
668             if (udiff < diff) {
669                 diff = udiff;
670                 pos = i;
671             }
672         }
673         // write(pos);
674 
675         int ansi = pos + 30;
676         if (pos >= 8) ansi += 60 - 8;
677         return ansi + (isBackground ? 10 : 0);
678     }
679 
680     /** 
681     Returns closest ANSI256 color index
682     */
683     int toAnsi() {
684         /*
685         256 ANSI color coding is:
686         0 - 14 Special colors, probably check by hand
687         goes like:
688         black, red, green, yellow, blue, magenta, cyan, lgray
689         then repeat with lighter variation
690         16 - 231 RGB colors with color coding like this:
691         Pure R component is on 16, 52, 88, 124, 160, 196. Aka map(r, comp)
692         B component is r +0..5
693         G component is rb +0,6,12,18,24,30 (but not 36 coz it's next red)
694         in end rgb coding, considering mcol = floor(col*5)
695         rgbansi = 16 + (16 * r) + (6 * g) + b;
696         232 - 255 Grayscale from dark to light
697         refer to https://misc.flogisoft.com/_media/bash/colors_format/256-colors.sh-v2.png
698         */
699 
700         float r = data[0];
701         float g = data[1];
702         float b = data[2];
703 
704         int ri = rint(data[0] * 255);
705         int gi = rint(data[1] * 255);
706         int bi = rint(data[2] * 255);
707 
708         if (ri / 16 == gi / 16 && gi / 16 == bi / 16) {
709             // handles grayscale
710             if (ri < 8) {
711                 return 16;
712             }
713 
714             if (ri > 248) {
715                 return 231;
716             }
717 
718             return rint(((ri - 8.0) / 247.0) * 24.0) + 232;
719         }
720 
721         int ansi = 16
722             + ( 36 * rint(r * 5)) 
723             + ( 6 *  rint(g * 5)) 
724             +        rint(b * 5);
725 
726         return ansi;
727     }
728 
729     /** 
730     Returns closest bash ANSI8 color string 
731     Params:
732       isBackground = Is color a background
733     Returns: Bash ANSI8 color string
734     */
735     string toAnsi8String(bool isBackground = false) {
736         return "\033[" ~ toAnsi8(isBackground).to!string ~ "m";
737     }
738 
739     /** 
740     Returns closest bash ANSI256 color string 
741     Params:
742       isBackground = Is color a background
743     Returns: Bash ANSI256 color string
744     */
745     string toAnsiString(bool isBackground = false) {
746         return (isBackground ? "\033[48;5;" : "\033[38;5;") ~ 
747             toAnsi().to!string ~ "m";
748     }
749 
750     /** 
751     Returns bash truecolor string
752     Params:
753       isBackground = Is color a background
754     Returns: Bash truecolor string
755     */
756     string toTrueColorString(bool isBackground = false) {
757         return (isBackground ? "\033[48;2;" : "\033[38;2;") ~ 
758             rint(data[0] * 255).to!string ~ ";" ~ 
759             rint(data[1] * 255).to!string ~ ";"  ~ 
760             rint(data[2] * 255).to!string  ~ "m";
761         
762     }
763 
764 
765     // TODO add colours from godot
766     // LINK https://github.com/godotengine/godot/blob/master/core/math/color.cpp
767     
768     private static const uint[] lowHEX = [
769     0x000000, 0x800000, 0x008000, 0x808000, 0x000080, 0x800080, 0x008080, 0xc0c0c0,
770     0x808080, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff
771     ];
772 
773     private static const ushort[] lowRGB = [
774         0, 0, 0,  128, 0, 0,  0, 128, 0,  128, 128, 0,  0, 0, 128,  128, 0, 128,  0, 128, 128,  192, 192, 192,
775         128, 128, 128,  255, 0, 0,  0, 255, 0,  255, 255, 0,  0, 0, 255,  255, 0, 255,  0, 255, 255,  255, 255, 255
776     ];
777 }
778 
779 /// Enum containing most common web colors
780 enum Colors: Color {
781     aliceBlue            = Color8(240,248,255), /// <font color=aliceBlue>&#x25FC;</font>
782     antiqueWhite         = Color8(250,235,215), /// <font color=antiqueWhite>&#x25FC;</font>
783     aqua                 = Color8(0,255,255),   /// <font color=aqua>&#x25FC;</font>
784     aquamarine           = Color8(127,255,212), /// <font color=aquamarine>&#x25FC;</font>
785     azure                = Color8(240,255,255), /// <font color=azure>&#x25FC;</font>
786     beige                = Color8(245,245,220), /// <font color=beige>&#x25FC;</font>
787     bisque               = Color8(255,228,196), /// <font color=bisque>&#x25FC;</font>
788     black                = Color8(0,0,0),       /// <font color=black>&#x25FC;</font>
789     blanchedAlmond       = Color8(255,235,205), /// <font color=blanchedAlmond>&#x25FC;</font>
790     blue                 = Color8(0,0,255),     /// <font color=blue>&#x25FC;</font>
791     blueViolet           = Color8(138,43,226),  /// <font color=blueViolet>&#x25FC;</font>
792     brown                = Color8(165,42,42),   /// <font color=brown>&#x25FC;</font>
793     burlyWood            = Color8(222,184,135), /// <font color=burlyWood>&#x25FC;</font>
794     cadetBlue            = Color8(95,158,160),  /// <font color=cadetBlue>&#x25FC;</font>
795     chartreuse           = Color8(127,255,0),   /// <font color=chartreuse>&#x25FC;</font>
796     chocolate            = Color8(210,105,30),  /// <font color=chocolate>&#x25FC;</font>
797     coral                = Color8(255,127,80),  /// <font color=coral>&#x25FC;</font>
798     cornflowerBlue       = Color8(100,149,237), /// <font color=cornflowerBlue>&#x25FC;</font>
799     cornsilk             = Color8(255,248,220), /// <font color=cornsilk>&#x25FC;</font>
800     crimson              = Color8(220,20,60),   /// <font color=crimson>&#x25FC;</font>
801     cyan                 = Color8(0,255,255),   /// <font color=cyan>&#x25FC;</font>
802     darkBlue             = Color8(0,0,139),     /// <font color=darkBlue>&#x25FC;</font>
803     darkCyan             = Color8(0,139,139),   /// <font color=darkCyan>&#x25FC;</font>
804     darkGoldenrod        = Color8(184,134,11),  /// <font color=darkGoldenrod>&#x25FC;</font>
805     darkGray             = Color8(169,169,169), /// <font color=darkGray>&#x25FC;</font>
806     darkGrey             = Color8(169,169,169), /// <font color=darkGrey>&#x25FC;</font>
807     darkGreen            = Color8(0,100,0),     /// <font color=darkGreen>&#x25FC;</font>
808     darkKhaki            = Color8(189,183,107), /// <font color=darkKhaki>&#x25FC;</font>
809     darkMagenta          = Color8(139,0,139),   /// <font color=darkMagenta>&#x25FC;</font>
810     darkOliveGreen       = Color8(85,107,47),   /// <font color=darkOliveGreen>&#x25FC;</font>
811     darkOrange           = Color8(255,140,0),   /// <font color=darkOrange>&#x25FC;</font>
812     darkOrchid           = Color8(153,50,204),  /// <font color=darkOrchid>&#x25FC;</font>
813     darkRed              = Color8(139,0,0),     /// <font color=darkRed>&#x25FC;</font>
814     darkSalmon           = Color8(233,150,122), /// <font color=darkSalmon>&#x25FC;</font>
815     darkSeaGreen         = Color8(143,188,143), /// <font color=darkSeaGreen>&#x25FC;</font>
816     darkSlateBlue        = Color8(72,61,139),   /// <font color=darkSlateBlue>&#x25FC;</font>
817     darkSlateGray        = Color8(47,79,79),    /// <font color=darkSlateGray>&#x25FC;</font>
818     darkSlateGrey        = Color8(47,79,79),    /// <font color=darkSlateGrey>&#x25FC;</font>
819     darkTurquoise        = Color8(0,206,209),   /// <font color=darkTurquoise>&#x25FC;</font>
820     darkViolet           = Color8(148,0,211),   /// <font color=darkViolet>&#x25FC;</font>
821     deepPink             = Color8(255,20,147),  /// <font color=deepPink>&#x25FC;</font>
822     deepSkyBlue          = Color8(0,191,255),   /// <font color=deepSkyBlue>&#x25FC;</font>
823     dimGray              = Color8(105,105,105), /// <font color=dimGray>&#x25FC;</font>
824     dimGrey              = Color8(105,105,105), /// <font color=dimGrey>&#x25FC;</font>
825     dodgerBlue           = Color8(30,144,255),  /// <font color=dodgerBlue>&#x25FC;</font>
826     fireBrick            = Color8(178,34,34),   /// <font color=fireBrick>&#x25FC;</font>
827     floralWhite          = Color8(255,250,240), /// <font color=floralWhite>&#x25FC;</font>
828     forestGreen          = Color8(34,139,34),   /// <font color=forestGreen>&#x25FC;</font>
829     fuchsia              = Color8(255,0,255),   /// <font color=fuchsia>&#x25FC;</font>
830     gainsboro            = Color8(220,220,220), /// <font color=gainsboro>&#x25FC;</font>
831     ghostWhite           = Color8(248,248,255), /// <font color=ghostWhite>&#x25FC;</font>
832     gold                 = Color8(255,215,0),   /// <font color=gold>&#x25FC;</font>
833     goldenrod            = Color8(218,165,32),  /// <font color=goldenrod>&#x25FC;</font>
834     gray                 = Color8(128,128,128), /// <font color=gray>&#x25FC;</font>
835     grey                 = Color8(128,128,128), /// <font color=grey>&#x25FC;</font>
836     green                = Color8(0,128,0),     /// <font color=green>&#x25FC;</font>
837     greenYellow          = Color8(173,255,47),  /// <font color=greenYellow>&#x25FC;</font>
838     honeydew             = Color8(240,255,240), /// <font color=honeydew>&#x25FC;</font>
839     hotPink              = Color8(255,105,180), /// <font color=hotPink>&#x25FC;</font>
840     indianRed            = Color8(205,92,92),   /// <font color=indianRed>&#x25FC;</font>
841     indigo               = Color8(75,0,130),    /// <font color=indigo>&#x25FC;</font>
842     ivory                = Color8(255,255,240), /// <font color=ivory>&#x25FC;</font>
843     khaki                = Color8(240,230,140), /// <font color=khaki>&#x25FC;</font>
844     lavender             = Color8(230,230,250), /// <font color=lavender>&#x25FC;</font>
845     lavenderBlush        = Color8(255,240,245), /// <font color=lavenderBlush>&#x25FC;</font>
846     lawnGreen            = Color8(124,252,0),   /// <font color=lawnGreen>&#x25FC;</font>
847     lemonChiffon         = Color8(255,250,205), /// <font color=lemonChiffon>&#x25FC;</font>
848     lightBlue            = Color8(173,216,230), /// <font color=lightBlue>&#x25FC;</font>
849     lightCoral           = Color8(240,128,128), /// <font color=lightCoral>&#x25FC;</font>
850     lightCyan            = Color8(224,255,255), /// <font color=lightCyan>&#x25FC;</font>
851     lightGoldenrodYellow = Color8(250,250,210), /// <font color=lightGoldenrodYellow>&#x25FC;</font>
852     lightGray            = Color8(211,211,211), /// <font color=lightGray>&#x25FC;</font>
853     lightGrey            = Color8(211,211,211), /// <font color=lightGrey>&#x25FC;</font>
854     lightGreen           = Color8(144,238,144), /// <font color=lightGreen>&#x25FC;</font>
855     lightPink            = Color8(255,182,193), /// <font color=lightPink>&#x25FC;</font>
856     lightSalmon          = Color8(255,160,122), /// <font color=lightSalmon>&#x25FC;</font>
857     lightSeaGreen        = Color8(32,178,170),  /// <font color=lightSeaGreen>&#x25FC;</font>
858     lightSkyBlue         = Color8(135,206,250), /// <font color=lightSkyBlue>&#x25FC;</font>
859     lightSlateGray       = Color8(119,136,153), /// <font color=lightSlateGray>&#x25FC;</font>
860     lightSlateGrey       = Color8(119,136,153), /// <font color=lightSlateGrey>&#x25FC;</font>
861     lightSteelBlue       = Color8(176,196,222), /// <font color=lightSteelBlue>&#x25FC;</font>
862     lightYellow          = Color8(255,255,224), /// <font color=lightYellow>&#x25FC;</font>
863     lime                 = Color8(0,255,0),     /// <font color=lime>&#x25FC;</font>
864     limeGreen            = Color8(50,205,50),   /// <font color=limeGreen>&#x25FC;</font>
865     linen                = Color8(250,240,230), /// <font color=linen>&#x25FC;</font>
866     magenta              = Color8(255,0,255),   /// <font color=magenta>&#x25FC;</font>
867     maroon               = Color8(128,0,0),     /// <font color=maroon>&#x25FC;</font>
868     mediumAquamarine     = Color8(102,205,170), /// <font color=mediumAquamarine>&#x25FC;</font>
869     mediumBlue           = Color8(0,0,205),     /// <font color=mediumBlue>&#x25FC;</font>
870     mediumOrchid         = Color8(186,85,211),  /// <font color=mediumOrchid>&#x25FC;</font>
871     mediumPurple         = Color8(147,112,219), /// <font color=mediumPurple>&#x25FC;</font>
872     mediumSeaGreen       = Color8(60,179,113),  /// <font color=mediumSeaGreen>&#x25FC;</font>
873     mediumSlateBlue      = Color8(123,104,238), /// <font color=mediumSlateBlue>&#x25FC;</font>
874     mediumSpringGreen    = Color8(0,250,154),   /// <font color=mediumSpringGreen>&#x25FC;</font>
875     mediumTurquoise      = Color8(72,209,204),  /// <font color=mediumTurquoise>&#x25FC;</font>
876     mediumVioletRed      = Color8(199,21,133),  /// <font color=mediumVioletRed>&#x25FC;</font>
877     midnightBlue         = Color8(25,25,112),   /// <font color=midnightBlue>&#x25FC;</font>
878     mintCream            = Color8(245,255,250), /// <font color=mintCream>&#x25FC;</font>
879     mistyRose            = Color8(255,228,225), /// <font color=mistyRose>&#x25FC;</font>
880     moccasin             = Color8(255,228,181), /// <font color=moccasin>&#x25FC;</font>
881     navajoWhite          = Color8(255,222,173), /// <font color=navajoWhite>&#x25FC;</font>
882     navy                 = Color8(0,0,128),     /// <font color=navy>&#x25FC;</font>
883     oldLace              = Color8(253,245,230), /// <font color=oldLace>&#x25FC;</font>
884     olive                = Color8(128,128,0),   /// <font color=olive>&#x25FC;</font>
885     oliveDrab            = Color8(107,142,35),  /// <font color=oliveDrab>&#x25FC;</font>
886     orange               = Color8(255,165,0),   /// <font color=orange>&#x25FC;</font>
887     orangeRed            = Color8(255,69,0),    /// <font color=orangeRed>&#x25FC;</font>
888     orchid               = Color8(218,112,214), /// <font color=orchid>&#x25FC;</font>
889     paleGoldenrod        = Color8(238,232,170), /// <font color=paleGoldenrod>&#x25FC;</font>
890     paleGreen            = Color8(152,251,152), /// <font color=paleGreen>&#x25FC;</font>
891     paleTurquoise        = Color8(175,238,238), /// <font color=paleTurquoise>&#x25FC;</font>
892     paleVioletRed        = Color8(219,112,147), /// <font color=paleVioletRed>&#x25FC;</font>
893     papayaWhip           = Color8(255,239,213), /// <font color=papayaWhip>&#x25FC;</font>
894     peachPuff            = Color8(255,218,185), /// <font color=peachPuff>&#x25FC;</font>
895     peru                 = Color8(205,133,63),  /// <font color=peru>&#x25FC;</font>
896     pink                 = Color8(255,192,203), /// <font color=pink>&#x25FC;</font>
897     plum                 = Color8(221,160,221), /// <font color=plum>&#x25FC;</font>
898     powderBlue           = Color8(176,224,230), /// <font color=powderBlue>&#x25FC;</font>
899     purple               = Color8(128,0,128),   /// <font color=purple>&#x25FC;</font>
900     red                  = Color8(255,0,0),     /// <font color=red>&#x25FC;</font>
901     rosyBrown            = Color8(188,143,143), /// <font color=rosyBrown>&#x25FC;</font>
902     royalBlue            = Color8(65,105,225),  /// <font color=royalBlue>&#x25FC;</font>
903     saddleBrown          = Color8(139,69,19),   /// <font color=saddleBrown>&#x25FC;</font>
904     salmon               = Color8(250,128,114), /// <font color=salmon>&#x25FC;</font>
905     sandyBrown           = Color8(244,164,96),  /// <font color=sandyBrown>&#x25FC;</font>
906     seaGreen             = Color8(46,139,87),   /// <font color=seaGreen>&#x25FC;</font>
907     seashell             = Color8(255,245,238), /// <font color=seashell>&#x25FC;</font>
908     sienna               = Color8(160,82,45),   /// <font color=sienna>&#x25FC;</font>
909     silver               = Color8(192,192,192), /// <font color=silver>&#x25FC;</font>
910     skyBlue              = Color8(135,206,235), /// <font color=skyBlue>&#x25FC;</font>
911     slateBlue            = Color8(106,90,205),  /// <font color=slateBlue>&#x25FC;</font>
912     slateGray            = Color8(112,128,144), /// <font color=slateGray>&#x25FC;</font>
913     slateGrey            = Color8(112,128,144), /// <font color=slateGrey>&#x25FC;</font>
914     snow                 = Color8(255,250,250), /// <font color=snow>&#x25FC;</font>
915     springGreen          = Color8(0,255,127),   /// <font color=springGreen>&#x25FC;</font>
916     steelBlue            = Color8(70,130,180),  /// <font color=steelBlue>&#x25FC;</font>
917     tan                  = Color8(210,180,140), /// <font color=tan>&#x25FC;</font>
918     teal                 = Color8(0,128,128),   /// <font color=teal>&#x25FC;</font>
919     thistle              = Color8(216,191,216), /// <font color=thistle>&#x25FC;</font>
920     tomato               = Color8(255,99,71),   /// <font color=tomato>&#x25FC;</font>
921     turquoise            = Color8(64,224,208),  /// <font color=turquoise>&#x25FC;</font>
922     violet               = Color8(238,130,238), /// <font color=violet>&#x25FC;</font>
923     wheat                = Color8(245,222,179), /// <font color=wheat>&#x25FC;</font>
924     white                = Color8(255,255,255), /// <font color=white>&#x25FC;</font>
925     whiteSmoke           = Color8(245,245,245), /// <font color=whiteSmoke>&#x25FC;</font>
926     yellow               = Color8(255,255,0),   /// <font color=yellow>&#x25FC;</font>
927     yellowGreen          = Color8(154,205,50)   /// <font color=yellowGreen>&#x25FC;</font>
928 }