1 // Written in the D programming language.
2 
3 /**
4 * Copyright: Copyright 2012 -
5 * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
6 * Authors: Callum Anderson
7 * Date: June 8, 2012
8 */
9 module imaged.image;
10 
11 import
12     std.file,
13     std.math,
14     std.stdio,
15     std.conv,
16     std.path,
17     undead.stream;
18 
19 import
20     imaged.jpeg,
21     imaged.png;
22 
23 
24 // Convenience function for loading from a file
25 Image load(string filename, out IMGError err, bool logging = false)
26 {
27     Decoder dcd = getDecoder(filename, logging);
28 
29     if (dcd is null)
30     {
31         return null;
32     }
33     else
34     {
35         err = dcd.errorState;
36         dcd.parseFile(filename);
37         return dcd.image;
38     }
39 }
40 
41 
42 // Convenience function for getting a decoder for a given filename
43 Decoder getDecoder(string filename, bool logging = false)
44 {
45     Decoder dcd = null;
46 
47     switch(extension(filename))
48     {
49     case ".jpg":
50     case ".jpeg": dcd = new JpegDecoder(logging); break;
51     case ".png":  dcd = new PngDecoder(logging);  break;
52     default: writeln("Imaged: no loader for extension " ~ extension(filename));
53     }
54     return dcd;
55 }
56 
57 // Convenience function for getting an encoder for a given filename
58 Encoder getEncoder(string filename)
59 {
60     Encoder enc = null;
61 
62     switch(extension(filename))
63     {
64     case ".png":  enc = new PngEncoder();  break;
65     default: writeln("Imaged: no loader for extension " ~ extension(filename));
66     }
67     return enc;
68 }
69 
70 
71 /**
72 * The functions below allow you to create an OpenGL texture from an Image, and then
73 * throw away the image. These functions assume that you have already called DerelictGL.load().
74 */
75 version(OpenGL)
76 {
77     import derelict.opengl3.gl;
78 
79     /**
80     * Set internal format to force a specific format, else the format will
81     * be chosen based on the image type.
82     */
83     GLuint loadTexture(string filename, GLuint internalFormat = 0,
84                                         bool logging = false,
85                                         IMGError err = IMGError())
86     {
87         // Keep a static lookup table for images/textures which have already been loaded
88         static GLuint[string] loadedTextures;
89 
90         auto ptr = filename in loadedTextures;
91         if (ptr !is null)
92         {
93             // Texture has already been loaded, just return the GL handle
94             return *ptr;
95         }
96         else
97         {
98             // Load the texture and store it in the lookup table
99             GLuint tex = makeGLTexture(filename, internalFormat, logging, err);
100             loadedTextures[filename] = tex;
101             return tex;
102         }
103     }
104 
105     GLuint makeGLTexture(string filename, GLuint internalFormat = 0,
106                                           bool logging = false,
107                                           IMGError err = IMGError())
108     {
109         debug { writeln("Making GL Texture from: " ~ filename); }
110 
111         GLuint tex = 0;
112         GLenum texformat;
113         GLint nchannels;
114 
115         glGenTextures(1, &tex);
116         auto img = load(filename, err, logging);
117 
118         if (img.pixelFormat == Px.R8G8B8)
119         {
120             nchannels = 3;
121             texformat = GL_RGB;
122             debug { writeln("Texture format is: GL_RGB"); }
123         }
124         else if (img.pixelFormat == Px.R8G8B8A8)
125         {
126             nchannels = 4;
127             texformat = GL_RGBA;
128             debug { writeln("Texture format is: GL_RGBA"); }
129         }
130         else if (img.pixelFormat == Px.L8)
131         {
132             nchannels = 1;
133             texformat = GL_LUMINANCE;
134             debug { writeln("Texture format is: GL_LUMINANCE"); }
135         }
136 
137         GLuint useFormat = nchannels;
138         if (internalFormat != 0)
139             useFormat = internalFormat;
140 
141         /// Bind the texture object.
142         glBindTexture(GL_TEXTURE_2D, tex);
143 
144         /// Set the texture interp properties.
145         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
146         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
147 
148         /// Create the tex data.
149         if (DerelictGL3.loadedVersion < GLVersion.GL30)
150         {
151             glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
152             glTexImage2D(GL_TEXTURE_2D, 0, useFormat, cast(int)img.width, cast(int)img.height, 0,
153                          texformat, GL_UNSIGNED_BYTE, img.pixels.ptr);
154         }
155         else
156         {
157             glTexImage2D(GL_TEXTURE_2D, 0, useFormat, cast(int)img.width, cast(int)img.height, 0,
158                          texformat, GL_UNSIGNED_BYTE, img.pixels.ptr);
159             glGenerateMipmap(GL_TEXTURE_2D);
160         }
161 
162         return tex;
163     } // makeGLTexture
164 }
165 
166 
167 // Structure to report loading/decoding errors
168 struct IMGError
169 {
170     string message;
171     int code;
172 }
173 
174 
175 // Interface for an image decoder
176 abstract class Decoder
177 {
178     // Parse a single byte
179     void parseByte(ubyte bite);
180 
181 
182     // Parse a file directly
183     void parseFile(in string filename)
184     {
185         // Loop through the image data
186         auto data = cast(ubyte[]) read(filename);
187         foreach (bite; data)
188         {
189             if (m_errorState.code == 0)
190             {
191                 parseByte(bite);
192             }
193             else
194             {
195                 debug
196                 {
197                     if (m_logging) writeln("IMAGE ERROR: ", m_errorState.message);
198                 }
199                 break;
200             }
201         }
202     } // parseFile
203 
204 
205     // Parse from the stream. Returns the amount of data left in the stream.
206     size_t parseStream(Stream stream, in size_t chunkSize = 100000)
207     {
208         if (!stream.readable)
209         {
210             m_errorState.code = 1;
211             m_errorState.message = "DECODER: Stream is not readable";
212             return 0;
213         }
214 
215         ubyte[] buffer;
216         buffer.length = chunkSize;
217         size_t rlen = stream.readBlock(cast(void*)buffer.ptr, chunkSize*ubyte.sizeof);
218         if (rlen)
219         {
220             foreach(bite; buffer)
221             {
222                 if (m_errorState.code != 0)
223                 {
224                     rlen = 0;
225                     break;
226                 }
227                 parseByte(bite);
228             }
229         }
230         return rlen;
231     }
232 
233     // Getters
234     @property Image image() { return m_image; }
235     @property IMGError errorState() const { return m_errorState; } // ditto
236 
237 protected:
238     bool m_logging = false; // if true, will emit logs when in debug mode
239     IMGError m_errorState;
240     Image m_image;
241 }
242 
243 
244 // Interface for an image encoder
245 abstract class Encoder
246 {
247     bool write(in Image img, string filename);
248 }
249 
250 
251 // Currently allowed pixel formats
252 enum Px
253 {
254     L8,
255     L8A8,
256     R8G8B8,
257     R8G8B8A8,
258     L16,
259     L16A16,
260     R16G16B16,
261     R16G16B16A16
262 }
263 
264 // Enumerate the image formats
265 enum ImageFormat
266 {
267     GETFROMEXTENSION,
268     PNG,
269     JPG,
270     JPEG
271 }
272 
273 
274 // Container for RGBA values
275 struct Pixel
276 {
277     /**
278     * Note that alpha defaults to opaque for _8 bit_ formats.
279     * For 16 bit formats, be aware of this.
280     */
281     ushort r, g, b, a = 255;
282 
283     this(int r, int g, int b, int a = 255)
284     {
285         this.r = cast(ushort) r;
286         this.g = cast(ushort) g;
287         this.b = cast(ushort) b;
288         this.a = cast(ushort) a;
289     }
290 }
291 
292 
293 // Interface for an Image
294 interface Image
295 {
296     // Algorithm for image resizing
297     enum ResizeAlgo {
298         CROP,
299         NEAREST,
300         BILINEAR,
301         BICUBIC
302     }
303 
304     // Overload index operator to return pixel at (x,y) coords (y is measured from the top down)
305     Pixel opIndex(size_t x, size_t y, bool scaleToByte = true);
306 
307     // Simply calls opIndex
308     Pixel getPixel(size_t x, size_t y, bool scaleToByte = true);
309 
310     // Set the pixel at (x,y) from the given Pixel
311     void setPixel(size_t x, size_t y, Pixel p);
312 
313     // Set the pixel at (x,y) from the given ubyte array
314     void setPixel(size_t x, size_t y, const(ubyte[]) data);
315 
316     // Set a complete row of the image, from the supplied buffer
317     void setRow(size_t y, const(ubyte[]) data);
318 
319     // Return a copy of the current image
320     Image copy() const;
321 
322     // Resize the image, either by cropping, nearest neighbor or bilinear algorithms
323     bool resize(uint newWidth, uint newHeight, ResizeAlgo algo = ResizeAlgo.NEAREST);
324 
325     // Write image to disk as
326     bool write(string filename, ImageFormat fmt = ImageFormat.GETFROMEXTENSION);
327 
328     // Getters
329     @property const(uint) width() const;
330     @property const(uint) height() const; // ditto
331     @property const(int) stride() const; // ditto
332     @property const(uint) bitDepth() const; // ditto
333     @property const(Px) pixelFormat() const; // ditto
334     @property const(ubyte[]) pixels() const; // ditto
335     @property ref ubyte[] pixels(); // ditto
336     @property ubyte* pixelsPtr(); // ditto
337 }
338 
339 
340 // The image class backend, parameterized by pixel format
341 class Img(Px F) : Image
342 {
343     this(uint width, uint height, bool noAlloc = false)
344     {
345         static if (F == Px.L8)
346         {
347             m_bitDepth = 8;
348             m_channels = 1;
349         }
350         else if (F == Px.L8A8)
351         {
352             m_bitDepth = 8;
353             m_channels = 2;
354         }
355         else if (F == Px.L8A8)
356         {
357             m_bitDepth = 8;
358             m_channels = 2;
359         }
360         else if (F == Px.R8G8B8)
361         {
362             m_bitDepth = 8;
363             m_channels = 3;
364         }
365         else if (F == Px.R8G8B8A8)
366         {
367             m_bitDepth = 8;
368             m_channels = 4;
369         }
370         else if (F == Px.L16)
371         {
372             m_bitDepth = 16;
373             m_channels = 1;
374         }
375         else if (F == Px.L16A16)
376         {
377             m_bitDepth = 16;
378             m_channels = 2;
379         }
380         else if (F == Px.R16G16B16)
381         {
382             m_bitDepth = 16;
383             m_channels = 3;
384         }
385         else if (F == Px.R16G16B16A16)
386         {
387             m_bitDepth = 16;
388             m_channels = 4;
389         }
390 
391         m_width = width;
392         m_height = height;
393         m_stride = (m_bitDepth/8)*m_channels;
394 
395         // Allocate data array
396         if (!noAlloc)
397             m_data = new ubyte[](width*height*m_stride);
398     }
399 
400 
401     // Create an image using a pre-existing buffer
402     this(uint width, uint height, ubyte[] data)
403     {
404         this(width, height, true);
405         m_data = data;
406     }
407 
408 
409     /**
410     * Get the pixel at the given index. If scaleToByte is set,
411     * 16 bit formats will only return the high bytes, effectively
412     * reducing precision to 8bit.
413     */
414     Pixel opIndex(size_t x, size_t y, bool scaleToByte = true)
415     {
416         auto index = getIndex(x, y);
417 
418         static if (F == Px.L8)
419         {
420             auto v = m_data[index];
421             return Pixel(v,v,v);
422         }
423         else if (F == Px.L8A8)
424         {
425             auto v = m_data[index];
426             return Pixel(v, v, v, m_data[index+1]);
427         }
428         else if (F == Px.R8G8B8)
429         {
430             return Pixel(m_data[index],m_data[index+1],m_data[index+2]);
431         }
432         else if (F == Px.R8G8B8A8)
433         {
434             return Pixel(m_data[index],m_data[index+1],m_data[index+2],m_data[index+3]);
435         }
436         else if (F == Px.L16)
437         {
438             int v;
439             if (scaleToByte)
440             {
441                 v = m_data[index];
442                 return Pixel(v,v,v,ubyte.max);
443             }
444             else
445             {
446                 v = m_data[index] << 8 | m_data[index+1];
447                 return Pixel(v,v,v,ushort.max);
448             }
449 
450         }
451         else if (F == Px.L16A16)
452         {
453             int v, a;
454             if (scaleToByte)
455             {
456                 v = m_data[index];
457                 a = m_data[index+2];
458             }
459             else
460             {
461                 v = m_data[index] << 8 | m_data[index+1];
462                 a = m_data[index+2] << 8 | m_data[index+3];
463             }
464             return Pixel(v,v,v,a);
465         }
466         else if (F == Px.R16G16B16)
467         {
468             int r, g, b;
469             if (scaleToByte)
470             {
471                 r = m_data[index];
472                 g = m_data[index+2];
473                 b = m_data[index+4];
474                 return Pixel(r,g,b,ubyte.max);
475             }
476             else
477             {
478                 r = m_data[index] << 8 | m_data[index+1];
479                 g = m_data[index+2] << 8 | m_data[index+3];
480                 b = m_data[index+4] << 8 | m_data[index+5];
481                 return Pixel(r,g,b,ushort.max);
482             }
483         }
484         else if (F == Px.R16G16B16A16)
485         {
486             int r, g, b, a;
487             if (scaleToByte)
488             {
489                 r = m_data[index];
490                 g = m_data[index+2];
491                 b = m_data[index+4];
492                 a = m_data[index+6];
493             }
494             else
495             {
496                 r = m_data[index] << 8 | m_data[index+1];
497                 g = m_data[index+2] << 8 | m_data[index+3];
498                 b = m_data[index+4] << 8 | m_data[index+5];
499                 a = m_data[index+6] << 8 | m_data[index+7];
500             }
501             return Pixel(r,g,b,a);
502         }
503     }
504 
505 
506     // Simply a different way of calling opIndex
507     Pixel getPixel(size_t x, size_t y, bool scaleToByte = true)
508     {
509         return this[x,y,scaleToByte];
510     }
511 
512 
513     // Set the pixel at the given index
514     void setPixel(size_t x, size_t y, Pixel p)
515     {
516         auto index = getIndex(x, y);
517 
518         static if (F == Px.L8)
519         {
520             m_data[index] = cast(ubyte)p.r;
521         }
522         else if (F == Px.L8A8)
523         {
524             m_data[index] = cast(ubyte)p.r;
525             m_data[index+1] = cast(ubyte)p.a;
526         }
527         else if (F == Px.R8G8B8)
528         {
529             m_data[index] = cast(ubyte)p.r;
530             m_data[index+1] = cast(ubyte)p.g;
531             m_data[index+2] = cast(ubyte)p.b;
532         }
533         else if (F == Px.R8G8B8A8)
534         {
535             m_data[index] = cast(ubyte)p.r;
536             m_data[index+1] = cast(ubyte)p.g;
537             m_data[index+2] = cast(ubyte)p.b;
538             m_data[index+3] = cast(ubyte)p.a;
539         }
540         else if (F == Px.L16)
541         {
542             m_data[index] = cast(ubyte)(p.r >> 8);
543             m_data[index+1] = cast(ubyte)(p.r & 0xFF);
544         }
545         else if (F == Px.L16A16)
546         {
547             m_data[index] = cast(ubyte)(p.r >> 8);
548             m_data[index+1] = cast(ubyte)(p.r & 0xFF);
549             m_data[index+2] = cast(ubyte)(p.a >> 8);
550             m_data[index+3] = cast(ubyte)(p.a & 0xFF);
551         }
552         else if (F == Px.R16G16B16)
553         {
554             m_data[index] = cast(ubyte)(p.r >> 8);
555             m_data[index+1] = cast(ubyte)(p.r & 0xFF);
556             m_data[index+2] = cast(ubyte)(p.g >> 8);
557             m_data[index+3] = cast(ubyte)(p.g & 0xFF);
558             m_data[index+4] = cast(ubyte)(p.b >> 8);
559             m_data[index+5] = cast(ubyte)(p.b & 0xFF);
560         }
561         else if (F == Px.R16G16B16A16)
562         {
563             m_data[index] = cast(ubyte)(p.r >> 8);
564             m_data[index+1] = cast(ubyte)(p.r & 0xFF);
565             m_data[index+2] = cast(ubyte)(p.g >> 8);
566             m_data[index+3] = cast(ubyte)(p.g & 0xFF);
567             m_data[index+4] = cast(ubyte)(p.b >> 8);
568             m_data[index+5] = cast(ubyte)(p.b & 0xFF);
569             m_data[index+6] = cast(ubyte)(p.a >> 8);
570             m_data[index+7] = cast(ubyte)(p.a & 0xFF);
571         }
572     }
573 
574     // Set the pixel at the given index
575     void setPixel(size_t x, size_t y, const(ubyte[]) data)
576     {
577         auto index = getIndex(x, y);
578 
579         static if (F == Px.L8)
580         {
581             setPixel(x, y, Pixel(data[0],0,0,0));
582         }
583         else if (F == Px.L8A8)
584         {
585             setPixel(x, y, Pixel(data[0],
586                                  0,
587                                  0,
588                                  data[1]));
589         }
590         else if (F == Px.R8G8B8)
591         {
592             setPixel(x, y, Pixel(data[0],
593                                  data[1],
594                                  data[2],
595                                  0));
596         }
597         else if (F == Px.R8G8B8A8)
598         {
599             setPixel(x, y, Pixel(data[0],
600                                  data[1],
601                                  data[2],
602                                  data[3]));
603         }
604         else if (F == Px.L16)
605         {
606             setPixel(x, y, Pixel(data[0] << 8 | data[1],0,0,0));
607         }
608         else if (F == Px.L16A16)
609         {
610             setPixel(x, y, Pixel(data[0] << 8 | data[1],
611                                  0,
612                                  0,
613                                  data[2] << 8 | data[3]));
614         }
615         else if (F == Px.R16G16B16)
616         {
617             setPixel(x, y, Pixel(data[0] << 8 | data[1],
618                                  data[2] << 8 | data[3],
619                                  data[4] << 8 | data[5],
620                                  0));
621         }
622         else if (F == Px.R16G16B16A16)
623         {
624             setPixel(x, y, Pixel(data[0] << 8 | data[1],
625                                  data[2] << 8 | data[3],
626                                  data[4] << 8 | data[5],
627                                  data[6] << 8 | data[7]));
628         }
629     }
630 
631 
632 
633     // Set a whole row (scanline) of data from the given buffer. Rows count down from the top.
634     void setRow(size_t y, const(ubyte[]) data)
635     {
636         auto takeBytes = m_width*m_stride;
637         auto index = getIndex(0, y);
638 
639         debug   // Check array bounds if debug mode
640         {
641             if (data.length < takeBytes)
642             {
643                 writeln(takeBytes, ", ", data.length);
644                 throw new Exception("Image setRow: buffer does not have required length!");
645             }
646         }
647         m_data[index..index+takeBytes] = data[];
648     }
649 
650 
651     // Return an Image which is a copy of this one
652     Img!F copy() const
653     {
654         auto copy = new Img!F(m_width, m_height);
655         copy.pixels = m_data.dup;
656         return copy;
657     }
658 
659 
660     /*
661     * Resize an image to the given dimensions, using the given algorithm.
662     * Returns: true on successful resize, else false.
663     */
664     bool resize(uint newWidth, uint newHeight, ResizeAlgo algo)
665     {
666         // If new dimensions are same as old ones, flag success
667         if (newWidth == m_width && newHeight == m_height)
668         {
669             return true;
670         }
671 
672         // If old dimensions are 1x1, and algo is BILINEAR, switch to NEAREST
673         if ((m_width == 1 || m_height == 1) && algo == ResizeAlgo.BILINEAR)
674         {
675             algo = ResizeAlgo.NEAREST;
676         }
677 
678         // Create a delegate to define the resizing algorithm
679         ushort[4] delegate(Img!F, float, float, uint, uint) algorithmDelegate;
680 
681         if (algo == ResizeAlgo.NEAREST)
682         {
683             algorithmDelegate = &getNearestNeighbour;
684         }
685         else if (algo == ResizeAlgo.BILINEAR)
686         {
687             algorithmDelegate = &getBilinearInterpolate;
688         }
689         else if (algo == ResizeAlgo.CROP)
690         {
691             algorithmDelegate = &getCropped;
692         }
693         else
694         {
695             return false; // Algorithm not implemented!!
696         }
697 
698         // Make a copy of the current image, this is the 'source'
699         auto oldImg = this.copy();
700         int oldWidth = oldImg.width;
701         int oldHeight = oldImg.height;
702 
703         // Allocate a new array to hold the new image
704         m_data = new ubyte[](newWidth*newHeight*m_stride);
705         m_width = newWidth;
706         m_height = newHeight;
707 
708         uint i = 0; // 1D array index
709         float x_ratio = cast(float)(oldWidth-1)/cast(float)(newWidth);
710         float y_ratio = cast(float)(oldHeight-1)/cast(float)(newHeight);
711 
712         // Loop through rows and columns of the new image
713         foreach (row; 0..newHeight)
714         {
715             foreach (col; 0..newWidth)
716             {
717                 float x = x_ratio * cast(float)col;
718                 float y = y_ratio * cast(float)row;
719 
720                 // Use the selected algorithm to get the pixel value
721                 ushort[4] p = algorithmDelegate(oldImg, x, y, col, row);
722 
723                 // Store the new pixel
724                 static if (F == Px.L8)
725                 {
726                     m_data[i+col] = cast(ubyte)p[0];
727                 }
728                 else if (F == Px.L8A8 ||
729                          F == Px.R8G8B8 ||
730                          F == Px.R8G8B8A8 )
731                 {
732                     m_data[(i+col)*m_stride..(i+col + 1)*m_stride] = to!(ubyte[])(p[0..m_stride]);
733                 }
734                 else if (F == Px.L16)
735                 {
736                     m_data[(i+col)*m_stride..(i+col + 1)*m_stride] = [p[0] >> 8, p[0] & 0xFF];
737                 }
738                 else if (F == Px.L16A16)
739                 {
740                     m_data[(i+col)*m_stride..(i+col + 1)*m_stride] = [p[0] >> 8, p[0] & 0xFF,
741                             p[3] >> 8, p[3] & 0xFF];
742                 }
743                 else if (F == Px.R16G16B16)
744                 {
745                     m_data[(i+col)*m_stride..(i+col + 1)*m_stride] = [p[0] >> 8, p[0] & 0xFF,
746                             p[1] >> 8, p[1] & 0xFF,
747                             p[2] >> 8, p[2] & 0xFF];
748                 }
749                 else if (F == Px.R16G16B16A16)
750                 {
751                     m_data[(i+col)*m_stride..(i+col + 1)*m_stride] = [p[0] >> 8, p[0] & 0xFF,
752                             p[1] >> 8, p[1] & 0xFF,
753                             p[2] >> 8, p[2] & 0xFF,
754                             p[3] >> 8, p[3] & 0xFF];
755                 }
756 
757             } // columns
758             i += m_width;
759         }
760 
761         return true; // successfully resized
762     } // resize
763 
764     /**
765     * Write this image to disk with the given filename. Format can be inferred from
766     * filename extension by default, or supplied explicitly.
767     */
768     bool write(string filename, ImageFormat fmt = ImageFormat.GETFROMEXTENSION)
769     {
770         Encoder enc;
771         if (fmt == ImageFormat.GETFROMEXTENSION)
772         {
773             enc = getEncoder(filename);
774         }
775         else if (fmt == ImageFormat.PNG)
776         {
777             enc = new PngEncoder();
778         }
779 
780         if (enc is null)
781             return false;
782         else
783             return enc.write(this, filename);
784     }
785 
786 
787     // Getters
788     @property const(uint) width() const { return m_width; } // ditto
789     @property const(uint) height() const { return m_height; } // ditto
790     @property const(int) stride() const { return m_stride; } // ditto
791     @property const(uint) bitDepth() const { return m_bitDepth; } // ditto
792     @property const(Px) pixelFormat() const { return F; } // ditto
793     @property const(ubyte[]) pixels() const { return m_data; } // ditto
794     @property ref ubyte[] pixels() { return m_data; } // ditto
795     @property ubyte* pixelsPtr() { return m_data.ptr; } // ditto
796 
797 
798 private:
799 
800     // Get the byte index and bit offset for a given (x,y)
801     uint getIndex(size_t x, size_t y)
802     {
803         return cast(uint)(x + y*m_width)*m_stride;
804     }
805 
806 
807     // Cropping algorithm - If (x,y) is in the original, return that pixel, else return 0,0,0,0
808     ushort[4] getCropped(Img!F i, float x, float y, uint col, uint row)
809     {
810         Pixel p;
811         if (col < i.width && row < i.height)
812         {
813             p = i[col, row, false];
814         }
815         else
816         {
817             p = Pixel(0,0,0); // note: alpha is left as default here, so it will appear black
818         }
819         return [p.r, p.g, p.b, p.a];
820     }
821 
822 
823     // Nearest neighbour sampling (actually just the nearest neighbour to the left and down)
824     ushort[4] getNearestNeighbour(Img!F i, float x, float y, uint col, uint row)
825     {
826         int x0 = cast(int)x;
827         int y0 = cast(int)y;
828         Pixel p = i[x0, y0, false];
829         return [p.r, p.g, p.b, p.a];
830     }
831 
832     /**
833     * Calculate a bilinear interpolate at x, y. This implementation is from:
834     * http://fastcpp.blogspot.com/2011/06/bilinear-pixel-interpolation-using-sse.html
835     */
836     ushort[4] getBilinearInterpolate(Img!F i, float x, float y, uint col, uint row)
837     {
838         int x0 = cast(int)x;
839         int y0 = cast(int)y;
840 
841         // Weighting factors
842         float fx = x - x0;
843         float fy = y - y0;
844         float fx1 = 1.0f - fx;
845         float fy1 = 1.0f - fy;
846 
847         /** Get the locations in the src array of the 2x2 block surrounding (row,col)
848         * 01 ------- 11
849         * | (row,col) |
850         * 00 ------- 10
851         */
852         Pixel p1 = i[x0, y0, false];
853         Pixel p2 = i[x0+1, y0, false];
854         Pixel p3 = i[x0, y0+1, false];
855         Pixel p4 = i[x0+1, y0+1, false];
856 
857         int wgt1 = cast(int)(fx1 * fy1 * 256.0f);
858         int wgt2 = cast(int)(fx  * fy1 * 256.0f);
859         int wgt3 = cast(int)(fx1 * fy  * 256.0f);
860         int wgt4 = cast(int)(fx  * fy  * 256.0f);
861 
862         int r = (p1.r * wgt1 + p2.r * wgt2 + p3.r * wgt3 + p4.r * wgt4) >> 8;
863         int g = (p1.g * wgt1 + p2.g * wgt2 + p3.g * wgt3 + p4.g * wgt4) >> 8;
864         int b = (p1.b * wgt1 + p2.b * wgt2 + p3.b * wgt3 + p4.b * wgt4) >> 8;
865         int a = (p1.a * wgt1 + p2.a * wgt2 + p3.a * wgt3 + p4.a * wgt4) >> 8;
866 
867         return [cast(short)r, cast(short)g, cast(short)b, cast(short)a];
868     }
869 
870     uint m_width = 0, m_height = 0;
871     int m_stride = 0; // in bytes (minimum 1)
872     uint m_bitDepth = 0;
873     uint m_channels = 0;
874     ubyte[] m_data;
875 }
876