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 ///
1912 struct Size {
1913 	int width; ///
1914 	int height; ///
1915 
1916 	pure nothrow @safe:
1917 
1918 	/++
1919 		Rectangular surface area
1920 
1921 		Calculates the surface area of a rectangle with dimensions equivalent to the width and height of the size.
1922 	 +/
1923 	int area() const @nogc { return width * height; }
1924 
1925 	Point opCast(T : Point)() inout @nogc {
1926 		return Point(width, height);
1927 	}
1928 
1929 	// gonna leave this undocumented for now since it might be removed later
1930 	/+ +
1931 		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).
1932 	+/
1933 	Size opBinary(string op)(in Size rhs) const @nogc {
1934 		return Size(
1935 			mixin("width" ~ op ~ "rhs.width"),
1936 			mixin("height" ~ op ~ "rhs.height"),
1937 		);
1938 	}
1939 
1940 	Size opBinary(string op)(int rhs) const @nogc {
1941 		return Size(
1942 			mixin("width" ~ op ~ "rhs"),
1943 			mixin("height" ~ op ~ "rhs"),
1944 		);
1945 	}
1946 }
1947 
1948 /++
1949 	Calculates the linear offset of a point
1950 	from the start (0/0) of a rectangle.
1951 
1952 	This assumes that (0/0) is equivalent to offset `0`.
1953 	Each step on the x-coordinate advances the resulting offset by `1`.
1954 	Each step on the y-coordinate advances the resulting offset by `width`.
1955 
1956 	This function is only defined for the 1st quadrant,
1957 	i.e. both coordinates (x and y) of `pos` are positive.
1958 
1959 	Returns:
1960 		`y * width + x`
1961 
1962 	History:
1963 		Added December 19, 2023 (dub v11.4)
1964  +/
1965 int linearOffset(const Point pos, const int width) @safe pure nothrow @nogc {
1966 	return ((width * pos.y) + pos.x);
1967 }
1968 
1969 /// ditto
1970 int linearOffset(const int width, const Point pos) @safe pure nothrow @nogc {
1971 	return ((width * pos.y) + pos.x);
1972 }
1973 
1974 ///
1975 struct Rectangle {
1976 	int left; ///
1977 	int top; ///
1978 	int right; ///
1979 	int bottom; ///
1980 
1981 	pure const nothrow @safe @nogc:
1982 
1983 	///
1984 	this(int left, int top, int right, int bottom) {
1985 		this.left = left;
1986 		this.top = top;
1987 		this.right = right;
1988 		this.bottom = bottom;
1989 	}
1990 
1991 	///
1992 	this(in Point upperLeft, in Point lowerRight) {
1993 		this(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
1994 	}
1995 
1996 	///
1997 	this(in Point upperLeft, in Size size) {
1998 		this(upperLeft.x, upperLeft.y, upperLeft.x + size.width, upperLeft.y + size.height);
1999 	}
2000 
2001 	///
2002 	@property Point upperLeft() {
2003 		return Point(left, top);
2004 	}
2005 
2006 	///
2007 	@property Point upperRight() {
2008 		return Point(right, top);
2009 	}
2010 
2011 	///
2012 	@property Point lowerLeft() {
2013 		return Point(left, bottom);
2014 	}
2015 
2016 	///
2017 	@property Point lowerRight() {
2018 		return Point(right, bottom);
2019 	}
2020 
2021 	///
2022 	@property Point center() {
2023 		return Point((right + left) / 2, (bottom + top) / 2);
2024 	}
2025 
2026 	///
2027 	@property Size size() {
2028 		return Size(width, height);
2029 	}
2030 
2031 	///
2032 	@property int width() {
2033 		return right - left;
2034 	}
2035 
2036 	///
2037 	@property int height() {
2038 		return bottom - top;
2039 	}
2040 
2041 	/// Returns true if this rectangle entirely contains the other
2042 	bool contains(in Rectangle r) {
2043 		return contains(r.upperLeft) && contains(r.lowerRight);
2044 	}
2045 
2046 	/// ditto
2047 	bool contains(in Point p) {
2048 		return (p.x >= left && p.x < right && p.y >= top && p.y < bottom);
2049 	}
2050 
2051 	/// Returns true of the two rectangles at any point overlap
2052 	bool overlaps(in Rectangle r) {
2053 		// the -1 in here are because right and top are exclusive
2054 		return !((right-1) < r.left || (r.right-1) < left || (bottom-1) < r.top || (r.bottom-1) < top);
2055 	}
2056 
2057 	/++
2058 		Returns a Rectangle representing the intersection of this and the other given one.
2059 
2060 		History:
2061 			Added July 1, 2021
2062 	+/
2063 	Rectangle intersectionOf(in Rectangle r) {
2064 		auto tmp = Rectangle(max(left, r.left), max(top, r.top), min(right, r.right), min(bottom, r.bottom));
2065 		if(tmp.left >= tmp.right || tmp.top >= tmp.bottom)
2066 			tmp = Rectangle.init;
2067 
2068 		return tmp;
2069 	}
2070 }
2071 
2072 /++
2073 	A type to represent an angle, taking away ambiguity of if it wants degrees or radians.
2074 
2075 	---
2076 		Angle a = Angle.degrees(180);
2077 		Angle b = Angle.radians(3.14159);
2078 
2079 		// note there might be slight changes in precision due to internal conversions
2080 	---
2081 
2082 	History:
2083 		Added August 29, 2023 (dub v11.1)
2084 +/
2085 struct Angle {
2086 	private enum PI = 3.14159265358979;
2087 	private float angle;
2088 
2089 	pure @nogc nothrow @safe:
2090 
2091 	private this(float angle) {
2092 		this.angle = angle;
2093 	}
2094 
2095 	/++
2096 
2097 	+/
2098 	float degrees() const {
2099 		return angle * 180.0 / PI;
2100 	}
2101 
2102 	/// ditto
2103 	static Angle degrees(float deg) {
2104 		return Angle(deg * PI / 180.0);
2105 	}
2106 
2107 	/// ditto
2108 	float radians() const {
2109 		return angle;
2110 	}
2111 
2112 	/// ditto
2113 	static Angle radians(float rad) {
2114 		return Angle(rad);
2115 	}
2116 
2117 	/++
2118 		The +, -, +=, and -= operators all work on the angles too.
2119 	+/
2120 	Angle opBinary(string op : "+")(const Angle rhs) const {
2121 		return Angle(this.angle + rhs.angle);
2122 	}
2123 	/// ditto
2124 	Angle opBinary(string op : "-")(const Angle rhs) const {
2125 		return Angle(this.angle + rhs.angle);
2126 	}
2127 	/// ditto
2128 	Angle opOpAssign(string op : "+")(const Angle rhs) {
2129 		return this.angle += rhs.angle;
2130 	}
2131 	/// ditto
2132 	Angle opOpAssign(string op : "-")(const Angle rhs) {
2133 		return this.angle -= rhs.angle;
2134 	}
2135 
2136 	// maybe sin, cos, tan but meh you can .radians on them too.
2137 }
2138 
2139 private int max(int a, int b) @nogc nothrow pure @safe {
2140 	return a >= b ? a : b;
2141 }
2142 private int min(int a, int b) @nogc nothrow pure @safe {
2143 	return a <= b ? a : b;
2144 }
2145 
2146 /++
2147 	Implements a flood fill algorithm, like the bucket tool in
2148 	MS Paint.
2149 
2150 	Note it assumes `what.length == width*height`.
2151 
2152 	Params:
2153 		what = the canvas to work with, arranged as top to bottom, left to right elements
2154 		width = the width of the canvas
2155 		height = the height of the canvas
2156 		target = the type to replace. You may pass the existing value if you want to do what Paint does
2157 		replacement = the replacement value
2158 		x = the x-coordinate to start the fill (think of where the user clicked in Paint)
2159 		y = the y-coordinate to start the fill
2160 		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.
2161 +/
2162 void floodFill(T)(
2163 	T[] what, int width, int height, // the canvas to inspect
2164 	T target, T replacement, // fill params
2165 	int x, int y, bool delegate(int x, int y) @safe additionalCheck) // the node
2166 
2167 	// in(what.length == width * height) // gdc doesn't support this syntax yet so not gonna use it until that comes out.
2168 {
2169 	assert(what.length == width * height); // will use the contract above when gdc supports it
2170 
2171 	T node = what[y * width + x];
2172 
2173 	if(target == replacement) return;
2174 
2175 	if(node != target) return;
2176 
2177 	if(additionalCheck is null)
2178 		additionalCheck = (int, int) => true;
2179 
2180 	if(!additionalCheck(x, y))
2181 		return;
2182 
2183 	Point[] queue;
2184 
2185 	queue ~= Point(x, y);
2186 
2187 	while(queue.length) {
2188 		auto n = queue[0];
2189 		queue = queue[1 .. $];
2190 		//queue.assumeSafeAppend(); // lol @safe breakage
2191 
2192 		auto w = n;
2193 		int offset = cast(int) (n.y * width + n.x);
2194 		auto e = n;
2195 		auto eoffset = offset;
2196 		w.x--;
2197 		offset--;
2198 		while(w.x >= 0 && what[offset] == target && additionalCheck(w.x, w.y)) {
2199 			w.x--;
2200 			offset--;
2201 		}
2202 		while(e.x < width && what[eoffset] == target && additionalCheck(e.x, e.y)) {
2203 			e.x++;
2204 			eoffset++;
2205 		}
2206 
2207 		// to make it inclusive again
2208 		w.x++;
2209 		offset++;
2210 		foreach(o ; offset .. eoffset) {
2211 			what[o] = replacement;
2212 			if(w.y && what[o - width] == target && additionalCheck(w.x, w.y))
2213 				queue ~= Point(w.x, w.y - 1);
2214 			if(w.y + 1 < height && what[o + width] == target && additionalCheck(w.x, w.y))
2215 				queue ~= Point(w.x, w.y + 1);
2216 			w.x++;
2217 		}
2218 	}
2219 
2220 	/+
2221 	what[y * width + x] = replacement;
2222 
2223 	if(x)
2224 		floodFill(what, width, height, target, replacement,
2225 			x - 1, y, additionalCheck);
2226 
2227 	if(x != width - 1)
2228 		floodFill(what, width, height, target, replacement,
2229 			x + 1, y, additionalCheck);
2230 
2231 	if(y)
2232 		floodFill(what, width, height, target, replacement,
2233 			x, y - 1, additionalCheck);
2234 
2235 	if(y != height - 1)
2236 		floodFill(what, width, height, target, replacement,
2237 			x, y + 1, additionalCheck);
2238 	+/
2239 }
2240 
2241 // for scripting, so you can tag it without strictly needing to import arsd.jsvar
2242 enum arsd_jsvar_compatible = "arsd_jsvar_compatible";