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