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