1 /*
2 Copyright (c) 2011-2021 Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 
6 Permission is hereby granted, free of charge, to any person or organization
7 obtaining a copy of the software and accompanying documentation covered by
8 this license (the "Software") to use, reproduce, display, distribute,
9 execute, and transmit the Software, and to prepare derivative works of the
10 Software, and to permit third-parties to whom the Software is furnished to
11 do so, all subject to the following:
12 
13 The copyright notices in the Software and this entire statement, including
14 the above license grant, this restriction and the following disclaimer,
15 must be included in all copies of the Software, in whole or in part, and
16 all derivative works of the Software, unless such copies or derivative
17 works are solely in the form of machine-executable object code generated by
18 a source language processor.
19 
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 DEALINGS IN THE SOFTWARE.
27 */
28 
29 /**
30  * Generic image interface and its implementations for integer pixel formats
31  *
32  * Copyright: Timur Gafarov 2011-2021.
33  * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).
34  * Authors: Timur Gafarov
35  */
36 module dlib.image.image;
37 
38 import std.stdio;
39 import std.math;
40 import std.conv;
41 import std.range;
42 import dlib.core.memory;
43 import dlib.math.vector;
44 import dlib.math.interpolation;
45 import dlib.image.color;
46 
47 /// sRGBa integer pixel formats, 8 and 16 bits per channel
48 enum IntegerPixelFormat: uint
49 {
50     L8 = 0,
51     LA8 = 1,
52     RGB8 = 2,
53     RGBA8 = 3,
54     L16 = 4,
55     LA16 = 5,
56     RGB16 = 6,
57     RGBA16 = 7
58 }
59 
60 /**
61  * Abstract image interface
62  */
63 interface SuperImage: Freeable
64 {
65     /**
66      * Image width in pixels
67      */
68     @property uint width();
69 
70     /**
71      * Image height in pixels
72      */
73     @property uint height();
74     
75     /**
76      * Bits per channel
77      */
78     @property uint bitDepth();
79     
80     /**
81      * Number of channels
82      */
83     @property uint channels();
84     
85     /**
86      * Bytes per pixel
87      */
88     @property uint pixelSize();
89 
90     /**
91      * This is compatible with IntegerPixelFormat and other internal format enums in dlib.
92      * Values from 0 to 255 are reserved for dlib.
93      * Values 256 and above are application-specific and can be used for custom SuperImage implementations
94      */
95     @property uint pixelFormat();
96 
97     /**
98      * Returns raw buffer of image data in scan order.
99      * Pixel layout is specified by pixelFormat
100      */
101     @property ubyte[] data();
102 
103     /**
104      * Pixel access operator.
105      * Should always return floating-point sRGBa or linear RGBa,
106      * depending on format family (IntegerPixelFormat or FloatPixelFormat)
107      */
108     Color4f opIndex(int x, int y);
109     
110     /**
111      * Pixel assignment operator.
112      * Accepts floating-point sRGBa or linear RGBa,
113      * depending on format family (IntegerPixelFormat or FloatPixelFormat)
114      */
115     Color4f opIndexAssign(Color4f c, int x, int y);
116 
117     /**
118      * Makes a copy of the image
119      */
120     @property SuperImage dup();
121 
122     /**
123      * Makes a blank image of the same format
124      */
125     SuperImage createSameFormat(uint w, uint h);
126 
127     /**
128      * Range of x pixel indices
129      */
130     final @property auto row()
131     {
132         return iota(0, width);
133     }
134 
135     /**
136      * Range of y pixel indices
137      */
138     final @property auto col()
139     {
140         return iota(0, height);
141     }
142 
143     /**
144      * Enumerates all pixels of the image in scan order
145      */
146     final int opApply(scope int delegate(ref Color4f p, uint x, uint y) dg)
147     {
148         int result = 0;
149 
150         foreach(uint y; col)
151         {
152             foreach(uint x; row)
153             {
154                 Color4f col = opIndex(x, y);
155                 result = dg(col, x, y);
156                 opIndexAssign(col, x, y);
157 
158                 if (result)
159                     break;
160             }
161 
162             if (result)
163                 break;
164         }
165 
166         return result;
167     }
168 }
169 
170 /**
171  * SuperImage implementation template for integer pixel formats
172  */
173 class Image(IntegerPixelFormat fmt): SuperImage
174 {
175     public:
176 
177     override @property uint width()
178     {
179         return _width;
180     }
181 
182     override @property uint height()
183     {
184         return _height;
185     }
186 
187     override @property uint bitDepth()
188     {
189         return _bitDepth;
190     }
191 
192     override @property uint channels()
193     {
194         return _channels;
195     }
196 
197     override @property uint pixelSize()
198     {
199         return _pixelSize;
200     }
201 
202     override @property uint pixelFormat()
203     {
204         return fmt;
205     }
206 
207     override @property ubyte[] data()
208     {
209         return _data;
210     }
211 
212     override @property SuperImage dup()
213     {
214         auto res = new Image!(fmt)(_width, _height);
215         res.data[] = data[];
216         return res;
217     }
218 
219     override SuperImage createSameFormat(uint w, uint h)
220     {
221         return new Image!(fmt)(w, h);
222     }
223 
224     this(uint w, uint h)
225     {
226         _width = w;
227         _height = h;
228 
229         _bitDepth = [
230             IntegerPixelFormat.L8:     8, IntegerPixelFormat.LA8:     8,
231             IntegerPixelFormat.RGB8:   8, IntegerPixelFormat.RGBA8:   8,
232             IntegerPixelFormat.L16:   16, IntegerPixelFormat.LA16:   16,
233             IntegerPixelFormat.RGB16: 16, IntegerPixelFormat.RGBA16: 16
234         ][fmt];
235 
236         _channels = [
237             IntegerPixelFormat.L8:    1, IntegerPixelFormat.LA8:    2,
238             IntegerPixelFormat.RGB8:  3, IntegerPixelFormat.RGBA8:  4,
239             IntegerPixelFormat.L16:   1, IntegerPixelFormat.LA16:   2,
240             IntegerPixelFormat.RGB16: 3, IntegerPixelFormat.RGBA16: 4
241         ][fmt];
242 
243         _pixelSize = (_bitDepth / 8) * _channels;
244         
245         enum maxDimension = int.max;
246         
247         if (w > maxDimension)
248         {
249             writeln("Image data is not allocated. Exceeded maximum image width ", maxDimension);
250             return;
251         }
252         
253         if (h > maxDimension)
254         {
255             writeln("Image data is not allocated. Exceeded maximum image height ", maxDimension);
256             return;
257         }
258             
259         allocateData();
260     }
261 
262     protected void allocateData()
263     {
264         size_t size = cast(size_t)_width * cast(size_t)_height * cast(size_t)_pixelSize;
265         _data = new ubyte[size];
266     }
267 
268     public Color4 getPixel(int x, int y)
269     {
270         ubyte[] pixData = data();
271 
272         if (x >= width) x = width-1;
273         else if (x < 0) x = 0;
274 
275         if (y >= height) y = height-1;
276         else if (y < 0) y = 0;
277 
278         size_t index = (cast(size_t)y * cast(size_t)_width + cast(size_t)x) * cast(size_t)_pixelSize;
279 
280         auto maxv = (2 ^^ bitDepth) - 1;
281 
282         static if (fmt == IntegerPixelFormat.L8)
283         {
284             auto v = pixData[index];
285             return Color4(v, v, v);
286         }
287         else if (fmt == IntegerPixelFormat.LA8)
288         {
289             auto v = pixData[index];
290             return Color4(v, v, v, pixData[index+1]);
291         }
292         else if (fmt == IntegerPixelFormat.RGB8)
293         {
294             return Color4(pixData[index], pixData[index+1], pixData[index+2], cast(ubyte)maxv);
295         }
296         else if (fmt == IntegerPixelFormat.RGBA8)
297         {
298             return Color4(pixData[index], pixData[index+1], pixData[index+2], pixData[index+3]);
299         }
300         else if (fmt == IntegerPixelFormat.L16)
301         {
302             ushort v = pixData[index] << 8 | pixData[index+1];
303             return Color4(v, v, v);
304         }
305         else if (fmt == IntegerPixelFormat.LA16)
306         {
307             ushort v = pixData[index]   << 8 | pixData[index+1];
308             ushort a = pixData[index+2] << 8 | pixData[index+3];
309             return Color4(v, v, v, a);
310         }
311         else if (fmt == IntegerPixelFormat.RGB16)
312         {
313             ushort r = pixData[index]   << 8 | pixData[index+1];
314             ushort g = pixData[index+2] << 8 | pixData[index+3];
315             ushort b = pixData[index+4] << 8 | pixData[index+5];
316             ushort a = cast(ushort)maxv;
317             return Color4(r, g, b, a);
318         }
319         else if (fmt == IntegerPixelFormat.RGBA16)
320         {
321             ushort r = pixData[index]   << 8 | pixData[index+1];
322             ushort g = pixData[index+2] << 8 | pixData[index+3];
323             ushort b = pixData[index+4] << 8 | pixData[index+5];
324             ushort a = pixData[index+6] << 8 | pixData[index+7];
325             return Color4(r, g, b, a);
326         }
327         else
328         {
329             assert (0, "Image.opIndex is not implemented for IntegerPixelFormat." ~ to!string(fmt));
330         }
331     }
332 
333     public Color4 setPixel(Color4 c, int x, int y)
334     {
335         ubyte[] pixData = data();
336 
337         if (x >= width || y >= height || x < 0 || y < 0)
338             return c;
339 
340         size_t index = (cast(size_t)y * cast(size_t)_width + cast(size_t)x) * cast(size_t)_pixelSize;
341 
342         static if (fmt == IntegerPixelFormat.L8)
343         {
344             pixData[index] = cast(ubyte)c.r;
345         }
346         else if (fmt == IntegerPixelFormat.LA8)
347         {
348             pixData[index] = cast(ubyte)c.r;
349             pixData[index+1] = cast(ubyte)c.a;
350         }
351         else if (fmt == IntegerPixelFormat.RGB8)
352         {
353             pixData[index] = cast(ubyte)c.r;
354             pixData[index+1] = cast(ubyte)c.g;
355             pixData[index+2] = cast(ubyte)c.b;
356         }
357         else if (fmt == IntegerPixelFormat.RGBA8)
358         {
359             pixData[index] = cast(ubyte)c.r;
360             pixData[index+1] = cast(ubyte)c.g;
361             pixData[index+2] = cast(ubyte)c.b;
362             pixData[index+3] = cast(ubyte)c.a;
363         }
364         else if (fmt == IntegerPixelFormat.L16)
365         {
366             pixData[index] = cast(ubyte)(c.r >> 8);
367             pixData[index+1] = cast(ubyte)(c.r & 0xFF);
368         }
369         else if (fmt == IntegerPixelFormat.LA16)
370         {
371             pixData[index] = cast(ubyte)(c.r >> 8);
372             pixData[index+1] = cast(ubyte)(c.r & 0xFF);
373             pixData[index+2] = cast(ubyte)(c.a >> 8);
374             pixData[index+3] = cast(ubyte)(c.a & 0xFF);
375         }
376         else if (fmt == IntegerPixelFormat.RGB16)
377         {
378             pixData[index] = cast(ubyte)(c.r >> 8);
379             pixData[index+1] = cast(ubyte)(c.r & 0xFF);
380             pixData[index+2] = cast(ubyte)(c.g >> 8);
381             pixData[index+3] = cast(ubyte)(c.g & 0xFF);
382             pixData[index+4] = cast(ubyte)(c.b >> 8);
383             pixData[index+5] = cast(ubyte)(c.b & 0xFF);
384         }
385         else if (fmt == IntegerPixelFormat.RGBA16)
386         {
387             pixData[index] = cast(ubyte)(c.r >> 8);
388             pixData[index+1] = cast(ubyte)(c.r & 0xFF);
389             pixData[index+2] = cast(ubyte)(c.g >> 8);
390             pixData[index+3] = cast(ubyte)(c.g & 0xFF);
391             pixData[index+4] = cast(ubyte)(c.b >> 8);
392             pixData[index+5] = cast(ubyte)(c.b & 0xFF);
393             pixData[index+6] = cast(ubyte)(c.a >> 8);
394             pixData[index+7] = cast(ubyte)(c.a & 0xFF);
395         }
396         else
397         {
398             assert (0, "Image.opIndexAssign is not implemented for IntegerPixelFormat." ~ to!string(fmt));
399         }
400 
401         return c;
402     }
403 
404     override Color4f opIndex(int x, int y)
405     {
406         return Color4f(getPixel(x, y), _bitDepth);
407     }
408 
409     override Color4f opIndexAssign(Color4f c, int x, int y)
410     {
411         setPixel(c.convert(_bitDepth), x, y);
412         return c;
413     }
414 
415     void free()
416     {
417         // Do nothing, let GC delete the object
418     }
419 
420     protected:
421 
422     uint _width;
423     uint _height;
424     uint _bitDepth;
425     uint _channels;
426     uint _pixelSize;
427     ubyte[] _data;
428 }
429 
430 /// Specialization of Image for 8-bit luminance pixel format
431 alias ImageL8 = Image!(IntegerPixelFormat.L8);
432 /// Specialization of Image for 8-bit luminance-alpha pixel format
433 alias ImageLA8 = Image!(IntegerPixelFormat.LA8);
434 /// Specialization of Image for 8-bit RGB pixel format
435 alias ImageRGB8 = Image!(IntegerPixelFormat.RGB8);
436 /// Specialization of Image for 8-bit RGBA pixel format
437 alias ImageRGBA8 = Image!(IntegerPixelFormat.RGBA8);
438 
439 /// Specialization of Image for 16-bit luminance pixel format
440 alias ImageL16 = Image!(IntegerPixelFormat.L16);
441 /// Specialization of Image for 16-bit luminance-alpha pixel format
442 alias ImageLA16 = Image!(IntegerPixelFormat.LA16);
443 /// Specialization of Image for 16-bit RGB pixel format
444 alias ImageRGB16 = Image!(IntegerPixelFormat.RGB16);
445 /// Specialization of Image for 16-bit RGBA pixel format
446 alias ImageRGBA16 = Image!(IntegerPixelFormat.RGBA16);
447 
448 /**
449  * All-in-one image factory interface
450  */
451 interface SuperImageFactory
452 {
453     SuperImage createImage(uint w, uint h, uint channels, uint bitDepth, uint numFrames = 1);
454 }
455 
456 /**
457  * All-in-one image factory class
458  */
459 class ImageFactory: SuperImageFactory
460 {
461     SuperImage createImage(uint w, uint h, uint channels, uint bitDepth, uint numFrames = 1)
462     {
463         return image(w, h, channels, bitDepth);
464     }
465 }
466 
467 private SuperImageFactory _defaultImageFactory;
468 
469 /**
470  * Get default image factory singleton
471  */
472 SuperImageFactory defaultImageFactory()
473 {
474     if (!_defaultImageFactory)
475         _defaultImageFactory = new ImageFactory();
476     return _defaultImageFactory;
477 }
478 
479 /// Create an image with specified parameters
480 SuperImage image(uint w, uint h, uint channels = 3, uint bitDepth = 8)
481 in
482 {
483     assert(channels > 0 && channels <= 4);
484     assert(bitDepth == 8 || bitDepth == 16);
485 }
486 do
487 {
488     switch(channels)
489     {
490         case 1:
491         {
492             if (bitDepth == 8)
493                 return new ImageL8(w, h);
494             else
495                 return new ImageL16(w, h);
496         }
497         case 2:
498         {
499             if (bitDepth == 8)
500                 return new ImageLA8(w, h);
501             else
502                 return new ImageLA16(w, h);
503         }
504         case 3:
505         {
506             if (bitDepth == 8)
507                 return new ImageRGB8(w, h);
508             else
509                 return new ImageRGB16(w, h);
510         }
511         case 4:
512         {
513             if (bitDepth == 8)
514                 return new ImageRGBA8(w, h);
515             else
516                 return new ImageRGBA16(w, h);
517         }
518         default:
519             assert(0);
520     }
521 }
522 
523 /// Convert image to specified pixel format
524 T convert(T)(SuperImage img)
525 {
526     auto res = new T(img.width, img.height);
527     foreach(x; 0..img.width)
528     foreach(y; 0..img.height)
529         res[x, y] = img[x, y];
530     return res;
531 }
532 
533 /// Get interpolated pixel value from an image
534 Color4f bilinearPixel(SuperImage img, float x, float y)
535 {
536     real intX;
537     real fracX = modf(x, intX);
538     real intY;
539     real fracY = modf(y, intY);
540 
541     Color4f c1 = img[cast(int)intX, cast(int)intY];
542     Color4f c2 = img[cast(int)(intX + 1.0f), cast(int)intY];
543     Color4f c3 = img[cast(int)(intX + 1.0f), cast(int)(intY + 1.0f)];
544     Color4f c4 = img[cast(int)intX, cast(int)(intY + 1.0f)];
545 
546     Color4f ic1 = lerp(c1, c2, fracX);
547     Color4f ic2 = lerp(c4, c3, fracX);
548     Color4f ic3 = lerp(ic1, ic2, fracY);
549 
550     return ic3;
551 }
552 
553 /**
554  * Rectangular region of an image that can be iterated with foreach
555  */
556 struct ImageRegion
557 {
558     SuperImage img;
559     uint xstart;
560     uint ystart;
561     uint width;
562     uint height;
563 
564     final int opApply(scope int delegate(ref Color4f p, uint x, uint y) dg)
565     {
566         int result = 0;
567         uint x1, y1;
568 
569         foreach(uint y; 0..height)
570         {
571             y1 = ystart + y;
572             foreach(uint x; 0..width)
573             {
574                 x1 = xstart + x;
575                 Color4f col = img[x1, y1];
576                 result = dg(col, x, y);
577                 img[x1, y1] = col;
578 
579                 if (result)
580                     break;
581             }
582 
583             if (result)
584                 break;
585         }
586 
587         return result;
588     }
589 }
590 
591 /// ImageRegion factory function
592 ImageRegion region(SuperImage img, uint x, uint y, uint width, uint height)
593 {
594     return ImageRegion(img, x, y, width, height);
595 }
596 
597 /**
598  An InputRange of windows (regions around pixels) of an image that can be iterated with foreach
599  */
600 struct ImageWindowRange
601 {
602     SuperImage img;
603     uint width;
604     uint height;
605 
606     private uint halfWidth;
607     private uint halfHeight;
608     private uint wx = 0;
609     private uint wy = 0;
610 
611     this(SuperImage img, uint w, uint h)
612     {
613         this.img = img;
614         this.width = w;
615         this.height = h;
616 
617         this.halfWidth = this.width / 2;
618         this.halfHeight = this.height / 2;
619     }
620 
621     final int opApply(scope int delegate(ImageRegion w, uint x, uint y) dg)
622     {
623         int result = 0;
624 
625         foreach(uint y; img.col)
626         {
627             uint ystart = y - halfWidth;
628             foreach(uint x; img.row)
629             {
630                 uint xstart = x - halfHeight;
631 
632                 auto window = region(img, xstart, ystart, width, height);
633                 result = dg(window, x, y);
634 
635                 if (result)
636                     break;
637             }
638 
639             if (result)
640                 break;
641         }
642 
643         return result;
644     }
645 
646     bool empty = false;
647 
648     void popFront()
649     {
650         wx++;
651         if (wx == img.width)
652         {
653             wx = 0;
654             wy++;
655 
656             if (wy == img.height)
657             {
658                 wy = 0;
659                 empty = true;
660             }
661         }
662     }
663 
664     @property ImageRegion front()
665     {
666         return region(img, wx - halfWidth, wy - halfHeight, width, height);
667     }
668 }
669 
670 /**
671  ImageWindowRange factory function
672  
673  Examples:
674  ---
675  // Convolution with emboss kernel
676  
677  float[3][3] kernel = [
678      [-1, -1,  0],
679      [-1,  0,  1],
680      [ 0,  1,  1],
681  ];
682 
683  foreach(window, x, y; inputImage.windows(3, 3))
684  {
685      Color4f sum = Color4f(0, 0, 0);
686      foreach(ref Color4f pixel, x, y; window)
687          sum += pixel * kernel[y][x];
688      outputImage[x, y] = sum / 4.0f + 0.5f;
689  }
690  ---
691  */
692 ImageWindowRange windows(SuperImage img, uint width, uint height)
693 {
694     return ImageWindowRange(img, width, height);
695 }