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