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