1 /+
2 	== pixmappaint ==
3 	Copyright Elias Batek (0xEAB) 2024.
4 	Distributed under the Boost Software License, Version 1.0.
5 +/
6 /++
7 	Pixmap image manipulation
8 
9 	$(WARNING
10 		$(B Early Technology Preview.)
11 	)
12 
13 	$(PITFALL
14 		This module is $(B work in progress).
15 		API is subject to changes until further notice.
16 	)
17 
18 	Pixmap refers to raster graphics, a subset of “bitmap” graphics.
19 	A pixmap is an array of pixels and the corresponding meta data to describe
20 	how an image if formed from those pixels.
21 	In the case of this library, a “width” field is used to map a specified
22 	number of pixels to a row of an image.
23 
24 
25 
26 
27 	### Pixel mapping
28 
29 	```text
30 	pixels := [ 0, 1, 2, 3 ]
31 	width  := 2
32 
33 	pixmap(pixels, width)
34 		=> [
35 			[ 0, 1 ]
36 			[ 2, 3 ]
37 		]
38 	```
39 
40 	```text
41 	pixels := [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ]
42 	width  := 3
43 
44 	pixmap(pixels, width)
45 		=> [
46 			[ 0,  1,  2 ]
47 			[ 3,  4,  5 ]
48 			[ 6,  7,  8 ]
49 			[ 9, 10, 11 ]
50 		]
51 	```
52 
53 	```text
54 	pixels := [ 0, 1, 2, 3, 4, 5, 6, 7 ]
55 	width  := 4
56 
57 	pixmap(pixels, width)
58 		=> [
59 			[ 0, 1, 2, 3 ]
60 			[ 4, 5, 6, 7 ]
61 		]
62 	```
63 
64 
65 
66 
67 	### Colors
68 
69 	Colors are stored in an RGBA format with 8 bit per channel.
70 	See [arsd.color.Color|Pixel] for details.
71 
72 
73 	### The coordinate system
74 
75 	The top left corner of a pixmap is its $(B origin) `(0,0)`.
76 
77 	The $(horizontal axis) is called `x`.
78 	Its corresponding length/dimension is known as `width`.
79 
80 	The letter `y` is used to describe the $(B vertical axis).
81 	Its corresponding length/dimension is known as `height`.
82 
83 	```
84 	0 → x
85 86 	y
87 	```
88 
89 	Furthermore, $(B length) refers to the areal size of a pixmap.
90 	It represents the total number of pixels in a pixmap.
91 	It follows from the foregoing that the term $(I long) usually refers to
92 	the length (not the width).
93 
94 
95 
96 
97 	### Pixmaps
98 
99 	A [Pixmap] consist of two fields:
100 	$(LIST
101 		* a slice (of an array of [Pixel|Pixels])
102 		* a width
103 	)
104 
105 	This design comes with many advantages.
106 	First and foremost it brings simplicity.
107 
108 	Pixel data buffers can be reused across pixmaps,
109 	even when those have different sizes.
110 	Simply slice the buffer to fit just enough pixels for the new pixmap.
111 
112 	Memory management can also happen outside of the pixmap.
113 	It is possible to use a buffer allocated elsewhere. (Such a one shouldn’t
114 	be mixed with the built-in memory management facilities of the pixmap type.
115 	Otherwise one will end up with GC-allocated copies.)
116 
117 	The most important downside is that it makes pixmaps basically a partial
118 	reference type.
119 
120 	Copying a pixmap creates a shallow copy still poiting to the same pixel
121 	data that is also used by the source pixmap.
122 	This implies that manipulating the source pixels also manipulates the
123 	pixels of the copy – and vice versa.
124 
125 	The issues implied by this become an apparent when one of the references
126 	modifies the pixel data in a way that also affects the dimensions of the
127 	image; such as cropping.
128 
129 	Pixmaps describe how pixel data stored in a 1-dimensional memory space is
130 	meant to be interpreted as a 2-dimensional image.
131 
132 	A notable implication of this 1D ↔ 2D mapping is, that slicing the 1D data
133 	leads to non-sensical results in the 2D space when the 1D-slice is
134 	reinterpreted as 2D-image.
135 
136 	Especially slicing across scanlines (→ horizontal rows of an image) is
137 	prone to such errors.
138 
139 	(Slicing of the 1D array data can actually be utilized to cut off the
140 	top or bottom part of an image. Any other naiv cropping operations will run
141 	into the aforementioned issues.)
142 
143 
144 
145 
146 	### Image manipulation
147 
148 	The term “image manipulation function” here refers to functions that
149 	manipulate (e.g. transform) an image as a whole.
150 
151 	Image manipulation functions in this library are provided in up to three
152 	flavors:
153 
154 	$(LIST
155 		* a “source to target” function
156 		* a “source to newly allocated target” wrapper
157 		* $(I optionally) an “in-place” adaption
158 	)
159 
160 	Additionally, a “compute dimensions of target” function is provided.
161 
162 
163 	#### Source to Target
164 
165 	The regular “source to target” function takes (at least) two parameters:
166 	A source [Pixmap] and a target [Pixmap].
167 
168 	(Additional operation-specific arguments may be required as well.)
169 
170 	The target pixmap usually needs to be able to fit at least the same number
171 	of pixels as the source holds.
172 	Use the corresponding “compute size of target function” to calculate the
173 	required size when needed.
174 	(A notable exception would be cropping, where to target pixmap must be only
175 	at least long enough to hold the area of the size to crop to.)
176 
177 	The data stored in the buffer of the target pixmap is overwritten by the
178 	operation.
179 
180 	A modified Pixmap structure with adjusted dimensions is returned.
181 
182 	These functions are named plain and simple after the respective operation
183 	they perform; e.g. [flipHorizontally] or [crop].
184 
185 	---
186 	// Allocate a new target Pixmap.
187 	Pixmap target = Pixmap.makeNew(
188 		flipHorizontallyCalcDims(sourceImage)
189 	);
190 
191 	// Flip the image horizontally and store the updated structure.
192 	// (Note: As a horizontal flip does not affect the dimensions of a Pixmap,
193 	//        storing the updated structure would not be necessary
194 	//        in this specific scenario.)
195 	target = sourceImage.flipHorizontally(target);
196 	---
197 
198 	---
199 	const cropOffset = Point(0, 0);
200 	const cropSize = Size(100, 100);
201 
202 	// Allocate a new target Pixmap.
203 	Pixmap target = Pixmap.makeNew(
204 		cropCalcDims(sourceImage, cropSize, cropOffset)
205 	);
206 
207 	// Crop the Pixmap.
208 	target = sourceImage.crop(target, cropSize, cropOffset);
209 	---
210 
211 	$(PITFALL
212 		“Source to target” functions do not work in place.
213 		Do not attempt to pass Pixmaps sharing the same buffer for both source
214 		and target. Such would lead to bad results with heavy artifacts.
215 
216 		Use the “in-place” variant of the operation instead.
217 
218 		Moreover:
219 		Do not use the artifacts produced by this as a creative effect.
220 		Those are an implementation detail (and may change at any point).
221 	)
222 
223 
224 	#### Source to New Target
225 
226 	The “source to newly allocated target” wrapper allocates a new buffer to
227 	hold the manipulated target.
228 
229 	These wrappers are provided for user convenience.
230 
231 	They are identified by the suffix `-New` that is appended to the name of
232 	the corresponding “source to target” function;
233 	e.g. [flipHorizontallyNew] or [cropNew].
234 
235 	---
236 	// Create a new flipped Pixmap.
237 	Pixmap target = sourceImage.flipHorizontallyNew();
238 	---
239 
240 	---
241 	const cropOffset = Point(0, 0);
242 	const cropSize = Size(100, 100);
243 
244 	// Create a new cropped Pixmap.
245 	Pixmap target = sourceImage.cropNew(cropSize, cropOffset);
246 	---
247 
248 
249 	#### In-Place
250 
251 	For selected image manipulation functions a special adaption is provided
252 	that stores the result in the source pixel data buffer.
253 
254 	Depending on the operation, implementing in-place transformations can be
255 	either straightforward or a major undertaking (and topic of research).
256 	This library focuses and the former case and leaves out those where the
257 	latter applies.
258 	In particular, algorithms that require allocating further buffers to store
259 	temporary results or auxiliary data will probably not get implemented.
260 
261 	Furthermore, operations where to result is larger than the source cannot
262 	be performed in-place.
263 
264 	Certain in-place manipulation functions return a shallow-copy of the
265 	source structure with dimensions adjusted accordingly.
266 	This is behavior is not streamlined consistently as the lack of an
267 	in-place option for certain operations makes them a special case anyway.
268 
269 	These function are suffixed with `-InPlace`;
270 	e.g. [flipHorizontallyInPlace] or [cropInPlace].
271 
272 	$(TIP
273 		Manipulating the source image directly can lead to unexpected results
274 		when the source image is used in multiple places.
275 	)
276 
277 	$(NOTE
278 		Users are usually better off to utilize the regular “source to target”
279 		functions with a reused pixel data buffer.
280 
281 		These functions do not serve as a performance optimization.
282 		Some of them might perform significantly worse than their regular
283 		variant. Always benchmark and profile.
284 	)
285 
286 	---
287 	image.flipHorizontallyInPlace();
288 	---
289 
290 	---
291 	const cropOffset = Point(0, 0);
292 	const cropSize = Size(100, 100);
293 
294 	image = image.cropInPlace(cropSize, cropOffset);
295 	---
296 
297 
298 	#### Compute size of target
299 
300 	Functions to “compute (the) dimensions of (a) target” are primarily meant
301 	to be utilized to calculate the size for allocating new pixmaps to be used
302 	as a target for manipulation functions.
303 
304 	They are provided for all manipulation functions even in cases where they
305 	are provide little to no benefit. This is for consistency and to ease
306 	development.
307 
308 	Such functions are identified by a `-CalcDims` suffix;
309 	e.g. [flipHorizontallyCalcDims] or [cropCalcDims].
310 
311 	They usually take the same parameters as their corresponding
312 	“source to new target” function. This does not apply in cases where
313 	certain parameters are irrelevant for the computation of the target size.
314  +/
315 module arsd.pixmappaint;
316 
317 import arsd.color;
318 import arsd.core;
319 
320 private float roundImpl(float f) {
321 	import std.math : round;
322 
323 	return round(f);
324 }
325 
326 // `pure` rounding function.
327 // std.math.round() isn’t pure on all targets.
328 // → <https://issues.dlang.org/show_bug.cgi?id=11320>
329 private float round(float f) pure @nogc nothrow @trusted {
330 	return (castTo!(float function(float) pure @nogc nothrow)(&roundImpl))(f);
331 }
332 
333 /*
334 	## TODO:
335 
336 	- Refactoring the template-mess of blendPixel() & co.
337 	- Rotating (by arbitrary angles)
338 	- Skewing
339 	- HSL
340 	- Advanced blend modes (maybe)
341  */
342 
343 ///
344 alias Color = arsd.color.Color;
345 
346 ///
347 alias ColorF = arsd.color.ColorF;
348 
349 ///
350 alias Pixel = Color;
351 
352 ///
353 alias Point = arsd.color.Point;
354 
355 ///
356 alias Rectangle = arsd.color.Rectangle;
357 
358 ///
359 alias Size = arsd.color.Size;
360 
361 // verify assumption(s)
362 static assert(Pixel.sizeof == uint.sizeof);
363 
364 @safe pure nothrow @nogc {
365 	///
366 	Pixel rgba(ubyte r, ubyte g, ubyte b, ubyte a = 0xFF) {
367 		return Pixel(r, g, b, a);
368 	}
369 
370 	///
371 	Pixel rgba(ubyte r, ubyte g, ubyte b, float aPct) {
372 		return Pixel(r, g, b, percentageDecimalToUInt8(aPct));
373 	}
374 
375 	///
376 	Pixel rgb(ubyte r, ubyte g, ubyte b) {
377 		return rgba(r, g, b, 0xFF);
378 	}
379 }
380 
381 /++
382 	Unsigned 64-bit fixed-point decimal type
383 
384 	Assigns 32 bits to the digits of the pre-decimal point portion
385 	and the other 32 bits to fractional digits.
386  +/
387 struct UDecimal {
388 	private {
389 		ulong _value = 0;
390 	}
391 
392 @safe pure nothrow @nogc:
393 
394 	///
395 	public this(uint initialValue) {
396 		_value = (ulong(initialValue) << 32);
397 	}
398 
399 	private static UDecimal make(ulong internal) {
400 		auto result = UDecimal();
401 		result._value = internal;
402 		return result;
403 	}
404 
405 	///
406 	T opCast(T : uint)() const {
407 		return (_value >> 32).castTo!uint;
408 	}
409 
410 	///
411 	T opCast(T : double)() const {
412 		return (_value / double(0xFFFF_FFFF));
413 	}
414 
415 	///
416 	T opCast(T : float)() const {
417 		return (_value / float(0xFFFF_FFFF));
418 	}
419 
420 	///
421 	public UDecimal round() const {
422 		const truncated = (_value & 0xFFFF_FFFF_0000_0000);
423 		const delta = _value - truncated;
424 
425 		// dfmt off
426 		const rounded = (delta >= 0x8000_0000)
427 			? truncated + 0x1_0000_0000
428 			: truncated;
429 		// dfmt on
430 
431 		return UDecimal.make(rounded);
432 	}
433 
434 	///
435 	public UDecimal roundEven() const {
436 		const truncated = (_value & 0xFFFF_FFFF_0000_0000);
437 		const delta = _value - truncated;
438 
439 		ulong rounded;
440 
441 		if (delta == 0x8000_0000) {
442 			const bool floorIsOdd = ((truncated & 0x1_0000_0000) != 0);
443 			// dfmt off
444 			rounded = (floorIsOdd)
445 				? truncated + 0x1_0000_0000 // ceil
446 				: truncated;                // floor
447 			// dfmt on
448 		} else if (delta > 0x8000_0000) {
449 			rounded = truncated + 0x1_0000_0000;
450 		} else {
451 			rounded = truncated;
452 		}
453 
454 		return UDecimal.make(rounded);
455 	}
456 
457 	///
458 	public UDecimal floor() const {
459 		const truncated = (_value & 0xFFFF_FFFF_0000_0000);
460 		return UDecimal.make(truncated);
461 	}
462 
463 	///
464 	public UDecimal ceil() const {
465 		const truncated = (_value & 0xFFFF_FFFF_0000_0000);
466 
467 		// dfmt off
468 		const ceiling = (truncated != _value)
469 			? truncated + 0x1_0000_0000
470 			: truncated;
471 		// dfmt on
472 
473 		return UDecimal.make(ceiling);
474 	}
475 
476 	///
477 	public uint fractionalDigits() const {
478 		return (_value & 0x0000_0000_FFFF_FFFF);
479 	}
480 
481 	public {
482 		///
483 		int opCmp(const UDecimal that) const {
484 			return ((this._value > that._value) - (this._value < that._value));
485 		}
486 	}
487 
488 	public {
489 		///
490 		UDecimal opBinary(string op : "+")(const uint rhs) const {
491 			return UDecimal.make(_value + (ulong(rhs) << 32));
492 		}
493 
494 		/// ditto
495 		UDecimal opBinary(string op : "+")(const UDecimal rhs) const {
496 			return UDecimal.make(_value + rhs._value);
497 		}
498 
499 		/// ditto
500 		UDecimal opBinary(string op : "-")(const uint rhs) const {
501 			return UDecimal.make(_value - (ulong(rhs) << 32));
502 		}
503 
504 		/// ditto
505 		UDecimal opBinary(string op : "-")(const UDecimal rhs) const {
506 			return UDecimal.make(_value - rhs._value);
507 		}
508 
509 		/// ditto
510 		UDecimal opBinary(string op : "*")(const uint rhs) const {
511 			return UDecimal.make(_value * rhs);
512 		}
513 
514 		/// ditto
515 		UDecimal opBinary(string op : "/")(const uint rhs) const {
516 			return UDecimal.make(_value / rhs);
517 		}
518 
519 		/// ditto
520 		UDecimal opBinary(string op : "<<")(const uint rhs) const {
521 			return UDecimal.make(_value << rhs);
522 		}
523 
524 		/// ditto
525 		UDecimal opBinary(string op : ">>")(const uint rhs) const {
526 			return UDecimal.make(_value >> rhs);
527 		}
528 	}
529 
530 	public {
531 		///
532 		UDecimal opBinaryRight(string op : "+")(const uint lhs) const {
533 			return UDecimal.make((ulong(lhs) << 32) + _value);
534 		}
535 
536 		/// ditto
537 		UDecimal opBinaryRight(string op : "-")(const uint lhs) const {
538 			return UDecimal.make((ulong(lhs) << 32) - _value);
539 		}
540 
541 		/// ditto
542 		UDecimal opBinaryRight(string op : "*")(const uint lhs) const {
543 			return UDecimal.make(lhs * _value);
544 		}
545 
546 		/// ditto
547 		UDecimal opBinaryRight(string op : "/")(const uint) const {
548 			static assert(false, "Use `uint(…) / cast(uint)(UDecimal(…))` instead.");
549 		}
550 	}
551 
552 	public {
553 		///
554 		UDecimal opOpAssign(string op : "+")(const uint rhs) {
555 			_value += (ulong(rhs) << 32);
556 			return this;
557 		}
558 
559 		/// ditto
560 		UDecimal opOpAssign(string op : "+")(const UDecimal rhs) {
561 			_value += rhs._value;
562 			return this;
563 		}
564 
565 		/// ditto
566 		UDecimal opOpAssign(string op : "-")(const uint rhs) {
567 			_value -= (ulong(rhs) << 32);
568 			return this;
569 		}
570 
571 		/// ditto
572 		UDecimal opOpAssign(string op : "-")(const UDecimal rhs) {
573 			_value -= rhs._value;
574 			return this;
575 		}
576 
577 		/// ditto
578 		UDecimal opOpAssign(string op : "*")(const uint rhs) {
579 			_value *= rhs;
580 			return this;
581 		}
582 
583 		/// ditto
584 		UDecimal opOpAssign(string op : "/")(const uint rhs) {
585 			_value /= rhs;
586 			return this;
587 		}
588 
589 		/// ditto
590 		UDecimal opOpAssign(string op : "<<")(const uint rhs) const {
591 			_value <<= rhs;
592 			return this;
593 		}
594 
595 		/// ditto
596 		UDecimal opOpAssign(string op : ">>")(const uint rhs) const {
597 			_value >>= rhs;
598 			return this;
599 		}
600 	}
601 }
602 
603 @safe unittest {
604 	assert(UDecimal(uint.max).castTo!uint == uint.max);
605 	assert(UDecimal(uint.min).castTo!uint == uint.min);
606 	assert(UDecimal(1).castTo!uint == 1);
607 	assert(UDecimal(2).castTo!uint == 2);
608 	assert(UDecimal(1_991_007).castTo!uint == 1_991_007);
609 
610 	assert((UDecimal(10) + 9).castTo!uint == 19);
611 	assert((UDecimal(10) - 9).castTo!uint == 1);
612 	assert((UDecimal(10) * 9).castTo!uint == 90);
613 	assert((UDecimal(99) / 9).castTo!uint == 11);
614 
615 	assert((4 + UDecimal(4)).castTo!uint == 8);
616 	assert((4 - UDecimal(4)).castTo!uint == 0);
617 	assert((4 * UDecimal(4)).castTo!uint == 16);
618 
619 	assert((UDecimal(uint.max) / 2).castTo!uint == 2_147_483_647);
620 	assert((UDecimal(uint.max) / 2).round().castTo!uint == 2_147_483_648);
621 
622 	assert((UDecimal(10) / 8).round().castTo!uint == 1);
623 	assert((UDecimal(10) / 8).floor().castTo!uint == 1);
624 	assert((UDecimal(10) / 8).ceil().castTo!uint == 2);
625 
626 	assert((UDecimal(10) / 4).round().castTo!uint == 3);
627 	assert((UDecimal(10) / 4).floor().castTo!uint == 2);
628 	assert((UDecimal(10) / 4).ceil().castTo!uint == 3);
629 
630 	assert((UDecimal(10) / 5).round().castTo!uint == 2);
631 	assert((UDecimal(10) / 5).floor().castTo!uint == 2);
632 	assert((UDecimal(10) / 5).ceil().castTo!uint == 2);
633 }
634 
635 @safe unittest {
636 	UDecimal val;
637 
638 	val = (UDecimal(1) / 2);
639 	assert(val.roundEven().castTo!uint == 0);
640 	assert(val.castTo!double > 0.49);
641 	assert(val.castTo!double < 0.51);
642 
643 	val = (UDecimal(3) / 2);
644 	assert(val.roundEven().castTo!uint == 2);
645 	assert(val.castTo!double > 1.49);
646 	assert(val.castTo!double < 1.51);
647 }
648 
649 @safe unittest {
650 	UDecimal val;
651 
652 	val = UDecimal(10);
653 	val += 12;
654 	assert(val.castTo!uint == 22);
655 
656 	val = UDecimal(1024);
657 	val -= 24;
658 	assert(val.castTo!uint == 1000);
659 	val -= 100;
660 	assert(val.castTo!uint == 900);
661 	val += 5;
662 	assert(val.castTo!uint == 905);
663 
664 	val = UDecimal(256);
665 	val *= 4;
666 	assert(val.castTo!uint == (256 * 4));
667 
668 	val = UDecimal(2048);
669 	val /= 10;
670 	val *= 10;
671 	assert(val.castTo!uint == 2047);
672 }
673 
674 @safe unittest {
675 	UDecimal val;
676 
677 	val = UDecimal(9_000_000);
678 	val /= 13;
679 	val *= 4;
680 
681 	// ≈ 2,769,230.8
682 	assert(val.castTo!uint == 2_769_230);
683 	assert(val.round().castTo!uint == 2_769_231);
684 	// assert(uint(9_000_000) / uint(13) * uint(4) == 2_769_228);
685 
686 	val = UDecimal(64);
687 	val /= 31;
688 	val *= 30;
689 	val /= 29;
690 	val *= 28;
691 
692 	// ≈ 59.8
693 	assert(val.castTo!uint == 59);
694 	assert(val.round().castTo!uint == 60);
695 	// assert(((((64 / 31) * 30) / 29) * 28) == 56);
696 }
697 
698 /++
699 	$(I Advanced functionality.)
700 
701 	Meta data for the construction of a Pixmap.
702  +/
703 struct PixmapBlueprint {
704 	/++
705 		Total number of pixels stored in a Pixmap.
706 	 +/
707 	size_t length;
708 
709 	/++
710 		Width of a Pixmap.
711 	 +/
712 	int width;
713 
714 @safe pure nothrow @nogc:
715 
716 	///
717 	public static PixmapBlueprint fromSize(const Size size) {
718 		return PixmapBlueprint(
719 			size.area,
720 			size.width,
721 		);
722 	}
723 
724 	///
725 	public static PixmapBlueprint fromPixmap(const Pixmap pixmap) {
726 		return PixmapBlueprint(
727 			pixmap.length,
728 			pixmap.width,
729 		);
730 	}
731 
732 	/++
733 		Determines whether the blueprint is plausible.
734 	 +/
735 	bool isValid() const {
736 		return ((length % width) == 0);
737 	}
738 
739 	/++
740 		Height of a Pixmap.
741 
742 		See_also:
743 			This is the counterpart to the dimension known as [width].
744 	 +/
745 	int height() const {
746 		return castTo!int(length / width);
747 	}
748 
749 	///
750 	Size size() const {
751 		return Size(width, height);
752 	}
753 }
754 
755 /++
756 	Pixel data container
757  +/
758 struct Pixmap {
759 
760 	/// Pixel data
761 	Pixel[] data;
762 
763 	/// Pixel per row
764 	int width;
765 
766 @safe pure nothrow:
767 
768 	///
769 	deprecated("Use `Pixmap.makeNew(size)` instead.")
770 	this(Size size) {
771 		this.size = size;
772 	}
773 
774 	///
775 	deprecated("Use `Pixmap.makeNew(Size(width, height))` instead.")
776 	this(int width, int height)
777 	in (width > 0)
778 	in (height > 0) {
779 		this(Size(width, height));
780 	}
781 
782 	///
783 	this(inout(Pixel)[] data, int width) inout @nogc
784 	in (data.length % width == 0) {
785 		this.data = data;
786 		this.width = width;
787 	}
788 
789 	///
790 	static Pixmap makeNew(PixmapBlueprint blueprint) {
791 		auto data = new Pixel[](blueprint.length);
792 		return Pixmap(data, blueprint.width);
793 	}
794 
795 	///
796 	static Pixmap makeNew(Size size) {
797 		return Pixmap.makeNew(PixmapBlueprint.fromSize(size));
798 	}
799 
800 	/++
801 		Creates a $(I deep copy) of the Pixmap
802 	 +/
803 	Pixmap clone() const {
804 		return Pixmap(
805 			this.data.dup,
806 			this.width,
807 		);
808 	}
809 
810 	/++
811 		Copies the pixel data to the target Pixmap.
812 
813 		Returns:
814 			A size-adjusted shallow copy of the input Pixmap overwritten
815 			with the image data of the SubPixmap.
816 
817 		$(PITFALL
818 			While the returned Pixmap utilizes the buffer provided by the input,
819 			the returned Pixmap might not exactly match the input.
820 
821 			Always use the returned Pixmap structure.
822 
823 			---
824 			// Same buffer, but new structure:
825 			auto pixmap2 = source.copyTo(pixmap);
826 
827 			// Alternatively, replace the old structure:
828 			pixmap = source.copyTo(pixmap);
829 			---
830 		)
831 	 +/
832 	Pixmap copyTo(Pixmap target) @nogc const {
833 		// Length adjustment
834 		const l = this.length;
835 		if (target.data.length < l) {
836 			assert(false, "The target Pixmap is too small.");
837 		} else if (target.data.length > l) {
838 			target.data = target.data[0 .. l];
839 		}
840 
841 		copyToImpl(target);
842 
843 		return target;
844 	}
845 
846 	private void copyToImpl(Pixmap target) @nogc const {
847 		target.data[] = this.data[];
848 	}
849 
850 	// undocumented: really shouldn’t be used.
851 	// carries the risks of `length` and `width` getting out of sync accidentally.
852 	deprecated("Use `size` instead.")
853 	void length(int value) {
854 		data.length = value;
855 	}
856 
857 	/++
858 		Changes the size of the buffer
859 
860 		Reallocates the underlying pixel array.
861 	 +/
862 	void size(Size value) {
863 		data.length = value.area;
864 		width = value.width;
865 	}
866 
867 	/// ditto
868 	void size(int totalPixels, int width)
869 	in (totalPixels % width == 0) {
870 		data.length = totalPixels;
871 		this.width = width;
872 	}
873 
874 	static {
875 		/++
876 			Creates a Pixmap wrapping the pixel data from the provided `TrueColorImage`.
877 
878 			Interoperability function: `arsd.color`
879 		 +/
880 		Pixmap fromTrueColorImage(TrueColorImage source) @nogc {
881 			return Pixmap(source.imageData.colors, source.width);
882 		}
883 
884 		/++
885 			Creates a Pixmap wrapping the pixel data from the provided `MemoryImage`.
886 
887 			Interoperability function: `arsd.color`
888 		 +/
889 		Pixmap fromMemoryImage(MemoryImage source) {
890 			return fromTrueColorImage(source.getAsTrueColorImage());
891 		}
892 	}
893 
894 @safe pure nothrow @nogc:
895 
896 	/// Height of the buffer, i.e. the number of lines
897 	int height() inout {
898 		if (width == 0) {
899 			return 0;
900 		}
901 
902 		return castTo!int(data.length / width);
903 	}
904 
905 	/// Rectangular size of the buffer
906 	Size size() inout {
907 		return Size(width, height);
908 	}
909 
910 	/// Length of the buffer, i.e. the number of pixels
911 	int length() inout {
912 		return castTo!int(data.length);
913 	}
914 
915 	/++
916 		Number of bytes per line
917 
918 		Returns:
919 			width × Pixel.sizeof
920 	 +/
921 	int pitch() inout {
922 		return (width * int(Pixel.sizeof));
923 	}
924 
925 	/++
926 		Adjusts the Pixmap according to the provided blueprint.
927 
928 		The blueprint must not be larger than the data buffer of the pixmap.
929 
930 		This function does not reallocate the pixel data buffer.
931 
932 		If the blueprint is larger than the data buffer of the pixmap,
933 		this will result in a bounds-check error if applicable.
934 	 +/
935 	void adjustTo(PixmapBlueprint blueprint) {
936 		debug assert(this.data.length >= blueprint.length);
937 		debug assert(blueprint.isValid);
938 		this.data = this.data[0 .. blueprint.length];
939 		this.width = blueprint.width;
940 	}
941 
942 	/++
943 		Calculates the index (linear offset) of the requested position
944 		within the pixmap data.
945 	+/
946 	int scanTo(Point pos) inout {
947 		return linearOffset(width, pos);
948 	}
949 
950 	/++
951 		Accesses the pixel at the requested position within the pixmap data.
952 	 +/
953 	ref inout(Pixel) scan(Point pos) inout {
954 		return data[scanTo(pos)];
955 	}
956 
957 	/++
958 		Retrieves a linear slice of the pixmap.
959 
960 		Returns:
961 			`n` pixels starting at the top-left position `pos`.
962 	 +/
963 	inout(Pixel)[] scan(Point pos, int n) inout {
964 		immutable size_t offset = linearOffset(width, pos);
965 		immutable size_t end = (offset + n);
966 		return data[offset .. end];
967 	}
968 
969 	/// ditto
970 	inout(Pixel)[] sliceAt(Point pos, int n) inout {
971 		return scan(pos, n);
972 	}
973 
974 	/++
975 		Retrieves a rectangular subimage of the pixmap.
976 	 +/
977 	inout(SubPixmap) scanArea(Point pos, Size size) inout {
978 		return inout(SubPixmap)(this, size, pos);
979 	}
980 
981 	/// TODO: remove
982 	deprecated alias scanSubPixmap = scanArea;
983 
984 	/// TODO: remove
985 	deprecated alias scan2D = scanArea;
986 
987 	/++
988 		Retrieves the first line of the Pixmap.
989 
990 		See_also:
991 			Check out [PixmapScanner] for more useful scanning functionality.
992 	 +/
993 	inout(Pixel)[] scanLine() inout {
994 		return data[0 .. width];
995 	}
996 
997 	public {
998 		/++
999 			Provides access to a single pixel at the requested 2D-position.
1000 
1001 			See_also:
1002 				Accessing pixels through the [data] array will be more useful,
1003 				usually.
1004 		 +/
1005 		ref inout(Pixel) accessPixel(Point pos) inout @system {
1006 			const idx = linearOffset(pos, this.width);
1007 			return this.data[idx];
1008 		}
1009 
1010 		/// ditto
1011 		Pixel getPixel(Point pos) const {
1012 			const idx = linearOffset(pos, this.width);
1013 			return this.data[idx];
1014 		}
1015 
1016 		/// ditto
1017 		Pixel getPixel(int x, int y) const {
1018 			return this.getPixel(Point(x, y));
1019 		}
1020 
1021 		/// ditto
1022 		void setPixel(Point pos, Pixel value) {
1023 			const idx = linearOffset(pos, this.width);
1024 			this.data[idx] = value;
1025 		}
1026 
1027 		/// ditto
1028 		void setPixel(int x, int y, Pixel value) {
1029 			return this.setPixel(Point(x, y), value);
1030 		}
1031 	}
1032 
1033 	/// Clears the buffer’s contents (by setting each pixel to the same color)
1034 	void clear(Pixel value) {
1035 		data[] = value;
1036 	}
1037 }
1038 
1039 /++
1040 	A subpixmap represents a subimage of a [Pixmap].
1041 
1042 	This wrapper provides convenient access to a rectangular slice of a Pixmap.
1043 
1044 	```
1045 	╔═════════════╗
1046 	║ Pixmap      ║
1047 	║             ║
1048 	║      ┌───┐  ║
1049 	║      │Sub│  ║
1050 	║      └───┘  ║
1051 	╚═════════════╝
1052 	```
1053  +/
1054 struct SubPixmap {
1055 
1056 	/++
1057 		Source image referenced by the subimage
1058 	 +/
1059 	Pixmap source;
1060 
1061 	/++
1062 		Size of the subimage
1063 	 +/
1064 	Size size;
1065 
1066 	/++
1067 		2D offset of the subimage
1068 	 +/
1069 	Point offset;
1070 
1071 	public @safe pure nothrow @nogc {
1072 		///
1073 		this(inout Pixmap source, Size size = Size(0, 0), Point offset = Point(0, 0)) inout {
1074 			this.source = source;
1075 			this.size = size;
1076 			this.offset = offset;
1077 		}
1078 
1079 		///
1080 		this(inout Pixmap source, Point offset, Size size = Size(0, 0)) inout {
1081 			this(source, size, offset);
1082 		}
1083 	}
1084 
1085 @safe pure nothrow:
1086 
1087 	public {
1088 		/++
1089 			Allocates a new Pixmap cropped to the pixel data of the subimage.
1090 
1091 			See_also:
1092 				Use [extractToPixmap] for a non-allocating variant with a
1093 				target parameter.
1094 		 +/
1095 		Pixmap extractToNewPixmap() const {
1096 			auto pm = Pixmap.makeNew(size);
1097 			this.extractToPixmap(pm);
1098 			return pm;
1099 		}
1100 
1101 		/++
1102 			Copies the pixel data – cropped to the subimage region –
1103 			into the target Pixmap.
1104 
1105 			$(PITFALL
1106 				Do not attempt to extract a subimage back into the source pixmap.
1107 				This will fail in cases where source and target regions overlap
1108 				and potentially crash the program.
1109 			)
1110 
1111 			Returns:
1112 				A size-adjusted shallow copy of the input Pixmap overwritten
1113 				with the image data of the SubPixmap.
1114 
1115 			$(PITFALL
1116 				While the returned Pixmap utilizes the buffer provided by the input,
1117 				the returned Pixmap might not exactly match the input.
1118 				The dimensions (width and height) and the length might have changed.
1119 
1120 				Always use the returned Pixmap structure.
1121 
1122 				---
1123 				// Same buffer, but new structure:
1124 				auto pixmap2 = subPixmap.extractToPixmap(pixmap);
1125 
1126 				// Alternatively, replace the old structure:
1127 				pixmap = subPixmap.extractToPixmap(pixmap);
1128 				---
1129 			)
1130 		 +/
1131 		Pixmap extractToPixmap(Pixmap target) @nogc const {
1132 			// Length adjustment
1133 			const l = this.length;
1134 			if (target.data.length < l) {
1135 				assert(false, "The target Pixmap is too small.");
1136 			} else if (target.data.length > l) {
1137 				target.data = target.data[0 .. l];
1138 			}
1139 
1140 			target.width = this.width;
1141 
1142 			extractToPixmapCopyImpl(target);
1143 			return target;
1144 		}
1145 
1146 		private void extractToPixmapCopyImpl(Pixmap target) @nogc const {
1147 			auto src = SubPixmapScanner(this);
1148 			auto dst = PixmapScannerRW(target);
1149 
1150 			foreach (dstLine; dst) {
1151 				dstLine[] = src.front[];
1152 				src.popFront();
1153 			}
1154 		}
1155 
1156 		private void extractToPixmapCopyPixelByPixelImpl(Pixmap target) @nogc const {
1157 			auto src = SubPixmapScanner(this);
1158 			auto dst = PixmapScannerRW(target);
1159 
1160 			foreach (dstLine; dst) {
1161 				const srcLine = src.front;
1162 				foreach (idx, ref px; dstLine) {
1163 					px = srcLine[idx];
1164 				}
1165 				src.popFront();
1166 			}
1167 		}
1168 	}
1169 
1170 @safe pure nothrow @nogc:
1171 
1172 	public {
1173 		/++
1174 			Width of the subimage.
1175 		 +/
1176 		int width() const {
1177 			return size.width;
1178 		}
1179 
1180 		/// ditto
1181 		void width(int value) {
1182 			size.width = value;
1183 		}
1184 
1185 		/++
1186 			Height of the subimage.
1187 		 +/
1188 		int height() const {
1189 			return size.height;
1190 		}
1191 
1192 		/// ditto
1193 		void height(int value) {
1194 			size.height = value;
1195 		}
1196 
1197 		/++
1198 			Number of pixels in the subimage.
1199 		 +/
1200 		int length() const {
1201 			return size.area;
1202 		}
1203 	}
1204 
1205 	public {
1206 		/++
1207 			Linear offset of the subimage within the source image.
1208 
1209 			Calculates the index of the “first pixel of the subimage”
1210 			in the “pixel data of the source image”.
1211 		 +/
1212 		int sourceOffsetLinear() const {
1213 			return linearOffset(offset, source.width);
1214 		}
1215 
1216 		/// ditto
1217 		void sourceOffsetLinear(int value) {
1218 			this.offset = Point.fromLinearOffset(value, source.width);
1219 		}
1220 
1221 		/++
1222 			$(I Advanced functionality.)
1223 
1224 			Offset of the pixel following the bottom right corner of the subimage.
1225 
1226 			(`Point(O, 0)` is the top left corner of the source image.)
1227 		 +/
1228 		Point sourceOffsetEnd() const {
1229 			auto vec = Point(size.width, (size.height - 1));
1230 			return (offset + vec);
1231 		}
1232 
1233 		/++
1234 			Linear offset of the subimage within the source image.
1235 
1236 			Calculates the index of the “first pixel of the subimage”
1237 			in the “pixel data of the source image”.
1238 		 +/
1239 		int sourceOffsetLinearEnd() const {
1240 			return linearOffset(sourceOffsetEnd, source.width);
1241 		}
1242 	}
1243 
1244 	/++
1245 		Determines whether the area of the subimage
1246 		lies within the source image
1247 		and does not overflow its lines.
1248 
1249 		$(TIP
1250 			If the offset and/or size of a subimage are off, two issues can occur:
1251 
1252 			$(LIST
1253 				* The resulting subimage will look displaced.
1254 				  (As if the lines were shifted.)
1255 				  This indicates that one scanline of the subimage spans over
1256 				  two ore more lines of the source image.
1257 				  (Happens when `(subimage.offset.x + subimage.size.width) > source.size.width`.)
1258 				* When accessing the pixel data, bounds checks will fail.
1259 				  This suggests that the area of the subimage extends beyond
1260 				  the bottom end (and optionally also beyond the right end) of
1261 				  the source.
1262 			)
1263 
1264 			Both defects could indicate an invalid subimage.
1265 			Use this function to verify the SubPixmap.
1266 		)
1267 
1268 		$(WARNING
1269 			Do not use invalid SubPixmaps.
1270 			The library assumes that the SubPixmaps it receives are always valid.
1271 
1272 			Non-valid SubPixmaps are not meant to be used for creative effects
1273 			or similar either. Such uses might lead to unexpected quirks or
1274 			crashes eventually.
1275 		)
1276 	 +/
1277 	bool isValid() const {
1278 		return (
1279 			(sourceMarginLeft >= 0)
1280 				&& (sourceMarginTop >= 0)
1281 				&& (sourceMarginBottom >= 0)
1282 				&& (sourceMarginRight >= 0)
1283 		);
1284 	}
1285 
1286 	public inout {
1287 		/++
1288 			Retrieves the pixel at the requested position of the subimage.
1289 		 +/
1290 		ref inout(Pixel) scan(Point pos) {
1291 			return source.scan(offset + pos);
1292 		}
1293 
1294 		/++
1295 			Retrieves the first line of the subimage.
1296 		 +/
1297 		inout(Pixel)[] scanLine() {
1298 			const lo = linearOffset(offset, size.width);
1299 			return source.data[lo .. size.width];
1300 		}
1301 	}
1302 
1303 	/++
1304 		Copies the pixels of this subimage to a target image.
1305 
1306 		The target MUST have the same size.
1307 
1308 		See_also:
1309 			Usually you’ll want to use [extractToPixmap] or [drawPixmap] instead.
1310 	 +/
1311 	public void xferTo(SubPixmap target) const {
1312 		debug assert(target.size == this.size);
1313 
1314 		auto src = SubPixmapScanner(this);
1315 		auto dst = SubPixmapScannerRW(target);
1316 
1317 		foreach (dstLine; dst) {
1318 			dstLine[] = src.front[];
1319 			src.popFront();
1320 		}
1321 	}
1322 
1323 	/++
1324 		Blends the pixels of this subimage into a target image.
1325 
1326 		The target MUST have the same size.
1327 
1328 		See_also:
1329 			Usually you’ll want to use [extractToPixmap] or [drawPixmap] instead.
1330 	 +/
1331 	public void xferTo(SubPixmap target, Blend blend) const {
1332 		debug assert(target.size == this.size);
1333 
1334 		auto src = SubPixmapScanner(this);
1335 		auto dst = SubPixmapScannerRW(target);
1336 
1337 		foreach (dstLine; dst) {
1338 			blendPixels(dstLine, src.front, blend);
1339 			src.popFront();
1340 		}
1341 	}
1342 
1343 	// opposite offset
1344 	public const {
1345 		/++
1346 			$(I Advanced functionality.)
1347 
1348 			Offset of the bottom right corner of the source image
1349 			to the bottom right corner of the subimage.
1350 
1351 			```
1352 			╔═══════════╗
1353 			║           ║
1354 			║   ┌───┐   ║
1355 			║   │   │   ║
1356 			║   └───┘   ║
1357 			║         ↘ ║
1358 			╚═══════════╝
1359 			```
1360 		 +/
1361 		Point oppositeOffset() {
1362 			return Point(oppositeOffsetX, oppositeOffsetY);
1363 		}
1364 
1365 		/++
1366 			$(I Advanced functionality.)
1367 
1368 			Offset of the right edge of the source image
1369 			to the right edge of the subimage.
1370 
1371 			```
1372 			╔═══════════╗
1373 			║           ║
1374 			║   ┌───┐   ║
1375 			║   │ S │ → ║
1376 			║   └───┘   ║
1377 			║           ║
1378 			╚═══════════╝
1379 			```
1380 		 +/
1381 		int oppositeOffsetX() {
1382 			return (offset.x + size.width);
1383 		}
1384 
1385 		/++
1386 			$(I Advanced functionality.)
1387 
1388 			Offset of the bottom edge of the source image
1389 			to the bottom edge of the subimage.
1390 
1391 			```
1392 			╔═══════════╗
1393 			║           ║
1394 			║   ┌───┐   ║
1395 			║   │ S │   ║
1396 			║   └───┘   ║
1397 			║     ↓     ║
1398 			╚═══════════╝
1399 			```
1400 		 +/
1401 		int oppositeOffsetY() {
1402 			return (offset.y + size.height);
1403 		}
1404 
1405 	}
1406 
1407 	// source-image margins
1408 	public const {
1409 		/++
1410 			$(I Advanced functionality.)
1411 
1412 			X-axis margin (left + right) of the subimage within the source image.
1413 
1414 			```
1415 			╔═══════════╗
1416 			║           ║
1417 			║   ┌───┐   ║
1418 			║ ↔ │ S │ ↔ ║
1419 			║   └───┘   ║
1420 			║           ║
1421 			╚═══════════╝
1422 			```
1423 		 +/
1424 		int sourceMarginX() {
1425 			return (source.width - size.width);
1426 		}
1427 
1428 		/++
1429 			$(I Advanced functionality.)
1430 
1431 			Y-axis margin (top + bottom) of the subimage within the source image.
1432 
1433 			```
1434 			╔═══════════╗
1435 			║     ↕     ║
1436 			║   ┌───┐   ║
1437 			║   │ S │   ║
1438 			║   └───┘   ║
1439 			║     ↕     ║
1440 			╚═══════════╝
1441 			```
1442 		 +/
1443 		int sourceMarginY() {
1444 			return (source.height - size.height);
1445 		}
1446 
1447 		/++
1448 			$(I Advanced functionality.)
1449 
1450 			Top margin of the subimage within the source image.
1451 
1452 			```
1453 			╔═══════════╗
1454 			║     ↕     ║
1455 			║   ┌───┐   ║
1456 			║   │ S │   ║
1457 			║   └───┘   ║
1458 			║           ║
1459 			╚═══════════╝
1460 			```
1461 		 +/
1462 		int sourceMarginTop() {
1463 			return offset.y;
1464 		}
1465 
1466 		/++
1467 			$(I Advanced functionality.)
1468 
1469 			Right margin of the subimage within the source image.
1470 
1471 			```
1472 			╔═══════════╗
1473 			║           ║
1474 			║   ┌───┐   ║
1475 			║   │ S │ ↔ ║
1476 			║   └───┘   ║
1477 			║           ║
1478 			╚═══════════╝
1479 			```
1480 		 +/
1481 		int sourceMarginRight() {
1482 			return (sourceMarginX - sourceMarginLeft);
1483 		}
1484 
1485 		/++
1486 			$(I Advanced functionality.)
1487 
1488 			Bottom margin of the subimage within the source image.
1489 
1490 			```
1491 			╔═══════════╗
1492 			║           ║
1493 			║   ┌───┐   ║
1494 			║   │ S │   ║
1495 			║   └───┘   ║
1496 			║     ↕     ║
1497 			╚═══════════╝
1498 			```
1499 		 +/
1500 		int sourceMarginBottom() {
1501 			return (sourceMarginY - sourceMarginTop);
1502 		}
1503 
1504 		/++
1505 			$(I Advanced functionality.)
1506 
1507 			Left margin of the subimage within the source image.
1508 
1509 			```
1510 			╔═══════════╗
1511 			║           ║
1512 			║   ┌───┐   ║
1513 			║ ↔ │ S │   ║
1514 			║   └───┘   ║
1515 			║           ║
1516 			╚═══════════╝
1517 			```
1518 		 +/
1519 		int sourceMarginLeft() {
1520 			return offset.x;
1521 		}
1522 	}
1523 
1524 	public const {
1525 		/++
1526 			$(I Advanced functionality.)
1527 
1528 			Calculates the linear offset of the provided point in the subimage
1529 			relative to the source image.
1530 		 +/
1531 		int sourceOffsetOf(Point pos) {
1532 			pos = (pos + offset);
1533 			return linearOffset(pos, source.width);
1534 		}
1535 	}
1536 }
1537 
1538 /++
1539 	$(I Advanced functionality.)
1540 
1541 	Wrapper for scanning a [Pixmap] line by line.
1542  +/
1543 struct PixmapScanner {
1544 	private {
1545 		const(Pixel)[] _data;
1546 		int _width;
1547 	}
1548 
1549 @safe pure nothrow @nogc:
1550 
1551 	///
1552 	public this(const(Pixmap) pixmap) {
1553 		_data = pixmap.data;
1554 		_width = pixmap.width;
1555 	}
1556 
1557 	///
1558 	typeof(this) save() {
1559 		return this;
1560 	}
1561 
1562 	///
1563 	bool empty() const {
1564 		return (_data.length == 0);
1565 	}
1566 
1567 	///
1568 	const(Pixel)[] front() const {
1569 		return _data[0 .. _width];
1570 	}
1571 
1572 	///
1573 	void popFront() {
1574 		_data = _data[_width .. $];
1575 	}
1576 
1577 	///
1578 	const(Pixel)[] back() const {
1579 		return _data[($ - _width) .. $];
1580 	}
1581 
1582 	///
1583 	void popBack() {
1584 		_data = _data[0 .. ($ - _width)];
1585 	}
1586 }
1587 
1588 /++
1589 	$(I Advanced functionality.)
1590 
1591 	Wrapper for scanning a [Pixmap] line by line.
1592 
1593 	See_also:
1594 		Unlike [PixmapScanner], this does not work with `const(Pixmap)`.
1595  +/
1596 struct PixmapScannerRW {
1597 	private {
1598 		Pixel[] _data;
1599 		int _width;
1600 	}
1601 
1602 @safe pure nothrow @nogc:
1603 
1604 	///
1605 	public this(Pixmap pixmap) {
1606 		_data = pixmap.data;
1607 		_width = pixmap.width;
1608 	}
1609 
1610 	///
1611 	typeof(this) save() {
1612 		return this;
1613 	}
1614 
1615 	///
1616 	bool empty() const {
1617 		return (_data.length == 0);
1618 	}
1619 
1620 	///
1621 	Pixel[] front() {
1622 		return _data[0 .. _width];
1623 	}
1624 
1625 	///
1626 	void popFront() {
1627 		_data = _data[_width .. $];
1628 	}
1629 
1630 	///
1631 	Pixel[] back() {
1632 		return _data[($ - _width) .. $];
1633 	}
1634 
1635 	///
1636 	void popBack() {
1637 		_data = _data[0 .. ($ - _width)];
1638 	}
1639 }
1640 
1641 /++
1642 	$(I Advanced functionality.)
1643 
1644 	Wrapper for scanning a [Pixmap] line by line.
1645  +/
1646 struct SubPixmapScanner {
1647 	private {
1648 		const(Pixel)[] _data;
1649 		int _width;
1650 		int _feed;
1651 	}
1652 
1653 @safe pure nothrow @nogc:
1654 
1655 	///
1656 	public this(const(SubPixmap) subPixmap) {
1657 		_data = subPixmap.source.data[subPixmap.sourceOffsetLinear .. subPixmap.sourceOffsetLinearEnd];
1658 		_width = subPixmap.size.width;
1659 		_feed = subPixmap.source.width;
1660 	}
1661 
1662 	///
1663 	typeof(this) save() {
1664 		return this;
1665 	}
1666 
1667 	///
1668 	bool empty() const {
1669 		return (_data.length == 0);
1670 	}
1671 
1672 	///
1673 	const(Pixel)[] front() const {
1674 		return _data[0 .. _width];
1675 	}
1676 
1677 	///
1678 	void popFront() {
1679 		if (_data.length < _feed) {
1680 			_data.length = 0;
1681 			return;
1682 		}
1683 
1684 		_data = _data[_feed .. $];
1685 	}
1686 
1687 	///
1688 	const(Pixel)[] back() const {
1689 		return _data[($ - _width) .. $];
1690 	}
1691 
1692 	///
1693 	void popBack() {
1694 		if (_data.length < _feed) {
1695 			_data.length = 0;
1696 			return;
1697 		}
1698 
1699 		_data = _data[0 .. ($ - _feed)];
1700 	}
1701 }
1702 
1703 /++
1704 	$(I Advanced functionality.)
1705 
1706 	Wrapper for scanning a [Pixmap] line by line.
1707 
1708 	See_also:
1709 		Unlike [SubPixmapScanner], this does not work with `const(SubPixmap)`.
1710  +/
1711 struct SubPixmapScannerRW {
1712 	private {
1713 		Pixel[] _data;
1714 		int _width;
1715 		int _feed;
1716 	}
1717 
1718 @safe pure nothrow @nogc:
1719 
1720 	///
1721 	public this(SubPixmap subPixmap) {
1722 		_data = subPixmap.source.data[subPixmap.sourceOffsetLinear .. subPixmap.sourceOffsetLinearEnd];
1723 		_width = subPixmap.size.width;
1724 		_feed = subPixmap.source.width;
1725 	}
1726 
1727 	///
1728 	typeof(this) save() {
1729 		return this;
1730 	}
1731 
1732 	///
1733 	bool empty() const {
1734 		return (_data.length == 0);
1735 	}
1736 
1737 	///
1738 	Pixel[] front() {
1739 		return _data[0 .. _width];
1740 	}
1741 
1742 	///
1743 	void popFront() {
1744 		if (_data.length < _feed) {
1745 			_data.length = 0;
1746 			return;
1747 		}
1748 
1749 		_data = _data[_feed .. $];
1750 	}
1751 
1752 	///
1753 	Pixel[] back() {
1754 		return _data[($ - _width) .. $];
1755 	}
1756 
1757 	///
1758 	void popBack() {
1759 		if (_data.length < _feed) {
1760 			_data.length = 0;
1761 			return;
1762 		}
1763 
1764 		_data = _data[0 .. ($ - _feed)];
1765 	}
1766 }
1767 
1768 ///
1769 struct SpriteSheet {
1770 	private {
1771 		Pixmap _pixmap;
1772 		Size _spriteDimensions;
1773 		Size _layout; // pre-computed upon construction
1774 	}
1775 
1776 @safe pure nothrow @nogc:
1777 
1778 	///
1779 	public this(Pixmap pixmap, Size spriteSize) {
1780 		_pixmap = pixmap;
1781 		_spriteDimensions = spriteSize;
1782 
1783 		_layout = Size(
1784 			_pixmap.width / _spriteDimensions.width,
1785 			_pixmap.height / _spriteDimensions.height,
1786 		);
1787 	}
1788 
1789 	///
1790 	inout(Pixmap) pixmap() inout {
1791 		return _pixmap;
1792 	}
1793 
1794 	///
1795 	Size spriteSize() inout {
1796 		return _spriteDimensions;
1797 	}
1798 
1799 	///
1800 	Size layout() inout {
1801 		return _layout;
1802 	}
1803 
1804 	///
1805 	Point getSpriteColumn(int index) inout {
1806 		immutable x = index % layout.width;
1807 		immutable y = (index - x) / layout.height;
1808 		return Point(x, y);
1809 	}
1810 
1811 	///
1812 	Point getSpritePixelOffset2D(int index) inout {
1813 		immutable col = this.getSpriteColumn(index);
1814 		return Point(
1815 			col.x * _spriteDimensions.width,
1816 			col.y * _spriteDimensions.height,
1817 		);
1818 	}
1819 }
1820 
1821 // Silly micro-optimization
1822 private struct OriginRectangle {
1823 	Size size;
1824 
1825 @safe pure nothrow @nogc:
1826 
1827 	int left() const => 0;
1828 	int top() const => 0;
1829 	int right() const => size.width;
1830 	int bottom() const => size.height;
1831 
1832 	bool intersect(const Rectangle b) const {
1833 		// dfmt off
1834 		return (
1835 			(b.right    > 0          ) &&
1836 			(b.left     < this.right ) &&
1837 			(b.bottom   > 0          ) &&
1838 			(b.top      < this.bottom)
1839 		);
1840 		// dfmt on
1841 	}
1842 }
1843 
1844 @safe pure nothrow:
1845 
1846 // misc
1847 private @nogc {
1848 	Point pos(Rectangle r) => r.upperLeft;
1849 
1850 	T max(T)(T a, T b) => (a >= b) ? a : b;
1851 	T min(T)(T a, T b) => (a <= b) ? a : b;
1852 }
1853 
1854 /++
1855 	Calculates the square root
1856 	of an integer number
1857 	as an integer number.
1858  +/
1859 ubyte intSqrt(const ubyte value) @safe pure nothrow @nogc {
1860 	switch (value) {
1861 	default:
1862 		// unreachable
1863 		assert(false, "ubyte != uint8");
1864 	case 0:
1865 		return 0;
1866 	case 1: .. case 2:
1867 		return 1;
1868 	case 3: .. case 6:
1869 		return 2;
1870 	case 7: .. case 12:
1871 		return 3;
1872 	case 13: .. case 20:
1873 		return 4;
1874 	case 21: .. case 30:
1875 		return 5;
1876 	case 31: .. case 42:
1877 		return 6;
1878 	case 43: .. case 56:
1879 		return 7;
1880 	case 57: .. case 72:
1881 		return 8;
1882 	case 73: .. case 90:
1883 		return 9;
1884 	case 91: .. case 110:
1885 		return 10;
1886 	case 111: .. case 132:
1887 		return 11;
1888 	case 133: .. case 156:
1889 		return 12;
1890 	case 157: .. case 182:
1891 		return 13;
1892 	case 183: .. case 210:
1893 		return 14;
1894 	case 211: .. case 240:
1895 		return 15;
1896 	case 241: .. case 255:
1897 		return 16;
1898 	}
1899 }
1900 
1901 ///
1902 unittest {
1903 	assert(intSqrt(4) == 2);
1904 	assert(intSqrt(9) == 3);
1905 	assert(intSqrt(10) == 3);
1906 }
1907 
1908 unittest {
1909 	import std.math : round, sqrt;
1910 
1911 	foreach (n; ubyte.min .. ubyte.max + 1) {
1912 		ubyte fp = sqrt(float(n)).round().castTo!ubyte;
1913 		ubyte i8 = intSqrt(n.castTo!ubyte);
1914 		assert(fp == i8);
1915 	}
1916 }
1917 
1918 /++
1919 	Calculates the square root
1920 	of the normalized value
1921 	representated by the input integer number.
1922 
1923 	Normalization:
1924 		`[0x00 .. 0xFF]` → `[0.0 .. 1.0]`
1925 
1926 	Returns:
1927 		sqrt(value / 255f) * 255
1928  +/
1929 ubyte intNormalizedSqrt(const ubyte value) @nogc {
1930 	switch (value) {
1931 	default:
1932 		// unreachable
1933 		assert(false, "ubyte != uint8");
1934 	case 0x00:
1935 		return 0x00;
1936 	case 0x01:
1937 		return 0x10;
1938 	case 0x02:
1939 		return 0x17;
1940 	case 0x03:
1941 		return 0x1C;
1942 	case 0x04:
1943 		return 0x20;
1944 	case 0x05:
1945 		return 0x24;
1946 	case 0x06:
1947 		return 0x27;
1948 	case 0x07:
1949 		return 0x2A;
1950 	case 0x08:
1951 		return 0x2D;
1952 	case 0x09:
1953 		return 0x30;
1954 	case 0x0A:
1955 		return 0x32;
1956 	case 0x0B:
1957 		return 0x35;
1958 	case 0x0C:
1959 		return 0x37;
1960 	case 0x0D:
1961 		return 0x3A;
1962 	case 0x0E:
1963 		return 0x3C;
1964 	case 0x0F:
1965 		return 0x3E;
1966 	case 0x10:
1967 		return 0x40;
1968 	case 0x11:
1969 		return 0x42;
1970 	case 0x12:
1971 		return 0x44;
1972 	case 0x13:
1973 		return 0x46;
1974 	case 0x14:
1975 		return 0x47;
1976 	case 0x15:
1977 		return 0x49;
1978 	case 0x16:
1979 		return 0x4B;
1980 	case 0x17:
1981 		return 0x4D;
1982 	case 0x18:
1983 		return 0x4E;
1984 	case 0x19:
1985 		return 0x50;
1986 	case 0x1A:
1987 		return 0x51;
1988 	case 0x1B:
1989 		return 0x53;
1990 	case 0x1C:
1991 		return 0x54;
1992 	case 0x1D:
1993 		return 0x56;
1994 	case 0x1E:
1995 		return 0x57;
1996 	case 0x1F:
1997 		return 0x59;
1998 	case 0x20:
1999 		return 0x5A;
2000 	case 0x21:
2001 		return 0x5C;
2002 	case 0x22:
2003 		return 0x5D;
2004 	case 0x23:
2005 		return 0x5E;
2006 	case 0x24:
2007 		return 0x60;
2008 	case 0x25:
2009 		return 0x61;
2010 	case 0x26:
2011 		return 0x62;
2012 	case 0x27:
2013 		return 0x64;
2014 	case 0x28:
2015 		return 0x65;
2016 	case 0x29:
2017 		return 0x66;
2018 	case 0x2A:
2019 		return 0x67;
2020 	case 0x2B:
2021 		return 0x69;
2022 	case 0x2C:
2023 		return 0x6A;
2024 	case 0x2D:
2025 		return 0x6B;
2026 	case 0x2E:
2027 		return 0x6C;
2028 	case 0x2F:
2029 		return 0x6D;
2030 	case 0x30:
2031 		return 0x6F;
2032 	case 0x31:
2033 		return 0x70;
2034 	case 0x32:
2035 		return 0x71;
2036 	case 0x33:
2037 		return 0x72;
2038 	case 0x34:
2039 		return 0x73;
2040 	case 0x35:
2041 		return 0x74;
2042 	case 0x36:
2043 		return 0x75;
2044 	case 0x37:
2045 		return 0x76;
2046 	case 0x38:
2047 		return 0x77;
2048 	case 0x39:
2049 		return 0x79;
2050 	case 0x3A:
2051 		return 0x7A;
2052 	case 0x3B:
2053 		return 0x7B;
2054 	case 0x3C:
2055 		return 0x7C;
2056 	case 0x3D:
2057 		return 0x7D;
2058 	case 0x3E:
2059 		return 0x7E;
2060 	case 0x3F:
2061 		return 0x7F;
2062 	case 0x40:
2063 		return 0x80;
2064 	case 0x41:
2065 		return 0x81;
2066 	case 0x42:
2067 		return 0x82;
2068 	case 0x43:
2069 		return 0x83;
2070 	case 0x44:
2071 		return 0x84;
2072 	case 0x45:
2073 		return 0x85;
2074 	case 0x46:
2075 		return 0x86;
2076 	case 0x47: .. case 0x48:
2077 		return 0x87;
2078 	case 0x49:
2079 		return 0x88;
2080 	case 0x4A:
2081 		return 0x89;
2082 	case 0x4B:
2083 		return 0x8A;
2084 	case 0x4C:
2085 		return 0x8B;
2086 	case 0x4D:
2087 		return 0x8C;
2088 	case 0x4E:
2089 		return 0x8D;
2090 	case 0x4F:
2091 		return 0x8E;
2092 	case 0x50:
2093 		return 0x8F;
2094 	case 0x51:
2095 		return 0x90;
2096 	case 0x52: .. case 0x53:
2097 		return 0x91;
2098 	case 0x54:
2099 		return 0x92;
2100 	case 0x55:
2101 		return 0x93;
2102 	case 0x56:
2103 		return 0x94;
2104 	case 0x57:
2105 		return 0x95;
2106 	case 0x58:
2107 		return 0x96;
2108 	case 0x59: .. case 0x5A:
2109 		return 0x97;
2110 	case 0x5B:
2111 		return 0x98;
2112 	case 0x5C:
2113 		return 0x99;
2114 	case 0x5D:
2115 		return 0x9A;
2116 	case 0x5E:
2117 		return 0x9B;
2118 	case 0x5F: .. case 0x60:
2119 		return 0x9C;
2120 	case 0x61:
2121 		return 0x9D;
2122 	case 0x62:
2123 		return 0x9E;
2124 	case 0x63:
2125 		return 0x9F;
2126 	case 0x64: .. case 0x65:
2127 		return 0xA0;
2128 	case 0x66:
2129 		return 0xA1;
2130 	case 0x67:
2131 		return 0xA2;
2132 	case 0x68:
2133 		return 0xA3;
2134 	case 0x69: .. case 0x6A:
2135 		return 0xA4;
2136 	case 0x6B:
2137 		return 0xA5;
2138 	case 0x6C:
2139 		return 0xA6;
2140 	case 0x6D: .. case 0x6E:
2141 		return 0xA7;
2142 	case 0x6F:
2143 		return 0xA8;
2144 	case 0x70:
2145 		return 0xA9;
2146 	case 0x71: .. case 0x72:
2147 		return 0xAA;
2148 	case 0x73:
2149 		return 0xAB;
2150 	case 0x74:
2151 		return 0xAC;
2152 	case 0x75: .. case 0x76:
2153 		return 0xAD;
2154 	case 0x77:
2155 		return 0xAE;
2156 	case 0x78:
2157 		return 0xAF;
2158 	case 0x79: .. case 0x7A:
2159 		return 0xB0;
2160 	case 0x7B:
2161 		return 0xB1;
2162 	case 0x7C:
2163 		return 0xB2;
2164 	case 0x7D: .. case 0x7E:
2165 		return 0xB3;
2166 	case 0x7F:
2167 		return 0xB4;
2168 	case 0x80: .. case 0x81:
2169 		return 0xB5;
2170 	case 0x82:
2171 		return 0xB6;
2172 	case 0x83: .. case 0x84:
2173 		return 0xB7;
2174 	case 0x85:
2175 		return 0xB8;
2176 	case 0x86:
2177 		return 0xB9;
2178 	case 0x87: .. case 0x88:
2179 		return 0xBA;
2180 	case 0x89:
2181 		return 0xBB;
2182 	case 0x8A: .. case 0x8B:
2183 		return 0xBC;
2184 	case 0x8C:
2185 		return 0xBD;
2186 	case 0x8D: .. case 0x8E:
2187 		return 0xBE;
2188 	case 0x8F:
2189 		return 0xBF;
2190 	case 0x90: .. case 0x91:
2191 		return 0xC0;
2192 	case 0x92:
2193 		return 0xC1;
2194 	case 0x93: .. case 0x94:
2195 		return 0xC2;
2196 	case 0x95:
2197 		return 0xC3;
2198 	case 0x96: .. case 0x97:
2199 		return 0xC4;
2200 	case 0x98:
2201 		return 0xC5;
2202 	case 0x99: .. case 0x9A:
2203 		return 0xC6;
2204 	case 0x9B: .. case 0x9C:
2205 		return 0xC7;
2206 	case 0x9D:
2207 		return 0xC8;
2208 	case 0x9E: .. case 0x9F:
2209 		return 0xC9;
2210 	case 0xA0:
2211 		return 0xCA;
2212 	case 0xA1: .. case 0xA2:
2213 		return 0xCB;
2214 	case 0xA3: .. case 0xA4:
2215 		return 0xCC;
2216 	case 0xA5:
2217 		return 0xCD;
2218 	case 0xA6: .. case 0xA7:
2219 		return 0xCE;
2220 	case 0xA8:
2221 		return 0xCF;
2222 	case 0xA9: .. case 0xAA:
2223 		return 0xD0;
2224 	case 0xAB: .. case 0xAC:
2225 		return 0xD1;
2226 	case 0xAD:
2227 		return 0xD2;
2228 	case 0xAE: .. case 0xAF:
2229 		return 0xD3;
2230 	case 0xB0: .. case 0xB1:
2231 		return 0xD4;
2232 	case 0xB2:
2233 		return 0xD5;
2234 	case 0xB3: .. case 0xB4:
2235 		return 0xD6;
2236 	case 0xB5: .. case 0xB6:
2237 		return 0xD7;
2238 	case 0xB7:
2239 		return 0xD8;
2240 	case 0xB8: .. case 0xB9:
2241 		return 0xD9;
2242 	case 0xBA: .. case 0xBB:
2243 		return 0xDA;
2244 	case 0xBC:
2245 		return 0xDB;
2246 	case 0xBD: .. case 0xBE:
2247 		return 0xDC;
2248 	case 0xBF: .. case 0xC0:
2249 		return 0xDD;
2250 	case 0xC1: .. case 0xC2:
2251 		return 0xDE;
2252 	case 0xC3:
2253 		return 0xDF;
2254 	case 0xC4: .. case 0xC5:
2255 		return 0xE0;
2256 	case 0xC6: .. case 0xC7:
2257 		return 0xE1;
2258 	case 0xC8: .. case 0xC9:
2259 		return 0xE2;
2260 	case 0xCA:
2261 		return 0xE3;
2262 	case 0xCB: .. case 0xCC:
2263 		return 0xE4;
2264 	case 0xCD: .. case 0xCE:
2265 		return 0xE5;
2266 	case 0xCF: .. case 0xD0:
2267 		return 0xE6;
2268 	case 0xD1: .. case 0xD2:
2269 		return 0xE7;
2270 	case 0xD3:
2271 		return 0xE8;
2272 	case 0xD4: .. case 0xD5:
2273 		return 0xE9;
2274 	case 0xD6: .. case 0xD7:
2275 		return 0xEA;
2276 	case 0xD8: .. case 0xD9:
2277 		return 0xEB;
2278 	case 0xDA: .. case 0xDB:
2279 		return 0xEC;
2280 	case 0xDC: .. case 0xDD:
2281 		return 0xED;
2282 	case 0xDE: .. case 0xDF:
2283 		return 0xEE;
2284 	case 0xE0:
2285 		return 0xEF;
2286 	case 0xE1: .. case 0xE2:
2287 		return 0xF0;
2288 	case 0xE3: .. case 0xE4:
2289 		return 0xF1;
2290 	case 0xE5: .. case 0xE6:
2291 		return 0xF2;
2292 	case 0xE7: .. case 0xE8:
2293 		return 0xF3;
2294 	case 0xE9: .. case 0xEA:
2295 		return 0xF4;
2296 	case 0xEB: .. case 0xEC:
2297 		return 0xF5;
2298 	case 0xED: .. case 0xEE:
2299 		return 0xF6;
2300 	case 0xEF: .. case 0xF0:
2301 		return 0xF7;
2302 	case 0xF1: .. case 0xF2:
2303 		return 0xF8;
2304 	case 0xF3: .. case 0xF4:
2305 		return 0xF9;
2306 	case 0xF5: .. case 0xF6:
2307 		return 0xFA;
2308 	case 0xF7: .. case 0xF8:
2309 		return 0xFB;
2310 	case 0xF9: .. case 0xFA:
2311 		return 0xFC;
2312 	case 0xFB: .. case 0xFC:
2313 		return 0xFD;
2314 	case 0xFD: .. case 0xFE:
2315 		return 0xFE;
2316 	case 0xFF:
2317 		return 0xFF;
2318 	}
2319 }
2320 
2321 unittest {
2322 	import std.math : round, sqrt;
2323 
2324 	foreach (n; ubyte.min .. ubyte.max + 1) {
2325 		ubyte fp = (sqrt(n / 255.0f) * 255).round().castTo!ubyte;
2326 		ubyte i8 = intNormalizedSqrt(n.castTo!ubyte);
2327 		assert(fp == i8);
2328 	}
2329 }
2330 
2331 /++
2332 	Limits a value to a maximum of 0xFF (= 255).
2333  +/
2334 ubyte clamp255(Tint)(const Tint value) @nogc {
2335 	pragma(inline, true);
2336 	return (value < 0xFF) ? value.castTo!ubyte : 0xFF;
2337 }
2338 
2339 /++
2340 	Fast 8-bit “percentage” function
2341 
2342 	This function optimizes its runtime performance by substituting
2343 	the division by 255 with an approximation using bitshifts.
2344 
2345 	Nonetheless, its result are as accurate as a floating point
2346 	division with 64-bit precision.
2347 
2348 	Params:
2349 		nPercentage = percentage as the number of 255ths (“two hundred fifty-fifths”)
2350 		value = base value (“total”)
2351 
2352 	Returns:
2353 		`round(value * nPercentage / 255.0)`
2354  +/
2355 ubyte n255thsOf(const ubyte nPercentage, const ubyte value) @nogc {
2356 	immutable factor = (nPercentage | (nPercentage << 8));
2357 	return (((value * factor) + 0x8080) >> 16);
2358 }
2359 
2360 @safe unittest {
2361 	// Accuracy verification
2362 
2363 	static ubyte n255thsOfFP64(const ubyte nPercentage, const ubyte value) {
2364 		return (double(value) * double(nPercentage) / 255.0).round().castTo!ubyte();
2365 	}
2366 
2367 	for (int value = ubyte.min; value <= ubyte.max; ++value) {
2368 		for (int percent = ubyte.min; percent <= ubyte.max; ++percent) {
2369 			immutable v = cast(ubyte) value;
2370 			immutable p = cast(ubyte) percent;
2371 
2372 			immutable approximated = n255thsOf(p, v);
2373 			immutable precise = n255thsOfFP64(p, v);
2374 			assert(approximated == precise);
2375 		}
2376 	}
2377 }
2378 
2379 ///
2380 ubyte percentageDecimalToUInt8(const float decimal) @nogc
2381 in (decimal >= 0)
2382 in (decimal <= 1) {
2383 	return round(decimal * 255).castTo!ubyte;
2384 }
2385 
2386 ///
2387 float percentageUInt8ToDecimal(const ubyte n255ths) @nogc {
2388 	return (float(n255ths) / 255.0f);
2389 }
2390 
2391 // ==== Image manipulation functions ====
2392 
2393 /++
2394 	Lowers the opacity of a Pixel.
2395 
2396 	This function multiplies the opacity of the input
2397 	with the given percentage.
2398 
2399 	See_Also:
2400 		Use [decreaseOpacityF] with decimal opacity values in percent (%).
2401  +/
2402 Pixel decreaseOpacity(const Pixel source, ubyte opacityPercentage) @nogc {
2403 	return Pixel(
2404 		source.r,
2405 		source.g,
2406 		source.b,
2407 		opacityPercentage.n255thsOf(source.a),
2408 	);
2409 }
2410 
2411 /++
2412 	Lowers the opacity of a Pixel.
2413 
2414 	This function multiplies the opacity of the input
2415 	with the given percentage.
2416 
2417 	Value Range:
2418 		0.0 =   0%
2419 		1.0 = 100%
2420 
2421 	See_Also:
2422 		Use [opacity] with 8-bit integer opacity values (in 255ths).
2423  +/
2424 Pixel decreaseOpacityF(const Pixel source, float opacityPercentage) @nogc {
2425 	return decreaseOpacity(source, percentageDecimalToUInt8(opacityPercentage));
2426 }
2427 
2428 // Don’t get fooled by the name of this function.
2429 // It’s called like that for consistency reasons.
2430 private void decreaseOpacityInto(const Pixmap source, Pixmap target, ubyte opacityPercentage) @trusted @nogc {
2431 	debug assert(source.data.length == target.data.length);
2432 	foreach (idx, ref px; target.data) {
2433 		px = decreaseOpacity(source.data.ptr[idx], opacityPercentage);
2434 	}
2435 }
2436 
2437 /++
2438 	Lowers the opacity of a [Pixmap].
2439 
2440 	This operation updates the alpha-channel value of each pixel.
2441 	→ `alpha *= opacity`
2442 
2443 	See_Also:
2444 		Use [decreaseOpacityF] with decimal opacity values in percent (%).
2445  +/
2446 Pixmap decreaseOpacity(const Pixmap source, Pixmap target, ubyte opacityPercentage) @nogc {
2447 	target.adjustTo(source.decreaseOpacityCalcDims());
2448 	source.decreaseOpacityInto(target, opacityPercentage);
2449 	return target;
2450 }
2451 
2452 /// ditto
2453 Pixmap decreaseOpacityNew(const Pixmap source, ubyte opacityPercentage) {
2454 	auto target = Pixmap.makeNew(source.decreaseOpacityCalcDims());
2455 	source.decreaseOpacityInto(target, opacityPercentage);
2456 	return target;
2457 }
2458 
2459 /// ditto
2460 void decreaseOpacityInPlace(Pixmap source, ubyte opacityPercentage) @nogc {
2461 	foreach (ref px; source.data) {
2462 		px.a = opacityPercentage.n255thsOf(px.a);
2463 	}
2464 }
2465 
2466 /// ditto
2467 PixmapBlueprint decreaseOpacityCalcDims(const Pixmap source) @nogc {
2468 	return PixmapBlueprint.fromPixmap(source);
2469 }
2470 
2471 /++
2472 	Adjusts the opacity of a [Pixmap].
2473 
2474 	This operation updates the alpha-channel value of each pixel.
2475 	→ `alpha *= opacity`
2476 
2477 	See_Also:
2478 		Use [decreaseOpacity] with 8-bit integer opacity values (in 255ths).
2479  +/
2480 Pixmap decreaseOpacityF(const Pixmap source, Pixmap target, float opacityPercentage) @nogc {
2481 	return source.decreaseOpacity(target, percentageDecimalToUInt8(opacityPercentage));
2482 }
2483 
2484 /// ditto
2485 Pixmap decreaseOpacityFNew(const Pixmap source, float opacityPercentage) {
2486 	return source.decreaseOpacityNew(percentageDecimalToUInt8(opacityPercentage));
2487 }
2488 
2489 /// ditto
2490 void decreaseOpacityFInPlace(Pixmap source, float opacityPercentage) @nogc {
2491 	return source.decreaseOpacityInPlace(percentageDecimalToUInt8(opacityPercentage));
2492 }
2493 
2494 /// ditto
2495 PixmapBlueprint decreaseOpacityF(Pixmap source) @nogc {
2496 	return PixmapBlueprint.fromPixmap(source);
2497 }
2498 
2499 /++
2500 	Inverts a color (to its negative color).
2501  +/
2502 Pixel invert(const Pixel color) @nogc {
2503 	return Pixel(
2504 		0xFF - color.r,
2505 		0xFF - color.g,
2506 		0xFF - color.b,
2507 		color.a,
2508 	);
2509 }
2510 
2511 private void invertInto(const Pixmap source, Pixmap target) @trusted @nogc {
2512 	debug assert(source.length == target.length);
2513 	foreach (idx, ref px; target.data) {
2514 		px = invert(source.data.ptr[idx]);
2515 	}
2516 }
2517 
2518 /++
2519 	Inverts all colors to produce a $(I negative image).
2520 
2521 	$(TIP
2522 		Develops a positive image when applied to a negative one.
2523 	)
2524  +/
2525 Pixmap invert(const Pixmap source, Pixmap target) @nogc {
2526 	target.adjustTo(source.invertCalcDims());
2527 	source.invertInto(target);
2528 	return target;
2529 }
2530 
2531 /// ditto
2532 Pixmap invertNew(const Pixmap source) {
2533 	auto target = Pixmap.makeNew(source.invertCalcDims());
2534 	source.invertInto(target);
2535 	return target;
2536 }
2537 
2538 /// ditto
2539 void invertInPlace(Pixmap pixmap) @nogc {
2540 	foreach (ref px; pixmap.data) {
2541 		px = invert(px);
2542 	}
2543 }
2544 
2545 /// ditto
2546 PixmapBlueprint invertCalcDims(const Pixmap source) @nogc {
2547 	return PixmapBlueprint.fromPixmap(source);
2548 }
2549 
2550 /++
2551 	Crops an image and stores the result in the provided target Pixmap.
2552 
2553 	The size of the area to crop the image to
2554 	is derived from the size of the target.
2555 
2556 	---
2557 	// This function can be used to omit a redundant size parameter
2558 	// in cases like this:
2559 	target = crop(source, target, target.size, offset);
2560 
2561 	// → Instead do:
2562 	cropTo(source, target, offset);
2563 	---
2564  +/
2565 void cropTo(const Pixmap source, Pixmap target, Point offset = Point(0, 0)) @nogc {
2566 	auto src = const(SubPixmap)(source, target.size, offset);
2567 	src.extractToPixmapCopyImpl(target);
2568 }
2569 
2570 // consistency
2571 private alias cropInto = cropTo;
2572 
2573 /++
2574 	Crops an image to the provided size with the requested offset.
2575 
2576 	The target Pixmap must be big enough in length to hold the cropped image.
2577  +/
2578 Pixmap crop(const Pixmap source, Pixmap target, Size cropToSize, Point offset = Point(0, 0)) @nogc {
2579 	target.adjustTo(cropCalcDims(cropToSize));
2580 	cropInto(source, target, offset);
2581 	return target;
2582 }
2583 
2584 /// ditto
2585 Pixmap cropNew(const Pixmap source, Size cropToSize, Point offset = Point(0, 0)) {
2586 	auto target = Pixmap.makeNew(cropToSize);
2587 	cropInto(source, target, offset);
2588 	return target;
2589 }
2590 
2591 /// ditto
2592 Pixmap cropInPlace(Pixmap source, Size cropToSize, Point offset = Point(0, 0)) @nogc {
2593 	Pixmap target = source;
2594 	target.width = cropToSize.width;
2595 	target.data = target.data[0 .. cropToSize.area];
2596 
2597 	auto src = const(SubPixmap)(source, cropToSize, offset);
2598 	src.extractToPixmapCopyPixelByPixelImpl(target);
2599 	return target;
2600 }
2601 
2602 /// ditto
2603 PixmapBlueprint cropCalcDims(Size cropToSize) @nogc {
2604 	return PixmapBlueprint.fromSize(cropToSize);
2605 }
2606 
2607 private void transposeInto(const Pixmap source, Pixmap target) @nogc {
2608 	foreach (y; 0 .. target.width) {
2609 		foreach (x; 0 .. source.width) {
2610 			const idxSrc = linearOffset(Point(x, y), source.width);
2611 			const idxDst = linearOffset(Point(y, x), target.width);
2612 
2613 			target.data[idxDst] = source.data[idxSrc];
2614 		}
2615 	}
2616 }
2617 
2618 /++
2619 	Transposes an image.
2620 
2621 	```
2622 	╔══╗   ╔══╗
2623 	║# ║   ║#+║
2624 	║+x║ → ║ x║
2625 	╚══╝   ╚══╝
2626 	```
2627  +/
2628 Pixmap transpose(const Pixmap source, Pixmap target) @nogc {
2629 	target.adjustTo(source.transposeCalcDims());
2630 	source.transposeInto(target);
2631 	return target;
2632 }
2633 
2634 /// ditto
2635 Pixmap transposeNew(const Pixmap source) {
2636 	auto target = Pixmap.makeNew(source.transposeCalcDims());
2637 	source.transposeInto(target);
2638 	return target;
2639 }
2640 
2641 /// ditto
2642 PixmapBlueprint transposeCalcDims(const Pixmap source) @nogc {
2643 	return PixmapBlueprint(source.length, source.height);
2644 }
2645 
2646 private void rotateClockwiseInto(const Pixmap source, Pixmap target) @nogc {
2647 	const area = source.data.length;
2648 	const rowLength = source.size.height;
2649 	ptrdiff_t cursor = -1;
2650 
2651 	foreach (px; source.data) {
2652 		cursor += rowLength;
2653 		if (cursor > area) {
2654 			cursor -= (area + 1);
2655 		}
2656 
2657 		target.data[cursor] = px;
2658 	}
2659 }
2660 
2661 /++
2662 	Rotates an image by 90° clockwise.
2663 
2664 	```
2665 	╔══╗   ╔══╗
2666 	║# ║   ║+#║
2667 	║+x║ → ║x ║
2668 	╚══╝   ╚══╝
2669 	```
2670  +/
2671 Pixmap rotateClockwise(const Pixmap source, Pixmap target) @nogc {
2672 	target.adjustTo(source.rotateClockwiseCalcDims());
2673 	source.rotateClockwiseInto(target);
2674 	return target;
2675 }
2676 
2677 /// ditto
2678 Pixmap rotateClockwiseNew(const Pixmap source) {
2679 	auto target = Pixmap.makeNew(source.rotateClockwiseCalcDims());
2680 	source.rotateClockwiseInto(target);
2681 	return target;
2682 }
2683 
2684 /// ditto
2685 PixmapBlueprint rotateClockwiseCalcDims(const Pixmap source) @nogc {
2686 	return PixmapBlueprint(source.length, source.height);
2687 }
2688 
2689 private void rotateCounterClockwiseInto(const Pixmap source, Pixmap target) @nogc {
2690 	// TODO: can this be optimized?
2691 	target = transpose(source, target);
2692 	target.flipVerticallyInPlace();
2693 }
2694 
2695 /++
2696 	Rotates an image by 90° counter-clockwise.
2697 
2698 	```
2699 	╔══╗   ╔══╗
2700 	║# ║   ║ x║
2701 	║+x║ → ║#+║
2702 	╚══╝   ╚══╝
2703 	```
2704  +/
2705 Pixmap rotateCounterClockwise(const Pixmap source, Pixmap target) @nogc {
2706 	target.adjustTo(source.rotateCounterClockwiseCalcDims());
2707 	source.rotateCounterClockwiseInto(target);
2708 	return target;
2709 }
2710 
2711 /// ditto
2712 Pixmap rotateCounterClockwiseNew(const Pixmap source) {
2713 	auto target = Pixmap.makeNew(source.rotateCounterClockwiseCalcDims());
2714 	source.rotateCounterClockwiseInto(target);
2715 	return target;
2716 }
2717 
2718 /// ditto
2719 PixmapBlueprint rotateCounterClockwiseCalcDims(const Pixmap source) @nogc {
2720 	return PixmapBlueprint(source.length, source.height);
2721 }
2722 
2723 private void rotate180degInto(const Pixmap source, Pixmap target) @nogc {
2724 	// Technically, this is implemented as flip vertical + flip horizontal.
2725 	auto src = PixmapScanner(source);
2726 	auto dst = PixmapScannerRW(target);
2727 
2728 	foreach (srcLine; src) {
2729 		auto dstLine = dst.back;
2730 		foreach (idxSrc, px; srcLine) {
2731 			const idxDst = (dstLine.length - (idxSrc + 1));
2732 			dstLine[idxDst] = px;
2733 		}
2734 		dst.popBack();
2735 	}
2736 }
2737 
2738 /++
2739 	Rotates an image by 180°.
2740 
2741 	```
2742 	╔═══╗   ╔═══╗
2743 	║#- ║   ║%~~║
2744 	║~~%║ → ║ -#║
2745 	╚═══╝   ╚═══╝
2746 	```
2747  +/
2748 Pixmap rotate180deg(const Pixmap source, Pixmap target) @nogc {
2749 	target.adjustTo(source.rotate180degCalcDims());
2750 	source.rotate180degInto(target);
2751 	return target;
2752 }
2753 
2754 /// ditto
2755 Pixmap rotate180degNew(const Pixmap source) {
2756 	auto target = Pixmap.makeNew(source.size);
2757 	source.rotate180degInto(target);
2758 	return target;
2759 }
2760 
2761 /// ditto
2762 void rotate180degInPlace(Pixmap source) @nogc {
2763 	auto scanner = PixmapScannerRW(source);
2764 
2765 	// Technically, this is implemented as a flip vertical + flip horizontal
2766 	// combo, i.e. the image is flipped vertically line by line, but the lines
2767 	// are overwritten in a horizontally flipped way.
2768 	while (!scanner.empty) {
2769 		auto a = scanner.front;
2770 		auto b = scanner.back;
2771 
2772 		// middle line? (odd number of lines)
2773 		if (a.ptr is b.ptr) {
2774 			break;
2775 		}
2776 
2777 		foreach (idxSrc, ref pxA; a) {
2778 			const idxDst = (b.length - (idxSrc + 1));
2779 			const tmp = pxA;
2780 			pxA = b[idxDst];
2781 			b[idxDst] = tmp;
2782 		}
2783 
2784 		scanner.popFront();
2785 		scanner.popBack();
2786 	}
2787 }
2788 
2789 ///
2790 PixmapBlueprint rotate180degCalcDims(const Pixmap source) @nogc {
2791 	return PixmapBlueprint.fromPixmap(source);
2792 }
2793 
2794 private void flipHorizontallyInto(const Pixmap source, Pixmap target) @nogc {
2795 	auto src = PixmapScanner(source);
2796 	auto dst = PixmapScannerRW(target);
2797 
2798 	foreach (srcLine; src) {
2799 		auto dstLine = dst.front;
2800 		foreach (idxSrc, px; srcLine) {
2801 			const idxDst = (dstLine.length - (idxSrc + 1));
2802 			dstLine[idxDst] = px;
2803 		}
2804 
2805 		dst.popFront();
2806 	}
2807 }
2808 
2809 /++
2810 	Flips an image horizontally.
2811 
2812 	```
2813 	╔═══╗   ╔═══╗
2814 	║#-.║ → ║.-#║
2815 	╚═══╝   ╚═══╝
2816 	```
2817  +/
2818 Pixmap flipHorizontally(const Pixmap source, Pixmap target) @nogc {
2819 	target.adjustTo(source.flipHorizontallyCalcDims());
2820 	source.flipHorizontallyInto(target);
2821 	return target;
2822 }
2823 
2824 /// ditto
2825 Pixmap flipHorizontallyNew(const Pixmap source) {
2826 	auto target = Pixmap.makeNew(source.size);
2827 	source.flipHorizontallyInto(target);
2828 	return target;
2829 }
2830 
2831 /// ditto
2832 void flipHorizontallyInPlace(Pixmap source) @nogc {
2833 	auto scanner = PixmapScannerRW(source);
2834 
2835 	foreach (line; scanner) {
2836 		const idxMiddle = (1 + (line.length >> 1));
2837 		auto halfA = line[0 .. idxMiddle];
2838 
2839 		foreach (idxA, ref px; halfA) {
2840 			const idxB = (line.length - (idxA + 1));
2841 			const tmp = line[idxB];
2842 			// swap
2843 			line[idxB] = px;
2844 			px = tmp;
2845 		}
2846 	}
2847 }
2848 
2849 /// ditto
2850 PixmapBlueprint flipHorizontallyCalcDims(const Pixmap source) @nogc {
2851 	return PixmapBlueprint.fromPixmap(source);
2852 }
2853 
2854 private void flipVerticallyInto(const Pixmap source, Pixmap target) @nogc {
2855 	auto src = PixmapScanner(source);
2856 	auto dst = PixmapScannerRW(target);
2857 
2858 	foreach (srcLine; src) {
2859 		dst.back[] = srcLine[];
2860 		dst.popBack();
2861 	}
2862 }
2863 
2864 /++
2865 	Flips an image vertically.
2866 
2867 	```
2868 	╔═══╗   ╔═══╗
2869 	║## ║   ║  -║
2870 	║  -║ → ║## ║
2871 	╚═══╝   ╚═══╝
2872 	```
2873  +/
2874 Pixmap flipVertically(const Pixmap source, Pixmap target) @nogc {
2875 	target.adjustTo(source.flipVerticallyCalcDims());
2876 	source.flipVerticallyInto(target);
2877 	return target;
2878 }
2879 
2880 /// ditto
2881 Pixmap flipVerticallyNew(const Pixmap source) {
2882 	auto target = Pixmap.makeNew(source.flipVerticallyCalcDims());
2883 	source.flipVerticallyInto(target);
2884 	return target;
2885 }
2886 
2887 /// ditto
2888 void flipVerticallyInPlace(Pixmap source) @nogc {
2889 	auto scanner = PixmapScannerRW(source);
2890 
2891 	while (!scanner.empty) {
2892 		auto a = scanner.front;
2893 		auto b = scanner.back;
2894 
2895 		// middle line? (odd number of lines)
2896 		if (a.ptr is b.ptr) {
2897 			break;
2898 		}
2899 
2900 		foreach (idx, ref pxA; a) {
2901 			const tmp = pxA;
2902 			pxA = b[idx];
2903 			b[idx] = tmp;
2904 		}
2905 
2906 		scanner.popFront();
2907 		scanner.popBack();
2908 	}
2909 }
2910 
2911 /// ditto
2912 PixmapBlueprint flipVerticallyCalcDims(const Pixmap source) @nogc {
2913 	return PixmapBlueprint.fromPixmap(source);
2914 }
2915 
2916 /++
2917 	Interpolation methods to apply when scaling images
2918 
2919 	Each filter has its own distinctive properties.
2920 
2921 	$(TIP
2922 		Bilinear filtering (`linear`) is general-purpose.
2923 		Works well with photos.
2924 
2925 		For pixel graphics the retro look of `nearest` (as
2926 		in $(I nearest neighbor)) is usually the option of choice.
2927 	)
2928 
2929 	$(NOTE
2930 		When used as a parameter, it shall be understood as a hint.
2931 
2932 		Implementations are not required to support all enumerated options
2933 		and may pick a different filter as a substitute at their own discretion.
2934 	)
2935  +/
2936 enum ScalingFilter {
2937 	/++
2938 		Nearest neighbor interpolation
2939 
2940 		Also known as $(B proximal interpolation)
2941 		and $(B point sampling).
2942 
2943 		$(TIP
2944 			Visual impression: “blocky”, “pixelated”, “slightly displaced”
2945 		)
2946 	 +/
2947 	nearest,
2948 
2949 	/++
2950 		Bilinear interpolation
2951 
2952 		(Uses arithmetic mean for downscaling.)
2953 
2954 		$(TIP
2955 			Visual impression: “smooth”, “blurred”
2956 		)
2957 	 +/
2958 	bilinear,
2959 
2960 	///
2961 	linear = bilinear,
2962 }
2963 
2964 private enum ScalingDirection {
2965 	none,
2966 	up,
2967 	down,
2968 }
2969 
2970 private static ScalingDirection scalingDirectionFromDelta(const int delta) @nogc {
2971 	if (delta == 0) {
2972 		return ScalingDirection.none;
2973 	} else if (delta > 0) {
2974 		return ScalingDirection.up;
2975 	} else {
2976 		return ScalingDirection.down;
2977 	}
2978 }
2979 
2980 private void scaleToImpl(ScalingFilter filter)(const Pixmap source, Pixmap target) @nogc {
2981 	enum none = ScalingDirection.none;
2982 	enum up = ScalingDirection.up;
2983 	enum down = ScalingDirection.down;
2984 
2985 	enum udecimalHalf = UDecimal.make(0x8000_0000);
2986 	enum uint udecimalHalfFD = udecimalHalf.fractionalDigits;
2987 
2988 	enum idxX = 0, idxY = 1;
2989 	enum idxL = 0, idxR = 1;
2990 	enum idxT = 0, idxB = 1;
2991 
2992 	const int[2] sourceMax = [
2993 		(source.width - 1),
2994 		(source.height - 1),
2995 	];
2996 
2997 	const UDecimal[2] ratios = [
2998 		(UDecimal(source.width) / target.width),
2999 		(UDecimal(source.height) / target.height),
3000 	];
3001 
3002 	const UDecimal[2] ratiosHalf = [
3003 		(ratios[idxX] >> 1),
3004 		(ratios[idxY] >> 1),
3005 	];
3006 
3007 	// ==== Nearest Neighbor ====
3008 	static if (filter == ScalingFilter.nearest) {
3009 
3010 		Point translate(const Point dstPos) {
3011 			pragma(inline, true);
3012 			const x = (dstPos.x * ratios[idxX]).castTo!int;
3013 			const y = (dstPos.y * ratios[idxY]).castTo!int;
3014 			return Point(x, y);
3015 		}
3016 
3017 		auto dst = PixmapScannerRW(target);
3018 
3019 		size_t y = 0;
3020 		foreach (dstLine; dst) {
3021 			foreach (x, ref pxDst; dstLine) {
3022 				const posDst = Point(x.castTo!int, y.castTo!int);
3023 				const posSrc = translate(posDst);
3024 				const pxInt = source.getPixel(posSrc);
3025 				pxDst = pxInt;
3026 			}
3027 			++y;
3028 		}
3029 	}
3030 
3031 	// ==== Bilinear ====
3032 	static if (filter == ScalingFilter.bilinear) {
3033 		void scaleToLinearImpl(ScalingDirection directionX, ScalingDirection directionY)() {
3034 
3035 			alias InterPixel = ulong[4];
3036 
3037 			static Pixel toPixel(const InterPixel ipx) @safe pure nothrow @nogc {
3038 				pragma(inline, true);
3039 				return Pixel(
3040 					clamp255(ipx[0]),
3041 					clamp255(ipx[1]),
3042 					clamp255(ipx[2]),
3043 					clamp255(ipx[3]),
3044 				);
3045 			}
3046 
3047 			static InterPixel toInterPixel(const Pixel ipx) @safe pure nothrow @nogc {
3048 				pragma(inline, true);
3049 				InterPixel result = [
3050 					ipx.r,
3051 					ipx.g,
3052 					ipx.b,
3053 					ipx.a,
3054 				];
3055 				return result;
3056 			}
3057 
3058 			int[2] posSrcCenterToInterpolationTargets(
3059 				ScalingDirection direction,
3060 			)(
3061 				UDecimal posSrcCenter,
3062 				int sourceMax,
3063 			) {
3064 				pragma(inline, true);
3065 
3066 				int[2] result;
3067 				static if (direction == none) {
3068 					const value = posSrcCenter.castTo!int;
3069 					result = [
3070 						value,
3071 						value,
3072 					];
3073 				}
3074 
3075 				static if (direction == up || direction == down) {
3076 					if (posSrcCenter < udecimalHalf) {
3077 						result = [
3078 							0,
3079 							0,
3080 						];
3081 					} else {
3082 						const floor = posSrcCenter.castTo!uint;
3083 						if (posSrcCenter.fractionalDigits == udecimalHalfFD) {
3084 							result = [
3085 								floor,
3086 								floor,
3087 							];
3088 						} else if (posSrcCenter.fractionalDigits > udecimalHalfFD) {
3089 							const upper = min((floor + 1), sourceMax);
3090 							result = [
3091 								floor,
3092 								upper,
3093 							];
3094 						} else {
3095 							result = [
3096 								floor - 1,
3097 								floor,
3098 							];
3099 						}
3100 					}
3101 				}
3102 
3103 				return result;
3104 			}
3105 
3106 			auto dst = PixmapScannerRW(target);
3107 
3108 			size_t y = 0;
3109 			foreach (dstLine; dst) {
3110 				const posDstY = y.castTo!uint;
3111 				const UDecimal posSrcCenterY = posDstY * ratios[idxY] + ratiosHalf[idxY];
3112 
3113 				const int[2] posSrcY = posSrcCenterToInterpolationTargets!(directionY)(
3114 					posSrcCenterY,
3115 					sourceMax[idxY],
3116 				);
3117 
3118 				static if (directionY == down) {
3119 					const nLines = 1 + posSrcY[idxB] - posSrcY[idxT];
3120 				}
3121 
3122 				static if (directionY == up) {
3123 					const ulong[2] weightsY = () {
3124 						ulong[2] result;
3125 						result[0] = (udecimalHalf + posSrcY[1] - posSrcCenterY).fractionalDigits;
3126 						result[1] = ulong(uint.max) + 1 - result[0];
3127 						return result;
3128 					}();
3129 				}
3130 
3131 				foreach (const x, ref pxDst; dstLine) {
3132 					const posDstX = x.castTo!uint;
3133 					const int[2] posDst = [
3134 						posDstX,
3135 						posDstY,
3136 					];
3137 
3138 					const posSrcCenterX = posDst[idxX] * ratios[idxX] + ratiosHalf[idxX];
3139 
3140 					const int[2] posSrcX = posSrcCenterToInterpolationTargets!(directionX)(
3141 						posSrcCenterX,
3142 						sourceMax[idxX],
3143 					);
3144 
3145 					static if (directionX == down) {
3146 						const nSamples = 1 + posSrcX[idxR] - posSrcX[idxL];
3147 					}
3148 
3149 					const Point[4] posNeighs = [
3150 						Point(posSrcX[idxL], posSrcY[idxT]),
3151 						Point(posSrcX[idxR], posSrcY[idxT]),
3152 						Point(posSrcX[idxL], posSrcY[idxB]),
3153 						Point(posSrcX[idxR], posSrcY[idxB]),
3154 					];
3155 
3156 					const Color[4] pxNeighs = [
3157 						source.getPixel(posNeighs[0]),
3158 						source.getPixel(posNeighs[1]),
3159 						source.getPixel(posNeighs[2]),
3160 						source.getPixel(posNeighs[3]),
3161 					];
3162 
3163 					enum idxTL = 0, idxTR = 1, idxBL = 2, idxBR = 3;
3164 
3165 					// ====== Proper bilinear (up) + Avg (down) ======
3166 					static if (filter == ScalingFilter.bilinear) {
3167 						auto pxInt = Pixel(0, 0, 0, 0);
3168 
3169 						// ======== Interpolate X ========
3170 						auto sampleX() {
3171 							pragma(inline, true);
3172 
3173 							static if (directionY == down) {
3174 								alias ForeachLineCallback =
3175 									InterPixel delegate(const Point posLine) @safe pure nothrow @nogc;
3176 
3177 								InterPixel foreachLine(scope ForeachLineCallback apply) {
3178 									pragma(inline, true);
3179 									InterPixel linesSum = 0;
3180 									foreach (const lineY; posSrcY[idxT] .. (1 + posSrcY[idxB])) {
3181 										const posLine = Point(posSrcX[idxL], lineY);
3182 										const lineValues = apply(posLine);
3183 										linesSum[] += lineValues[];
3184 									}
3185 									return linesSum;
3186 								}
3187 							}
3188 
3189 							// ========== None ==========
3190 							static if (directionX == none) {
3191 								static if (directionY == none) {
3192 									return pxNeighs[idxTL];
3193 								}
3194 
3195 								static if (directionY == up) {
3196 									return () @trusted {
3197 										InterPixel[2] result = [
3198 											toInterPixel(pxNeighs[idxTL]),
3199 											toInterPixel(pxNeighs[idxBL]),
3200 										];
3201 										return result;
3202 									}();
3203 								}
3204 
3205 								static if (directionY == down) {
3206 									auto ySum = foreachLine(delegate(const Point posLine) {
3207 										const pxSrc = source.getPixel(posLine);
3208 										return toInterPixel(pxSrc);
3209 									});
3210 									ySum[] /= nLines;
3211 									return ySum;
3212 								}
3213 							}
3214 
3215 							// ========== Down ==========
3216 							static if (directionX == down) {
3217 								static if (directionY == none) {
3218 									const posSampling = posNeighs[idxTL];
3219 									const samplingOffset = source.scanTo(posSampling);
3220 									const srcSamples = () @trusted {
3221 										return source.data.ptr[samplingOffset .. (samplingOffset + nSamples)];
3222 									}();
3223 
3224 									InterPixel xSum = [0, 0, 0, 0];
3225 
3226 									foreach (const srcSample; srcSamples) {
3227 										foreach (immutable ib, const c; srcSample.components) {
3228 											() @trusted { xSum.ptr[ib] += c; }();
3229 										}
3230 									}
3231 
3232 									xSum[] /= nSamples;
3233 									return toPixel(xSum);
3234 								}
3235 
3236 								static if (directionY == up) {
3237 									const Point[2] posSampling = [
3238 										posNeighs[idxTL],
3239 										posNeighs[idxBL],
3240 									];
3241 
3242 									const int[2] samplingOffsets = [
3243 										source.scanTo(posSampling[idxT]),
3244 										source.scanTo(posSampling[idxB]),
3245 									];
3246 
3247 									const srcSamples2 = () @trusted {
3248 										const(const(Pixel)[])[2] result = [
3249 											source.data.ptr[samplingOffsets[idxT] .. (samplingOffsets[idxT] + nSamples)],
3250 											source.data.ptr[samplingOffsets[idxB] .. (samplingOffsets[idxB] + nSamples)],
3251 										];
3252 										return result;
3253 									}();
3254 
3255 									InterPixel[2] xSums = [[0, 0, 0, 0], [0, 0, 0, 0]];
3256 
3257 									foreach (immutable idx, const srcSamples; srcSamples2) {
3258 										foreach (const srcSample; srcSamples) {
3259 											foreach (immutable ib, const c; srcSample.components)
3260 												() @trusted { xSums.ptr[idx].ptr[ib] += c; }();
3261 										}
3262 									}
3263 
3264 									foreach (ref xSum; xSums) {
3265 										xSum[] /= nSamples;
3266 									}
3267 
3268 									return xSums;
3269 								}
3270 
3271 								static if (directionY == down) {
3272 									auto ySum = foreachLine(delegate(const Point posLine) {
3273 										const samplingOffset = source.scanTo(posLine);
3274 										const srcSamples = () @trusted {
3275 											return source.data.ptr[samplingOffset .. (samplingOffset + nSamples)];
3276 										}();
3277 
3278 										InterPixel xSum = 0;
3279 
3280 										foreach (srcSample; srcSamples) {
3281 											foreach (immutable ib, const c; srcSample.components) {
3282 												() @trusted { xSum.ptr[ib] += c; }();
3283 											}
3284 										}
3285 
3286 										return xSum;
3287 									});
3288 
3289 									ySum[] /= nSamples;
3290 									ySum[] /= nLines;
3291 									return ySum;
3292 								}
3293 							}
3294 
3295 							// ========== Up ==========
3296 							static if (directionX == up) {
3297 
3298 								if (posSrcX[0] == posSrcX[1]) {
3299 									static if (directionY == none) {
3300 										return pxNeighs[idxTL];
3301 									}
3302 									static if (directionY == up) {
3303 										return () @trusted {
3304 											InterPixel[2] result = [
3305 												toInterPixel(pxNeighs[idxTL]),
3306 												toInterPixel(pxNeighs[idxBL]),
3307 											];
3308 											return result;
3309 										}();
3310 									}
3311 									static if (directionY == down) {
3312 										auto ySum = foreachLine(delegate(const Point posLine) {
3313 											const samplingOffset = source.scanTo(posLine);
3314 											return toInterPixel(
3315 												(() @trusted => source.data.ptr[samplingOffset])()
3316 											);
3317 										});
3318 										ySum[] /= nLines;
3319 										return ySum;
3320 									}
3321 								}
3322 
3323 								const ulong[2] weightsX = () {
3324 									ulong[2] result;
3325 									result[0] = (udecimalHalf + posSrcX[1] - posSrcCenterX).fractionalDigits;
3326 									result[1] = ulong(uint.max) + 1 - result[0];
3327 									return result;
3328 								}();
3329 
3330 								static if (directionY == none) {
3331 									InterPixel xSum = [0, 0, 0, 0];
3332 
3333 									foreach (immutable ib, ref c; xSum) {
3334 										c += ((() @trusted => pxNeighs[idxTL].components.ptr[ib])() * weightsX[0]);
3335 										c += ((() @trusted => pxNeighs[idxTR].components.ptr[ib])() * weightsX[1]);
3336 									}
3337 
3338 									foreach (ref c; xSum) {
3339 										c >>= 32;
3340 									}
3341 									return toPixel(xSum);
3342 								}
3343 
3344 								static if (directionY == up) {
3345 									InterPixel[2] xSums = [[0, 0, 0, 0], [0, 0, 0, 0]];
3346 
3347 									() @trusted {
3348 										foreach (immutable ib, ref c; xSums[0]) {
3349 											c += (pxNeighs[idxTL].components.ptr[ib] * weightsX[idxL]);
3350 											c += (pxNeighs[idxTR].components.ptr[ib] * weightsX[idxR]);
3351 										}
3352 
3353 										foreach (immutable ib, ref c; xSums[1]) {
3354 											c += (pxNeighs[idxBL].components.ptr[ib] * weightsX[idxL]);
3355 											c += (pxNeighs[idxBR].components.ptr[ib] * weightsX[idxR]);
3356 										}
3357 									}();
3358 
3359 									foreach (ref sum; xSums) {
3360 										foreach (ref c; sum) {
3361 											c >>= 32;
3362 										}
3363 									}
3364 
3365 									return xSums;
3366 								}
3367 
3368 								static if (directionY == down) {
3369 									auto ySum = foreachLine(delegate(const Point posLine) {
3370 										InterPixel xSum = [0, 0, 0, 0];
3371 
3372 										const samplingOffset = source.scanTo(posLine);
3373 										Pixel[2] pxcLR = () @trusted {
3374 											Pixel[2] result = [
3375 												source.data.ptr[samplingOffset],
3376 												source.data.ptr[samplingOffset + 1],
3377 											];
3378 											return result;
3379 										}();
3380 
3381 										foreach (immutable ib, ref c; xSum) {
3382 											c += ((() @trusted => pxcLR[idxL].components.ptr[ib])() * weightsX[idxL]);
3383 											c += ((() @trusted => pxcLR[idxR].components.ptr[ib])() * weightsX[idxR]);
3384 										}
3385 
3386 										foreach (ref c; xSum) {
3387 											c >>= 32;
3388 										}
3389 										return xSum;
3390 									});
3391 
3392 									ySum[] /= nLines;
3393 									return ySum;
3394 								}
3395 							}
3396 						}
3397 
3398 						// ======== Interpolate Y ========
3399 						static if (directionY == none) {
3400 							const Pixel tmp = sampleX();
3401 							pxInt = tmp;
3402 						}
3403 						static if (directionY == down) {
3404 							const InterPixel tmp = sampleX();
3405 							pxInt = toPixel(tmp);
3406 						}
3407 						static if (directionY == up) {
3408 							const InterPixel[2] xSums = sampleX();
3409 							foreach (immutable ib, ref c; pxInt.components) {
3410 								ulong ySum = 0;
3411 								ySum += ((() @trusted => xSums[idxT].ptr[ib])() * weightsY[idxT]);
3412 								ySum += ((() @trusted => xSums[idxB].ptr[ib])() * weightsY[idxB]);
3413 
3414 								const xySum = (ySum >> 32);
3415 								c = clamp255(xySum);
3416 							}
3417 						}
3418 					}
3419 
3420 					pxDst = pxInt;
3421 				}
3422 
3423 				++y;
3424 			}
3425 		}
3426 
3427 		const Size delta = (target.size - source.size);
3428 
3429 		const ScalingDirection[2] directions = [
3430 			scalingDirectionFromDelta(delta.width),
3431 			scalingDirectionFromDelta(delta.height),
3432 		];
3433 
3434 		if (directions[0] == none) {
3435 			if (directions[1] == none) {
3436 				version (none) {
3437 					scaleToLinearImpl!(none, none)();
3438 				} else {
3439 					target.data[] = source.data[];
3440 				}
3441 			} else if (directions[1] == up) {
3442 				scaleToLinearImpl!(none, up)();
3443 			} else /* if (directions[1] == down) */ {
3444 				scaleToLinearImpl!(none, down)();
3445 			}
3446 		} else if (directions[0] == up) {
3447 			if (directions[1] == none) {
3448 				scaleToLinearImpl!(up, none)();
3449 			} else if (directions[1] == up) {
3450 				scaleToLinearImpl!(up, up)();
3451 			} else /* if (directions[1] == down) */ {
3452 				scaleToLinearImpl!(up, down)();
3453 			}
3454 		} else /* if (directions[0] == down) */ {
3455 			if (directions[1] == none) {
3456 				scaleToLinearImpl!(down, none)();
3457 			} else if (directions[1] == up) {
3458 				scaleToLinearImpl!(down, up)();
3459 			} else /* if (directions[1] == down) */ {
3460 				scaleToLinearImpl!(down, down)();
3461 			}
3462 		}
3463 	}
3464 }
3465 
3466 /++
3467 	Scales a pixmap and stores the result in the provided target Pixmap.
3468 
3469 	The size to scale the image to
3470 	is derived from the size of the target.
3471 
3472 	---
3473 	// This function can be used to omit a redundant size parameter
3474 	// in cases like this:
3475 	target = scale(source, target, target.size, ScalingFilter.bilinear);
3476 
3477 	// → Instead do:
3478 	scaleTo(source, target, ScalingFilter.bilinear);
3479 	---
3480  +/
3481 void scaleTo(const Pixmap source, Pixmap target, ScalingFilter filter) @nogc {
3482 	import std.meta : NoDuplicates;
3483 	import std.traits : EnumMembers;
3484 
3485 	// dfmt off
3486 	final switch (filter) {
3487 		static foreach (scalingFilter; NoDuplicates!(EnumMembers!ScalingFilter))
3488 			case scalingFilter: {
3489 				scaleToImpl!scalingFilter(source, target);
3490 				return;
3491 			}
3492 	}
3493 	// dfmt on
3494 }
3495 
3496 // consistency
3497 private alias scaleInto = scaleTo;
3498 
3499 /++
3500 	Scales an image to a new size.
3501 
3502 	```
3503 	╔═══╗   ╔═╗
3504 	║———║ → ║—║
3505 	╚═══╝   ╚═╝
3506 	```
3507  +/
3508 Pixmap scale(const Pixmap source, Pixmap target, Size scaleToSize, ScalingFilter filter) @nogc {
3509 	target.adjustTo(scaleCalcDims(scaleToSize));
3510 	source.scaleInto(target, filter);
3511 	return target;
3512 }
3513 
3514 /// ditto
3515 Pixmap scaleNew(const Pixmap source, Size scaleToSize, ScalingFilter filter) {
3516 	auto target = Pixmap.makeNew(scaleToSize);
3517 	source.scaleInto(target, filter);
3518 	return target;
3519 }
3520 
3521 /// ditto
3522 PixmapBlueprint scaleCalcDims(Size scaleToSize) @nogc {
3523 	return PixmapBlueprint.fromSize(scaleToSize);
3524 }
3525 
3526 @safe pure nothrow @nogc:
3527 
3528 // ==== Blending functions ====
3529 
3530 /++
3531 	Alpha-blending accuracy level
3532 
3533 	$(TIP
3534 		This primarily exists for performance reasons.
3535 		In my tests LLVM manages to auto-vectorize the RGB-only codepath significantly better,
3536 		while the codegen for the accurate RGBA path is pretty conservative.
3537 
3538 		This provides an optimization opportunity for use-cases
3539 		that don’t require an alpha-channel on the result.
3540 	)
3541  +/
3542 enum BlendAccuracy {
3543 	/++
3544 		Only RGB channels will have the correct result.
3545 
3546 		A(lpha) channel can contain any value.
3547 
3548 		Suitable for blending into non-transparent targets (e.g. framebuffer, canvas)
3549 		where the resulting alpha-channel (opacity) value does not matter.
3550 	 +/
3551 	rgb = false,
3552 
3553 	/++
3554 		All RGBA channels will have the correct result.
3555 
3556 		Suitable for blending into transparent targets (e.g. images)
3557 		where the resulting alpha-channel (opacity) value matters.
3558 
3559 		Use this mode for image manipulation.
3560 	 +/
3561 	rgba = true,
3562 }
3563 
3564 /++
3565 	Blend modes
3566 
3567 	$(NOTE
3568 		As blending operations are implemented as integer calculations,
3569 		results may be slightly less precise than those from image manipulation
3570 		programs using floating-point math.
3571 	)
3572 
3573 	See_Also:
3574 		<https://www.w3.org/TR/compositing/#blending>
3575  +/
3576 enum BlendMode {
3577 	///
3578 	none = 0,
3579 	///
3580 	replace = none,
3581 	///
3582 	normal = 1,
3583 	///
3584 	alpha = normal,
3585 
3586 	///
3587 	multiply,
3588 	///
3589 	screen,
3590 
3591 	///
3592 	overlay,
3593 	///
3594 	hardLight,
3595 	///
3596 	softLight,
3597 
3598 	///
3599 	darken,
3600 	///
3601 	lighten,
3602 
3603 	///
3604 	colorDodge,
3605 	///
3606 	colorBurn,
3607 
3608 	///
3609 	difference,
3610 	///
3611 	exclusion,
3612 	///
3613 	subtract,
3614 	///
3615 	divide,
3616 }
3617 
3618 ///
3619 alias Blend = BlendMode;
3620 
3621 // undocumented
3622 enum blendNormal = BlendMode.normal;
3623 
3624 ///
3625 alias BlendFn = ubyte function(const ubyte background, const ubyte foreground) pure nothrow @nogc;
3626 
3627 /++
3628 	Blends `source` into `target`
3629 	with respect to the opacity of the source image (as stored in the alpha channel).
3630 
3631 	See_Also:
3632 		[alphaBlendRGBA] and [alphaBlendRGB] are shorthand functions
3633 		in cases where no special blending algorithm is needed.
3634  +/
3635 template alphaBlend(BlendFn blend = null, BlendAccuracy accuracy = BlendAccuracy.rgba) {
3636 	/// ditto
3637 	public void alphaBlend(scope Pixel[] target, scope const Pixel[] source) @trusted
3638 	in (source.length == target.length) {
3639 		foreach (immutable idx, ref pxTarget; target) {
3640 			alphaBlend(pxTarget, source.ptr[idx]);
3641 		}
3642 	}
3643 
3644 	/// ditto
3645 	public void alphaBlend(ref Pixel pxTarget, const Pixel pxSource) @trusted {
3646 		pragma(inline, true);
3647 
3648 		static if (accuracy == BlendAccuracy.rgba) {
3649 			immutable alphaResult = clamp255(pxSource.a + n255thsOf(pxTarget.a, (0xFF - pxSource.a)));
3650 			//immutable alphaResult = clamp255(pxTarget.a + n255thsOf(pxSource.a, (0xFF - pxTarget.a)));
3651 		}
3652 
3653 		immutable alphaSource = (pxSource.a | (pxSource.a << 8));
3654 		immutable alphaTarget = (0xFFFF - alphaSource);
3655 
3656 		foreach (immutable ib, ref px; pxTarget.components) {
3657 			static if (blend !is null) {
3658 				immutable bx = blend(px, pxSource.components.ptr[ib]);
3659 			} else {
3660 				immutable bx = pxSource.components.ptr[ib];
3661 			}
3662 			immutable d = cast(ubyte)(((px * alphaTarget) + 0x8080) >> 16);
3663 			immutable s = cast(ubyte)(((bx * alphaSource) + 0x8080) >> 16);
3664 			px = cast(ubyte)(d + s);
3665 		}
3666 
3667 		static if (accuracy == BlendAccuracy.rgba) {
3668 			pxTarget.a = alphaResult;
3669 		}
3670 	}
3671 }
3672 
3673 /// ditto
3674 template alphaBlend(BlendAccuracy accuracy, BlendFn blend = null) {
3675 	alias alphaBlend = alphaBlend!(blend, accuracy);
3676 }
3677 
3678 /++
3679 	Blends `source` into `target`
3680 	with respect to the opacity of the source image (as stored in the alpha channel).
3681 
3682 	This variant is $(slower than) [alphaBlendRGB],
3683 	but calculates the correct alpha-channel value of the target.
3684 	See [BlendAccuracy] for further explanation.
3685  +/
3686 public void alphaBlendRGBA(scope Pixel[] target, scope const Pixel[] source) @safe {
3687 	return alphaBlend!(null, BlendAccuracy.rgba)(target, source);
3688 }
3689 
3690 /// ditto
3691 public void alphaBlendRGBA(ref Pixel pxTarget, const Pixel pxSource) @safe {
3692 	return alphaBlend!(null, BlendAccuracy.rgba)(pxTarget, pxSource);
3693 }
3694 
3695 /++
3696 	Blends `source` into `target`
3697 	with respect to the opacity of the source image (as stored in the alpha channel).
3698 
3699 	This variant is $(B faster than) [alphaBlendRGBA],
3700 	but leads to a wrong alpha-channel value in the target.
3701 	Useful because of the performance advantage in cases where the resulting
3702 	alpha does not matter.
3703 	See [BlendAccuracy] for further explanation.
3704  +/
3705 public void alphaBlendRGB(scope Pixel[] target, scope const Pixel[] source) @safe {
3706 	return alphaBlend!(null, BlendAccuracy.rgb)(target, source);
3707 }
3708 
3709 /// ditto
3710 public void alphaBlendRGB(ref Pixel pxTarget, const Pixel pxSource) @safe {
3711 	return alphaBlend!(null, BlendAccuracy.rgb)(pxTarget, pxSource);
3712 }
3713 
3714 /++
3715 	Blends pixel `source` into pixel `target`
3716 	using the requested [BlendMode|blending mode].
3717  +/
3718 template blendPixel(BlendMode mode, BlendAccuracy accuracy = BlendAccuracy.rgba) {
3719 
3720 	static if (mode == BlendMode.replace) {
3721 		/// ditto
3722 		void blendPixel(ref Pixel target, const Pixel source) {
3723 			target = source;
3724 		}
3725 	}
3726 
3727 	static if (mode == BlendMode.alpha) {
3728 		/// ditto
3729 		void blendPixel(ref Pixel target, const Pixel source) {
3730 			return alphaBlend!accuracy(target, source);
3731 		}
3732 	}
3733 
3734 	static if (mode == BlendMode.multiply) {
3735 		/// ditto
3736 		void blendPixel(ref Pixel target, const Pixel source) {
3737 			return alphaBlend!(accuracy,
3738 				(a, b) => n255thsOf(a, b)
3739 			)(target, source);
3740 		}
3741 	}
3742 
3743 	static if (mode == BlendMode.screen) {
3744 		/// ditto
3745 		void blendPixel()(ref Pixel target, const Pixel source) {
3746 			return alphaBlend!(accuracy,
3747 				(a, b) => castTo!ubyte(0xFF - n255thsOf((0xFF - a), (0xFF - b)))
3748 			)(target, source);
3749 		}
3750 	}
3751 
3752 	static if (mode == BlendMode.darken) {
3753 		/// ditto
3754 		void blendPixel()(ref Pixel target, const Pixel source) {
3755 			return alphaBlend!(accuracy,
3756 				(a, b) => min(a, b)
3757 			)(target, source);
3758 		}
3759 	}
3760 	static if (mode == BlendMode.lighten) {
3761 		/// ditto
3762 		void blendPixel()(ref Pixel target, const Pixel source) {
3763 			return alphaBlend!(accuracy,
3764 				(a, b) => max(a, b)
3765 			)(target, source);
3766 		}
3767 	}
3768 
3769 	static if (mode == BlendMode.overlay) {
3770 		/// ditto
3771 		void blendPixel()(ref Pixel target, const Pixel source) {
3772 			return alphaBlend!(accuracy, function(const ubyte b, const ubyte f) {
3773 				if (b < 0x80) {
3774 					return n255thsOf((2 * b).castTo!ubyte, f);
3775 				}
3776 				return castTo!ubyte(
3777 					0xFF - n255thsOf(castTo!ubyte(2 * (0xFF - b)), (0xFF - f))
3778 				);
3779 			})(target, source);
3780 		}
3781 	}
3782 
3783 	static if (mode == BlendMode.hardLight) {
3784 		/// ditto
3785 		void blendPixel()(ref Pixel target, const Pixel source) {
3786 			return alphaBlend!(accuracy, function(const ubyte b, const ubyte f) {
3787 				if (f < 0x80) {
3788 					return n255thsOf(castTo!ubyte(2 * f), b);
3789 				}
3790 				return castTo!ubyte(
3791 					0xFF - n255thsOf(castTo!ubyte(2 * (0xFF - f)), (0xFF - b))
3792 				);
3793 			})(target, source);
3794 		}
3795 	}
3796 
3797 	static if (mode == BlendMode.softLight) {
3798 		/// ditto
3799 		void blendPixel()(ref Pixel target, const Pixel source) {
3800 			return alphaBlend!(accuracy, function(const ubyte b, const ubyte f) {
3801 				if (f < 0x80) {
3802 					// dfmt off
3803 					return castTo!ubyte(
3804 						b - n255thsOf(
3805 								n255thsOf((0xFF - 2 * f).castTo!ubyte, b),
3806 								(0xFF - b),
3807 							)
3808 					);
3809 					// dfmt on
3810 				}
3811 
3812 				// TODO: optimize if possible
3813 				// dfmt off
3814 				immutable ubyte d = (b < 0x40)
3815 					? castTo!ubyte((b * (0x3FC + (((16 * b - 0xBF4) * b) / 255))) / 255)
3816 					: intNormalizedSqrt(b);
3817 				//dfmt on
3818 
3819 				return castTo!ubyte(
3820 					b + n255thsOf((2 * f - 0xFF).castTo!ubyte, (d - b).castTo!ubyte)
3821 				);
3822 			})(target, source);
3823 		}
3824 	}
3825 
3826 	static if (mode == BlendMode.colorDodge) {
3827 		/// ditto
3828 		void blendPixel()(ref Pixel target, const Pixel source) {
3829 			return alphaBlend!(accuracy, function(const ubyte b, const ubyte f) {
3830 				if (b == 0x00) {
3831 					return ubyte(0x00);
3832 				}
3833 				if (f == 0xFF) {
3834 					return ubyte(0xFF);
3835 				}
3836 				return min(
3837 					ubyte(0xFF),
3838 					clamp255((255 * b) / (0xFF - f))
3839 				);
3840 			})(target, source);
3841 		}
3842 	}
3843 
3844 	static if (mode == BlendMode.colorBurn) {
3845 		/// ditto
3846 		void blendPixel()(ref Pixel target, const Pixel source) {
3847 			return alphaBlend!(accuracy, function(const ubyte b, const ubyte f) {
3848 				if (b == 0xFF) {
3849 					return ubyte(0xFF);
3850 				}
3851 				if (f == 0x00) {
3852 					return ubyte(0x00);
3853 				}
3854 
3855 				immutable m = min(
3856 					ubyte(0xFF),
3857 					clamp255(((0xFF - b) * 255) / f)
3858 				);
3859 				return castTo!ubyte(0xFF - m);
3860 			})(target, source);
3861 		}
3862 	}
3863 
3864 	static if (mode == BlendMode.difference) {
3865 		/// ditto
3866 		void blendPixel()(ref Pixel target, const Pixel source) {
3867 			return alphaBlend!(accuracy,
3868 				(b, f) => (b > f) ? castTo!ubyte(b - f) : castTo!ubyte(f - b)
3869 			)(target, source);
3870 		}
3871 	}
3872 
3873 	static if (mode == BlendMode.exclusion) {
3874 		/// ditto
3875 		void blendPixel()(ref Pixel target, const Pixel source) {
3876 			return alphaBlend!(accuracy,
3877 				(b, f) => castTo!ubyte(b + f - (2 * n255thsOf(f, b)))
3878 			)(target, source);
3879 		}
3880 	}
3881 
3882 	static if (mode == BlendMode.subtract) {
3883 		/// ditto
3884 		void blendPixel()(ref Pixel target, const Pixel source) {
3885 			return alphaBlend!(accuracy,
3886 				(b, f) => (b > f) ? castTo!ubyte(b - f) : ubyte(0)
3887 			)(target, source);
3888 		}
3889 	}
3890 
3891 	static if (mode == BlendMode.divide) {
3892 		/// ditto
3893 		void blendPixel()(ref Pixel target, const Pixel source) {
3894 			return alphaBlend!(accuracy,
3895 				(b, f) => (f == 0) ? ubyte(0xFF) : clamp255(0xFF * b / f)
3896 			)(target, source);
3897 		}
3898 	}
3899 
3900 	//else {
3901 	//	static assert(false, "Missing `blendPixel()` implementation for `BlendMode`.`" ~ mode ~ "`.");
3902 	//}
3903 }
3904 
3905 /++
3906 	Blends the pixel data of `source` into `target`
3907 	using the requested [BlendMode|blending mode].
3908 
3909 	`source` and `target` MUST have the same length.
3910  +/
3911 void blendPixels(
3912 	BlendMode mode,
3913 	BlendAccuracy accuracy,
3914 )(scope Pixel[] target, scope const Pixel[] source) @trusted
3915 in (source.length == target.length) {
3916 	static if (mode == BlendMode.replace) {
3917 		// explicit optimization
3918 		target.ptr[0 .. target.length] = source.ptr[0 .. target.length];
3919 	} else {
3920 
3921 		// better error message in case it’s not implemented
3922 		static if (!is(typeof(blendPixel!(mode, accuracy)))) {
3923 			pragma(msg, "Hint: Missing or bad `blendPixel!(" ~ mode.stringof ~ ")`.");
3924 		}
3925 
3926 		foreach (immutable idx, ref pxTarget; target) {
3927 			blendPixel!(mode, accuracy)(pxTarget, source.ptr[idx]);
3928 		}
3929 	}
3930 }
3931 
3932 /// ditto
3933 void blendPixels(BlendAccuracy accuracy)(scope Pixel[] target, scope const Pixel[] source, BlendMode mode) {
3934 	import std.meta : NoDuplicates;
3935 	import std.traits : EnumMembers;
3936 
3937 	final switch (mode) with (BlendMode) {
3938 		static foreach (m; NoDuplicates!(EnumMembers!BlendMode)) {
3939 	case m:
3940 			return blendPixels!(m, accuracy)(target, source);
3941 		}
3942 	}
3943 }
3944 
3945 /// ditto
3946 void blendPixels(
3947 	scope Pixel[] target,
3948 	scope const Pixel[] source,
3949 	BlendMode mode,
3950 	BlendAccuracy accuracy = BlendAccuracy.rgba,
3951 ) {
3952 	if (accuracy == BlendAccuracy.rgb) {
3953 		return blendPixels!(BlendAccuracy.rgb)(target, source, mode);
3954 	} else {
3955 		return blendPixels!(BlendAccuracy.rgba)(target, source, mode);
3956 	}
3957 }
3958 
3959 // ==== Drawing functions ====
3960 
3961 /++
3962 	Draws a single pixel
3963  +/
3964 void drawPixel(Pixmap target, Point pos, Pixel color) {
3965 	immutable size_t offset = linearOffset(target.width, pos);
3966 	target.data[offset] = color;
3967 }
3968 
3969 /++
3970 	Draws a rectangle
3971  +/
3972 void drawRectangle(Pixmap target, Rectangle rectangle, Pixel color) {
3973 	alias r = rectangle;
3974 
3975 	immutable tRect = OriginRectangle(
3976 		Size(target.width, target.height),
3977 	);
3978 
3979 	// out of bounds?
3980 	if (!tRect.intersect(r)) {
3981 		return;
3982 	}
3983 
3984 	immutable drawingTarget = Point(
3985 		(r.pos.x >= 0) ? r.pos.x : 0,
3986 		(r.pos.y >= 0) ? r.pos.y : 0,
3987 	);
3988 
3989 	immutable drawingEnd = Point(
3990 		(r.right < tRect.right) ? r.right : tRect.right,
3991 		(r.bottom < tRect.bottom) ? r.bottom : tRect.bottom,
3992 	);
3993 
3994 	immutable int drawingWidth = drawingEnd.x - drawingTarget.x;
3995 
3996 	foreach (y; drawingTarget.y .. drawingEnd.y) {
3997 		target.sliceAt(Point(drawingTarget.x, y), drawingWidth)[] = color;
3998 	}
3999 }
4000 
4001 /++
4002 	Draws a line
4003  +/
4004 void drawLine(Pixmap target, Point a, Point b, Pixel color) {
4005 	import std.math : sqrt;
4006 
4007 	// TODO: line width
4008 	// TODO: anti-aliasing (looks awful without it!)
4009 
4010 	float deltaX = b.x - a.x;
4011 	float deltaY = b.y - a.y;
4012 	int steps = sqrt(deltaX * deltaX + deltaY * deltaY).castTo!int;
4013 
4014 	float[2] step = [
4015 		(deltaX / steps),
4016 		(deltaY / steps),
4017 	];
4018 
4019 	foreach (i; 0 .. steps) {
4020 		// dfmt off
4021 		immutable Point p = a + Point(
4022 			round(step[0] * i).castTo!int,
4023 			round(step[1] * i).castTo!int,
4024 		);
4025 		// dfmt on
4026 
4027 		immutable offset = linearOffset(p, target.width);
4028 		target.data[offset] = color;
4029 	}
4030 
4031 	immutable offsetEnd = linearOffset(b, target.width);
4032 	target.data[offsetEnd] = color;
4033 }
4034 
4035 /++
4036 	Draws an image (a source pixmap) on a target pixmap
4037 
4038 	Params:
4039 		target = target pixmap to draw on
4040 		image = source pixmap
4041 		pos = top-left destination position (on the target pixmap)
4042  +/
4043 void drawPixmap(Pixmap target, const Pixmap image, Point pos, Blend blend = blendNormal) {
4044 	alias source = image;
4045 
4046 	immutable tRect = OriginRectangle(
4047 		Size(target.width, target.height),
4048 	);
4049 
4050 	immutable sRect = Rectangle(pos, source.size);
4051 
4052 	// out of bounds?
4053 	if (!tRect.intersect(sRect)) {
4054 		return;
4055 	}
4056 
4057 	immutable drawingTarget = Point(
4058 		(pos.x >= 0) ? pos.x : 0,
4059 		(pos.y >= 0) ? pos.y : 0,
4060 	);
4061 
4062 	immutable drawingEnd = Point(
4063 		(sRect.right < tRect.right) ? sRect.right : tRect.right,
4064 		(sRect.bottom < tRect.bottom) ? sRect.bottom : tRect.bottom,
4065 	);
4066 
4067 	immutable drawingSource = Point(drawingTarget.x, 0) - Point(sRect.pos.x, sRect.pos.y);
4068 	immutable int drawingWidth = drawingEnd.x - drawingTarget.x;
4069 
4070 	foreach (y; drawingTarget.y .. drawingEnd.y) {
4071 		blendPixels(
4072 			target.sliceAt(Point(drawingTarget.x, y), drawingWidth),
4073 			source.sliceAt(Point(drawingSource.x, y + drawingSource.y), drawingWidth),
4074 			blend,
4075 		);
4076 	}
4077 }
4078 
4079 /++
4080 	Draws an image (a subimage from a source pixmap) on a target pixmap
4081 
4082 	Params:
4083 		target = target pixmap to draw on
4084 		image = source subpixmap
4085 		pos = top-left destination position (on the target pixmap)
4086  +/
4087 void drawPixmap(Pixmap target, const SubPixmap image, Point pos, Blend blend = blendNormal) {
4088 	alias source = image;
4089 
4090 	debug assert(source.isValid);
4091 
4092 	immutable tRect = OriginRectangle(
4093 		Size(target.width, target.height),
4094 	);
4095 
4096 	immutable sRect = Rectangle(pos, source.size);
4097 
4098 	// out of bounds?
4099 	if (!tRect.intersect(sRect)) {
4100 		return;
4101 	}
4102 
4103 	Point sourceOffset = source.offset;
4104 	Point drawingTarget;
4105 	Size drawingSize = source.size;
4106 
4107 	if (pos.x <= 0) {
4108 		sourceOffset.x -= pos.x;
4109 		drawingTarget.x = 0;
4110 		drawingSize.width += pos.x;
4111 	} else {
4112 		drawingTarget.x = pos.x;
4113 	}
4114 
4115 	if (pos.y <= 0) {
4116 		sourceOffset.y -= pos.y;
4117 		drawingTarget.y = 0;
4118 		drawingSize.height += pos.y;
4119 	} else {
4120 		drawingTarget.y = pos.y;
4121 	}
4122 
4123 	Point drawingEnd = drawingTarget + drawingSize.castTo!Point();
4124 	if (drawingEnd.x >= target.width) {
4125 		drawingSize.width -= (drawingEnd.x - target.width);
4126 	}
4127 	if (drawingEnd.y >= target.height) {
4128 		drawingSize.height -= (drawingEnd.y - target.height);
4129 	}
4130 
4131 	auto dst = SubPixmap(target, drawingTarget, drawingSize);
4132 	auto src = const(SubPixmap)(
4133 		source.source,
4134 		drawingSize,
4135 		sourceOffset,
4136 	);
4137 
4138 	src.xferTo(dst, blend);
4139 }
4140 
4141 /++
4142 	Draws a sprite from a spritesheet
4143  +/
4144 void drawSprite(Pixmap target, const SpriteSheet sheet, int spriteIndex, Point pos, Blend blend = blendNormal) {
4145 	immutable tRect = OriginRectangle(
4146 		Size(target.width, target.height),
4147 	);
4148 
4149 	immutable spriteOffset = sheet.getSpritePixelOffset2D(spriteIndex);
4150 	immutable sRect = Rectangle(pos, sheet.spriteSize);
4151 
4152 	// out of bounds?
4153 	if (!tRect.intersect(sRect)) {
4154 		return;
4155 	}
4156 
4157 	immutable drawingTarget = Point(
4158 		(pos.x >= 0) ? pos.x : 0,
4159 		(pos.y >= 0) ? pos.y : 0,
4160 	);
4161 
4162 	immutable drawingEnd = Point(
4163 		(sRect.right < tRect.right) ? sRect.right : tRect.right,
4164 		(sRect.bottom < tRect.bottom) ? sRect.bottom : tRect.bottom,
4165 	);
4166 
4167 	immutable drawingSource =
4168 		spriteOffset
4169 		+ Point(drawingTarget.x, 0)
4170 		- Point(sRect.pos.x, sRect.pos.y);
4171 	immutable int drawingWidth = drawingEnd.x - drawingTarget.x;
4172 
4173 	foreach (y; drawingTarget.y .. drawingEnd.y) {
4174 		blendPixels(
4175 			target.sliceAt(Point(drawingTarget.x, y), drawingWidth),
4176 			sheet.pixmap.sliceAt(Point(drawingSource.x, y + drawingSource.y), drawingWidth),
4177 			blend,
4178 		);
4179 	}
4180 }