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 }