The OpenD Programming Language

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