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 @safe:
10 
11 // importing phobos explodes the size of this code 10x, so not doing it.
12 
13 private {
14 	double toInternal(T)(scope const(char)[] s) {
15 		double accumulator = 0.0;
16 		size_t i = s.length;
17 		foreach(idx, c; s) {
18 			if(c >= '0' && c <= '9') {
19 				accumulator *= 10;
20 				accumulator += c - '0';
21 			} else if(c == '.') {
22 				i = idx + 1;
23 				break;
24 			} else {
25 				string wtfIsWrongWithThisStupidLanguageWithItsBrokenSafeAttribute = "bad char to make double from ";
26 				wtfIsWrongWithThisStupidLanguageWithItsBrokenSafeAttribute ~= s;
27 				throw new Exception(wtfIsWrongWithThisStupidLanguageWithItsBrokenSafeAttribute);
28 			}
29 		}
30 
31 		double accumulator2 = 0.0;
32 		double count = 1;
33 		foreach(c; s[i .. $]) {
34 			if(c >= '0' && c <= '9') {
35 				accumulator2 *= 10;
36 				accumulator2 += c - '0';
37 				count *= 10;
38 			} else {
39 				string wtfIsWrongWithThisStupidLanguageWithItsBrokenSafeAttribute = "bad char to make double from ";
40 				wtfIsWrongWithThisStupidLanguageWithItsBrokenSafeAttribute ~= s;
41 				throw new Exception(wtfIsWrongWithThisStupidLanguageWithItsBrokenSafeAttribute);
42 			}
43 		}
44 
45 		return accumulator + accumulator2 / count;
46 	}
47 
48 	package(arsd) @trusted
49 	string toInternal(T)(int a) {
50 		if(a == 0)
51 			return "0";
52 		char[] ret;
53 		bool neg;
54 		if(a < 0) {
55 			neg = true;
56 			a = -a;
57 		}
58 		while(a) {
59 			ret ~= (a % 10) + '0';
60 			a /= 10;
61 		}
62 		for(int i = 0; i < ret.length / 2; i++) {
63 			char c = ret[i];
64 			ret[i] = ret[$ - i - 1];
65 			ret[$ - i - 1] = c;
66 		}
67 		if(neg)
68 			ret = "-" ~ ret;
69 
70 		return cast(string) ret;
71 	}
72 	string toInternal(T)(double a) {
73 		// a simplifying assumption here is the fact that we only use this in one place: toInternal!string(cast(double) a / 255)
74 		// thus we know this will always be between 0.0 and 1.0, inclusive.
75 		if(a <= 0.0)
76 			return "0.0";
77 		if(a >= 1.0)
78 			return "1.0";
79 		string ret = "0.";
80 		// I wonder if I can handle round off error any better. Phobos does, but that isn't worth 100 KB of code.
81 		int amt = cast(int)(a * 1000);
82 		return ret ~ toInternal!string(amt);
83 	}
84 
85 	nothrow @safe @nogc pure
86 	double absInternal(double a) { return a < 0 ? -a : a; }
87 	nothrow @safe @nogc pure
88 	double minInternal(double a, double b, double c) {
89 		auto m = a;
90 		if(b < m) m = b;
91 		if(c < m) m = c;
92 		return m;
93 	}
94 	nothrow @safe @nogc pure
95 	double maxInternal(double a, double b, double c) {
96 		auto m = a;
97 		if(b > m) m = b;
98 		if(c > m) m = c;
99 		return m;
100 	}
101 	nothrow @safe @nogc pure
102 	bool startsWithInternal(in char[] a, in char[] b) {
103 		return (a.length >= b.length && a[0 .. b.length] == b);
104 	}
105 	void splitInternal(scope inout(char)[] a, char c, scope void delegate(int, scope inout(char)[]) @safe dg) {
106 		int count;
107 		size_t previous = 0;
108 		foreach(i, char ch; a) {
109 			if(ch == c) {
110 				dg(count++, a[previous .. i]);
111 				previous = i + 1;
112 			}
113 		}
114 		if(previous != a.length)
115 			dg(count++, a[previous .. $]);
116 	}
117 	nothrow @safe @nogc pure
118 	inout(char)[] stripInternal(return inout(char)[] s) {
119 		foreach(i, char c; s)
120 			if(c != ' ' && c != '\t' && c != '\n') {
121 				s = s[i .. $];
122 				break;
123 			}
124 		for(int a = cast(int)(s.length - 1); a > 0; a--) {
125 			char c = s[a];
126 			if(c != ' ' && c != '\t' && c != '\n') {
127 				s = s[0 .. a + 1];
128 				break;
129 			}
130 		}
131 
132 		return s;
133 	}
134 }
135 
136 // done with mini-phobos
137 
138 /// Represents an RGBA color
139 struct Color {
140 
141 	@system static Color fromJsVar(T)(T v) { // it is a template so i don't have to actually import arsd.jsvar...
142 		return Color.fromString(v.get!string);
143 	}
144 
145 @safe:
146 	/++
147 		The color components are available as a static array, individual bytes, and a uint inside this union.
148 
149 		Since it is anonymous, you can use the inner members' names directly.
150 	+/
151 	union {
152 		ubyte[4] components; /// [r, g, b, a]
153 
154 		/// Holder for rgba individual components.
155 		struct {
156 			ubyte r; /// red
157 			ubyte g; /// green
158 			ubyte b; /// blue
159 			ubyte a; /// alpha. 255 == opaque
160 		}
161 
162 		uint asUint; /// The components as a single 32 bit value (beware of endian issues!)
163 	}
164 
165 	/++
166 		Returns a value compatible with [https://docs.microsoft.com/en-us/windows/win32/gdi/colorref|a Win32 COLORREF].
167 
168 		Please note that the alpha value is lost in translation.
169 
170 		History:
171 			Added November 27, 2021 (dub v10.4)
172 		See_Also:
173 			[fromWindowsColorRef]
174 	+/
175 	nothrow pure @nogc
176 	uint asWindowsColorRef() {
177 		uint cr;
178 		cr |= b << 16;
179 		cr |= g << 8;
180 		cr |= r;
181 		return cr;
182 	}
183 
184 	/++
185 		Constructs a Color from [https://docs.microsoft.com/en-us/windows/win32/gdi/colorref|a Win32 COLORREF].
186 
187 		History:
188 			Added November 27, 2021 (dub v10.4)
189 		See_Also:
190 			[asWindowsColorRef]
191 	+/
192 	nothrow pure @nogc
193 	static Color fromWindowsColorRef(uint cr) {
194 		return Color(cr & 0xff, (cr >> 8) & 0xff, (cr >> 16) & 0xff);
195 	}
196 
197 	/++
198 		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.
199 	+/
200 	nothrow pure @nogc
201 	static Color fromIntegers(int red, int green, int blue, int alpha = 255) {
202 		return Color(clampToByte(red), clampToByte(green), clampToByte(blue), clampToByte(alpha));
203 	}
204 
205 	/// 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.
206 	nothrow pure @nogc
207 	this(int red, int green, int blue, int alpha = 255) {
208 		this.r = cast(ubyte) red;
209 		this.g = cast(ubyte) green;
210 		this.b = cast(ubyte) blue;
211 		this.a = cast(ubyte) alpha;
212 	}
213 
214 	/// Static convenience functions for common color names
215 	nothrow pure @nogc
216 	static Color transparent() { return Color(0, 0, 0, 0); }
217 	/// Ditto
218 	nothrow pure @nogc
219 	static Color white() { return Color(255, 255, 255); }
220 	/// Ditto
221 	nothrow pure @nogc
222 	static Color gray() { return Color(128, 128, 128); }
223 	/// Ditto
224 	nothrow pure @nogc
225 	static Color black() { return Color(0, 0, 0); }
226 	/// Ditto
227 	nothrow pure @nogc
228 	static Color red() { return Color(255, 0, 0); }
229 	/// Ditto
230 	nothrow pure @nogc
231 	static Color green() { return Color(0, 255, 0); }
232 	/// Ditto
233 	nothrow pure @nogc
234 	static Color blue() { return Color(0, 0, 255); }
235 	/// Ditto
236 	nothrow pure @nogc
237 	static Color yellow() { return Color(255, 255, 0); }
238 	/// Ditto
239 	nothrow pure @nogc
240 	static Color teal() { return Color(0, 255, 255); }
241 	/// Ditto
242 	nothrow pure @nogc
243 	static Color purple() { return Color(128, 0, 128); }
244 	/// Ditto
245 	nothrow pure @nogc
246 	static Color magenta() { return Color(255, 0, 255); }
247 	/// Ditto
248 	nothrow pure @nogc
249 	static Color brown() { return Color(128, 64, 0); }
250 
251 	nothrow pure @nogc
252 	void premultiply() {
253 		r = (r * a) / 255;
254 		g = (g * a) / 255;
255 		b = (b * a) / 255;
256 	}
257 
258 	nothrow pure @nogc
259 	void unPremultiply() {
260 		r = cast(ubyte)(r * 255 / a);
261 		g = cast(ubyte)(g * 255 / a);
262 		b = cast(ubyte)(b * 255 / a);
263 	}
264 
265 
266 	/*
267 	ubyte[4] toRgbaArray() {
268 		return [r,g,b,a];
269 	}
270 	*/
271 
272 	/// Return black-and-white color
273 	Color toBW() () nothrow pure @safe @nogc {
274 		// FIXME: gamma?
275 		int intens = clampToByte(cast(int)(0.2126*r+0.7152*g+0.0722*b));
276 		return Color(intens, intens, intens, a);
277 	}
278 
279 	/// Makes a string that matches CSS syntax for websites
280 	string toCssString() const {
281 		if(a == 255)
282 			return "#" ~ toHexInternal(r) ~ toHexInternal(g) ~ toHexInternal(b);
283 		else {
284 			return "rgba("~toInternal!string(r)~", "~toInternal!string(g)~", "~toInternal!string(b)~", "~toInternal!string(cast(double)a / 255.0)~")";
285 		}
286 	}
287 
288 	/// Makes a hex string RRGGBBAA (aa only present if it is not 255)
289 	string toString() const {
290 		if(a == 255)
291 			return toCssString()[1 .. $];
292 		else
293 			return toRgbaHexString();
294 	}
295 
296 	/// returns RRGGBBAA, even if a== 255
297 	string toRgbaHexString() const {
298 		return toHexInternal(r) ~ toHexInternal(g) ~ toHexInternal(b) ~ toHexInternal(a);
299 	}
300 
301 	/// Gets a color by name, iff the name is one of the static members listed above
302 	static Color fromNameString(string s) {
303 		Color c;
304 		foreach(member; __traits(allMembers, Color)) {
305 			static if(__traits(compiles, c = __traits(getMember, Color, member))) {
306 				if(s == member)
307 					return __traits(getMember, Color, member);
308 			}
309 		}
310 		throw new Exception("Unknown color " ~ s);
311 	}
312 
313 	/++
314 		Reads a CSS style string to get the color. Understands #rrggbb, rgba(), hsl(), and rrggbbaa
315 
316 		History:
317 			The short-form hex string parsing (`#fff`) was added on April 10, 2020. (v7.2.0)
318 	+/
319 	static Color fromString(scope const(char)[] s) {
320 		s = s.stripInternal();
321 
322 		Color c;
323 		c.a = 255;
324 
325 		// trying named colors via the static no-arg methods here
326 		foreach(member; __traits(allMembers, Color)) {
327 			static if(__traits(compiles, c = __traits(getMember, Color, member))) {
328 				if(s == member)
329 					return __traits(getMember, Color, member);
330 			}
331 		}
332 
333 		// try various notations borrowed from CSS (though a little extended)
334 
335 		// hsl(h,s,l,a) where h is degrees and s,l,a are 0 >= x <= 1.0
336 		if(s.startsWithInternal("hsl(") || s.startsWithInternal("hsla(")) {
337 			assert(s[$-1] == ')');
338 			s = s[s.startsWithInternal("hsl(") ? 4 : 5  .. $ - 1]; // the closing paren
339 
340 			double[3] hsl;
341 			ubyte a = 255;
342 
343 			s.splitInternal(',', (int i, scope const(char)[] part) {
344 				if(i < 3)
345 					hsl[i] = toInternal!double(part.stripInternal);
346 				else
347 					a = clampToByte(cast(int) (toInternal!double(part.stripInternal) * 255));
348 			});
349 
350 			c = .fromHsl(hsl);
351 			c.a = a;
352 
353 			return c;
354 		}
355 
356 		// rgb(r,g,b,a) where r,g,b are 0-255 and a is 0-1.0
357 		if(s.startsWithInternal("rgb(") || s.startsWithInternal("rgba(")) {
358 			assert(s[$-1] == ')');
359 			s = s[s.startsWithInternal("rgb(") ? 4 : 5  .. $ - 1]; // the closing paren
360 
361 			s.splitInternal(',', (int i, scope const(char)[] part) {
362 				// lol the loop-switch pattern
363 				auto v = toInternal!double(part.stripInternal);
364 				switch(i) {
365 					case 0: // red
366 						c.r = clampToByte(cast(int) v);
367 					break;
368 					case 1:
369 						c.g = clampToByte(cast(int) v);
370 					break;
371 					case 2:
372 						c.b = clampToByte(cast(int) v);
373 					break;
374 					case 3:
375 						c.a = clampToByte(cast(int) (v * 255));
376 					break;
377 					default: // ignore
378 				}
379 			});
380 
381 			return c;
382 		}
383 
384 
385 
386 
387 		// otherwise let's try it as a hex string, really loosely
388 
389 		if(s.length && s[0] == '#')
390 			s = s[1 .. $];
391 
392 		// support short form #fff for example
393 		if(s.length == 3 || s.length == 4) {
394 			string n;
395 			n.reserve(8);
396 			foreach(ch; s) {
397 				n ~= ch;
398 				n ~= ch;
399 			}
400 			s = n;
401 		}
402 
403 		// not a built in... do it as a hex string
404 		if(s.length >= 2) {
405 			c.r = fromHexInternal(s[0 .. 2]);
406 			s = s[2 .. $];
407 		}
408 		if(s.length >= 2) {
409 			c.g = fromHexInternal(s[0 .. 2]);
410 			s = s[2 .. $];
411 		}
412 		if(s.length >= 2) {
413 			c.b = fromHexInternal(s[0 .. 2]);
414 			s = s[2 .. $];
415 		}
416 		if(s.length >= 2) {
417 			c.a = fromHexInternal(s[0 .. 2]);
418 			s = s[2 .. $];
419 		}
420 
421 		return c;
422 	}
423 
424 	/// from hsl
425 	static Color fromHsl(double h, double s, double l) {
426 		return .fromHsl(h, s, l);
427 	}
428 
429 	// this is actually branch-less for ints on x86, and even for longs on x86_64
430 	static ubyte clampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) {
431 		static if (__VERSION__ > 2067) pragma(inline, true);
432 		static if (T.sizeof == 2 || T.sizeof == 4) {
433 			static if (__traits(isUnsigned, T)) {
434 				return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24)));
435 			} else {
436 				n &= -cast(int)(n >= 0);
437 				return cast(ubyte)(n|((255-cast(int)n)>>31));
438 			}
439 		} else static if (T.sizeof == 1) {
440 			static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?");
441 			return cast(ubyte)n;
442 		} else static if (T.sizeof == 8) {
443 			static if (__traits(isUnsigned, T)) {
444 				return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56)));
445 			} else {
446 				n &= -cast(long)(n >= 0);
447 				return cast(ubyte)(n|((255-cast(long)n)>>63));
448 			}
449 		} else {
450 			static assert(false, "clampToByte: integer too big");
451 		}
452 	}
453 
454 	/** this mixin can be used to alphablend two `uint` colors;
455 	 * `colu32name` is variable that holds color to blend,
456 	 * `destu32name` is variable that holds "current" color (from surface, for example).
457 	 * alpha value of `destu32name` doesn't matter.
458 	 * alpha value of `colu32name` means: 255 for replace color, 0 for keep `destu32name`.
459 	 *
460 	 * WARNING! This function does blending in RGB space, and RGB space is not linear!
461 	 */
462 	public enum ColorBlendMixinStr(string colu32name, string destu32name) = "{
463 		immutable uint a_tmp_ = (256-(255-(("~colu32name~")>>24)))&(-(1-(((255-(("~colu32name~")>>24))+1)>>8))); // to not lose bits, but 255 should become 0
464 		immutable uint dc_tmp_ = ("~destu32name~")&0xffffff;
465 		immutable uint srb_tmp_ = (("~colu32name~")&0xff00ff);
466 		immutable uint sg_tmp_ = (("~colu32name~")&0x00ff00);
467 		immutable uint drb_tmp_ = (dc_tmp_&0xff00ff);
468 		immutable uint dg_tmp_ = (dc_tmp_&0x00ff00);
469 		immutable uint orb_tmp_ = (drb_tmp_+(((srb_tmp_-drb_tmp_)*a_tmp_+0x800080)>>8))&0xff00ff;
470 		immutable uint og_tmp_ = (dg_tmp_+(((sg_tmp_-dg_tmp_)*a_tmp_+0x008000)>>8))&0x00ff00;
471 		("~destu32name~") = (orb_tmp_|og_tmp_)|0xff000000; /*&0xffffff;*/
472 	}";
473 
474 
475 	/// Perform alpha-blending of `fore` to this color, return new color.
476 	/// WARNING! This function does blending in RGB space, and RGB space is not linear!
477 	Color alphaBlend (Color fore) const pure nothrow @trusted @nogc {
478 		version(LittleEndian) {
479 			static if (__VERSION__ > 2067) pragma(inline, true);
480 			Color res;
481 			res.asUint = asUint;
482 			mixin(ColorBlendMixinStr!("fore.asUint", "res.asUint"));
483 			return res;
484 		} else {
485 			alias foreground = fore;
486 			alias background = this;
487 			foreach(idx, ref part; foreground.components)
488 				part = cast(ubyte) (part * foreground.a / 255 +  background.components[idx] * (255 - foreground.a) / 255);
489 			return foreground;
490 		}
491 	}
492 }
493 
494 void premultiplyBgra(ubyte[] bgra) pure @nogc @safe nothrow in { assert(bgra.length == 4); } do {
495 	auto a = bgra[3];
496 
497 	bgra[2] = (bgra[2] * a) / 255;
498 	bgra[1] = (bgra[1] * a) / 255;
499 	bgra[0] = (bgra[0] * a) / 255;
500 }
501 
502 void unPremultiplyRgba(ubyte[] rgba) pure @nogc @safe nothrow in { assert(rgba.length == 4); } do {
503 	auto a = rgba[3];
504 
505 	rgba[0] = cast(ubyte)(rgba[0] * 255 / a);
506 	rgba[1] = cast(ubyte)(rgba[1] * 255 / a);
507 	rgba[2] = cast(ubyte)(rgba[2] * 255 / a);
508 }
509 
510 unittest {
511 	Color c = Color.fromString("#fff");
512 	assert(c == Color.white);
513 	assert(c == Color.fromString("#ffffff"));
514 
515 	c = Color.fromString("#f0f");
516 	assert(c == Color.fromString("rgb(255, 0, 255)"));
517 }
518 
519 nothrow @safe
520 private string toHexInternal(ubyte b) {
521 	string s;
522 	if(b < 16)
523 		s ~= '0';
524 	else {
525 		ubyte t = (b & 0xf0) >> 4;
526 		if(t >= 10)
527 			s ~= 'A' + t - 10;
528 		else
529 			s ~= '0' + t;
530 		b &= 0x0f;
531 	}
532 	if(b >= 10)
533 		s ~= 'A' + b - 10;
534 	else
535 		s ~= '0' + b;
536 
537 	return s;
538 }
539 
540 nothrow @safe @nogc pure
541 private ubyte fromHexInternal(in char[] s) {
542 	int result = 0;
543 
544 	int exp = 1;
545 	//foreach(c; retro(s)) { // FIXME: retro doesn't work right in dtojs
546 	foreach_reverse(c; s) {
547 		if(c >= 'A' && c <= 'F')
548 			result += exp * (c - 'A' + 10);
549 		else if(c >= 'a' && c <= 'f')
550 			result += exp * (c - 'a' + 10);
551 		else if(c >= '0' && c <= '9')
552 			result += exp * (c - '0');
553 		else
554 			// throw new Exception("invalid hex character: " ~ cast(char) c);
555 			return 0;
556 
557 		exp *= 16;
558 	}
559 
560 	return cast(ubyte) result;
561 }
562 
563 /// Converts hsl to rgb
564 Color fromHsl(real[3] hsl) nothrow pure @safe @nogc {
565 	return fromHsl(cast(double) hsl[0], cast(double) hsl[1], cast(double) hsl[2]);
566 }
567 
568 Color fromHsl(double[3] hsl) nothrow pure @safe @nogc {
569 	return fromHsl(hsl[0], hsl[1], hsl[2]);
570 }
571 
572 /// Converts hsl to rgb
573 Color fromHsl(double h, double s, double l, double a = 255) nothrow pure @safe @nogc {
574 	h = h % 360;
575 
576 	double C = (1 - absInternal(2 * l - 1)) * s;
577 
578 	double hPrime = h / 60;
579 
580 	double X = C * (1 - absInternal(hPrime % 2 - 1));
581 
582 	double r, g, b;
583 
584 	if(h is double.nan)
585 		r = g = b = 0;
586 	else if (hPrime >= 0 && hPrime < 1) {
587 		r = C;
588 		g = X;
589 		b = 0;
590 	} else if (hPrime >= 1 && hPrime < 2) {
591 		r = X;
592 		g = C;
593 		b = 0;
594 	} else if (hPrime >= 2 && hPrime < 3) {
595 		r = 0;
596 		g = C;
597 		b = X;
598 	} else if (hPrime >= 3 && hPrime < 4) {
599 		r = 0;
600 		g = X;
601 		b = C;
602 	} else if (hPrime >= 4 && hPrime < 5) {
603 		r = X;
604 		g = 0;
605 		b = C;
606 	} else if (hPrime >= 5 && hPrime < 6) {
607 		r = C;
608 		g = 0;
609 		b = X;
610 	}
611 
612 	double m = l - C / 2;
613 
614 	r += m;
615 	g += m;
616 	b += m;
617 
618 	return Color(
619 		cast(int)(r * 255),
620 		cast(int)(g * 255),
621 		cast(int)(b * 255),
622 		cast(int)(a));
623 }
624 
625 /// Assumes the input `u` is already between 0 and 1 fyi.
626 nothrow pure @safe @nogc
627 double srgbToLinearRgb(double u) {
628 	if(u < 0.4045)
629 		return u / 12.92;
630 	else
631 		return ((u + 0.055) / 1.055) ^^ 2.4;
632 }
633 
634 /// 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.
635 double[3] toHsl(Color c, bool useWeightedLightness = false) nothrow pure @trusted @nogc {
636 	double r1 = cast(double) c.r / 255;
637 	double g1 = cast(double) c.g / 255;
638 	double b1 = cast(double) c.b / 255;
639 
640 	double maxColor = maxInternal(r1, g1, b1);
641 	double minColor = minInternal(r1, g1, b1);
642 
643 	double L = (maxColor + minColor) / 2 ;
644 	if(useWeightedLightness) {
645 		// the colors don't affect the eye equally
646 		// this is a little more accurate than plain HSL numbers
647 		L = 0.2126*srgbToLinearRgb(r1) + 0.7152*srgbToLinearRgb(g1) + 0.0722*srgbToLinearRgb(b1);
648 		// maybe a better number is 299, 587, 114
649 	}
650 	double S = 0;
651 	double H = 0;
652 	if(maxColor != minColor) {
653 		if(L < 0.5) {
654 			S = (maxColor - minColor) / (maxColor + minColor);
655 		} else {
656 			S = (maxColor - minColor) / (2.0 - maxColor - minColor);
657 		}
658 		if(r1 == maxColor) {
659 			H = (g1-b1) / (maxColor - minColor);
660 		} else if(g1 == maxColor) {
661 			H = 2.0 + (b1 - r1) / (maxColor - minColor);
662 		} else {
663 			H = 4.0 + (r1 - g1) / (maxColor - minColor);
664 		}
665 	}
666 
667 	H = H * 60;
668 	if(H < 0){
669 		H += 360;
670 	}
671 
672 	return [H, S, L]; 
673 }
674 
675 /// .
676 Color lighten(Color c, double percentage) nothrow pure @safe @nogc {
677 	auto hsl = toHsl(c);
678 	hsl[2] *= (1 + percentage);
679 	if(hsl[2] > 1)
680 		hsl[2] = 1;
681 	return fromHsl(hsl);
682 }
683 
684 /// .
685 Color darken(Color c, double percentage) nothrow pure @safe @nogc {
686 	auto hsl = toHsl(c);
687 	hsl[2] *= (1 - percentage);
688 	return fromHsl(hsl);
689 }
690 
691 /// for light colors, call darken. for dark colors, call lighten.
692 /// The goal: get toward center grey.
693 Color moderate(Color c, double percentage) nothrow pure @safe @nogc {
694 	auto hsl = toHsl(c);
695 	if(hsl[2] > 0.5)
696 		hsl[2] *= (1 - percentage);
697 	else {
698 		if(hsl[2] <= 0.01) // if we are given black, moderating it means getting *something* out
699 			hsl[2] = percentage;
700 		else
701 			hsl[2] *= (1 + percentage);
702 	}
703 	if(hsl[2] > 1)
704 		hsl[2] = 1;
705 	return fromHsl(hsl);
706 }
707 
708 /// the opposite of moderate. Make darks darker and lights lighter
709 Color extremify(Color c, double percentage) nothrow pure @safe @nogc {
710 	auto hsl = toHsl(c, true);
711 	if(hsl[2] < 0.5)
712 		hsl[2] *= (1 - percentage);
713 	else
714 		hsl[2] *= (1 + percentage);
715 	if(hsl[2] > 1)
716 		hsl[2] = 1;
717 	return fromHsl(hsl);
718 }
719 
720 /// Move around the lightness wheel, trying not to break on moderate things
721 Color oppositeLightness(Color c) nothrow pure @safe @nogc {
722 	auto hsl = toHsl(c);
723 
724 	auto original = hsl[2];
725 
726 	if(original > 0.4 && original < 0.6)
727 		hsl[2] = 0.8 - original; // so it isn't quite the same
728 	else
729 		hsl[2] = 1 - original;
730 
731 	return fromHsl(hsl);
732 }
733 
734 /// Try to determine a text color - either white or black - based on the input
735 Color makeTextColor(Color c) nothrow pure @safe @nogc {
736 	auto hsl = toHsl(c, true); // give green a bonus for contrast
737 	if(hsl[2] > 0.71)
738 		return Color(0, 0, 0);
739 	else
740 		return Color(255, 255, 255);
741 }
742 
743 // These provide functional access to hsl manipulation; useful if you need a delegate
744 
745 Color setLightness(Color c, double lightness) nothrow pure @safe @nogc {
746 	auto hsl = toHsl(c);
747 	hsl[2] = lightness;
748 	return fromHsl(hsl);
749 }
750 
751 
752 ///
753 Color rotateHue(Color c, double degrees) nothrow pure @safe @nogc {
754 	auto hsl = toHsl(c);
755 	hsl[0] += degrees;
756 	return fromHsl(hsl);
757 }
758 
759 ///
760 Color setHue(Color c, double hue) nothrow pure @safe @nogc {
761 	auto hsl = toHsl(c);
762 	hsl[0] = hue;
763 	return fromHsl(hsl);
764 }
765 
766 ///
767 Color desaturate(Color c, double percentage) nothrow pure @safe @nogc {
768 	auto hsl = toHsl(c);
769 	hsl[1] *= (1 - percentage);
770 	return fromHsl(hsl);
771 }
772 
773 ///
774 Color saturate(Color c, double percentage) nothrow pure @safe @nogc {
775 	auto hsl = toHsl(c);
776 	hsl[1] *= (1 + percentage);
777 	if(hsl[1] > 1)
778 		hsl[1] = 1;
779 	return fromHsl(hsl);
780 }
781 
782 ///
783 Color setSaturation(Color c, double saturation) nothrow pure @safe @nogc {
784 	auto hsl = toHsl(c);
785 	hsl[1] = saturation;
786 	return fromHsl(hsl);
787 }
788 
789 
790 /*
791 void main(string[] args) {
792 	auto color1 = toHsl(Color(255, 0, 0));
793 	auto color = fromHsl(color1[0] + 60, color1[1], color1[2]);
794 
795 	writefln("#%02x%02x%02x", color.r, color.g, color.b);
796 }
797 */
798 
799 /* Color algebra functions */
800 
801 /* Alpha putpixel looks like this:
802 
803 void putPixel(Image i, Color c) {
804 	Color b;
805 	b.r = i.data[(y * i.width + x) * bpp + 0];
806 	b.g = i.data[(y * i.width + x) * bpp + 1];
807 	b.b = i.data[(y * i.width + x) * bpp + 2];
808 	b.a = i.data[(y * i.width + x) * bpp + 3];
809 
810 	float ca = cast(float) c.a / 255;
811 
812 	i.data[(y * i.width + x) * bpp + 0] = alpha(c.r, ca, b.r);
813 	i.data[(y * i.width + x) * bpp + 1] = alpha(c.g, ca, b.g);
814 	i.data[(y * i.width + x) * bpp + 2] = alpha(c.b, ca, b.b);
815 	i.data[(y * i.width + x) * bpp + 3] = alpha(c.a, ca, b.a);
816 }
817 
818 ubyte alpha(ubyte c1, float alpha, ubyte onto) {
819 	auto got = (1 - alpha) * onto + alpha * c1;
820 
821 	if(got > 255)
822 		return 255;
823 	return cast(ubyte) got;
824 }
825 
826 So, given the background color and the resultant color, what was
827 composited on to it?
828 */
829 
830 ///
831 ubyte unalpha(ubyte colorYouHave, float alpha, ubyte backgroundColor) nothrow pure @safe @nogc {
832 	// resultingColor = (1-alpha) * backgroundColor + alpha * answer
833 	auto resultingColorf = cast(float) colorYouHave;
834 	auto backgroundColorf = cast(float) backgroundColor;
835 
836 	auto answer = (resultingColorf - backgroundColorf + alpha * backgroundColorf) / alpha;
837 	return Color.clampToByte(cast(int) answer);
838 }
839 
840 ///
841 ubyte makeAlpha(ubyte colorYouHave, ubyte backgroundColor/*, ubyte foreground = 0x00*/) nothrow pure @safe @nogc {
842 	//auto foregroundf = cast(float) foreground;
843 	auto foregroundf = 0.00f;
844 	auto colorYouHavef = cast(float) colorYouHave;
845 	auto backgroundColorf = cast(float) backgroundColor;
846 
847 	// colorYouHave = backgroundColorf - alpha * backgroundColorf + alpha * foregroundf
848 	auto alphaf = 1 - colorYouHave / backgroundColorf;
849 	alphaf *= 255;
850 
851 	return Color.clampToByte(cast(int) alphaf);
852 }
853 
854 
855 int fromHex(string s) {
856 	int result = 0;
857 
858 	int exp = 1;
859 	// foreach(c; retro(s)) {
860 	foreach_reverse(c; s) {
861 		if(c >= 'A' && c <= 'F')
862 			result += exp * (c - 'A' + 10);
863 		else if(c >= 'a' && c <= 'f')
864 			result += exp * (c - 'a' + 10);
865 		else if(c >= '0' && c <= '9')
866 			result += exp * (c - '0');
867 		else
868 			throw new Exception("invalid hex character: " ~ cast(char) c);
869 
870 		exp *= 16;
871 	}
872 
873 	return result;
874 }
875 
876 ///
877 Color colorFromString(string s) {
878 	if(s.length == 0)
879 		return Color(0,0,0,255);
880 	if(s[0] == '#')
881 		s = s[1..$];
882 	assert(s.length == 6 || s.length == 8);
883 
884 	Color c;
885 
886 	c.r = cast(ubyte) fromHex(s[0..2]);
887 	c.g = cast(ubyte) fromHex(s[2..4]);
888 	c.b = cast(ubyte) fromHex(s[4..6]);
889 	if(s.length == 8)
890 		c.a = cast(ubyte) fromHex(s[6..8]);
891 	else
892 		c.a = 255;
893 
894 	return c;
895 }
896 
897 /*
898 import browser.window;
899 import std.conv;
900 void main() {
901 	import browser.document;
902 	foreach(ele; document.querySelectorAll("input")) {
903 		ele.addEventListener("change", {
904 			auto h = toInternal!double(document.querySelector("input[name=h]").value);
905 			auto s = toInternal!double(document.querySelector("input[name=s]").value);
906 			auto l = toInternal!double(document.querySelector("input[name=l]").value);
907 
908 			Color c = Color.fromHsl(h, s, l);
909 
910 			auto e = document.getElementById("example");
911 			e.style.backgroundColor = c.toCssString();
912 
913 			// JSElement __js_this;
914 			// __js_this.style.backgroundColor = c.toCssString();
915 		}, false);
916 	}
917 }
918 */
919 
920 
921 
922 /**
923 	This provides two image classes and a bunch of functions that work on them.
924 
925 	Why are they separate classes? I think the operations on the two of them
926 	are necessarily different. There's a whole bunch of operations that only
927 	really work on truecolor (blurs, gradients), and a few that only work
928 	on indexed images (palette swaps).
929 
930 	Even putpixel is pretty different. On indexed, it is a palette entry's
931 	index number. On truecolor, it is the actual color.
932 
933 	A greyscale image is the weird thing in the middle. It is truecolor, but
934 	fits in the same size as indexed. Still, I'd say it is a specialization
935 	of truecolor.
936 
937 	There is a subset that works on both
938 
939 */
940 
941 /// An image in memory
942 interface MemoryImage {
943 	//IndexedImage convertToIndexedImage() const;
944 	//TrueColorImage convertToTrueColor() const;
945 
946 	/// gets it as a TrueColorImage. May return this or may do a conversion and return a new image
947 	TrueColorImage getAsTrueColorImage() pure nothrow @safe;
948 
949 	/// Image width, in pixels
950 	int width() const pure nothrow @safe @nogc;
951 
952 	/// Image height, in pixels
953 	int height() const pure nothrow @safe @nogc;
954 
955 	/// Get image pixel. Slow, but returns valid RGBA color (completely transparent for off-image pixels).
956 	Color getPixel(int x, int y) const pure nothrow @safe @nogc;
957 
958   /// Set image pixel.
959 	void setPixel(int x, int y, in Color clr) nothrow @safe;
960 
961 	/// Returns a copy of the image
962 	MemoryImage clone() const pure nothrow @safe;
963 
964 	/// Load image from file. This will import arsd.image to do the actual work, and cost nothing if you don't use it.
965 	static MemoryImage fromImage(T : const(char)[]) (T filename) @trusted {
966 		static if (__traits(compiles, (){import arsd.image;})) {
967 			// yay, we have image loader here, try it!
968 			import arsd.image;
969 			return loadImageFromFile(filename);
970 		} else {
971 			static assert(0, "please provide 'arsd.image' to load images!");
972 		}
973 	}
974 
975 	// ***This method is deliberately not publicly documented.***
976 	// What it does is unconditionally frees internal image storage, without any sanity checks.
977 	// If you will do this, make sure that you have no references to image data left (like
978 	// slices of [data] array, for example). Those references will become invalid, and WILL
979 	// lead to Undefined Behavior.
980 	// tl;dr: IF YOU HAVE *ANY* QUESTIONS REGARDING THIS COMMENT, DON'T USE THIS!
981 	// Note to implementors: it is safe to simply do nothing in this method.
982 	// Also, it should be safe to call this method twice or more.
983 	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
984 
985 	/// Convenient alias for `fromImage`
986 	alias fromImageFile = fromImage;
987 }
988 
989 /// An image that consists of indexes into a color palette. Use [getAsTrueColorImage]() if you don't care about palettes
990 class IndexedImage : MemoryImage {
991 	bool hasAlpha;
992 
993 	/// .
994 	Color[] palette;
995 	/// the data as indexes into the palette. Stored left to right, top to bottom, no padding.
996 	ubyte[] data;
997 
998 	override void clearInternal () nothrow @system {// @nogc {
999 		import core.memory : GC;
1000 		// it is safe to call [GC.free] with `null` pointer.
1001 		GC.free(GC.addrOf(palette.ptr)); palette = null;
1002 		GC.free(GC.addrOf(data.ptr)); data = null;
1003 		_width = _height = 0;
1004 	}
1005 
1006 	/// .
1007 	override int width() const pure nothrow @safe @nogc {
1008 		return _width;
1009 	}
1010 
1011 	/// .
1012 	override int height() const pure nothrow @safe @nogc {
1013 		return _height;
1014 	}
1015 
1016 	/// .
1017 	override IndexedImage clone() const pure nothrow @trusted {
1018 		auto n = new IndexedImage(width, height);
1019 		n.data[] = this.data[]; // the data member is already there, so array copy
1020 		n.palette = this.palette.dup; // and here we need to allocate too, so dup
1021 		n.hasAlpha = this.hasAlpha;
1022 		return n;
1023 	}
1024 
1025 	override Color getPixel(int x, int y) const pure nothrow @trusted @nogc {
1026 		if (x >= 0 && y >= 0 && x < _width && y < _height) {
1027 			size_t pos = cast(size_t)y*_width+x;
1028 			if (pos >= data.length) return Color(0, 0, 0, 0);
1029 			ubyte b = data.ptr[pos];
1030 			if (b >= palette.length) return Color(0, 0, 0, 0);
1031 			return palette.ptr[b];
1032 		} else {
1033 			return Color(0, 0, 0, 0);
1034 		}
1035 	}
1036 
1037 	override void setPixel(int x, int y, in Color clr) nothrow @trusted {
1038 		if (x >= 0 && y >= 0 && x < _width && y < _height) {
1039 			size_t pos = cast(size_t)y*_width+x;
1040 			if (pos >= data.length) return;
1041 			ubyte pidx = findNearestColor(palette, clr);
1042 			if (palette.length < 255 &&
1043 				 (palette.ptr[pidx].r != clr.r || palette.ptr[pidx].g != clr.g || palette.ptr[pidx].b != clr.b || palette.ptr[pidx].a != clr.a)) {
1044 				// add new color
1045 				pidx = addColor(clr);
1046 			}
1047 			data.ptr[pos] = pidx;
1048 		}
1049 	}
1050 
1051 	private int _width;
1052 	private int _height;
1053 
1054 	/// .
1055 	this(int w, int h) pure nothrow @safe {
1056 		_width = w;
1057 		_height = h;
1058 
1059         // ensure that the computed size does not exceed basic address space limits
1060         assert(cast(ulong)w * h  <= size_t.max);
1061         // upcast to avoid overflow for images larger than 536 Mpix
1062 		data = new ubyte[cast(size_t)w*h];
1063 	}
1064 
1065 	/*
1066 	void resize(int w, int h, bool scale) {
1067 
1068 	}
1069 	*/
1070 
1071 	/// returns a new image
1072 	override TrueColorImage getAsTrueColorImage() pure nothrow @safe {
1073 		return convertToTrueColor();
1074 	}
1075 
1076 	/// Creates a new TrueColorImage based on this data
1077 	TrueColorImage convertToTrueColor() const pure nothrow @trusted {
1078 		auto tci = new TrueColorImage(width, height);
1079 		foreach(i, b; data) {
1080 			tci.imageData.colors[i] = palette[b];
1081 		}
1082 		return tci;
1083 	}
1084 
1085 	/// Gets an exact match, if possible, adds if not. See also: the findNearestColor free function.
1086 	ubyte getOrAddColor(Color c) nothrow @trusted {
1087 		foreach(i, co; palette) {
1088 			if(c == co)
1089 				return cast(ubyte) i;
1090 		}
1091 
1092 		return addColor(c);
1093 	}
1094 
1095 	/// Number of colors currently in the palette (note: palette entries are not necessarily used in the image data)
1096 	int numColors() const pure nothrow @trusted @nogc {
1097 		return cast(int) palette.length;
1098 	}
1099 
1100 	/// Adds an entry to the palette, returning its index
1101 	ubyte addColor(Color c) nothrow @trusted {
1102 		assert(palette.length < 256);
1103 		if(c.a != 255)
1104 			hasAlpha = true;
1105 		palette ~= c;
1106 
1107 		return cast(ubyte) (palette.length - 1);
1108 	}
1109 }
1110 
1111 /// An RGBA array of image data. Use the free function quantize() to convert to an IndexedImage
1112 class TrueColorImage : MemoryImage {
1113 //	bool hasAlpha;
1114 //	bool isGreyscale;
1115 
1116 	//ubyte[] data; // stored as rgba quads, upper left to right to bottom
1117 	/// .
1118 	struct Data {
1119 		ubyte[] bytes; /// the data as rgba bytes. Stored left to right, top to bottom, no padding.
1120 		// the union is no good because the length of the struct is wrong!
1121 
1122 		/// the same data as Color structs
1123 		@trusted // the cast here is typically unsafe, but it is ok
1124 		// here because I guarantee the layout, note the static assert below
1125 		@property inout(Color)[] colors() inout pure nothrow @nogc {
1126 			return cast(inout(Color)[]) bytes;
1127 		}
1128 
1129 		static assert(Color.sizeof == 4);
1130 	}
1131 
1132 	/// .
1133 	Data imageData;
1134 	alias imageData.bytes data;
1135 
1136 	int _width;
1137 	int _height;
1138 
1139 	override void clearInternal () nothrow @system {// @nogc {
1140 		import core.memory : GC;
1141 		// it is safe to call [GC.free] with `null` pointer.
1142 		GC.free(GC.addrOf(imageData.bytes.ptr)); imageData.bytes = null;
1143 		_width = _height = 0;
1144 	}
1145 
1146 	/// .
1147 	override TrueColorImage clone() const pure nothrow @trusted {
1148 		auto n = new TrueColorImage(width, height);
1149 		n.imageData.bytes[] = this.imageData.bytes[]; // copy into existing array ctor allocated
1150 		return n;
1151 	}
1152 
1153 	/// .
1154 	override int width() const pure nothrow @trusted @nogc { return _width; }
1155 	///.
1156 	override int height() const pure nothrow @trusted @nogc { return _height; }
1157 
1158 	override Color getPixel(int x, int y) const pure nothrow @trusted @nogc {
1159 		if (x >= 0 && y >= 0 && x < _width && y < _height) {
1160 			size_t pos = cast(size_t)y*_width+x;
1161 			return imageData.colors.ptr[pos];
1162 		} else {
1163 			return Color(0, 0, 0, 0);
1164 		}
1165 	}
1166 
1167 	override void setPixel(int x, int y, in Color clr) nothrow @trusted {
1168 		if (x >= 0 && y >= 0 && x < _width && y < _height) {
1169 			size_t pos = cast(size_t)y*_width+x;
1170 			if (pos < imageData.bytes.length/4) imageData.colors.ptr[pos] = clr;
1171 		}
1172 	}
1173 
1174 	/// .
1175 	this(int w, int h) pure nothrow @safe {
1176 		_width = w;
1177 		_height = h;
1178 
1179 		// ensure that the computed size does not exceed basic address space limits
1180         assert(cast(ulong)w * h * 4 <= size_t.max);
1181         // upcast to avoid overflow for images larger than 536 Mpix
1182 		imageData.bytes = new ubyte[cast(size_t)w * h * 4];
1183 	}
1184 
1185 	/// Creates with existing data. The data pointer is stored here.
1186 	this(int w, int h, ubyte[] data) pure nothrow @safe {
1187 		_width = w;
1188 		_height = h;
1189 		assert(cast(ulong)w * h * 4 <= size_t.max);
1190 		assert(data.length == cast(size_t)w * h * 4);
1191 		imageData.bytes = data;
1192 	}
1193 
1194 	/// Returns this
1195 	override TrueColorImage getAsTrueColorImage() pure nothrow @safe {
1196 		return this;
1197 	}
1198 }
1199 
1200 /+
1201 /// An RGB array of image data.
1202 class TrueColorImageWithoutAlpha : MemoryImage {
1203 	struct Data {
1204 		ubyte[] bytes; // the data as rgba bytes. Stored left to right, top to bottom, no padding.
1205 	}
1206 
1207 	/// .
1208 	Data imageData;
1209 
1210 	int _width;
1211 	int _height;
1212 
1213 	override void clearInternal () nothrow @system {// @nogc {
1214 		import core.memory : GC;
1215 		// it is safe to call [GC.free] with `null` pointer.
1216 		GC.free(imageData.bytes.ptr); imageData.bytes = null;
1217 		_width = _height = 0;
1218 	}
1219 
1220 	/// .
1221 	override TrueColorImageWithoutAlpha clone() const pure nothrow @trusted {
1222 		auto n = new TrueColorImageWithoutAlpha(width, height);
1223 		n.imageData.bytes[] = this.imageData.bytes[]; // copy into existing array ctor allocated
1224 		return n;
1225 	}
1226 
1227 	/// .
1228 	override int width() const pure nothrow @trusted @nogc { return _width; }
1229 	///.
1230 	override int height() const pure nothrow @trusted @nogc { return _height; }
1231 
1232 	override Color getPixel(int x, int y) const pure nothrow @trusted @nogc {
1233 		if (x >= 0 && y >= 0 && x < _width && y < _height) {
1234 			uint pos = (y*_width+x) * 3;
1235 			return Color(imageData.bytes[0], imageData.bytes[1], imageData.bytes[2], 255);
1236 		} else {
1237 			return Color(0, 0, 0, 0);
1238 		}
1239 	}
1240 
1241 	override void setPixel(int x, int y, in Color clr) nothrow @trusted {
1242 		if (x >= 0 && y >= 0 && x < _width && y < _height) {
1243 			uint pos = y*_width+x;
1244 			//if (pos < imageData.bytes.length/4) imageData.colors.ptr[pos] = clr;
1245 			// FIXME
1246 		}
1247 	}
1248 
1249 	/// .
1250 	this(int w, int h) pure nothrow @safe {
1251 		_width = w;
1252 		_height = h;
1253 		imageData.bytes = new ubyte[w*h*3];
1254 	}
1255 
1256 	/// Creates with existing data. The data pointer is stored here.
1257 	this(int w, int h, ubyte[] data) pure nothrow @safe {
1258 		_width = w;
1259 		_height = h;
1260 		assert(data.length == w * h * 3);
1261 		imageData.bytes = data;
1262 	}
1263 
1264 	///
1265 	override TrueColorImage getAsTrueColorImage() pure nothrow @safe {
1266 		// FIXME
1267 		//return this;
1268 	}
1269 }
1270 +/
1271 
1272 
1273 alias extern(C) int function(scope const void*, scope const void*) @system Comparator;
1274 @trusted void nonPhobosSort(T)(T[] obj,  Comparator comparator) {
1275 	import core.stdc.stdlib;
1276 	qsort(obj.ptr, obj.length, typeof(obj[0]).sizeof, comparator);
1277 }
1278 
1279 /// Converts true color to an indexed image. It uses palette as the starting point, adding entries
1280 /// until maxColors as needed. If palette is null, it creates a whole new palette.
1281 ///
1282 /// After quantizing the image, it applies a dithering algorithm.
1283 ///
1284 /// This is not written for speed.
1285 IndexedImage quantize(in TrueColorImage img, Color[] palette = null, in int maxColors = 256)
1286 	// this is just because IndexedImage assumes ubyte palette values
1287 	in { assert(maxColors <= 256); }
1288 do {
1289 	int[Color] uses;
1290 	foreach(pixel; img.imageData.colors) {
1291 		if(auto i = pixel in uses) {
1292 			(*i)++;
1293 		} else {
1294 			uses[pixel] = 1;
1295 		}
1296 	}
1297 
1298 	struct ColorUse {
1299 		Color c;
1300 		int uses;
1301 		//string toString() { import std.conv; return c.toCssString() ~ " x " ~ to!string(uses); }
1302 		int opCmp(ref const ColorUse co) const {
1303 			return co.uses - uses;
1304 		}
1305 		extern(C) static int comparator(scope const void* lhs, scope const void* rhs) {
1306 			return (cast(ColorUse*)rhs).uses - (cast(ColorUse*)lhs).uses;
1307 		}
1308 	}
1309 
1310 	ColorUse[] sorted;
1311 
1312 	foreach(color, count; uses)
1313 		sorted ~= ColorUse(color, count);
1314 
1315 	uses = null;
1316 
1317 	nonPhobosSort(sorted, &ColorUse.comparator);
1318 	// or, with phobos, but that adds 70ms to compile time
1319 	//import std.algorithm.sorting : sort;
1320 	//sort(sorted);
1321 
1322 	ubyte[Color] paletteAssignments;
1323 	foreach(idx, entry; palette)
1324 		paletteAssignments[entry] = cast(ubyte) idx;
1325 
1326 	// For the color assignments from the image, I do multiple passes, decreasing the acceptable
1327 	// distance each time until we're full.
1328 
1329 	// This is probably really slow.... but meh it gives pretty good results.
1330 
1331 	auto ddiff = 32;
1332 	outer: for(int d1 = 128; d1 >= 0; d1 -= ddiff) {
1333 	auto minDist = d1*d1;
1334 	if(d1 <= 64)
1335 		ddiff = 16;
1336 	if(d1 <= 32)
1337 		ddiff = 8;
1338 	foreach(possibility; sorted) {
1339 		if(palette.length == maxColors)
1340 			break;
1341 		if(palette.length) {
1342 			auto co = palette[findNearestColor(palette, possibility.c)];
1343 			auto pixel = possibility.c;
1344 
1345 			auto dr = cast(int) co.r - pixel.r;
1346 			auto dg = cast(int) co.g - pixel.g;
1347 			auto db = cast(int) co.b - pixel.b;
1348 
1349 			auto dist = dr*dr + dg*dg + db*db;
1350 			// not good enough variety to justify an allocation yet
1351 			if(dist < minDist)
1352 				continue;
1353 		}
1354 		paletteAssignments[possibility.c] = cast(ubyte) palette.length;
1355 		palette ~= possibility.c;
1356 	}
1357 	}
1358 
1359 	// Final pass: just fill in any remaining space with the leftover common colors
1360 	while(palette.length < maxColors && sorted.length) {
1361 		if(sorted[0].c !in paletteAssignments) {
1362 			paletteAssignments[sorted[0].c] = cast(ubyte) palette.length;
1363 			palette ~= sorted[0].c;
1364 		}
1365 		sorted = sorted[1 .. $];
1366 	}
1367 
1368 
1369 	bool wasPerfect = true;
1370 	auto newImage = new IndexedImage(img.width, img.height);
1371 	newImage.palette = palette;
1372 	foreach(idx, pixel; img.imageData.colors) {
1373 		if(auto p = pixel in paletteAssignments)
1374 			newImage.data[idx] = *p;
1375 		else {
1376 			// gotta find the closest one...
1377 			newImage.data[idx] = findNearestColor(palette, pixel);
1378 			wasPerfect = false;
1379 		}
1380 	}
1381 
1382 	if(!wasPerfect)
1383 		floydSteinbergDither(newImage, img);
1384 
1385 	return newImage;
1386 }
1387 
1388 /// Finds the best match for pixel in palette (currently by checking for minimum euclidean distance in rgb colorspace)
1389 ubyte findNearestColor(in Color[] palette, in Color pixel) nothrow pure @trusted @nogc {
1390 	int best = 0;
1391 	int bestDistance = int.max;
1392 	foreach(pe, co; palette) {
1393 		auto dr = cast(int) co.r - pixel.r;
1394 		auto dg = cast(int) co.g - pixel.g;
1395 		auto db = cast(int) co.b - pixel.b;
1396 		int dist = dr*dr + dg*dg + db*db;
1397 
1398 		if(dist < bestDistance) {
1399 			best = cast(int) pe;
1400 			bestDistance = dist;
1401 		}
1402 	}
1403 
1404 	return cast(ubyte) best;
1405 }
1406 
1407 /+
1408 
1409 // Quantizing and dithering test program
1410 
1411 void main( ){
1412 /*
1413 	auto img = new TrueColorImage(256, 32);
1414 	foreach(y; 0 .. img.height) {
1415 		foreach(x; 0 .. img.width) {
1416 			img.imageData.colors[x + y * img.width] = Color(x, y * (255 / img.height), 0);
1417 		}
1418 	}
1419 */
1420 
1421 TrueColorImage img;
1422 
1423 {
1424 
1425 import arsd.png;
1426 
1427 struct P {
1428 	ubyte[] range;
1429 	void put(ubyte[] a) { range ~= a; }
1430 }
1431 
1432 P range;
1433 import std.algorithm;
1434 
1435 import std.stdio;
1436 writePngLazy(range, pngFromBytes(File("/home/me/nyesha.png").byChunk(4096)).byRgbaScanline.map!((line) {
1437 	foreach(ref pixel; line.pixels) {
1438 	continue;
1439 		auto sum = cast(int) pixel.r + pixel.g + pixel.b;
1440 		ubyte a = cast(ubyte)(sum / 3);
1441 		pixel.r = a;
1442 		pixel.g = a;
1443 		pixel.b = a;
1444 	}
1445 	return line;
1446 }));
1447 
1448 img = imageFromPng(readPng(range.range)).getAsTrueColorImage;
1449 
1450 
1451 }
1452 
1453 
1454 
1455 	auto qimg = quantize(img, null, 2);
1456 
1457 	import arsd.simpledisplay;
1458 	auto win = new SimpleWindow(img.width, img.height * 3);
1459 	auto painter = win.draw();
1460 	painter.drawImage(Point(0, 0), Image.fromMemoryImage(img));
1461 	painter.drawImage(Point(0, img.height), Image.fromMemoryImage(qimg));
1462 	floydSteinbergDither(qimg, img);
1463 	painter.drawImage(Point(0, img.height * 2), Image.fromMemoryImage(qimg));
1464 	win.eventLoop(0);
1465 }
1466 +/
1467 
1468 /+
1469 /// If the background is transparent, it simply erases the alpha channel.
1470 void removeTransparency(IndexedImage img, Color background)
1471 +/
1472 
1473 /// Perform alpha-blending of `fore` to this color, return new color.
1474 /// WARNING! This function does blending in RGB space, and RGB space is not linear!
1475 Color alphaBlend(Color foreground, Color background) pure nothrow @safe @nogc {
1476 	//if(foreground.a == 255)
1477 		//return foreground;
1478 	if(foreground.a == 0)
1479 		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)
1480 
1481 	static if (__VERSION__ > 2067) pragma(inline, true);
1482 	return background.alphaBlend(foreground);
1483 }
1484 
1485 /*
1486 /// Reduces the number of colors in a palette.
1487 void reducePaletteSize(IndexedImage img, int maxColors = 16) {
1488 
1489 }
1490 */
1491 
1492 // I think I did this wrong... but the results aren't too bad so the bug can't be awful.
1493 /// Dithers img in place to look more like original.
1494 void floydSteinbergDither(IndexedImage img, in TrueColorImage original) nothrow @trusted {
1495 	assert(img.width == original.width);
1496 	assert(img.height == original.height);
1497 
1498 	auto buffer = new Color[](original.imageData.colors.length);
1499 
1500 	int x, y;
1501 
1502 	foreach(idx, c; original.imageData.colors) {
1503 		auto n = img.palette[img.data[idx]];
1504 		int errorR = cast(int) c.r - n.r;
1505 		int errorG = cast(int) c.g - n.g;
1506 		int errorB = cast(int) c.b - n.b;
1507 
1508 		void doit(int idxOffset, int multiplier) {
1509 		//	if(idx + idxOffset < buffer.length)
1510 				buffer[idx + idxOffset] = Color.fromIntegers(
1511 					c.r + multiplier * errorR / 16,
1512 					c.g + multiplier * errorG / 16,
1513 					c.b + multiplier * errorB / 16,
1514 					c.a
1515 				);
1516 		}
1517 
1518 		if((x+1) != original.width)
1519 			doit(1, 7);
1520 		if((y+1) != original.height) {
1521 			if(x != 0)
1522 				doit(-1 + img.width, 3);
1523 			doit(img.width, 5);
1524 			if(x+1 != original.width)
1525 				doit(1 + img.width, 1);
1526 		}
1527 
1528 		img.data[idx] = findNearestColor(img.palette, buffer[idx]);
1529 
1530 		x++;
1531 		if(x == original.width) {
1532 			x = 0;
1533 			y++;
1534 		}
1535 	}
1536 }
1537 
1538 // these are just really useful in a lot of places where the color/image functions are used,
1539 // so I want them available with Color
1540 ///
1541 struct Point {
1542 	int x; ///
1543 	int y; ///
1544 
1545 	pure const nothrow @safe:
1546 
1547 	Point opBinary(string op)(in Point rhs) @nogc {
1548 		return Point(mixin("x" ~ op ~ "rhs.x"), mixin("y" ~ op ~ "rhs.y"));
1549 	}
1550 
1551 	Point opBinary(string op)(int rhs) @nogc {
1552 		return Point(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs"));
1553 	}
1554 }
1555 
1556 ///
1557 struct Size {
1558 	int width; ///
1559 	int height; ///
1560 
1561 	int area() pure nothrow @safe const @nogc { return width * height; }
1562 }
1563 
1564 ///
1565 struct Rectangle {
1566 	int left; ///
1567 	int top; ///
1568 	int right; ///
1569 	int bottom; ///
1570 
1571 	pure const nothrow @safe @nogc:
1572 
1573 	///
1574 	this(int left, int top, int right, int bottom) {
1575 		this.left = left;
1576 		this.top = top;
1577 		this.right = right;
1578 		this.bottom = bottom;
1579 	}
1580 
1581 	///
1582 	this(in Point upperLeft, in Point lowerRight) {
1583 		this(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
1584 	}
1585 
1586 	///
1587 	this(in Point upperLeft, in Size size) {
1588 		this(upperLeft.x, upperLeft.y, upperLeft.x + size.width, upperLeft.y + size.height);
1589 	}
1590 
1591 	///
1592 	@property Point upperLeft() {
1593 		return Point(left, top);
1594 	}
1595 
1596 	///
1597 	@property Point upperRight() {
1598 		return Point(right, top);
1599 	}
1600 
1601 	///
1602 	@property Point lowerLeft() {
1603 		return Point(left, bottom);
1604 	}
1605 
1606 	///
1607 	@property Point lowerRight() {
1608 		return Point(right, bottom);
1609 	}
1610 
1611 	///
1612 	@property Point center() {
1613 		return Point((right + left) / 2, (bottom + top) / 2);
1614 	}
1615 
1616 	///
1617 	@property Size size() {
1618 		return Size(width, height);
1619 	}
1620 
1621 	///
1622 	@property int width() {
1623 		return right - left;
1624 	}
1625 
1626 	///
1627 	@property int height() {
1628 		return bottom - top;
1629 	}
1630 
1631 	/// Returns true if this rectangle entirely contains the other
1632 	bool contains(in Rectangle r) {
1633 		return contains(r.upperLeft) && contains(r.lowerRight);
1634 	}
1635 
1636 	/// ditto
1637 	bool contains(in Point p) {
1638 		return (p.x >= left && p.x < right && p.y >= top && p.y < bottom);
1639 	}
1640 
1641 	/// Returns true of the two rectangles at any point overlap
1642 	bool overlaps(in Rectangle r) {
1643 		// the -1 in here are because right and top are exclusive
1644 		return !((right-1) < r.left || (r.right-1) < left || (bottom-1) < r.top || (r.bottom-1) < top);
1645 	}
1646 
1647 	/++
1648 		Returns a Rectangle representing the intersection of this and the other given one.
1649 
1650 		History:
1651 			Added July 1, 2021
1652 	+/
1653 	Rectangle intersectionOf(in Rectangle r) {
1654 		auto tmp = Rectangle(max(left, r.left), max(top, r.top), min(right, r.right), min(bottom, r.bottom));
1655 		if(tmp.left >= tmp.right || tmp.top >= tmp.bottom)
1656 			tmp = Rectangle.init;
1657 
1658 		return tmp;
1659 	}
1660 }
1661 
1662 private int max(int a, int b) @nogc nothrow pure @safe {
1663 	return a >= b ? a : b;
1664 }
1665 private int min(int a, int b) @nogc nothrow pure @safe {
1666 	return a <= b ? a : b;
1667 }
1668 
1669 /++
1670 	Implements a flood fill algorithm, like the bucket tool in
1671 	MS Paint.
1672 
1673 	Note it assumes `what.length == width*height`.
1674 
1675 	Params:
1676 		what = the canvas to work with, arranged as top to bottom, left to right elements
1677 		width = the width of the canvas
1678 		height = the height of the canvas
1679 		target = the type to replace. You may pass the existing value if you want to do what Paint does
1680 		replacement = the replacement value
1681 		x = the x-coordinate to start the fill (think of where the user clicked in Paint)
1682 		y = the y-coordinate to start the fill
1683 		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.
1684 +/
1685 void floodFill(T)(
1686 	T[] what, int width, int height, // the canvas to inspect
1687 	T target, T replacement, // fill params
1688 	int x, int y, bool delegate(int x, int y) @safe additionalCheck) // the node
1689 
1690 	// in(what.length == width * height) // gdc doesn't support this syntax yet so not gonna use it until that comes out.
1691 {
1692 	assert(what.length == width * height); // will use the contract above when gdc supports it
1693 
1694 	T node = what[y * width + x];
1695 
1696 	if(target == replacement) return;
1697 
1698 	if(node != target) return;
1699 
1700 	if(additionalCheck is null)
1701 		additionalCheck = (int, int) => true;
1702 
1703 	if(!additionalCheck(x, y))
1704 		return;
1705 
1706 	Point[] queue;
1707 
1708 	queue ~= Point(x, y);
1709 
1710 	while(queue.length) {
1711 		auto n = queue[0];
1712 		queue = queue[1 .. $];
1713 		//queue.assumeSafeAppend(); // lol @safe breakage
1714 
1715 		auto w = n;
1716 		int offset = cast(int) (n.y * width + n.x);
1717 		auto e = n;
1718 		auto eoffset = offset;
1719 		w.x--;
1720 		offset--;
1721 		while(w.x >= 0 && what[offset] == target && additionalCheck(w.x, w.y)) {
1722 			w.x--;
1723 			offset--;
1724 		}
1725 		while(e.x < width && what[eoffset] == target && additionalCheck(e.x, e.y)) {
1726 			e.x++;
1727 			eoffset++;
1728 		}
1729 
1730 		// to make it inclusive again
1731 		w.x++;
1732 		offset++;
1733 		foreach(o ; offset .. eoffset) {
1734 			what[o] = replacement;
1735 			if(w.y && what[o - width] == target && additionalCheck(w.x, w.y))
1736 				queue ~= Point(w.x, w.y - 1);
1737 			if(w.y + 1 < height && what[o + width] == target && additionalCheck(w.x, w.y))
1738 				queue ~= Point(w.x, w.y + 1);
1739 			w.x++;
1740 		}
1741 	}
1742 
1743 	/+
1744 	what[y * width + x] = replacement;
1745 
1746 	if(x)
1747 		floodFill(what, width, height, target, replacement,
1748 			x - 1, y, additionalCheck);
1749 
1750 	if(x != width - 1)
1751 		floodFill(what, width, height, target, replacement,
1752 			x + 1, y, additionalCheck);
1753 
1754 	if(y)
1755 		floodFill(what, width, height, target, replacement,
1756 			x, y - 1, additionalCheck);
1757 
1758 	if(y != height - 1)
1759 		floodFill(what, width, height, target, replacement,
1760 			x, y + 1, additionalCheck);
1761 	+/
1762 }
1763 
1764 // for scripting, so you can tag it without strictly needing to import arsd.jsvar
1765 enum arsd_jsvar_compatible = "arsd_jsvar_compatible";