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 
724 	rgba[0] = cast(ubyte)(rgba[0] * 255 / a);
725 	rgba[1] = cast(ubyte)(rgba[1] * 255 / a);
726 	rgba[2] = cast(ubyte)(rgba[2] * 255 / a);
727 }
728 
729 unittest {
730 	Color c = Color.fromString("#fff");
731 	assert(c == Color.white);
732 	assert(c == Color.fromString("#ffffff"));
733 
734 	c = Color.fromString("#f0f");
735 	assert(c == Color.fromString("rgb(255, 0, 255)"));
736 }
737 
738 nothrow @safe
739 private string toHexInternal(ubyte b) {
740 	string s;
741 	if(b < 16)
742 		s ~= '0';
743 	else {
744 		ubyte t = (b & 0xf0) >> 4;
745 		if(t >= 10)
746 			s ~= 'A' + t - 10;
747 		else
748 			s ~= '0' + t;
749 		b &= 0x0f;
750 	}
751 	if(b >= 10)
752 		s ~= 'A' + b - 10;
753 	else
754 		s ~= '0' + b;
755 
756 	return s;
757 }
758 
759 nothrow @safe @nogc pure
760 private ubyte fromHexInternal(in char[] s) {
761 	int result = 0;
762 
763 	int exp = 1;
764 	//foreach(c; retro(s)) { // FIXME: retro doesn't work right in dtojs
765 	foreach_reverse(c; s) {
766 		if(c >= 'A' && c <= 'F')
767 			result += exp * (c - 'A' + 10);
768 		else if(c >= 'a' && c <= 'f')
769 			result += exp * (c - 'a' + 10);
770 		else if(c >= '0' && c <= '9')
771 			result += exp * (c - '0');
772 		else
773 			// throw new Exception("invalid hex character: " ~ cast(char) c);
774 			return 0;
775 
776 		exp *= 16;
777 	}
778 
779 	return cast(ubyte) result;
780 }
781 
782 /// Converts hsl to rgb
783 Color fromHsl(real[3] hsl) nothrow pure @safe @nogc {
784 	return fromHsl(cast(double) hsl[0], cast(double) hsl[1], cast(double) hsl[2]);
785 }
786 
787 Color fromHsl(double[3] hsl) nothrow pure @safe @nogc {
788 	return fromHsl(hsl[0], hsl[1], hsl[2]);
789 }
790 
791 /// Converts hsl to rgb
792 Color fromHsl(double h, double s, double l, double a = 255) nothrow pure @safe @nogc {
793 	h = h % 360;
794 
795 	double C = (1 - absInternal(2 * l - 1)) * s;
796 
797 	double hPrime = h / 60;
798 
799 	double X = C * (1 - absInternal(hPrime % 2 - 1));
800 
801 	double r, g, b;
802 
803 	if(h is double.nan)
804 		r = g = b = 0;
805 	else if (hPrime >= 0 && hPrime < 1) {
806 		r = C;
807 		g = X;
808 		b = 0;
809 	} else if (hPrime >= 1 && hPrime < 2) {
810 		r = X;
811 		g = C;
812 		b = 0;
813 	} else if (hPrime >= 2 && hPrime < 3) {
814 		r = 0;
815 		g = C;
816 		b = X;
817 	} else if (hPrime >= 3 && hPrime < 4) {
818 		r = 0;
819 		g = X;
820 		b = C;
821 	} else if (hPrime >= 4 && hPrime < 5) {
822 		r = X;
823 		g = 0;
824 		b = C;
825 	} else if (hPrime >= 5 && hPrime < 6) {
826 		r = C;
827 		g = 0;
828 		b = X;
829 	}
830 
831 	double m = l - C / 2;
832 
833 	r += m;
834 	g += m;
835 	b += m;
836 
837 	return Color(
838 		cast(int)(r * 255),
839 		cast(int)(g * 255),
840 		cast(int)(b * 255),
841 		cast(int)(a));
842 }
843 
844 /// Assumes the input `u` is already between 0 and 1 fyi.
845 nothrow pure @safe @nogc
846 double srgbToLinearRgb(double u) {
847 	if(u < 0.4045)
848 		return u / 12.92;
849 	else
850 		return pow((u + 0.055) / 1.055, 2.4);
851 		// return ((u + 0.055) / 1.055) ^^ 2.4;
852 }
853 
854 // could use the ^^ operator but that drags in some phobos. In this case, the import is
855 // actually insignificant, it doesn't really impact compile time, but it does complicate
856 // the dmd -v | grep std check to confirm i didn't miss any.
857 private extern(C) nothrow pure @safe @nogc double pow(double, double);
858 
859 /// 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.
860 double[3] toHsl(Color c, bool useWeightedLightness = false) nothrow pure @trusted @nogc {
861 	double r1 = cast(double) c.r / 255;
862 	double g1 = cast(double) c.g / 255;
863 	double b1 = cast(double) c.b / 255;
864 
865 	double maxColor = maxInternal(r1, g1, b1);
866 	double minColor = minInternal(r1, g1, b1);
867 
868 	double L = (maxColor + minColor) / 2 ;
869 	if(useWeightedLightness) {
870 		// the colors don't affect the eye equally
871 		// this is a little more accurate than plain HSL numbers
872 		L = 0.2126*srgbToLinearRgb(r1) + 0.7152*srgbToLinearRgb(g1) + 0.0722*srgbToLinearRgb(b1);
873 		// maybe a better number is 299, 587, 114
874 	}
875 	double S = 0;
876 	double H = 0;
877 	if(maxColor != minColor) {
878 		if(L < 0.5) {
879 			S = (maxColor - minColor) / (maxColor + minColor);
880 		} else {
881 			S = (maxColor - minColor) / (2.0 - maxColor - minColor);
882 		}
883 		if(r1 == maxColor) {
884 			H = (g1-b1) / (maxColor - minColor);
885 		} else if(g1 == maxColor) {
886 			H = 2.0 + (b1 - r1) / (maxColor - minColor);
887 		} else {
888 			H = 4.0 + (r1 - g1) / (maxColor - minColor);
889 		}
890 	}
891 
892 	H = H * 60;
893 	if(H < 0){
894 		H += 360;
895 	}
896 
897 	return [H, S, L];
898 }
899 
900 /// .
901 Color lighten(Color c, double percentage) nothrow pure @safe @nogc {
902 	auto hsl = toHsl(c);
903 	hsl[2] *= (1 + percentage);
904 	if(hsl[2] > 1)
905 		hsl[2] = 1;
906 	return fromHsl(hsl);
907 }
908 
909 /// .
910 Color darken(Color c, double percentage) nothrow pure @safe @nogc {
911 	auto hsl = toHsl(c);
912 	hsl[2] *= (1 - percentage);
913 	return fromHsl(hsl);
914 }
915 
916 /// for light colors, call darken. for dark colors, call lighten.
917 /// The goal: get toward center grey.
918 Color moderate(Color c, double percentage) nothrow pure @safe @nogc {
919 	auto hsl = toHsl(c);
920 	if(hsl[2] > 0.5)
921 		hsl[2] *= (1 - percentage);
922 	else {
923 		if(hsl[2] <= 0.01) // if we are given black, moderating it means getting *something* out
924 			hsl[2] = percentage;
925 		else
926 			hsl[2] *= (1 + percentage);
927 	}
928 	if(hsl[2] > 1)
929 		hsl[2] = 1;
930 	return fromHsl(hsl);
931 }
932 
933 /// the opposite of moderate. Make darks darker and lights lighter
934 Color extremify(Color c, double percentage) nothrow pure @safe @nogc {
935 	auto hsl = toHsl(c, true);
936 	if(hsl[2] < 0.5)
937 		hsl[2] *= (1 - percentage);
938 	else
939 		hsl[2] *= (1 + percentage);
940 	if(hsl[2] > 1)
941 		hsl[2] = 1;
942 	return fromHsl(hsl);
943 }
944 
945 /// Move around the lightness wheel, trying not to break on moderate things
946 Color oppositeLightness(Color c) nothrow pure @safe @nogc {
947 	auto hsl = toHsl(c);
948 
949 	auto original = hsl[2];
950 
951 	if(original > 0.4 && original < 0.6)
952 		hsl[2] = 0.8 - original; // so it isn't quite the same
953 	else
954 		hsl[2] = 1 - original;
955 
956 	return fromHsl(hsl);
957 }
958 
959 /// Try to determine a text color - either white or black - based on the input
960 Color makeTextColor(Color c) nothrow pure @safe @nogc {
961 	auto hsl = toHsl(c, true); // give green a bonus for contrast
962 	if(hsl[2] > 0.71)
963 		return Color(0, 0, 0);
964 	else
965 		return Color(255, 255, 255);
966 }
967 
968 // These provide functional access to hsl manipulation; useful if you need a delegate
969 
970 Color setLightness(Color c, double lightness) nothrow pure @safe @nogc {
971 	auto hsl = toHsl(c);
972 	hsl[2] = lightness;
973 	return fromHsl(hsl);
974 }
975 
976 
977 ///
978 Color rotateHue(Color c, double degrees) nothrow pure @safe @nogc {
979 	auto hsl = toHsl(c);
980 	hsl[0] += degrees;
981 	return fromHsl(hsl);
982 }
983 
984 ///
985 Color setHue(Color c, double hue) nothrow pure @safe @nogc {
986 	auto hsl = toHsl(c);
987 	hsl[0] = hue;
988 	return fromHsl(hsl);
989 }
990 
991 ///
992 Color desaturate(Color c, double percentage) nothrow pure @safe @nogc {
993 	auto hsl = toHsl(c);
994 	hsl[1] *= (1 - percentage);
995 	return fromHsl(hsl);
996 }
997 
998 ///
999 Color saturate(Color c, double percentage) nothrow pure @safe @nogc {
1000 	auto hsl = toHsl(c);
1001 	hsl[1] *= (1 + percentage);
1002 	if(hsl[1] > 1)
1003 		hsl[1] = 1;
1004 	return fromHsl(hsl);
1005 }
1006 
1007 ///
1008 Color setSaturation(Color c, double saturation) nothrow pure @safe @nogc {
1009 	auto hsl = toHsl(c);
1010 	hsl[1] = saturation;
1011 	return fromHsl(hsl);
1012 }
1013 
1014 
1015 /*
1016 void main(string[] args) {
1017 	auto color1 = toHsl(Color(255, 0, 0));
1018 	auto color = fromHsl(color1[0] + 60, color1[1], color1[2]);
1019 
1020 	writefln("#%02x%02x%02x", color.r, color.g, color.b);
1021 }
1022 */
1023 
1024 /* Color algebra functions */
1025 
1026 /* Alpha putpixel looks like this:
1027 
1028 void putPixel(Image i, Color c) {
1029 	Color b;
1030 	b.r = i.data[(y * i.width + x) * bpp + 0];
1031 	b.g = i.data[(y * i.width + x) * bpp + 1];
1032 	b.b = i.data[(y * i.width + x) * bpp + 2];
1033 	b.a = i.data[(y * i.width + x) * bpp + 3];
1034 
1035 	float ca = cast(float) c.a / 255;
1036 
1037 	i.data[(y * i.width + x) * bpp + 0] = alpha(c.r, ca, b.r);
1038 	i.data[(y * i.width + x) * bpp + 1] = alpha(c.g, ca, b.g);
1039 	i.data[(y * i.width + x) * bpp + 2] = alpha(c.b, ca, b.b);
1040 	i.data[(y * i.width + x) * bpp + 3] = alpha(c.a, ca, b.a);
1041 }
1042 
1043 ubyte alpha(ubyte c1, float alpha, ubyte onto) {
1044 	auto got = (1 - alpha) * onto + alpha * c1;
1045 
1046 	if(got > 255)
1047 		return 255;
1048 	return cast(ubyte) got;
1049 }
1050 
1051 So, given the background color and the resultant color, what was
1052 composited on to it?
1053 */
1054 
1055 ///
1056 ubyte unalpha(ubyte colorYouHave, float alpha, ubyte backgroundColor) nothrow pure @safe @nogc {
1057 	// resultingColor = (1-alpha) * backgroundColor + alpha * answer
1058 	auto resultingColorf = cast(float) colorYouHave;
1059 	auto backgroundColorf = cast(float) backgroundColor;
1060 
1061 	auto answer = (resultingColorf - backgroundColorf + alpha * backgroundColorf) / alpha;
1062 	return Color.clampToByte(cast(int) answer);
1063 }
1064 
1065 ///
1066 ubyte makeAlpha(ubyte colorYouHave, ubyte backgroundColor/*, ubyte foreground = 0x00*/) nothrow pure @safe @nogc {
1067 	//auto foregroundf = cast(float) foreground;
1068 	auto foregroundf = 0.00f;
1069 	auto colorYouHavef = cast(float) colorYouHave;
1070 	auto backgroundColorf = cast(float) backgroundColor;
1071 
1072 	// colorYouHave = backgroundColorf - alpha * backgroundColorf + alpha * foregroundf
1073 	auto alphaf = 1 - colorYouHave / backgroundColorf;
1074 	alphaf *= 255;
1075 
1076 	return Color.clampToByte(cast(int) alphaf);
1077 }
1078 
1079 
1080 int fromHex(string s) {
1081 	int result = 0;
1082 
1083 	int exp = 1;
1084 	// foreach(c; retro(s)) {
1085 	foreach_reverse(c; s) {
1086 		if(c >= 'A' && c <= 'F')
1087 			result += exp * (c - 'A' + 10);
1088 		else if(c >= 'a' && c <= 'f')
1089 			result += exp * (c - 'a' + 10);
1090 		else if(c >= '0' && c <= '9')
1091 			result += exp * (c - '0');
1092 		else
1093 			throw new Exception("invalid hex character: " ~ cast(char) c);
1094 
1095 		exp *= 16;
1096 	}
1097 
1098 	return result;
1099 }
1100 
1101 ///
1102 Color colorFromString(string s) {
1103 	if(s.length == 0)
1104 		return Color(0,0,0,255);
1105 	if(s[0] == '#')
1106 		s = s[1..$];
1107 	assert(s.length == 6 || s.length == 8);
1108 
1109 	Color c;
1110 
1111 	c.r = cast(ubyte) fromHex(s[0..2]);
1112 	c.g = cast(ubyte) fromHex(s[2..4]);
1113 	c.b = cast(ubyte) fromHex(s[4..6]);
1114 	if(s.length == 8)
1115 		c.a = cast(ubyte) fromHex(s[6..8]);
1116 	else
1117 		c.a = 255;
1118 
1119 	return c;
1120 }
1121 
1122 /*
1123 import browser.window;
1124 void main() {
1125 	import browser.document;
1126 	foreach(ele; document.querySelectorAll("input")) {
1127 		ele.addEventListener("change", {
1128 			auto h = toInternal!double(document.querySelector("input[name=h]").value);
1129 			auto s = toInternal!double(document.querySelector("input[name=s]").value);
1130 			auto l = toInternal!double(document.querySelector("input[name=l]").value);
1131 
1132 			Color c = Color.fromHsl(h, s, l);
1133 
1134 			auto e = document.getElementById("example");
1135 			e.style.backgroundColor = c.toCssString();
1136 
1137 			// JSElement __js_this;
1138 			// __js_this.style.backgroundColor = c.toCssString();
1139 		}, false);
1140 	}
1141 }
1142 */
1143 
1144 
1145 
1146 /**
1147 	This provides two image classes and a bunch of functions that work on them.
1148 
1149 	Why are they separate classes? I think the operations on the two of them
1150 	are necessarily different. There's a whole bunch of operations that only
1151 	really work on truecolor (blurs, gradients), and a few that only work
1152 	on indexed images (palette swaps).
1153 
1154 	Even putpixel is pretty different. On indexed, it is a palette entry's
1155 	index number. On truecolor, it is the actual color.
1156 
1157 	A greyscale image is the weird thing in the middle. It is truecolor, but
1158 	fits in the same size as indexed. Still, I'd say it is a specialization
1159 	of truecolor.
1160 
1161 	There is a subset that works on both
1162 
1163 */
1164 
1165 /// An image in memory
1166 interface MemoryImage {
1167 	//IndexedImage convertToIndexedImage() const;
1168 	//TrueColorImage convertToTrueColor() const;
1169 
1170 	/// gets it as a TrueColorImage. May return this or may do a conversion and return a new image
1171 	TrueColorImage getAsTrueColorImage() pure nothrow @safe;
1172 
1173 	/// Image width, in pixels
1174 	int width() const pure nothrow @safe @nogc;
1175 
1176 	/// Image height, in pixels
1177 	int height() const pure nothrow @safe @nogc;
1178 
1179 	/// Get image pixel. Slow, but returns valid RGBA color (completely transparent for off-image pixels).
1180 	Color getPixel(int x, int y) const pure nothrow @safe @nogc;
1181 
1182   /// Set image pixel.
1183 	void setPixel(int x, int y, in Color clr) nothrow @safe;
1184 
1185 	/// Returns a copy of the image
1186 	MemoryImage clone() const pure nothrow @safe;
1187 
1188 	/// Load image from file. This will import arsd.image to do the actual work, and cost nothing if you don't use it.
1189 	static MemoryImage fromImage(T : const(char)[]) (T filename) @trusted {
1190 		static if (__traits(compiles, (){import arsd.image;})) {
1191 			// yay, we have image loader here, try it!
1192 			import arsd.image;
1193 			return loadImageFromFile(filename);
1194 		} else {
1195 			static assert(0, "please provide 'arsd.image' to load images!");
1196 		}
1197 	}
1198 
1199 	// ***This method is deliberately not publicly documented.***
1200 	// What it does is unconditionally frees internal image storage, without any sanity checks.
1201 	// If you will do this, make sure that you have no references to image data left (like
1202 	// slices of [data] array, for example). Those references will become invalid, and WILL
1203 	// lead to Undefined Behavior.
1204 	// tl;dr: IF YOU HAVE *ANY* QUESTIONS REGARDING THIS COMMENT, DON'T USE THIS!
1205 	// Note to implementors: it is safe to simply do nothing in this method.
1206 	// Also, it should be safe to call this method twice or more.
1207 	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
1208 
1209 	/// Convenient alias for `fromImage`
1210 	alias fromImageFile = fromImage;
1211 }
1212 
1213 /// An image that consists of indexes into a color palette. Use [getAsTrueColorImage]() if you don't care about palettes
1214 class IndexedImage : MemoryImage {
1215 	bool hasAlpha;
1216 
1217 	/// .
1218 	Color[] palette;
1219 	/// the data as indexes into the palette. Stored left to right, top to bottom, no padding.
1220 	ubyte[] data;
1221 
1222 	override void clearInternal () nothrow @system {// @nogc {
1223 		import core.memory : GC;
1224 		// it is safe to call [GC.free] with `null` pointer.
1225 		GC.free(GC.addrOf(palette.ptr)); palette = null;
1226 		GC.free(GC.addrOf(data.ptr)); data = null;
1227 		_width = _height = 0;
1228 	}
1229 
1230 	/// .
1231 	override int width() const pure nothrow @safe @nogc {
1232 		return _width;
1233 	}
1234 
1235 	/// .
1236 	override int height() const pure nothrow @safe @nogc {
1237 		return _height;
1238 	}
1239 
1240 	/// .
1241 	override IndexedImage clone() const pure nothrow @trusted {
1242 		auto n = new IndexedImage(width, height);
1243 		n.data[] = this.data[]; // the data member is already there, so array copy
1244 		n.palette = this.palette.dup; // and here we need to allocate too, so dup
1245 		n.hasAlpha = this.hasAlpha;
1246 		return n;
1247 	}
1248 
1249 	override Color getPixel(int x, int y) const pure nothrow @trusted @nogc {
1250 		if (x >= 0 && y >= 0 && x < _width && y < _height) {
1251 			size_t pos = cast(size_t)y*_width+x;
1252 			if (pos >= data.length) return Color(0, 0, 0, 0);
1253 			ubyte b = data.ptr[pos];
1254 			if (b >= palette.length) return Color(0, 0, 0, 0);
1255 			return palette.ptr[b];
1256 		} else {
1257 			return Color(0, 0, 0, 0);
1258 		}
1259 	}
1260 
1261 	override void setPixel(int x, int y, in Color clr) nothrow @trusted {
1262 		if (x >= 0 && y >= 0 && x < _width && y < _height) {
1263 			size_t pos = cast(size_t)y*_width+x;
1264 			if (pos >= data.length) return;
1265 			ubyte pidx = findNearestColor(palette, clr);
1266 			if (palette.length < 255 &&
1267 				 (palette.ptr[pidx].r != clr.r || palette.ptr[pidx].g != clr.g || palette.ptr[pidx].b != clr.b || palette.ptr[pidx].a != clr.a)) {
1268 				// add new color
1269 				pidx = addColor(clr);
1270 			}
1271 			data.ptr[pos] = pidx;
1272 		}
1273 	}
1274 
1275 	private int _width;
1276 	private int _height;
1277 
1278 	/// .
1279 	this(int w, int h) pure nothrow @safe {
1280 		_width = w;
1281 		_height = h;
1282 
1283         // ensure that the computed size does not exceed basic address space limits
1284         assert(cast(ulong)w * h  <= size_t.max);
1285         // upcast to avoid overflow for images larger than 536 Mpix
1286 		data = new ubyte[cast(size_t)w*h];
1287 	}
1288 
1289 	/*
1290 	void resize(int w, int h, bool scale) {
1291 
1292 	}
1293 	*/
1294 
1295 	/// returns a new image
1296 	override TrueColorImage getAsTrueColorImage() pure nothrow @safe {
1297 		return convertToTrueColor();
1298 	}
1299 
1300 	/// Creates a new TrueColorImage based on this data
1301 	TrueColorImage convertToTrueColor() const pure nothrow @trusted {
1302 		auto tci = new TrueColorImage(width, height);
1303 		foreach(i, b; data) {
1304 			tci.imageData.colors[i] = palette[b];
1305 		}
1306 		return tci;
1307 	}
1308 
1309 	/// Gets an exact match, if possible, adds if not. See also: the findNearestColor free function.
1310 	ubyte getOrAddColor(Color c) nothrow @trusted {
1311 		foreach(i, co; palette) {
1312 			if(c == co)
1313 				return cast(ubyte) i;
1314 		}
1315 
1316 		return addColor(c);
1317 	}
1318 
1319 	/// Number of colors currently in the palette (note: palette entries are not necessarily used in the image data)
1320 	int numColors() const pure nothrow @trusted @nogc {
1321 		return cast(int) palette.length;
1322 	}
1323 
1324 	/// Adds an entry to the palette, returning its index
1325 	ubyte addColor(Color c) nothrow @trusted {
1326 		assert(palette.length < 256);
1327 		if(c.a != 255)
1328 			hasAlpha = true;
1329 		palette ~= c;
1330 
1331 		return cast(ubyte) (palette.length - 1);
1332 	}
1333 }
1334 
1335 /// An RGBA array of image data. Use the free function quantize() to convert to an IndexedImage
1336 class TrueColorImage : MemoryImage {
1337 //	bool hasAlpha;
1338 //	bool isGreyscale;
1339 
1340 	//ubyte[] data; // stored as rgba quads, upper left to right to bottom
1341 	/// .
1342 	struct Data {
1343 		ubyte[] bytes; /// the data as rgba bytes. Stored left to right, top to bottom, no padding.
1344 		// the union is no good because the length of the struct is wrong!
1345 
1346 		/// the same data as Color structs
1347 		@trusted // the cast here is typically unsafe, but it is ok
1348 		// here because I guarantee the layout, note the static assert below
1349 		@property inout(Color)[] colors() inout pure nothrow @nogc {
1350 			return cast(inout(Color)[]) bytes;
1351 		}
1352 
1353 		static assert(Color.sizeof == 4);
1354 	}
1355 
1356 	/// .
1357 	Data imageData;
1358 	alias imageData.bytes data;
1359 
1360 	int _width;
1361 	int _height;
1362 
1363 	override void clearInternal () nothrow @system {// @nogc {
1364 		import core.memory : GC;
1365 		// it is safe to call [GC.free] with `null` pointer.
1366 		GC.free(GC.addrOf(imageData.bytes.ptr)); imageData.bytes = null;
1367 		_width = _height = 0;
1368 	}
1369 
1370 	/// .
1371 	override TrueColorImage clone() const pure nothrow @trusted {
1372 		auto n = new TrueColorImage(width, height);
1373 		n.imageData.bytes[] = this.imageData.bytes[]; // copy into existing array ctor allocated
1374 		return n;
1375 	}
1376 
1377 	/// .
1378 	override int width() const pure nothrow @trusted @nogc { return _width; }
1379 	///.
1380 	override int height() const pure nothrow @trusted @nogc { return _height; }
1381 
1382 	override Color getPixel(int x, int y) const pure nothrow @trusted @nogc {
1383 		if (x >= 0 && y >= 0 && x < _width && y < _height) {
1384 			size_t pos = cast(size_t)y*_width+x;
1385 			return imageData.colors.ptr[pos];
1386 		} else {
1387 			return Color(0, 0, 0, 0);
1388 		}
1389 	}
1390 
1391 	override void setPixel(int x, int y, in Color clr) nothrow @trusted {
1392 		if (x >= 0 && y >= 0 && x < _width && y < _height) {
1393 			size_t pos = cast(size_t)y*_width+x;
1394 			if (pos < imageData.bytes.length/4) imageData.colors.ptr[pos] = clr;
1395 		}
1396 	}
1397 
1398 	/// .
1399 	this(int w, int h) pure nothrow @safe {
1400 		_width = w;
1401 		_height = h;
1402 
1403 		// ensure that the computed size does not exceed basic address space limits
1404         assert(cast(ulong)w * h * 4 <= size_t.max);
1405         // upcast to avoid overflow for images larger than 536 Mpix
1406 		imageData.bytes = new ubyte[cast(size_t)w * h * 4];
1407 	}
1408 
1409 	/// Creates with existing data. The data pointer is stored here.
1410 	this(int w, int h, ubyte[] data) pure nothrow @safe {
1411 		_width = w;
1412 		_height = h;
1413 		assert(cast(ulong)w * h * 4 <= size_t.max);
1414 		assert(data.length == cast(size_t)w * h * 4);
1415 		imageData.bytes = data;
1416 	}
1417 
1418 	/// Returns this
1419 	override TrueColorImage getAsTrueColorImage() pure nothrow @safe {
1420 		return this;
1421 	}
1422 }
1423 
1424 /+
1425 /// An RGB array of image data.
1426 class TrueColorImageWithoutAlpha : MemoryImage {
1427 	struct Data {
1428 		ubyte[] bytes; // the data as rgba bytes. Stored left to right, top to bottom, no padding.
1429 	}
1430 
1431 	/// .
1432 	Data imageData;
1433 
1434 	int _width;
1435 	int _height;
1436 
1437 	override void clearInternal () nothrow @system {// @nogc {
1438 		import core.memory : GC;
1439 		// it is safe to call [GC.free] with `null` pointer.
1440 		GC.free(imageData.bytes.ptr); imageData.bytes = null;
1441 		_width = _height = 0;
1442 	}
1443 
1444 	/// .
1445 	override TrueColorImageWithoutAlpha clone() const pure nothrow @trusted {
1446 		auto n = new TrueColorImageWithoutAlpha(width, height);
1447 		n.imageData.bytes[] = this.imageData.bytes[]; // copy into existing array ctor allocated
1448 		return n;
1449 	}
1450 
1451 	/// .
1452 	override int width() const pure nothrow @trusted @nogc { return _width; }
1453 	///.
1454 	override int height() const pure nothrow @trusted @nogc { return _height; }
1455 
1456 	override Color getPixel(int x, int y) const pure nothrow @trusted @nogc {
1457 		if (x >= 0 && y >= 0 && x < _width && y < _height) {
1458 			uint pos = (y*_width+x) * 3;
1459 			return Color(imageData.bytes[0], imageData.bytes[1], imageData.bytes[2], 255);
1460 		} else {
1461 			return Color(0, 0, 0, 0);
1462 		}
1463 	}
1464 
1465 	override void setPixel(int x, int y, in Color clr) nothrow @trusted {
1466 		if (x >= 0 && y >= 0 && x < _width && y < _height) {
1467 			uint pos = y*_width+x;
1468 			//if (pos < imageData.bytes.length/4) imageData.colors.ptr[pos] = clr;
1469 			// FIXME
1470 		}
1471 	}
1472 
1473 	/// .
1474 	this(int w, int h) pure nothrow @safe {
1475 		_width = w;
1476 		_height = h;
1477 		imageData.bytes = new ubyte[w*h*3];
1478 	}
1479 
1480 	/// Creates with existing data. The data pointer is stored here.
1481 	this(int w, int h, ubyte[] data) pure nothrow @safe {
1482 		_width = w;
1483 		_height = h;
1484 		assert(data.length == w * h * 3);
1485 		imageData.bytes = data;
1486 	}
1487 
1488 	///
1489 	override TrueColorImage getAsTrueColorImage() pure nothrow @safe {
1490 		// FIXME
1491 		//return this;
1492 	}
1493 }
1494 +/
1495 
1496 
1497 alias extern(C) int function(scope const void*, scope const void*) @system Comparator;
1498 @trusted void nonPhobosSort(T)(T[] obj,  Comparator comparator) {
1499 	import core.stdc.stdlib;
1500 	qsort(obj.ptr, obj.length, typeof(obj[0]).sizeof, comparator);
1501 }
1502 
1503 /// Converts true color to an indexed image. It uses palette as the starting point, adding entries
1504 /// until maxColors as needed. If palette is null, it creates a whole new palette.
1505 ///
1506 /// After quantizing the image, it applies a dithering algorithm.
1507 ///
1508 /// This is not written for speed.
1509 IndexedImage quantize(in TrueColorImage img, Color[] palette = null, in int maxColors = 256)
1510 	// this is just because IndexedImage assumes ubyte palette values
1511 	in { assert(maxColors <= 256); }
1512 do {
1513 	int[Color] uses;
1514 	foreach(pixel; img.imageData.colors) {
1515 		if(auto i = pixel in uses) {
1516 			(*i)++;
1517 		} else {
1518 			uses[pixel] = 1;
1519 		}
1520 	}
1521 
1522 	struct ColorUse {
1523 		Color c;
1524 		int uses;
1525 		//string toString() { return c.toCssString() ~ " x " ~ to!string(uses); }
1526 		int opCmp(ref const ColorUse co) const {
1527 			return co.uses - uses;
1528 		}
1529 		extern(C) static int comparator(scope const void* lhs, scope const void* rhs) {
1530 			return (cast(ColorUse*)rhs).uses - (cast(ColorUse*)lhs).uses;
1531 		}
1532 	}
1533 
1534 	ColorUse[] sorted;
1535 
1536 	foreach(color, count; uses)
1537 		sorted ~= ColorUse(color, count);
1538 
1539 	uses = null;
1540 
1541 	nonPhobosSort(sorted, &ColorUse.comparator);
1542 	// or, with phobos, but that adds 70ms to compile time
1543 	//import std.algorithm.sorting : sort;
1544 	//sort(sorted);
1545 
1546 	ubyte[Color] paletteAssignments;
1547 	foreach(idx, entry; palette)
1548 		paletteAssignments[entry] = cast(ubyte) idx;
1549 
1550 	// For the color assignments from the image, I do multiple passes, decreasing the acceptable
1551 	// distance each time until we're full.
1552 
1553 	// This is probably really slow.... but meh it gives pretty good results.
1554 
1555 	auto ddiff = 32;
1556 	outer: for(int d1 = 128; d1 >= 0; d1 -= ddiff) {
1557 	auto minDist = d1*d1;
1558 	if(d1 <= 64)
1559 		ddiff = 16;
1560 	if(d1 <= 32)
1561 		ddiff = 8;
1562 	foreach(possibility; sorted) {
1563 		if(palette.length == maxColors)
1564 			break;
1565 		if(palette.length) {
1566 			auto co = palette[findNearestColor(palette, possibility.c)];
1567 			auto pixel = possibility.c;
1568 
1569 			auto dr = cast(int) co.r - pixel.r;
1570 			auto dg = cast(int) co.g - pixel.g;
1571 			auto db = cast(int) co.b - pixel.b;
1572 
1573 			auto dist = dr*dr + dg*dg + db*db;
1574 			// not good enough variety to justify an allocation yet
1575 			if(dist < minDist)
1576 				continue;
1577 		}
1578 		paletteAssignments[possibility.c] = cast(ubyte) palette.length;
1579 		palette ~= possibility.c;
1580 	}
1581 	}
1582 
1583 	// Final pass: just fill in any remaining space with the leftover common colors
1584 	while(palette.length < maxColors && sorted.length) {
1585 		if(sorted[0].c !in paletteAssignments) {
1586 			paletteAssignments[sorted[0].c] = cast(ubyte) palette.length;
1587 			palette ~= sorted[0].c;
1588 		}
1589 		sorted = sorted[1 .. $];
1590 	}
1591 
1592 
1593 	bool wasPerfect = true;
1594 	auto newImage = new IndexedImage(img.width, img.height);
1595 	newImage.palette = palette;
1596 	foreach(idx, pixel; img.imageData.colors) {
1597 		if(auto p = pixel in paletteAssignments)
1598 			newImage.data[idx] = *p;
1599 		else {
1600 			// gotta find the closest one...
1601 			newImage.data[idx] = findNearestColor(palette, pixel);
1602 			wasPerfect = false;
1603 		}
1604 	}
1605 
1606 	if(!wasPerfect)
1607 		floydSteinbergDither(newImage, img);
1608 
1609 	return newImage;
1610 }
1611 
1612 /// Finds the best match for pixel in palette (currently by checking for minimum euclidean distance in rgb colorspace)
1613 ubyte findNearestColor(in Color[] palette, in Color pixel) nothrow pure @trusted @nogc {
1614 	int best = 0;
1615 	int bestDistance = int.max;
1616 	foreach(pe, co; palette) {
1617 		auto dr = cast(int) co.r - pixel.r;
1618 		auto dg = cast(int) co.g - pixel.g;
1619 		auto db = cast(int) co.b - pixel.b;
1620 		int dist = dr*dr + dg*dg + db*db;
1621 
1622 		if(dist < bestDistance) {
1623 			best = cast(int) pe;
1624 			bestDistance = dist;
1625 		}
1626 	}
1627 
1628 	return cast(ubyte) best;
1629 }
1630 
1631 /+
1632 
1633 // Quantizing and dithering test program
1634 
1635 void main( ){
1636 /*
1637 	auto img = new TrueColorImage(256, 32);
1638 	foreach(y; 0 .. img.height) {
1639 		foreach(x; 0 .. img.width) {
1640 			img.imageData.colors[x + y * img.width] = Color(x, y * (255 / img.height), 0);
1641 		}
1642 	}
1643 */
1644 
1645 TrueColorImage img;
1646 
1647 {
1648 
1649 import arsd.png;
1650 
1651 struct P {
1652 	ubyte[] range;
1653 	void put(ubyte[] a) { range ~= a; }
1654 }
1655 
1656 P range;
1657 import std.algorithm; // commented out
1658 
1659 import std.stdio; // commented out
1660 writePngLazy(range, pngFromBytes(File("/home/me/nyesha.png").byChunk(4096)).byRgbaScanline.map!((line) {
1661 	foreach(ref pixel; line.pixels) {
1662 	continue;
1663 		auto sum = cast(int) pixel.r + pixel.g + pixel.b;
1664 		ubyte a = cast(ubyte)(sum / 3);
1665 		pixel.r = a;
1666 		pixel.g = a;
1667 		pixel.b = a;
1668 	}
1669 	return line;
1670 }));
1671 
1672 img = imageFromPng(readPng(range.range)).getAsTrueColorImage;
1673 
1674 
1675 }
1676 
1677 
1678 
1679 	auto qimg = quantize(img, null, 2);
1680 
1681 	import arsd.simpledisplay;
1682 	auto win = new SimpleWindow(img.width, img.height * 3);
1683 	auto painter = win.draw();
1684 	painter.drawImage(Point(0, 0), Image.fromMemoryImage(img));
1685 	painter.drawImage(Point(0, img.height), Image.fromMemoryImage(qimg));
1686 	floydSteinbergDither(qimg, img);
1687 	painter.drawImage(Point(0, img.height * 2), Image.fromMemoryImage(qimg));
1688 	win.eventLoop(0);
1689 }
1690 +/
1691 
1692 /+
1693 /// If the background is transparent, it simply erases the alpha channel.
1694 void removeTransparency(IndexedImage img, Color background)
1695 +/
1696 
1697 /// Perform alpha-blending of `fore` to this color, return new color.
1698 /// WARNING! This function does blending in RGB space, and RGB space is not linear!
1699 Color alphaBlend(Color foreground, Color background) pure nothrow @safe @nogc {
1700 	//if(foreground.a == 255)
1701 		//return foreground;
1702 	if(foreground.a == 0)
1703 		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)
1704 
1705 	static if (__VERSION__ > 2067) pragma(inline, true);
1706 	return background.alphaBlend(foreground);
1707 }
1708 
1709 /*
1710 /// Reduces the number of colors in a palette.
1711 void reducePaletteSize(IndexedImage img, int maxColors = 16) {
1712 
1713 }
1714 */
1715 
1716 // I think I did this wrong... but the results aren't too bad so the bug can't be awful.
1717 /// Dithers img in place to look more like original.
1718 void floydSteinbergDither(IndexedImage img, in TrueColorImage original) nothrow @trusted {
1719 	assert(img.width == original.width);
1720 	assert(img.height == original.height);
1721 
1722 	auto buffer = new Color[](original.imageData.colors.length);
1723 
1724 	int x, y;
1725 
1726 	foreach(idx, c; original.imageData.colors) {
1727 		auto n = img.palette[img.data[idx]];
1728 		int errorR = cast(int) c.r - n.r;
1729 		int errorG = cast(int) c.g - n.g;
1730 		int errorB = cast(int) c.b - n.b;
1731 
1732 		void doit(int idxOffset, int multiplier) {
1733 		//	if(idx + idxOffset < buffer.length)
1734 				buffer[idx + idxOffset] = Color.fromIntegers(
1735 					c.r + multiplier * errorR / 16,
1736 					c.g + multiplier * errorG / 16,
1737 					c.b + multiplier * errorB / 16,
1738 					c.a
1739 				);
1740 		}
1741 
1742 		if((x+1) != original.width)
1743 			doit(1, 7);
1744 		if((y+1) != original.height) {
1745 			if(x != 0)
1746 				doit(-1 + img.width, 3);
1747 			doit(img.width, 5);
1748 			if(x+1 != original.width)
1749 				doit(1 + img.width, 1);
1750 		}
1751 
1752 		img.data[idx] = findNearestColor(img.palette, buffer[idx]);
1753 
1754 		x++;
1755 		if(x == original.width) {
1756 			x = 0;
1757 			y++;
1758 		}
1759 	}
1760 }
1761 
1762 // these are just really useful in a lot of places where the color/image functions are used,
1763 // so I want them available with Color
1764 ///
1765 struct Point {
1766 	int x; ///
1767 	int y; ///
1768 
1769 	pure const nothrow @safe:
1770 
1771 	Point opBinary(string op)(in Point rhs) @nogc {
1772 		return Point(mixin("x" ~ op ~ "rhs.x"), mixin("y" ~ op ~ "rhs.y"));
1773 	}
1774 
1775 	Point opBinary(string op)(int rhs) @nogc {
1776 		return Point(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs"));
1777 	}
1778 }
1779 
1780 ///
1781 struct Size {
1782 	int width; ///
1783 	int height; ///
1784 
1785 	int area() pure nothrow @safe const @nogc { return width * height; }
1786 }
1787 
1788 ///
1789 struct Rectangle {
1790 	int left; ///
1791 	int top; ///
1792 	int right; ///
1793 	int bottom; ///
1794 
1795 	pure const nothrow @safe @nogc:
1796 
1797 	///
1798 	this(int left, int top, int right, int bottom) {
1799 		this.left = left;
1800 		this.top = top;
1801 		this.right = right;
1802 		this.bottom = bottom;
1803 	}
1804 
1805 	///
1806 	this(in Point upperLeft, in Point lowerRight) {
1807 		this(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
1808 	}
1809 
1810 	///
1811 	this(in Point upperLeft, in Size size) {
1812 		this(upperLeft.x, upperLeft.y, upperLeft.x + size.width, upperLeft.y + size.height);
1813 	}
1814 
1815 	///
1816 	@property Point upperLeft() {
1817 		return Point(left, top);
1818 	}
1819 
1820 	///
1821 	@property Point upperRight() {
1822 		return Point(right, top);
1823 	}
1824 
1825 	///
1826 	@property Point lowerLeft() {
1827 		return Point(left, bottom);
1828 	}
1829 
1830 	///
1831 	@property Point lowerRight() {
1832 		return Point(right, bottom);
1833 	}
1834 
1835 	///
1836 	@property Point center() {
1837 		return Point((right + left) / 2, (bottom + top) / 2);
1838 	}
1839 
1840 	///
1841 	@property Size size() {
1842 		return Size(width, height);
1843 	}
1844 
1845 	///
1846 	@property int width() {
1847 		return right - left;
1848 	}
1849 
1850 	///
1851 	@property int height() {
1852 		return bottom - top;
1853 	}
1854 
1855 	/// Returns true if this rectangle entirely contains the other
1856 	bool contains(in Rectangle r) {
1857 		return contains(r.upperLeft) && contains(r.lowerRight);
1858 	}
1859 
1860 	/// ditto
1861 	bool contains(in Point p) {
1862 		return (p.x >= left && p.x < right && p.y >= top && p.y < bottom);
1863 	}
1864 
1865 	/// Returns true of the two rectangles at any point overlap
1866 	bool overlaps(in Rectangle r) {
1867 		// the -1 in here are because right and top are exclusive
1868 		return !((right-1) < r.left || (r.right-1) < left || (bottom-1) < r.top || (r.bottom-1) < top);
1869 	}
1870 
1871 	/++
1872 		Returns a Rectangle representing the intersection of this and the other given one.
1873 
1874 		History:
1875 			Added July 1, 2021
1876 	+/
1877 	Rectangle intersectionOf(in Rectangle r) {
1878 		auto tmp = Rectangle(max(left, r.left), max(top, r.top), min(right, r.right), min(bottom, r.bottom));
1879 		if(tmp.left >= tmp.right || tmp.top >= tmp.bottom)
1880 			tmp = Rectangle.init;
1881 
1882 		return tmp;
1883 	}
1884 }
1885 
1886 /++
1887 	A type to represent an angle, taking away ambiguity of if it wants degrees or radians.
1888 
1889 	---
1890 		Angle a = Angle.degrees(180);
1891 		Angle b = Angle.radians(3.14159);
1892 
1893 		// note there might be slight changes in precision due to internal conversions
1894 	---
1895 
1896 	History:
1897 		Added August 29, 2023 (dub v11.1)
1898 +/
1899 struct Angle {
1900 	private enum PI = 3.14159265358979;
1901 	private float angle;
1902 
1903 	pure @nogc nothrow @safe:
1904 
1905 	private this(float angle) {
1906 		this.angle = angle;
1907 	}
1908 
1909 	/++
1910 
1911 	+/
1912 	float degrees() const {
1913 		return angle * 180.0 / PI;
1914 	}
1915 
1916 	/// ditto
1917 	static Angle degrees(float deg) {
1918 		return Angle(deg * PI / 180.0);
1919 	}
1920 
1921 	/// ditto
1922 	float radians() const {
1923 		return angle;
1924 	}
1925 
1926 	/// ditto
1927 	static Angle radians(float rad) {
1928 		return Angle(rad);
1929 	}
1930 
1931 	/++
1932 		The +, -, +=, and -= operators all work on the angles too.
1933 	+/
1934 	Angle opBinary(string op : "+")(const Angle rhs) const {
1935 		return Angle(this.angle + rhs.angle);
1936 	}
1937 	/// ditto
1938 	Angle opBinary(string op : "-")(const Angle rhs) const {
1939 		return Angle(this.angle + rhs.angle);
1940 	}
1941 	/// ditto
1942 	Angle opOpAssign(string op : "+")(const Angle rhs) {
1943 		return this.angle += rhs.angle;
1944 	}
1945 	/// ditto
1946 	Angle opOpAssign(string op : "-")(const Angle rhs) {
1947 		return this.angle -= rhs.angle;
1948 	}
1949 
1950 	// maybe sin, cos, tan but meh you can .radians on them too.
1951 }
1952 
1953 private int max(int a, int b) @nogc nothrow pure @safe {
1954 	return a >= b ? a : b;
1955 }
1956 private int min(int a, int b) @nogc nothrow pure @safe {
1957 	return a <= b ? a : b;
1958 }
1959 
1960 /++
1961 	Implements a flood fill algorithm, like the bucket tool in
1962 	MS Paint.
1963 
1964 	Note it assumes `what.length == width*height`.
1965 
1966 	Params:
1967 		what = the canvas to work with, arranged as top to bottom, left to right elements
1968 		width = the width of the canvas
1969 		height = the height of the canvas
1970 		target = the type to replace. You may pass the existing value if you want to do what Paint does
1971 		replacement = the replacement value
1972 		x = the x-coordinate to start the fill (think of where the user clicked in Paint)
1973 		y = the y-coordinate to start the fill
1974 		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.
1975 +/
1976 void floodFill(T)(
1977 	T[] what, int width, int height, // the canvas to inspect
1978 	T target, T replacement, // fill params
1979 	int x, int y, bool delegate(int x, int y) @safe additionalCheck) // the node
1980 
1981 	// in(what.length == width * height) // gdc doesn't support this syntax yet so not gonna use it until that comes out.
1982 {
1983 	assert(what.length == width * height); // will use the contract above when gdc supports it
1984 
1985 	T node = what[y * width + x];
1986 
1987 	if(target == replacement) return;
1988 
1989 	if(node != target) return;
1990 
1991 	if(additionalCheck is null)
1992 		additionalCheck = (int, int) => true;
1993 
1994 	if(!additionalCheck(x, y))
1995 		return;
1996 
1997 	Point[] queue;
1998 
1999 	queue ~= Point(x, y);
2000 
2001 	while(queue.length) {
2002 		auto n = queue[0];
2003 		queue = queue[1 .. $];
2004 		//queue.assumeSafeAppend(); // lol @safe breakage
2005 
2006 		auto w = n;
2007 		int offset = cast(int) (n.y * width + n.x);
2008 		auto e = n;
2009 		auto eoffset = offset;
2010 		w.x--;
2011 		offset--;
2012 		while(w.x >= 0 && what[offset] == target && additionalCheck(w.x, w.y)) {
2013 			w.x--;
2014 			offset--;
2015 		}
2016 		while(e.x < width && what[eoffset] == target && additionalCheck(e.x, e.y)) {
2017 			e.x++;
2018 			eoffset++;
2019 		}
2020 
2021 		// to make it inclusive again
2022 		w.x++;
2023 		offset++;
2024 		foreach(o ; offset .. eoffset) {
2025 			what[o] = replacement;
2026 			if(w.y && what[o - width] == target && additionalCheck(w.x, w.y))
2027 				queue ~= Point(w.x, w.y - 1);
2028 			if(w.y + 1 < height && what[o + width] == target && additionalCheck(w.x, w.y))
2029 				queue ~= Point(w.x, w.y + 1);
2030 			w.x++;
2031 		}
2032 	}
2033 
2034 	/+
2035 	what[y * width + x] = replacement;
2036 
2037 	if(x)
2038 		floodFill(what, width, height, target, replacement,
2039 			x - 1, y, additionalCheck);
2040 
2041 	if(x != width - 1)
2042 		floodFill(what, width, height, target, replacement,
2043 			x + 1, y, additionalCheck);
2044 
2045 	if(y)
2046 		floodFill(what, width, height, target, replacement,
2047 			x, y - 1, additionalCheck);
2048 
2049 	if(y != height - 1)
2050 		floodFill(what, width, height, target, replacement,
2051 			x, y + 1, additionalCheck);
2052 	+/
2053 }
2054 
2055 // for scripting, so you can tag it without strictly needing to import arsd.jsvar
2056 enum arsd_jsvar_compatible = "arsd_jsvar_compatible";