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>◼</font> 782 antiqueWhite = Color8(250,235,215), /// <font color=antiqueWhite>◼</font> 783 aqua = Color8(0,255,255), /// <font color=aqua>◼</font> 784 aquamarine = Color8(127,255,212), /// <font color=aquamarine>◼</font> 785 azure = Color8(240,255,255), /// <font color=azure>◼</font> 786 beige = Color8(245,245,220), /// <font color=beige>◼</font> 787 bisque = Color8(255,228,196), /// <font color=bisque>◼</font> 788 black = Color8(0,0,0), /// <font color=black>◼</font> 789 blanchedAlmond = Color8(255,235,205), /// <font color=blanchedAlmond>◼</font> 790 blue = Color8(0,0,255), /// <font color=blue>◼</font> 791 blueViolet = Color8(138,43,226), /// <font color=blueViolet>◼</font> 792 brown = Color8(165,42,42), /// <font color=brown>◼</font> 793 burlyWood = Color8(222,184,135), /// <font color=burlyWood>◼</font> 794 cadetBlue = Color8(95,158,160), /// <font color=cadetBlue>◼</font> 795 chartreuse = Color8(127,255,0), /// <font color=chartreuse>◼</font> 796 chocolate = Color8(210,105,30), /// <font color=chocolate>◼</font> 797 coral = Color8(255,127,80), /// <font color=coral>◼</font> 798 cornflowerBlue = Color8(100,149,237), /// <font color=cornflowerBlue>◼</font> 799 cornsilk = Color8(255,248,220), /// <font color=cornsilk>◼</font> 800 crimson = Color8(220,20,60), /// <font color=crimson>◼</font> 801 cyan = Color8(0,255,255), /// <font color=cyan>◼</font> 802 darkBlue = Color8(0,0,139), /// <font color=darkBlue>◼</font> 803 darkCyan = Color8(0,139,139), /// <font color=darkCyan>◼</font> 804 darkGoldenrod = Color8(184,134,11), /// <font color=darkGoldenrod>◼</font> 805 darkGray = Color8(169,169,169), /// <font color=darkGray>◼</font> 806 darkGrey = Color8(169,169,169), /// <font color=darkGrey>◼</font> 807 darkGreen = Color8(0,100,0), /// <font color=darkGreen>◼</font> 808 darkKhaki = Color8(189,183,107), /// <font color=darkKhaki>◼</font> 809 darkMagenta = Color8(139,0,139), /// <font color=darkMagenta>◼</font> 810 darkOliveGreen = Color8(85,107,47), /// <font color=darkOliveGreen>◼</font> 811 darkOrange = Color8(255,140,0), /// <font color=darkOrange>◼</font> 812 darkOrchid = Color8(153,50,204), /// <font color=darkOrchid>◼</font> 813 darkRed = Color8(139,0,0), /// <font color=darkRed>◼</font> 814 darkSalmon = Color8(233,150,122), /// <font color=darkSalmon>◼</font> 815 darkSeaGreen = Color8(143,188,143), /// <font color=darkSeaGreen>◼</font> 816 darkSlateBlue = Color8(72,61,139), /// <font color=darkSlateBlue>◼</font> 817 darkSlateGray = Color8(47,79,79), /// <font color=darkSlateGray>◼</font> 818 darkSlateGrey = Color8(47,79,79), /// <font color=darkSlateGrey>◼</font> 819 darkTurquoise = Color8(0,206,209), /// <font color=darkTurquoise>◼</font> 820 darkViolet = Color8(148,0,211), /// <font color=darkViolet>◼</font> 821 deepPink = Color8(255,20,147), /// <font color=deepPink>◼</font> 822 deepSkyBlue = Color8(0,191,255), /// <font color=deepSkyBlue>◼</font> 823 dimGray = Color8(105,105,105), /// <font color=dimGray>◼</font> 824 dimGrey = Color8(105,105,105), /// <font color=dimGrey>◼</font> 825 dodgerBlue = Color8(30,144,255), /// <font color=dodgerBlue>◼</font> 826 fireBrick = Color8(178,34,34), /// <font color=fireBrick>◼</font> 827 floralWhite = Color8(255,250,240), /// <font color=floralWhite>◼</font> 828 forestGreen = Color8(34,139,34), /// <font color=forestGreen>◼</font> 829 fuchsia = Color8(255,0,255), /// <font color=fuchsia>◼</font> 830 gainsboro = Color8(220,220,220), /// <font color=gainsboro>◼</font> 831 ghostWhite = Color8(248,248,255), /// <font color=ghostWhite>◼</font> 832 gold = Color8(255,215,0), /// <font color=gold>◼</font> 833 goldenrod = Color8(218,165,32), /// <font color=goldenrod>◼</font> 834 gray = Color8(128,128,128), /// <font color=gray>◼</font> 835 grey = Color8(128,128,128), /// <font color=grey>◼</font> 836 green = Color8(0,128,0), /// <font color=green>◼</font> 837 greenYellow = Color8(173,255,47), /// <font color=greenYellow>◼</font> 838 honeydew = Color8(240,255,240), /// <font color=honeydew>◼</font> 839 hotPink = Color8(255,105,180), /// <font color=hotPink>◼</font> 840 indianRed = Color8(205,92,92), /// <font color=indianRed>◼</font> 841 indigo = Color8(75,0,130), /// <font color=indigo>◼</font> 842 ivory = Color8(255,255,240), /// <font color=ivory>◼</font> 843 khaki = Color8(240,230,140), /// <font color=khaki>◼</font> 844 lavender = Color8(230,230,250), /// <font color=lavender>◼</font> 845 lavenderBlush = Color8(255,240,245), /// <font color=lavenderBlush>◼</font> 846 lawnGreen = Color8(124,252,0), /// <font color=lawnGreen>◼</font> 847 lemonChiffon = Color8(255,250,205), /// <font color=lemonChiffon>◼</font> 848 lightBlue = Color8(173,216,230), /// <font color=lightBlue>◼</font> 849 lightCoral = Color8(240,128,128), /// <font color=lightCoral>◼</font> 850 lightCyan = Color8(224,255,255), /// <font color=lightCyan>◼</font> 851 lightGoldenrodYellow = Color8(250,250,210), /// <font color=lightGoldenrodYellow>◼</font> 852 lightGray = Color8(211,211,211), /// <font color=lightGray>◼</font> 853 lightGrey = Color8(211,211,211), /// <font color=lightGrey>◼</font> 854 lightGreen = Color8(144,238,144), /// <font color=lightGreen>◼</font> 855 lightPink = Color8(255,182,193), /// <font color=lightPink>◼</font> 856 lightSalmon = Color8(255,160,122), /// <font color=lightSalmon>◼</font> 857 lightSeaGreen = Color8(32,178,170), /// <font color=lightSeaGreen>◼</font> 858 lightSkyBlue = Color8(135,206,250), /// <font color=lightSkyBlue>◼</font> 859 lightSlateGray = Color8(119,136,153), /// <font color=lightSlateGray>◼</font> 860 lightSlateGrey = Color8(119,136,153), /// <font color=lightSlateGrey>◼</font> 861 lightSteelBlue = Color8(176,196,222), /// <font color=lightSteelBlue>◼</font> 862 lightYellow = Color8(255,255,224), /// <font color=lightYellow>◼</font> 863 lime = Color8(0,255,0), /// <font color=lime>◼</font> 864 limeGreen = Color8(50,205,50), /// <font color=limeGreen>◼</font> 865 linen = Color8(250,240,230), /// <font color=linen>◼</font> 866 magenta = Color8(255,0,255), /// <font color=magenta>◼</font> 867 maroon = Color8(128,0,0), /// <font color=maroon>◼</font> 868 mediumAquamarine = Color8(102,205,170), /// <font color=mediumAquamarine>◼</font> 869 mediumBlue = Color8(0,0,205), /// <font color=mediumBlue>◼</font> 870 mediumOrchid = Color8(186,85,211), /// <font color=mediumOrchid>◼</font> 871 mediumPurple = Color8(147,112,219), /// <font color=mediumPurple>◼</font> 872 mediumSeaGreen = Color8(60,179,113), /// <font color=mediumSeaGreen>◼</font> 873 mediumSlateBlue = Color8(123,104,238), /// <font color=mediumSlateBlue>◼</font> 874 mediumSpringGreen = Color8(0,250,154), /// <font color=mediumSpringGreen>◼</font> 875 mediumTurquoise = Color8(72,209,204), /// <font color=mediumTurquoise>◼</font> 876 mediumVioletRed = Color8(199,21,133), /// <font color=mediumVioletRed>◼</font> 877 midnightBlue = Color8(25,25,112), /// <font color=midnightBlue>◼</font> 878 mintCream = Color8(245,255,250), /// <font color=mintCream>◼</font> 879 mistyRose = Color8(255,228,225), /// <font color=mistyRose>◼</font> 880 moccasin = Color8(255,228,181), /// <font color=moccasin>◼</font> 881 navajoWhite = Color8(255,222,173), /// <font color=navajoWhite>◼</font> 882 navy = Color8(0,0,128), /// <font color=navy>◼</font> 883 oldLace = Color8(253,245,230), /// <font color=oldLace>◼</font> 884 olive = Color8(128,128,0), /// <font color=olive>◼</font> 885 oliveDrab = Color8(107,142,35), /// <font color=oliveDrab>◼</font> 886 orange = Color8(255,165,0), /// <font color=orange>◼</font> 887 orangeRed = Color8(255,69,0), /// <font color=orangeRed>◼</font> 888 orchid = Color8(218,112,214), /// <font color=orchid>◼</font> 889 paleGoldenrod = Color8(238,232,170), /// <font color=paleGoldenrod>◼</font> 890 paleGreen = Color8(152,251,152), /// <font color=paleGreen>◼</font> 891 paleTurquoise = Color8(175,238,238), /// <font color=paleTurquoise>◼</font> 892 paleVioletRed = Color8(219,112,147), /// <font color=paleVioletRed>◼</font> 893 papayaWhip = Color8(255,239,213), /// <font color=papayaWhip>◼</font> 894 peachPuff = Color8(255,218,185), /// <font color=peachPuff>◼</font> 895 peru = Color8(205,133,63), /// <font color=peru>◼</font> 896 pink = Color8(255,192,203), /// <font color=pink>◼</font> 897 plum = Color8(221,160,221), /// <font color=plum>◼</font> 898 powderBlue = Color8(176,224,230), /// <font color=powderBlue>◼</font> 899 purple = Color8(128,0,128), /// <font color=purple>◼</font> 900 red = Color8(255,0,0), /// <font color=red>◼</font> 901 rosyBrown = Color8(188,143,143), /// <font color=rosyBrown>◼</font> 902 royalBlue = Color8(65,105,225), /// <font color=royalBlue>◼</font> 903 saddleBrown = Color8(139,69,19), /// <font color=saddleBrown>◼</font> 904 salmon = Color8(250,128,114), /// <font color=salmon>◼</font> 905 sandyBrown = Color8(244,164,96), /// <font color=sandyBrown>◼</font> 906 seaGreen = Color8(46,139,87), /// <font color=seaGreen>◼</font> 907 seashell = Color8(255,245,238), /// <font color=seashell>◼</font> 908 sienna = Color8(160,82,45), /// <font color=sienna>◼</font> 909 silver = Color8(192,192,192), /// <font color=silver>◼</font> 910 skyBlue = Color8(135,206,235), /// <font color=skyBlue>◼</font> 911 slateBlue = Color8(106,90,205), /// <font color=slateBlue>◼</font> 912 slateGray = Color8(112,128,144), /// <font color=slateGray>◼</font> 913 slateGrey = Color8(112,128,144), /// <font color=slateGrey>◼</font> 914 snow = Color8(255,250,250), /// <font color=snow>◼</font> 915 springGreen = Color8(0,255,127), /// <font color=springGreen>◼</font> 916 steelBlue = Color8(70,130,180), /// <font color=steelBlue>◼</font> 917 tan = Color8(210,180,140), /// <font color=tan>◼</font> 918 teal = Color8(0,128,128), /// <font color=teal>◼</font> 919 thistle = Color8(216,191,216), /// <font color=thistle>◼</font> 920 tomato = Color8(255,99,71), /// <font color=tomato>◼</font> 921 turquoise = Color8(64,224,208), /// <font color=turquoise>◼</font> 922 violet = Color8(238,130,238), /// <font color=violet>◼</font> 923 wheat = Color8(245,222,179), /// <font color=wheat>◼</font> 924 white = Color8(255,255,255), /// <font color=white>◼</font> 925 whiteSmoke = Color8(245,245,245), /// <font color=whiteSmoke>◼</font> 926 yellow = Color8(255,255,0), /// <font color=yellow>◼</font> 927 yellowGreen = Color8(154,205,50) /// <font color=yellowGreen>◼</font> 928 }