1 module dimage.base; 2 3 import std.bitmanip; 4 5 /** 6 * Interface for accessing metadata within images. 7 * Any metadata that's not supported should return null. 8 */ 9 public interface ImageMetadata{ 10 public string getID(); 11 public string getAuthor(); 12 public string getComment(); 13 public string getJobName(); 14 public string getSoftwareInfo(); 15 public string getSoftwareVersion(); 16 public void setID(string val); 17 public void setAuthor(string val); 18 public void setComment(string val); 19 public void setJobName(string val); 20 public void setSoftwareInfo(string val); 21 public void setSoftwareVersion(string val); 22 } 23 /** 24 * All image classes should be derived from this base. 25 * Implements some basic functionality, such as reading and writing pixels, basic data storage, and basic information. 26 * Pixeldata should be stored decompressed, but indexing should be preserved on loading with the opinion of upconverting 27 * to truecolor. 28 */ 29 abstract class Image{ 30 protected static const ubyte[2] pixelOrder4BitBE = [0xF0, 0x0F]; 31 protected static const ubyte[2] pixelOrder4BitLE = [0x0F, 0xF0]; 32 protected static const ubyte[2] pixelShift4BitBE = [0x04, 0x00]; 33 protected static const ubyte[2] pixelShift4BitLE = [0x00, 0x04]; 34 protected static const ubyte[4] pixelOrder2BitBE = [0b1100_0000, 0b0011_0000, 0b0000_1100, 0b0000_0011]; 35 protected static const ubyte[4] pixelOrder2BitLE = [0b0000_0011, 0b0000_1100, 0b0011_0000, 0b1100_0000]; 36 protected static const ubyte[4] pixelShift2BitBE = [0x06, 0x04, 0x02, 0x00]; 37 protected static const ubyte[4] pixelShift2BitLE = [0x00, 0x02, 0x04, 0x06]; 38 protected static const ubyte[8] pixelOrder1BitBE = [0b1000_0000, 0b0100_0000, 0b0010_0000, 0b0001_0000, 39 0b0000_1000, 0b0000_0100, 0b0000_0010, 0b0000_0001]; 40 protected static const ubyte[8] pixelOrder1BitLE = [0b0000_0001, 0b0000_0010, 0b0000_0100, 0b0000_1000, 41 0b0001_0000, 0b0010_0000, 0b0100_0000, 0b1000_0000]; 42 protected static const ubyte[8] pixelShift1BitBE = [0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00]; 43 protected static const ubyte[8] pixelShift1BitLE = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]; 44 /** 45 * Raw image data. Cast the data to whatever data you need at the moment. 46 */ 47 protected ubyte[] imageData; 48 /** 49 * Raw palette data. Null if image is not indexed. 50 */ 51 protected ubyte[] paletteData; 52 53 protected ubyte mod; ///used for fast access of indexes 54 protected ubyte shift; ///used for fast access of indexes 55 56 abstract ushort width() @nogc @safe @property const; 57 abstract ushort height() @nogc @safe @property const; 58 abstract bool isIndexed() @nogc @safe @property const; 59 abstract ubyte getBitdepth() @nogc @safe @property const; 60 abstract ubyte getPaletteBitdepth() @nogc @safe @property const; 61 abstract PixelFormat getPixelFormat() @nogc @safe @property const; 62 /** 63 * Returns the pixel order for bitdepths less than 8. Almost excusively used for indexed bitmaps. 64 * Returns null if ordering not needed. 65 */ 66 public ubyte[] getPixelOrder() @safe @property const{ 67 return []; 68 } 69 /** 70 * Returns which pixel how much needs to be shifted right after a byteread. 71 */ 72 public ubyte[] getPixelOrderBitshift() @safe @property const{ 73 return []; 74 } 75 /** 76 * Reads a single 32bit pixel. If the image is indexed, a color lookup will be done. 77 */ 78 public Pixel32Bit readPixel(ushort x, ushort y){ 79 if(x >= width || y >= height){ 80 throw new ImageBoundsException("Image is being read out of bounds"); 81 } 82 if(isIndexed){ 83 ushort index = readPixelIndex!ushort(x, y); 84 return readPalette(index); 85 }else{ 86 final switch(getBitdepth){ 87 case 8: 88 ubyte data = imageData[x + y * width]; 89 return Pixel32Bit(data, data, data, 0xFF); 90 case 16: 91 PixelRGBA5551 data = (cast(PixelRGBA5551[])(cast(void[])imageData))[x + y * width]; 92 return Pixel32Bit(data); 93 case 24: 94 Pixel24Bit data = (cast(Pixel24Bit[])(cast(void[])imageData))[x + y * width]; 95 return Pixel32Bit(data); 96 case 32: 97 Pixel32Bit data = (cast(Pixel32Bit[])(cast(void[])imageData))[x + y * width]; 98 return data; 99 } 100 } 101 } 102 /** 103 * Reads an index, if the image isn't indexed throws an ImageFormatException. 104 */ 105 public T readPixelIndex(T = ubyte)(ushort x, ushort y) 106 if(T.stringof == ushort.stringof || T.stringof == ubyte.stringof){ 107 if(x >= width || y >= height){ 108 throw new ImageBoundsException("Image is being read out of bounds!"); 109 } 110 if(!isIndexed){ 111 throw new ImageFormatException("Image isn't indexed!"); 112 } 113 static if(T.stringof == ubyte.stringof){ 114 if(getBitdepth == 16){ 115 throw new ImageFormatException("Image isn't indexed!"); 116 }else if(getBitdepth == 8){ 117 return imageData[x + y * width]; 118 }else{ 119 const ubyte[] pixelorder = getPixelOrder; 120 const size_t offset = (x + y * width); 121 ubyte data = imageData[offset >> shift], currentPO = pixelorder[offset & mod]; 122 data &= currentPO; 123 data >>= getPixelOrderBitshift()[offset & mod]; 124 return data; 125 } 126 }else static if(T.stringof == ushort.stringof){ 127 if(getBitdepth == 16){ 128 return (cast(ushort[])(cast(void[])imageData))[x + y * width]; 129 }else if(getBitdepth == 8){ 130 return imageData[x + y * width]; 131 }else{ 132 const ubyte[] pixelorder = getPixelOrder, bitshift = getPixelOrderBitshift; 133 const size_t offset = (x + y * width); 134 ubyte data = imageData[offset >> shift], currentPO = pixelorder[offset & mod]; 135 data &= currentPO; 136 data >>= bitshift[offset & mod]; 137 return data; 138 } 139 }else static assert(0, "Use either ubyte or ushort!"); 140 } 141 /** 142 * Looks up the index on the palette, then returns the color value as a 32 bit value. 143 */ 144 public Pixel32Bit readPalette(ushort index){ 145 if(!isIndexed) 146 throw new ImageFormatException("Image isn't indexed!"); 147 final switch(getPaletteBitdepth){ 148 case 8: 149 if(index > paletteData.length) 150 throw new PaletteBoundsException("Palette index is too high!"); 151 const ubyte data = paletteData[index]; 152 return Pixel32Bit(data, data, data, 0xFF); 153 case 16: 154 if(index<<1 > paletteData.length) 155 throw new PaletteBoundsException("Palette index is too high!"); 156 PixelRGBA5551 data = (cast(PixelRGBA5551[])(cast(void[])imageData))[index]; 157 return Pixel32Bit(data); 158 case 24: 159 if(index * 3 > paletteData.length) 160 throw new PaletteBoundsException("Palette index is too high!"); 161 Pixel24Bit data = (cast(Pixel24Bit[])(cast(void[])imageData))[index]; 162 return Pixel32Bit(data); 163 case 32: 164 if(index<<2 > paletteData.length) 165 throw new PaletteBoundsException("Palette index is too high!"); 166 Pixel32Bit data = (cast(Pixel32Bit[])(cast(void[])imageData))[index]; 167 return data; 168 } 169 } 170 /** 171 * Writes a single pixel. 172 * ubyte: most indexed formats. 173 * ushort: all 16bit indexed formats. 174 * Any other pixel structs are used for direct color. 175 */ 176 public T writePixel(T)(ushort x, ushort y, T pixel) if(T.stringof == ubyte.stringof || T.stringof == ushort.stringof 177 || T.stringof == PixelRGBA5551.stringof || T.stringof == PixelRGB565.stringof || 178 T.stringof == Pixel24Bit.stringof || T.stringof == Pixel32Bit.stringof){ 179 if(x >= width || y >= height) 180 throw new ImageBoundsException("Image is being written out of bounds!"); 181 182 static if(T.stringof == ubyte.stringof || T.stringof == ushort.stringof){ 183 if(!isIndexed) 184 throw new ImageFormatException("Image isn't indexed!"); 185 186 static if(T.stringof == ubyte.stringof) 187 if(getBitdepth == 16) 188 throw new ImageFormatException("Image cannot be written as 8 bit!"); 189 static if(T.stringof == ushort.stringof) 190 if(getBitdepth <= 8) 191 throw new ImageFormatException("Image cannot be written as 16 bit!"); 192 switch(getBitdepth){ 193 case 8 : 194 return imageData[x + (y * width)] = pixel; 195 case 16 : 196 return (cast(ushort[])(cast(void[])imageData))[x + (y * width)] = pixel; 197 default: 198 const size_t offset = x + (y * width); 199 size_t offsetA = offset & ((1 << getBitdepth) - 1), offsetB = offset>>1; 200 if(getBitdepth == 2) 201 offsetB >>= 1; 202 else if (getBitdepth == 1) 203 offsetB >>= 2; 204 pixel <<= getPixelOrderBitshift[offsetA]; 205 return imageData[offsetB] = (imageData[offsetB] & !getPixelOrder[offsetB]) | pixel; 206 } 207 }else{ 208 T[] pixels = cast(T[])(cast(void[])imageData); 209 if(T.sizeof != getBitdepth / 8) 210 throw new ImageFormatException("Image format mismatch exception"); 211 return pixels[x + (y * width)] = pixel; 212 } 213 } 214 /** 215 * Returns the raw image data. 216 */ 217 public ubyte[] getImageData() @nogc nothrow{ 218 return imageData; 219 } 220 /** 221 * Returns the raw palette data. 222 */ 223 public ubyte[] getPaletteData() @nogc nothrow{ 224 return paletteData; 225 } 226 } 227 228 struct Pixel32Bit { 229 union{ 230 ubyte[4] bytes; /// BGRA 231 uint base; /// Direct address 232 } 233 ///Red 234 @property ref auto r() inout { return bytes[2]; } 235 ///Green 236 @property ref auto g() inout { return bytes[1]; } 237 ///Blue 238 @property ref auto b() inout { return bytes[0]; } 239 ///Alpha 240 @property ref auto a() inout { return bytes[3]; } 241 @nogc this(ubyte[4] bytes){ 242 this.bytes = bytes; 243 } 244 @nogc this(ubyte r, ubyte g, ubyte b, ubyte a){ 245 bytes[0] = b; 246 bytes[1] = g; 247 bytes[2] = r; 248 bytes[3] = a; 249 } 250 @nogc this(PixelRGBA5551 p){ 251 bytes[0] = cast(ubyte)(p.b<<3 | p.b>>2); 252 bytes[1] = cast(ubyte)(p.g<<3 | p.g>>2); 253 bytes[2] = cast(ubyte)(p.r<<3 | p.r>>2); 254 bytes[3] = p.a ? 0xFF : 0x00; 255 } 256 @nogc this(PixelRGB565 p){ 257 bytes[0] = cast(ubyte)(p.b<<3 | p.b>>2); 258 bytes[1] = cast(ubyte)(p.g<<2 | p.g>>4); 259 bytes[2] = cast(ubyte)(p.r<<3 | p.r>>2); 260 bytes[3] = p.a ? 0xFF : 0x00; 261 } 262 @nogc this(Pixel24Bit p){ 263 bytes[0] = p.b; 264 bytes[1] = p.g; 265 bytes[2] = p.r; 266 bytes[3] = 0xFF; 267 } 268 } 269 /** 270 * 16 Bit colorspace with a single bit alpha. 271 */ 272 struct PixelRGBA5551{ 273 union{ 274 ushort base; 275 mixin(bitfields!( 276 ubyte, "b", 5, 277 ubyte, "g", 5, 278 ubyte, "r", 5, 279 bool, "a", 1, 280 )); 281 } 282 } 283 /** 284 * 16 Bit colorspace with no alpha. 285 */ 286 struct PixelRGB565{ 287 union{ 288 ushort base; 289 mixin(bitfields!( 290 ubyte, "b", 5, 291 ubyte, "g", 5, 292 ubyte, "r", 5, 293 bool, "a", 1, 294 )); 295 } 296 } 297 298 align(1) struct Pixel24Bit { 299 ubyte[3] bytes; 300 @property ref auto r() inout { return bytes[2]; } 301 @property ref auto g() inout { return bytes[1]; } 302 @property ref auto b() inout { return bytes[0]; } 303 } 304 /** 305 * Pixel formats where its needed. 306 */ 307 enum PixelFormat : uint{ 308 RGBA5551 = 1, 309 RGBX5551 = 2, 310 RGB565 = 3, 311 Undefined = uint.max, 312 } 313 314 /** 315 * Thrown if image is being read or written out of bounds. 316 */ 317 class ImageBoundsException : Exception{ 318 @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) 319 { 320 super(msg, file, line, nextInChain); 321 } 322 323 @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) 324 { 325 super(msg, file, line, nextInChain); 326 } 327 } 328 /** 329 * Thrown if palette is being read or written out of bounds. 330 */ 331 class PaletteBoundsException : Exception{ 332 @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) 333 { 334 super(msg, file, line, nextInChain); 335 } 336 337 @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) 338 { 339 super(msg, file, line, nextInChain); 340 } 341 } 342 /** 343 * Thrown if image format doesn't match. 344 */ 345 class ImageFormatException : Exception{ 346 @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) 347 { 348 super(msg, file, line, nextInChain); 349 } 350 351 @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) 352 { 353 super(msg, file, line, nextInChain); 354 } 355 }