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 	pixels := [ 0, 1, 2, 3 ]
26 	width  := 2
27 
28 	pixmap(pixels, width)
29 		=> [
30 			[ 0, 1 ]
31 			[ 2, 3 ]
32 		]
33 	```
34 
35 	```
36 	pixels := [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ]
37 	width  := 3
38 
39 	pixmap(pixels, width)
40 		=> [
41 			[ 0,  1,  2 ]
42 			[ 3,  4,  5 ]
43 			[ 6,  7,  8 ]
44 			[ 9, 10, 11 ]
45 		]
46 	```
47 
48 	```
49 	pixels := [ 0, 1, 2, 3, 4, 5, 6, 7 ]
50 	width  := 4
51 
52 	pixmap(pixels, width)
53 		=> [
54 			[ 0, 1, 2, 3 ]
55 			[ 4, 5, 6, 7 ]
56 		]
57 	```
58 
59 
60 
61 
62 	### Colors
63 
64 	Colors are stored in an RGBA format with 8 bit per channel.
65 	See [arsd.color.Color|Pixel] for details.
66 
67 
68 	### The coordinate system
69 
70 	The top left corner of a pixmap is its $(B origin) `(0,0)`.
71 
72 	The $(horizontal axis) is called `x`.
73 	Its corresponding length/dimension is known as `width`.
74 
75 	The letter `y` is used to describe the $(B vertical axis).
76 	Its corresponding length/dimension is known as `height`.
77 
78 	```
79 	0 → x
80 81 	y
82 	```
83 
84 	Furthermore, $(B length) refers to the areal size of a pixmap.
85 	It represents the total number of pixels in a pixmap.
86 	It follows from the foregoing that the term $(I long) usually refers to
87 	the length (not the width).
88 
89 
90 
91 
92 	### Pixmaps
93 
94 	A [Pixmap] consist of two fields:
95 	$(LIST
96 		* a slice (of an array of [Pixel|Pixels])
97 		* a width
98 	)
99 
100 	This design comes with many advantages.
101 	First and foremost it brings simplicity.
102 
103 	Pixel data buffers can be reused across pixmaps,
104 	even when those have different sizes.
105 	Simply slice the buffer to fit just enough pixels for the new pixmap.
106 
107 	Memory management can also happen outside of the pixmap.
108 	It is possible to use a buffer allocated elsewhere. (Such a one shouldn’t
109 	be mixed with the built-in memory management facilities of the pixmap type.
110 	Otherwise one will end up with GC-allocated copies.)
111 
112 	The most important downside is that it makes pixmaps basically a partial
113 	reference type.
114 
115 	Copying a pixmap creates a shallow copy still poiting to the same pixel
116 	data that is also used by the source pixmap.
117 	This implies that manipulating the source pixels also manipulates the
118 	pixels of the copy – and vice versa.
119 
120 	The issues implied by this become an apparent when one of the references
121 	modifies the pixel data in a way that also affects the dimensions of the
122 	image; such as cropping.
123 
124 	Pixmaps describe how pixel data stored in a 1-dimensional memory space is
125 	meant to be interpreted as a 2-dimensional image.
126 
127 	A notable implication of this 1D ↔ 2D mapping is, that slicing the 1D data
128 	leads to non-sensical results in the 2D space when the 1D-slice is
129 	reinterpreted as 2D-image.
130 
131 	Especially slicing across scanlines (→ horizontal rows of an image) is
132 	prone to such errors.
133 
134 	(Slicing of the 1D array data can actually be utilized to cut off the
135 	bottom part of an image. Any other naiv cropping operations will run into
136 	the aforementioned issues.)
137 
138 
139 
140 
141 	### Image manipulation
142 
143 	The term “image manipulation function” here refers to functions that
144 	manipulate (e.g. transform) an image as a whole.
145 
146 	Image manipulation functions in this library are provided in up to three
147 	flavors:
148 
149 	$(LIST
150 		* a “source to target” function
151 		* a “source to newly allocated target” wrapper
152 		* $(I optionally) an “in-place” adaption
153 	)
154 
155 	Additionally, a “compute dimensions of target” function is provided.
156 
157 
158 	#### Source to Target
159 
160 	The regular “source to target” function takes (at least) two parameters:
161 	A source [Pixmap] and a target [Pixmap].
162 
163 	(Additional operation-specific arguments may be required as well.)
164 
165 	The target pixmap usually needs to be able to fit at least the same number
166 	of pixels as the source holds.
167 	Use the corresponding “compute size of target function” to calculate the
168 	required size when needed.
169 	(A notable exception would be cropping, where to target pixmap must be only
170 	at least long enough to hold the area of the size to crop to.)
171 
172 	The data stored in the buffer of the target pixmap is overwritten by the
173 	operation.
174 
175 	A modified Pixmap structure with adjusted dimensions is returned.
176 
177 	These functions are named plain and simple after the respective operation
178 	they perform; e.g. [flipHorizontally] or [crop].
179 
180 	---
181 	// Allocate a new target Pixmap.
182 	Pixmap target = Pixmap.makeNew(
183 		flipHorizontallyCalcDims(sourceImage)
184 	);
185 
186 	// Flip the image horizontally and store the updated structure.
187 	// (Note: As a horizontal flip does not affect the dimensions of a Pixmap,
188 	//        storing the updated structure would not be necessary
189 	//        in this specific scenario.)
190 	target = sourceImage.flipHorizontally(target);
191 	---
192 
193 	---
194 	const cropOffset = Point(0, 0);
195 	const cropSize = Size(100, 100);
196 
197 	// Allocate a new target Pixmap.
198 	Pixmap target = Pixmap.makeNew(
199 		cropCalcDims(sourceImage, cropSize, cropOffset)
200 	);
201 
202 	// Crop the Pixmap.
203 	target = sourceImage.crop(target, cropSize, cropOffset);
204 	---
205 
206 	$(PITFALL
207 		“Source to target” functions do not work in place.
208 		Do not attempt to pass Pixmaps sharing the same buffer for both source
209 		and target. Such would lead to bad results with heavy artifacts.
210 
211 		Use the “in-place” variant of the operation instead.
212 
213 		Moreover:
214 		Do not use the artifacts produced by this as a creative effect.
215 		Those are an implementation detail (and may change at any point).
216 	)
217 
218 
219 	#### Source to New Target
220 
221 	The “source to newly allocated target” wrapper allocates a new buffer to
222 	hold the manipulated target.
223 
224 	These wrappers are provided for user convenience.
225 
226 	They are identified by the suffix `-New` that is appended to the name of
227 	the corresponding “source to target” function;
228 	e.g. [flipHorizontallyNew] or [cropNew].
229 
230 	---
231 	// Create a new flipped Pixmap.
232 	Pixmap target = sourceImage.flipHorizontallyNew();
233 	---
234 
235 	---
236 	const cropOffset = Point(0, 0);
237 	const cropSize = Size(100, 100);
238 
239 	// Create a new cropped Pixmap.
240 	Pixmap target = sourceImage.cropNew(cropSize, cropOffset);
241 	---
242 
243 
244 	#### In-Place
245 
246 	For selected image manipulation functions a special adaption is provided
247 	that stores the result in the source pixel data buffer.
248 
249 	Depending on the operation, implementing in-place transformations can be
250 	either straightforward or a major undertaking (and topic of research).
251 	This library focuses and the former and leaves out cases where the latter
252 	applies.
253 	In particular, algorithms that require allocating further buffers to store
254 	temporary results or auxiliary data will probably not get implemented.
255 
256 	Furthermore, operations where to result is longer than the source cannot
257 	be performed in-place.
258 
259 	Certain in-place manipulation functions return a shallow-copy of the
260 	source structure with dimensions adjusted accordingly.
261 	This is behavior is not streamlined consistently as the lack of an
262 	in-place option for certain operations makes them a special case anyway.
263 
264 	These function are suffixed with `-InPlace`;
265 	e.g. [flipHorizontallyInPlace] or [cropInPlace].
266 
267 	$(TIP
268 		Manipulating the source image directly can lead to unexpected results
269 		when the source image is used in multiple places.
270 	)
271 
272 	$(NOTE
273 		Users are usually better off to utilize the regular “source to target”
274 		functions with a reused pixel data buffer.
275 
276 		These functions do not serve as a performance optimization.
277 		Some of them might perform significantly worse than their regular
278 		variant. Always benchmark and profile.
279 	)
280 
281 	---
282 	image.flipHorizontallyInPlace();
283 	---
284 
285 	---
286 	const cropOffset = Point(0, 0);
287 	const cropSize = Size(100, 100);
288 
289 	image = image.cropInPlace(cropSize, cropOffset);
290 	---
291 
292 
293 	#### Compute size of target
294 
295 	Functions to “compute (the) dimensions of (a) target” are primarily meant
296 	to be utilized to calculate the size for allocating new pixmaps to be used
297 	as a target for manipulation functions.
298 
299 	They are provided for all manipulation functions even in cases where they
300 	are provide little to no benefit. This is for consistency and to ease
301 	development.
302 
303 	Such functions are identified by a `-CalcDims` suffix;
304 	e.g. [flipHorizontallyCalcDims] or [cropCalcDims].
305 
306 	They usually take the same parameters as their corresponding
307 	“source to new target” function. This does not apply in cases where
308 	certain parameters are irrelevant for the computation of the target size.
309  +/
310 module arsd.pixmappaint;
311 
312 import arsd.color;
313 import arsd.core;
314 
315 private float roundImpl(float f) {
316 	import std.math : round;
317 
318 	return round(f);
319 }
320 
321 // `pure` rounding function.
322 // std.math.round() isn’t pure on all targets.
323 // → <https://issues.dlang.org/show_bug.cgi?id=11320>
324 private float round(float f) pure @nogc nothrow @trusted {
325 	return (castTo!(float function(float) pure @nogc nothrow)(&roundImpl))(f);
326 }
327 
328 /*
329 	## TODO:
330 
331 	- Refactoring the template-mess of blendPixel() & co.
332 	- Scaling
333 	- Rotating
334 	- Skewing
335 	- HSL
336 	- Advanced blend modes (maybe)
337  */
338 
339 ///
340 alias Color = arsd.color.Color;
341 
342 ///
343 alias ColorF = arsd.color.ColorF;
344 
345 ///
346 alias Pixel = Color;
347 
348 ///
349 alias Point = arsd.color.Point;
350 
351 ///
352 alias Rectangle = arsd.color.Rectangle;
353 
354 ///
355 alias Size = arsd.color.Size;
356 
357 // verify assumption(s)
358 static assert(Pixel.sizeof == uint.sizeof);
359 
360 @safe pure nothrow @nogc {
361 	///
362 	Pixel rgba(ubyte r, ubyte g, ubyte b, ubyte a = 0xFF) {
363 		return Pixel(r, g, b, a);
364 	}
365 
366 	///
367 	Pixel rgba(ubyte r, ubyte g, ubyte b, float aPct) {
368 		return Pixel(r, g, b, percentageDecimalToUInt8(aPct));
369 	}
370 
371 	///
372 	Pixel rgb(ubyte r, ubyte g, ubyte b) {
373 		return rgba(r, g, b, 0xFF);
374 	}
375 }
376 
377 /++
378 	$(I Advanced functionality.)
379 
380 	Meta data for the construction of a Pixmap.
381  +/
382 struct PixmapBlueprint {
383 	/++
384 		Total number of pixels stored in a Pixmap.
385 	 +/
386 	size_t length;
387 
388 	/++
389 		Width of a Pixmap.
390 	 +/
391 	int width;
392 
393 @safe pure nothrow @nogc:
394 
395 	///
396 	public static PixmapBlueprint fromSize(const Size size) {
397 		return PixmapBlueprint(
398 			size.area,
399 			size.width,
400 		);
401 	}
402 
403 	///
404 	public static PixmapBlueprint fromPixmap(const Pixmap pixmap) {
405 		return PixmapBlueprint(
406 			pixmap.length,
407 			pixmap.width,
408 		);
409 	}
410 
411 	/++
412 		Determines whether the blueprint is plausible.
413 	 +/
414 	bool isValid() const {
415 		return ((length % width) == 0);
416 	}
417 
418 	/++
419 		Height of a Pixmap.
420 
421 		See_also:
422 			This is the counterpart to the dimension known as [width].
423 	 +/
424 	int height() const {
425 		return castTo!int(length / width);
426 	}
427 
428 	///
429 	Size size() const {
430 		return Size(width, height);
431 	}
432 }
433 
434 /++
435 	Pixel data container
436  +/
437 struct Pixmap {
438 
439 	/// Pixel data
440 	Pixel[] data;
441 
442 	/// Pixel per row
443 	int width;
444 
445 @safe pure nothrow:
446 
447 	///
448 	deprecated("Use `Pixmap.makeNew(size)` instead.")
449 	this(Size size) {
450 		this.size = size;
451 	}
452 
453 	///
454 	deprecated("Use `Pixmap.makeNew(Size(width, height))` instead.")
455 	this(int width, int height)
456 	in (width > 0)
457 	in (height > 0) {
458 		this(Size(width, height));
459 	}
460 
461 	///
462 	this(inout(Pixel)[] data, int width) inout @nogc
463 	in (data.length % width == 0) {
464 		this.data = data;
465 		this.width = width;
466 	}
467 
468 	///
469 	static Pixmap makeNew(PixmapBlueprint blueprint) {
470 		auto data = new Pixel[](blueprint.length);
471 		return Pixmap(data, blueprint.width);
472 	}
473 
474 	///
475 	static Pixmap makeNew(Size size) {
476 		return Pixmap.makeNew(PixmapBlueprint.fromSize(size));
477 	}
478 
479 	/++
480 		Creates a $(I deep copy) of the Pixmap
481 	 +/
482 	Pixmap clone() const {
483 		return Pixmap(
484 			this.data.dup,
485 			this.width,
486 		);
487 	}
488 
489 	/++
490 		Copies the pixel data to the target Pixmap.
491 
492 		Returns:
493 			A size-adjusted shallow copy of the input Pixmap overwritten
494 			with the image data of the SubPixmap.
495 
496 		$(PITFALL
497 			While the returned Pixmap utilizes the buffer provided by the input,
498 			the returned Pixmap might not exactly match the input.
499 
500 			Always use the returned Pixmap structure.
501 
502 			---
503 			// Same buffer, but new structure:
504 			auto pixmap2 = source.copyTo(pixmap);
505 
506 			// Alternatively, replace the old structure:
507 			pixmap = source.copyTo(pixmap);
508 			---
509 		)
510 	 +/
511 	Pixmap copyTo(Pixmap target) @nogc const {
512 		// Length adjustment
513 		const l = this.length;
514 		if (target.data.length < l) {
515 			assert(false, "The target Pixmap is too small.");
516 		} else if (target.data.length > l) {
517 			target.data = target.data[0 .. l];
518 		}
519 
520 		copyToImpl(target);
521 
522 		return target;
523 	}
524 
525 	private void copyToImpl(Pixmap target) @nogc const {
526 		target.data[] = this.data[];
527 	}
528 
529 	// undocumented: really shouldn’t be used.
530 	// carries the risks of `length` and `width` getting out of sync accidentally.
531 	deprecated("Use `size` instead.")
532 	void length(int value) {
533 		data.length = value;
534 	}
535 
536 	/++
537 		Changes the size of the buffer
538 
539 		Reallocates the underlying pixel array.
540 	 +/
541 	void size(Size value) {
542 		data.length = value.area;
543 		width = value.width;
544 	}
545 
546 	/// ditto
547 	void size(int totalPixels, int width)
548 	in (totalPixels % width == 0) {
549 		data.length = totalPixels;
550 		this.width = width;
551 	}
552 
553 	static {
554 		/++
555 			Creates a Pixmap wrapping the pixel data from the provided `TrueColorImage`.
556 
557 			Interoperability function: `arsd.color`
558 		 +/
559 		Pixmap fromTrueColorImage(TrueColorImage source) @nogc {
560 			return Pixmap(source.imageData.colors, source.width);
561 		}
562 
563 		/++
564 			Creates a Pixmap wrapping the pixel data from the provided `MemoryImage`.
565 
566 			Interoperability function: `arsd.color`
567 		 +/
568 		Pixmap fromMemoryImage(MemoryImage source) {
569 			return fromTrueColorImage(source.getAsTrueColorImage());
570 		}
571 	}
572 
573 @safe pure nothrow @nogc:
574 
575 	/// Height of the buffer, i.e. the number of lines
576 	int height() inout {
577 		if (width == 0) {
578 			return 0;
579 		}
580 
581 		return castTo!int(data.length / width);
582 	}
583 
584 	/// Rectangular size of the buffer
585 	Size size() inout {
586 		return Size(width, height);
587 	}
588 
589 	/// Length of the buffer, i.e. the number of pixels
590 	int length() inout {
591 		return castTo!int(data.length);
592 	}
593 
594 	/++
595 		Number of bytes per line
596 
597 		Returns:
598 			width × Pixel.sizeof
599 	 +/
600 	int pitch() inout {
601 		return (width * int(Pixel.sizeof));
602 	}
603 
604 	/++
605 		Adjusts the Pixmap according to the provided blueprint.
606 
607 		The blueprint must not be larger than the data buffer of the pixmap.
608 
609 		This function does not reallocate the pixel data buffer.
610 
611 		If the blueprint is larger than the data buffer of the pixmap,
612 		this will result in a bounds-check error if applicable.
613 	 +/
614 	void adjustTo(PixmapBlueprint blueprint) {
615 		debug assert(this.data.length >= blueprint.length);
616 		debug assert(blueprint.isValid);
617 		this.data = this.data[0 .. blueprint.length];
618 		this.width = blueprint.width;
619 	}
620 
621 	/++
622 		Calculates the index (linear offset) of the requested position
623 		within the pixmap data.
624 	+/
625 	int scanTo(Point pos) inout {
626 		return linearOffset(width, pos);
627 	}
628 
629 	/++
630 		Accesses the pixel at the requested position within the pixmap data.
631 	 +/
632 	ref inout(Pixel) scan(Point pos) inout {
633 		return data[scanTo(pos)];
634 	}
635 
636 	/++
637 		Retrieves a linear slice of the pixmap.
638 
639 		Returns:
640 			`n` pixels starting at the top-left position `pos`.
641 	 +/
642 	inout(Pixel)[] scan(Point pos, int n) inout {
643 		immutable size_t offset = linearOffset(width, pos);
644 		immutable size_t end = (offset + n);
645 		return data[offset .. end];
646 	}
647 
648 	/// ditto
649 	inout(Pixel)[] sliceAt(Point pos, int n) inout {
650 		return scan(pos, n);
651 	}
652 
653 	/++
654 		Retrieves a rectangular subimage of the pixmap.
655 	 +/
656 	inout(SubPixmap) scanArea(Point pos, Size size) inout {
657 		return inout(SubPixmap)(this, size, pos);
658 	}
659 
660 	/// TODO: remove
661 	deprecated alias scanSubPixmap = scanArea;
662 
663 	/// TODO: remove
664 	deprecated alias scan2D = scanArea;
665 
666 	/++
667 		Retrieves the first line of the Pixmap.
668 
669 		See_also:
670 			Check out [PixmapScanner] for more useful scanning functionality.
671 	 +/
672 	inout(Pixel)[] scanLine() inout {
673 		return data[0 .. width];
674 	}
675 
676 	public {
677 		/++
678 			Provides access to a single pixel at the requested 2D-position.
679 
680 			See_also:
681 				Accessing pixels through the [data] array will be more useful,
682 				usually.
683 		 +/
684 		ref inout(Pixel) accessPixel(Point pos) inout @system {
685 			const idx = linearOffset(pos, this.width);
686 			return this.data[idx];
687 		}
688 
689 		/// ditto
690 		Pixel getPixel(Point pos) const {
691 			const idx = linearOffset(pos, this.width);
692 			return this.data[idx];
693 		}
694 
695 		/// ditto
696 		Pixel getPixel(int x, int y) const {
697 			return this.getPixel(Point(x, y));
698 		}
699 
700 		/// ditto
701 		void setPixel(Point pos, Pixel value) {
702 			const idx = linearOffset(pos, this.width);
703 			this.data[idx] = value;
704 		}
705 
706 		/// ditto
707 		void setPixel(int x, int y, Pixel value) {
708 			return this.setPixel(Point(x, y), value);
709 		}
710 	}
711 
712 	/// Clears the buffer’s contents (by setting each pixel to the same color)
713 	void clear(Pixel value) {
714 		data[] = value;
715 	}
716 }
717 
718 /++
719 	A subpixmap represents a subimage of a [Pixmap].
720 
721 	This wrapper provides convenient access to a rectangular slice of a Pixmap.
722 
723 	```
724 	╔═════════════╗
725 	║ Pixmap      ║
726 	║             ║
727 	║      ┌───┐  ║
728 	║      │Sub│  ║
729 	║      └───┘  ║
730 	╚═════════════╝
731 	```
732  +/
733 struct SubPixmap {
734 
735 	/++
736 		Source image referenced by the subimage
737 	 +/
738 	Pixmap source;
739 
740 	/++
741 		Size of the subimage
742 	 +/
743 	Size size;
744 
745 	/++
746 		2D offset of the subimage
747 	 +/
748 	Point offset;
749 
750 	public @safe pure nothrow @nogc {
751 		///
752 		this(inout Pixmap source, Size size = Size(0, 0), Point offset = Point(0, 0)) inout {
753 			this.source = source;
754 			this.size = size;
755 			this.offset = offset;
756 		}
757 
758 		///
759 		this(inout Pixmap source, Point offset, Size size = Size(0, 0)) inout {
760 			this(source, size, offset);
761 		}
762 	}
763 
764 @safe pure nothrow:
765 
766 	public {
767 		/++
768 			Allocates a new Pixmap cropped to the pixel data of the subimage.
769 
770 			See_also:
771 				Use [extractToPixmap] for a non-allocating variant with an .
772 		 +/
773 		Pixmap extractToNewPixmap() const {
774 			auto pm = Pixmap.makeNew(size);
775 			this.extractToPixmap(pm);
776 			return pm;
777 		}
778 
779 		/++
780 			Copies the pixel data – cropped to the subimage region –
781 			into the target Pixmap.
782 
783 			$(PITFALL
784 				Do not attempt to extract a subimage back into the source pixmap.
785 				This will fail in cases where source and target regions overlap
786 				and potentially crash the program.
787 			)
788 
789 			Returns:
790 				A size-adjusted shallow copy of the input Pixmap overwritten
791 				with the image data of the SubPixmap.
792 
793 			$(PITFALL
794 				While the returned Pixmap utilizes the buffer provided by the input,
795 				the returned Pixmap might not exactly match the input.
796 				The dimensions (width and height) and the length might have changed.
797 
798 				Always use the returned Pixmap structure.
799 
800 				---
801 				// Same buffer, but new structure:
802 				auto pixmap2 = subPixmap.extractToPixmap(pixmap);
803 
804 				// Alternatively, replace the old structure:
805 				pixmap = subPixmap.extractToPixmap(pixmap);
806 				---
807 			)
808 		 +/
809 		Pixmap extractToPixmap(Pixmap target) @nogc const {
810 			// Length adjustment
811 			const l = this.length;
812 			if (target.data.length < l) {
813 				assert(false, "The target Pixmap is too small.");
814 			} else if (target.data.length > l) {
815 				target.data = target.data[0 .. l];
816 			}
817 
818 			target.width = this.width;
819 
820 			extractToPixmapCopyImpl(target);
821 			return target;
822 		}
823 
824 		private void extractToPixmapCopyImpl(Pixmap target) @nogc const {
825 			auto src = SubPixmapScanner(this);
826 			auto dst = PixmapScannerRW(target);
827 
828 			foreach (dstLine; dst) {
829 				dstLine[] = src.front[];
830 				src.popFront();
831 			}
832 		}
833 
834 		private void extractToPixmapCopyPixelByPixelImpl(Pixmap target) @nogc const {
835 			auto src = SubPixmapScanner(this);
836 			auto dst = PixmapScannerRW(target);
837 
838 			foreach (dstLine; dst) {
839 				const srcLine = src.front;
840 				foreach (idx, ref px; dstLine) {
841 					px = srcLine[idx];
842 				}
843 				src.popFront();
844 			}
845 		}
846 	}
847 
848 @safe pure nothrow @nogc:
849 
850 	public {
851 		/++
852 			Width of the subimage.
853 		 +/
854 		int width() const {
855 			return size.width;
856 		}
857 
858 		/// ditto
859 		void width(int value) {
860 			size.width = value;
861 		}
862 
863 		/++
864 			Height of the subimage.
865 		 +/
866 		int height() const {
867 			return size.height;
868 		}
869 
870 		/// ditto
871 		void height(int value) {
872 			size.height = value;
873 		}
874 
875 		/++
876 			Number of pixels in the subimage.
877 		 +/
878 		int length() const {
879 			return size.area;
880 		}
881 	}
882 
883 	public {
884 		/++
885 			Linear offset of the subimage within the source image.
886 
887 			Calculates the index of the “first pixel of the subimage”
888 			in the “pixel data of the source image”.
889 		 +/
890 		int sourceOffsetLinear() const {
891 			return linearOffset(offset, source.width);
892 		}
893 
894 		/// ditto
895 		void sourceOffsetLinear(int value) {
896 			this.offset = Point.fromLinearOffset(value, source.width);
897 		}
898 
899 		/++
900 			$(I Advanced functionality.)
901 
902 			Offset of the pixel following the bottom right corner of the subimage.
903 
904 			(`Point(O, 0)` is the top left corner of the source image.)
905 		 +/
906 		Point sourceOffsetEnd() const {
907 			auto vec = Point(size.width, (size.height - 1));
908 			return (offset + vec);
909 		}
910 
911 		/++
912 			Linear offset of the subimage within the source image.
913 
914 			Calculates the index of the “first pixel of the subimage”
915 			in the “pixel data of the source image”.
916 		 +/
917 		int sourceOffsetLinearEnd() const {
918 			return linearOffset(sourceOffsetEnd, source.width);
919 		}
920 	}
921 
922 	/++
923 		Determines whether the area of the subimage
924 		lies within the source image
925 		and does not overflow its lines.
926 
927 		$(TIP
928 			If the offset and/or size of a subimage are off, two issues can occur:
929 
930 			$(LIST
931 				* The resulting subimage will look displaced.
932 				  (As if the lines were shifted.)
933 				  This indicates that one scanline of the subimage spans over
934 				  two ore more lines of the source image.
935 				  (Happens when `(subimage.offset.x + subimage.size.width) > source.size.width`.)
936 				* When accessing the pixel data, bounds checks will fail.
937 				  This suggests that the area of the subimage extends beyond
938 				  the bottom end (and optionally also beyond the right end) of
939 				  the source.
940 			)
941 
942 			Both defects could indicate an invalid subimage.
943 			Use this function to verify the SubPixmap.
944 		)
945 
946 		$(WARNING
947 			Do not use invalid SubPixmaps.
948 			The library assumes that the SubPixmaps it receives are always valid.
949 
950 			Non-valid SubPixmaps are not meant to be used for creative effects
951 			or similar either. Such uses might lead to unexpected quirks or
952 			crashes eventually.
953 		)
954 	 +/
955 	bool isValid() const {
956 		return (
957 			(sourceMarginLeft >= 0)
958 				&& (sourceMarginTop >= 0)
959 				&& (sourceMarginBottom >= 0)
960 				&& (sourceMarginRight >= 0)
961 		);
962 	}
963 
964 	public inout {
965 		/++
966 			Retrieves the pixel at the requested position of the subimage.
967 		 +/
968 		ref inout(Pixel) scan(Point pos) {
969 			return source.scan(offset + pos);
970 		}
971 
972 		/++
973 			Retrieves the first line of the subimage.
974 		 +/
975 		inout(Pixel)[] scanLine() {
976 			const lo = linearOffset(offset, size.width);
977 			return source.data[lo .. size.width];
978 		}
979 	}
980 
981 	/++
982 		Copies the pixels of this subimage to a target image.
983 
984 		The target MUST have the same size.
985 
986 		See_also:
987 			Usually you’ll want to use [extractToPixmap] or [drawPixmap] instead.
988 	 +/
989 	public void xferTo(SubPixmap target) const {
990 		debug assert(target.size == this.size);
991 
992 		auto src = SubPixmapScanner(this);
993 		auto dst = SubPixmapScannerRW(target);
994 
995 		foreach (dstLine; dst) {
996 			dstLine[] = src.front[];
997 			src.popFront();
998 		}
999 	}
1000 
1001 	/++
1002 		Blends the pixels of this subimage into a target image.
1003 
1004 		The target MUST have the same size.
1005 
1006 		See_also:
1007 			Usually you’ll want to use [extractToPixmap] or [drawPixmap] instead.
1008 	 +/
1009 	public void xferTo(SubPixmap target, Blend blend) const {
1010 		debug assert(target.size == this.size);
1011 
1012 		auto src = SubPixmapScanner(this);
1013 		auto dst = SubPixmapScannerRW(target);
1014 
1015 		foreach (dstLine; dst) {
1016 			blendPixels(dstLine, src.front, blend);
1017 			src.popFront();
1018 		}
1019 	}
1020 
1021 	// opposite offset
1022 	public const {
1023 		/++
1024 			$(I Advanced functionality.)
1025 
1026 			Offset of the bottom right corner of the source image
1027 			to the bottom right corner of the subimage.
1028 
1029 			```
1030 			╔═══════════╗
1031 			║           ║
1032 			║   ┌───┐   ║
1033 			║   │   │   ║
1034 			║   └───┘   ║
1035 			║         ↘ ║
1036 			╚═══════════╝
1037 			```
1038 		 +/
1039 		Point oppositeOffset() {
1040 			return Point(oppositeOffsetX, oppositeOffsetY);
1041 		}
1042 
1043 		/++
1044 			$(I Advanced functionality.)
1045 
1046 			Offset of the right edge of the source image
1047 			to the right edge of the subimage.
1048 
1049 			```
1050 			╔═══════════╗
1051 			║           ║
1052 			║   ┌───┐   ║
1053 			║   │ S │ → ║
1054 			║   └───┘   ║
1055 			║           ║
1056 			╚═══════════╝
1057 			```
1058 		 +/
1059 		int oppositeOffsetX() {
1060 			return (offset.x + size.width);
1061 		}
1062 
1063 		/++
1064 			$(I Advanced functionality.)
1065 
1066 			Offset of the bottom edge of the source image
1067 			to the bottom edge of the subimage.
1068 
1069 			```
1070 			╔═══════════╗
1071 			║           ║
1072 			║   ┌───┐   ║
1073 			║   │ S │   ║
1074 			║   └───┘   ║
1075 			║     ↓     ║
1076 			╚═══════════╝
1077 			```
1078 		 +/
1079 		int oppositeOffsetY() {
1080 			return (offset.y + size.height);
1081 		}
1082 
1083 	}
1084 
1085 	// source-image margins
1086 	public const {
1087 		/++
1088 			$(I Advanced functionality.)
1089 
1090 			X-axis margin (left + right) of the subimage within the source image.
1091 
1092 			```
1093 			╔═══════════╗
1094 			║           ║
1095 			║   ┌───┐   ║
1096 			║ ↔ │ S │ ↔ ║
1097 			║   └───┘   ║
1098 			║           ║
1099 			╚═══════════╝
1100 			```
1101 		 +/
1102 		int sourceMarginX() {
1103 			return (source.width - size.width);
1104 		}
1105 
1106 		/++
1107 			$(I Advanced functionality.)
1108 
1109 			Y-axis margin (top + bottom) of the subimage within the source image.
1110 
1111 			```
1112 			╔═══════════╗
1113 			║     ↕     ║
1114 			║   ┌───┐   ║
1115 			║   │ S │   ║
1116 			║   └───┘   ║
1117 			║     ↕     ║
1118 			╚═══════════╝
1119 			```
1120 		 +/
1121 		int sourceMarginY() {
1122 			return (source.height - size.height);
1123 		}
1124 
1125 		/++
1126 			$(I Advanced functionality.)
1127 
1128 			Top margin of the subimage within the source image.
1129 
1130 			```
1131 			╔═══════════╗
1132 			║     ↕     ║
1133 			║   ┌───┐   ║
1134 			║   │ S │   ║
1135 			║   └───┘   ║
1136 			║           ║
1137 			╚═══════════╝
1138 			```
1139 		 +/
1140 		int sourceMarginTop() {
1141 			return offset.y;
1142 		}
1143 
1144 		/++
1145 			$(I Advanced functionality.)
1146 
1147 			Right margin of the subimage within the source image.
1148 
1149 			```
1150 			╔═══════════╗
1151 			║           ║
1152 			║   ┌───┐   ║
1153 			║   │ S │ ↔ ║
1154 			║   └───┘   ║
1155 			║           ║
1156 			╚═══════════╝
1157 			```
1158 		 +/
1159 		int sourceMarginRight() {
1160 			return (sourceMarginX - sourceMarginLeft);
1161 		}
1162 
1163 		/++
1164 			$(I Advanced functionality.)
1165 
1166 			Bottom margin of the subimage within the source image.
1167 
1168 			```
1169 			╔═══════════╗
1170 			║           ║
1171 			║   ┌───┐   ║
1172 			║   │ S │   ║
1173 			║   └───┘   ║
1174 			║     ↕     ║
1175 			╚═══════════╝
1176 			```
1177 		 +/
1178 		int sourceMarginBottom() {
1179 			return (sourceMarginY - sourceMarginTop);
1180 		}
1181 
1182 		/++
1183 			$(I Advanced functionality.)
1184 
1185 			Left margin of the subimage within the source image.
1186 
1187 			```
1188 			╔═══════════╗
1189 			║           ║
1190 			║   ┌───┐   ║
1191 			║ ↔ │ S │   ║
1192 			║   └───┘   ║
1193 			║           ║
1194 			╚═══════════╝
1195 			```
1196 		 +/
1197 		int sourceMarginLeft() {
1198 			return offset.x;
1199 		}
1200 	}
1201 
1202 	public const {
1203 		/++
1204 			$(I Advanced functionality.)
1205 
1206 			Calculates the linear offset of the provided point in the subimage
1207 			relative to the source image.
1208 		 +/
1209 		int sourceOffsetOf(Point pos) {
1210 			pos = (pos + offset);
1211 			return linearOffset(pos, source.width);
1212 		}
1213 	}
1214 }
1215 
1216 /++
1217 	$(I Advanced functionality.)
1218 
1219 	Wrapper for scanning a [Pixmap] line by line.
1220  +/
1221 struct PixmapScanner {
1222 	private {
1223 		const(Pixel)[] _data;
1224 		int _width;
1225 	}
1226 
1227 @safe pure nothrow @nogc:
1228 
1229 	///
1230 	public this(const(Pixmap) pixmap) {
1231 		_data = pixmap.data;
1232 		_width = pixmap.width;
1233 	}
1234 
1235 	///
1236 	typeof(this) save() {
1237 		return this;
1238 	}
1239 
1240 	///
1241 	bool empty() const {
1242 		return (_data.length == 0);
1243 	}
1244 
1245 	///
1246 	const(Pixel)[] front() const {
1247 		return _data[0 .. _width];
1248 	}
1249 
1250 	///
1251 	void popFront() {
1252 		_data = _data[_width .. $];
1253 	}
1254 
1255 	///
1256 	const(Pixel)[] back() const {
1257 		return _data[($ - _width) .. $];
1258 	}
1259 
1260 	///
1261 	void popBack() {
1262 		_data = _data[0 .. ($ - _width)];
1263 	}
1264 }
1265 
1266 /++
1267 	$(I Advanced functionality.)
1268 
1269 	Wrapper for scanning a [Pixmap] line by line.
1270 
1271 	See_also:
1272 		Unlike [PixmapScanner], this does not work with `const(Pixmap)`.
1273  +/
1274 struct PixmapScannerRW {
1275 	private {
1276 		Pixel[] _data;
1277 		int _width;
1278 	}
1279 
1280 @safe pure nothrow @nogc:
1281 
1282 	///
1283 	public this(Pixmap pixmap) {
1284 		_data = pixmap.data;
1285 		_width = pixmap.width;
1286 	}
1287 
1288 	///
1289 	typeof(this) save() {
1290 		return this;
1291 	}
1292 
1293 	///
1294 	bool empty() const {
1295 		return (_data.length == 0);
1296 	}
1297 
1298 	///
1299 	Pixel[] front() {
1300 		return _data[0 .. _width];
1301 	}
1302 
1303 	///
1304 	void popFront() {
1305 		_data = _data[_width .. $];
1306 	}
1307 
1308 	///
1309 	Pixel[] back() {
1310 		return _data[($ - _width) .. $];
1311 	}
1312 
1313 	///
1314 	void popBack() {
1315 		_data = _data[0 .. ($ - _width)];
1316 	}
1317 }
1318 
1319 /++
1320 	$(I Advanced functionality.)
1321 
1322 	Wrapper for scanning a [Pixmap] line by line.
1323  +/
1324 struct SubPixmapScanner {
1325 	private {
1326 		const(Pixel)[] _data;
1327 		int _width;
1328 		int _feed;
1329 	}
1330 
1331 @safe pure nothrow @nogc:
1332 
1333 	///
1334 	public this(const(SubPixmap) subPixmap) {
1335 		_data = subPixmap.source.data[subPixmap.sourceOffsetLinear .. subPixmap.sourceOffsetLinearEnd];
1336 		_width = subPixmap.size.width;
1337 		_feed = subPixmap.source.width;
1338 	}
1339 
1340 	///
1341 	typeof(this) save() {
1342 		return this;
1343 	}
1344 
1345 	///
1346 	bool empty() const {
1347 		return (_data.length == 0);
1348 	}
1349 
1350 	///
1351 	const(Pixel)[] front() const {
1352 		return _data[0 .. _width];
1353 	}
1354 
1355 	///
1356 	void popFront() {
1357 		if (_data.length < _feed) {
1358 			_data.length = 0;
1359 			return;
1360 		}
1361 
1362 		_data = _data[_feed .. $];
1363 	}
1364 
1365 	///
1366 	const(Pixel)[] back() const {
1367 		return _data[($ - _width) .. $];
1368 	}
1369 
1370 	///
1371 	void popBack() {
1372 		if (_data.length < _feed) {
1373 			_data.length = 0;
1374 			return;
1375 		}
1376 
1377 		_data = _data[0 .. ($ - _feed)];
1378 	}
1379 }
1380 
1381 /++
1382 	$(I Advanced functionality.)
1383 
1384 	Wrapper for scanning a [Pixmap] line by line.
1385 
1386 	See_also:
1387 		Unlike [SubPixmapScanner], this does not work with `const(SubPixmap)`.
1388  +/
1389 struct SubPixmapScannerRW {
1390 	private {
1391 		Pixel[] _data;
1392 		int _width;
1393 		int _feed;
1394 	}
1395 
1396 @safe pure nothrow @nogc:
1397 
1398 	///
1399 	public this(SubPixmap subPixmap) {
1400 		_data = subPixmap.source.data[subPixmap.sourceOffsetLinear .. subPixmap.sourceOffsetLinearEnd];
1401 		_width = subPixmap.size.width;
1402 		_feed = subPixmap.source.width;
1403 	}
1404 
1405 	///
1406 	typeof(this) save() {
1407 		return this;
1408 	}
1409 
1410 	///
1411 	bool empty() const {
1412 		return (_data.length == 0);
1413 	}
1414 
1415 	///
1416 	Pixel[] front() {
1417 		return _data[0 .. _width];
1418 	}
1419 
1420 	///
1421 	void popFront() {
1422 		if (_data.length < _feed) {
1423 			_data.length = 0;
1424 			return;
1425 		}
1426 
1427 		_data = _data[_feed .. $];
1428 	}
1429 
1430 	///
1431 	Pixel[] back() {
1432 		return _data[($ - _width) .. $];
1433 	}
1434 
1435 	///
1436 	void popBack() {
1437 		if (_data.length < _feed) {
1438 			_data.length = 0;
1439 			return;
1440 		}
1441 
1442 		_data = _data[0 .. ($ - _feed)];
1443 	}
1444 }
1445 
1446 ///
1447 struct SpriteSheet {
1448 	private {
1449 		Pixmap _pixmap;
1450 		Size _spriteDimensions;
1451 		Size _layout; // pre-computed upon construction
1452 	}
1453 
1454 @safe pure nothrow @nogc:
1455 
1456 	///
1457 	public this(Pixmap pixmap, Size spriteSize) {
1458 		_pixmap = pixmap;
1459 		_spriteDimensions = spriteSize;
1460 
1461 		_layout = Size(
1462 			_pixmap.width / _spriteDimensions.width,
1463 			_pixmap.height / _spriteDimensions.height,
1464 		);
1465 	}
1466 
1467 	///
1468 	inout(Pixmap) pixmap() inout {
1469 		return _pixmap;
1470 	}
1471 
1472 	///
1473 	Size spriteSize() inout {
1474 		return _spriteDimensions;
1475 	}
1476 
1477 	///
1478 	Size layout() inout {
1479 		return _layout;
1480 	}
1481 
1482 	///
1483 	Point getSpriteColumn(int index) inout {
1484 		immutable x = index % layout.width;
1485 		immutable y = (index - x) / layout.height;
1486 		return Point(x, y);
1487 	}
1488 
1489 	///
1490 	Point getSpritePixelOffset2D(int index) inout {
1491 		immutable col = this.getSpriteColumn(index);
1492 		return Point(
1493 			col.x * _spriteDimensions.width,
1494 			col.y * _spriteDimensions.height,
1495 		);
1496 	}
1497 }
1498 
1499 // Silly micro-optimization
1500 private struct OriginRectangle {
1501 	Size size;
1502 
1503 @safe pure nothrow @nogc:
1504 
1505 	int left() const => 0;
1506 	int top() const => 0;
1507 	int right() const => size.width;
1508 	int bottom() const => size.height;
1509 
1510 	bool intersect(const Rectangle b) const {
1511 		// dfmt off
1512 		return (
1513 			(b.right    > 0          ) &&
1514 			(b.left     < this.right ) &&
1515 			(b.bottom   > 0          ) &&
1516 			(b.top      < this.bottom)
1517 		);
1518 		// dfmt on
1519 	}
1520 }
1521 
1522 @safe pure nothrow:
1523 
1524 // misc
1525 private @nogc {
1526 	Point pos(Rectangle r) => r.upperLeft;
1527 
1528 	T max(T)(T a, T b) => (a >= b) ? a : b;
1529 	T min(T)(T a, T b) => (a <= b) ? a : b;
1530 }
1531 
1532 /++
1533 	Calculates the square root
1534 	of an integer number
1535 	as an integer number.
1536  +/
1537 ubyte intSqrt(const ubyte value) @safe pure nothrow @nogc {
1538 	switch (value) {
1539 	default:
1540 		// unreachable
1541 		assert(false, "ubyte != uint8");
1542 	case 0:
1543 		return 0;
1544 	case 1: .. case 2:
1545 		return 1;
1546 	case 3: .. case 6:
1547 		return 2;
1548 	case 7: .. case 12:
1549 		return 3;
1550 	case 13: .. case 20:
1551 		return 4;
1552 	case 21: .. case 30:
1553 		return 5;
1554 	case 31: .. case 42:
1555 		return 6;
1556 	case 43: .. case 56:
1557 		return 7;
1558 	case 57: .. case 72:
1559 		return 8;
1560 	case 73: .. case 90:
1561 		return 9;
1562 	case 91: .. case 110:
1563 		return 10;
1564 	case 111: .. case 132:
1565 		return 11;
1566 	case 133: .. case 156:
1567 		return 12;
1568 	case 157: .. case 182:
1569 		return 13;
1570 	case 183: .. case 210:
1571 		return 14;
1572 	case 211: .. case 240:
1573 		return 15;
1574 	case 241: .. case 255:
1575 		return 16;
1576 	}
1577 }
1578 
1579 ///
1580 unittest {
1581 	assert(intSqrt(4) == 2);
1582 	assert(intSqrt(9) == 3);
1583 	assert(intSqrt(10) == 3);
1584 }
1585 
1586 unittest {
1587 	import std.math : round, sqrt;
1588 
1589 	foreach (n; ubyte.min .. ubyte.max + 1) {
1590 		ubyte fp = sqrt(float(n)).round().castTo!ubyte;
1591 		ubyte i8 = intSqrt(n.castTo!ubyte);
1592 		assert(fp == i8);
1593 	}
1594 }
1595 
1596 /++
1597 	Calculates the square root
1598 	of the normalized value
1599 	representated by the input integer number.
1600 
1601 	Normalization:
1602 		`[0x00 .. 0xFF]` → `[0.0 .. 1.0]`
1603 
1604 	Returns:
1605 		sqrt(value / 255f) * 255
1606  +/
1607 ubyte intNormalizedSqrt(const ubyte value) @nogc {
1608 	switch (value) {
1609 	default:
1610 		// unreachable
1611 		assert(false, "ubyte != uint8");
1612 	case 0x00:
1613 		return 0x00;
1614 	case 0x01:
1615 		return 0x10;
1616 	case 0x02:
1617 		return 0x17;
1618 	case 0x03:
1619 		return 0x1C;
1620 	case 0x04:
1621 		return 0x20;
1622 	case 0x05:
1623 		return 0x24;
1624 	case 0x06:
1625 		return 0x27;
1626 	case 0x07:
1627 		return 0x2A;
1628 	case 0x08:
1629 		return 0x2D;
1630 	case 0x09:
1631 		return 0x30;
1632 	case 0x0A:
1633 		return 0x32;
1634 	case 0x0B:
1635 		return 0x35;
1636 	case 0x0C:
1637 		return 0x37;
1638 	case 0x0D:
1639 		return 0x3A;
1640 	case 0x0E:
1641 		return 0x3C;
1642 	case 0x0F:
1643 		return 0x3E;
1644 	case 0x10:
1645 		return 0x40;
1646 	case 0x11:
1647 		return 0x42;
1648 	case 0x12:
1649 		return 0x44;
1650 	case 0x13:
1651 		return 0x46;
1652 	case 0x14:
1653 		return 0x47;
1654 	case 0x15:
1655 		return 0x49;
1656 	case 0x16:
1657 		return 0x4B;
1658 	case 0x17:
1659 		return 0x4D;
1660 	case 0x18:
1661 		return 0x4E;
1662 	case 0x19:
1663 		return 0x50;
1664 	case 0x1A:
1665 		return 0x51;
1666 	case 0x1B:
1667 		return 0x53;
1668 	case 0x1C:
1669 		return 0x54;
1670 	case 0x1D:
1671 		return 0x56;
1672 	case 0x1E:
1673 		return 0x57;
1674 	case 0x1F:
1675 		return 0x59;
1676 	case 0x20:
1677 		return 0x5A;
1678 	case 0x21:
1679 		return 0x5C;
1680 	case 0x22:
1681 		return 0x5D;
1682 	case 0x23:
1683 		return 0x5E;
1684 	case 0x24:
1685 		return 0x60;
1686 	case 0x25:
1687 		return 0x61;
1688 	case 0x26:
1689 		return 0x62;
1690 	case 0x27:
1691 		return 0x64;
1692 	case 0x28:
1693 		return 0x65;
1694 	case 0x29:
1695 		return 0x66;
1696 	case 0x2A:
1697 		return 0x67;
1698 	case 0x2B:
1699 		return 0x69;
1700 	case 0x2C:
1701 		return 0x6A;
1702 	case 0x2D:
1703 		return 0x6B;
1704 	case 0x2E:
1705 		return 0x6C;
1706 	case 0x2F:
1707 		return 0x6D;
1708 	case 0x30:
1709 		return 0x6F;
1710 	case 0x31:
1711 		return 0x70;
1712 	case 0x32:
1713 		return 0x71;
1714 	case 0x33:
1715 		return 0x72;
1716 	case 0x34:
1717 		return 0x73;
1718 	case 0x35:
1719 		return 0x74;
1720 	case 0x36:
1721 		return 0x75;
1722 	case 0x37:
1723 		return 0x76;
1724 	case 0x38:
1725 		return 0x77;
1726 	case 0x39:
1727 		return 0x79;
1728 	case 0x3A:
1729 		return 0x7A;
1730 	case 0x3B:
1731 		return 0x7B;
1732 	case 0x3C:
1733 		return 0x7C;
1734 	case 0x3D:
1735 		return 0x7D;
1736 	case 0x3E:
1737 		return 0x7E;
1738 	case 0x3F:
1739 		return 0x7F;
1740 	case 0x40:
1741 		return 0x80;
1742 	case 0x41:
1743 		return 0x81;
1744 	case 0x42:
1745 		return 0x82;
1746 	case 0x43:
1747 		return 0x83;
1748 	case 0x44:
1749 		return 0x84;
1750 	case 0x45:
1751 		return 0x85;
1752 	case 0x46:
1753 		return 0x86;
1754 	case 0x47: .. case 0x48:
1755 		return 0x87;
1756 	case 0x49:
1757 		return 0x88;
1758 	case 0x4A:
1759 		return 0x89;
1760 	case 0x4B:
1761 		return 0x8A;
1762 	case 0x4C:
1763 		return 0x8B;
1764 	case 0x4D:
1765 		return 0x8C;
1766 	case 0x4E:
1767 		return 0x8D;
1768 	case 0x4F:
1769 		return 0x8E;
1770 	case 0x50:
1771 		return 0x8F;
1772 	case 0x51:
1773 		return 0x90;
1774 	case 0x52: .. case 0x53:
1775 		return 0x91;
1776 	case 0x54:
1777 		return 0x92;
1778 	case 0x55:
1779 		return 0x93;
1780 	case 0x56:
1781 		return 0x94;
1782 	case 0x57:
1783 		return 0x95;
1784 	case 0x58:
1785 		return 0x96;
1786 	case 0x59: .. case 0x5A:
1787 		return 0x97;
1788 	case 0x5B:
1789 		return 0x98;
1790 	case 0x5C:
1791 		return 0x99;
1792 	case 0x5D:
1793 		return 0x9A;
1794 	case 0x5E:
1795 		return 0x9B;
1796 	case 0x5F: .. case 0x60:
1797 		return 0x9C;
1798 	case 0x61:
1799 		return 0x9D;
1800 	case 0x62:
1801 		return 0x9E;
1802 	case 0x63:
1803 		return 0x9F;
1804 	case 0x64: .. case 0x65:
1805 		return 0xA0;
1806 	case 0x66:
1807 		return 0xA1;
1808 	case 0x67:
1809 		return 0xA2;
1810 	case 0x68:
1811 		return 0xA3;
1812 	case 0x69: .. case 0x6A:
1813 		return 0xA4;
1814 	case 0x6B:
1815 		return 0xA5;
1816 	case 0x6C:
1817 		return 0xA6;
1818 	case 0x6D: .. case 0x6E:
1819 		return 0xA7;
1820 	case 0x6F:
1821 		return 0xA8;
1822 	case 0x70:
1823 		return 0xA9;
1824 	case 0x71: .. case 0x72:
1825 		return 0xAA;
1826 	case 0x73:
1827 		return 0xAB;
1828 	case 0x74:
1829 		return 0xAC;
1830 	case 0x75: .. case 0x76:
1831 		return 0xAD;
1832 	case 0x77:
1833 		return 0xAE;
1834 	case 0x78:
1835 		return 0xAF;
1836 	case 0x79: .. case 0x7A:
1837 		return 0xB0;
1838 	case 0x7B:
1839 		return 0xB1;
1840 	case 0x7C:
1841 		return 0xB2;
1842 	case 0x7D: .. case 0x7E:
1843 		return 0xB3;
1844 	case 0x7F:
1845 		return 0xB4;
1846 	case 0x80: .. case 0x81:
1847 		return 0xB5;
1848 	case 0x82:
1849 		return 0xB6;
1850 	case 0x83: .. case 0x84:
1851 		return 0xB7;
1852 	case 0x85:
1853 		return 0xB8;
1854 	case 0x86:
1855 		return 0xB9;
1856 	case 0x87: .. case 0x88:
1857 		return 0xBA;
1858 	case 0x89:
1859 		return 0xBB;
1860 	case 0x8A: .. case 0x8B:
1861 		return 0xBC;
1862 	case 0x8C:
1863 		return 0xBD;
1864 	case 0x8D: .. case 0x8E:
1865 		return 0xBE;
1866 	case 0x8F:
1867 		return 0xBF;
1868 	case 0x90: .. case 0x91:
1869 		return 0xC0;
1870 	case 0x92:
1871 		return 0xC1;
1872 	case 0x93: .. case 0x94:
1873 		return 0xC2;
1874 	case 0x95:
1875 		return 0xC3;
1876 	case 0x96: .. case 0x97:
1877 		return 0xC4;
1878 	case 0x98:
1879 		return 0xC5;
1880 	case 0x99: .. case 0x9A:
1881 		return 0xC6;
1882 	case 0x9B: .. case 0x9C:
1883 		return 0xC7;
1884 	case 0x9D:
1885 		return 0xC8;
1886 	case 0x9E: .. case 0x9F:
1887 		return 0xC9;
1888 	case 0xA0:
1889 		return 0xCA;
1890 	case 0xA1: .. case 0xA2:
1891 		return 0xCB;
1892 	case 0xA3: .. case 0xA4:
1893 		return 0xCC;
1894 	case 0xA5:
1895 		return 0xCD;
1896 	case 0xA6: .. case 0xA7:
1897 		return 0xCE;
1898 	case 0xA8:
1899 		return 0xCF;
1900 	case 0xA9: .. case 0xAA:
1901 		return 0xD0;
1902 	case 0xAB: .. case 0xAC:
1903 		return 0xD1;
1904 	case 0xAD:
1905 		return 0xD2;
1906 	case 0xAE: .. case 0xAF:
1907 		return 0xD3;
1908 	case 0xB0: .. case 0xB1:
1909 		return 0xD4;
1910 	case 0xB2:
1911 		return 0xD5;
1912 	case 0xB3: .. case 0xB4:
1913 		return 0xD6;
1914 	case 0xB5: .. case 0xB6:
1915 		return 0xD7;
1916 	case 0xB7:
1917 		return 0xD8;
1918 	case 0xB8: .. case 0xB9:
1919 		return 0xD9;
1920 	case 0xBA: .. case 0xBB:
1921 		return 0xDA;
1922 	case 0xBC:
1923 		return 0xDB;
1924 	case 0xBD: .. case 0xBE:
1925 		return 0xDC;
1926 	case 0xBF: .. case 0xC0:
1927 		return 0xDD;
1928 	case 0xC1: .. case 0xC2:
1929 		return 0xDE;
1930 	case 0xC3:
1931 		return 0xDF;
1932 	case 0xC4: .. case 0xC5:
1933 		return 0xE0;
1934 	case 0xC6: .. case 0xC7:
1935 		return 0xE1;
1936 	case 0xC8: .. case 0xC9:
1937 		return 0xE2;
1938 	case 0xCA:
1939 		return 0xE3;
1940 	case 0xCB: .. case 0xCC:
1941 		return 0xE4;
1942 	case 0xCD: .. case 0xCE:
1943 		return 0xE5;
1944 	case 0xCF: .. case 0xD0:
1945 		return 0xE6;
1946 	case 0xD1: .. case 0xD2:
1947 		return 0xE7;
1948 	case 0xD3:
1949 		return 0xE8;
1950 	case 0xD4: .. case 0xD5:
1951 		return 0xE9;
1952 	case 0xD6: .. case 0xD7:
1953 		return 0xEA;
1954 	case 0xD8: .. case 0xD9:
1955 		return 0xEB;
1956 	case 0xDA: .. case 0xDB:
1957 		return 0xEC;
1958 	case 0xDC: .. case 0xDD:
1959 		return 0xED;
1960 	case 0xDE: .. case 0xDF:
1961 		return 0xEE;
1962 	case 0xE0:
1963 		return 0xEF;
1964 	case 0xE1: .. case 0xE2:
1965 		return 0xF0;
1966 	case 0xE3: .. case 0xE4:
1967 		return 0xF1;
1968 	case 0xE5: .. case 0xE6:
1969 		return 0xF2;
1970 	case 0xE7: .. case 0xE8:
1971 		return 0xF3;
1972 	case 0xE9: .. case 0xEA:
1973 		return 0xF4;
1974 	case 0xEB: .. case 0xEC:
1975 		return 0xF5;
1976 	case 0xED: .. case 0xEE:
1977 		return 0xF6;
1978 	case 0xEF: .. case 0xF0:
1979 		return 0xF7;
1980 	case 0xF1: .. case 0xF2:
1981 		return 0xF8;
1982 	case 0xF3: .. case 0xF4:
1983 		return 0xF9;
1984 	case 0xF5: .. case 0xF6:
1985 		return 0xFA;
1986 	case 0xF7: .. case 0xF8:
1987 		return 0xFB;
1988 	case 0xF9: .. case 0xFA:
1989 		return 0xFC;
1990 	case 0xFB: .. case 0xFC:
1991 		return 0xFD;
1992 	case 0xFD: .. case 0xFE:
1993 		return 0xFE;
1994 	case 0xFF:
1995 		return 0xFF;
1996 	}
1997 }
1998 
1999 unittest {
2000 	import std.math : round, sqrt;
2001 
2002 	foreach (n; ubyte.min .. ubyte.max + 1) {
2003 		ubyte fp = (sqrt(n / 255.0f) * 255).round().castTo!ubyte;
2004 		ubyte i8 = intNormalizedSqrt(n.castTo!ubyte);
2005 		assert(fp == i8);
2006 	}
2007 }
2008 
2009 /++
2010 	Limits a value to a maximum of 0xFF (= 255).
2011  +/
2012 ubyte clamp255(Tint)(const Tint value) @nogc {
2013 	pragma(inline, true);
2014 	return (value < 0xFF) ? value.castTo!ubyte : 0xFF;
2015 }
2016 
2017 /++
2018 	Fast 8-bit “percentage” function
2019 
2020 	This function optimizes its runtime performance by substituting
2021 	the division by 255 with an approximation using bitshifts.
2022 
2023 	Nonetheless, its result are as accurate as a floating point
2024 	division with 64-bit precision.
2025 
2026 	Params:
2027 		nPercentage = percentage as the number of 255ths (“two hundred fifty-fifths”)
2028 		value = base value (“total”)
2029 
2030 	Returns:
2031 		`round(value * nPercentage / 255.0)`
2032  +/
2033 ubyte n255thsOf(const ubyte nPercentage, const ubyte value) @nogc {
2034 	immutable factor = (nPercentage | (nPercentage << 8));
2035 	return (((value * factor) + 0x8080) >> 16);
2036 }
2037 
2038 @safe unittest {
2039 	// Accuracy verification
2040 
2041 	static ubyte n255thsOfFP64(const ubyte nPercentage, const ubyte value) {
2042 		return (double(value) * double(nPercentage) / 255.0).round().castTo!ubyte();
2043 	}
2044 
2045 	for (int value = ubyte.min; value <= ubyte.max; ++value) {
2046 		for (int percent = ubyte.min; percent <= ubyte.max; ++percent) {
2047 			immutable v = cast(ubyte) value;
2048 			immutable p = cast(ubyte) percent;
2049 
2050 			immutable approximated = n255thsOf(p, v);
2051 			immutable precise = n255thsOfFP64(p, v);
2052 			assert(approximated == precise);
2053 		}
2054 	}
2055 }
2056 
2057 ///
2058 ubyte percentageDecimalToUInt8(const float decimal) @nogc
2059 in (decimal >= 0)
2060 in (decimal <= 1) {
2061 	return round(decimal * 255).castTo!ubyte;
2062 }
2063 
2064 ///
2065 float percentageUInt8ToDecimal(const ubyte n255ths) @nogc {
2066 	return (float(n255ths) / 255.0f);
2067 }
2068 
2069 // ==== Image manipulation functions ====
2070 
2071 /++
2072 	Lowers the opacity of a Pixel.
2073 
2074 	This function multiplies the opacity of the input
2075 	with the given percentage.
2076 
2077 	See_Also:
2078 		Use [decreaseOpacityF] with decimal opacity values in percent (%).
2079  +/
2080 Pixel decreaseOpacity(const Pixel source, ubyte opacityPercentage) @nogc {
2081 	return Pixel(
2082 		source.r,
2083 		source.g,
2084 		source.b,
2085 		opacityPercentage.n255thsOf(source.a),
2086 	);
2087 }
2088 
2089 /++
2090 	Lowers the opacity of a Pixel.
2091 
2092 	This function multiplies the opacity of the input
2093 	with the given percentage.
2094 
2095 	Value Range:
2096 		0.0 =   0%
2097 		1.0 = 100%
2098 
2099 	See_Also:
2100 		Use [opacity] with 8-bit integer opacity values (in 255ths).
2101  +/
2102 Pixel decreaseOpacityF(const Pixel source, float opacityPercentage) @nogc {
2103 	return decreaseOpacity(source, percentageDecimalToUInt8(opacityPercentage));
2104 }
2105 
2106 // Don’t get fooled by the name of this function.
2107 // It’s called like that for consistency reasons.
2108 private void decreaseOpacityInto(const Pixmap source, Pixmap target, ubyte opacityPercentage) @trusted @nogc {
2109 	debug assert(source.data.length == target.data.length);
2110 	foreach (idx, ref px; target.data) {
2111 		px = decreaseOpacity(source.data.ptr[idx], opacityPercentage);
2112 	}
2113 }
2114 
2115 /++
2116 	Lowers the opacity of a [Pixmap].
2117 
2118 	This operation updates the alpha-channel value of each pixel.
2119 	→ `alpha *= opacity`
2120 
2121 	See_Also:
2122 		Use [decreaseOpacityF] with decimal opacity values in percent (%).
2123  +/
2124 Pixmap decreaseOpacity(const Pixmap source, Pixmap target, ubyte opacityPercentage) @nogc {
2125 	target.adjustTo(source.decreaseOpacityCalcDims());
2126 	source.decreaseOpacityInto(target, opacityPercentage);
2127 	return target;
2128 }
2129 
2130 /// ditto
2131 Pixmap decreaseOpacityNew(const Pixmap source, ubyte opacityPercentage) {
2132 	auto target = Pixmap.makeNew(source.decreaseOpacityCalcDims());
2133 	source.decreaseOpacityInto(target, opacityPercentage);
2134 	return target;
2135 }
2136 
2137 /// ditto
2138 void decreaseOpacityInPlace(Pixmap source, ubyte opacityPercentage) @nogc {
2139 	foreach (ref px; source.data) {
2140 		px.a = opacityPercentage.n255thsOf(px.a);
2141 	}
2142 }
2143 
2144 /// ditto
2145 PixmapBlueprint decreaseOpacityCalcDims(const Pixmap source) @nogc {
2146 	return PixmapBlueprint.fromPixmap(source);
2147 }
2148 
2149 /++
2150 	Adjusts the opacity of a [Pixmap].
2151 
2152 	This operation updates the alpha-channel value of each pixel.
2153 	→ `alpha *= opacity`
2154 
2155 	See_Also:
2156 		Use [decreaseOpacity] with 8-bit integer opacity values (in 255ths).
2157  +/
2158 Pixmap decreaseOpacityF(const Pixmap source, Pixmap target, float opacityPercentage) @nogc {
2159 	return source.decreaseOpacity(target, percentageDecimalToUInt8(opacityPercentage));
2160 }
2161 
2162 /// ditto
2163 Pixmap decreaseOpacityFNew(const Pixmap source, float opacityPercentage) {
2164 	return source.decreaseOpacityNew(percentageDecimalToUInt8(opacityPercentage));
2165 }
2166 
2167 /// ditto
2168 void decreaseOpacityFInPlace(Pixmap source, float opacityPercentage) @nogc {
2169 	return source.decreaseOpacityInPlace(percentageDecimalToUInt8(opacityPercentage));
2170 }
2171 
2172 /// ditto
2173 PixmapBlueprint decreaseOpacityF(Pixmap source) @nogc {
2174 	return PixmapBlueprint.fromPixmap(source);
2175 }
2176 
2177 /++
2178 	Inverts a color (to its negative color).
2179  +/
2180 Pixel invert(const Pixel color) @nogc {
2181 	return Pixel(
2182 		0xFF - color.r,
2183 		0xFF - color.g,
2184 		0xFF - color.b,
2185 		color.a,
2186 	);
2187 }
2188 
2189 private void invertInto(const Pixmap source, Pixmap target) @trusted @nogc {
2190 	debug assert(source.length == target.length);
2191 	foreach (idx, ref px; target.data) {
2192 		px = invert(source.data.ptr[idx]);
2193 	}
2194 }
2195 
2196 /++
2197 	Inverts all colors to produce a $(I negative image).
2198 
2199 	$(TIP
2200 		Develops a positive image when applied to a negative one.
2201 	)
2202  +/
2203 Pixmap invert(const Pixmap source, Pixmap target) @nogc {
2204 	target.adjustTo(source.invertCalcDims());
2205 	source.invertInto(target);
2206 	return target;
2207 }
2208 
2209 /// ditto
2210 Pixmap invertNew(const Pixmap source) {
2211 	auto target = Pixmap.makeNew(source.invertCalcDims());
2212 	source.invertInto(target);
2213 	return target;
2214 }
2215 
2216 /// ditto
2217 void invertInPlace(Pixmap pixmap) @nogc {
2218 	foreach (ref px; pixmap.data) {
2219 		px = invert(px);
2220 	}
2221 }
2222 
2223 /// ditto
2224 PixmapBlueprint invertCalcDims(const Pixmap source) @nogc {
2225 	return PixmapBlueprint.fromPixmap(source);
2226 }
2227 
2228 /++
2229 	Crops an image and stores the result in the provided target Pixmap.
2230 
2231 	The size of the area to crop the image to
2232 	is derived from the size of the target.
2233 
2234 	---
2235 	// This function can be used to omit a redundant size parameter
2236 	// in cases like this:
2237 	target = crop(source, target, target.size, offset);
2238 
2239 	// → Instead do:
2240 	cropTo(source, target, offset);
2241 	---
2242  +/
2243 void cropTo(const Pixmap source, Pixmap target, Point offset = Point(0, 0)) @nogc {
2244 	auto src = const(SubPixmap)(source, target.size, offset);
2245 	src.extractToPixmapCopyImpl(target);
2246 }
2247 
2248 // consistency
2249 private alias cropInto = cropTo;
2250 
2251 /++
2252 	Crops an image to the provided size with the requested offset.
2253 
2254 	The target Pixmap must be big enough in length to hold the cropped image.
2255  +/
2256 Pixmap crop(const Pixmap source, Pixmap target, Size cropToSize, Point offset = Point(0, 0)) @nogc {
2257 	target.adjustTo(cropCalcDims(cropToSize));
2258 	cropInto(source, target, offset);
2259 	return target;
2260 }
2261 
2262 /// ditto
2263 Pixmap cropNew(const Pixmap source, Size cropToSize, Point offset = Point(0, 0)) {
2264 	auto target = Pixmap.makeNew(cropToSize);
2265 	cropInto(source, target, offset);
2266 	return target;
2267 }
2268 
2269 /// ditto
2270 Pixmap cropInPlace(Pixmap source, Size cropToSize, Point offset = Point(0, 0)) @nogc {
2271 	Pixmap target = source;
2272 	target.width = cropToSize.width;
2273 	target.data = target.data[0 .. cropToSize.area];
2274 
2275 	auto src = const(SubPixmap)(source, cropToSize, offset);
2276 	src.extractToPixmapCopyPixelByPixelImpl(target);
2277 	return target;
2278 }
2279 
2280 /// ditto
2281 PixmapBlueprint cropCalcDims(Size cropToSize) @nogc {
2282 	return PixmapBlueprint.fromSize(cropToSize);
2283 }
2284 
2285 private void transposeInto(const Pixmap source, Pixmap target) @nogc {
2286 	foreach (y; 0 .. target.width) {
2287 		foreach (x; 0 .. source.width) {
2288 			const idxSrc = linearOffset(Point(x, y), source.width);
2289 			const idxDst = linearOffset(Point(y, x), target.width);
2290 
2291 			target.data[idxDst] = source.data[idxSrc];
2292 		}
2293 	}
2294 }
2295 
2296 /++
2297 	Transposes an image.
2298 
2299 	```
2300 	╔══╗   ╔══╗
2301 	║# ║   ║#+║
2302 	║+x║ → ║ x║
2303 	╚══╝   ╚══╝
2304 	```
2305  +/
2306 Pixmap transpose(const Pixmap source, Pixmap target) @nogc {
2307 	target.adjustTo(source.transposeCalcDims());
2308 	source.transposeInto(target);
2309 	return target;
2310 }
2311 
2312 /// ditto
2313 Pixmap transposeNew(const Pixmap source) {
2314 	auto target = Pixmap.makeNew(source.transposeCalcDims());
2315 	source.transposeInto(target);
2316 	return target;
2317 }
2318 
2319 /// ditto
2320 PixmapBlueprint transposeCalcDims(const Pixmap source) @nogc {
2321 	return PixmapBlueprint(source.length, source.height);
2322 }
2323 
2324 private void rotateClockwiseInto(const Pixmap source, Pixmap target) @nogc {
2325 	const area = source.data.length;
2326 	const rowLength = source.size.height;
2327 	ptrdiff_t cursor = -1;
2328 
2329 	foreach (px; source.data) {
2330 		cursor += rowLength;
2331 		if (cursor > area) {
2332 			cursor -= (area + 1);
2333 		}
2334 
2335 		target.data[cursor] = px;
2336 	}
2337 }
2338 
2339 /++
2340 	Rotates an image by 90° clockwise.
2341 
2342 	```
2343 	╔══╗   ╔══╗
2344 	║# ║   ║+#║
2345 	║+x║ → ║x ║
2346 	╚══╝   ╚══╝
2347 	```
2348  +/
2349 Pixmap rotateClockwise(const Pixmap source, Pixmap target) @nogc {
2350 	target.adjustTo(source.rotateClockwiseCalcDims());
2351 	source.rotateClockwiseInto(target);
2352 	return target;
2353 }
2354 
2355 /// ditto
2356 Pixmap rotateClockwiseNew(const Pixmap source) {
2357 	auto target = Pixmap.makeNew(source.rotateClockwiseCalcDims());
2358 	source.rotateClockwiseInto(target);
2359 	return target;
2360 }
2361 
2362 /// ditto
2363 PixmapBlueprint rotateClockwiseCalcDims(const Pixmap source) @nogc {
2364 	return PixmapBlueprint(source.length, source.height);
2365 }
2366 
2367 private void rotateCounterClockwiseInto(const Pixmap source, Pixmap target) @nogc {
2368 	// TODO: can this be optimized?
2369 	target = transpose(source, target);
2370 	target.flipVerticallyInPlace();
2371 }
2372 
2373 /++
2374 	Rotates an image by 90° counter-clockwise.
2375 
2376 	```
2377 	╔══╗   ╔══╗
2378 	║# ║   ║ x║
2379 	║+x║ → ║#+║
2380 	╚══╝   ╚══╝
2381 	```
2382  +/
2383 Pixmap rotateCounterClockwise(const Pixmap source, Pixmap target) @nogc {
2384 	target.adjustTo(source.rotateCounterClockwiseCalcDims());
2385 	source.rotateCounterClockwiseInto(target);
2386 	return target;
2387 }
2388 
2389 /// ditto
2390 Pixmap rotateCounterClockwiseNew(const Pixmap source) {
2391 	auto target = Pixmap.makeNew(source.rotateCounterClockwiseCalcDims());
2392 	source.rotateCounterClockwiseInto(target);
2393 	return target;
2394 }
2395 
2396 /// ditto
2397 PixmapBlueprint rotateCounterClockwiseCalcDims(const Pixmap source) @nogc {
2398 	return PixmapBlueprint(source.length, source.height);
2399 }
2400 
2401 private void rotate180degInto(const Pixmap source, Pixmap target) @nogc {
2402 	// Technically, this is implemented as flip vertical + flip horizontal.
2403 	auto src = PixmapScanner(source);
2404 	auto dst = PixmapScannerRW(target);
2405 
2406 	foreach (srcLine; src) {
2407 		auto dstLine = dst.back;
2408 		foreach (idxSrc, px; srcLine) {
2409 			const idxDst = (dstLine.length - (idxSrc + 1));
2410 			dstLine[idxDst] = px;
2411 		}
2412 		dst.popBack();
2413 	}
2414 }
2415 
2416 /++
2417 	Rotates an image by 180°.
2418 
2419 	```
2420 	╔═══╗   ╔═══╗
2421 	║#- ║   ║%~~║
2422 	║~~%║ → ║ -#║
2423 	╚═══╝   ╚═══╝
2424 	```
2425  +/
2426 Pixmap rotate180deg(const Pixmap source, Pixmap target) @nogc {
2427 	target.adjustTo(source.rotate180degCalcDims());
2428 	source.rotate180degInto(target);
2429 	return target;
2430 }
2431 
2432 /// ditto
2433 Pixmap rotate180degNew(const Pixmap source) {
2434 	auto target = Pixmap.makeNew(source.size);
2435 	source.rotate180degInto(target);
2436 	return target;
2437 }
2438 
2439 /// ditto
2440 void rotate180degInPlace(Pixmap source) @nogc {
2441 	auto scanner = PixmapScannerRW(source);
2442 
2443 	// Technically, this is implemented as a flip vertical + flip horizontal
2444 	// combo, i.e. the image is flipped vertically line by line, but the lines
2445 	// are overwritten in a horizontally flipped way.
2446 	while (!scanner.empty) {
2447 		auto a = scanner.front;
2448 		auto b = scanner.back;
2449 
2450 		// middle line? (odd number of lines)
2451 		if (a.ptr is b.ptr) {
2452 			break;
2453 		}
2454 
2455 		foreach (idxSrc, ref pxA; a) {
2456 			const idxDst = (b.length - (idxSrc + 1));
2457 			const tmp = pxA;
2458 			pxA = b[idxDst];
2459 			b[idxDst] = tmp;
2460 		}
2461 
2462 		scanner.popFront();
2463 		scanner.popBack();
2464 	}
2465 }
2466 
2467 ///
2468 PixmapBlueprint rotate180degCalcDims(const Pixmap source) @nogc {
2469 	return PixmapBlueprint.fromPixmap(source);
2470 }
2471 
2472 private void flipHorizontallyInto(const Pixmap source, Pixmap target) @nogc {
2473 	auto src = PixmapScanner(source);
2474 	auto dst = PixmapScannerRW(target);
2475 
2476 	foreach (srcLine; src) {
2477 		auto dstLine = dst.front;
2478 		foreach (idxSrc, px; srcLine) {
2479 			const idxDst = (dstLine.length - (idxSrc + 1));
2480 			dstLine[idxDst] = px;
2481 		}
2482 
2483 		dst.popFront();
2484 	}
2485 }
2486 
2487 /++
2488 	Flips an image horizontally.
2489 
2490 	```
2491 	╔═══╗   ╔═══╗
2492 	║#-.║ → ║.-#║
2493 	╚═══╝   ╚═══╝
2494 	```
2495  +/
2496 Pixmap flipHorizontally(const Pixmap source, Pixmap target) @nogc {
2497 	target.adjustTo(source.flipHorizontallyCalcDims());
2498 	source.flipHorizontallyInto(target);
2499 	return target;
2500 }
2501 
2502 /// ditto
2503 Pixmap flipHorizontallyNew(const Pixmap source) {
2504 	auto target = Pixmap.makeNew(source.size);
2505 	source.flipHorizontallyInto(target);
2506 	return target;
2507 }
2508 
2509 /// ditto
2510 void flipHorizontallyInPlace(Pixmap source) @nogc {
2511 	auto scanner = PixmapScannerRW(source);
2512 
2513 	foreach (line; scanner) {
2514 		const idxMiddle = (1 + (line.length >> 1));
2515 		auto halfA = line[0 .. idxMiddle];
2516 
2517 		foreach (idxA, ref px; halfA) {
2518 			const idxB = (line.length - (idxA + 1));
2519 			const tmp = line[idxB];
2520 			// swap
2521 			line[idxB] = px;
2522 			px = tmp;
2523 		}
2524 	}
2525 }
2526 
2527 /// ditto
2528 PixmapBlueprint flipHorizontallyCalcDims(const Pixmap source) @nogc {
2529 	return PixmapBlueprint.fromPixmap(source);
2530 }
2531 
2532 private void flipVerticallyInto(const Pixmap source, Pixmap target) @nogc {
2533 	auto src = PixmapScanner(source);
2534 	auto dst = PixmapScannerRW(target);
2535 
2536 	foreach (srcLine; src) {
2537 		dst.back[] = srcLine[];
2538 		dst.popBack();
2539 	}
2540 }
2541 
2542 /++
2543 	Flips an image vertically.
2544 
2545 	```
2546 	╔═══╗   ╔═══╗
2547 	║## ║   ║  -║
2548 	║  -║ → ║## ║
2549 	╚═══╝   ╚═══╝
2550 	```
2551  +/
2552 Pixmap flipVertically(const Pixmap source, Pixmap target) @nogc {
2553 	target.adjustTo(source.flipVerticallyCalcDims());
2554 	flipVerticallyInto(source, target);
2555 	return target;
2556 }
2557 
2558 /// ditto
2559 Pixmap flipVerticallyNew(const Pixmap source) {
2560 	auto target = Pixmap.makeNew(source.flipVerticallyCalcDims());
2561 	source.flipVerticallyInto(target);
2562 	return target;
2563 }
2564 
2565 /// ditto
2566 void flipVerticallyInPlace(Pixmap source) @nogc {
2567 	auto scanner = PixmapScannerRW(source);
2568 
2569 	while (!scanner.empty) {
2570 		auto a = scanner.front;
2571 		auto b = scanner.back;
2572 
2573 		// middle line? (odd number of lines)
2574 		if (a.ptr is b.ptr) {
2575 			break;
2576 		}
2577 
2578 		foreach (idx, ref pxA; a) {
2579 			const tmp = pxA;
2580 			pxA = b[idx];
2581 			b[idx] = tmp;
2582 		}
2583 
2584 		scanner.popFront();
2585 		scanner.popBack();
2586 	}
2587 }
2588 
2589 /// ditto
2590 PixmapBlueprint flipVerticallyCalcDims(const Pixmap source) @nogc {
2591 	return PixmapBlueprint.fromPixmap(source);
2592 }
2593 
2594 @safe pure nothrow @nogc:
2595 
2596 // ==== Blending functions ====
2597 
2598 /++
2599 	Alpha-blending accuracy level
2600 
2601 	$(TIP
2602 		This primarily exists for performance reasons.
2603 		In my tests LLVM manages to auto-vectorize the RGB-only codepath significantly better,
2604 		while the codegen for the accurate RGBA path is pretty conservative.
2605 
2606 		This provides an optimization opportunity for use-cases
2607 		that don’t require an alpha-channel on the result.
2608 	)
2609  +/
2610 enum BlendAccuracy {
2611 	/++
2612 		Only RGB channels will have the correct result.
2613 
2614 		A(lpha) channel can contain any value.
2615 
2616 		Suitable for blending into non-transparent targets (e.g. framebuffer, canvas)
2617 		where the resulting alpha-channel (opacity) value does not matter.
2618 	 +/
2619 	rgb = false,
2620 
2621 	/++
2622 		All RGBA channels will have the correct result.
2623 
2624 		Suitable for blending into transparent targets (e.g. images)
2625 		where the resulting alpha-channel (opacity) value matters.
2626 
2627 		Use this mode for image manipulation.
2628 	 +/
2629 	rgba = true,
2630 }
2631 
2632 /++
2633 	Blend modes
2634 
2635 	$(NOTE
2636 		As blending operations are implemented as integer calculations,
2637 		results may be slightly less precise than those from image manipulation
2638 		programs using floating-point math.
2639 	)
2640 
2641 	See_Also:
2642 		<https://www.w3.org/TR/compositing/#blending>
2643  +/
2644 enum BlendMode {
2645 	///
2646 	none = 0,
2647 	///
2648 	replace = none,
2649 	///
2650 	normal = 1,
2651 	///
2652 	alpha = normal,
2653 
2654 	///
2655 	multiply,
2656 	///
2657 	screen,
2658 
2659 	///
2660 	overlay,
2661 	///
2662 	hardLight,
2663 	///
2664 	softLight,
2665 
2666 	///
2667 	darken,
2668 	///
2669 	lighten,
2670 
2671 	///
2672 	colorDodge,
2673 	///
2674 	colorBurn,
2675 
2676 	///
2677 	difference,
2678 	///
2679 	exclusion,
2680 	///
2681 	subtract,
2682 	///
2683 	divide,
2684 }
2685 
2686 ///
2687 alias Blend = BlendMode;
2688 
2689 // undocumented
2690 enum blendNormal = BlendMode.normal;
2691 
2692 ///
2693 alias BlendFn = ubyte function(const ubyte background, const ubyte foreground) pure nothrow @nogc;
2694 
2695 /++
2696 	Blends `source` into `target`
2697 	with respect to the opacity of the source image (as stored in the alpha channel).
2698 
2699 	See_Also:
2700 		[alphaBlendRGBA] and [alphaBlendRGB] are shorthand functions
2701 		in cases where no special blending algorithm is needed.
2702  +/
2703 template alphaBlend(BlendFn blend = null, BlendAccuracy accuracy = BlendAccuracy.rgba) {
2704 	/// ditto
2705 	public void alphaBlend(scope Pixel[] target, scope const Pixel[] source) @trusted
2706 	in (source.length == target.length) {
2707 		foreach (immutable idx, ref pxTarget; target) {
2708 			alphaBlend(pxTarget, source.ptr[idx]);
2709 		}
2710 	}
2711 
2712 	/// ditto
2713 	public void alphaBlend(ref Pixel pxTarget, const Pixel pxSource) @trusted {
2714 		pragma(inline, true);
2715 
2716 		static if (accuracy == BlendAccuracy.rgba) {
2717 			immutable alphaResult = clamp255(pxSource.a + n255thsOf(pxTarget.a, (0xFF - pxSource.a)));
2718 			//immutable alphaResult = clamp255(pxTarget.a + n255thsOf(pxSource.a, (0xFF - pxTarget.a)));
2719 		}
2720 
2721 		immutable alphaSource = (pxSource.a | (pxSource.a << 8));
2722 		immutable alphaTarget = (0xFFFF - alphaSource);
2723 
2724 		foreach (immutable ib, ref px; pxTarget.components) {
2725 			static if (blend !is null) {
2726 				immutable bx = blend(px, pxSource.components.ptr[ib]);
2727 			} else {
2728 				immutable bx = pxSource.components.ptr[ib];
2729 			}
2730 			immutable d = cast(ubyte)(((px * alphaTarget) + 0x8080) >> 16);
2731 			immutable s = cast(ubyte)(((bx * alphaSource) + 0x8080) >> 16);
2732 			px = cast(ubyte)(d + s);
2733 		}
2734 
2735 		static if (accuracy == BlendAccuracy.rgba) {
2736 			pxTarget.a = alphaResult;
2737 		}
2738 	}
2739 }
2740 
2741 /// ditto
2742 template alphaBlend(BlendAccuracy accuracy, BlendFn blend = null) {
2743 	alias alphaBlend = alphaBlend!(blend, accuracy);
2744 }
2745 
2746 /++
2747 	Blends `source` into `target`
2748 	with respect to the opacity of the source image (as stored in the alpha channel).
2749 
2750 	This variant is $(slower than) [alphaBlendRGB],
2751 	but calculates the correct alpha-channel value of the target.
2752 	See [BlendAccuracy] for further explanation.
2753  +/
2754 public void alphaBlendRGBA(scope Pixel[] target, scope const Pixel[] source) @safe {
2755 	return alphaBlend!(null, BlendAccuracy.rgba)(target, source);
2756 }
2757 
2758 /// ditto
2759 public void alphaBlendRGBA(ref Pixel pxTarget, const Pixel pxSource) @safe {
2760 	return alphaBlend!(null, BlendAccuracy.rgba)(pxTarget, pxSource);
2761 }
2762 
2763 /++
2764 	Blends `source` into `target`
2765 	with respect to the opacity of the source image (as stored in the alpha channel).
2766 
2767 	This variant is $(B faster than) [alphaBlendRGBA],
2768 	but leads to a wrong alpha-channel value in the target.
2769 	Useful because of the performance advantage in cases where the resulting
2770 	alpha does not matter.
2771 	See [BlendAccuracy] for further explanation.
2772  +/
2773 public void alphaBlendRGB(scope Pixel[] target, scope const Pixel[] source) @safe {
2774 	return alphaBlend!(null, BlendAccuracy.rgb)(target, source);
2775 }
2776 
2777 /// ditto
2778 public void alphaBlendRGB(ref Pixel pxTarget, const Pixel pxSource) @safe {
2779 	return alphaBlend!(null, BlendAccuracy.rgb)(pxTarget, pxSource);
2780 }
2781 
2782 /++
2783 	Blends pixel `source` into pixel `target`
2784 	using the requested [BlendMode|blending mode].
2785  +/
2786 template blendPixel(BlendMode mode, BlendAccuracy accuracy = BlendAccuracy.rgba) {
2787 
2788 	static if (mode == BlendMode.replace) {
2789 		/// ditto
2790 		void blendPixel(ref Pixel target, const Pixel source) {
2791 			target = source;
2792 		}
2793 	}
2794 
2795 	static if (mode == BlendMode.alpha) {
2796 		/// ditto
2797 		void blendPixel(ref Pixel target, const Pixel source) {
2798 			return alphaBlend!accuracy(target, source);
2799 		}
2800 	}
2801 
2802 	static if (mode == BlendMode.multiply) {
2803 		/// ditto
2804 		void blendPixel(ref Pixel target, const Pixel source) {
2805 			return alphaBlend!(accuracy,
2806 				(a, b) => n255thsOf(a, b)
2807 			)(target, source);
2808 		}
2809 	}
2810 
2811 	static if (mode == BlendMode.screen) {
2812 		/// ditto
2813 		void blendPixel()(ref Pixel target, const Pixel source) {
2814 			return alphaBlend!(accuracy,
2815 				(a, b) => castTo!ubyte(0xFF - n255thsOf((0xFF - a), (0xFF - b)))
2816 			)(target, source);
2817 		}
2818 	}
2819 
2820 	static if (mode == BlendMode.darken) {
2821 		/// ditto
2822 		void blendPixel()(ref Pixel target, const Pixel source) {
2823 			return alphaBlend!(accuracy,
2824 				(a, b) => min(a, b)
2825 			)(target, source);
2826 		}
2827 	}
2828 	static if (mode == BlendMode.lighten) {
2829 		/// ditto
2830 		void blendPixel()(ref Pixel target, const Pixel source) {
2831 			return alphaBlend!(accuracy,
2832 				(a, b) => max(a, b)
2833 			)(target, source);
2834 		}
2835 	}
2836 
2837 	static if (mode == BlendMode.overlay) {
2838 		/// ditto
2839 		void blendPixel()(ref Pixel target, const Pixel source) {
2840 			return alphaBlend!(accuracy, function(const ubyte b, const ubyte f) {
2841 				if (b < 0x80) {
2842 					return n255thsOf((2 * b).castTo!ubyte, f);
2843 				}
2844 				return castTo!ubyte(
2845 					0xFF - n255thsOf(castTo!ubyte(2 * (0xFF - b)), (0xFF - f))
2846 				);
2847 			})(target, source);
2848 		}
2849 	}
2850 
2851 	static if (mode == BlendMode.hardLight) {
2852 		/// ditto
2853 		void blendPixel()(ref Pixel target, const Pixel source) {
2854 			return alphaBlend!(accuracy, function(const ubyte b, const ubyte f) {
2855 				if (f < 0x80) {
2856 					return n255thsOf(castTo!ubyte(2 * f), b);
2857 				}
2858 				return castTo!ubyte(
2859 					0xFF - n255thsOf(castTo!ubyte(2 * (0xFF - f)), (0xFF - b))
2860 				);
2861 			})(target, source);
2862 		}
2863 	}
2864 
2865 	static if (mode == BlendMode.softLight) {
2866 		/// ditto
2867 		void blendPixel()(ref Pixel target, const Pixel source) {
2868 			return alphaBlend!(accuracy, function(const ubyte b, const ubyte f) {
2869 				if (f < 0x80) {
2870 					// dfmt off
2871 					return castTo!ubyte(
2872 						b - n255thsOf(
2873 								n255thsOf((0xFF - 2 * f).castTo!ubyte, b),
2874 								(0xFF - b),
2875 							)
2876 					);
2877 					// dfmt on
2878 				}
2879 
2880 				// TODO: optimize if possible
2881 				// dfmt off
2882 				immutable ubyte d = (b < 0x40)
2883 					? castTo!ubyte((b * (0x3FC + (((16 * b - 0xBF4) * b) / 255))) / 255)
2884 					: intNormalizedSqrt(b);
2885 				//dfmt on
2886 
2887 				return castTo!ubyte(
2888 					b + n255thsOf((2 * f - 0xFF).castTo!ubyte, (d - b).castTo!ubyte)
2889 				);
2890 			})(target, source);
2891 		}
2892 	}
2893 
2894 	static if (mode == BlendMode.colorDodge) {
2895 		/// ditto
2896 		void blendPixel()(ref Pixel target, const Pixel source) {
2897 			return alphaBlend!(accuracy, function(const ubyte b, const ubyte f) {
2898 				if (b == 0x00) {
2899 					return ubyte(0x00);
2900 				}
2901 				if (f == 0xFF) {
2902 					return ubyte(0xFF);
2903 				}
2904 				return min(
2905 					ubyte(0xFF),
2906 					clamp255((255 * b) / (0xFF - f))
2907 				);
2908 			})(target, source);
2909 		}
2910 	}
2911 
2912 	static if (mode == BlendMode.colorBurn) {
2913 		/// ditto
2914 		void blendPixel()(ref Pixel target, const Pixel source) {
2915 			return alphaBlend!(accuracy, function(const ubyte b, const ubyte f) {
2916 				if (b == 0xFF) {
2917 					return ubyte(0xFF);
2918 				}
2919 				if (f == 0x00) {
2920 					return ubyte(0x00);
2921 				}
2922 
2923 				immutable m = min(
2924 					ubyte(0xFF),
2925 					clamp255(((0xFF - b) * 255) / f)
2926 				);
2927 				return castTo!ubyte(0xFF - m);
2928 			})(target, source);
2929 		}
2930 	}
2931 
2932 	static if (mode == BlendMode.difference) {
2933 		/// ditto
2934 		void blendPixel()(ref Pixel target, const Pixel source) {
2935 			return alphaBlend!(accuracy,
2936 				(b, f) => (b > f) ? castTo!ubyte(b - f) : castTo!ubyte(f - b)
2937 			)(target, source);
2938 		}
2939 	}
2940 
2941 	static if (mode == BlendMode.exclusion) {
2942 		/// ditto
2943 		void blendPixel()(ref Pixel target, const Pixel source) {
2944 			return alphaBlend!(accuracy,
2945 				(b, f) => castTo!ubyte(b + f - (2 * n255thsOf(f, b)))
2946 			)(target, source);
2947 		}
2948 	}
2949 
2950 	static if (mode == BlendMode.subtract) {
2951 		/// ditto
2952 		void blendPixel()(ref Pixel target, const Pixel source) {
2953 			return alphaBlend!(accuracy,
2954 				(b, f) => (b > f) ? castTo!ubyte(b - f) : ubyte(0)
2955 			)(target, source);
2956 		}
2957 	}
2958 
2959 	static if (mode == BlendMode.divide) {
2960 		/// ditto
2961 		void blendPixel()(ref Pixel target, const Pixel source) {
2962 			return alphaBlend!(accuracy,
2963 				(b, f) => (f == 0) ? ubyte(0xFF) : clamp255(0xFF * b / f)
2964 			)(target, source);
2965 		}
2966 	}
2967 
2968 	//else {
2969 	//	static assert(false, "Missing `blendPixel()` implementation for `BlendMode`.`" ~ mode ~ "`.");
2970 	//}
2971 }
2972 
2973 /++
2974 	Blends the pixel data of `source` into `target`
2975 	using the requested [BlendMode|blending mode].
2976 
2977 	`source` and `target` MUST have the same length.
2978  +/
2979 void blendPixels(
2980 	BlendMode mode,
2981 	BlendAccuracy accuracy,
2982 )(scope Pixel[] target, scope const Pixel[] source) @trusted
2983 in (source.length == target.length) {
2984 	static if (mode == BlendMode.replace) {
2985 		// explicit optimization
2986 		target.ptr[0 .. target.length] = source.ptr[0 .. target.length];
2987 	} else {
2988 
2989 		// better error message in case it’s not implemented
2990 		static if (!is(typeof(blendPixel!(mode, accuracy)))) {
2991 			pragma(msg, "Hint: Missing or bad `blendPixel!(" ~ mode.stringof ~ ")`.");
2992 		}
2993 
2994 		foreach (immutable idx, ref pxTarget; target) {
2995 			blendPixel!(mode, accuracy)(pxTarget, source.ptr[idx]);
2996 		}
2997 	}
2998 }
2999 
3000 /// ditto
3001 void blendPixels(BlendAccuracy accuracy)(scope Pixel[] target, scope const Pixel[] source, BlendMode mode) {
3002 	import std.meta : NoDuplicates;
3003 	import std.traits : EnumMembers;
3004 
3005 	final switch (mode) with (BlendMode) {
3006 		static foreach (m; NoDuplicates!(EnumMembers!BlendMode)) {
3007 	case m:
3008 			return blendPixels!(m, accuracy)(target, source);
3009 		}
3010 	}
3011 }
3012 
3013 /// ditto
3014 void blendPixels(
3015 	scope Pixel[] target,
3016 	scope const Pixel[] source,
3017 	BlendMode mode,
3018 	BlendAccuracy accuracy = BlendAccuracy.rgba,
3019 ) {
3020 	if (accuracy == BlendAccuracy.rgb) {
3021 		return blendPixels!(BlendAccuracy.rgb)(target, source, mode);
3022 	} else {
3023 		return blendPixels!(BlendAccuracy.rgba)(target, source, mode);
3024 	}
3025 }
3026 
3027 // ==== Drawing functions ====
3028 
3029 /++
3030 	Draws a single pixel
3031  +/
3032 void drawPixel(Pixmap target, Point pos, Pixel color) {
3033 	immutable size_t offset = linearOffset(target.width, pos);
3034 	target.data[offset] = color;
3035 }
3036 
3037 /++
3038 	Draws a rectangle
3039  +/
3040 void drawRectangle(Pixmap target, Rectangle rectangle, Pixel color) {
3041 	alias r = rectangle;
3042 
3043 	immutable tRect = OriginRectangle(
3044 		Size(target.width, target.height),
3045 	);
3046 
3047 	// out of bounds?
3048 	if (!tRect.intersect(r)) {
3049 		return;
3050 	}
3051 
3052 	immutable drawingTarget = Point(
3053 		(r.pos.x >= 0) ? r.pos.x : 0,
3054 		(r.pos.y >= 0) ? r.pos.y : 0,
3055 	);
3056 
3057 	immutable drawingEnd = Point(
3058 		(r.right < tRect.right) ? r.right : tRect.right,
3059 		(r.bottom < tRect.bottom) ? r.bottom : tRect.bottom,
3060 	);
3061 
3062 	immutable int drawingWidth = drawingEnd.x - drawingTarget.x;
3063 
3064 	foreach (y; drawingTarget.y .. drawingEnd.y) {
3065 		target.sliceAt(Point(drawingTarget.x, y), drawingWidth)[] = color;
3066 	}
3067 }
3068 
3069 /++
3070 	Draws a line
3071  +/
3072 void drawLine(Pixmap target, Point a, Point b, Pixel color) {
3073 	import std.math : sqrt;
3074 
3075 	// TODO: line width
3076 	// TODO: anti-aliasing (looks awful without it!)
3077 
3078 	float deltaX = b.x - a.x;
3079 	float deltaY = b.y - a.y;
3080 	int steps = sqrt(deltaX * deltaX + deltaY * deltaY).castTo!int;
3081 
3082 	float[2] step = [
3083 		(deltaX / steps),
3084 		(deltaY / steps),
3085 	];
3086 
3087 	foreach (i; 0 .. steps) {
3088 		// dfmt off
3089 		immutable Point p = a + Point(
3090 			round(step[0] * i).castTo!int,
3091 			round(step[1] * i).castTo!int,
3092 		);
3093 		// dfmt on
3094 
3095 		immutable offset = linearOffset(p, target.width);
3096 		target.data[offset] = color;
3097 	}
3098 
3099 	immutable offsetEnd = linearOffset(b, target.width);
3100 	target.data[offsetEnd] = color;
3101 }
3102 
3103 /++
3104 	Draws an image (a source pixmap) on a target pixmap
3105 
3106 	Params:
3107 		target = target pixmap to draw on
3108 		image = source pixmap
3109 		pos = top-left destination position (on the target pixmap)
3110  +/
3111 void drawPixmap(Pixmap target, const Pixmap image, Point pos, Blend blend = blendNormal) {
3112 	alias source = image;
3113 
3114 	immutable tRect = OriginRectangle(
3115 		Size(target.width, target.height),
3116 	);
3117 
3118 	immutable sRect = Rectangle(pos, source.size);
3119 
3120 	// out of bounds?
3121 	if (!tRect.intersect(sRect)) {
3122 		return;
3123 	}
3124 
3125 	immutable drawingTarget = Point(
3126 		(pos.x >= 0) ? pos.x : 0,
3127 		(pos.y >= 0) ? pos.y : 0,
3128 	);
3129 
3130 	immutable drawingEnd = Point(
3131 		(sRect.right < tRect.right) ? sRect.right : tRect.right,
3132 		(sRect.bottom < tRect.bottom) ? sRect.bottom : tRect.bottom,
3133 	);
3134 
3135 	immutable drawingSource = Point(drawingTarget.x, 0) - Point(sRect.pos.x, sRect.pos.y);
3136 	immutable int drawingWidth = drawingEnd.x - drawingTarget.x;
3137 
3138 	foreach (y; drawingTarget.y .. drawingEnd.y) {
3139 		blendPixels(
3140 			target.sliceAt(Point(drawingTarget.x, y), drawingWidth),
3141 			source.sliceAt(Point(drawingSource.x, y + drawingSource.y), drawingWidth),
3142 			blend,
3143 		);
3144 	}
3145 }
3146 
3147 /++
3148 	Draws an image (a subimage from a source pixmap) on a target pixmap
3149 
3150 	Params:
3151 		target = target pixmap to draw on
3152 		image = source subpixmap
3153 		pos = top-left destination position (on the target pixmap)
3154  +/
3155 void drawPixmap(Pixmap target, const SubPixmap image, Point pos, Blend blend = blendNormal) {
3156 	alias source = image;
3157 
3158 	debug assert(source.isValid);
3159 
3160 	immutable tRect = OriginRectangle(
3161 		Size(target.width, target.height),
3162 	);
3163 
3164 	immutable sRect = Rectangle(pos, source.size);
3165 
3166 	// out of bounds?
3167 	if (!tRect.intersect(sRect)) {
3168 		return;
3169 	}
3170 
3171 	Point sourceOffset = source.offset;
3172 	Point drawingTarget;
3173 	Size drawingSize = source.size;
3174 
3175 	if (pos.x <= 0) {
3176 		sourceOffset.x -= pos.x;
3177 		drawingTarget.x = 0;
3178 		drawingSize.width += pos.x;
3179 	} else {
3180 		drawingTarget.x = pos.x;
3181 	}
3182 
3183 	if (pos.y <= 0) {
3184 		sourceOffset.y -= pos.y;
3185 		drawingTarget.y = 0;
3186 		drawingSize.height += pos.y;
3187 	} else {
3188 		drawingTarget.y = pos.y;
3189 	}
3190 
3191 	Point drawingEnd = drawingTarget + drawingSize.castTo!Point();
3192 	if (drawingEnd.x >= target.width) {
3193 		drawingSize.width -= (drawingEnd.x - target.width);
3194 	}
3195 	if (drawingEnd.y >= target.height) {
3196 		drawingSize.height -= (drawingEnd.y - target.height);
3197 	}
3198 
3199 	auto dst = SubPixmap(target, drawingTarget, drawingSize);
3200 	auto src = const(SubPixmap)(
3201 		source.source,
3202 		drawingSize,
3203 		sourceOffset,
3204 	);
3205 
3206 	src.xferTo(dst, blend);
3207 }
3208 
3209 /++
3210 	Draws a sprite from a spritesheet
3211  +/
3212 void drawSprite(Pixmap target, const SpriteSheet sheet, int spriteIndex, Point pos, Blend blend = blendNormal) {
3213 	immutable tRect = OriginRectangle(
3214 		Size(target.width, target.height),
3215 	);
3216 
3217 	immutable spriteOffset = sheet.getSpritePixelOffset2D(spriteIndex);
3218 	immutable sRect = Rectangle(pos, sheet.spriteSize);
3219 
3220 	// out of bounds?
3221 	if (!tRect.intersect(sRect)) {
3222 		return;
3223 	}
3224 
3225 	immutable drawingTarget = Point(
3226 		(pos.x >= 0) ? pos.x : 0,
3227 		(pos.y >= 0) ? pos.y : 0,
3228 	);
3229 
3230 	immutable drawingEnd = Point(
3231 		(sRect.right < tRect.right) ? sRect.right : tRect.right,
3232 		(sRect.bottom < tRect.bottom) ? sRect.bottom : tRect.bottom,
3233 	);
3234 
3235 	immutable drawingSource =
3236 		spriteOffset
3237 		+ Point(drawingTarget.x, 0)
3238 		- Point(sRect.pos.x, sRect.pos.y);
3239 	immutable int drawingWidth = drawingEnd.x - drawingTarget.x;
3240 
3241 	foreach (y; drawingTarget.y .. drawingEnd.y) {
3242 		blendPixels(
3243 			target.sliceAt(Point(drawingTarget.x, y), drawingWidth),
3244 			sheet.pixmap.sliceAt(Point(drawingSource.x, y + drawingSource.y), drawingWidth),
3245 			blend,
3246 		);
3247 	}
3248 }