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