1 /**
2  * Exposes the :d:class:`Image` class, which provides basic storage, access and
3  * conversion of images.
4  */
5 module daffodil.image;
6 
7 import std.conv;
8 import std.math;
9 import std.typecons;
10 
11 import daffodil;
12 import daffodil.util.data;
13 import daffodil.util.range;
14 
15 /**
16  * Generic Image class for a given pixel format.
17  * Holds a two dimensional array of pixels in a specified format, allowing
18  * generic transformations and manipulations using other interfaces.
19  */
20 class Image(V) if (isColorValue!V) {
21     /**
22      * Storage type for each value in a channel.
23      */
24     alias Value = V;
25 
26     /// The metadata of the image.
27     MetaData meta;
28 
29     private {
30         size_t[2] _size;
31         size_t    _channelCount;
32         Value[]   raster;
33 
34         const ColorSpace* colorSpace;
35     }
36 
37     /**
38      * Create an empty Image given a width and a height.
39      * Pixels default to `init`
40      */
41     this(size_t width, size_t height, size_t channelCount,
42          const ColorSpace* colorSpace, MetaData meta = null) {
43         _size = [width, height];
44         this._channelCount = channelCount;
45         this.colorSpace = colorSpace;
46         raster.length = width * height * channelCount;
47         this.meta = meta;
48     }
49 
50     /**
51      * Create a Image from a given image range, color space and optional
52      * metadata.
53      */
54     this(R)(R range, MetaData meta = null) if (isImageRange!(R, PixelData)) {
55         this(range.width, range.height, range.channelCount, range.colorSpace, meta);
56 
57         foreach (pixel; range) {
58             this[pixel.x, pixel.y] = pixel.data;
59         }
60     }
61 
62     /// Create a image from data copied off another image.
63     this(const Image other) {
64         this._size         = other._size;
65         this._channelCount = other._channelCount;
66         this.raster        = other.raster.dup;
67         this.colorSpace    = other.colorSpace;
68         // TODO: Take copy here?
69         this.meta          = cast(MetaData)other.meta;
70     }
71 
72     /**
73      * Get the width and height of the Image.
74      */
75     @property auto width() const { return _size[0]; }
76     /// Ditto
77     @property auto height() const { return _size[1]; }
78     /// Ditto
79     @property auto size() const { return _size; }
80     /// Ditto
81     auto opDollar(size_t pos)() const { return _size[pos]; }
82 
83     /**
84      * Get the number of channels in the image.
85      */
86     @property auto channelCount() const { return _channelCount; }
87 
88     /**
89      * Get a pixel of the given pixel format at a location on the image.
90      */
91     auto opIndex(size_t x, size_t y) const {
92         auto index = (x + y * width) * channelCount;
93         auto slice = raster[index..index + channelCount];
94 
95         return Pixel!Value(cast(Value[])slice, colorSpace);
96     }
97     /// Ditto
98     void opIndexAssign(const Pixel!Value color, size_t x, size_t y) {
99         (cast(Pixel!Value)this[x, y]).opAssign(color);
100     }
101     /// Ditto
102     void opIndexAssign(real[] values, size_t x, size_t y) {
103         assert(values.length == channelCount);
104         auto index = (x + y * width) * channelCount;
105         foreach (i; 0..channelCount) {
106             raster[index + i] = values[i].toColorValue!Value;
107         }
108     }
109 
110     /**
111      * Create a new color within the color space of the image.
112      */
113     auto newColor() const {
114         return Pixel!Value(channelCount, colorSpace);
115     }
116 
117     /// Return a copy of the image.
118     @property Image dup() const {
119         return new Image(this);
120     }
121 
122     ///
123     override string toString() const {
124         return raster.to!string;
125     }
126 
127     /// Return a image range for the image.
128     @property auto range() const {
129         struct Range {
130             const Image image;
131             real[] outBuffer;
132 
133             this(const Image image) {
134                 this.image = image;
135                 outBuffer = new real[channelCount];
136             }
137             @property auto width() { return image.width; }
138             @property auto height() { return image.height;}
139             @property auto channelCount() { return image.channelCount; }
140             @property auto colorSpace() { return image.colorSpace; }
141             @property auto front() { return outBuffer; }
142             @property auto empty() { return false; }
143             void popFront() {}
144             real[] opIndex(size_t x, size_t y) {
145                 auto color = image[x, y];
146                 foreach (index; 0..channelCount) {
147                     outBuffer[index] = color[index].toReal;
148                 }
149                 return outBuffer;
150             }
151         }
152         static assert(isRandomAccessImageRange!Range);
153 
154         return Range(this);
155     }
156 }
157 
158 @("Image size properties")
159 unittest {
160     auto image = new Image!uint(123, 234, 1, RGB);
161     assert(image.width == 123);
162     assert(image.height == 234);
163     assert(image.size == [123, 234]);
164     assert(image.opDollar!0 == 123);
165     assert(image.opDollar!1 == 234);
166 }
167 
168 @("Image to string")
169 unittest {
170     auto image = new Image!uint(2, 2, 3, RGB);
171     assert(image.toString == "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]");
172 }
173 
174 @("Image range")
175 unittest {
176     auto image = new Image!uint(2, 2, 3, RGB);
177     assert(isRandomAccessImageRange!(typeof(image.range)));
178 }