1 module arsd.color;
2 
3 @safe:
4 
5 // importing phobos explodes the size of this code 10x, so not doing it.
6 
7 private {
8 	real toInternal(T)(string s) {
9 		real accumulator = 0.0;
10 		size_t i = s.length;
11 		foreach(idx, c; s) {
12 			if(c >= '0' && c <= '9') {
13 				accumulator *= 10;
14 				accumulator += c - '0';
15 			} else if(c == '.') {
16 				i = idx + 1;
17 				break;
18 			} else
19 				throw new Exception("bad char to make real from " ~ s);
20 		}
21 
22 		real accumulator2 = 0.0;
23 		real count = 1;
24 		foreach(c; s[i .. $]) {
25 			if(c >= '0' && c <= '9') {
26 				accumulator2 *= 10;
27 				accumulator2 += c - '0';
28 				count *= 10;
29 			} else
30 				throw new Exception("bad char to make real from " ~ s);
31 		}
32 
33 		return accumulator + accumulator2 / count;
34 	}
35 
36 	@trusted
37 	string toInternal(T)(int a) {
38 		if(a == 0)
39 			return "0";
40 		char[] ret;
41 		while(a) {
42 			ret ~= (a % 10) + '0';
43 			a /= 10;
44 		}
45 		for(int i = 0; i < ret.length / 2; i++) {
46 			char c = ret[i];
47 			ret[i] = ret[$ - i - 1];
48 			ret[$ - i - 1] = c;
49 		}
50 		return cast(string) ret;
51 	}
52 	string toInternal(T)(real a) {
53 		// a simplifying assumption here is the fact that we only use this in one place: toInternal!string(cast(real) a / 255)
54 		// thus we know this will always be between 0.0 and 1.0, inclusive.
55 		if(a <= 0.0)
56 			return "0.0";
57 		if(a >= 1.0)
58 			return "1.0";
59 		string ret = "0.";
60 		// I wonder if I can handle round off error any better. Phobos does, but that isn't worth 100 KB of code.
61 		int amt = cast(int)(a * 1000);
62 		return ret ~ toInternal!string(amt);
63 	}
64 
65 	real absInternal(real a) { return a < 0 ? -a : a; }
66 	real minInternal(real a, real b, real c) {
67 		auto m = a;
68 		if(b < m) m = b;
69 		if(c < m) m = c;
70 		return m;
71 	}
72 	real maxInternal(real a, real b, real c) {
73 		auto m = a;
74 		if(b > m) m = b;
75 		if(c > m) m = c;
76 		return m;
77 	}
78 	bool startsWithInternal(string a, string b) {
79 		return (a.length >= b.length && a[0 .. b.length] == b);
80 	}
81 	string[] splitInternal(string a, char c) {
82 		string[] ret;
83 		size_t previous = 0;
84 		foreach(i, char ch; a) {
85 			if(ch == c) {
86 				ret ~= a[previous .. i];
87 				previous = i + 1;
88 			}
89 		}
90 		if(previous != a.length)
91 			ret ~= a[previous .. $];
92 		return ret;
93 	}
94 	string stripInternal(string s) {
95 		foreach(i, char c; s)
96 			if(c != ' ' && c != '\t' && c != '\n') {
97 				s = s[i .. $];
98 				break;
99 			}
100 		for(int a = cast(int)(s.length - 1); a > 0; a--) {
101 			char c = s[a];
102 			if(c != ' ' && c != '\t' && c != '\n') {
103 				s = s[0 .. a + 1];
104 				break;
105 			}
106 		}
107 
108 		return s;
109 	}
110 }
111 
112 // done with mini-phobos
113 
114 /// Represents an RGBA color
115 struct Color {
116 	union {
117 		ubyte[4] components;
118 
119 		struct {
120 			ubyte r; /// red
121 			ubyte g; /// green
122 			ubyte b; /// blue
123 			ubyte a; /// alpha. 255 == opaque
124 		}
125 
126 		uint asUint;
127 	}
128 
129 	// this makes sure they are in range before casting
130 	static Color fromIntegers(int red, int green, int blue, int alpha = 255) {
131 		if(red < 0) red = 0; if(red > 255) red = 255;
132 		if(green < 0) green = 0; if(green > 255) green = 255;
133 		if(blue < 0) blue = 0; if(blue > 255) blue = 255;
134 		if(alpha < 0) alpha = 0; if(alpha > 255) alpha = 255;
135 		return Color(red, green, blue, alpha);
136 	}
137 
138 	/// .
139 	this(int red, int green, int blue, int alpha = 255) {
140 		// workaround dmd bug 10937
141 		if(__ctfe)
142 			this.components[0] = cast(ubyte) red;
143 		else
144 			this.r = cast(ubyte) red;
145 		this.g = cast(ubyte) green;
146 		this.b = cast(ubyte) blue;
147 		this.a = cast(ubyte) alpha;
148 	}
149 
150 	/// Convenience functions for common color names
151 	static Color transparent() { return Color(0, 0, 0, 0); }
152 	static Color white() { return Color(255, 255, 255); } ///.
153 	static Color black() { return Color(0, 0, 0); } ///.
154 	static Color red() { return Color(255, 0, 0); } ///.
155 	static Color green() { return Color(0, 255, 0); } ///.
156 	static Color blue() { return Color(0, 0, 255); } ///.
157 	static Color yellow() { return Color(255, 255, 0); } ///.
158 	static Color teal() { return Color(0, 255, 255); } ///.
159 	static Color purple() { return Color(255, 0, 255); } ///.
160 
161 	/*
162 	ubyte[4] toRgbaArray() {
163 		return [r,g,b,a];
164 	}
165 	*/
166 
167 	/// Makes a string that matches CSS syntax for websites
168 	string toCssString() {
169 		if(a == 255)
170 			return "#" ~ toHexInternal(r) ~ toHexInternal(g) ~ toHexInternal(b);
171 		else {
172 			return "rgba("~toInternal!string(r)~", "~toInternal!string(g)~", "~toInternal!string(b)~", "~toInternal!string(cast(real)a / 255.0)~")";
173 		}
174 	}
175 
176 	/// Makes a hex string RRGGBBAA (aa only present if it is not 255)
177 	string toString() {
178 		if(a == 255)
179 			return toCssString()[1 .. $];
180 		else
181 			return toRgbaHexString();
182 	}
183 
184 	/// returns RRGGBBAA, even if a== 255
185 	string toRgbaHexString() {
186 		return toHexInternal(r) ~ toHexInternal(g) ~ toHexInternal(b) ~ toHexInternal(a);
187 	}
188 
189 	/// Gets a color by name, iff the name is one of the static members listed above
190 	static Color fromNameString(string s) {
191 		Color c;
192 		foreach(member; __traits(allMembers, Color)) {
193 			static if(__traits(compiles, c = __traits(getMember, Color, member))) {
194 				if(s == member)
195 					return __traits(getMember, Color, member);
196 			}
197 		}
198 		throw new Exception("Unknown color " ~ s);
199 	}
200 
201 	/// Reads a CSS style string to get the color. Understands #rrggbb, rgba(), hsl(), and rrggbbaa
202 	static Color fromString(string s) {
203 		s = s.stripInternal();
204 
205 		Color c;
206 		c.a = 255;
207 
208 		// trying named colors via the static no-arg methods here
209 		foreach(member; __traits(allMembers, Color)) {
210 			static if(__traits(compiles, c = __traits(getMember, Color, member))) {
211 				if(s == member)
212 					return __traits(getMember, Color, member);
213 			}
214 		}
215 
216 		// try various notations borrowed from CSS (though a little extended)
217 
218 		// hsl(h,s,l,a) where h is degrees and s,l,a are 0 >= x <= 1.0
219 		if(s.startsWithInternal("hsl(") || s.startsWithInternal("hsla(")) {
220 			assert(s[$-1] == ')');
221 			s = s[s.startsWithInternal("hsl(") ? 4 : 5  .. $ - 1]; // the closing paren
222 
223 			real[3] hsl;
224 			ubyte a = 255;
225 
226 			auto parts = s.splitInternal(',');
227 			foreach(i, part; parts) {
228 				if(i < 3)
229 					hsl[i] = toInternal!real(part.stripInternal);
230 				else
231 					a = cast(ubyte) (toInternal!real(part.stripInternal) * 255);
232 			}
233 
234 			c = .fromHsl(hsl);
235 			c.a = a;
236 
237 			return c;
238 		}
239 
240 		// rgb(r,g,b,a) where r,g,b are 0-255 and a is 0-1.0
241 		if(s.startsWithInternal("rgb(") || s.startsWithInternal("rgba(")) {
242 			assert(s[$-1] == ')');
243 			s = s[s.startsWithInternal("rgb(") ? 4 : 5  .. $ - 1]; // the closing paren
244 
245 			auto parts = s.splitInternal(',');
246 			foreach(i, part; parts) {
247 				// lol the loop-switch pattern
248 				auto v = toInternal!real(part.stripInternal);
249 				switch(i) {
250 					case 0: // red
251 						c.r = cast(ubyte) v;
252 					break;
253 					case 1:
254 						c.g = cast(ubyte) v;
255 					break;
256 					case 2:
257 						c.b = cast(ubyte) v;
258 					break;
259 					case 3:
260 						c.a = cast(ubyte) (v * 255);
261 					break;
262 					default: // ignore
263 				}
264 			}
265 
266 			return c;
267 		}
268 
269 
270 
271 
272 		// otherwise let's try it as a hex string, really loosely
273 
274 		if(s.length && s[0] == '#')
275 			s = s[1 .. $];
276 
277 		// not a built in... do it as a hex string
278 		if(s.length >= 2) {
279 			c.r = fromHexInternal(s[0 .. 2]);
280 			s = s[2 .. $];
281 		}
282 		if(s.length >= 2) {
283 			c.g = fromHexInternal(s[0 .. 2]);
284 			s = s[2 .. $];
285 		}
286 		if(s.length >= 2) {
287 			c.b = fromHexInternal(s[0 .. 2]);
288 			s = s[2 .. $];
289 		}
290 		if(s.length >= 2) {
291 			c.a = fromHexInternal(s[0 .. 2]);
292 			s = s[2 .. $];
293 		}
294 
295 		return c;
296 	}
297 
298 	/// from hsl
299 	static Color fromHsl(real h, real s, real l) {
300 		return .fromHsl(h, s, l);
301 	}
302 }
303 
304 private string toHexInternal(ubyte b) {
305 	string s;
306 	if(b < 16)
307 		s ~= '0';
308 	else {
309 		ubyte t = (b & 0xf0) >> 4;
310 		if(t >= 10)
311 			s ~= 'A' + t - 10;
312 		else
313 			s ~= '0' + t;
314 		b &= 0x0f;
315 	}
316 	if(b >= 10)
317 		s ~= 'A' + b - 10;
318 	else
319 		s ~= '0' + b;
320 
321 	return s;
322 }
323 
324 private ubyte fromHexInternal(string s) {
325 	int result = 0;
326 
327 	int exp = 1;
328 	//foreach(c; retro(s)) { // FIXME: retro doesn't work right in dtojs
329 	foreach_reverse(c; s) {
330 		if(c >= 'A' && c <= 'F')
331 			result += exp * (c - 'A' + 10);
332 		else if(c >= 'a' && c <= 'f')
333 			result += exp * (c - 'a' + 10);
334 		else if(c >= '0' && c <= '9')
335 			result += exp * (c - '0');
336 		else
337 			// throw new Exception("invalid hex character: " ~ cast(char) c);
338 			return 0;
339 
340 		exp *= 16;
341 	}
342 
343 	return cast(ubyte) result;
344 }
345 
346 /// Converts hsl to rgb
347 Color fromHsl(real[3] hsl) {
348 	return fromHsl(hsl[0], hsl[1], hsl[2]);
349 }
350 
351 /// Converts hsl to rgb
352 Color fromHsl(real h, real s, real l, real a = 255) {
353 	h = h % 360;
354 
355 	real C = (1 - absInternal(2 * l - 1)) * s;
356 
357 	real hPrime = h / 60;
358 
359 	real X = C * (1 - absInternal(hPrime % 2 - 1));
360 
361 	real r, g, b;
362 
363 	if(h is real.nan)
364 		r = g = b = 0;
365 	else if (hPrime >= 0 && hPrime < 1) {
366 		r = C;
367 		g = X;
368 		b = 0;
369 	} else if (hPrime >= 1 && hPrime < 2) {
370 		r = X;
371 		g = C;
372 		b = 0;
373 	} else if (hPrime >= 2 && hPrime < 3) {
374 		r = 0;
375 		g = C;
376 		b = X;
377 	} else if (hPrime >= 3 && hPrime < 4) {
378 		r = 0;
379 		g = X;
380 		b = C;
381 	} else if (hPrime >= 4 && hPrime < 5) {
382 		r = X;
383 		g = 0;
384 		b = C;
385 	} else if (hPrime >= 5 && hPrime < 6) {
386 		r = C;
387 		g = 0;
388 		b = X;
389 	}
390 
391 	real m = l - C / 2;
392 
393 	r += m;
394 	g += m;
395 	b += m;
396 
397 	return Color(
398 		cast(ubyte)(r * 255),
399 		cast(ubyte)(g * 255),
400 		cast(ubyte)(b * 255),
401 		cast(ubyte)(a));
402 }
403 
404 /// 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.
405 real[3] toHsl(Color c, bool useWeightedLightness = false) {
406 	real r1 = cast(real) c.r / 255;
407 	real g1 = cast(real) c.g / 255;
408 	real b1 = cast(real) c.b / 255;
409 
410 	real maxColor = maxInternal(r1, g1, b1);
411 	real minColor = minInternal(r1, g1, b1);
412 
413 	real L = (maxColor + minColor) / 2 ;
414 	if(useWeightedLightness) {
415 		// the colors don't affect the eye equally
416 		// this is a little more accurate than plain HSL numbers
417 		L = 0.2126*r1 + 0.7152*g1 + 0.0722*b1;
418 	}
419 	real S = 0;
420 	real H = 0;
421 	if(maxColor != minColor) {
422 		if(L < 0.5) {
423 			S = (maxColor - minColor) / (maxColor + minColor);
424 		} else {
425 			S = (maxColor - minColor) / (2.0 - maxColor - minColor);
426 		}
427 		if(r1 == maxColor) {
428 			H = (g1-b1) / (maxColor - minColor);
429 		} else if(g1 == maxColor) {
430 			H = 2.0 + (b1 - r1) / (maxColor - minColor);
431 		} else {
432 			H = 4.0 + (r1 - g1) / (maxColor - minColor);
433 		}
434 	}
435 
436 	H = H * 60;
437 	if(H < 0){
438 		H += 360;
439 	}
440 
441 	return [H, S, L]; 
442 }
443 
444 /// .
445 Color lighten(Color c, real percentage) {
446 	auto hsl = toHsl(c);
447 	hsl[2] *= (1 + percentage);
448 	if(hsl[2] > 1)
449 		hsl[2] = 1;
450 	return fromHsl(hsl);
451 }
452 
453 /// .
454 Color darken(Color c, real percentage) {
455 	auto hsl = toHsl(c);
456 	hsl[2] *= (1 - percentage);
457 	return fromHsl(hsl);
458 }
459 
460 /// for light colors, call darken. for dark colors, call lighten.
461 /// The goal: get toward center grey.
462 Color moderate(Color c, real percentage) {
463 	auto hsl = toHsl(c);
464 	if(hsl[2] > 0.5)
465 		hsl[2] *= (1 - percentage);
466 	else {
467 		if(hsl[2] <= 0.01) // if we are given black, moderating it means getting *something* out
468 			hsl[2] = percentage;
469 		else
470 			hsl[2] *= (1 + percentage);
471 	}
472 	if(hsl[2] > 1)
473 		hsl[2] = 1;
474 	return fromHsl(hsl);
475 }
476 
477 /// the opposite of moderate. Make darks darker and lights lighter
478 Color extremify(Color c, real percentage) {
479 	auto hsl = toHsl(c, true);
480 	if(hsl[2] < 0.5)
481 		hsl[2] *= (1 - percentage);
482 	else
483 		hsl[2] *= (1 + percentage);
484 	if(hsl[2] > 1)
485 		hsl[2] = 1;
486 	return fromHsl(hsl);
487 }
488 
489 /// Move around the lightness wheel, trying not to break on moderate things
490 Color oppositeLightness(Color c) {
491 	auto hsl = toHsl(c);
492 
493 	auto original = hsl[2];
494 
495 	if(original > 0.4 && original < 0.6)
496 		hsl[2] = 0.8 - original; // so it isn't quite the same
497 	else
498 		hsl[2] = 1 - original;
499 
500 	return fromHsl(hsl);
501 }
502 
503 /// Try to determine a text color - either white or black - based on the input
504 Color makeTextColor(Color c) {
505 	auto hsl = toHsl(c, true); // give green a bonus for contrast
506 	if(hsl[2] > 0.71)
507 		return Color(0, 0, 0);
508 	else
509 		return Color(255, 255, 255);
510 }
511 
512 // These provide functional access to hsl manipulation; useful if you need a delegate
513 
514 Color setLightness(Color c, real lightness) {
515 	auto hsl = toHsl(c);
516 	hsl[2] = lightness;
517 	return fromHsl(hsl);
518 }
519 
520 
521 
522 Color rotateHue(Color c, real degrees) {
523 	auto hsl = toHsl(c);
524 	hsl[0] += degrees;
525 	return fromHsl(hsl);
526 }
527 
528 Color setHue(Color c, real hue) {
529 	auto hsl = toHsl(c);
530 	hsl[0] = hue;
531 	return fromHsl(hsl);
532 }
533 
534 Color desaturate(Color c, real percentage) {
535 	auto hsl = toHsl(c);
536 	hsl[1] *= (1 - percentage);
537 	return fromHsl(hsl);
538 }
539 
540 Color saturate(Color c, real percentage) {
541 	auto hsl = toHsl(c);
542 	hsl[1] *= (1 + percentage);
543 	if(hsl[1] > 1)
544 		hsl[1] = 1;
545 	return fromHsl(hsl);
546 }
547 
548 Color setSaturation(Color c, real saturation) {
549 	auto hsl = toHsl(c);
550 	hsl[1] = saturation;
551 	return fromHsl(hsl);
552 }
553 
554 
555 /*
556 void main(string[] args) {
557 	auto color1 = toHsl(Color(255, 0, 0));
558 	auto color = fromHsl(color1[0] + 60, color1[1], color1[2]);
559 
560 	writefln("#%02x%02x%02x", color.r, color.g, color.b);
561 }
562 */
563 
564 /* Color algebra functions */
565 
566 /* Alpha putpixel looks like this:
567 
568 void putPixel(Image i, Color c) {
569 	Color b;
570 	b.r = i.data[(y * i.width + x) * bpp + 0];
571 	b.g = i.data[(y * i.width + x) * bpp + 1];
572 	b.b = i.data[(y * i.width + x) * bpp + 2];
573 	b.a = i.data[(y * i.width + x) * bpp + 3];
574 
575 	float ca = cast(float) c.a / 255;
576 
577 	i.data[(y * i.width + x) * bpp + 0] = alpha(c.r, ca, b.r);
578 	i.data[(y * i.width + x) * bpp + 1] = alpha(c.g, ca, b.g);
579 	i.data[(y * i.width + x) * bpp + 2] = alpha(c.b, ca, b.b);
580 	i.data[(y * i.width + x) * bpp + 3] = alpha(c.a, ca, b.a);
581 }
582 
583 ubyte alpha(ubyte c1, float alpha, ubyte onto) {
584 	auto got = (1 - alpha) * onto + alpha * c1;
585 
586 	if(got > 255)
587 		return 255;
588 	return cast(ubyte) got;
589 }
590 
591 So, given the background color and the resultant color, what was
592 composited on to it?
593 */
594 
595 ubyte unalpha(ubyte colorYouHave, float alpha, ubyte backgroundColor) {
596 	// resultingColor = (1-alpha) * backgroundColor + alpha * answer
597 	auto resultingColorf = cast(float) colorYouHave;
598 	auto backgroundColorf = cast(float) backgroundColor;
599 
600 	auto answer = (resultingColorf - backgroundColorf + alpha * backgroundColorf) / alpha;
601 	if(answer > 255)
602 		return 255;
603 	if(answer < 0)
604 		return 0;
605 	return cast(ubyte) answer;
606 }
607 
608 ubyte makeAlpha(ubyte colorYouHave, ubyte backgroundColor/*, ubyte foreground = 0x00*/) {
609 	//auto foregroundf = cast(float) foreground;
610 	auto foregroundf = 0.00f;
611 	auto colorYouHavef = cast(float) colorYouHave;
612 	auto backgroundColorf = cast(float) backgroundColor;
613 
614 	// colorYouHave = backgroundColorf - alpha * backgroundColorf + alpha * foregroundf
615 	auto alphaf = 1 - colorYouHave / backgroundColorf;
616 	alphaf *= 255;
617 
618 	if(alphaf < 0)
619 		return 0;
620 	if(alphaf > 255)
621 		return 255;
622 	return cast(ubyte) alphaf;
623 }
624 
625 
626 int fromHex(string s) {
627 	int result = 0;
628 
629 	int exp = 1;
630 	// foreach(c; retro(s)) {
631 	foreach_reverse(c; s) {
632 		if(c >= 'A' && c <= 'F')
633 			result += exp * (c - 'A' + 10);
634 		else if(c >= 'a' && c <= 'f')
635 			result += exp * (c - 'a' + 10);
636 		else if(c >= '0' && c <= '9')
637 			result += exp * (c - '0');
638 		else
639 			throw new Exception("invalid hex character: " ~ cast(char) c);
640 
641 		exp *= 16;
642 	}
643 
644 	return result;
645 }
646 
647 Color colorFromString(string s) {
648 	if(s.length == 0)
649 		return Color(0,0,0,255);
650 	if(s[0] == '#')
651 		s = s[1..$];
652 	assert(s.length == 6 || s.length == 8);
653 
654 	Color c;
655 
656 	c.r = cast(ubyte) fromHex(s[0..2]);
657 	c.g = cast(ubyte) fromHex(s[2..4]);
658 	c.b = cast(ubyte) fromHex(s[4..6]);
659 	if(s.length == 8)
660 		c.a = cast(ubyte) fromHex(s[6..8]);
661 	else
662 		c.a = 255;
663 
664 	return c;
665 }
666 
667 /*
668 import browser.window;
669 import std.conv;
670 void main() {
671 	import browser.document;
672 	foreach(ele; document.querySelectorAll("input")) {
673 		ele.addEventListener("change", {
674 			auto h = toInternal!real(document.querySelector("input[name=h]").value);
675 			auto s = toInternal!real(document.querySelector("input[name=s]").value);
676 			auto l = toInternal!real(document.querySelector("input[name=l]").value);
677 
678 			Color c = Color.fromHsl(h, s, l);
679 
680 			auto e = document.getElementById("example");
681 			e.style.backgroundColor = c.toCssString();
682 
683 			// JSElement __js_this;
684 			// __js_this.style.backgroundColor = c.toCssString();
685 		}, false);
686 	}
687 }
688 */
689 
690 
691 
692 /**
693 	This provides two image classes and a bunch of functions that work on them.
694 
695 	Why are they separate classes? I think the operations on the two of them
696 	are necessarily different. There's a whole bunch of operations that only
697 	really work on truecolor (blurs, gradients), and a few that only work
698 	on indexed images (palette swaps).
699 
700 	Even putpixel is pretty different. On indexed, it is a palette entry's
701 	index number. On truecolor, it is the actual color.
702 
703 	A greyscale image is the weird thing in the middle. It is truecolor, but
704 	fits in the same size as indexed. Still, I'd say it is a specialization
705 	of truecolor.
706 
707 	There is a subset that works on both
708 
709 */
710 
711 /// An image in memory
712 interface MemoryImage {
713 	//IndexedImage convertToIndexedImage() const;
714 	//TrueColorImage convertToTrueColor() const;
715 
716 	/// gets it as a TrueColorImage. May return this or may do a conversion and return a new image
717 	TrueColorImage getAsTrueColorImage();
718 
719 	/// Image width, in pixels
720 	int width() const;
721 
722 	/// Image height, in pixels
723 	int height() const;
724 }
725 
726 /// An image that consists of indexes into a color palette. Use getAsTrueColorImage() if you don't care about palettes
727 class IndexedImage : MemoryImage {
728 	bool hasAlpha;
729 
730 	/// .
731 	Color[] palette;
732 	/// the data as indexes into the palette. Stored left to right, top to bottom, no padding.
733 	ubyte[] data;
734 
735 	/// .
736 	override int width() const {
737 		return _width;
738 	}
739 
740 	/// .
741 	override int height() const {
742 		return _height;
743 	}
744 
745 	private int _width;
746 	private int _height;
747 
748 	/// .
749 	this(int w, int h) {
750 		_width = w;
751 		_height = h;
752 		data = new ubyte[w*h];
753 	}
754 
755 	/*
756 	void resize(int w, int h, bool scale) {
757 
758 	}
759 	*/
760 
761 	/// returns a new image
762 	override TrueColorImage getAsTrueColorImage() {
763 		return convertToTrueColor();
764 	}
765 
766 	/// Creates a new TrueColorImage based on this data
767 	TrueColorImage convertToTrueColor() const {
768 		auto tci = new TrueColorImage(width, height);
769 		foreach(i, b; data) {
770 			/*
771 			if(b >= palette.length) {
772 				string fuckyou;
773 				fuckyou ~= b + '0';
774 				fuckyou ~= " ";
775 				fuckyou ~= palette.length + '0';
776 				assert(0, fuckyou);
777 			}
778 			*/
779 			tci.imageData.colors[i] = palette[b];
780 		}
781 		return tci;
782 	}
783 
784 	/// Gets an exact match, if possible, adds if not. See also: the findNearestColor free function.
785 	ubyte getOrAddColor(Color c) {
786 		foreach(i, co; palette) {
787 			if(c == co)
788 				return cast(ubyte) i;
789 		}
790 
791 		return addColor(c);
792 	}
793 
794 	/// Number of colors currently in the palette (note: palette entries are not necessarily used in the image data)
795 	int numColors() const {
796 		return cast(int) palette.length;
797 	}
798 
799 	/// Adds an entry to the palette, returning its inded
800 	ubyte addColor(Color c) {
801 		assert(palette.length < 256);
802 		if(c.a != 255)
803 			hasAlpha = true;
804 		palette ~= c;
805 
806 		return cast(ubyte) (palette.length - 1);
807 	}
808 }
809 
810 /// An RGBA array of image data. Use the free function quantize() to convert to an IndexedImage
811 class TrueColorImage : MemoryImage {
812 //	bool hasAlpha;
813 //	bool isGreyscale;
814 
815 	//ubyte[] data; // stored as rgba quads, upper left to right to bottom
816 	/// .
817 	struct Data {
818 		ubyte[] bytes; /// the data as rgba bytes. Stored left to right, top to bottom, no padding.
819 		// the union is no good because the length of the struct is wrong!
820 
821 		/// the same data as Color structs
822 		@trusted // the cast here is typically unsafe, but it is ok
823 		// here because I guarantee the layout, note the static assert below
824 		@property inout(Color)[] colors() inout {
825 			return cast(inout(Color)[]) bytes;
826 		}
827 
828 		static assert(Color.sizeof == 4);
829 	}
830 
831 	/// .
832 	Data imageData;
833 	alias imageData.bytes data;
834 
835 	int _width;
836 	int _height;
837 
838 	/// .
839 	override int width() const { return _width; }
840 	///.
841 	override int height() const { return _height; }
842 
843 	/// .
844 	this(int w, int h) {
845 		_width = w;
846 		_height = h;
847 		imageData.bytes = new ubyte[w*h*4];
848 	}
849 
850 	/// Creates with existing data. The data pointer is stored here.
851 	this(int w, int h, ubyte[] data) {
852 		_width = w;
853 		_height = h;
854 		assert(data.length == w * h * 4);
855 		imageData.bytes = data;
856 	}
857 
858 	/// Returns this
859 	override TrueColorImage getAsTrueColorImage() {
860 		return this;
861 	}
862 }
863 
864 /// Converts true color to an indexed image. It uses palette as the starting point, adding entries
865 /// until maxColors as needed. If palette is null, it creates a whole new palette.
866 ///
867 /// After quantizing the image, it applies a dithering algorithm.
868 ///
869 /// This is not written for speed.
870 IndexedImage quantize(in TrueColorImage img, Color[] palette = null, in int maxColors = 256)
871 	// this is just because IndexedImage assumes ubyte palette values
872 	in { assert(maxColors <= 256); }
873 body {
874 	int[Color] uses;
875 	foreach(pixel; img.imageData.colors) {
876 		if(auto i = pixel in uses) {
877 			(*i)++;
878 		} else {
879 			uses[pixel] = 1;
880 		}
881 	}
882 
883 	struct ColorUse {
884 		Color c;
885 		int uses;
886 		//string toString() { import std.conv; return c.toCssString() ~ " x " ~ to!string(uses); }
887 		int opCmp(ref const ColorUse co) const {
888 			return co.uses - uses;
889 		}
890 	}
891 
892 	ColorUse[] sorted;
893 
894 	foreach(color, count; uses)
895 		sorted ~= ColorUse(color, count);
896 
897 	uses = null;
898 	version(no_phobos)
899 		sorted = sorted.sort;
900 	else {
901 		import std.algorithm : sort;
902 		sort(sorted);
903 	}
904 
905 	ubyte[Color] paletteAssignments;
906 	foreach(idx, entry; palette)
907 		paletteAssignments[entry] = cast(ubyte) idx;
908 
909 	// For the color assignments from the image, I do multiple passes, decreasing the acceptable
910 	// distance each time until we're full.
911 
912 	// This is probably really slow.... but meh it gives pretty good results.
913 
914 	auto ddiff = 32;
915 	outer: for(int d1 = 128; d1 >= 0; d1 -= ddiff) {
916 	auto minDist = d1*d1;
917 	if(d1 <= 64)
918 		ddiff = 16;
919 	if(d1 <= 32)
920 		ddiff = 8;
921 	foreach(possibility; sorted) {
922 		if(palette.length == maxColors)
923 			break;
924 		if(palette.length) {
925 			auto co = palette[findNearestColor(palette, possibility.c)];
926 			auto pixel = possibility.c;
927 
928 			auto dr = cast(int) co.r - pixel.r;
929 			auto dg = cast(int) co.g - pixel.g;
930 			auto db = cast(int) co.b - pixel.b;
931 
932 			auto dist = dr*dr + dg*dg + db*db;
933 			// not good enough variety to justify an allocation yet
934 			if(dist < minDist)
935 				continue;
936 		}
937 		paletteAssignments[possibility.c] = cast(ubyte) palette.length;
938 		palette ~= possibility.c;
939 	}
940 	}
941 
942 	// Final pass: just fill in any remaining space with the leftover common colors
943 	while(palette.length < maxColors && sorted.length) {
944 		if(sorted[0].c !in paletteAssignments) {
945 			paletteAssignments[sorted[0].c] = cast(ubyte) palette.length;
946 			palette ~= sorted[0].c;
947 		}
948 		sorted = sorted[1 .. $];
949 	}
950 
951 
952 	bool wasPerfect = true;
953 	auto newImage = new IndexedImage(img.width, img.height);
954 	newImage.palette = palette;
955 	foreach(idx, pixel; img.imageData.colors) {
956 		if(auto p = pixel in paletteAssignments)
957 			newImage.data[idx] = *p;
958 		else {
959 			// gotta find the closest one...
960 			newImage.data[idx] = findNearestColor(palette, pixel);
961 			wasPerfect = false;
962 		}
963 	}
964 
965 	if(!wasPerfect)
966 		floydSteinbergDither(newImage, img);
967 
968 	return newImage;
969 }
970 
971 /// Finds the best match for pixel in palette (currently by checking for minimum euclidean distance in rgb colorspace)
972 ubyte findNearestColor(in Color[] palette, in Color pixel) {
973 	int best = 0;
974 	int bestDistance = int.max;
975 	foreach(pe, co; palette) {
976 		auto dr = cast(int) co.r - pixel.r;
977 		auto dg = cast(int) co.g - pixel.g;
978 		auto db = cast(int) co.b - pixel.b;
979 		int dist = dr*dr + dg*dg + db*db;
980 
981 		if(dist < bestDistance) {
982 			best = cast(int) pe;
983 			bestDistance = dist;
984 		}
985 	}
986 
987 	return cast(ubyte) best;
988 }
989 
990 /+
991 
992 // Quantizing and dithering test program
993 
994 void main( ){
995 /*
996 	auto img = new TrueColorImage(256, 32);
997 	foreach(y; 0 .. img.height) {
998 		foreach(x; 0 .. img.width) {
999 			img.imageData.colors[x + y * img.width] = Color(x, y * (255 / img.height), 0);
1000 		}
1001 	}
1002 */
1003 
1004 TrueColorImage img;
1005 
1006 {
1007 
1008 import arsd.png;
1009 
1010 struct P {
1011 	ubyte[] range;
1012 	void put(ubyte[] a) { range ~= a; }
1013 }
1014 
1015 P range;
1016 import std.algorithm;
1017 
1018 import std.stdio;
1019 writePngLazy(range, pngFromBytes(File("/home/me/nyesha.png").byChunk(4096)).byRgbaScanline.map!((line) {
1020 	foreach(ref pixel; line.pixels) {
1021 	continue;
1022 		auto sum = cast(int) pixel.r + pixel.g + pixel.b;
1023 		ubyte a = cast(ubyte)(sum / 3);
1024 		pixel.r = a;
1025 		pixel.g = a;
1026 		pixel.b = a;
1027 	}
1028 	return line;
1029 }));
1030 
1031 img = imageFromPng(readPng(range.range)).getAsTrueColorImage;
1032 
1033 
1034 }
1035 
1036 
1037 
1038 	auto qimg = quantize(img, null, 2);
1039 
1040 	import simpledisplay;
1041 	auto win = new SimpleWindow(img.width, img.height * 3);
1042 	auto painter = win.draw();
1043 	painter.drawImage(Point(0, 0), Image.fromMemoryImage(img));
1044 	painter.drawImage(Point(0, img.height), Image.fromMemoryImage(qimg));
1045 	floydSteinbergDither(qimg, img);
1046 	painter.drawImage(Point(0, img.height * 2), Image.fromMemoryImage(qimg));
1047 	win.eventLoop(0);
1048 }
1049 +/
1050 
1051 /+
1052 /// If the background is transparent, it simply erases the alpha channel.
1053 void removeTransparency(IndexedImage img, Color background)
1054 +/
1055 
1056 Color alphaBlend(Color foreground, Color background) {
1057 	if(foreground.a != 255)
1058 	foreach(idx, ref part; foreground.components) {
1059 		part = cast(ubyte) (part * foreground.a / 255 +
1060 			background.components[idx] * (255 - foreground.a) / 255);
1061 	}
1062 
1063 	return foreground;
1064 }
1065 
1066 /*
1067 /// Reduces the number of colors in a palette.
1068 void reducePaletteSize(IndexedImage img, int maxColors = 16) {
1069 
1070 }
1071 */
1072 
1073 // I think I did this wrong... but the results aren't too bad so the bug can't be awful.
1074 /// Dithers img in place to look more like original.
1075 void floydSteinbergDither(IndexedImage img, in TrueColorImage original) {
1076 	assert(img.width == original.width);
1077 	assert(img.height == original.height);
1078 
1079 	auto buffer = new Color[](original.imageData.colors.length);
1080 
1081 	int x, y;
1082 
1083 	foreach(idx, c; original.imageData.colors) {
1084 		auto n = img.palette[img.data[idx]];
1085 		int errorR = cast(int) c.r - n.r;
1086 		int errorG = cast(int) c.g - n.g;
1087 		int errorB = cast(int) c.b - n.b;
1088 
1089 		void doit(int idxOffset, int multiplier) {
1090 		//	if(idx + idxOffset < buffer.length)
1091 				buffer[idx + idxOffset] = Color.fromIntegers(
1092 					c.r + multiplier * errorR / 16,
1093 					c.g + multiplier * errorG / 16,
1094 					c.b + multiplier * errorB / 16,
1095 					c.a
1096 				);
1097 		}
1098 
1099 		if((x+1) != original.width)
1100 			doit(1, 7);
1101 		if((y+1) != original.height) {
1102 			if(x != 0)
1103 				doit(-1 + img.width, 3);
1104 			doit(img.width, 5);
1105 			if(x+1 != original.width)
1106 				doit(1 + img.width, 1);
1107 		}
1108 
1109 		img.data[idx] = findNearestColor(img.palette, buffer[idx]);
1110 
1111 		x++;
1112 		if(x == original.width) {
1113 			x = 0;
1114 			y++;
1115 		}
1116 	}
1117 }
1118 
1119 // these are just really useful in a lot of places where the color/image functions are used,
1120 // so I want them available with Color
1121 struct Point {
1122 	int x;
1123 	int y;
1124 }
1125 
1126 struct Size {
1127 	int width;
1128 	int height;
1129 }
1130 
1131 struct Rectangle {
1132 	int left;
1133 	int top;
1134 	int right;
1135 	int bottom;
1136 }