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 }