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