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