1 /++ 2 Base module for working with colors and in-memory image pixmaps. 3 4 Also has various basic data type definitions that are generally 5 useful with images like [Point], [Size], and [Rectangle]. 6 +/ 7 module arsd.color; 8 9 import arsd.core; 10 11 @safe: 12 13 // importing phobos explodes the size of this code 10x, so not doing it. 14 15 private { 16 double toInternal(T)(scope const(char)[] s) { 17 double accumulator = 0.0; 18 size_t i = s.length; 19 foreach(idx, c; s) { 20 if(c >= '0' && c <= '9') { 21 accumulator *= 10; 22 accumulator += c - '0'; 23 } else if(c == '.') { 24 i = idx + 1; 25 break; 26 } else { 27 string wtfIsWrongWithThisStupidLanguageWithItsBrokenSafeAttribute = "bad char to make double from "; 28 wtfIsWrongWithThisStupidLanguageWithItsBrokenSafeAttribute ~= s; 29 throw new Exception(wtfIsWrongWithThisStupidLanguageWithItsBrokenSafeAttribute); 30 } 31 } 32 33 double accumulator2 = 0.0; 34 double count = 1; 35 foreach(c; s[i .. $]) { 36 if(c >= '0' && c <= '9') { 37 accumulator2 *= 10; 38 accumulator2 += c - '0'; 39 count *= 10; 40 } else { 41 string wtfIsWrongWithThisStupidLanguageWithItsBrokenSafeAttribute = "bad char to make double from "; 42 wtfIsWrongWithThisStupidLanguageWithItsBrokenSafeAttribute ~= s; 43 throw new Exception(wtfIsWrongWithThisStupidLanguageWithItsBrokenSafeAttribute); 44 } 45 } 46 47 return accumulator + accumulator2 / count; 48 } 49 50 package(arsd) @trusted 51 string toInternal(T)(long a) { 52 if(a == 0) 53 return "0"; 54 char[] ret; 55 bool neg; 56 if(a < 0) { 57 neg = true; 58 a = -a; 59 } 60 while(a) { 61 ret ~= (a % 10) + '0'; 62 a /= 10; 63 } 64 for(int i = 0; i < ret.length / 2; i++) { 65 char c = ret[i]; 66 ret[i] = ret[$ - i - 1]; 67 ret[$ - i - 1] = c; 68 } 69 if(neg) 70 ret = "-" ~ ret; 71 72 return cast(string) ret; 73 } 74 string toInternal(T)(double a) { 75 // a simplifying assumption here is the fact that we only use this in one place: toInternal!string(cast(double) a / 255) 76 // thus we know this will always be between 0.0 and 1.0, inclusive. 77 if(a <= 0.0) 78 return "0.0"; 79 if(a >= 1.0) 80 return "1.0"; 81 string ret = "0."; 82 // I wonder if I can handle round off error any better. Phobos does, but that isn't worth 100 KB of code. 83 int amt = cast(int)(a * 1000); 84 return ret ~ toInternal!string(amt); 85 } 86 87 nothrow @safe @nogc pure 88 double absInternal(double a) { return a < 0 ? -a : a; } 89 nothrow @safe @nogc pure 90 double minInternal(double a, double b, double c) { 91 auto m = a; 92 if(b < m) m = b; 93 if(c < m) m = c; 94 return m; 95 } 96 nothrow @safe @nogc pure 97 double maxInternal(double a, double b, double c) { 98 auto m = a; 99 if(b > m) m = b; 100 if(c > m) m = c; 101 return m; 102 } 103 nothrow @safe @nogc pure 104 bool startsWithInternal(in char[] a, in char[] b) { 105 return (a.length >= b.length && a[0 .. b.length] == b); 106 } 107 void splitInternal(scope inout(char)[] a, char c, scope void delegate(int, scope inout(char)[]) @safe dg) { 108 int count; 109 size_t previous = 0; 110 foreach(i, char ch; a) { 111 if(ch == c) { 112 dg(count++, a[previous .. i]); 113 previous = i + 1; 114 } 115 } 116 if(previous != a.length) 117 dg(count++, a[previous .. $]); 118 } 119 } 120 121 // done with mini-phobos 122 123 /// Represents an RGBA color 124 struct Color { 125 126 @system static Color fromJsVar(T)(T v) { // it is a template so i don't have to actually import arsd.jsvar... 127 return Color.fromString(v.get!string); 128 } 129 130 @safe: 131 132 ubyte[4] components; /// [r, g, b, a] 133 134 // The color components are available as individual bytes and a uint through this property functions. 135 // Unlike an anonymous union, this works with CTFE as well. 136 @safe pure nothrow @nogc { 137 // individual rgb components 138 pragma(inline, true) ref inout(ubyte) r() inout scope return { return components[0]; } /// red 139 pragma(inline, true) ref inout(ubyte) g() inout scope return { return components[1]; } /// green 140 pragma(inline, true) ref inout(ubyte) b() inout scope return { return components[2]; } /// blue 141 pragma(inline, true) ref inout(ubyte) a() inout scope return { return components[3]; } /// alpha. 255 == opaque 142 143 /* 144 pragma(inline, true) void r(ubyte value) { components[0] = value; } /// red 145 pragma(inline, true) void g(ubyte value) { components[1] = value; } /// green 146 pragma(inline, true) void b(ubyte value) { components[2] = value; } /// blue 147 pragma(inline, true) void a(ubyte value) { components[3] = value; } /// alpha. 255 == opaque 148 */ 149 150 /// The components as a single 32 bit value (beware of endian issues!) 151 uint asUint() inout { 152 if (__ctfe) { 153 version (LittleEndian) 154 return (r) | (g << 8) | (b << 16) | (a << 24); 155 else version (BigEndian) 156 return (r << 24) | (g << 16) | (b << 8) | (a); 157 else 158 static assert(false, "Unsupported endianness"); 159 } else { 160 return (cast(inout(uint)[1]) components)[0]; 161 } 162 } 163 164 /// ditto 165 void asUint(uint value) { 166 if (__ctfe) { 167 version (LittleEndian) { 168 r = (value & 0xFF); 169 g = ((value & 0xFF_00) >> 8); 170 b = ((value & 0xFF_00_00) >> 16); 171 a = ((value & 0xFF_00_00_00) >> 24); 172 } else version (BigEndian) { 173 r = ((value & 0xFF_00_00_00) >> 24); 174 g = ((value & 0xFF_00_00) >> 16); 175 b = ((value & 0xFF_00) >> 8); 176 a = (value & 0xFF); 177 } else { 178 static assert(false, "Unsupported endianness"); 179 } 180 } else { 181 components = function(uint value) @trusted { 182 return *(cast(ubyte[4]*) cast(void*) &value); 183 }(value); 184 } 185 } 186 187 /// ditto 188 uint opCast(T : uint)() inout { return this.asUint; } 189 } 190 191 /++ 192 Returns a value compatible with [https://docs.microsoft.com/en-us/windows/win32/gdi/colorref|a Win32 COLORREF]. 193 194 Please note that the alpha value is lost in translation. 195 196 History: 197 Added November 27, 2021 (dub v10.4) 198 See_Also: 199 [fromWindowsColorRef] 200 +/ 201 nothrow pure @nogc 202 uint asWindowsColorRef() { 203 uint cr; 204 cr |= b << 16; 205 cr |= g << 8; 206 cr |= r; 207 return cr; 208 } 209 210 /++ 211 Constructs a Color from [https://docs.microsoft.com/en-us/windows/win32/gdi/colorref|a Win32 COLORREF]. 212 213 History: 214 Added November 27, 2021 (dub v10.4) 215 See_Also: 216 [asWindowsColorRef] 217 +/ 218 nothrow pure @nogc 219 static Color fromWindowsColorRef(uint cr) { 220 return Color(cr & 0xff, (cr >> 8) & 0xff, (cr >> 16) & 0xff); 221 } 222 223 /++ 224 Like the constructor, but this makes sure they are in range before casting. If they are out of range, it saturates: anything less than zero becomes zero and anything greater than 255 becomes 255. 225 +/ 226 nothrow pure @nogc 227 static Color fromIntegers(int red, int green, int blue, int alpha = 255) { 228 return Color(clampToByte(red), clampToByte(green), clampToByte(blue), clampToByte(alpha)); 229 } 230 231 /// Construct a color with the given values. They should be in range 0 <= x <= 255, where 255 is maximum intensity and 0 is minimum intensity. 232 nothrow pure @nogc 233 this(int red, int green, int blue, int alpha = 255) { 234 this.r = cast(ubyte) red; 235 this.g = cast(ubyte) green; 236 this.b = cast(ubyte) blue; 237 this.a = cast(ubyte) alpha; 238 } 239 240 /++ 241 Construct a color from components[0 .. 4]. It must have length of at least 4 and be in r, g, b, a order. 242 243 History: 244 Added July 18, 2022 (dub v10.9) 245 +/ 246 nothrow pure @nogc 247 this(scope ubyte[] components) { 248 this.components[] = components[0 .. 4]; 249 } 250 251 /++ 252 Constructs a color from floating-point rgba components, each between 0 and 1.0. 253 254 History: 255 Added December 1, 2022 (dub v10.10) 256 +/ 257 this(float r, float g, float b, float a = 1.0) { 258 if(r < 0) r = 0; 259 if(g < 0) g = 0; 260 if(b < 0) b = 0; 261 if(r > 1) r = 1; 262 if(g > 1) g = 1; 263 if(b > 1) b = 1; 264 /* 265 assert(r >= 0.0 && r <= 1.0, to!string(r)); 266 assert(g >= 0.0 && g <= 1.0, to!string(g)); 267 assert(b >= 0.0 && b <= 1.0, to!string(b)); 268 assert(a >= 0.0 && a <= 1.0, to!string(a)); 269 */ 270 this.r = cast(ubyte) (r * 255); 271 this.g = cast(ubyte) (g * 255); 272 this.b = cast(ubyte) (b * 255); 273 this.a = cast(ubyte) (a * 255); 274 } 275 276 /++ 277 Constructs a color from a [ColorF] (floating-point) 278 279 History: 280 Added December 20, 2023 281 +/ 282 nothrow pure @nogc 283 this(const ColorF colorF) { 284 this.r = cast(ubyte) (colorF.r * 255); 285 this.g = cast(ubyte) (colorF.g * 255); 286 this.b = cast(ubyte) (colorF.b * 255); 287 this.a = cast(ubyte) (colorF.a * 255); 288 } 289 290 /// Static convenience functions for common color names 291 nothrow pure @nogc 292 static Color transparent() { return Color(0, 0, 0, 0); } 293 /// Ditto 294 nothrow pure @nogc 295 static Color white() { return Color(255, 255, 255); } 296 /// Ditto 297 nothrow pure @nogc 298 static Color gray() { return Color(128, 128, 128); } 299 /// Ditto 300 nothrow pure @nogc 301 static Color black() { return Color(0, 0, 0); } 302 /// Ditto 303 nothrow pure @nogc 304 static Color red() { return Color(255, 0, 0); } 305 /// Ditto 306 nothrow pure @nogc 307 static Color green() { return Color(0, 255, 0); } 308 /// Ditto 309 nothrow pure @nogc 310 static Color blue() { return Color(0, 0, 255); } 311 /// Ditto 312 nothrow pure @nogc 313 static Color yellow() { return Color(255, 255, 0); } 314 /// Ditto 315 nothrow pure @nogc 316 static Color teal() { return Color(0, 255, 255); } 317 /// Ditto 318 nothrow pure @nogc 319 static Color purple() { return Color(128, 0, 128); } 320 /// Ditto 321 nothrow pure @nogc 322 static Color magenta() { return Color(255, 0, 255); } 323 /// Ditto 324 nothrow pure @nogc 325 static Color brown() { return Color(128, 64, 0); } 326 327 nothrow pure @nogc 328 void premultiply() { 329 r = (r * a) / 255; 330 g = (g * a) / 255; 331 b = (b * a) / 255; 332 } 333 334 nothrow pure @nogc 335 void unPremultiply() { 336 r = cast(ubyte)(r * 255 / a); 337 g = cast(ubyte)(g * 255 / a); 338 b = cast(ubyte)(b * 255 / a); 339 } 340 341 342 /* 343 ubyte[4] toRgbaArray() { 344 return [r,g,b,a]; 345 } 346 */ 347 348 /// Return black-and-white color 349 Color toBW() () nothrow pure @safe @nogc { 350 // FIXME: gamma? 351 int intens = clampToByte(cast(int)(0.2126*r+0.7152*g+0.0722*b)); 352 return Color(intens, intens, intens, a); 353 } 354 355 /// Makes a string that matches CSS syntax for websites 356 string toCssString() const { 357 if(a == 255) 358 return "#" ~ toHexInternal(r) ~ toHexInternal(g) ~ toHexInternal(b); 359 else { 360 return "rgba("~toInternal!string(r)~", "~toInternal!string(g)~", "~toInternal!string(b)~", "~toInternal!string(cast(double)a / 255.0)~")"; 361 } 362 } 363 364 /// Makes a hex string RRGGBBAA (aa only present if it is not 255) 365 string toString() const { 366 if(a == 255) 367 return toCssString()[1 .. $]; 368 else 369 return toRgbaHexString(); 370 } 371 372 /// returns RRGGBBAA, even if a== 255 373 string toRgbaHexString() const { 374 return toHexInternal(r) ~ toHexInternal(g) ~ toHexInternal(b) ~ toHexInternal(a); 375 } 376 377 /// Gets a color by name, iff the name is one of the static members listed above 378 static Color fromNameString(string s) { 379 Color c; 380 foreach(member; __traits(allMembers, Color)) { 381 static if(__traits(compiles, c = __traits(getMember, Color, member))) { 382 if(s == member) 383 return __traits(getMember, Color, member); 384 } 385 } 386 throw new Exception("Unknown color " ~ s); 387 } 388 389 /++ 390 Reads a CSS style string to get the color. Understands #rrggbb, rgba(), hsl(), and rrggbbaa 391 392 History: 393 The short-form hex string parsing (`#fff`) was added on April 10, 2020. (v7.2.0) 394 +/ 395 static Color fromString(scope const(char)[] s) { 396 s = s.stripInternal(); 397 398 Color c; 399 c.a = 255; 400 401 // trying named colors via the static no-arg methods here 402 foreach(member; __traits(allMembers, Color)) { 403 static if(__traits(compiles, c = __traits(getMember, Color, member))) { 404 if(s == member) 405 return __traits(getMember, Color, member); 406 } 407 } 408 409 // try various notations borrowed from CSS (though a little extended) 410 411 // hsl(h,s,l,a) where h is degrees and s,l,a are 0 >= x <= 1.0 412 if(s.startsWithInternal("hsl(") || s.startsWithInternal("hsla(")) { 413 assert(s[$-1] == ')'); 414 s = s[s.startsWithInternal("hsl(") ? 4 : 5 .. $ - 1]; // the closing paren 415 416 double[3] hsl; 417 ubyte a = 255; 418 419 s.splitInternal(',', (int i, scope const(char)[] part) { 420 if(i < 3) 421 hsl[i] = toInternal!double(part.stripInternal); 422 else 423 a = clampToByte(cast(int) (toInternal!double(part.stripInternal) * 255)); 424 }); 425 426 c = .fromHsl(hsl); 427 c.a = a; 428 429 return c; 430 } 431 432 // rgb(r,g,b,a) where r,g,b are 0-255 and a is 0-1.0 433 if(s.startsWithInternal("rgb(") || s.startsWithInternal("rgba(")) { 434 assert(s[$-1] == ')'); 435 s = s[s.startsWithInternal("rgb(") ? 4 : 5 .. $ - 1]; // the closing paren 436 437 s.splitInternal(',', (int i, scope const(char)[] part) { 438 // lol the loop-switch pattern 439 auto v = toInternal!double(part.stripInternal); 440 switch(i) { 441 case 0: // red 442 c.r = clampToByte(cast(int) v); 443 break; 444 case 1: 445 c.g = clampToByte(cast(int) v); 446 break; 447 case 2: 448 c.b = clampToByte(cast(int) v); 449 break; 450 case 3: 451 c.a = clampToByte(cast(int) (v * 255)); 452 break; 453 default: // ignore 454 } 455 }); 456 457 return c; 458 } 459 460 461 462 463 // otherwise let's try it as a hex string, really loosely 464 465 if(s.length && s[0] == '#') 466 s = s[1 .. $]; 467 468 // support short form #fff for example 469 if(s.length == 3 || s.length == 4) { 470 string n; 471 n.reserve(8); 472 foreach(ch; s) { 473 n ~= ch; 474 n ~= ch; 475 } 476 s = n; 477 } 478 479 // not a built in... do it as a hex string 480 if(s.length >= 2) { 481 c.r = fromHexInternal(s[0 .. 2]); 482 s = s[2 .. $]; 483 } 484 if(s.length >= 2) { 485 c.g = fromHexInternal(s[0 .. 2]); 486 s = s[2 .. $]; 487 } 488 if(s.length >= 2) { 489 c.b = fromHexInternal(s[0 .. 2]); 490 s = s[2 .. $]; 491 } 492 if(s.length >= 2) { 493 c.a = fromHexInternal(s[0 .. 2]); 494 s = s[2 .. $]; 495 } 496 497 return c; 498 } 499 500 /// from hsl 501 static Color fromHsl(double h, double s, double l) { 502 return .fromHsl(h, s, l); 503 } 504 505 // this is actually branch-less for ints on x86, and even for longs on x86_64 506 static ubyte clampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) { 507 static if (__VERSION__ > 2067) pragma(inline, true); 508 static if (T.sizeof == 2 || T.sizeof == 4) { 509 static if (__traits(isUnsigned, T)) { 510 return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24))); 511 } else { 512 n &= -cast(int)(n >= 0); 513 return cast(ubyte)(n|((255-cast(int)n)>>31)); 514 } 515 } else static if (T.sizeof == 1) { 516 static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?"); 517 return cast(ubyte)n; 518 } else static if (T.sizeof == 8) { 519 static if (__traits(isUnsigned, T)) { 520 return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56))); 521 } else { 522 n &= -cast(long)(n >= 0); 523 return cast(ubyte)(n|((255-cast(long)n)>>63)); 524 } 525 } else { 526 static assert(false, "clampToByte: integer too big"); 527 } 528 } 529 530 /** this mixin can be used to alphablend two `uint` colors; 531 * `colu32name` is variable that holds color to blend, 532 * `destu32name` is variable that holds "current" color (from surface, for example). 533 * alpha value of `destu32name` doesn't matter. 534 * alpha value of `colu32name` means: 255 for replace color, 0 for keep `destu32name`. 535 * 536 * WARNING! This function does blending in RGB space, and RGB space is not linear! 537 */ 538 public enum ColorBlendMixinStr(string colu32name, string destu32name) = "{ 539 immutable uint a_tmp_ = (256-(255-(("~colu32name~")>>24)))&(-(1-(((255-(("~colu32name~")>>24))+1)>>8))); // to not lose bits, but 255 should become 0 540 immutable uint dc_tmp_ = ("~destu32name~")&0xffffff; 541 immutable uint srb_tmp_ = (("~colu32name~")&0xff00ff); 542 immutable uint sg_tmp_ = (("~colu32name~")&0x00ff00); 543 immutable uint drb_tmp_ = (dc_tmp_&0xff00ff); 544 immutable uint dg_tmp_ = (dc_tmp_&0x00ff00); 545 immutable uint orb_tmp_ = (drb_tmp_+(((srb_tmp_-drb_tmp_)*a_tmp_+0x800080)>>8))&0xff00ff; 546 immutable uint og_tmp_ = (dg_tmp_+(((sg_tmp_-dg_tmp_)*a_tmp_+0x008000)>>8))&0x00ff00; 547 ("~destu32name~") = (orb_tmp_|og_tmp_)|0xff000000; /*&0xffffff;*/ 548 }"; 549 550 551 /// Perform alpha-blending of `fore` to this color, return new color. 552 /// WARNING! This function does blending in RGB space, and RGB space is not linear! 553 Color alphaBlend (Color fore) const pure nothrow @trusted @nogc { 554 version(LittleEndian) { 555 static if (__VERSION__ > 2067) pragma(inline, true); 556 Color res; 557 res.asUint = asUint; 558 mixin(ColorBlendMixinStr!("fore.asUint", "res.asUint")); 559 return res; 560 } else { 561 alias foreground = fore; 562 alias background = this; 563 foreach(idx, ref part; foreground.components) 564 part = cast(ubyte) (part * foreground.a / 255 + background.components[idx] * (255 - foreground.a) / 255); 565 return foreground; 566 } 567 } 568 } 569 570 /++ 571 Represents an RGBA color in floating-point (from 0 to 1.0). 572 573 $(NOTE 574 Most of the time, you’ll probably want to use [Color] instead. 575 576 This primarily exists to provide a tested out-of-the-box solution 577 when utilizing APIs that work with FP colors. 578 579 Constructors and setters come with $(B `in`-contracts) 580 to assert one won’t run into out-of-range color values. 581 ) 582 583 History: 584 Added December 20, 2023 585 +/ 586 struct ColorF { 587 588 private float[4] _components; 589 590 @safe pure nothrow @nogc: 591 592 /// 593 public this(const float[4] components) 594 in(isValidComponent(components[0])) 595 in(isValidComponent(components[1])) 596 in(isValidComponent(components[2])) 597 in(isValidComponent(components[3])) { 598 _components = components; 599 } 600 601 /// ditto 602 public this(float r, float g, float b, float a = 1.0f) 603 in(isValidComponent(r)) 604 in(isValidComponent(g)) 605 in(isValidComponent(b)) 606 in(isValidComponent(a)) { 607 _components = [r, g, b, a]; 608 } 609 610 /++ 611 Constructs a FP color from an integer one 612 +/ 613 public this(const Color integer) { 614 _components[] = integer.components[] / 255.0f; 615 } 616 617 /// 618 float[4] components() inout { return _components; } 619 620 // component getters 621 float r() inout { return _components[0]; } /// red 622 float g() inout { return _components[1]; } /// green 623 float b() inout { return _components[2]; } /// blue 624 float a() inout { return _components[3]; } /// alpha 625 626 // component setters 627 void r(float v) in(isValidComponent(v)) { _components[0] = v; } /// red 628 void g(float v) in(isValidComponent(v)) { _components[1] = v; } /// green 629 void b(float v) in(isValidComponent(v)) { _components[2] = v; } /// blue 630 void a(float v) in(isValidComponent(v)) { _components[3] = v; } /// alpha 631 632 /// 633 static bool isValidComponent(const float v) { 634 return (v >= 0.0f && v <= 1.0f); 635 } 636 } 637 638 /++ 639 OKLab colorspace conversions to/from [Color]. See: [https://bottosson.github.io/posts/oklab/] 640 641 L = perceived lightness. From 0 to 1.0. 642 643 a = how green/red the color is. Apparently supposed to be from -.233887 to .276216 644 645 b = how blue/yellow the color is. Apparently supposed to be from -.311528 to 0.198570. 646 647 History: 648 Added December 1, 2022 (dub v10.10) 649 650 Bugs: 651 Seems to be some but i might just not understand what the result is supposed to be. 652 +/ 653 struct Lab { 654 float L = 0.0; 655 float a = 0.0; 656 float b = 0.0; 657 float alpha = 1.0; 658 659 float C() const { 660 import core.stdc.math; 661 return sqrtf(a * a + b * b); 662 } 663 664 float h() const { 665 import core.stdc.math; 666 return atan2f(b, a); 667 } 668 669 /++ 670 L's useful range is between 0 and 1.0 671 672 C's useful range is between 0 and 0.4 673 674 H can be 0 to 360 for degrees, or 0 to 2pi for radians. 675 +/ 676 static Lab fromLChDegrees(float L, float C, float h, float alpha = 1.0) { 677 return fromLChRadians(L, C, h * 3.14159265358979323f / 180.0f, alpha); 678 } 679 680 /// ditto 681 static Lab fromLChRadians(float L, float C, float h, float alpha = 1.0) { 682 import core.stdc.math; 683 // if(C > 0.4) C = 0.4; 684 return Lab(L, C * cosf(h), C * sinf(h), alpha); 685 } 686 } 687 688 /// ditto 689 Lab toOklab(Color c) { 690 import core.stdc.math; 691 692 // this algorithm requires linear sRGB 693 694 float f(float w) { 695 w = srbgToLinear(w); 696 if(w < 0) 697 w = 0; 698 if(w > 1) 699 w = 1; 700 return w; 701 } 702 703 float r = f(cast(float) c.r / 255); 704 float g = f(cast(float) c.g / 255); 705 float b = f(cast(float) c.b / 255); 706 707 float l = 0.4122214708f * r + 0.5363325363f * g + 0.0514459929f * b; 708 float m = 0.2119034982f * r + 0.6806995451f * g + 0.1073969566f * b; 709 float s = 0.0883024619f * r + 0.2817188376f * g + 0.6299787005f * b; 710 711 float l_ = cbrtf(l); 712 float m_ = cbrtf(m); 713 float s_ = cbrtf(s); 714 715 return Lab( 716 0.2104542553f*l_ + 0.7936177850f*m_ - 0.0040720468f*s_, 717 1.9779984951f*l_ - 2.4285922050f*m_ + 0.4505937099f*s_, 718 0.0259040371f*l_ + 0.7827717662f*m_ - 0.8086757660f*s_, 719 cast(float) c.a / 255 720 ); 721 } 722 723 /// ditto 724 Color fromOklab(Lab c) { 725 float l_ = c.L + 0.3963377774f * c.a + 0.2158037573f * c.b; 726 float m_ = c.L - 0.1055613458f * c.a - 0.0638541728f * c.b; 727 float s_ = c.L - 0.0894841775f * c.a - 1.2914855480f * c.b; 728 729 float l = l_*l_*l_; 730 float m = m_*m_*m_; 731 float s = s_*s_*s_; 732 733 float f(float w) { 734 w = linearToSrbg(w); 735 if(w < 0) 736 w = 0; 737 if(w > 1) 738 w = 1; 739 return w; 740 } 741 742 return Color( 743 f(+4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s), 744 f(-1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s), 745 f(-0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s), 746 c.alpha 747 ); 748 } 749 750 // from https://bottosson.github.io/posts/colorwrong/#what-can-we-do%3F 751 float linearToSrbg(float x) { // aka f 752 import core.stdc.math; 753 if (x >= 0.0031308) 754 return (1.055) * powf(x, (1.0/2.4)) - 0.055; 755 else 756 return 12.92 * x; 757 } 758 759 float srbgToLinear(float x) { // aka f_inv 760 import core.stdc.math; 761 if (x >= 0.04045) 762 return powf((x + 0.055)/(1 + 0.055), 2.4); 763 else 764 return x / 12.92; 765 } 766 767 /+ 768 float[3] colorToYCbCr(Color c) { 769 return matrixMultiply( 770 [ 771 +0.2126, +0.7152, +0.0722, 772 -0.1146, -0.3854, +0.5000, 773 +0.5000, -0.4542, -0.0458 774 ], 775 [float(c.r) / 255, float(c.g) / 255, float(c.b) / 255] 776 ); 777 } 778 779 Color YCbCrToColor(float Y, float Cb, float Cr) { 780 781 /* 782 Y = Y * 255; 783 Cb = Cb * 255; 784 Cr = Cr * 255; 785 786 int r = cast(int) (Y + 1.40200 * (Cr - 0x80)); 787 int g = cast(int) (Y - 0.34414 * (Cb - 0x80) - 0.71414 * (Cr - 0x80)); 788 int b = cast(int) (Y + 1.77200 * (Cb - 0x80)); 789 790 void clamp(ref int item, int min, int max) { 791 if(item < min) item = min; 792 if(item > max) item = max; 793 } 794 795 clamp(r, 0, 255); 796 clamp(g, 0, 255); 797 clamp(b, 0, 255); 798 return Color(r, g, b); 799 */ 800 801 float f(float w) { 802 if(w < 0 || w > 1) 803 return 0; 804 assert(w >= 0.0); 805 assert(w <= 1.0); 806 //w = linearToSrbg(w); 807 if(w < 0) 808 w = 0; 809 if(w > 1) 810 w = 1; 811 return w; 812 } 813 814 auto rgb = matrixMultiply( 815 [ 816 1, +0.0000, +1.5748, 817 1, -0.1873, -0.4681, 818 1, +1.8556, +0.0000 819 ], 820 [Y, Cb, Cr] 821 ); 822 823 return Color(f(rgb[0]), f(rgb[1]), f(rgb[2])); 824 } 825 826 private float[3] matrixMultiply(float[9] matrix, float[3] vector) { 827 return [ 828 matrix[0] * vector[0] + matrix[1] * vector[1] + matrix[2] * vector[2], 829 matrix[3] * vector[0] + matrix[4] * vector[1] + matrix[5] * vector[2], 830 matrix[6] * vector[0] + matrix[7] * vector[1] + matrix[8] * vector[2], 831 ]; 832 } 833 834 +/ 835 836 void premultiplyBgra(ubyte[] bgra) pure @nogc @safe nothrow in { assert(bgra.length == 4); } do { 837 auto a = bgra[3]; 838 839 bgra[2] = (bgra[2] * a) / 255; 840 bgra[1] = (bgra[1] * a) / 255; 841 bgra[0] = (bgra[0] * a) / 255; 842 } 843 844 void unPremultiplyRgba(ubyte[] rgba) pure @nogc @safe nothrow in { assert(rgba.length == 4); } do { 845 auto a = rgba[3]; 846 if(a == 0) 847 return; 848 849 rgba[0] = cast(ubyte)(rgba[0] * 255 / a); 850 rgba[1] = cast(ubyte)(rgba[1] * 255 / a); 851 rgba[2] = cast(ubyte)(rgba[2] * 255 / a); 852 } 853 854 unittest { 855 Color c = Color.fromString("#fff"); 856 assert(c == Color.white); 857 assert(c == Color.fromString("#ffffff")); 858 859 c = Color.fromString("#f0f"); 860 assert(c == Color.fromString("rgb(255, 0, 255)")); 861 } 862 863 nothrow @safe 864 private string toHexInternal(ubyte b) { 865 string s; 866 if(b < 16) 867 s ~= '0'; 868 else { 869 ubyte t = (b & 0xf0) >> 4; 870 if(t >= 10) 871 s ~= 'A' + t - 10; 872 else 873 s ~= '0' + t; 874 b &= 0x0f; 875 } 876 if(b >= 10) 877 s ~= 'A' + b - 10; 878 else 879 s ~= '0' + b; 880 881 return s; 882 } 883 884 nothrow @safe @nogc pure 885 private ubyte fromHexInternal(in char[] s) { 886 int result = 0; 887 888 int exp = 1; 889 //foreach(c; retro(s)) { // FIXME: retro doesn't work right in dtojs 890 foreach_reverse(c; s) { 891 if(c >= 'A' && c <= 'F') 892 result += exp * (c - 'A' + 10); 893 else if(c >= 'a' && c <= 'f') 894 result += exp * (c - 'a' + 10); 895 else if(c >= '0' && c <= '9') 896 result += exp * (c - '0'); 897 else 898 // throw new Exception("invalid hex character: " ~ cast(char) c); 899 return 0; 900 901 exp *= 16; 902 } 903 904 return cast(ubyte) result; 905 } 906 907 /// Converts hsl to rgb 908 Color fromHsl(real[3] hsl) nothrow pure @safe @nogc { 909 return fromHsl(cast(double) hsl[0], cast(double) hsl[1], cast(double) hsl[2]); 910 } 911 912 Color fromHsl(double[3] hsl) nothrow pure @safe @nogc { 913 return fromHsl(hsl[0], hsl[1], hsl[2]); 914 } 915 916 /// Converts hsl to rgb 917 Color fromHsl(double h, double s, double l, double a = 255) nothrow pure @safe @nogc { 918 h = h % 360; 919 920 double C = (1 - absInternal(2 * l - 1)) * s; 921 922 double hPrime = h / 60; 923 924 double X = C * (1 - absInternal(hPrime % 2 - 1)); 925 926 double r, g, b; 927 928 if(h is double.nan) 929 r = g = b = 0; 930 else if (hPrime >= 0 && hPrime < 1) { 931 r = C; 932 g = X; 933 b = 0; 934 } else if (hPrime >= 1 && hPrime < 2) { 935 r = X; 936 g = C; 937 b = 0; 938 } else if (hPrime >= 2 && hPrime < 3) { 939 r = 0; 940 g = C; 941 b = X; 942 } else if (hPrime >= 3 && hPrime < 4) { 943 r = 0; 944 g = X; 945 b = C; 946 } else if (hPrime >= 4 && hPrime < 5) { 947 r = X; 948 g = 0; 949 b = C; 950 } else if (hPrime >= 5 && hPrime < 6) { 951 r = C; 952 g = 0; 953 b = X; 954 } 955 956 double m = l - C / 2; 957 958 r += m; 959 g += m; 960 b += m; 961 962 return Color( 963 cast(int)(r * 255), 964 cast(int)(g * 255), 965 cast(int)(b * 255), 966 cast(int)(a)); 967 } 968 969 /// Assumes the input `u` is already between 0 and 1 fyi. 970 nothrow pure @safe @nogc 971 double srgbToLinearRgb(double u) { 972 if(u < 0.4045) 973 return u / 12.92; 974 else 975 return pow((u + 0.055) / 1.055, 2.4); 976 // return ((u + 0.055) / 1.055) ^^ 2.4; 977 } 978 979 // could use the ^^ operator but that drags in some phobos. In this case, the import is 980 // actually insignificant, it doesn't really impact compile time, but it does complicate 981 // the dmd -v | grep std check to confirm i didn't miss any. 982 private extern(C) nothrow pure @safe @nogc double pow(double, double); 983 984 /// Converts an RGB color into an HSL triplet. useWeightedLightness will try to get a better value for luminosity for the human eye, which is more sensitive to green than red and more to red than blue. If it is false, it just does average of the rgb. 985 double[3] toHsl(Color c, bool useWeightedLightness = false) nothrow pure @trusted @nogc { 986 double r1 = cast(double) c.r / 255; 987 double g1 = cast(double) c.g / 255; 988 double b1 = cast(double) c.b / 255; 989 990 double maxColor = maxInternal(r1, g1, b1); 991 double minColor = minInternal(r1, g1, b1); 992 993 double L = (maxColor + minColor) / 2 ; 994 if(useWeightedLightness) { 995 // the colors don't affect the eye equally 996 // this is a little more accurate than plain HSL numbers 997 L = 0.2126*srgbToLinearRgb(r1) + 0.7152*srgbToLinearRgb(g1) + 0.0722*srgbToLinearRgb(b1); 998 // maybe a better number is 299, 587, 114 999 } 1000 double S = 0; 1001 double H = 0; 1002 if(maxColor != minColor) { 1003 if(L < 0.5) { 1004 S = (maxColor - minColor) / (maxColor + minColor); 1005 } else { 1006 S = (maxColor - minColor) / (2.0 - maxColor - minColor); 1007 } 1008 if(r1 == maxColor) { 1009 H = (g1-b1) / (maxColor - minColor); 1010 } else if(g1 == maxColor) { 1011 H = 2.0 + (b1 - r1) / (maxColor - minColor); 1012 } else { 1013 H = 4.0 + (r1 - g1) / (maxColor - minColor); 1014 } 1015 } 1016 1017 H = H * 60; 1018 if(H < 0){ 1019 H += 360; 1020 } 1021 1022 return [H, S, L]; 1023 } 1024 1025 /// . 1026 Color lighten(Color c, double percentage) nothrow pure @safe @nogc { 1027 auto hsl = toHsl(c); 1028 hsl[2] *= (1 + percentage); 1029 if(hsl[2] > 1) 1030 hsl[2] = 1; 1031 return fromHsl(hsl); 1032 } 1033 1034 /// . 1035 Color darken(Color c, double percentage) nothrow pure @safe @nogc { 1036 auto hsl = toHsl(c); 1037 hsl[2] *= (1 - percentage); 1038 return fromHsl(hsl); 1039 } 1040 1041 /// for light colors, call darken. for dark colors, call lighten. 1042 /// The goal: get toward center grey. 1043 Color moderate(Color c, double percentage) nothrow pure @safe @nogc { 1044 auto hsl = toHsl(c); 1045 if(hsl[2] > 0.5) 1046 hsl[2] *= (1 - percentage); 1047 else { 1048 if(hsl[2] <= 0.01) // if we are given black, moderating it means getting *something* out 1049 hsl[2] = percentage; 1050 else 1051 hsl[2] *= (1 + percentage); 1052 } 1053 if(hsl[2] > 1) 1054 hsl[2] = 1; 1055 return fromHsl(hsl); 1056 } 1057 1058 /// the opposite of moderate. Make darks darker and lights lighter 1059 Color extremify(Color c, double percentage) nothrow pure @safe @nogc { 1060 auto hsl = toHsl(c, true); 1061 if(hsl[2] < 0.5) 1062 hsl[2] *= (1 - percentage); 1063 else 1064 hsl[2] *= (1 + percentage); 1065 if(hsl[2] > 1) 1066 hsl[2] = 1; 1067 return fromHsl(hsl); 1068 } 1069 1070 /// Move around the lightness wheel, trying not to break on moderate things 1071 Color oppositeLightness(Color c) nothrow pure @safe @nogc { 1072 auto hsl = toHsl(c); 1073 1074 auto original = hsl[2]; 1075 1076 if(original > 0.4 && original < 0.6) 1077 hsl[2] = 0.8 - original; // so it isn't quite the same 1078 else 1079 hsl[2] = 1 - original; 1080 1081 return fromHsl(hsl); 1082 } 1083 1084 /// Try to determine a text color - either white or black - based on the input 1085 Color makeTextColor(Color c) nothrow pure @safe @nogc { 1086 auto hsl = toHsl(c, true); // give green a bonus for contrast 1087 if(hsl[2] > 0.71) 1088 return Color(0, 0, 0); 1089 else 1090 return Color(255, 255, 255); 1091 } 1092 1093 // These provide functional access to hsl manipulation; useful if you need a delegate 1094 1095 Color setLightness(Color c, double lightness) nothrow pure @safe @nogc { 1096 auto hsl = toHsl(c); 1097 hsl[2] = lightness; 1098 return fromHsl(hsl); 1099 } 1100 1101 1102 /// 1103 Color rotateHue(Color c, double degrees) nothrow pure @safe @nogc { 1104 auto hsl = toHsl(c); 1105 hsl[0] += degrees; 1106 return fromHsl(hsl); 1107 } 1108 1109 /// 1110 Color setHue(Color c, double hue) nothrow pure @safe @nogc { 1111 auto hsl = toHsl(c); 1112 hsl[0] = hue; 1113 return fromHsl(hsl); 1114 } 1115 1116 /// 1117 Color desaturate(Color c, double percentage) nothrow pure @safe @nogc { 1118 auto hsl = toHsl(c); 1119 hsl[1] *= (1 - percentage); 1120 return fromHsl(hsl); 1121 } 1122 1123 /// 1124 Color saturate(Color c, double percentage) nothrow pure @safe @nogc { 1125 auto hsl = toHsl(c); 1126 hsl[1] *= (1 + percentage); 1127 if(hsl[1] > 1) 1128 hsl[1] = 1; 1129 return fromHsl(hsl); 1130 } 1131 1132 /// 1133 Color setSaturation(Color c, double saturation) nothrow pure @safe @nogc { 1134 auto hsl = toHsl(c); 1135 hsl[1] = saturation; 1136 return fromHsl(hsl); 1137 } 1138 1139 1140 /* 1141 void main(string[] args) { 1142 auto color1 = toHsl(Color(255, 0, 0)); 1143 auto color = fromHsl(color1[0] + 60, color1[1], color1[2]); 1144 1145 writefln("#%02x%02x%02x", color.r, color.g, color.b); 1146 } 1147 */ 1148 1149 /* Color algebra functions */ 1150 1151 /* Alpha putpixel looks like this: 1152 1153 void putPixel(Image i, Color c) { 1154 Color b; 1155 b.r = i.data[(y * i.width + x) * bpp + 0]; 1156 b.g = i.data[(y * i.width + x) * bpp + 1]; 1157 b.b = i.data[(y * i.width + x) * bpp + 2]; 1158 b.a = i.data[(y * i.width + x) * bpp + 3]; 1159 1160 float ca = cast(float) c.a / 255; 1161 1162 i.data[(y * i.width + x) * bpp + 0] = alpha(c.r, ca, b.r); 1163 i.data[(y * i.width + x) * bpp + 1] = alpha(c.g, ca, b.g); 1164 i.data[(y * i.width + x) * bpp + 2] = alpha(c.b, ca, b.b); 1165 i.data[(y * i.width + x) * bpp + 3] = alpha(c.a, ca, b.a); 1166 } 1167 1168 ubyte alpha(ubyte c1, float alpha, ubyte onto) { 1169 auto got = (1 - alpha) * onto + alpha * c1; 1170 1171 if(got > 255) 1172 return 255; 1173 return cast(ubyte) got; 1174 } 1175 1176 So, given the background color and the resultant color, what was 1177 composited on to it? 1178 */ 1179 1180 /// 1181 ubyte unalpha(ubyte colorYouHave, float alpha, ubyte backgroundColor) nothrow pure @safe @nogc { 1182 // resultingColor = (1-alpha) * backgroundColor + alpha * answer 1183 auto resultingColorf = cast(float) colorYouHave; 1184 auto backgroundColorf = cast(float) backgroundColor; 1185 1186 auto answer = (resultingColorf - backgroundColorf + alpha * backgroundColorf) / alpha; 1187 return Color.clampToByte(cast(int) answer); 1188 } 1189 1190 /// 1191 ubyte makeAlpha(ubyte colorYouHave, ubyte backgroundColor/*, ubyte foreground = 0x00*/) nothrow pure @safe @nogc { 1192 //auto foregroundf = cast(float) foreground; 1193 auto foregroundf = 0.00f; 1194 auto colorYouHavef = cast(float) colorYouHave; 1195 auto backgroundColorf = cast(float) backgroundColor; 1196 1197 // colorYouHave = backgroundColorf - alpha * backgroundColorf + alpha * foregroundf 1198 auto alphaf = 1 - colorYouHave / backgroundColorf; 1199 alphaf *= 255; 1200 1201 return Color.clampToByte(cast(int) alphaf); 1202 } 1203 1204 1205 int fromHex(string s) { 1206 int result = 0; 1207 1208 int exp = 1; 1209 // foreach(c; retro(s)) { 1210 foreach_reverse(c; s) { 1211 if(c >= 'A' && c <= 'F') 1212 result += exp * (c - 'A' + 10); 1213 else if(c >= 'a' && c <= 'f') 1214 result += exp * (c - 'a' + 10); 1215 else if(c >= '0' && c <= '9') 1216 result += exp * (c - '0'); 1217 else 1218 throw new Exception("invalid hex character: " ~ cast(char) c); 1219 1220 exp *= 16; 1221 } 1222 1223 return result; 1224 } 1225 1226 /// 1227 Color colorFromString(string s) { 1228 if(s.length == 0) 1229 return Color(0,0,0,255); 1230 if(s[0] == '#') 1231 s = s[1..$]; 1232 assert(s.length == 6 || s.length == 8); 1233 1234 Color c; 1235 1236 c.r = cast(ubyte) fromHex(s[0..2]); 1237 c.g = cast(ubyte) fromHex(s[2..4]); 1238 c.b = cast(ubyte) fromHex(s[4..6]); 1239 if(s.length == 8) 1240 c.a = cast(ubyte) fromHex(s[6..8]); 1241 else 1242 c.a = 255; 1243 1244 return c; 1245 } 1246 1247 /* 1248 import browser.window; 1249 void main() { 1250 import browser.document; 1251 foreach(ele; document.querySelectorAll("input")) { 1252 ele.addEventListener("change", { 1253 auto h = toInternal!double(document.querySelector("input[name=h]").value); 1254 auto s = toInternal!double(document.querySelector("input[name=s]").value); 1255 auto l = toInternal!double(document.querySelector("input[name=l]").value); 1256 1257 Color c = Color.fromHsl(h, s, l); 1258 1259 auto e = document.getElementById("example"); 1260 e.style.backgroundColor = c.toCssString(); 1261 1262 // JSElement __js_this; 1263 // __js_this.style.backgroundColor = c.toCssString(); 1264 }, false); 1265 } 1266 } 1267 */ 1268 1269 1270 1271 /** 1272 This provides two image classes and a bunch of functions that work on them. 1273 1274 Why are they separate classes? I think the operations on the two of them 1275 are necessarily different. There's a whole bunch of operations that only 1276 really work on truecolor (blurs, gradients), and a few that only work 1277 on indexed images (palette swaps). 1278 1279 Even putpixel is pretty different. On indexed, it is a palette entry's 1280 index number. On truecolor, it is the actual color. 1281 1282 A greyscale image is the weird thing in the middle. It is truecolor, but 1283 fits in the same size as indexed. Still, I'd say it is a specialization 1284 of truecolor. 1285 1286 There is a subset that works on both 1287 1288 */ 1289 1290 /// An image in memory 1291 interface MemoryImage { 1292 //IndexedImage convertToIndexedImage() const; 1293 //TrueColorImage convertToTrueColor() const; 1294 1295 /// gets it as a TrueColorImage. May return this or may do a conversion and return a new image 1296 TrueColorImage getAsTrueColorImage() pure nothrow @safe; 1297 1298 /// Image width, in pixels 1299 int width() const pure nothrow @safe @nogc; 1300 1301 /// Image height, in pixels 1302 int height() const pure nothrow @safe @nogc; 1303 1304 /// Get image pixel. Slow, but returns valid RGBA color (completely transparent for off-image pixels). 1305 Color getPixel(int x, int y) const pure nothrow @safe @nogc; 1306 1307 /// Set image pixel. 1308 void setPixel(int x, int y, in Color clr) nothrow @safe; 1309 1310 /// Returns a copy of the image 1311 MemoryImage clone() const pure nothrow @safe; 1312 1313 /// Load image from file. This will import arsd.image to do the actual work, and cost nothing if you don't use it. 1314 static MemoryImage fromImage(T : const(char)[]) (T filename) @trusted { 1315 static if (__traits(compiles, (){import arsd.image;})) { 1316 // yay, we have image loader here, try it! 1317 import arsd.image; 1318 return loadImageFromFile(filename); 1319 } else { 1320 static assert(0, "please provide 'arsd.image' to load images!"); 1321 } 1322 } 1323 1324 // ***This method is deliberately not publicly documented.*** 1325 // What it does is unconditionally frees internal image storage, without any sanity checks. 1326 // If you will do this, make sure that you have no references to image data left (like 1327 // slices of [data] array, for example). Those references will become invalid, and WILL 1328 // lead to Undefined Behavior. 1329 // tl;dr: IF YOU HAVE *ANY* QUESTIONS REGARDING THIS COMMENT, DON'T USE THIS! 1330 // Note to implementors: it is safe to simply do nothing in this method. 1331 // Also, it should be safe to call this method twice or more. 1332 void clearInternal () nothrow @system;// @nogc; // nogc is commented right now just because GC.free is only @nogc in newest dmd and i want to stay compatible a few versions back too. it can be added later 1333 1334 /// Convenient alias for `fromImage` 1335 alias fromImageFile = fromImage; 1336 } 1337 1338 /// An image that consists of indexes into a color palette. Use [getAsTrueColorImage]() if you don't care about palettes 1339 class IndexedImage : MemoryImage { 1340 bool hasAlpha; 1341 1342 /// . 1343 Color[] palette; 1344 /// the data as indexes into the palette. Stored left to right, top to bottom, no padding. 1345 ubyte[] data; 1346 1347 override void clearInternal () nothrow @system {// @nogc { 1348 import core.memory : GC; 1349 // it is safe to call [GC.free] with `null` pointer. 1350 GC.free(GC.addrOf(palette.ptr)); palette = null; 1351 GC.free(GC.addrOf(data.ptr)); data = null; 1352 _width = _height = 0; 1353 } 1354 1355 /// . 1356 override int width() const pure nothrow @safe @nogc { 1357 return _width; 1358 } 1359 1360 /// . 1361 override int height() const pure nothrow @safe @nogc { 1362 return _height; 1363 } 1364 1365 /// . 1366 override IndexedImage clone() const pure nothrow @trusted { 1367 auto n = new IndexedImage(width, height); 1368 n.data[] = this.data[]; // the data member is already there, so array copy 1369 n.palette = this.palette.dup; // and here we need to allocate too, so dup 1370 n.hasAlpha = this.hasAlpha; 1371 return n; 1372 } 1373 1374 override Color getPixel(int x, int y) const pure nothrow @trusted @nogc { 1375 if (x >= 0 && y >= 0 && x < _width && y < _height) { 1376 size_t pos = cast(size_t)y*_width+x; 1377 if (pos >= data.length) return Color(0, 0, 0, 0); 1378 ubyte b = data.ptr[pos]; 1379 if (b >= palette.length) return Color(0, 0, 0, 0); 1380 return palette.ptr[b]; 1381 } else { 1382 return Color(0, 0, 0, 0); 1383 } 1384 } 1385 1386 override void setPixel(int x, int y, in Color clr) nothrow @trusted { 1387 if (x >= 0 && y >= 0 && x < _width && y < _height) { 1388 size_t pos = cast(size_t)y*_width+x; 1389 if (pos >= data.length) return; 1390 ubyte pidx = findNearestColor(palette, clr); 1391 if (palette.length < 255 && 1392 (palette.ptr[pidx].r != clr.r || palette.ptr[pidx].g != clr.g || palette.ptr[pidx].b != clr.b || palette.ptr[pidx].a != clr.a)) { 1393 // add new color 1394 pidx = addColor(clr); 1395 } 1396 data.ptr[pos] = pidx; 1397 } 1398 } 1399 1400 private int _width; 1401 private int _height; 1402 1403 /// . 1404 this(int w, int h) pure nothrow @safe { 1405 _width = w; 1406 _height = h; 1407 1408 // ensure that the computed size does not exceed basic address space limits 1409 assert(cast(ulong)w * h <= size_t.max); 1410 // upcast to avoid overflow for images larger than 536 Mpix 1411 data = new ubyte[cast(size_t)w*h]; 1412 } 1413 1414 /* 1415 void resize(int w, int h, bool scale) { 1416 1417 } 1418 */ 1419 1420 /// returns a new image 1421 override TrueColorImage getAsTrueColorImage() pure nothrow @safe { 1422 return convertToTrueColor(); 1423 } 1424 1425 /// Creates a new TrueColorImage based on this data 1426 TrueColorImage convertToTrueColor() const pure nothrow @trusted { 1427 auto tci = new TrueColorImage(width, height); 1428 foreach(i, b; data) { 1429 tci.imageData.colors[i] = palette[b]; 1430 } 1431 return tci; 1432 } 1433 1434 /// Gets an exact match, if possible, adds if not. See also: the findNearestColor free function. 1435 ubyte getOrAddColor(Color c) nothrow @trusted { 1436 foreach(i, co; palette) { 1437 if(c == co) 1438 return cast(ubyte) i; 1439 } 1440 1441 return addColor(c); 1442 } 1443 1444 /// Number of colors currently in the palette (note: palette entries are not necessarily used in the image data) 1445 int numColors() const pure nothrow @trusted @nogc { 1446 return cast(int) palette.length; 1447 } 1448 1449 /// Adds an entry to the palette, returning its index 1450 ubyte addColor(Color c) nothrow @trusted { 1451 assert(palette.length < 256); 1452 if(c.a != 255) 1453 hasAlpha = true; 1454 palette ~= c; 1455 1456 return cast(ubyte) (palette.length - 1); 1457 } 1458 } 1459 1460 /// An RGBA array of image data. Use the free function quantize() to convert to an IndexedImage 1461 class TrueColorImage : MemoryImage { 1462 // bool hasAlpha; 1463 // bool isGreyscale; 1464 1465 //ubyte[] data; // stored as rgba quads, upper left to right to bottom 1466 /// . 1467 struct Data { 1468 ubyte[] bytes; /// the data as rgba bytes. Stored left to right, top to bottom, no padding. 1469 // the union is no good because the length of the struct is wrong! 1470 1471 /// the same data as Color structs 1472 @trusted // the cast here is typically unsafe, but it is ok 1473 // here because I guarantee the layout, note the static assert below 1474 @property inout(Color)[] colors() inout pure nothrow @nogc { 1475 return cast(inout(Color)[]) bytes; 1476 } 1477 1478 static assert(Color.sizeof == 4); 1479 } 1480 1481 /// . 1482 Data imageData; 1483 alias imageData.bytes data; 1484 1485 int _width; 1486 int _height; 1487 1488 override void clearInternal () nothrow @system {// @nogc { 1489 import core.memory : GC; 1490 // it is safe to call [GC.free] with `null` pointer. 1491 GC.free(GC.addrOf(imageData.bytes.ptr)); imageData.bytes = null; 1492 _width = _height = 0; 1493 } 1494 1495 /// . 1496 override TrueColorImage clone() const pure nothrow @trusted { 1497 auto n = new TrueColorImage(width, height); 1498 n.imageData.bytes[] = this.imageData.bytes[]; // copy into existing array ctor allocated 1499 return n; 1500 } 1501 1502 /// . 1503 override int width() const pure nothrow @trusted @nogc { return _width; } 1504 ///. 1505 override int height() const pure nothrow @trusted @nogc { return _height; } 1506 1507 override Color getPixel(int x, int y) const pure nothrow @trusted @nogc { 1508 if (x >= 0 && y >= 0 && x < _width && y < _height) { 1509 size_t pos = cast(size_t)y*_width+x; 1510 return imageData.colors.ptr[pos]; 1511 } else { 1512 return Color(0, 0, 0, 0); 1513 } 1514 } 1515 1516 override void setPixel(int x, int y, in Color clr) nothrow @trusted { 1517 if (x >= 0 && y >= 0 && x < _width && y < _height) { 1518 size_t pos = cast(size_t)y*_width+x; 1519 if (pos < imageData.bytes.length/4) imageData.colors.ptr[pos] = clr; 1520 } 1521 } 1522 1523 /// . 1524 this(int w, int h) pure nothrow @safe { 1525 _width = w; 1526 _height = h; 1527 1528 // ensure that the computed size does not exceed basic address space limits 1529 assert(cast(ulong)w * h * 4 <= size_t.max); 1530 // upcast to avoid overflow for images larger than 536 Mpix 1531 imageData.bytes = new ubyte[cast(size_t)w * h * 4]; 1532 } 1533 1534 /// Creates with existing data. The data pointer is stored here. 1535 this(int w, int h, ubyte[] data) pure nothrow @safe { 1536 _width = w; 1537 _height = h; 1538 assert(cast(ulong)w * h * 4 <= size_t.max); 1539 assert(data.length == cast(size_t)w * h * 4); 1540 imageData.bytes = data; 1541 } 1542 1543 /// Returns this 1544 override TrueColorImage getAsTrueColorImage() pure nothrow @safe { 1545 return this; 1546 } 1547 } 1548 1549 /+ 1550 /// An RGB array of image data. 1551 class TrueColorImageWithoutAlpha : MemoryImage { 1552 struct Data { 1553 ubyte[] bytes; // the data as rgba bytes. Stored left to right, top to bottom, no padding. 1554 } 1555 1556 /// . 1557 Data imageData; 1558 1559 int _width; 1560 int _height; 1561 1562 override void clearInternal () nothrow @system {// @nogc { 1563 import core.memory : GC; 1564 // it is safe to call [GC.free] with `null` pointer. 1565 GC.free(imageData.bytes.ptr); imageData.bytes = null; 1566 _width = _height = 0; 1567 } 1568 1569 /// . 1570 override TrueColorImageWithoutAlpha clone() const pure nothrow @trusted { 1571 auto n = new TrueColorImageWithoutAlpha(width, height); 1572 n.imageData.bytes[] = this.imageData.bytes[]; // copy into existing array ctor allocated 1573 return n; 1574 } 1575 1576 /// . 1577 override int width() const pure nothrow @trusted @nogc { return _width; } 1578 ///. 1579 override int height() const pure nothrow @trusted @nogc { return _height; } 1580 1581 override Color getPixel(int x, int y) const pure nothrow @trusted @nogc { 1582 if (x >= 0 && y >= 0 && x < _width && y < _height) { 1583 uint pos = (y*_width+x) * 3; 1584 return Color(imageData.bytes[0], imageData.bytes[1], imageData.bytes[2], 255); 1585 } else { 1586 return Color(0, 0, 0, 0); 1587 } 1588 } 1589 1590 override void setPixel(int x, int y, in Color clr) nothrow @trusted { 1591 if (x >= 0 && y >= 0 && x < _width && y < _height) { 1592 uint pos = y*_width+x; 1593 //if (pos < imageData.bytes.length/4) imageData.colors.ptr[pos] = clr; 1594 // FIXME 1595 } 1596 } 1597 1598 /// . 1599 this(int w, int h) pure nothrow @safe { 1600 _width = w; 1601 _height = h; 1602 imageData.bytes = new ubyte[w*h*3]; 1603 } 1604 1605 /// Creates with existing data. The data pointer is stored here. 1606 this(int w, int h, ubyte[] data) pure nothrow @safe { 1607 _width = w; 1608 _height = h; 1609 assert(data.length == w * h * 3); 1610 imageData.bytes = data; 1611 } 1612 1613 /// 1614 override TrueColorImage getAsTrueColorImage() pure nothrow @safe { 1615 // FIXME 1616 //return this; 1617 } 1618 } 1619 +/ 1620 1621 1622 alias extern(C) int function(scope const void*, scope const void*) @system Comparator; 1623 @trusted void nonPhobosSort(T)(T[] obj, Comparator comparator) { 1624 import core.stdc.stdlib; 1625 qsort(obj.ptr, obj.length, typeof(obj[0]).sizeof, comparator); 1626 } 1627 1628 /// Converts true color to an indexed image. It uses palette as the starting point, adding entries 1629 /// until maxColors as needed. If palette is null, it creates a whole new palette. 1630 /// 1631 /// After quantizing the image, it applies a dithering algorithm. 1632 /// 1633 /// This is not written for speed. 1634 IndexedImage quantize(in TrueColorImage img, Color[] palette = null, in int maxColors = 256) 1635 // this is just because IndexedImage assumes ubyte palette values 1636 in { assert(maxColors <= 256); } 1637 do { 1638 int[Color] uses; 1639 foreach(pixel; img.imageData.colors) { 1640 if(auto i = pixel in uses) { 1641 (*i)++; 1642 } else { 1643 uses[pixel] = 1; 1644 } 1645 } 1646 1647 struct ColorUse { 1648 Color c; 1649 int uses; 1650 //string toString() { return c.toCssString() ~ " x " ~ to!string(uses); } 1651 int opCmp(ref const ColorUse co) const { 1652 return co.uses - uses; 1653 } 1654 extern(C) static int comparator(scope const void* lhs, scope const void* rhs) { 1655 return (cast(ColorUse*)rhs).uses - (cast(ColorUse*)lhs).uses; 1656 } 1657 } 1658 1659 ColorUse[] sorted; 1660 1661 foreach(color, count; uses) 1662 sorted ~= ColorUse(color, count); 1663 1664 uses = null; 1665 1666 nonPhobosSort(sorted, &ColorUse.comparator); 1667 // or, with phobos, but that adds 70ms to compile time 1668 //import std.algorithm.sorting : sort; 1669 //sort(sorted); 1670 1671 ubyte[Color] paletteAssignments; 1672 foreach(idx, entry; palette) 1673 paletteAssignments[entry] = cast(ubyte) idx; 1674 1675 // For the color assignments from the image, I do multiple passes, decreasing the acceptable 1676 // distance each time until we're full. 1677 1678 // This is probably really slow.... but meh it gives pretty good results. 1679 1680 auto ddiff = 32; 1681 outer: for(int d1 = 128; d1 >= 0; d1 -= ddiff) { 1682 auto minDist = d1*d1; 1683 if(d1 <= 64) 1684 ddiff = 16; 1685 if(d1 <= 32) 1686 ddiff = 8; 1687 foreach(possibility; sorted) { 1688 if(palette.length == maxColors) 1689 break; 1690 if(palette.length) { 1691 auto co = palette[findNearestColor(palette, possibility.c)]; 1692 auto pixel = possibility.c; 1693 1694 auto dr = cast(int) co.r - pixel.r; 1695 auto dg = cast(int) co.g - pixel.g; 1696 auto db = cast(int) co.b - pixel.b; 1697 1698 auto dist = dr*dr + dg*dg + db*db; 1699 // not good enough variety to justify an allocation yet 1700 if(dist < minDist) 1701 continue; 1702 } 1703 paletteAssignments[possibility.c] = cast(ubyte) palette.length; 1704 palette ~= possibility.c; 1705 } 1706 } 1707 1708 // Final pass: just fill in any remaining space with the leftover common colors 1709 while(palette.length < maxColors && sorted.length) { 1710 if(sorted[0].c !in paletteAssignments) { 1711 paletteAssignments[sorted[0].c] = cast(ubyte) palette.length; 1712 palette ~= sorted[0].c; 1713 } 1714 sorted = sorted[1 .. $]; 1715 } 1716 1717 1718 bool wasPerfect = true; 1719 auto newImage = new IndexedImage(img.width, img.height); 1720 newImage.palette = palette; 1721 foreach(idx, pixel; img.imageData.colors) { 1722 if(auto p = pixel in paletteAssignments) 1723 newImage.data[idx] = *p; 1724 else { 1725 // gotta find the closest one... 1726 newImage.data[idx] = findNearestColor(palette, pixel); 1727 wasPerfect = false; 1728 } 1729 } 1730 1731 if(!wasPerfect) 1732 floydSteinbergDither(newImage, img); 1733 1734 return newImage; 1735 } 1736 1737 /// Finds the best match for pixel in palette (currently by checking for minimum euclidean distance in rgb colorspace) 1738 ubyte findNearestColor(in Color[] palette, in Color pixel) nothrow pure @trusted @nogc { 1739 int best = 0; 1740 int bestDistance = int.max; 1741 foreach(pe, co; palette) { 1742 auto dr = cast(int) co.r - pixel.r; 1743 auto dg = cast(int) co.g - pixel.g; 1744 auto db = cast(int) co.b - pixel.b; 1745 int dist = dr*dr + dg*dg + db*db; 1746 1747 if(dist < bestDistance) { 1748 best = cast(int) pe; 1749 bestDistance = dist; 1750 } 1751 } 1752 1753 return cast(ubyte) best; 1754 } 1755 1756 /+ 1757 1758 // Quantizing and dithering test program 1759 1760 void main( ){ 1761 /* 1762 auto img = new TrueColorImage(256, 32); 1763 foreach(y; 0 .. img.height) { 1764 foreach(x; 0 .. img.width) { 1765 img.imageData.colors[x + y * img.width] = Color(x, y * (255 / img.height), 0); 1766 } 1767 } 1768 */ 1769 1770 TrueColorImage img; 1771 1772 { 1773 1774 import arsd.png; 1775 1776 struct P { 1777 ubyte[] range; 1778 void put(ubyte[] a) { range ~= a; } 1779 } 1780 1781 P range; 1782 import std.algorithm; // commented out 1783 1784 import std.stdio; // commented out 1785 writePngLazy(range, pngFromBytes(File("/home/me/nyesha.png").byChunk(4096)).byRgbaScanline.map!((line) { 1786 foreach(ref pixel; line.pixels) { 1787 continue; 1788 auto sum = cast(int) pixel.r + pixel.g + pixel.b; 1789 ubyte a = cast(ubyte)(sum / 3); 1790 pixel.r = a; 1791 pixel.g = a; 1792 pixel.b = a; 1793 } 1794 return line; 1795 })); 1796 1797 img = imageFromPng(readPng(range.range)).getAsTrueColorImage; 1798 1799 1800 } 1801 1802 1803 1804 auto qimg = quantize(img, null, 2); 1805 1806 import arsd.simpledisplay; 1807 auto win = new SimpleWindow(img.width, img.height * 3); 1808 auto painter = win.draw(); 1809 painter.drawImage(Point(0, 0), Image.fromMemoryImage(img)); 1810 painter.drawImage(Point(0, img.height), Image.fromMemoryImage(qimg)); 1811 floydSteinbergDither(qimg, img); 1812 painter.drawImage(Point(0, img.height * 2), Image.fromMemoryImage(qimg)); 1813 win.eventLoop(0); 1814 } 1815 +/ 1816 1817 /+ 1818 /// If the background is transparent, it simply erases the alpha channel. 1819 void removeTransparency(IndexedImage img, Color background) 1820 +/ 1821 1822 /// Perform alpha-blending of `fore` to this color, return new color. 1823 /// WARNING! This function does blending in RGB space, and RGB space is not linear! 1824 Color alphaBlend(Color foreground, Color background) pure nothrow @safe @nogc { 1825 //if(foreground.a == 255) 1826 //return foreground; 1827 if(foreground.a == 0) 1828 return background; // the other blend function always returns alpha 255, but if the foreground has nothing, we should keep the background the same so its antialiasing doesn't get smashed (assuming this is blending in like a png instead of on a framebuffer) 1829 1830 static if (__VERSION__ > 2067) pragma(inline, true); 1831 return background.alphaBlend(foreground); 1832 } 1833 1834 /* 1835 /// Reduces the number of colors in a palette. 1836 void reducePaletteSize(IndexedImage img, int maxColors = 16) { 1837 1838 } 1839 */ 1840 1841 // I think I did this wrong... but the results aren't too bad so the bug can't be awful. 1842 /// Dithers img in place to look more like original. 1843 void floydSteinbergDither(IndexedImage img, in TrueColorImage original) nothrow @trusted { 1844 assert(img.width == original.width); 1845 assert(img.height == original.height); 1846 1847 auto buffer = new Color[](original.imageData.colors.length); 1848 1849 int x, y; 1850 1851 foreach(idx, c; original.imageData.colors) { 1852 auto n = img.palette[img.data[idx]]; 1853 int errorR = cast(int) c.r - n.r; 1854 int errorG = cast(int) c.g - n.g; 1855 int errorB = cast(int) c.b - n.b; 1856 1857 void doit(int idxOffset, int multiplier) { 1858 // if(idx + idxOffset < buffer.length) 1859 buffer[idx + idxOffset] = Color.fromIntegers( 1860 c.r + multiplier * errorR / 16, 1861 c.g + multiplier * errorG / 16, 1862 c.b + multiplier * errorB / 16, 1863 c.a 1864 ); 1865 } 1866 1867 if((x+1) != original.width) 1868 doit(1, 7); 1869 if((y+1) != original.height) { 1870 if(x != 0) 1871 doit(-1 + img.width, 3); 1872 doit(img.width, 5); 1873 if(x+1 != original.width) 1874 doit(1 + img.width, 1); 1875 } 1876 1877 img.data[idx] = findNearestColor(img.palette, buffer[idx]); 1878 1879 x++; 1880 if(x == original.width) { 1881 x = 0; 1882 y++; 1883 } 1884 } 1885 } 1886 1887 // these are just really useful in a lot of places where the color/image functions are used, 1888 // so I want them available with Color 1889 /++ 1890 2D location point 1891 +/ 1892 struct Point { 1893 int x; /// x-coordinate (aka abscissa) 1894 int y; /// y-coordinate (aka ordinate) 1895 1896 pure const nothrow @safe: 1897 1898 Point opBinary(string op)(in Point rhs) @nogc { 1899 return Point(mixin("x" ~ op ~ "rhs.x"), mixin("y" ~ op ~ "rhs.y")); 1900 } 1901 1902 Point opBinary(string op)(int rhs) @nogc { 1903 return Point(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs")); 1904 } 1905 1906 Size opCast(T : Size)() inout @nogc { 1907 return Size(x, y); 1908 } 1909 1910 /++ 1911 Calculates the point of linear offset in a rectangle. 1912 1913 `Offset = 0` is assumed to be equivalent to `Point(0,0)`. 1914 1915 See_also: 1916 [linearOffset] is the inverse function. 1917 1918 History: 1919 Added October 05, 2024. 1920 +/ 1921 static Point fromLinearOffset(int linearOffset, int width) @nogc { 1922 const y = (linearOffset / width); 1923 const x = (linearOffset % width); 1924 return Point(x, y); 1925 } 1926 } 1927 1928 /// 1929 struct Size { 1930 int width; /// 1931 int height; /// 1932 1933 pure nothrow @safe: 1934 1935 /++ 1936 Rectangular surface area 1937 1938 Calculates the surface area of a rectangle with dimensions equivalent to the width and height of the size. 1939 +/ 1940 int area() const @nogc { return width * height; } 1941 1942 Point opCast(T : Point)() inout @nogc { 1943 return Point(width, height); 1944 } 1945 1946 // gonna leave this undocumented for now since it might be removed later 1947 /+ + 1948 Adding (and other arithmetic operations) two sizes together will operate on the width and height independently. So Size(2, 3) + Size(4, 5) will give you Size(6, 8). 1949 +/ 1950 Size opBinary(string op)(in Size rhs) const @nogc { 1951 return Size( 1952 mixin("width" ~ op ~ "rhs.width"), 1953 mixin("height" ~ op ~ "rhs.height"), 1954 ); 1955 } 1956 1957 Size opBinary(string op)(int rhs) const @nogc { 1958 return Size( 1959 mixin("width" ~ op ~ "rhs"), 1960 mixin("height" ~ op ~ "rhs"), 1961 ); 1962 } 1963 } 1964 1965 /++ 1966 Calculates the linear offset of a point 1967 from the start (0/0) of a rectangle. 1968 1969 This assumes that (0/0) is equivalent to offset `0`. 1970 Each step on the x-coordinate advances the resulting offset by `1`. 1971 Each step on the y-coordinate advances the resulting offset by `width`. 1972 1973 This function is only defined for the 1st quadrant, 1974 i.e. both coordinates (x and y) of `pos` are positive. 1975 1976 Returns: 1977 `y * width + x` 1978 1979 See_also: 1980 [Point.fromLinearOffset] is the inverse function. 1981 1982 History: 1983 Added December 19, 2023 (dub v11.4) 1984 +/ 1985 int linearOffset(const Point pos, const int width) @safe pure nothrow @nogc { 1986 return ((width * pos.y) + pos.x); 1987 } 1988 1989 /// ditto 1990 int linearOffset(const int width, const Point pos) @safe pure nothrow @nogc { 1991 return ((width * pos.y) + pos.x); 1992 } 1993 1994 /// 1995 struct Rectangle { 1996 int left; /// 1997 int top; /// 1998 int right; /// 1999 int bottom; /// 2000 2001 pure const nothrow @safe @nogc: 2002 2003 /// 2004 this(int left, int top, int right, int bottom) { 2005 this.left = left; 2006 this.top = top; 2007 this.right = right; 2008 this.bottom = bottom; 2009 } 2010 2011 /// 2012 this(in Point upperLeft, in Point lowerRight) { 2013 this(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 2014 } 2015 2016 /// 2017 this(in Point upperLeft, in Size size) { 2018 this(upperLeft.x, upperLeft.y, upperLeft.x + size.width, upperLeft.y + size.height); 2019 } 2020 2021 /// 2022 @property Point upperLeft() { 2023 return Point(left, top); 2024 } 2025 2026 /// 2027 @property Point upperRight() { 2028 return Point(right, top); 2029 } 2030 2031 /// 2032 @property Point lowerLeft() { 2033 return Point(left, bottom); 2034 } 2035 2036 /// 2037 @property Point lowerRight() { 2038 return Point(right, bottom); 2039 } 2040 2041 /// 2042 @property Point center() { 2043 return Point((right + left) / 2, (bottom + top) / 2); 2044 } 2045 2046 /// 2047 @property Size size() { 2048 return Size(width, height); 2049 } 2050 2051 /// 2052 @property int width() { 2053 return right - left; 2054 } 2055 2056 /// 2057 @property int height() { 2058 return bottom - top; 2059 } 2060 2061 /// Returns true if this rectangle entirely contains the other 2062 bool contains(in Rectangle r) { 2063 return contains(r.upperLeft) && contains(r.lowerRight); 2064 } 2065 2066 /// ditto 2067 bool contains(in Point p) { 2068 return (p.x >= left && p.x < right && p.y >= top && p.y < bottom); 2069 } 2070 2071 /// Returns true of the two rectangles at any point overlap 2072 bool overlaps(in Rectangle r) { 2073 // the -1 in here are because right and top are exclusive 2074 return !((right-1) < r.left || (r.right-1) < left || (bottom-1) < r.top || (r.bottom-1) < top); 2075 } 2076 2077 /++ 2078 Returns a Rectangle representing the intersection of this and the other given one. 2079 2080 History: 2081 Added July 1, 2021 2082 +/ 2083 Rectangle intersectionOf(in Rectangle r) { 2084 auto tmp = Rectangle(max(left, r.left), max(top, r.top), min(right, r.right), min(bottom, r.bottom)); 2085 if(tmp.left >= tmp.right || tmp.top >= tmp.bottom) 2086 tmp = Rectangle.init; 2087 2088 return tmp; 2089 } 2090 } 2091 2092 /++ 2093 A type to represent an angle, taking away ambiguity of if it wants degrees or radians. 2094 2095 --- 2096 Angle a = Angle.degrees(180); 2097 Angle b = Angle.radians(3.14159); 2098 2099 // note there might be slight changes in precision due to internal conversions 2100 --- 2101 2102 History: 2103 Added August 29, 2023 (dub v11.1) 2104 +/ 2105 struct Angle { 2106 private enum PI = 3.14159265358979; 2107 private float angle; 2108 2109 pure @nogc nothrow @safe: 2110 2111 private this(float angle) { 2112 this.angle = angle; 2113 } 2114 2115 /++ 2116 2117 +/ 2118 float degrees() const { 2119 return angle * 180.0 / PI; 2120 } 2121 2122 /// ditto 2123 static Angle degrees(float deg) { 2124 return Angle(deg * PI / 180.0); 2125 } 2126 2127 /// ditto 2128 float radians() const { 2129 return angle; 2130 } 2131 2132 /// ditto 2133 static Angle radians(float rad) { 2134 return Angle(rad); 2135 } 2136 2137 /++ 2138 The +, -, +=, and -= operators all work on the angles too. 2139 +/ 2140 Angle opBinary(string op : "+")(const Angle rhs) const { 2141 return Angle(this.angle + rhs.angle); 2142 } 2143 /// ditto 2144 Angle opBinary(string op : "-")(const Angle rhs) const { 2145 return Angle(this.angle + rhs.angle); 2146 } 2147 /// ditto 2148 Angle opOpAssign(string op : "+")(const Angle rhs) { 2149 return this.angle += rhs.angle; 2150 } 2151 /// ditto 2152 Angle opOpAssign(string op : "-")(const Angle rhs) { 2153 return this.angle -= rhs.angle; 2154 } 2155 2156 // maybe sin, cos, tan but meh you can .radians on them too. 2157 } 2158 2159 private int max(int a, int b) @nogc nothrow pure @safe { 2160 return a >= b ? a : b; 2161 } 2162 private int min(int a, int b) @nogc nothrow pure @safe { 2163 return a <= b ? a : b; 2164 } 2165 2166 /++ 2167 Implements a flood fill algorithm, like the bucket tool in 2168 MS Paint. 2169 2170 Note it assumes `what.length == width*height`. 2171 2172 Params: 2173 what = the canvas to work with, arranged as top to bottom, left to right elements 2174 width = the width of the canvas 2175 height = the height of the canvas 2176 target = the type to replace. You may pass the existing value if you want to do what Paint does 2177 replacement = the replacement value 2178 x = the x-coordinate to start the fill (think of where the user clicked in Paint) 2179 y = the y-coordinate to start the fill 2180 additionalCheck = A custom additional check to perform on each square before continuing. Returning true means keep flooding, returning false means stop. If null, it is not used. 2181 +/ 2182 void floodFill(T)( 2183 T[] what, int width, int height, // the canvas to inspect 2184 T target, T replacement, // fill params 2185 int x, int y, bool delegate(int x, int y) @safe additionalCheck) // the node 2186 2187 // in(what.length == width * height) // gdc doesn't support this syntax yet so not gonna use it until that comes out. 2188 { 2189 assert(what.length == width * height); // will use the contract above when gdc supports it 2190 2191 T node = what[y * width + x]; 2192 2193 if(target == replacement) return; 2194 2195 if(node != target) return; 2196 2197 if(additionalCheck is null) 2198 additionalCheck = (int, int) => true; 2199 2200 if(!additionalCheck(x, y)) 2201 return; 2202 2203 Point[] queue; 2204 2205 queue ~= Point(x, y); 2206 2207 while(queue.length) { 2208 auto n = queue[0]; 2209 queue = queue[1 .. $]; 2210 //queue.assumeSafeAppend(); // lol @safe breakage 2211 2212 auto w = n; 2213 int offset = cast(int) (n.y * width + n.x); 2214 auto e = n; 2215 auto eoffset = offset; 2216 w.x--; 2217 offset--; 2218 while(w.x >= 0 && what[offset] == target && additionalCheck(w.x, w.y)) { 2219 w.x--; 2220 offset--; 2221 } 2222 while(e.x < width && what[eoffset] == target && additionalCheck(e.x, e.y)) { 2223 e.x++; 2224 eoffset++; 2225 } 2226 2227 // to make it inclusive again 2228 w.x++; 2229 offset++; 2230 foreach(o ; offset .. eoffset) { 2231 what[o] = replacement; 2232 if(w.y && what[o - width] == target && additionalCheck(w.x, w.y)) 2233 queue ~= Point(w.x, w.y - 1); 2234 if(w.y + 1 < height && what[o + width] == target && additionalCheck(w.x, w.y)) 2235 queue ~= Point(w.x, w.y + 1); 2236 w.x++; 2237 } 2238 } 2239 2240 /+ 2241 what[y * width + x] = replacement; 2242 2243 if(x) 2244 floodFill(what, width, height, target, replacement, 2245 x - 1, y, additionalCheck); 2246 2247 if(x != width - 1) 2248 floodFill(what, width, height, target, replacement, 2249 x + 1, y, additionalCheck); 2250 2251 if(y) 2252 floodFill(what, width, height, target, replacement, 2253 x, y - 1, additionalCheck); 2254 2255 if(y != height - 1) 2256 floodFill(what, width, height, target, replacement, 2257 x, y + 1, additionalCheck); 2258 +/ 2259 } 2260 2261 // for scripting, so you can tag it without strictly needing to import arsd.jsvar 2262 enum arsd_jsvar_compatible = "arsd_jsvar_compatible";