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