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