1 /*
2  * dimage - base.d
3  * by Laszlo Szeremi
4  *
5  * Copyright under Boost Software License.
6  */
7 
8 module dimage.base;
9 
10 import std.bitmanip;
11 import std.range : InputRange;
12 import std.conv : to;
13 
14 import dimage.util;
15 
16 import bitleveld.datatypes;
17 
18 public import dimage.types;
19 public import dimage.exceptions;
20 
21 /**
22  * Interface for accessing metadata within images.
23  * Any metadata that's not supported should return null.
24  */
25 public interface ImageMetadata {
26 	public string getID() @safe pure;
27 	public string getAuthor() @safe pure;
28 	public string getComment() @safe pure;
29 	public string getJobName() @safe pure;
30 	public string getSoftwareInfo() @safe pure;
31 	public string getSoftwareVersion() @safe pure;
32 	public string getDescription() @safe pure;
33 	public string getSource() @safe pure;
34 	public string getCopyright() @safe pure;
35 	public string getCreationTimeStr() @safe pure;
36 
37 	public string setID(string val) @safe pure;
38 	public string setAuthor(string val) @safe pure;
39 	public string setComment(string val) @safe pure;
40 	public string setJobName(string val) @safe pure;
41 	public string setSoftwareInfo(string val) @safe pure;
42 	public string setSoftwareVersion(string val) @safe pure;
43 	public string setDescription(string val) @safe pure;
44 	public string setSource(string val) @safe pure;
45 	public string setCopyright(string val) @safe pure;
46 	public string setCreationTime(string val) @safe pure;
47 }
48 /**
49  * Allows to access custom-tagged textual metadata in images.
50  */
51 public interface CustomImageMetadata : ImageMetadata {
52 	/**
53 	 * Returns the metadata with the given `id`.
54 	 * Returns null if not found.
55 	 */
56 	public string getMetadata(string id) @safe pure;
57 	/**
58 	 * Sets the given metadata to `val` at the given `id`, then returns the new value.
59 	 */
60 	public string setMetadata(string id, string val) @safe pure;
61 }
62 /**
63  * Interface for common multi-image (eg. animation) functions.
64  */
65 public interface MultiImage {
66 	///Returns which image is being set to be worked on.
67 	public uint getCurrentImage() @safe pure;
68 	///Sets which image is being set to be worked on.
69 	public uint setCurrentImage(uint frame) @safe pure;
70 	///Sets the current image to the static if available
71 	public void setStaticImage() @safe pure;
72 	///Number of images in a given multi-image.
73 	public uint nOfImages() @property @safe @nogc pure const;
74 	///Returns the frame duration in hmsec if animation for the given frame.
75 	///Returns 0 if not an animation.
76 	public uint frameTime() @property @safe @nogc pure const;
77 	///Returns true if the multi-image is animated
78 	public bool isAnimation() @property @safe @nogc pure const;
79 }
80 /**
81  * Basic palette wrapper.
82  */
83 public interface IPalette {
84 	///Returns the number of indexes within the palette.
85 	public @property size_t length() @nogc @safe pure nothrow const;
86 	///Returns the bitdepth of the palette.
87 	public @property ubyte bitDepth() @nogc @safe pure nothrow const;
88 	///Returns the color format of the palette.
89 	public @property uint paletteFormat() @nogc @safe pure nothrow const;
90 	///Converts the palette to the given format if supported
91 	public IPalette convTo(uint format) @safe;
92 	///Reads palette in standard indexed format
93 	public ARGB8888 read(size_t index) @safe pure;
94 	///Reads palette in standard floating point format
95 	public RGBA_f32 readF(size_t index) @safe pure;
96 	///Returns the raw data cast to ubyte
97 	public ubyte[] raw() @safe pure;
98 }
99 /**
100  * Contains palette information.
101  * Implements some range capabilities.
102  */
103 public class Palette(T) : IPalette {
104 	protected T[]			data;	///Raw data
105 	//protected size_t		_length;///The number of items in the palette (should be less than 65536)
106 	protected size_t		begin, end;
107 	protected uint			format;
108 	protected ubyte			_bitDepth;
109 	///CTOR
110 	this(T[] data, uint format, ubyte bitDepth) @nogc @safe pure nothrow {
111 		this.data = data;
112 		this.format = format;
113 		_bitDepth = bitDepth;
114 		end = data.length;
115 	}
116 	///Copy CTOR
117 	this(Palette!T src) @nogc @safe pure nothrow {
118 		data = src.data;
119 		format = src.format;
120 		_bitDepth = src._bitDepth;
121 		end = data.length;
122 	}
123 	///Returns the number of indexes within the palette.
124 	public @property size_t length() @nogc @safe pure nothrow const {
125 		return data.length;
126 	}
127 	///Returns the bitdepth of the palette.
128 	public @property ubyte bitDepth() @nogc @safe pure nothrow const {
129 		return _bitDepth;
130 	}
131 	///Returns the color format of the palette.
132 	public @property uint paletteFormat() @nogc @safe pure nothrow const {
133 		return format;
134 	}
135 	///Converts the palette to the given format if supported.
136 	public IPalette convTo(uint format) @safe {
137 		IPalette result;
138 		void converter(OutputType)() @safe {
139 			OutputType[] array;
140 			array.length = data.length;
141 			for(int i ; i < data.length ; i++)
142 				array[i] = OutputType(data[i]);
143 			result = new Palette!OutputType(array, format, getBitDepth(format));
144 		}
145 		switch (format & ~(PixelFormat.BigEndian | PixelFormat.ValidAlpha)) {
146 			case PixelFormat.RGB888:
147 				if(format & PixelFormat.BigEndian)
148 					converter!(RGB888BE);
149 				else
150 					converter!(RGB888);
151 				break;
152 			case PixelFormat.RGBX8888:
153 				if(format & PixelFormat.BigEndian)
154 					converter!(RGBA8888BE);
155 				else
156 					converter!(RGBA8888);
157 				break;
158 			case PixelFormat.XRGB8888:
159 				if(format & PixelFormat.BigEndian)
160 					converter!(ARGB8888BE);
161 				else
162 					converter!(ARGB8888);
163 				break;
164 			case PixelFormat.RGB565:
165 				converter!(RGB565);
166 				break;
167 			case PixelFormat.RGBX5551:
168 				converter!(RGBA5551);
169 				break;
170 			default:
171 				throw new ImageFormatException("Format not supported");
172 		}
173 		return result;
174 	}
175 	///Returns the raw data as an array.
176 	public T[] getRawData() @nogc @safe pure nothrow {
177 		return data;
178 	}
179 	///Reads palette in standard format.
180 	public ARGB8888 read(size_t index) @safe pure {
181 		if (index < data.length) return ARGB8888(data[index]);
182 		else throw new PaletteBoundsException("Palette is being read out of bounds!");
183 	}
184 	///Reads palette in standard format.
185 	public RGBA_f32 readF(size_t index) @safe pure {
186 		if (index < data.length) return RGBA_f32(data[index]);
187 		else throw new PaletteBoundsException("Palette is being read out of bounds!");
188 	}
189 	///Palette indexing.
190 	public ref T opIndex(size_t index) @safe pure {
191 		if (index < data.length) return data[index];
192 		else throw new PaletteBoundsException("Palette is being read out of bounds!");
193 	}
194 	/+
195 	///Assigns a value to the given index
196 	public T opIndexAssign(T value, size_t index) @safe pure {
197 		if (index < data.length) return data[index] = value;
198 		else throw new PaletteBoundsException("Palette is being read out of bounds!");
199 	}+/
200 	///Returns true if the range have reached its end
201 	public @property bool empty() @nogc @safe pure nothrow const {
202 		return begin == end;
203 	}
204 	///Returns the element at the front
205 	public ref T front() @nogc @safe pure nothrow {
206 		return data[begin];
207 	}
208 	alias opDollar = length;
209 	///Moves the front pointer forward by one
210 	public void popFront() @nogc @safe pure nothrow {
211 		if (begin != end) begin++;
212 	}
213 	///Moves the front pointer forward by one and returns the element
214 	public ref T moveFront() @nogc @safe pure nothrow {
215 		if (begin != end) return data[begin++];
216 		else return data[begin];
217 	}
218 	///Returns the element at the back
219 	public ref T back() @nogc @safe pure nothrow {
220 		return data[end - 1];
221 	}
222 	///Moves the back pointer backward by one
223 	public void popBack() @nogc @safe pure nothrow {
224 		if (begin != end) end--;
225 	}
226 	///Moves the back pointer backward and returns the element
227 	public ref T moveBack() @nogc @safe pure nothrow {
228 		if (begin != end) return data[--end];
229 		else return data[end];
230 	}
231 	///Creates a copy with the front and back pointers reset
232 	public Palette!T save() @safe pure nothrow {
233 		return new Palette!T(this);
234 	}
235 	///Returns the raw data cast to ubyte
236 	public ubyte[] raw() @safe pure {
237 		return reinterpretCast!ubyte(data);
238 	}
239 }
240 /**
241  * Palette with separate alpha field, used primarily by PNG.
242  */
243 public class PaletteWithSepA(T) : Palette!T {
244 	protected ubyte[]		alphaField;
245 	///CTOR
246 	this(T[] data, ubyte[] alphaField, uint format, ubyte bitDepth) @nogc @safe pure nothrow {
247 		assert(data.length == alphaField.length);
248 		super(data, format, bitDepth);
249 		this.alphaField = alphaField;
250 	}
251 	///Reads palette in standard format.
252 	public override ARGB8888 read(size_t index) @safe pure {
253 		if (index < data.length) return ARGB8888(data[index].r, data[index].g, data[index].b, alphaField[index]);
254 		else throw new PaletteBoundsException("Palette is being read out of bounds!");
255 	}
256 	///Reads palette in standard format.
257 	public override RGBA_f32 readF(size_t index) @safe pure {
258 		if (index < data.length) {
259 			RGBA_f32 result = RGBA_f32(data[index]);
260 			result.fA = alphaField[index] * (1.0 / ubyte.max);
261 			return result;
262 		}
263 		else throw new PaletteBoundsException("Palette is being read out of bounds!");
264 	}
265 	///Returns the raw data cast to ubyte
266 	public override ubyte[] raw() @safe pure {
267 		return reinterpretCast!ubyte(data) ~ alphaField;
268 	}
269 	///Converts the palette to the given format if supported.
270 	public override IPalette convTo(uint format) @safe {
271 		IPalette result;
272 		void converter(OutputType)() @safe {
273 			OutputType[] array;
274 			array.length = data.length;
275 			for(int i ; i < data.length ; i++)
276 				array[i] = OutputType(read(i));
277 			result = new Palette!OutputType(array, format, getBitDepth(format));
278 		}
279 		switch (format & ~(PixelFormat.BigEndian | PixelFormat.ValidAlpha)) {
280 			case PixelFormat.RGB888:
281 				if(format & PixelFormat.BigEndian)
282 					converter!(RGB888BE);
283 				else
284 					converter!(RGB888);
285 				break;
286 			case PixelFormat.RGBX8888:
287 				if(format & PixelFormat.BigEndian)
288 					converter!(RGBA8888BE);
289 				else
290 					converter!(RGBA8888);
291 				break;
292 			case PixelFormat.XRGB8888:
293 				if(format & PixelFormat.BigEndian)
294 					converter!(ARGB8888BE);
295 				else
296 					converter!(ARGB8888);
297 				break;
298 			case PixelFormat.RGB565:
299 				converter!(RGB565);
300 				break;
301 			case PixelFormat.RGBX5551:
302 				converter!(RGBA5551);
303 				break;
304 			default:
305 				throw new ImageFormatException("Format not supported");
306 		}
307 		return result;
308 	}
309 }
310 /**
311  * Auxiliary data wrapper.
312  * Stores data like filters for PNG images.
313  */
314 public interface AuxData {
315 	///Returns the type of the auxiliary data.
316 	public @property uint type() @nogc @safe pure nothrow const;
317 }
318 /**
319  * Frame data for animation.
320  */
321 public class AnimData {
322 	public uint		hOffset;		///Horizontal offset
323 	public uint		vOffset;		///Vertical offset
324 	public uint		hold;			///msecs to display this animation
325 }
326 /**
327  * Basic imagedata wrapper.
328  */
329 public interface IImageData {
330 	///Returns the width of the image.
331 	public @property uint width() @nogc @safe pure nothrow const;
332 	///Returns the height of the image.
333 	public @property uint height() @nogc @safe pure nothrow const;
334 	///Returns the bitdepth of the image.
335 	public @property ubyte bitDepth() @nogc @safe pure nothrow const;
336 	///Returns the number of bitplanes per image.
337 	///Default should be 1.
338 	public @property ubyte bitplanes() @nogc @safe pure nothrow const;
339 	///Returns the color format of the image.
340 	public @property uint pixelFormat() @nogc @safe pure nothrow const;
341 	///Converts the imagedata to the given format if supported
342 	public IImageData convTo(uint format) @safe;
343 	///Reads the image at the given point in ARGB32 format.
344 	///Does palette lookup if needed.
345 	public ARGB8888 read(uint x, uint y) @safe pure;
346 	///Reads the image at the given point in RGBA_f32 format.
347 	///Does palette lookup if needed.
348 	public RGBA_f32 readF(uint x, uint y) @safe pure;
349 	///Flips the image horizontally
350 	public void flipHorizontal() @safe pure;
351 	///Flips the image vertically
352 	public void flipVertical() @safe pure;
353 	///Returns the raw data cast to ubyte
354 	public ubyte[] raw() @safe pure;
355 }
356 /**
357  * Imagedata container.
358  */
359 public class ImageData(T) : IImageData {
360 	protected T[]			data;
361 	protected uint			_width, _height, _pixelFormat;
362 	protected ubyte			_bitDepth;
363 	///CTOR
364 	public this(T[] data, uint width, uint height, uint pixelFormat, ubyte bitDepth) @safe pure {
365 		//assert(data.length == _width * _height);
366 		this.data = data;
367 		this._width = width;
368 		this._height = height;
369 		this._pixelFormat = pixelFormat;
370 		this._bitDepth = bitDepth;
371 	}
372 	///CTOR with no preexisting image data
373 	public this(uint width, uint height, uint pixelFormat, ubyte bitDepth) @safe pure {
374 		//assert(data.length == _width * _height);
375 		this.data.length = width * height;
376 		this._width = width;
377 		this._height = height;
378 		this._pixelFormat = pixelFormat;
379 		this._bitDepth = bitDepth;
380 	}
381 	///Returns the raw data
382 	public @property T[] getData() @nogc @safe pure nothrow {
383 		return data;
384 	}
385 	///Returns the raw data cast to ubyte
386 	public ubyte[] raw() @safe pure {
387 		return reinterpretCast!ubyte(data);
388 	}
389 	///Returns the width of the image.
390 	public @property uint width() @nogc @safe pure nothrow const {
391 		return _width;
392 	}
393 	///Returns the height of the image.
394 	public @property uint height() @nogc @safe pure nothrow const {
395 		return _height;
396 	}
397 	///Returns the bitdepth of the image.
398 	public @property ubyte bitDepth() @nogc @safe pure nothrow const {
399 		return _bitDepth;
400 	}
401 
402 	public @property ubyte bitplanes() @nogc @safe pure nothrow const {
403 		return 1;
404 	}
405 
406 	public @property uint pixelFormat() @nogc @safe pure nothrow const {
407 		return _pixelFormat;
408 	}
409 
410 	public IImageData convTo(uint format) @safe {
411 		IImageData result;
412 		void converter(OutputType)() @safe {
413 			OutputType[] array;
414 			array.length = data.length;
415 			for(int i ; i < data.length ; i++)
416 				array[i] = OutputType(data[i]);
417 			result = new ImageData!OutputType(array, _width, _height, format, getBitDepth(format));
418 		}
419 		switch (format & ~(PixelFormat.BigEndian | PixelFormat.ValidAlpha)) {
420 			case PixelFormat.Grayscale16Bit:
421 				ushort[] datastream;
422 				datastream.length = _width * _height;
423 				MonochromeImageData!ushort mid = new MonochromeImageData!ushort(datastream, _width, _height, format, 16);
424 				for (int y ; y < _height ; y++) {
425 					for (int x ; x < _width ; x++) {
426 						const RGBA_f32 pixel = readF(x,y);
427 						mid[x,y] = cast(ushort)((pixel.fR * 0.2125 + pixel.fG * 0.7154 + pixel.fB * 0.0721) / MonochromeImageData!ushort.fYStepping);
428 					}
429 				}
430 				result = mid;
431 				break;
432 			case PixelFormat.Grayscale8Bit:
433 				ubyte[] datastream;
434 				datastream.length = _width * _height;
435 				MonochromeImageData!ubyte mid = new MonochromeImageData!ubyte(datastream, _width, _height, format, 8);
436 				for (int y ; y < _height ; y++) {
437 					for (int x ; x < _width ; x++) {
438 						const RGBA_f32 pixel = readF(x,y);
439 						mid[x,y] = cast(ubyte)((pixel.fR * 0.2125 + pixel.fG * 0.7154 + pixel.fB * 0.0721) / MonochromeImageData!ubyte.fYStepping);
440 					}
441 				}
442 				result = mid;
443 				break;
444 			case PixelFormat.YX88:
445 				if(format & PixelFormat.BigEndian)
446 					converter!(YA88BE);
447 				else
448 					converter!(YA88);
449 				break;
450 			case PixelFormat.RGB888:
451 				if(format & PixelFormat.BigEndian)
452 					converter!(RGB888BE);
453 				else
454 					converter!(RGB888);
455 				break;
456 			case PixelFormat.RGBX8888:
457 				if(format & PixelFormat.BigEndian)
458 					converter!(RGBA8888BE);
459 				else
460 					converter!(RGBA8888);
461 				break;
462 			case PixelFormat.XRGB8888:
463 				if(format & PixelFormat.BigEndian)
464 					converter!(ARGB8888BE);
465 				else
466 					converter!(ARGB8888);
467 				break;
468 			case PixelFormat.RGB565:
469 				converter!(RGB565);
470 				break;
471 			case PixelFormat.RGBX5551:
472 				converter!(RGBA5551);
473 				break;
474 			default:
475 				throw new ImageFormatException("Format not supported");
476 		}
477 		return result;
478 	}
479 	public ARGB8888 read(uint x, uint y) @safe pure {
480 		if(x < _width && y < _height) return ARGB8888(data[x + (y * _width)]);
481 		else throw new ImageBoundsException("Image is being read out of bounds!");
482 	}
483 	public RGBA_f32 readF(uint x, uint y) @safe pure {
484 		if(x < _width && y < _height) return RGBA_f32(data[x + (y * _width)]);
485 		else throw new ImageBoundsException("Image is being read out of bounds!");
486 	}
487 	public ref T opIndex(uint x, uint y) @safe pure {
488 		if(x < _width && y < _height) return data[x + (y * _width)];
489 		else throw new ImageBoundsException("Image is being read out of bounds!");
490 	}
491 	///Flips the image horizontally
492 	public void flipHorizontal() @safe pure {
493 		for (uint y ; y < _height ; y++) {
494 			for (uint x ; x < _width / 2 ; x++) {
495 				const T tmp = opIndex(x, y);
496 				opIndex(x, y) = opIndex(_width - x, y);
497 				opIndex(_width - x, y) = tmp;
498 			}
499 		}
500 	}
501 	///Flips the image vertically
502 	public void flipVertical() @safe pure {
503 		import std.algorithm.mutation : swapRanges;
504 		for (uint y ; y < _height / 2 ; y++) {
505 			const uint y0 = _height - y - 1;
506 			T[] a = data[(y * _width)..((y + 1) * _width)];
507 			T[] b = data[(y0 * _width)..((y0 + 1) * _width)];
508 			swapRanges(a, b);
509 		}
510 	}
511 }
512 /**
513  * Monochrome imagedata container for 8 and 16 bit types.
514  */
515 public class MonochromeImageData (T) : IImageData {
516 	public static immutable double fYStepping = 1.0 / T.max;
517 	protected T[]			data;
518 	protected uint			_width, _height, _pixelFormat;
519 	protected ubyte			_bitDepth;
520 	public this(T[] data, uint width, uint height, uint pixelFormat, ubyte bitDepth) @safe pure {
521 		//assert(data.length == _width * _height);
522 		this.data = data;
523 		this._width = width;
524 		this._height = height;
525 		this._pixelFormat = pixelFormat;
526 		this._bitDepth = bitDepth;
527 	}
528 	///CTOR with no preexisting image data
529 	public this(uint width, uint height, uint pixelFormat, ubyte bitDepth) @safe pure {
530 		//assert(data.length == _width * _height);
531 		this.data.length = width * height;
532 		this._width = width;
533 		this._height = height;
534 		this._pixelFormat = pixelFormat;
535 		this._bitDepth = bitDepth;
536 	}
537 	///Returns the width of the image.
538 	public @property uint width() @nogc @safe pure nothrow const {
539 		return _width;
540 	}
541 	///Returns the height of the image.
542 	public @property uint height() @nogc @safe pure nothrow const {
543 		return _height;
544 	}
545 	///Returns the bitdepth of the image.
546 	public @property ubyte bitDepth() @nogc @safe pure nothrow const {
547 		static if(is(T == ubyte)) return 8;
548 		else return 16;
549 	}
550 	///Returns the number of bitplanes per image.
551 	///Default should be 1.
552 	public @property ubyte bitplanes() @nogc @safe pure nothrow const {
553 		return 1;
554 	}
555 	///Returns the color format of the image.
556 	public @property uint pixelFormat() @nogc @safe pure nothrow const {
557 		static if(is(T == ubyte)) return PixelFormat.Grayscale8Bit;
558 		else return PixelFormat.Grayscale16Bit;
559 	}
560 	///Converts the imagedata to the given format if supported
561 	public IImageData convTo(uint format) @safe {
562 		IImageData result;
563 		void converter(OutputType)() @safe {
564 			OutputType[] array;
565 			array.length = data.length;
566 			for(int i ; i < data.length ; i++)
567 				array[i] = OutputType(data[i] * fYStepping);
568 			result = new ImageData!OutputType(array, _width, _height, format, getBitDepth(format));
569 		}
570 		switch (format & ~(PixelFormat.BigEndian | PixelFormat.ValidAlpha)) {
571 			case PixelFormat.Grayscale16Bit:
572 				ushort[] datastream;
573 				datastream.length = _width * _height;
574 				MonochromeImageData!ushort mid = new MonochromeImageData!ushort(datastream, _width, _height, format, 16);
575 				for (int y ; y < _height ; y++) {
576 					for (int x ; x < _width ; x++) {
577 						const RGBA_f32 pixel = readF(x,y);
578 						mid[x,y] = cast(ushort)((pixel.fR * 0.2125 + pixel.fG * 0.7154 + pixel.fB * 0.0721) / MonochromeImageData!ushort.fYStepping);
579 					}
580 				}
581 				result = mid;
582 				break;
583 			case PixelFormat.Grayscale8Bit:
584 				ubyte[] datastream;
585 				datastream.length = _width * _height;
586 				MonochromeImageData!ubyte mid = new MonochromeImageData!ubyte(datastream, _width, _height, format, 8);
587 				for (int y ; y < _height ; y++) {
588 					for (int x ; x < _width ; x++) {
589 						const RGBA_f32 pixel = readF(x,y);
590 						mid[x,y] = cast(ubyte)((pixel.fR * 0.2125 + pixel.fG * 0.7154 + pixel.fB * 0.0721) / MonochromeImageData!ubyte.fYStepping);
591 					}
592 				}
593 				result = mid;
594 				break;
595 			case PixelFormat.YX88:
596 				if(format & PixelFormat.BigEndian)
597 					converter!(YA88BE);
598 				else
599 					converter!(YA88);
600 				break;
601 			case PixelFormat.RGB888:
602 				if(format & PixelFormat.BigEndian)
603 					converter!(RGB888BE);
604 				else
605 					converter!(RGB888);
606 				break;
607 			case PixelFormat.RGBX8888:
608 				if(format & PixelFormat.BigEndian)
609 					converter!(RGBA8888BE);
610 				else
611 					converter!(RGBA8888);
612 				break;
613 			case PixelFormat.XRGB8888:
614 				if(format & PixelFormat.BigEndian)
615 					converter!(ARGB8888BE);
616 				else
617 					converter!(ARGB8888);
618 				break;
619 			case PixelFormat.RGB565:
620 				converter!(RGB565);
621 				break;
622 			case PixelFormat.RGBX5551:
623 				converter!(RGBA5551);
624 				break;
625 			default:
626 				throw new ImageFormatException("Format not supported");
627 		}
628 		return result;
629 	}
630 	///Reads the image at the given point in ARGB32 format.
631 	public ARGB8888 read(uint x, uint y) @safe pure {
632 		static if (is(T == ubyte)) {
633 			if(x < _width && y < _height) return ARGB8888(data[x + (y * _width)]);
634 			else throw new ImageBoundsException("Image is being read out of bounds!");
635 		} else static if (is(T == ushort)) {
636 			if(x < _width && y < _height) return ARGB8888(cast(ubyte)(cast(uint)data[x + (y * _width)]>>>8));
637 			else throw new ImageBoundsException("Image is being read out of bounds!");
638 		}
639 	}
640 	///Reads the image at the given point in RGBA_f32 format.
641 	public RGBA_f32 readF(uint x, uint y) @safe pure {
642 		if(x < _width && y < _height) return RGBA_f32(data[x + (y * _width)] * fYStepping);
643 		else throw new ImageBoundsException("Image is being read out of bounds!");
644 	}
645 	public ref T opIndex(uint x, uint y) @safe pure {
646 		if(x < _width && y < _height) return data[x + (y * _width)];
647 		else throw new ImageBoundsException("Image is being read out of bounds!");
648 	}
649 	///Flips the image horizontally
650 	public void flipHorizontal() @safe pure {
651 		for (uint y ; y < _height ; y++) {
652 			for (uint x ; x < _width / 2 ; x++) {
653 				const T tmp = opIndex(x, y);
654 				opIndex(x, y) = opIndex(_width - x, y);
655 				opIndex(_width - x, y) = tmp;
656 			}
657 		}
658 	}
659 	///Flips the image vertically
660 	public void flipVertical() @safe pure {
661 		import std.algorithm.mutation : swapRanges;
662 		for (uint y ; y < _height / 2 ; y++) {
663 			const uint y0 = _height - y - 1;
664 			T[] a = data[(y * _width)..((y + 1) * _width)];
665 			T[] b = data[(y0 * _width)..((y0 + 1) * _width)];
666 			swapRanges(a, b);
667 		}
668 	}
669 	///Returns the raw data cast to ubyte
670 	public ubyte[] raw() @safe pure {
671 		return reinterpretCast!ubyte(data);
672 	}
673 }
674 /**
675  * 4 Bit indexed image data.
676  */
677 public class MonochromeImageData4Bit : IImageData {
678 	public static immutable double fYStepping = 1.0 / 15;
679 	protected ubyte[]		data;
680 	protected NibbleArray	accessor;
681 	protected uint			_width, _height, _pitch;
682 	///CTOR
683 	public this(ubyte[] data,  uint width, uint height) @safe pure {
684 		this.data = data;
685 		this._width = width;
686 		this._height = height;
687 		_pitch = _width + (_width % 2);
688 		accessor = NibbleArray(data, _pitch * _height);
689 	}
690 	///CTOR with no preexisting image data
691 	public this(uint width, uint height) @safe pure {
692 		//assert(data.length == _width * _height);
693 		this._width = width;
694 		this._height = height;
695 		_pitch = _width + (_width % 2);
696 		this.data.length = _pitch * _height;
697 		accessor = NibbleArray(data, _pitch * _height);
698 	}
699 	///Returns the raw data
700 	public @property NibbleArray getData() @nogc @safe pure nothrow {
701 		return accessor;
702 	}
703 	///Returns the raw data cast to ubyte
704 	public ubyte[] raw() @safe pure {
705 		return data;
706 	}
707 	///Returns the width of the image.
708 	public @property uint width() @nogc @safe pure nothrow const {
709 		return _width;
710 	}
711 	///Returns the height of the image.
712 	public @property uint height() @nogc @safe pure nothrow const {
713 		return _height;
714 	}
715 
716 	public @property ubyte bitDepth() @nogc @safe pure nothrow const {
717 		return 4;
718 	}
719 
720 	public @property ubyte bitplanes() @nogc @safe pure nothrow const {
721 		return 1;
722 	}
723 
724 	public @property uint pixelFormat() @nogc @safe pure nothrow const {
725 		return PixelFormat.Grayscale4Bit;
726 	}
727 
728 	public IImageData convTo(uint format) @safe {
729 		IImageData result;
730 		void converter(OutputType)() @safe {
731 			OutputType[] array;
732 			array.reserve(_width * _height);
733 			for (uint y ; y < _height ; y++) {
734 				for (uint x ; x < _width ; x++) {
735 					array ~= OutputType(readF(x, y));
736 				}
737 			}
738 			result = new ImageData!OutputType(array, _width, _height, format, getBitDepth(format));
739 		}
740 		
741 		switch (format & ~(PixelFormat.BigEndian | PixelFormat.ValidAlpha)) {
742 			
743 			case PixelFormat.Grayscale16Bit:
744 				ushort[] datastream;
745 				datastream.length = _width * _height;
746 				MonochromeImageData!ushort mid = new MonochromeImageData!ushort(datastream, _width, _height, format, 16);
747 				for (int y ; y < _height ; y++) {
748 					for (int x ; x < _width ; x++) {
749 						const ubyte val = opIndex(x, y);
750 						mid[x,y] = cast(ushort)(val * 0x1111);
751 					}
752 				}
753 				result = mid;
754 				break;
755 			case PixelFormat.Grayscale8Bit:
756 				ubyte[] datastream;
757 				datastream.length = _width * _height;
758 				MonochromeImageData!ubyte mid = new MonochromeImageData!ubyte(datastream, _width, _height, format, 8);
759 				for (int y ; y < _height ; y++) {
760 					for (int x ; x < _width ; x++) {
761 						const ubyte val = opIndex(x, y);
762 						mid[x,y] = cast(ubyte)(val<<4 | val);
763 					}
764 				}
765 				result = mid;
766 				break;
767 			case PixelFormat.YX88:
768 				if(format & PixelFormat.BigEndian)
769 					converter!(YA88BE);
770 				else
771 					converter!(YA88);
772 				break;
773 			case PixelFormat.RGB888:
774 				if(format & PixelFormat.BigEndian)
775 					converter!(RGB888BE);
776 				else
777 					converter!(RGB888);
778 				break;
779 			case PixelFormat.RGBX8888:
780 				if(format & PixelFormat.BigEndian)
781 					converter!(RGBA8888BE);
782 				else
783 					converter!(RGBA8888);
784 				break;
785 			case PixelFormat.XRGB8888:
786 				if(format & PixelFormat.BigEndian)
787 					converter!(ARGB8888BE);
788 				else
789 					converter!(ARGB8888);
790 				break;
791 			case PixelFormat.RGB565:
792 				converter!(RGB565);
793 				break;
794 			case PixelFormat.RGBX5551:
795 				converter!(RGBA5551);
796 				break;
797 			default:
798 				throw new ImageFormatException("Format not supported");
799 		}
800 		return result;
801 	}
802 	public ARGB8888 read(uint x, uint y) @safe pure {
803 		const ubyte val = opIndex(x,y);
804 		return ARGB8888(val<<4 | val);
805 	}
806 	public RGBA_f32 readF(uint x, uint y) @safe pure {
807 		const ubyte val = opIndex(x,y);
808 		return RGBA_f32(val * fYStepping);
809 	}
810 	public ubyte opIndex(uint x, uint y) @safe pure {
811 		if(x < _width && y < _height) return accessor[x + (y * _pitch)];
812 		else throw new ImageBoundsException("Image is being read out of bounds!");
813 	}
814 	public ubyte opIndexAssign(ubyte val, uint x, uint y) @safe pure {
815 		if(x < _width && y < _height) return accessor[x + (y * _pitch)] = val;
816 		else throw new ImageBoundsException("Image is being read out of bounds!");
817 	}
818 	///Flips the image horizontally
819 	public void flipHorizontal() @safe pure {
820 		for (uint y ; y < _height ; y++) {
821 			for (uint x ; x < _width>>>1 ; x++) {
822 				const ubyte tmp = opIndex(x, y);
823 				opIndexAssign(opIndex(_width - x, y), x, y);
824 				opIndexAssign(tmp, _width - x, y);
825 			}
826 		}
827 	}
828 	///Flips the image vertically
829 	public void flipVertical() @safe pure {
830 		import std.algorithm.mutation : swapRanges;
831 		for (uint y ; y < _height / 2 ; y++) {
832 			const uint y0 = _height - y - 1;
833 			ubyte[] a = data[(y * _pitch/2)..((y + 1) * _pitch/2)];
834 			ubyte[] b = data[(y0 * _pitch/2)..((y0 + 1) * _pitch/2)];
835 			swapRanges(a, b);
836 		}
837 	}
838 }
839 /**
840  * 2 Bit grayscale image data.
841  */
842 public class MonochromeImageData2Bit : IImageData {
843 	public static immutable double fYStepping = 1.0 / 3;
844 	protected ubyte[]		data;
845 	protected QuadArray		accessor;
846 	protected uint			_width, _height, _pitch;
847 	///CTOR
848 	public this(ubyte[] data, uint width, uint height) @safe pure {
849 		this.data = data;
850 		this._width = width;
851 		this._height = height;
852 		_pitch = width;
853 		_pitch += width % 4 ? 4 - width % 4 : 0;
854 		accessor = QuadArray(data, _pitch * _height);
855 	}
856 	///CTOR without preexisting data
857 	public this(uint width, uint height) @safe pure {
858 		this._width = width;
859 		this._height = height;
860 		_pitch = _width;
861 		_pitch += _width % 4 ? 4 - _width % 4 : 0;
862 		data.length = _pitch * _height;
863 		accessor = QuadArray(data, _pitch * _height);
864 	}
865 	///Returns the raw data
866 	public @property QuadArray getData() @nogc @safe pure nothrow {
867 		return accessor;
868 	}
869 	///Returns the raw data cast to ubyte
870 	public ubyte[] raw() @safe pure {
871 		return data;
872 	}
873 	///Returns the width of the image.
874 	public @property uint width() @nogc @safe pure nothrow const {
875 		return _width;
876 	}
877 	///Returns the height of the image.
878 	public @property uint height() @nogc @safe pure nothrow const {
879 		return _height;
880 	}
881 
882 	public @property ubyte bitDepth() @nogc @safe pure nothrow const {
883 		return 2;
884 	}
885 
886 	public @property ubyte bitplanes() @nogc @safe pure nothrow const {
887 		return 1;
888 	}
889 
890 	public @property uint pixelFormat() @nogc @safe pure nothrow const {
891 		return PixelFormat.Grayscale2Bit;
892 	}
893 
894 	public IImageData convTo(uint format) @safe {
895 		IImageData result;
896 		void converter(OutputType)() @safe {
897 			OutputType[] array;
898 			array.reserve(_width * _height);
899 			for (uint y ; y < _height ; y++) {
900 				for (uint x ; x < _width ; x++) {
901 					array ~= OutputType(readF(x, y));
902 				}
903 			}
904 			result = new ImageData!OutputType(array, _width, _height, format, getBitDepth(format));
905 		}
906 		switch (format & ~(PixelFormat.BigEndian | PixelFormat.ValidAlpha)) {
907 			case PixelFormat.Grayscale16Bit:
908 				ushort[] datastream;
909 				datastream.length = _width * _height;
910 				MonochromeImageData!ushort mid = new MonochromeImageData!ushort(datastream, _width, _height, format, 16);
911 				for (int y ; y < _height ; y++) {
912 					for (int x ; x < _width ; x++) {
913 						const ubyte val = opIndex(x, y);
914 						mid[x,y] = cast(ushort)(val * 0b0101_0101_0101_0101);
915 					}
916 				}
917 				result = mid;
918 				break;
919 			case PixelFormat.Grayscale8Bit:
920 				ubyte[] datastream;
921 				datastream.length = _width * _height;
922 				MonochromeImageData!ubyte mid = new MonochromeImageData!ubyte(datastream, _width, _height, format, 8);
923 				for (int y ; y < _height ; y++) {
924 					for (int x ; x < _width ; x++) {
925 						const ubyte val = opIndex(x, y);
926 						mid[x,y] = cast(ubyte)(val * 0b0101_0101);
927 					}
928 				}
929 				result = mid;
930 				break;
931 			case PixelFormat.YX88:
932 				if(format & PixelFormat.BigEndian)
933 					converter!(YA88BE);
934 				else
935 					converter!(YA88);
936 				break;
937 			case PixelFormat.RGB888:
938 				if(format & PixelFormat.BigEndian)
939 					converter!(RGB888BE);
940 				else
941 					converter!(RGB888);
942 				break;
943 			case PixelFormat.RGBX8888:
944 				if(format & PixelFormat.BigEndian)
945 					converter!(RGBA8888BE);
946 				else
947 					converter!(RGBA8888);
948 				break;
949 			case PixelFormat.XRGB8888:
950 				if(format & PixelFormat.BigEndian)
951 					converter!(ARGB8888BE);
952 				else
953 					converter!(ARGB8888);
954 				break;
955 			case PixelFormat.RGB565:
956 				converter!(RGB565);
957 				break;
958 			case PixelFormat.RGBX5551:
959 				converter!(RGBA5551);
960 				break;
961 			default:
962 				throw new ImageFormatException("Format not supported");
963 		}
964 		return result;
965 	}
966 	public ARGB8888 read(uint x, uint y) @safe pure {
967 		const ubyte val = opIndex(x,y);
968 		return ARGB8888(val<<6 | val<<4 | val<<2 | val);
969 	}
970 	public RGBA_f32 readF(uint x, uint y) @safe pure {
971 		const ubyte val = opIndex(x,y);
972 		return RGBA_f32(val * fYStepping);
973 	}
974 	public ubyte opIndex(uint x, uint y) @safe pure {
975 		if(x < _width && y < _height) return accessor[x + (y * _pitch)];
976 		else throw new ImageBoundsException("Image is being read out of bounds!");
977 	}
978 	public ubyte opIndexAssign(ubyte val, uint x, uint y) @safe pure {
979 		if(x < _width && y < _height) return accessor[x + (y * _pitch)] = val;
980 		else throw new ImageBoundsException("Image is being read out of bounds!");
981 	}
982 	///Flips the image horizontally
983 	public void flipHorizontal() @safe pure {
984 		for (uint y ; y < _height ; y++) {
985 			for (uint x ; x < _width>>>1 ; x++) {
986 				const ubyte tmp = opIndex(x, y);
987 				opIndexAssign(opIndex(_width - x, y), x, y);
988 				opIndexAssign(tmp, _width - x, y);
989 			}
990 		}
991 	}
992 	///Flips the image vertically
993 	public void flipVertical() @safe pure {
994 		import std.algorithm.mutation : swapRanges;
995 		for (uint y ; y < _height / 2 ; y++) {
996 			const uint y0 = _height - y - 1;
997 			ubyte[] a = data[(y * _pitch/4)..((y + 1) * _pitch/4)];
998 			ubyte[] b = data[(y0 * _pitch/4)..((y0 + 1) * _pitch/4)];
999 			swapRanges(a, b);
1000 		}
1001 	}
1002 }
1003 /**
1004  * Monochrome 1 bit access
1005  */
1006 public class MonochromeImageData1Bit : IImageData {
1007 	protected ubyte[]		data;
1008 	protected BitArray		accessor;
1009 	protected uint			_width, _height, _pitch;
1010 	///CTOR
1011 	public this(ubyte[] data, uint _width, uint _height) @trusted pure {
1012 		this.data = data;
1013 		this._width = _width;
1014 		this._height = _height;
1015 		_pitch = _width;
1016 		_pitch += width % 8 ? 8 - width % 8 : 0;
1017 		accessor = BitArray(data, _pitch * _height);
1018 	}
1019 	///Returns the raw data
1020 	public @property BitArray getData() @nogc @safe pure nothrow {
1021 		return accessor;
1022 	}
1023 	///Returns the raw data cast to ubyte
1024 	public ubyte[] raw() @safe pure {
1025 		return data;
1026 	}
1027 	///Returns the width of the image.
1028 	public @property uint width() @nogc @safe pure nothrow const {
1029 		return _width;
1030 	}
1031 	///Returns the height of the image.
1032 	public @property uint height() @nogc @safe pure nothrow const {
1033 		return _height;
1034 	}
1035 
1036 	public @property ubyte bitDepth() @nogc @safe pure nothrow const {
1037 		return 1;
1038 	}
1039 
1040 	public @property ubyte bitplanes() @nogc @safe pure nothrow const {
1041 		return 1;
1042 	}
1043 
1044 	public @property uint pixelFormat() @nogc @safe pure nothrow const {
1045 		return PixelFormat.Grayscale1Bit;
1046 	}
1047 
1048 	public IImageData convTo(uint format) @safe {
1049 		IImageData result;
1050 		void converter(OutputType)() @safe {
1051 			OutputType[] array;
1052 			array.reserve(_width * _height);
1053 			for (uint y ; y < _height ; y++) {
1054 				for (uint x ; x < _width ; x++) {
1055 					array ~= OutputType(readF(x, y));
1056 				}
1057 			}
1058 			result = new ImageData!OutputType(array, _width, _height, format, getBitDepth(format));
1059 		}
1060 		
1061 		switch (format & ~(PixelFormat.BigEndian | PixelFormat.ValidAlpha)) {
1062 			case PixelFormat.Grayscale16Bit:
1063 				ushort[] datastream;
1064 				datastream.length = _width * _height;
1065 				MonochromeImageData!ushort mid = new MonochromeImageData!ushort(datastream, _width, _height, format, 16);
1066 				for (int y ; y < _height ; y++) {
1067 					for (int x ; x < _width ; x++) {
1068 						const RGBA_f32 pixel = readF(x,y);
1069 						mid[x,y] = cast(ushort)((pixel.fR * 0.2125 + pixel.fG * 0.7154 + pixel.fB * 0.0721) / MonochromeImageData!ushort.fYStepping);
1070 					}
1071 				}
1072 				result = mid;
1073 				break;
1074 			case PixelFormat.Grayscale8Bit:
1075 				ubyte[] datastream;
1076 				datastream.length = _width * _height;
1077 				MonochromeImageData!ubyte mid = new MonochromeImageData!ubyte(datastream, _width, _height, format, 8);
1078 				for (int y ; y < _height ; y++) {
1079 					for (int x ; x < _width ; x++) {
1080 						const RGBA_f32 pixel = readF(x,y);
1081 						mid[x,y] = cast(ubyte)((pixel.fR * 0.2125 + pixel.fG * 0.7154 + pixel.fB * 0.0721) / MonochromeImageData!ubyte.fYStepping);
1082 					}
1083 				}
1084 				result = mid;
1085 				break;
1086 			case PixelFormat.YX88:
1087 				if(format & PixelFormat.BigEndian)
1088 					converter!(YA88BE);
1089 				else
1090 					converter!(YA88);
1091 				break;
1092 			case PixelFormat.RGB888:
1093 				if(format & PixelFormat.BigEndian)
1094 					converter!(RGB888BE);
1095 				else
1096 					converter!(RGB888);
1097 				break;
1098 			case PixelFormat.RGBX8888:
1099 				if(format & PixelFormat.BigEndian)
1100 					converter!(RGBA8888BE);
1101 				else
1102 					converter!(RGBA8888);
1103 				break;
1104 			case PixelFormat.XRGB8888:
1105 				if(format & PixelFormat.BigEndian)
1106 					converter!(ARGB8888BE);
1107 				else
1108 					converter!(ARGB8888);
1109 				break;
1110 			case PixelFormat.RGB565:
1111 				converter!(RGB565);
1112 				break;
1113 			case PixelFormat.RGBX5551:
1114 				converter!(RGBA5551);
1115 				break;
1116 			default:
1117 				throw new ImageFormatException("Format not supported");
1118 		}
1119 		return result;
1120 	}
1121 
1122 	public ARGB8888 read(uint x, uint y) @safe pure {
1123 		if(x < _width && y < _height) return opIndex(x, y) ? ARGB8888(255) : ARGB8888(0);
1124 		else throw new ImageBoundsException("Image is being read out of bounds!");
1125 	}
1126 	public RGBA_f32 readF(uint x, uint y) @safe pure {
1127 		if(x < _width && y < _height) return opIndex(x, y) ? RGBA_f32(1.0) : RGBA_f32(0.0);
1128 		else throw new ImageBoundsException("Image is being read out of bounds!");
1129 	}
1130 	public bool opIndex(uint x, uint y) @trusted pure {
1131 		if(x < _width && y < _height) return accessor[x + (y * _pitch)];
1132 		else throw new ImageBoundsException("Image is being read out of bounds!");
1133 	}
1134 	public ubyte opIndexAssign(bool val, uint x, uint y) @trusted pure {
1135 		if(x < _width && y < _height) return accessor[x + (y * _pitch)] = val;
1136 		else throw new ImageBoundsException("Image is being read out of bounds!");
1137 	}
1138 	///Flips the image horizontally
1139 	public void flipHorizontal() @safe pure {
1140 		for (uint y ; y < _height ; y++) {
1141 			for (uint x ; x < _width>>>1 ; x++) {
1142 				const bool tmp = opIndex(x, y);
1143 				opIndexAssign(opIndex(_width - x, y), x, y);
1144 				opIndexAssign(tmp, _width - x, y);
1145 			}
1146 		}
1147 	}
1148 	///Flips the image vertically
1149 	public void flipVertical() @safe pure {
1150 		import std.algorithm.mutation : swapRanges;
1151 		for (uint y ; y < _height / 2 ; y++) {
1152 			const uint y0 = _height - y - 1;
1153 			ubyte[] a = data[(y * _pitch/8)..((y + 1) * _pitch/8)];
1154 			ubyte[] b = data[(y0 * _pitch/8)..((y0 + 1) * _pitch/8)];
1155 			swapRanges(a, b);
1156 		}
1157 	}
1158 }
1159 /**
1160  * Indexed imagedata container for ubyte and ushort based formats
1161  */
1162 public class IndexedImageData (T) : IImageData {
1163 	protected T[]			data;
1164 	public IPalette			palette;
1165 	protected uint			_width, _height;
1166 	///CTOR
1167 	public this(T[] data, IPalette palette, uint width, uint height) @safe pure {
1168 		this.data = data;
1169 		this.palette = palette;
1170 		this._width = width;
1171 		this._height = height;
1172 	}
1173 	///CTOR with no preexisting image data
1174 	public this(IPalette palette, uint width, uint height) @safe pure {
1175 		//assert(data.length == _width * _height);
1176 		this.data.length = width * height;
1177 		this.palette = palette;
1178 		this._width = width;
1179 		this._height = height;
1180 	}
1181 	///Returns the raw data
1182 	public @property T[] getData() @nogc @safe pure nothrow {
1183 		return data;
1184 	}
1185 	///Returns the raw data cast to ubyte
1186 	public ubyte[] raw() @safe pure {
1187 		return reinterpretCast!ubyte(data);
1188 	}
1189 	///Returns the width of the image.
1190 	public @property uint width() @nogc @safe pure nothrow const {
1191 		return _width;
1192 	}
1193 	///Returns the height of the image.
1194 	public @property uint height() @nogc @safe pure nothrow const {
1195 		return _height;
1196 	}
1197 
1198 	public @property ubyte bitDepth() @nogc @safe pure nothrow const {
1199 		static if(is(T == ubyte)) return 8;
1200 		else return 16;
1201 	}
1202 
1203 	public @property ubyte bitplanes() @nogc @safe pure nothrow const {
1204 		return 1;
1205 	}
1206 
1207 	public @property uint pixelFormat() @nogc @safe pure nothrow const {
1208 		static if(is(T == ubyte)) return PixelFormat.Indexed8Bit;
1209 		else return PixelFormat.Indexed16Bit;
1210 	}
1211 
1212 	public IImageData convTo(uint format) @safe {
1213 		IImageData result;
1214 		void converter(OutputType)() @safe {
1215 			OutputType[] array;
1216 			array.length = data.length;
1217 			for(int i ; i < data.length ; i++)
1218 				array[i] = OutputType(palette.read(data[i]));
1219 			result = new ImageData!OutputType(array, _width, _height, format, getBitDepth(format));
1220 		}
1221 		void upconv(OutputType)() @safe {
1222 			OutputType[] array;
1223 			array.length = data.length;
1224 			for(int i ; i < data.length ; i++)
1225 				array[i] = data[i];
1226 			result = new IndexedImageData!OutputType(array, palette, _width, _height);
1227 		}
1228 		switch (format & ~(PixelFormat.BigEndian | PixelFormat.ValidAlpha)) {
1229 			case PixelFormat.Indexed16Bit:
1230 				upconv!ushort;
1231 				break;
1232 			case PixelFormat.Grayscale16Bit:
1233 				ushort[] datastream;
1234 				datastream.length = _width * _height;
1235 				MonochromeImageData!ushort mid = new MonochromeImageData!ushort(datastream, _width, _height, format, 16);
1236 				for (int y ; y < _height ; y++) {
1237 					for (int x ; x < _width ; x++) {
1238 						const RGBA_f32 pixel = readF(x,y);
1239 						mid[x,y] = cast(ushort)((pixel.fR * 0.2125 + pixel.fG * 0.7154 + pixel.fB * 0.0721) / MonochromeImageData!ushort.fYStepping);
1240 					}
1241 				}
1242 				result = mid;
1243 				break;
1244 			case PixelFormat.Grayscale8Bit:
1245 				ubyte[] datastream;
1246 				datastream.length = _width * _height;
1247 				MonochromeImageData!ubyte mid = new MonochromeImageData!ubyte(datastream, _width, _height, format, 8);
1248 				for (int y ; y < _height ; y++) {
1249 					for (int x ; x < _width ; x++) {
1250 						const RGBA_f32 pixel = readF(x,y);
1251 						mid[x,y] = cast(ubyte)((pixel.fR * 0.2125 + pixel.fG * 0.7154 + pixel.fB * 0.0721) / MonochromeImageData!ubyte.fYStepping);
1252 					}
1253 				}
1254 				result = mid;
1255 				break;
1256 			case PixelFormat.YX88:
1257 				if(format & PixelFormat.BigEndian)
1258 					converter!(YA88BE);
1259 				else
1260 					converter!(YA88);
1261 				break;
1262 			case PixelFormat.RGB888:
1263 				if(format & PixelFormat.BigEndian)
1264 					converter!(RGB888BE);
1265 				else
1266 					converter!(RGB888);
1267 				break;
1268 			case PixelFormat.RGBX8888:
1269 				if(format & PixelFormat.BigEndian)
1270 					converter!(RGBA8888BE);
1271 				else
1272 					converter!(RGBA8888);
1273 				break;
1274 			case PixelFormat.XRGB8888:
1275 				if(format & PixelFormat.BigEndian)
1276 					converter!(ARGB8888BE);
1277 				else
1278 					converter!(ARGB8888);
1279 				break;
1280 			case PixelFormat.RGB565:
1281 				converter!(RGB565);
1282 				break;
1283 			case PixelFormat.RGBX5551:
1284 				converter!(RGBA5551);
1285 				break;
1286 			default:
1287 				throw new ImageFormatException("Format not supported");
1288 		}
1289 		return result;
1290 	}
1291 	public ARGB8888 read(uint x, uint y) @safe pure {
1292 		if(x < _width && y < _height) return palette.read(data[x + (y * _width)]);
1293 		else throw new ImageBoundsException("Image is being read out of bounds!");
1294 	}
1295 	public RGBA_f32 readF(uint x, uint y) @safe pure {
1296 		if(x < _width && y < _height) return palette.readF(data[x + (y * _width)]);
1297 		else throw new ImageBoundsException("Image is being read out of bounds!");
1298 	}
1299 	public ref T opIndex(uint x, uint y) @safe pure {
1300 		if(x < _width && y < _height) return data[x + (y * _width)];
1301 		else throw new ImageBoundsException("Image is being read out of bounds!");
1302 	}
1303 	///Flips the image horizontally
1304 	public void flipHorizontal() @safe pure {
1305 		for (uint y ; y < _height ; y++) {
1306 			for (uint x ; x < _width>>>1 ; x++) {
1307 				const T tmp = opIndex(x, y);
1308 				opIndex(x, y) = opIndex(_width - x, y);
1309 				opIndex(_width - x, y) = tmp;
1310 			}
1311 		}
1312 	}
1313 	///Flips the image vertically
1314 	public void flipVertical() @safe pure {
1315 		import std.algorithm.mutation : swapRanges;
1316 		for (uint y ; y < _height / 2 ; y++) {
1317 			const uint y0 = _height - y - 1;
1318 			T[] a = data[(y * _width)..((y + 1) * _width)];
1319 			T[] b = data[(y0 * _width)..((y0 + 1) * _width)];
1320 			swapRanges(a, b);
1321 		}
1322 	}
1323 }
1324 /**
1325  * 4 Bit indexed image data.
1326  */
1327 public class IndexedImageData4Bit : IImageData {
1328 	protected ubyte[]		data;
1329 	protected NibbleArray	accessor;
1330 	public IPalette			palette;
1331 	protected uint			_width, _height, _pitch;
1332 	///CTOR
1333 	public this(ubyte[] data, IPalette palette, uint width, uint height) @safe pure {
1334 		this.data = data;
1335 		this.palette = palette;
1336 		this._width = width;
1337 		this._height = height;
1338 		_pitch = _width + (_width % 2);
1339 		accessor = NibbleArray(data, _pitch * _height);
1340 	}
1341 	///CTOR with no preexisting image data
1342 	public this(IPalette palette, uint width, uint height) @safe pure {
1343 		//assert(data.length == _width * _height);
1344 		this._width = width;
1345 		this._height = height;
1346 		_pitch = _width + (_width % 2);
1347 		this.data.length = _pitch * _height;
1348 		accessor = NibbleArray(data, _pitch * _height);
1349 		this.palette = palette;
1350 	}
1351 	///Returns the raw data
1352 	public @property NibbleArray getData() @nogc @safe pure nothrow {
1353 		return accessor;
1354 	}
1355 	///Returns the raw data cast to ubyte
1356 	public ubyte[] raw() @safe pure {
1357 		return data;
1358 	}
1359 	///Returns the width of the image.
1360 	public @property uint width() @nogc @safe pure nothrow const {
1361 		return _width;
1362 	}
1363 	///Returns the height of the image.
1364 	public @property uint height() @nogc @safe pure nothrow const {
1365 		return _height;
1366 	}
1367 
1368 	public @property ubyte bitDepth() @nogc @safe pure nothrow const {
1369 		return 4;
1370 	}
1371 
1372 	public @property ubyte bitplanes() @nogc @safe pure nothrow const {
1373 		return 1;
1374 	}
1375 
1376 	public @property uint pixelFormat() @nogc @safe pure nothrow const {
1377 		return PixelFormat.Indexed4Bit;
1378 	}
1379 
1380 	public IImageData convTo(uint format) @safe {
1381 		IImageData result;
1382 		void converter(OutputType)() @safe {
1383 			OutputType[] array;
1384 			array.length = data.length;
1385 			for(int i ; i < data.length ; i++)
1386 				array[i] = OutputType(palette.read(data[i]));
1387 			result = new ImageData!OutputType(array, _width, _height, format, getBitDepth(format));
1388 		}
1389 		void upconv(OutputType)() @safe {
1390 			IndexedImageData!OutputType iid = new IndexedImageData!OutputType(palette, _width, _height);
1391 			for(int y ; y < _height ; y++)
1392 				for(int x ; x < _width ; x++)
1393 					iid[x, y] = opIndex(x, y);
1394 			result = iid;
1395 		}
1396 		switch (format & ~(PixelFormat.BigEndian | PixelFormat.ValidAlpha)) {
1397 			case PixelFormat.Indexed16Bit:
1398 				upconv!ushort;
1399 				break;
1400 			case PixelFormat.Indexed8Bit:
1401 				upconv!ubyte;
1402 				break;
1403 			case PixelFormat.Grayscale16Bit:
1404 				ushort[] datastream;
1405 				datastream.length = _width * _height;
1406 				MonochromeImageData!ushort mid = new MonochromeImageData!ushort(datastream, _width, _height, format, 16);
1407 				for (int y ; y < _height ; y++) {
1408 					for (int x ; x < _width ; x++) {
1409 						const RGBA_f32 pixel = readF(x,y);
1410 						mid[x,y] = cast(ushort)((pixel.fR * 0.2125 + pixel.fG * 0.7154 + pixel.fB * 0.0721) / MonochromeImageData!ushort.fYStepping);
1411 					}
1412 				}
1413 				result = mid;
1414 				break;
1415 			case PixelFormat.Grayscale8Bit:
1416 				ubyte[] datastream;
1417 				datastream.length = _width * _height;
1418 				MonochromeImageData!ubyte mid = new MonochromeImageData!ubyte(datastream, _width, _height, format, 8);
1419 				for (int y ; y < _height ; y++) {
1420 					for (int x ; x < _width ; x++) {
1421 						const RGBA_f32 pixel = readF(x,y);
1422 						mid[x,y] = cast(ubyte)((pixel.fR * 0.2125 + pixel.fG * 0.7154 + pixel.fB * 0.0721) / MonochromeImageData!ubyte.fYStepping);
1423 					}
1424 				}
1425 				result = mid;
1426 				break;
1427 			case PixelFormat.YX88:
1428 				if(format & PixelFormat.BigEndian)
1429 					converter!(YA88BE);
1430 				else
1431 					converter!(YA88);
1432 				break;
1433 			case PixelFormat.RGB888:
1434 				if(format & PixelFormat.BigEndian)
1435 					converter!(RGB888BE);
1436 				else
1437 					converter!(RGB888);
1438 				break;
1439 			case PixelFormat.RGBX8888:
1440 				if(format & PixelFormat.BigEndian)
1441 					converter!(RGBA8888BE);
1442 				else
1443 					converter!(RGBA8888);
1444 				break;
1445 			case PixelFormat.XRGB8888:
1446 				if(format & PixelFormat.BigEndian)
1447 					converter!(ARGB8888BE);
1448 				else
1449 					converter!(ARGB8888);
1450 				break;
1451 			case PixelFormat.RGB565:
1452 				converter!(RGB565);
1453 				break;
1454 			case PixelFormat.RGBX5551:
1455 				converter!(RGBA5551);
1456 				break;
1457 			default:
1458 				throw new ImageFormatException("Format not supported");
1459 		}
1460 		return result;
1461 	}
1462 	public ARGB8888 read(uint x, uint y) @safe pure {
1463 		if(x < _width && y < _height) return palette.read(accessor[x + (y * _pitch)]);
1464 		else throw new ImageBoundsException("Image is being read out of bounds!");
1465 	}
1466 	public RGBA_f32 readF(uint x, uint y) @safe pure {
1467 		if(x < _width && y < _height) return palette.readF(accessor[x + (y * _pitch)]);
1468 		else throw new ImageBoundsException("Image is being read out of bounds!");
1469 	}
1470 	public ubyte opIndex(uint x, uint y) @safe pure {
1471 		if(x < _width && y < _height) return accessor[x + (y * _pitch)];
1472 		else throw new ImageBoundsException("Image is being read out of bounds!");
1473 	}
1474 	public ubyte opIndexAssign(ubyte val, uint x, uint y) @safe pure {
1475 		if(x < _width && y < _height) return accessor[x + (y * _pitch)] = val;
1476 		else throw new ImageBoundsException("Image is being read out of bounds!");
1477 	}
1478 	///Flips the image horizontally
1479 	public void flipHorizontal() @safe pure {
1480 		for (uint y ; y < _height ; y++) {
1481 			for (uint x ; x < _width>>>1 ; x++) {
1482 				const ubyte tmp = opIndex(x, y);
1483 				opIndexAssign(opIndex(_width - x, y), x, y);
1484 				opIndexAssign(tmp, _width - x, y);
1485 			}
1486 		}
1487 	}
1488 	///Flips the image vertically
1489 	public void flipVertical() @safe pure {
1490 		import std.algorithm.mutation : swapRanges;
1491 		for (uint y ; y < _height / 2 ; y++) {
1492 			const uint y0 = _height - y - 1;
1493 			ubyte[] a = data[(y * _pitch/2)..((y + 1) * _pitch/2)];
1494 			ubyte[] b = data[(y0 * _pitch/2)..((y0 + 1) * _pitch/2)];
1495 			swapRanges(a, b);
1496 		}
1497 	}
1498 }
1499 /**
1500  * 2 Bit indexed image data.
1501  */
1502 public class IndexedImageData2Bit : IImageData {
1503 	protected ubyte[]		data;
1504 	protected QuadArray		accessor;
1505 	public IPalette			palette;
1506 	protected uint			_width, _height, _pitch;
1507 	///CTOR
1508 	public this(ubyte[] data, IPalette palette, uint width, uint height) @safe pure {
1509 		this.data = data;
1510 		this.palette = palette;
1511 		this._width = width;
1512 		this._height = height;
1513 		_pitch = width;
1514 		_pitch += width % 4 ? 4 - width % 4 : 0;
1515 		accessor = QuadArray(data, _pitch * _height);
1516 	}
1517 	///CTOR without preexisting data
1518 	public this(IPalette palette, uint width, uint height) @safe pure {
1519 		this.palette = palette;
1520 		this._width = width;
1521 		this._height = height;
1522 		_pitch = _width;
1523 		_pitch += _width % 4 ? 4 - _width % 4 : 0;
1524 		data.length = _pitch * _height;
1525 		accessor = QuadArray(data, _pitch * _height);
1526 	}
1527 	///Returns the raw data
1528 	public @property QuadArray getData() @nogc @safe pure nothrow {
1529 		return accessor;
1530 	}
1531 	///Returns the raw data cast to ubyte
1532 	public ubyte[] raw() @safe pure {
1533 		return data;
1534 	}
1535 	///Returns the width of the image.
1536 	public @property uint width() @nogc @safe pure nothrow const {
1537 		return _width;
1538 	}
1539 	///Returns the height of the image.
1540 	public @property uint height() @nogc @safe pure nothrow const {
1541 		return _height;
1542 	}
1543 
1544 	public @property ubyte bitDepth() @nogc @safe pure nothrow const {
1545 		return 2;
1546 	}
1547 
1548 	public @property ubyte bitplanes() @nogc @safe pure nothrow const {
1549 		return 1;
1550 	}
1551 
1552 	public @property uint pixelFormat() @nogc @safe pure nothrow const {
1553 		return PixelFormat.Indexed2Bit;
1554 	}
1555 
1556 	public IImageData convTo(uint format) @safe {
1557 		IImageData result;
1558 		void converter(OutputType)() @safe {
1559 			OutputType[] array;
1560 			array.length = data.length;
1561 			for(int i ; i < data.length ; i++)
1562 				array[i] = OutputType(palette.read(data[i]));
1563 			result = new ImageData!OutputType(array, _width, _height, format, getBitDepth(format));
1564 		}
1565 		void upconv(OutputType)() @safe {
1566 			IndexedImageData!OutputType iid = new IndexedImageData!OutputType(palette, _width, _height);
1567 			for(int y ; y < _height ; y++)
1568 				for(int x ; x < _width ; x++)
1569 					iid[x, y] = opIndex(x, y);
1570 			result = iid;
1571 		}
1572 		switch (format & ~(PixelFormat.BigEndian | PixelFormat.ValidAlpha)) {
1573 			case PixelFormat.Indexed16Bit:
1574 				upconv!ushort;
1575 				break;
1576 			case PixelFormat.Indexed8Bit:
1577 				upconv!ubyte;
1578 				break;
1579 			case PixelFormat.Indexed4Bit:
1580 				IndexedImageData4Bit iid = new IndexedImageData4Bit(palette, _width, _height);
1581 				for(int y ; y < _height ; y++)
1582 					for(int x ; x < _width ; x++)
1583 						iid[x, y] = opIndex(x, y);
1584 				result = iid;
1585 				break;
1586 			case PixelFormat.Grayscale16Bit:
1587 				ushort[] datastream;
1588 				datastream.length = _width * _height;
1589 				MonochromeImageData!ushort mid = new MonochromeImageData!ushort(datastream, _width, _height, format, 16);
1590 				for (int y ; y < _height ; y++) {
1591 					for (int x ; x < _width ; x++) {
1592 						const RGBA_f32 pixel = readF(x,y);
1593 						mid[x,y] = cast(ushort)((pixel.fR * 0.2125 + pixel.fG * 0.7154 + pixel.fB * 0.0721) / MonochromeImageData!ushort.fYStepping);
1594 					}
1595 				}
1596 				result = mid;
1597 				break;
1598 			case PixelFormat.Grayscale8Bit:
1599 				ubyte[] datastream;
1600 				datastream.length = _width * _height;
1601 				MonochromeImageData!ubyte mid = new MonochromeImageData!ubyte(datastream, _width, _height, format, 8);
1602 				for (int y ; y < _height ; y++) {
1603 					for (int x ; x < _width ; x++) {
1604 						const RGBA_f32 pixel = readF(x,y);
1605 						mid[x,y] = cast(ubyte)((pixel.fR * 0.2125 + pixel.fG * 0.7154 + pixel.fB * 0.0721) / MonochromeImageData!ubyte.fYStepping);
1606 					}
1607 				}
1608 				result = mid;
1609 				break;
1610 			case PixelFormat.YX88:
1611 				if(format & PixelFormat.BigEndian)
1612 					converter!(YA88BE);
1613 				else
1614 					converter!(YA88);
1615 				break;
1616 			case PixelFormat.RGB888:
1617 				if(format & PixelFormat.BigEndian)
1618 					converter!(RGB888BE);
1619 				else
1620 					converter!(RGB888);
1621 				break;
1622 			case PixelFormat.RGBX8888:
1623 				if(format & PixelFormat.BigEndian)
1624 					converter!(RGBA8888BE);
1625 				else
1626 					converter!(RGBA8888);
1627 				break;
1628 			case PixelFormat.XRGB8888:
1629 				if(format & PixelFormat.BigEndian)
1630 					converter!(ARGB8888BE);
1631 				else
1632 					converter!(ARGB8888);
1633 				break;
1634 			case PixelFormat.RGB565:
1635 				converter!(RGB565);
1636 				break;
1637 			case PixelFormat.RGBX5551:
1638 				converter!(RGBA5551);
1639 				break;
1640 			default:
1641 				throw new ImageFormatException("Format not supported");
1642 		}
1643 		return result;
1644 	}
1645 	public ARGB8888 read(uint x, uint y) @safe pure {
1646 		if(x < _width && y < _height) return palette.read(accessor[x + (y * _pitch)]);
1647 		else throw new ImageBoundsException("Image is being read out of bounds!");
1648 	}
1649 	public RGBA_f32 readF(uint x, uint y) @safe pure {
1650 		if(x < _width && y < _height) return palette.readF(accessor[x + (y * _pitch)]);
1651 		else throw new ImageBoundsException("Image is being read out of bounds!");
1652 	}
1653 	public ubyte opIndex(uint x, uint y) @safe pure {
1654 		if(x < _width && y < _height) return accessor[x + (y * _pitch)];
1655 		else throw new ImageBoundsException("Image is being read out of bounds!");
1656 	}
1657 	public ubyte opIndexAssign(ubyte val, uint x, uint y) @safe pure {
1658 		if(x < _width && y < _height) return accessor[x + (y * _pitch)] = val;
1659 		else throw new ImageBoundsException("Image is being read out of bounds!");
1660 	}
1661 	///Flips the image horizontally
1662 	public void flipHorizontal() @safe pure {
1663 		for (uint y ; y < _height ; y++) {
1664 			for (uint x ; x < _width>>>1 ; x++) {
1665 				const ubyte tmp = opIndex(x, y);
1666 				opIndexAssign(opIndex(_width - x, y),x, y);
1667 				opIndexAssign(tmp, _width - x, y);
1668 			}
1669 		}
1670 	}
1671 	///Flips the image vertically
1672 	public void flipVertical() @safe pure {
1673 		import std.algorithm.mutation : swapRanges;
1674 		for (uint y ; y < _height / 2 ; y++) {
1675 			const uint y0 = _height - y - 1;
1676 			ubyte[] a = data[(y * _pitch/4)..((y + 1) * _pitch/4)];
1677 			ubyte[] b = data[(y0 * _pitch/4)..((y0 + 1) * _pitch/4)];
1678 			swapRanges(a, b);
1679 		}
1680 	}
1681 }
1682 /**
1683  * Monochrome 1 bit access
1684  */
1685 public class IndexedImageData1Bit : IImageData {
1686 	protected ubyte[]		data;
1687 	protected BitArray		accessor;
1688 	public IPalette			palette;
1689 	protected uint			_width, _height, _pitch;
1690 	///CTOR
1691 	public this(ubyte[] data, IPalette palette, uint _width, uint _height) @trusted pure {
1692 		this.data = data;
1693 		this.palette = palette;
1694 		this._width = _width;
1695 		this._height = _height;
1696 		_pitch = _width;
1697 		_pitch += width % 8 ? 8 - width % 8 : 0;
1698 		accessor = BitArray(data, _pitch * _height);
1699 	}
1700 	///Returns the raw data
1701 	public @property BitArray getData() @nogc @safe pure nothrow {
1702 		return accessor;
1703 	}
1704 	///Returns the raw data cast to ubyte
1705 	public ubyte[] raw() @safe pure {
1706 		return data;
1707 	}
1708 	///Returns the width of the image.
1709 	public @property uint width() @nogc @safe pure nothrow const {
1710 		return _width;
1711 	}
1712 	///Returns the height of the image.
1713 	public @property uint height() @nogc @safe pure nothrow const {
1714 		return _height;
1715 	}
1716 
1717 	public @property ubyte bitDepth() @nogc @safe pure nothrow const {
1718 		return 1;
1719 	}
1720 
1721 	public @property ubyte bitplanes() @nogc @safe pure nothrow const {
1722 		return 1;
1723 	}
1724 
1725 	public @property uint pixelFormat() @nogc @safe pure nothrow const {
1726 		return PixelFormat.Indexed1Bit;
1727 	}
1728 
1729 	public IImageData convTo(uint format) @safe {
1730 		IImageData result;
1731 		void converter(OutputType)() @safe {
1732 			OutputType[] array;
1733 			array.length = data.length;
1734 			for(int i ; i < data.length ; i++)
1735 				array[i] = OutputType(palette.read(data[i]));
1736 			result = new ImageData!OutputType(array, _width, _height, format, getBitDepth(format));
1737 		}
1738 		void upconv(OutputType)() @safe {
1739 			IndexedImageData!OutputType iid = new IndexedImageData!OutputType(palette, _width, _height);
1740 			for(int y ; y < _height ; y++)
1741 				for(int x ; x < _width ; x++)
1742 					iid[x, y] = opIndex(x, y);
1743 			result = iid;
1744 		}
1745 		switch (format & ~(PixelFormat.BigEndian | PixelFormat.ValidAlpha)) {
1746 			case PixelFormat.Indexed16Bit:
1747 				upconv!ushort;
1748 				break;
1749 			case PixelFormat.Indexed8Bit:
1750 				upconv!ubyte;
1751 				break;
1752 			case PixelFormat.Indexed4Bit:
1753 				IndexedImageData4Bit iid = new IndexedImageData4Bit(palette, _width, _height);
1754 				for(int y ; y < _height ; y++)
1755 					for(int x ; x < _width ; x++)
1756 						iid[x, y] = opIndex(x, y) ? 0x01 : 0x00;
1757 				result = iid;
1758 				break;
1759 			case PixelFormat.Indexed2Bit:
1760 				IndexedImageData2Bit iid = new IndexedImageData2Bit(palette, _width, _height);
1761 				for(int y ; y < _height ; y++)
1762 					for(int x ; x < _width ; x++)
1763 						iid[x, y] = opIndex(x, y) ? 0x01 : 0x00;
1764 				result = iid;
1765 				break;
1766 			case PixelFormat.Grayscale16Bit:
1767 				ushort[] datastream;
1768 				datastream.length = _width * _height;
1769 				MonochromeImageData!ushort mid = new MonochromeImageData!ushort(datastream, _width, _height, format, 16);
1770 				for (int y ; y < _height ; y++) {
1771 					for (int x ; x < _width ; x++) {
1772 						const RGBA_f32 pixel = readF(x,y);
1773 						mid[x,y] = cast(ushort)((pixel.fR * 0.2125 + pixel.fG * 0.7154 + pixel.fB * 0.0721) / MonochromeImageData!ushort.fYStepping);
1774 					}
1775 				}
1776 				result = mid;
1777 				break;
1778 			case PixelFormat.Grayscale8Bit:
1779 				ubyte[] datastream;
1780 				datastream.length = _width * _height;
1781 				MonochromeImageData!ubyte mid = new MonochromeImageData!ubyte(datastream, _width, _height, format, 8);
1782 				for (int y ; y < _height ; y++) {
1783 					for (int x ; x < _width ; x++) {
1784 						const RGBA_f32 pixel = readF(x,y);
1785 						mid[x,y] = cast(ubyte)((pixel.fR * 0.2125 + pixel.fG * 0.7154 + pixel.fB * 0.0721) / MonochromeImageData!ubyte.fYStepping);
1786 					}
1787 				}
1788 				result = mid;
1789 				break;
1790 			case PixelFormat.YX88:
1791 				if(format & PixelFormat.BigEndian)
1792 					converter!(YA88BE);
1793 				else
1794 					converter!(YA88);
1795 				break;
1796 			case PixelFormat.RGB888:
1797 				if(format & PixelFormat.BigEndian)
1798 					converter!(RGB888BE);
1799 				else
1800 					converter!(RGB888);
1801 				break;
1802 			case PixelFormat.RGBX8888:
1803 				if(format & PixelFormat.BigEndian)
1804 					converter!(RGBA8888BE);
1805 				else
1806 					converter!(RGBA8888);
1807 				break;
1808 			case PixelFormat.XRGB8888:
1809 				if(format & PixelFormat.BigEndian)
1810 					converter!(ARGB8888BE);
1811 				else
1812 					converter!(ARGB8888);
1813 				break;
1814 			case PixelFormat.RGB565:
1815 				converter!(RGB565);
1816 				break;
1817 			case PixelFormat.RGBX5551:
1818 				converter!(RGBA5551);
1819 				break;
1820 			default:
1821 				throw new ImageFormatException("Format not supported");
1822 		}
1823 		return result;
1824 	}
1825 
1826 	public ARGB8888 read(uint x, uint y) @safe pure {
1827 		return palette.read(opIndex(x, y));
1828 	}
1829 	public RGBA_f32 readF(uint x, uint y) @safe pure {
1830 		return palette.readF(opIndex(x, y));
1831 	}
1832 	public bool opIndex(uint x, uint y) @trusted pure {
1833 		if(x < _width && y < _height) return accessor[x + (y * _pitch)];
1834 		else throw new ImageBoundsException("Image is being read out of bounds!");
1835 	}
1836 	public ubyte opIndexAssign(bool val, uint x, uint y) @trusted pure {
1837 		if(x < _width && y < _height) return accessor[x + (y * _pitch)] = val;
1838 		else throw new ImageBoundsException("Image is being read out of bounds!");
1839 	}
1840 	///Flips the image horizontally
1841 	public void flipHorizontal() @safe pure {
1842 		for (uint y ; y < _height ; y++) {
1843 			for (uint x ; x < _width>>>1 ; x++) {
1844 				const bool tmp = opIndex(x, y);
1845 				opIndexAssign(opIndex(_width - x, y), x, y);
1846 				opIndexAssign(tmp, _width - x, y);
1847 			}
1848 		}
1849 	}
1850 	///Flips the image vertically
1851 	public void flipVertical() @safe pure {
1852 		import std.algorithm.mutation : swapRanges;
1853 		for (uint y ; y < _height / 2 ; y++) {
1854 			const uint y0 = _height - y - 1;
1855 			ubyte[] a = data[(y * _pitch/8)..((y + 1) * _pitch/8)];
1856 			ubyte[] b = data[(y0 * _pitch/8)..((y0 + 1) * _pitch/8)];
1857 			swapRanges(a, b);
1858 		}
1859 	}
1860 }
1861 /**
1862  * All image classes should be derived from this base.
1863  * Implements some basic functionality, such as reading and writing pixels, basic data storage, and basic information.
1864  * Pixeldata should be stored decompressed, but indexing should be preserved on loading with the opinion of upconverting
1865  * to truecolor.
1866  */
1867 abstract class Image{
1868 	/**
1869 	 * Contains palette data and information
1870 	 */
1871 	protected IPalette _palette;
1872 	/**
1873 	 * Contains image data and information.
1874 	 */
1875 	protected IImageData _imageData;
1876 	protected ubyte mod;	///used for fast access of indexes DEPRECATED!
1877 	protected ubyte shift;	///used for fast access of indexes DEPRECATED!
1878 
1879 	/+protected @safe pure ubyte delegate(uint x, uint y) indexReader8Bit;		///Used for bypassing typechecking when reading pixels
1880 	protected @safe pure ushort delegate(uint x, uint y) indexReader16bit;	///Used for bypassing typechecking when reading pixels
1881 	protected @safe pure ubyte delegate(uint x, uint y, ubyte val) indexWriter8Bit;	///Used for bypassing typechecking when writing pixels
1882 	protected @safe pure ushort delegate(uint x, uint y, ushort val) indexWriter16bit;	///Used for bypassing typechecking when writing pixels
1883 	protected @safe pure ARGB8888 delegate(uint x, uint y) pixelReader;		//Used for bypassing typechecking
1884 	protected @safe pure ARGB8888 delegate(ushort i) paletteReader;			//Used for bypassing typechecking
1885 	+/
1886 	
1887 	/+protected uint	pitch;	///Contains the precalculated scanline size with the occassional padding for 8bit values.+/
1888 	///Returns the width of the image in pixels.
1889 	@property uint width() @nogc @safe pure nothrow const {
1890 		return _imageData.width;
1891 	}
1892 	///Returns the height of the image in pixels.
1893 	@property uint height() @nogc @safe pure nothrow const {
1894 		return _imageData.height;
1895 	}
1896 	///Returns true if the image is indexed.
1897 	@property bool isIndexed() @nogc @safe pure nothrow const {
1898 		return _palette !is null;
1899 	}
1900 	///Returns the number of bits used per sample.
1901 	@property ubyte getBitdepth() @nogc @safe pure nothrow const {
1902 		return _imageData.bitDepth;
1903 	}
1904 	///Returns the number of bits used per colormap entry.
1905 	@property ubyte getPaletteBitdepth() @nogc @safe pure nothrow const {
1906 		if (_palette) return _palette.bitDepth;
1907 		else return 0;
1908 	}
1909 	///Returns the pixelformat of the image. See enumerator `PixelFormat` for more info.
1910 	@property uint getPixelFormat() @nogc @safe pure nothrow const {
1911 		return _imageData.pixelFormat;
1912 	}
1913 	///Returns the pixelformat of the palette. See enumerator `PixelFormat` for more info.
1914 	@property uint getPalettePixelFormat() @nogc @safe pure nothrow const {
1915 		if (_palette) return _palette.paletteFormat;
1916 		else return PixelFormat.Undefined;
1917 	}
1918 	///Returns the background color index if there's any. Returns -1 if there's no background color, -2 if background color is not indexed.
1919 	@property int backgroundColorIndex() @nogc @safe pure nothrow const {
1920 		return -1;
1921 	}
1922 	///Returns the background color if there's any, or a default value otherwise.
1923 	@property ARGB8888 backgroundColor() @nogc @safe pure nothrow const {
1924 		return ARGB8888.init;
1925 	}
1926 	/**
1927 	 * Returns the number of planes the image have.
1928 	 * Default is one.
1929 	 */
1930 	public ubyte getBitplanes() @safe pure {
1931 		return _imageData.bitplanes;
1932 	}
1933 	/**
1934 	 * Returns a palette range, which can be used to read the palette.
1935 	 */
1936 	public IPalette palette() @safe @property pure {
1937 		return _palette;
1938 	}
1939 	/**
1940 	 * Returns the image data.
1941 	 */
1942 	public IImageData imageData() @safe @property pure {
1943 		return _imageData;
1944 	}
1945 	/**
1946 	 * Reads a single 32bit pixel. If the image is indexed, a color lookup will be done.
1947 	 */
1948 	public ARGB8888 readPixel(uint x, uint y) @safe pure {
1949 		return _imageData.read(x, y);
1950 	}
1951 	/**
1952 	 * Looks up the index on the palette, then returns the color value as a 32 bit value.
1953 	 */
1954 	public ARGB8888 readPalette(size_t index) @safe pure {
1955 		return _palette.read(index);
1956 	}
1957 	/**
1958 	 * Flips the image on the vertical axis. Useful to set images to the correct top-left screen origin point.
1959 	 */
1960 	public void flipVertical() @safe pure {
1961 		_imageData.flipVertical;
1962 	}
1963 	/**
1964 	 * Flips the image on the vertical axis. Useful to set images to the correct top-left screen origin point.
1965 	 */
1966 	public void flipHorizontal() @safe pure {
1967 		_imageData.flipVertical;
1968 	}
1969 	///Returns true if the image originates from the top
1970 	public bool topOrigin() @property @nogc @safe pure const {
1971 		return false;
1972 	}
1973 	///Returns true if the image originates from the right
1974 	public bool rightSideOrigin() @property @nogc @safe pure const {
1975 		return false;
1976 	}
1977 }