1 /**
2 In-memory images.
3 
4 License:
5     This Source Code Form is subject to the terms of
6     the Mozilla Public License, v. 2.0. If a copy of
7     the MPL was not distributed with this file, You
8     can obtain one at http://mozilla.org/MPL/2.0/.
9  
10  Copyright: Vladimir Panteleev <vladimir@thecybershadow.net>
11  Copyright: Guillaume Piolat <contact@auburnsounds.com>
12  */
13 
14 module dplug.graphics.image;
15 
16 import dplug.core.math;
17 import dplug.core.vec;
18 import dplug.core.nogc;
19 import dplug.math.box;
20 public import dplug.graphics.color;
21 import gamut;
22 
23 nothrow @nogc:
24 
25 /// A view is any type which provides a width, height,
26 /// and can be indexed to get the color at a specific
27 /// coordinate.
28 enum isView(T) =
29     is(typeof(T.init.w) : size_t) && // width
30     is(typeof(T.init.h) : size_t) && // height
31     is(typeof(T.init[0, 0])     );   // color information
32 
33 /// Returns the color type of the specified view.
34 /// By convention, colors are structs with numeric
35 /// fields named after the channel they indicate.
36 alias ViewColor(T) = typeof(T.init[0, 0]);
37 
38 /// Views can be read-only or writable.
39 enum isWritableView(T) =
40     isView!T &&
41     is(typeof(T.init[0, 0] = ViewColor!T.init));
42 
43 /// Optionally, a view can also provide direct pixel
44 /// access. We call these "direct views".
45 enum isDirectView(T) =
46     isView!T &&
47     is(typeof(T.init.scanline(0)) : ViewColor!T[]);
48 
49 /// Mixin which implements view primitives on top of
50 /// existing direct view primitives.
51 mixin template DirectView()
52 {
53     alias COLOR = typeof(scanline(0)[0]);
54 
55     /// Implements the view[x, y] operator.
56     ref COLOR opIndex(int x, int y)
57     {
58         return scanline(y)[x];
59     }
60 
61     /// Implements the view[x, y] = c operator.
62     COLOR opIndexAssign(COLOR value, int x, int y)
63     {
64         return scanline(y)[x] = value;
65     }
66 }
67 
68 /// Blits a view onto another.
69 /// The views must have the same size.
70 void blitTo(SRC, DST)(auto ref SRC src, auto ref DST dst)
71 	if (isView!SRC && isWritableView!DST)
72 {
73 	assert(src.w == dst.w && src.h == dst.h, "View size mismatch");
74 	foreach (y; 0..src.h)
75 	{
76 		static if (isDirectView!SRC && isDirectView!DST)
77 			dst.scanline(y)[] = src.scanline(y)[];
78 		else
79 		{
80 			foreach (x; 0..src.w)
81 				dst[x, y] = src[x, y];
82 		}
83 	}
84 }
85 
86 /// Helper function to blit an image onto another at a specified location.
87 void blitTo(SRC, DST)(auto ref SRC src, auto ref DST dst, int x, int y)
88 {
89 	src.blitTo(dst.crop(x, y, x+src.w, y+src.h));
90 }
91 
92 /// Default implementation for the .size method.
93 /// Asserts that the view has the desired size.
94 void size(V)(auto ref V src, int w, int h)
95 	if (isView!V)
96 {
97 	assert(src.w == w && src.h == h, "Wrong size for " ~ V.stringof);
98 }
99 
100 // ***************************************************************************
101 
102 /// Mixin which implements view primitives on top of
103 /// another view, using a coordinate transform function.
104 mixin template Warp(V)
105 	if (isView!V)
106 {
107 	V src;
108 
109 	auto ref ViewColor!V opIndex(int x, int y)
110 	{
111 		warp(x, y);
112 		return src[x, y];
113 	}
114 
115 	static if (isWritableView!V)
116 	ViewColor!V opIndexAssign(ViewColor!V value, int x, int y)
117 	{
118 		warp(x, y);
119 		return src[x, y] = value;
120 	}
121 }
122 
123 /// Represents a reference to COLOR data
124 /// already existing elsewhere in memory.
125 /// Assumes that pixels are stored row-by-row,
126 /// with a known distance between each row.
127 struct ImageRef(COLOR)
128 {
129 	int w, h;
130 	size_t pitch; /// In bytes, not COLORs
131 	COLOR* pixels;
132 
133 	/// Returns an array for the pixels at row y.
134 	COLOR[] scanline(int y)
135 	{
136 		assert(y >= 0 && y < h);
137 		assert(pitch);
138 
139         // perf: this cast help in 64-bit, since it avoids a MOVSXD to extend a signed 32-bit into a 64-bit pointer offset
140         uint unsignedY = cast(uint)y;
141 
142 		auto row = cast(COLOR*)(cast(ubyte*)pixels + unsignedY * pitch);
143 		return row[0..w];
144 	}
145 
146 	mixin DirectView;
147 
148     /// Returns a cropped view of the same `ImageRef`.
149     ImageRef!COLOR cropBorder(int borderPixels)
150     {
151         assert(w >= 2*borderPixels);
152         assert(h >= 2*borderPixels);
153 
154         ImageRef cropped;
155         cropped.w = w - 2*borderPixels;
156         cropped.h = h - 2*borderPixels;
157         cropped.pitch = pitch;
158         cropped.pixels = cast(COLOR*)(cast(ubyte*)pixels + borderPixels*pitch + borderPixels*COLOR.sizeof);
159         return cropped;
160     }
161 }
162 
163 unittest
164 {
165     static assert(isDirectView!(ImageRef!ubyte));
166 }
167 
168 /// Convert an `OwnedImage` to an `ImageRef`.
169 ImageRef!L8 toRef(OwnedImage!L8 src) pure
170 {
171     return ImageRef!L8(src.w, src.h, src.pitchInBytes(), src.scanlinePtr(0));
172 }
173 
174 ///ditto
175 ImageRef!L16 toRef(OwnedImage!L16 src) pure
176 {
177     return ImageRef!L16(src.w, src.h, src.pitchInBytes(), src.scanlinePtr(0));
178 }
179 
180 ///ditto
181 ImageRef!RGBA toRef(OwnedImage!RGBA src) pure
182 {
183     return ImageRef!RGBA(src.w, src.h, src.pitchInBytes(), src.scanlinePtr(0));
184 }
185 
186 ///ditto
187 ImageRef!RGBA16 toRef(OwnedImage!RGBA16 src) pure
188 {
189     return ImageRef!RGBA16(src.w, src.h, src.pitchInBytes(), src.scanlinePtr(0));
190 }
191 
192 /// Copy the indicated row of src to a COLOR buffer.
193 void copyScanline(SRC, COLOR)(auto ref SRC src, int y, COLOR[] dst)
194     if (isView!SRC && is(COLOR == ViewColor!SRC))
195 {
196 	static if (isDirectView!SRC)
197 		dst[] = src.scanline(y)[];
198 	else
199 	{
200 		assert(src.w == dst.length);
201 		foreach (x; 0..src.w)
202 			dst[x] = src[x, y];
203 	}
204 }
205 
206 /// Copy a view's pixels (top-to-bottom) to a COLOR buffer.
207 void copyPixels(SRC, COLOR)(auto ref SRC src, COLOR[] dst)
208 	if (isView!SRC && is(COLOR == ViewColor!SRC))
209 {
210 	assert(dst.length == src.w * src.h);
211 	foreach (y; 0..src.h)
212 		src.copyScanline(y, dst[y*src.w..(y+1)*src.w]);
213 }
214 
215 /// Crop an ImageRef and get an ImageRef instead of a Voldemort type.
216 /// This also avoid adding offset to coordinates.
217 ImageRef!COLOR cropImageRef(COLOR)(ImageRef!COLOR src, box2i rect)
218 {
219     assert(rect.max.x <= src.w);
220     assert(rect.max.y <= src.h);
221     ImageRef!COLOR result;
222     result.w = rect.width;
223     result.h = rect.height;
224     result.pitch = src.pitch;
225     COLOR[] scan = src.scanline(rect.min.y);
226     result.pixels = &scan[rect.min.x];
227     return result;
228 }
229 ///ditto
230 ImageRef!COLOR cropImageRef(COLOR)(ImageRef!COLOR src, int xmin, int ymin, int xmax, int ymax)
231 {
232     return src.cropImageRef(box2i(xmin, ymin, xmax, ymax));
233 }
234 
235 /// Manually managed image.
236 final class OwnedImage(COLOR)
237 {
238 public:
239 nothrow:
240 @nogc:
241 
242     /// Width of the meaningful area. Public in order to be an `Image`.
243     int w;
244 
245     /// Height of the meaningful area. Public in order to be an `Image`.
246     int h;
247 
248     /// Create empty and with no size.
249     this() nothrow @nogc
250     {
251     }
252 
253     /// Create with given initial size.
254     ///
255     /// Params:
256     ///   w               Width of meaningful area in pixels.
257     ///   h               Height of meaningful area in pixels.
258     ///   border          Number of border pixels around the meaningful area. For the right border, this could actually be more depending on other constraints.
259     ///   rowAlignment    Alignment of the _first_ pixel of each row, in bytes (excluding border).
260     ///   xMultiplicity   Starting with the first meaningful pixel of a line, force the number of adressable
261     ///                   pixels to be a multiple of `xMultiplicity`.
262     ///                   All these "padding" samples added at the right of each line if needed will be considered 
263     ///                   part of the border and replicated if need be.
264     ///                   This is eg. to process a whole line with only aligned loads.
265     ///                   Implemented with additional right border.
266     ///   trailingSamples When reading from a meaningful position, you can read 1 + `trailingSamples` neightbours
267     ///                   Their value is NOT guaranteed in any way and is completely undefined. 
268     ///                   They are guaranteed non-NaN if you've generated borders and did not write into borders.
269     ///                   Because of row alignment padding, this is not implemented with a few samples at the end 
270     ///                   of the buffer, but is instead again a right border extension.
271     ///
272     /// Note: default arguments leads to a gapless representation.
273     this(int w, int h, int border = 0, int rowAlignment = 1, int xMultiplicity = 1, int trailingSamples = 0) nothrow @nogc
274     {
275         this();
276         size(w, h, border, rowAlignment, xMultiplicity, trailingSamples);
277     }
278 
279     /// Create from already loaded dense RGBA pixel data.
280     /// `buffer` should be allocated with `alignedMalloc`/`alignedRealloc` with an alignment of 1.
281     /// Ownership of `buffer` is given to the `OwnedImage`.
282     this(int w, int h, ubyte* buffer)
283     {
284         this.w = w;
285         this.h = h;
286         _pixels = cast(COLOR*) buffer;
287         _bytePitch = w * cast(int)(COLOR.sizeof);
288         _buffer = buffer;
289         _border = 0;
290         _borderRight = 0;
291     }
292 
293     ~this()
294     {
295         if (_buffer !is null)
296         {
297              // FUTURE: use intel-intrinsics _mm_malloc/_mm_free
298             alignedFree(_buffer, 1); // Note: this allocation methods must be synced with the one in JPEG and PNL loading.
299             _buffer = null;
300         }
301     }
302 
303     /// Returns: A pointer to the pixels at row y. Excluding border pixels.
304     COLOR* scanlinePtr(int y) pure
305     {
306         assert(y >= 0 && y < h);
307 
308         // perf: this cast help in 64-bit, since it avoids a MOVSXD to extend a signed 32-bit into a 64-bit pointer offset
309         uint offsetBytes = _bytePitch * cast(uint)y;
310 
311         return cast(COLOR*)( cast(ubyte*)_pixels + offsetBytes );
312     }
313 
314     /// Returns: A slice of the pixels at row y. Excluding border pixels.
315     COLOR[] scanline(int y) pure
316     {
317         return scanlinePtr(y)[0..w];
318     }    
319 
320     /// Resize the image, the content is lost and the new content is undefined.
321     ///
322     /// Params:
323     ///   w               Width of meaningful area in pixels.
324     ///   h               Height of meaningful area in pixels.
325     ///   border          Number of border pixels around the meaningful area. For the right border, this could actually be more depending on other constraints.
326     ///   rowAlignment    Alignment of the _first_ pixel of each row, in bytes (excluding border).
327     ///   xMultiplicity   Starting with the first meaningful pixel of a line, force the number of adressable
328     ///                   pixels to be a multiple of `xMultiplicity`.
329     ///                   All these "padding" samples added at the right of each line if needed will be considered 
330     ///                   part of the border and replicated if need be.
331     ///                   This is eg. to process a whole line with only aligned loads.
332     ///                   Implemented with additional right border.
333     ///   trailingSamples When reading from a meaningful position, you can read 1 + `trailingSamples` neightbours
334     ///                   Their value is NOT guaranteed in any way and is completely undefined. 
335     ///                   They are guaranteed non-NaN if you've generated borders and did not write into borders.
336     ///                   Because of row alignment padding, this is not implemented with a few samples at the end 
337     ///                   of the buffer, but is instead again a right border extension.
338     ///
339     /// Note: Default arguments leads to a gapless representation.
340     void size(int width, 
341               int height, 
342               int border = 0, 
343               int rowAlignment = 1, 
344               int xMultiplicity = 1, 
345               int trailingSamples = 0)
346     {
347         assert(width >= 0); // Note sure if 0 is supported
348         assert(height >= 0); // Note sure if 0 is supported
349         assert(border >= 0);
350         assert(rowAlignment >= 1); // Not yet implemented!
351         assert(xMultiplicity >= 1); // Not yet implemented!
352 
353         // Compute size of right border.
354         // How many "padding samples" do we need to extend the right border with to respect `xMultiplicity`?
355         int rightPadding = computeRightPadding(w, border, xMultiplicity);
356         int borderRight = border + rightPadding;
357         if (borderRight < trailingSamples)
358             borderRight = trailingSamples;
359 
360         int actualWidthInSamples  = border + width  + borderRight;
361         int actualHeightInSamples = border + height + border;        
362 
363         // Compute byte pitch and align it on `rowAlignment`
364         int bytePitch = cast(int)(COLOR.sizeof) * actualWidthInSamples;
365         bytePitch = cast(int) nextMultipleOf(bytePitch, rowAlignment);
366 
367         this.w = width;
368         this.h = height;
369         this._border = border;
370         this._borderRight = borderRight;
371         this._bytePitch = bytePitch;
372 
373         // How many bytes do we need for all samples? A bit more for aligning the first valid pixel.
374         size_t allocationSize = bytePitch * actualHeightInSamples;
375         allocationSize += (rowAlignment - 1);
376 
377         // We don't need to preserve former data, nor to align the first border pixel
378         this._buffer = alignedReallocDiscard(this._buffer, allocationSize, 1);
379 
380         // Set _pixels to the right place
381         size_t offsetToFirstMeaningfulPixel = bytePitch * borderTop() + COLOR.sizeof * borderLeft();
382         this._pixels = cast(COLOR*) nextAlignedPointer(&_buffer[offsetToFirstMeaningfulPixel], rowAlignment);
383 
384         // Test alignment of rows
385         assert( isPointerAligned(_pixels, rowAlignment) );
386         if (height > 0)
387             assert( isPointerAligned(scanlinePtr(0), rowAlignment) );
388 
389         if (border == 0 && rowAlignment == 1 && xMultiplicity == 1 && trailingSamples == 0)
390         {
391             assert(isGapless());
392         }
393     }
394 
395     /// Returns: `true` if rows of pixels are immediately consecutive in memory.
396     ///          Meaning that there is no border or lost pixels in the data.
397     bool isGapless() pure
398     {
399         return w * COLOR.sizeof  == _bytePitch;
400     }
401 
402     /// Returns: Number of bytes to add to a COLOR* pointer to get to the previous/next line.
403     ///          This pitch is guaranteed to be positive (>= 0).
404     int pitchInBytes() pure
405     {
406         return _bytePitch;
407     }
408 
409     /// Returns: Number of border pixels in the left direction (small X).
410     int borderLeft() pure
411     {
412         return _border;
413     }
414 
415     /// Returns: Number of border pixels in the right direction (large X).
416     int borderRight() pure
417     {
418         return _borderRight;
419     }
420 
421     /// Returns: Number of border pixels in the left direction (small Y).
422     int borderTop() pure
423     {
424         return _border;
425     }
426 
427     /// Returns: Number of border pixels in the left direction (large Y).
428     int borderBottom() pure
429     {
430         return _border;
431     }
432 
433     // It is a `DirectView`.
434     mixin DirectView;
435 
436     /// Fills the whole image, border included, with a single color value.
437     void fillWith(COLOR fill)
438     {
439         for (int y = -borderTop(); y < h + borderBottom(); ++y)
440         {
441             int pixelsInRow = borderLeft() + w + borderRight();
442             COLOR* scan = unsafeScanlinePtr(y) - borderLeft();
443             scan[0..pixelsInRow] = fill;
444         }
445     }
446 
447     /// Fill the borders by taking the nearest existing pixel in the meaningful area.
448     void replicateBorders()
449     {
450         replicateBordersTouching( box2i.rectangle(0, 0, this.w, this.h) );
451     }
452 
453     /// Fill the borders _touching updatedRect_ by taking the nearest existing pixel in the meaningful area.
454     void replicateBordersTouching(box2i updatedRect)
455     {
456         if (w < 1 || h < 1)
457             return; // can't replicate borders of an empty image
458 
459         // BORDER REPLICATION.
460         // If an area is touching left border, then the border needs replication.
461         // If an area is touching left and top border, then the border needs replication and the corner pixel should be filled too.
462         // Same for all four corners. Border only applies to level 0.
463         //
464         // OOOOOOOxxxxxxxxxxxxxxx
465         // Ooooooo
466         // Oo    o
467         // Oo    o    <------ if the update area is made of 'o', then the area to replicate is 'O'
468         // Ooooooo
469         // x
470         // x
471 
472         int W = this.w;
473         int H = this.h;
474 
475         int minx = updatedRect.min.x;
476         int maxx = updatedRect.max.x;
477         int miny = updatedRect.min.y;
478         int maxy = updatedRect.max.y;
479 
480         bool touchLeft   = (minx == 0);
481         bool touchTop    = (miny == 0);
482         bool touchRight  = (maxx == W);
483         bool touchBottom = (maxy == H);
484 
485         int bTop   = borderTop();
486         int bBott  = borderBottom();
487         int bLeft  = borderLeft();
488         int bRight = borderRight();
489 
490         if (touchTop)
491         {
492             if (touchLeft)
493             {
494                 COLOR topLeft = this[0, 0];
495                 for (int y = -borderTop; y < 0; ++y)
496                     for (int x = -borderLeft; x < 0; ++x)
497                         unsafeScanlinePtr(y)[x] = topLeft;
498             }
499 
500             for (int y = -borderTop; y < 0; ++y)
501             {
502                 unsafeScanlinePtr(y)[minx..maxx] = scanline(0)[minx..maxx];
503             }
504 
505             if (touchRight)
506             {
507                 COLOR topRight = this[W-1, 0];
508                 for (int y = -borderTop; y < 0; ++y)
509                     for (int x = 0; x < borderRight(); ++x)
510                         unsafeScanlinePtr(y)[W + x] = topRight;
511             }
512         }
513 
514         if (touchLeft)
515         {
516             for (int y = miny; y < maxy; ++y)
517             {
518                 COLOR edge = this[0, y];
519                 for (int x = -borderLeft(); x < 0; ++x)
520                     unsafeScanlinePtr(y)[x] = edge;
521             }
522         }
523 
524         if (touchRight)
525         {
526             for (int y = miny; y < maxy; ++y)
527             {
528                 COLOR edge = this[W-1, y];
529                 for (int x = 0; x < borderRight(); ++x)
530                     unsafeScanlinePtr(y)[W + x] = edge;
531             }
532         }
533 
534         if (touchBottom)
535         {
536             if (touchLeft)
537             {
538                 COLOR bottomLeft = this[0, H-1];
539                 for (int y = H; y < H + borderTop(); ++y)
540                     for (int x = -borderLeft; x < 0; ++x)
541                         unsafeScanlinePtr(y)[x] = bottomLeft;
542             }
543 
544             for (int y = H; y < H + borderBottom(); ++y)
545             {
546                 unsafeScanlinePtr(y)[minx..maxx] = scanline(H-1)[minx..maxx];
547             }
548 
549             if (touchRight)
550             {
551                 COLOR bottomRight = this[W-1, H-1];
552                 for (int y = H; y < H + borderTop(); ++y)
553                     for (int x = 0; x < borderRight(); ++x)
554                         unsafeScanlinePtr(y)[W + x] = bottomRight;
555             }
556         }
557     }
558 
559 private:
560 
561     /// Adress of the first meaningful pixel
562     COLOR* _pixels;
563 
564     /// Samples difference between rows of pixels.
565     int _bytePitch;
566 
567     /// Address of the allocation itself
568     void* _buffer = null;
569 
570     /// Size of left, top and bottom borders, around the meaningful area.
571     int _border;
572 
573     /// Size of border at thr right of the meaningful area (most positive X)
574     int _borderRight;
575 
576     // Internal use: allows to get the scanlines of top and bottom borders too
577     COLOR* unsafeScanlinePtr(int y) pure
578     {
579         assert(y >= -borderTop());      // negative Y allows to get top border scanlines
580         assert(y < h + borderBottom()); // Y overflow allows to get bottom border scanlines
581         int byteOffset = _bytePitch * y; // unlike the normal `scanlinePtr`, there will be a MOVSXD here
582         return cast(COLOR*)(cast(ubyte*)(_pixels) + byteOffset);
583     }
584 
585     static int computeRightPadding(int width, int border, int xMultiplicity) pure
586     {
587         int nextMultiple = cast(int)(nextMultipleOf(width + border, xMultiplicity));
588         return nextMultiple - (width + border);
589     }
590 
591     static size_t nextMultipleOf(size_t base, size_t multiple) pure
592     {
593         assert(multiple > 0);
594         size_t n = (base + multiple - 1) / multiple;
595         return multiple * n;
596     }
597 
598     /// Returns: next pointer aligned with alignment bytes.
599     static void* nextAlignedPointer(void* start, size_t alignment) pure
600     {
601         return cast(void*)nextMultipleOf(cast(size_t)(start), alignment);
602     }
603 }
604 
605 unittest
606 {
607     static assert(isDirectView!(OwnedImage!ubyte));
608     assert(OwnedImage!RGBA.computeRightPadding(4, 0, 4) == 0);
609     assert(OwnedImage!RGBA.computeRightPadding(1, 3, 5) == 1);
610     assert(OwnedImage!L16.computeRightPadding(2, 0, 4) == 2);
611     assert(OwnedImage!RGBA.computeRightPadding(2, 1, 4) == 1);
612     assert(OwnedImage!RGBf.nextMultipleOf(0, 7) == 0);
613     assert(OwnedImage!RGBAf.nextMultipleOf(1, 7) == 7);
614     assert(OwnedImage!RGBA.nextMultipleOf(6, 7) == 7);
615     assert(OwnedImage!RGBA.nextMultipleOf(7, 7) == 7);
616     assert(OwnedImage!RGBA.nextMultipleOf(8, 7) == 14);
617 
618     {
619         OwnedImage!RGBA img = mallocNew!(OwnedImage!RGBA);
620         scope(exit) destroyFree(img);
621         img.size(0, 0); // should be supported
622 
623         int border = 10;
624         img.size(0, 0, border); // should also be supported (border with no data!)
625         img.replicateBorders(); // should not crash
626 
627         border = 0;
628         img.size(1, 1, border);
629         img.replicateBorders(); // should not crash
630     }
631 }
632 
633 // Test border replication
634 unittest
635 {
636     OwnedImage!L8 img = mallocNew!(OwnedImage!L8);
637     scope(exit) destroyFree(img);
638     int width = 2;
639     int height = 2;
640     int border = 2;
641     int xMultiplicity = 1;
642     int trailing = 3;
643     img.size(width, height, border, xMultiplicity, trailing);
644     assert(img.w == 2 && img.h == 2);
645     assert(img.borderLeft() == 2 && img.borderRight() == 3);
646     assert(img._bytePitch == 7);
647 
648     img.fillWith(L8(5));
649     img.scanline(0)[0].l = 1;
650     img.scanlinePtr(0)[1].l = 2;
651     img[0, 1].l = 3;
652     img[1, 1].l = 4;
653     
654     img.replicateBorders();
655     ubyte[7][6] correct = 
656     [
657         [1, 1, 1, 2, 2, 2, 2],
658         [1, 1, 1, 2, 2, 2, 2],
659         [1, 1, 1, 2, 2, 2, 2],
660         [3, 3, 3, 4, 4, 4, 4],
661         [3, 3, 3, 4, 4, 4, 4],
662         [3, 3, 3, 4, 4, 4, 4],
663     ];
664 
665     for (int y = -2; y < 4; ++y)
666     {
667         for (int x = -2; x < 5; ++x)
668         {            
669             L8 read = img.unsafeScanlinePtr(y)[x];
670             ubyte good = correct[y+2][x+2];
671             assert(read.l == good);
672         }
673     }
674 }
675 
676 // Test toRef
677 unittest
678 {
679     OwnedImage!L8 img = mallocNew!(OwnedImage!L8);
680     scope(exit) destroyFree(img);
681 
682     int border = 0;
683     int rowAlignment = 1;
684     int xMultiplicity = 1;
685     int trailingSamples = 4;
686     img.size(10, 10, border, rowAlignment, xMultiplicity, trailingSamples); // Ask for 4 trailing samples
687     ImageRef!L8 r = img.toRef();
688     assert(r.pitch >= 10 + 4); // per the API, OwnedImage could add more space than required
689 }
690 
691 /// Loads an image from compressed data.
692 /// The returned `OwnedImage!RGBA` should be destroyed with `destroyFree`.
693 /// Throws: $(D ImageIOException) on error.
694 OwnedImage!RGBA loadOwnedImage(in void[] imageData)
695 {
696     Image image;
697     image.loadFromMemory(cast(const(ubyte[])) imageData, 
698                          LOAD_RGB | LOAD_8BIT | LOAD_ALPHA | LAYOUT_VERT_STRAIGHT | LAYOUT_GAPLESS);
699     if (image.isError)
700     {
701         assert(false, "Decoding failed"); // FUTURE: could do something more sensible maybe
702     }
703 
704     return convertImageToOwnedImage_rgba8(image);
705 }
706 
707 /// Loads two different images:
708 /// - the 1st is the RGB channels
709 /// - the 2nd is interpreted as greyscale and fetch in the alpha channel of the result.
710 /// The returned `OwnedImage!RGBA` should be destroyed with `destroyFree`.
711 OwnedImage!RGBA loadImageSeparateAlpha(in void[] imageDataRGB, in void[] imageDataAlpha)
712 {
713     Image alpha;
714     alpha.loadFromMemory(cast(const(ubyte[])) imageDataAlpha, 
715                          LOAD_GREYSCALE | LOAD_8BIT | LOAD_NO_ALPHA | LAYOUT_VERT_STRAIGHT | LAYOUT_GAPLESS);
716     if (alpha.isError)
717     {
718         assert(false, "Decoding failed"); // same remark as above
719     }
720 
721     Image rgb;
722     rgb.loadFromMemory(cast(const(ubyte[])) imageDataRGB, 
723                        LOAD_RGB | LOAD_8BIT | LOAD_ALPHA | LAYOUT_VERT_STRAIGHT | LAYOUT_GAPLESS);
724     if (rgb.isError)
725     {
726         assert(false, "Decoding failed"); // same remark as above
727     }
728 
729     if ( (rgb.width != alpha.width) || (rgb.height != alpha.height) )
730     {
731         // If you fail here, typically size of your Diffuse map doesn't match the Emissive map.
732         assert(false, "Image size mismatch"); // same remark as above
733     }
734 
735     int W = rgb.width;
736     int H = rgb.height;
737 
738     for (int y = 0; y < H; ++y)
739     {
740         ubyte* scanA = cast(ubyte*) alpha.scanline(y);
741         ubyte* scanRGBA = cast(ubyte*) rgb.scanline(y);
742         for (int x = 0; x < W; ++x)
743         {
744             scanRGBA[4*x + 3] = scanA[x];
745         }
746     }
747 
748     return convertImageToOwnedImage_rgba8(rgb);
749 }
750 
751 /// Loads two different images:
752 /// - the 1st is the RGB channels
753 /// - the 2nd is interpreted as greyscale and fetch in the alpha channel of the result.
754 /// The returned `OwnedImage!RGBA` should be destroyed with `destroyFree`.
755 OwnedImage!RGBA loadImageWithFilledAlpha(in void[] imageDataRGB, ubyte alphaValue)
756 {
757     Image rgb;
758     rgb.loadFromMemory(cast(const(ubyte[])) imageDataRGB, 
759                        LOAD_RGB | LOAD_8BIT | LOAD_ALPHA | LAYOUT_VERT_STRAIGHT | LAYOUT_GAPLESS);
760     if (rgb.isError)
761     {
762         assert(false, "Decoding failed"); // same remark as above
763     }
764     int W = rgb.width;
765     int H = rgb.height;
766     for (int y = 0; y < H; ++y)
767     {
768         ubyte* scanRGBA = cast(ubyte*) rgb.scanline(y);
769         for (int x = 0; x < W; ++x)
770         {
771             scanRGBA[4*x + 3] = alphaValue;
772         }
773     }
774     return convertImageToOwnedImage_rgba8(rgb);
775 }
776 
777 
778 /// Loads an image to be a 16-bit one channel image (`OwnedImage!L16`).
779 /// The returned `OwnedImage!L16` should be destroyed with `destroyFree`.
780 /// Throws: $(D ImageIOException) on error.
781 OwnedImage!L16 loadOwnedImageDepth(in void[] imageData)
782 {
783     Image image;
784     image.loadFromMemory(cast(const(ubyte[])) imageData, LOAD_NO_ALPHA);
785     if (image.isError)
786     {
787         assert(false, "Decoding failed");
788     }
789   
790     if (image.type() == PixelType.rgb8)
791     {
792         // Legacy 8-bit to 16-bit depth conversion
793         // This is the original legacy way to load depth.
794         // Load as 8-bit RGBA, then mix the channels so as to support
795         // legacy depth maps.
796         Image image16;
797         image16.setSize(image.width, image.height, PixelType.l16, LAYOUT_VERT_STRAIGHT | LAYOUT_GAPLESS);
798         int width = image.width;
799         int height = image.height;
800 
801         OwnedImage!L16 result = mallocNew!(OwnedImage!L16)(width, height);
802 
803         for (int j = 0; j < height; ++j)
804         {
805             ubyte* inDepth = cast(ubyte*) image.scanline(j);
806             ushort* outDepth = cast(ushort*) image16.scanline(j);
807 
808             for (int i = 0; i < width; ++i)
809             {
810                 // Using 257 to span the full range of depth: 257 * (255+255+255)/3 = 65535
811                 // If we do that inside stb_image, it will first reduce channels _then_ increase bitdepth.
812                 // Instead, this is used to gain 1.5 bit of accuracy for 8-bit depth maps. :)
813                 float d = 0.5f + 257 * (inDepth[3*i+0] + inDepth[3*i+1] + inDepth[3*i+2]) / 3; 
814                 outDepth[i] = cast(ushort)(d);
815             }
816         }
817         return convertImageToOwnedImage_l16(image16);
818     }
819     else
820     {
821         // Ensure the format pixel is l16, and the layout is compatible with OwnedImage
822         image.convertTo(PixelType.l16, LAYOUT_VERT_STRAIGHT | LAYOUT_GAPLESS);
823         return convertImageToOwnedImage_l16(image);
824     }
825 }
826 
827 /// Make a gamut `Image` view from a dplug:graphics `ImageRef`.
828 /// The original `OwnedImage` is still the owner of the pixel data.
829 void createViewFromImageRef(COLOR)(ref Image view, ImageRef!COLOR source)
830 {
831     static if (is(COLOR == RGBA))
832     {
833         view.createViewFromData(source.pixels, source.w, source.h, PixelType.rgba8, cast(int)source.pitch);
834     }
835     else static if (is(COLOR == RGB))
836     {
837         view.createViewFromData(source.pixels, source.w, source.h, PixelType.rgb8, cast(int)source.pitch);
838     }
839     else static if (is(COLOR == L8))
840     {
841         view.createViewFromData(source.pixels, source.w, source.h, PixelType.l8, cast(int)source.pitch);
842     }
843     else static if (is(COLOR == L16))
844     {
845         view.createViewFromData(source.pixels, source.w, source.h, PixelType.l16, cast(int)source.pitch);
846     }
847     else
848         static assert(false);
849 }
850 
851 /// On the contrary, get an ImageRef!XXX from a Gamut `Image` without disowning it.
852 ImageRef!COLOR getImageRef(COLOR)(ref Image image)
853 {
854     // Must check dynamic type though.
855     static if (is(COLOR == L8))
856         assert(image.type() == PixelType.l8);
857     else static if (is(COLOR == L16))
858         assert(image.type() == PixelType.l16);
859     else static if (is(COLOR == RGB))
860         assert(image.type() == PixelType.rgb8);
861     else static if (is(COLOR == RGBA))
862         assert(image.type() == PixelType.rgba8);
863     else static if (is(COLOR == RGBA16))
864         assert(image.type() == PixelType.rgba16);
865     else 
866         static assert(false);
867 
868     ImageRef!COLOR r;
869     r.w = image.width();
870     r.h = image.height();
871     assert(image.pitchInBytes() >= 0); // Doesn't work for negative pitches.
872     r.pitch = image.pitchInBytes;
873     r.pixels = cast(COLOR*) image.scanptr(0);   
874     return r;
875 }
876 
877 
878 /// Convert and disown gamut Image to OwnedImage.
879 /// Result: ìmage` is disowned, result is owning the image data.
880 OwnedImage!RGBA convertImageToOwnedImage_rgba8(ref Image image)
881 {
882     assert(image.type() == PixelType.rgba8);
883     OwnedImage!RGBA r = mallocNew!(OwnedImage!RGBA);
884     r.w = image.width;
885     r.h = image.height;    
886     r._pixels = cast(RGBA*) image.scanline(0);
887     r._bytePitch = image.pitchInBytes();
888     r._buffer = image.disownData();
889     r._border = 0;      // TODO should keep it from input
890     r._borderRight = 0; // TODO should keep it from input
891     return r;
892 }
893 
894 ///ditto
895 OwnedImage!RGB convertImageToOwnedImage_rgb8(ref Image image)
896 {
897     assert(image.type() == PixelType.rgb8);
898     OwnedImage!RGB r = mallocNew!(OwnedImage!RGB);
899     r.w = image.width;
900     r.h = image.height;    
901     r._pixels = cast(RGB*) image.scanline(0);
902     r._bytePitch = image.pitchInBytes();
903     r._buffer = image.disownData();
904     r._border = 0;      // TODO should keep it from input
905     r._borderRight = 0; // TODO should keep it from input
906     return r;
907 }
908 
909 ///ditto
910 OwnedImage!L16 convertImageToOwnedImage_l16(ref Image image)
911 {
912     assert(image.type() == PixelType.l16);
913     OwnedImage!L16 r = mallocNew!(OwnedImage!L16);
914     r.w = image.width;
915     r.h = image.height;    
916     r._pixels = cast(L16*) image.scanline(0);
917     r._bytePitch = image.pitchInBytes();
918     r._buffer = image.disownData();
919     r._border = 0;      // TODO should keep it from input
920     r._borderRight = 0; // TODO should keep it from input
921     return r;
922 }
923 
924 ///ditto
925 OwnedImage!L8 convertImageToOwnedImage_l8(ref Image image)
926 {
927     assert(image.type() == PixelType.l8);
928     OwnedImage!L8 r = mallocNew!(OwnedImage!L8);
929     r.w = image.width;
930     r.h = image.height;    
931     r._pixels = cast(L8*) image.scanline(0);
932     r._bytePitch = image.pitchInBytes();
933     r._buffer = image.disownData();
934     r._border = 0;      // TODO should keep it from input
935     r._borderRight = 0; // TODO should keep it from input
936     return r;
937 }