1 /*
2  * dimage - tga.d
3  * by Laszlo Szeremi
4  *
5  * Copyright under Boost Software License.
6  */
7 
8 module dimage.tga;
9 
10 import std.bitmanip;
11 static import std.stdio;
12 
13 public import dimage.base;
14 import dimage.util;
15 import bitleveld.datatypes;
16 
17 //import core.stdc.string;
18 import std.conv : to;
19 
20 /**
21  * Implements the Truevision Graphics bitmap file format (*.tga) with some extra capabilities at the cost
22  * of making them unusable in applications not implementing these features:
23  * 
24  * * Capability of storing 1, 2, and 4 bit indexed images.
25  * * External color map support.
26  * * More coming soon such as more advanced compression methods.
27  * 
28  * Accessing developer area is fully implemented, accessing extension area is partly implemented.
29  */
30 public class TGA : Image, ImageMetadata{
31 	/**
32 	 * Implements Truevision Graphics bitmap header.
33 	 */
34 	public struct Header {
35 	align(1) :
36 		/**
37 		 * Defines the type of the color map.
38 		 */
39 		public enum ColorMapType{
40 			NoColorMapPresent		=	0,
41 			ColorMapPresent			=	1,
42 			/**
43 			 * In this case, the palette is stored in a *.pal file, colorMapLength specifies the lenght of the filename, and the usual colorMap field instead stores the filename.
44 			 */
45 			ExtColorMap				=	128,	
46 		}
47 		/**
48 		 * Defines the type of the image.
49 		 */
50 		enum ImageType : ubyte {
51 			NoData					=	0,
52 			UncompressedMapped		=	1,
53 			UncompressedTrueColor	=	2,
54 			UncompressedGrayscale	=	3,
55 			RLEMapped				=	9,	/// RLE in 8 or 16 bit chunks
56 			RLETrueColor			=	10,
57 			RLEGrayscale			=	11,
58 			/**
59 			 * RLE optimized for 4 bit bitmaps. Added by me. Also works with 2 and 1 bit bitmaps
60 			 * Packet layout:
61 			 * bits 0 - 3: index needs to written
62 			 * bits 4 - 7: repeated occurence of indexes + 1 (1-16)
63 			 */
64 			RLE4BitMapped			=	12,
65 			/**
66 			 * RLE optimized for 1 bit bitmaps. Added by me.
67 			 * Packet layout:
68 			 * Every odd numbered byte: n of zeros (0-255)
69 			 * Every even numbered byte: n of ones (0-255)
70 			 */
71 			RLE1BitMapped			=	13,
72 			/**
73 			 * Mapped image with Huffman-Delta-RLE compression.
74 			 * I can't find any info on how this works, so I currently just leaving it as a placeholder
75 			 */
76 			HDRLEMapped				=	32,
77 			/**
78 			 * Mapped image with Huffman-Delta-RLE compression with 4-pass quadtree-type process.
79 			 * I can't find any info on how this works, so I currently just leaving it as a placeholder
80 			 */
81 			HDRLEMappedQT			=	33
82 		}
83 		ubyte			idLength;           /// length in bytes
84 		ubyte			colorMapType;		/// See ColorMapType enumerator
85 		ubyte			imageType;			/// See ImageType enumerator
86 		ushort			colorMapOffset;     /// index of first actual map entry
87 		ushort			colorMapLength;     /// number of total entries (incl. skipped)
88 		ubyte			colorMapDepth;      /// bits per pixel (entry)
89 		ushort			xOrigin;			/// X origin of the image on the screen
90 		ushort			yOrigin;			/// Y origin of the image on the screen
91 		ushort			width;				/// Image width
92 		ushort			height;				/// Image height
93 		ubyte			pixelDepth;         /// bits per pixel
94 		//imageDescriptor:
95 		mixin(bitfields!(
96 			ubyte, "alphaChannelBits", 4, 
97 			bool , "rightSideOrigin", 1, 
98 			bool , "topOrigin", 1, 
99 			ubyte, "interleaving", 2, 
100 		));
101 		///Sets the colormapdepth of the image
102 		public void setColorMapDepth(uint format) @safe pure {
103 			switch (format) {
104 				case PixelFormat.Grayscale8Bit:
105 					colorMapDepth = 8;
106 					break;
107 				case PixelFormat.RGBX5551, PixelFormat.RGBA5551:
108 					colorMapDepth = 16;
109 					break;
110 				/+case PixelFormat.RGB565:
111 					colorMapDepth = 16;
112 					break;+/
113 				case PixelFormat.RGB888:
114 					colorMapDepth = 24;
115 					break;
116 				case PixelFormat.RGBX8888, PixelFormat.RGBA8888:
117 					colorMapDepth = 32;
118 					break;
119 				default: throw new ImageFormatException("Palette format not supported!");
120 			}
121 		}
122 		///Sets the pixeldepth of the image
123 		public void setImageType(uint format, bool isRLE) @safe pure {
124 			switch (format) {
125 				case PixelFormat.Grayscale8Bit:
126 					pixelDepth = 8;
127 					if (isRLE) imageType = ImageType.RLEGrayscale;
128 					else imageType = ImageType.UncompressedGrayscale;
129 					break;
130 				case PixelFormat.Indexed8Bit:
131 					pixelDepth = 8;
132 					if (isRLE) imageType = ImageType.RLEMapped;
133 					else imageType = ImageType.UncompressedMapped;
134 					break;
135 				case PixelFormat.Indexed16Bit:
136 					pixelDepth = 16;
137 					if (isRLE) imageType = ImageType.RLEMapped;
138 					else imageType = ImageType.UncompressedMapped;
139 					break;
140 				case PixelFormat.Indexed4Bit:
141 					pixelDepth = 4;
142 					if (isRLE) imageType = ImageType.RLE4BitMapped;
143 					else imageType = ImageType.UncompressedMapped;
144 					break;
145 				case PixelFormat.Indexed2Bit:
146 					pixelDepth = 2;
147 					if (isRLE) imageType = ImageType.RLEMapped;
148 					else imageType = ImageType.UncompressedMapped;
149 					break;
150 				case PixelFormat.Indexed1Bit:
151 					pixelDepth = 1;
152 					if (isRLE) imageType = ImageType.RLE1BitMapped;
153 					else imageType = ImageType.UncompressedMapped;
154 					break;
155 				case PixelFormat.RGBA5551, PixelFormat.RGBX5551:
156 					pixelDepth = 16;
157 					alphaChannelBits = 1;
158 					if (isRLE) imageType = ImageType.RLETrueColor;
159 					else imageType = ImageType.UncompressedTrueColor;
160 					break;
161 				case PixelFormat.RGB888:
162 					pixelDepth = 24;
163 					if (isRLE) imageType = ImageType.RLETrueColor;
164 					else imageType = ImageType.UncompressedTrueColor;
165 					break;
166 				case PixelFormat.RGBX8888, PixelFormat.RGBA8888:
167 					pixelDepth = 32;
168 					alphaChannelBits = 8;
169 					if (isRLE) imageType = ImageType.RLETrueColor;
170 					else imageType = ImageType.UncompressedTrueColor;
171 					break;
172 				default: throw new ImageFormatException("Palette format not supported!");
173 			}
174 		}
175 		public string toString(){
176 			import std.conv : to;
177 			return 
178 			"idLength:" ~ to!string(idLength) ~ "\n" ~
179 			"colorMapType:" ~ to!string(colorMapType) ~ "\n" ~
180 			"imageType:" ~ to!string(imageType) ~ "\n" ~
181 			"colorMapOffset:" ~ to!string(colorMapOffset) ~ "\n" ~
182 			"colorMapLength:" ~ to!string(colorMapLength) ~ "\n" ~
183 			"colorMapDepth:" ~ to!string(colorMapDepth) ~ "\n" ~
184 			"xOrigin:" ~ to!string(xOrigin) ~ "\n" ~
185 			"yOrigin:" ~ to!string(yOrigin) ~ "\n" ~
186 			"width:" ~ to!string(width) ~ "\n" ~
187 			"height:" ~ to!string(height) ~ "\n" ~
188 			"pixelDepth:" ~ to!string(pixelDepth);
189 			
190 		}
191 	}
192 	/**
193 	 * Implements Truevision Graphics bitmap footer, which is used to indicate the locations of extra fields.
194 	 */
195 	struct Footer {
196 	align(1) :
197 		uint			extensionAreaOffset;				/// offset of the extensionArea, zero if doesn't exist
198 		uint			developerAreaOffset;				/// offset of the developerArea, zero if doesn't exist
199 		char[16]		signature = "TRUEVISION-XFILE";		/// if equals with "TRUEVISION-XFILE", it's the new format
200 		char			reserved = '.';						/// should be always a dot
201 		ubyte			terminator;							/// terminates the file, always null
202 		///Returns true if it's a valid TGA footer
203 		@property bool isValid(){
204 			return signature == "TRUEVISION-XFILE" && reserved == '.' && terminator == 0;
205 		}
206 	}
207 	/**
208 	 * Contains extended data, mostly metadata.
209 	 */
210 	struct ExtArea{
211 		/**
212 		 * Stores attributes about the alpha channel.
213 		 */
214 	    enum Attributes : ubyte{
215 	        NoAlpha                         =   0,
216 	        UndefinedAlphaCanBeIgnored      =   1,
217 	        UndefinedAlphaMustBePreserved   =   2,
218 	        UsefulAlpha                     =   4,
219 	        PreMultipliedAlpha              =   5
220 	    }
221 	align(1) :
222 	    ushort      size = cast(ushort)ExtArea.sizeof;	///size of this field (should be ExtArea.sizeof)
223 	    char[41]    authorName;				///Name of the author
224 	    char[324]   authorComments;			///Stores author comments
225 		/**
226 		 * Stores the datetime in the following format
227 		 * 0: Year
228 		 * 1: Month
229 		 * 2: Day
230 		 * 3: Hour
231 		 * 4: Minute
232 		 * 5: Second
233 		 */
234 	    ushort[6]   dateTimeStamp;			
235 	    char[41]    jobName;				///Name of the job
236 		/**
237 		 * Time of the job in the following format:
238 		 * 0: Hours
239 		 * 1: Minutes
240 		 * 2: Seconds
241 		 */
242 	    ushort[3]   jobTime;
243 	    char[41]    softwareID;				///Stores the name of the software
244 	    ushort      softwareVersNum;		///Stores the version of the software in a decimal system in the following format: 000.0.0
245 	    char        softwareVersChar;		///Stores the version of the software
246 	    ubyte[4]    keyColor;				///Key color, mostly used for greenscreening and similar thing
247 	    ushort      pixelWidth;				///Pixel width ratio
248 	    ushort      pixelHeight;			///Pixel height ratio
249 	    ushort      gammaNumerator;			///Gamma correction
250 	    ushort      gammaDenominator;		///Gamma correction
251 	    uint        colorCorrectionOffset;	///Color correction field. The library cannot process this information.
252 	    uint        postageStampOffset;		///Thumbnail image offset
253 	    uint        scanlineOffset;			///Fast access to scanline offsets. The library can create it and load it, but doesn't use it since it decompresses RLE images upon load
254 	    ubyte       attributes;				///Information on the alpha channel
255 	}
256 	/**
257 	 * Identifies the embedded data.
258 	 */
259 	struct DevAreaTag{
260 	    ushort          reserved;       /// number of tags in the beginning
261 	    /**
262 	     * Identifier tag of the developer area field.
263 	     * Supposedly the range of 32768 - 65535 is reserved by Truevision, however there are no information on whenever it was
264 	     * used by them or not.
265 	     */
266 	    ushort          tag;            
267 	    uint            offset;         /// offset into file
268 	    uint            fieldSize;      /// field size in bytes
269 	}
270 	/**
271 	 * Represents embedded data within the developer area
272 	 */
273 	struct DevArea{
274 	    ubyte[] data;
275 	    /**
276 	     * Returns the data as a certain type (preferrably struct) if available
277 	     */
278 	    T get(T)(){
279 	        if(T.sizeof == data.length){
280 	            return cast(T)(cast(void[])data);
281 	        }
282 	    }
283 	}
284 	protected Header		header;
285 	protected Footer		footer;
286 	protected char[]		imageID;
287 	protected ExtArea[]		extensionArea;
288 	protected DevAreaTag[]	developerAreaTags;
289 	protected DevArea[]		developerArea;
290 	protected size_t		pitch;
291 	public uint[]			scanlineTable;				///stores offset to scanlines
292     public ubyte[]			postageStampImage;			///byte 0 is width, byte 1 is height
293     public ushort[]			colorCorrectionTable;		///color correction table
294 	///Empty CTOR for loading
295 	protected this() @nogc @safe pure nothrow {
296 
297 	}
298 	/**
299 	 * Creates a TGA file without a footer.
300 	 * Params:
301 	 *   header = The header to be used for the image. Must match the values of _imageData and such.
302 	 *   _imageData = The imagedata to be used for the imagefile.
303 	 *   _palette = Palette data if any.
304 	 *   imageID = Image name.
305 	 */
306 	public this(Header header, IImageData _imageData, IPalette _palette = null, char[] imageID = null) @safe {
307 		this.header = header;
308 		this._imageData = _imageData;
309 		this._palette = _palette;
310 		this.imageID = imageID;
311 	}
312 	/**
313 	 * Creates a TGA file safely with automatically generated header.
314 	 * This constructor is the recommended one for general use.
315 	 * Params:
316 	 *   _imageData = The imagedata to be used for the imagefile. Automatically adjusts the header format to this.
317 	 *   _palette = Palette data if any.
318 	 *   imageID = Image name.
319 	 *   isRLE = If true, then RLE compression will be used.
320 	 */
321 	public this(IImageData _imageData, IPalette _palette = null, char[] imageID = null, bool isRLE = false) @safe {
322 		this._imageData = _imageData;
323 		this._palette = _palette;
324 		this.imageID = imageID;
325 		header.width = cast(ushort)_imageData.width;
326 		header.height = cast(ushort)_imageData.height;
327 		if(_palette) {
328 			header.colorMapLength = cast(ushort)this._palette.length;
329 			header.colorMapDepth = this._palette.bitDepth;
330 			header.colorMapType = Header.ColorMapType.ColorMapPresent;
331 		}
332 		header.setImageType(_imageData.pixelFormat, isRLE);
333 	}
334 	/**
335 	 * Creates a TGA file with a footer.
336 	 * Params:
337 	 *   header = The header to be used for the image. Must match the values of _imageData and such.
338 	 *   footer = The footer to be used for the image.
339 	 *   _imageData = The imagedata to be used for the imagefile.
340 	 *   _palette = Palette data if any.
341 	 *   imageID = Image name.
342 	 */
343 	public this(Header header, Footer footer, IImageData _imageData, IPalette _palette = null, char[] imageID = null) @safe {
344 		this.header = header;
345 		this.footer = footer;
346 		this._imageData = _imageData;
347 		this._palette = _palette;
348 		this.imageID = imageID;
349 	}
350 	/**
351 	 * Loads a Truevision TARGA file and creates a TGA object.
352 	 * FILE can be either std.stdio's file, my own implementation of a virtual file (see ziltoid1991/vfile), or any compatible solution.
353 	 */
354 	public static TGA load(FILE = std.stdio.File, bool loadDevArea = true, bool loadExtArea = true)(ref FILE file){
355 		import std.stdio;
356 		TGA result = new TGA();
357 		ubyte[] loadRLEImageData(const ref Header header) {
358 			size_t target = header.width * header.height;
359 			const size_t bytedepth = header.pixelDepth >= 8 ? (header.pixelDepth / 8) : 1;
360 			target >>= header.pixelDepth < 8 ? 1 : 0;
361 			target >>= header.pixelDepth < 4 ? 1 : 0;
362 			target >>= header.pixelDepth < 2 ? 1 : 0;
363 			ubyte[] result, dataBuffer;
364 			result.reserve(header.pixelDepth >= 8 ? target * bytedepth : target);
365 			switch(header.pixelDepth) {
366 				case 15, 16:
367 					dataBuffer.length = 3;
368 					break;
369 				case 24:
370 					dataBuffer.length = 4;
371 					break;
372 				case 32:
373 					dataBuffer.length = 5;
374 					break;
375 				default:		//all indexed type having less than 8bits of depth use the same method of RLE as 8bit ones
376 					dataBuffer.length = 2;
377 					break;
378 			}
379 			while(result.length < target * bytedepth){
380 				file.rawRead(dataBuffer);
381 				if(dataBuffer[0] & 0b1000_0000){//RLE block
382 					dataBuffer[0] &= 0b0111_1111;
383 					dataBuffer[0]++;
384 					while(dataBuffer[0]){
385 						result ~= dataBuffer[1..$];
386 						dataBuffer[0]--;
387 						//target--;
388 					}
389 					//result ~= rleBlock;
390 				}else{//literal block
391 					dataBuffer[0] &= 0b0111_1111;
392 					//dataBuffer[0]--;
393 					ubyte[] literalBlock;
394 					literalBlock.length = (dataBuffer[0] * bytedepth);
395 					if(literalBlock.length)file.rawRead(literalBlock);
396 					result ~= dataBuffer[1..$] ~ literalBlock;
397 				}
398 			}
399 			//std.stdio.writeln(result.length, ";", (header.width * header.height * bytedepth));
400 			assert(result.length == (header.width * header.height * bytedepth), "RLE length mismatch error!");
401 			return result;
402 		}
403 		ubyte[] readBuffer;
404 		readBuffer.length = Header.sizeof;
405 		file.rawRead(readBuffer);
406 		result.header = reinterpretGet!Header(readBuffer);
407 		result.imageID.length = result.header.idLength;
408 		if (result.imageID.length) file.rawRead(result.imageID);
409 		ubyte[] palette;
410 		palette.length = result.header.colorMapLength * (result.getPaletteBitdepth / 8);
411 		if (palette.length) {
412 			file.rawRead(palette);
413 			switch (result.getPalettePixelFormat) {
414 				case PixelFormat.Grayscale8Bit:
415 					result._palette = new Palette!ubyte(palette, PixelFormat.Grayscale8Bit, 8);
416 					break;
417 				case PixelFormat.RGBA5551:
418 					result._palette = new Palette!RGBA5551(reinterpretCast!RGBA5551(palette), PixelFormat.RGBA5551, 16);
419 					break;
420 				case PixelFormat.RGB888:
421 					result._palette = new Palette!RGB888(reinterpretCast!RGB888(palette), PixelFormat.RGB888, 24);
422 					break;
423 				case PixelFormat.ARGB8888:
424 					result._palette = new Palette!ARGB8888(reinterpretCast!ARGB8888(palette), PixelFormat.ARGB8888, 32);
425 					break;
426 				default: throw new ImageFileException("Unknown palette type!");
427 			}
428 		}
429 		ubyte[] image;
430 		if (result.header.imageType >= Header.ImageType.RLEMapped && result.header.imageType <= Header.ImageType.RLEGrayscale) {
431 			image = loadRLEImageData(result.header);
432 		} else {
433 			image.length = (result.header.width * result.header.height * result.header.pixelDepth) / 8;
434 			if(image.length) file.rawRead(image);
435 		}
436 		if (image.length) {
437 			switch (result.getPixelFormat) {
438 				case PixelFormat.Grayscale8Bit:
439 					result._imageData = new ImageData!ubyte(image, result.width, result.height, PixelFormat.Grayscale8Bit, 8);
440 					break;
441 				case PixelFormat.RGBA5551:
442 					result._imageData = new ImageData!RGBA5551(reinterpretCast!RGBA5551(image), result.width, result.height, 
443 							PixelFormat.RGBA5551, 16);
444 					break;
445 				case PixelFormat.RGB888:
446 					result._imageData = new ImageData!RGB888(reinterpretCast!RGB888(image), result.width, result.height, 
447 							PixelFormat.RGB888, 24);
448 					break;
449 				case PixelFormat.ARGB8888:
450 					result._imageData = new ImageData!ARGB8888(reinterpretCast!ARGB8888(image), result.width, result.height, 
451 							PixelFormat.ARGB8888, 32);
452 					break;
453 				case PixelFormat.Indexed8Bit:
454 					result._imageData = new IndexedImageData!ubyte(image, result._palette, result.width, result.height);
455 					break;
456 				case PixelFormat.Indexed16Bit:
457 					result._imageData = new IndexedImageData!ushort(reinterpretCast!ushort(image), result._palette, result.width, 
458 							result.height);
459 					break;
460 				default: throw new ImageFileException("Unknown Image type!");
461 			}
462 		}
463 		static if(loadExtArea || loadDevArea){
464 			readBuffer.length = Footer.sizeof;
465 			file.seek(cast(int)Footer.sizeof * -1, SEEK_END);
466 			file.rawRead(readBuffer);
467 			result.footer = reinterpretGet!Footer(readBuffer);
468 			//TGA result = new TGA(result.header, footerLoad, image, palette, imageIDLoad);
469 			if(result.footer.isValid){
470 				static if(loadDevArea){
471 					if(result.footer.developerAreaOffset){
472 						file.seek(result.footer.developerAreaOffset);
473 						result.developerAreaTags.length = 1;
474 						file.rawRead(result.developerAreaTags);
475 						result.developerAreaTags.length = result.developerAreaTags[0].reserved;
476 						file.rawRead(result.developerAreaTags[1..$]);
477 						result.developerArea.reserve = result.developerAreaTags[0].reserved;
478 						ubyte[] dataBuffer;
479 						foreach(tag; result.developerAreaTags){
480 							file.seek(tag.offset);
481 							dataBuffer.length = tag.fieldSize;
482 							file.rawRead(dataBuffer);
483 							result.developerArea ~= DevArea(dataBuffer.dup);
484 						}
485 					}
486 				}
487 				static if(loadExtArea){
488 					if(result.footer.extensionAreaOffset){
489 						file.seek(result.footer.extensionAreaOffset);
490 						result.extensionArea.length = 1;
491 						file.rawRead(result.extensionArea);
492 						if(result.extensionArea[0].postageStampOffset){
493 							file.seek(result.extensionArea[0].postageStampOffset);
494 							result.postageStampImage.length = 2;
495 							file.rawRead(result.postageStampImage);
496 							result.postageStampImage.length = 2 + result.postageStampImage[0] * result.postageStampImage[1];
497 							file.rawRead(result.postageStampImage[2..$]);
498 						}
499 						if(result.extensionArea[0].colorCorrectionOffset){
500 							result.colorCorrectionTable.length = 1024;
501 							file.seek(result.extensionArea[0].colorCorrectionOffset);
502 							file.rawRead(result.colorCorrectionTable);
503 						}
504 						if(result.extensionArea[0].scanlineOffset){
505 							result.scanlineTable.length = result.header.height;
506 							file.seek(result.extensionArea[0].scanlineOffset);
507 							file.rawRead(result.scanlineTable);
508 						}
509 					}
510 				}
511 			}
512 			return result;
513 		}else{
514 			//TGA result = new TGA(result.header, image, palette, imageIDLoad);
515 			return result;
516 		}
517 			
518 	}
519 	/**
520 	 * Saves the current TGA object into a Truevision TARGA file.
521 	 * If ignoreScanlineBounds is true, then the compression methods will ignore the scanline bounds, this disables per-line accessing, but enhances compression
522 	 * rates by a margin in exchange. If false, then it'll generate a scanline table.
523 	 */
524 	public void save(FILE = std.stdio.File, bool saveDevArea = false, bool saveExtArea = false, 
525 			bool ignoreScanlineBounds = false)(ref FILE file){
526 		import std.stdio;
527 		ubyte[] imageBuffer;
528 			if(_imageData) imageBuffer = _imageData.raw;
529 		void compressRLE(){		//Help!!! I haven't used templates for this and I can't debug!!!
530 			static if(!ignoreScanlineBounds){
531 				const uint maxScanlineLength = header.width;
532 				scanlineTable.length = 0;
533 				scanlineTable.reserve(height);
534 			}
535 			version(unittest) uint pixelCount;
536 			ubyte[] writeBuff;
537 			static if(!ignoreScanlineBounds)
538 				uint currScanlineLength;
539 			switch(header.pixelDepth){
540 				case 16:
541 					ushort* src = cast(ushort*)(cast(void*)imageBuffer.ptr);
542 					const ushort* dest = src + (imageBuffer.length / 2);
543 					writeBuff.length = 257;
544 					ushort* writeBuff0 = cast(ushort*)(cast(void*)writeBuff.ptr + 1);
545 					while(src < dest){
546 						ushort* currBlockBegin = src, currBlockEnd = src;
547 						if(currBlockBegin[0] == currBlockBegin[1]){	//RLE block
548 							ubyte blockLength;
549 							//while(src < dest && currBlockEnd[0] == currBlockEnd[1]){
550 							do{
551 								src++;
552 								currBlockEnd++;
553 								blockLength++;
554 								static if(!ignoreScanlineBounds){
555 									currScanlineLength++;
556 									if(currScanlineLength == maxScanlineLength){
557 										currScanlineLength = 0;
558 										scanlineTable ~= cast(uint)file.tell;
559 										break;
560 									}
561 								}
562 								if(blockLength == 128)
563 									break;
564 							}while(src < dest && currBlockBegin[0] == currBlockEnd[0]);
565 							version(unittest){
566 								import std.conv : to;
567 								pixelCount += blockLength;
568 								assert(pixelCount <= header.width * header.height, "Required size: " ~ to!string(header.width * header.height) 
569 										~ " Current size:" ~ to!string(pixelCount));
570 							}
571 							blockLength--;
572 							blockLength |= 0b1000_0000;
573 							writeBuff[0] = blockLength;
574 							writeBuff0[0] = currBlockBegin[0];
575 							file.rawWrite(writeBuff[0..3]);
576 						}else{		//literal block
577 							ubyte blockLength;
578 							
579 							//while(src < dest && currBlockEnd[0] != currBlockEnd[1]){
580 							do{
581 								writeBuff0[blockLength] = currBlockEnd[0];
582 								src++;
583 								currBlockEnd++;
584 								blockLength++;
585 								static if(!ignoreScanlineBounds){
586 									currScanlineLength++;
587 									if(currScanlineLength == maxScanlineLength){
588 										currScanlineLength = 0;
589 										break;
590 									}
591 								}
592 								if(blockLength == 128)
593 									break;
594 								//blockLength++;
595 							}while(src < dest && currBlockBegin[0] == currBlockEnd[0]);
596 							//writeBuff[1] = currBlockEnd[0];
597 							version(unittest){
598 								import std.conv : to;
599 								pixelCount += blockLength;
600 								//std.stdio.writeln(pixelCount);
601 								assert(pixelCount <= header.width * header.height, "Required size: " ~ to!string(header.width * header.height) 
602 										~ " Current size:" ~ to!string(pixelCount));
603 							}
604 							blockLength--;
605 							writeBuff[0] = blockLength;
606 							file.rawWrite(writeBuff[0..((blockLength * 2) + 3)]);
607 						}
608 					}
609 					break;
610 				case 24:
611 					RGB888* src = cast(RGB888*)(cast(void*)imageBuffer.ptr);
612 					const RGB888* dest = src + (imageBuffer.length / 3);
613 					writeBuff.length = 1;
614 					RGB888[] writeBuff0;
615 					writeBuff0.length = 128;
616 					while(src < dest){
617 						RGB888* currBlockBegin = src, currBlockEnd = src;
618 						if(currBlockBegin[0] == currBlockBegin[1]){	//RLE block
619 							ubyte blockLength;
620 							//while(src < dest && currBlockEnd[0] == currBlockEnd[1]){
621 							do{
622 								src++;
623 								currBlockEnd++;
624 								blockLength++;
625 								static if(!ignoreScanlineBounds){
626 									currScanlineLength++;
627 									if(currScanlineLength == maxScanlineLength){
628 										currScanlineLength = 0;
629 										scanlineTable ~= cast(uint)file.tell;
630 										break;
631 									}
632 								}
633 								if(blockLength == 128)
634 									break;
635 							}while(src < dest && currBlockBegin[0] == currBlockEnd[0]);
636 							version(unittest){
637 								import std.conv : to;
638 								pixelCount += blockLength;
639 								assert(pixelCount <= header.width * header.height, "Required size: " ~ to!string(header.width * header.height) 
640 										~ " Current size:" ~ to!string(pixelCount));
641 							}
642 							blockLength--;
643 							blockLength |= 0b1000_0000;
644 							writeBuff[0] = blockLength;
645 							writeBuff0[0] = currBlockBegin[0];
646 							file.rawWrite(writeBuff[0..1]);
647 							file.rawWrite(writeBuff0[0..1]);
648 						}else{		//literal block
649 							ubyte blockLength;
650 							
651 							//while(src < dest && currBlockEnd[0] != currBlockEnd[1]){
652 							do{
653 								writeBuff0[blockLength] = currBlockEnd[0];
654 								src++;
655 								currBlockEnd++;
656 								blockLength++;
657 								static if(!ignoreScanlineBounds){
658 									currScanlineLength++;
659 									if(currScanlineLength == maxScanlineLength){
660 										currScanlineLength = 0;
661 										break;
662 									}
663 								}
664 								if(blockLength == 128)
665 									break;
666 								//blockLength++;
667 							}while(src < dest && currBlockBegin[0] == currBlockEnd[0]);
668 							//writeBuff[1] = currBlockEnd[0];
669 							version(unittest){
670 								import std.conv : to;
671 								pixelCount += blockLength;
672 								assert(pixelCount <= header.width * header.height, "Required size: " ~ to!string(header.width * header.height) 
673 										~ " Current size:" ~ to!string(pixelCount));
674 							}
675 							blockLength--;
676 							writeBuff[0] = blockLength;
677 							file.rawWrite(writeBuff[0..1]);
678 							file.rawWrite(writeBuff0[0..(blockLength + 1)]);
679 						}
680 					}
681 					break;
682 				case 32:
683 					uint* src = cast(uint*)(cast(void*)imageBuffer.ptr);
684 					const uint* dest = src + (imageBuffer.length / 4);
685 					writeBuff.length = 1;
686 					uint[] writeBuff0;
687 					writeBuff0.length = 128;
688 					while(src < dest){
689 						uint* currBlockBegin = src, currBlockEnd = src;
690 						if(currBlockBegin[0] == currBlockBegin[1]){	//RLE block
691 							ubyte blockLength;
692 							//while(src < dest && currBlockEnd[0] == currBlockEnd[1]){
693 							do{
694 								src++;
695 								currBlockEnd++;
696 								blockLength++;
697 								static if(!ignoreScanlineBounds){
698 									currScanlineLength++;
699 									if(currScanlineLength == maxScanlineLength){
700 										currScanlineLength = 0;
701 										scanlineTable ~= cast(uint)file.tell;
702 										break;
703 									}
704 								}
705 								if(blockLength == 128)
706 									break;
707 							}while(src < dest && currBlockBegin[0] == currBlockEnd[0]);
708 							version(unittest){
709 								import std.conv : to;
710 								pixelCount += blockLength;
711 								assert(pixelCount <= header.width * header.height, "Required size: " ~ to!string(header.width * header.height) 
712 										~ " Current size:" ~ to!string(pixelCount));
713 							}
714 							blockLength--;
715 							blockLength |= 0b1000_0000;
716 							writeBuff[0] = blockLength;
717 							writeBuff0[0] = currBlockBegin[0];
718 							file.rawWrite(writeBuff[0..1]);
719 							file.rawWrite(writeBuff0[0..1]);
720 						}else{		//literal block
721 							ubyte blockLength;
722 							
723 							//while(src < dest && currBlockEnd[0] != currBlockEnd[1]){
724 							do{
725 								writeBuff0[blockLength] = currBlockEnd[0];
726 								src++;
727 								currBlockEnd++;
728 								blockLength++;
729 								static if(!ignoreScanlineBounds){
730 									currScanlineLength++;
731 									if(currScanlineLength == maxScanlineLength){
732 										currScanlineLength = 0;
733 										break;
734 									}
735 								}
736 								if(blockLength == 128)
737 									break;
738 								//blockLength++;
739 							}while(src < dest && currBlockBegin[0] == currBlockEnd[0]);
740 							//writeBuff[1] = currBlockEnd[0];
741 							version(unittest){
742 								import std.conv : to;
743 								pixelCount += blockLength;
744 								assert(pixelCount <= header.width * header.height, "Required size: " ~ to!string(header.width * header.height) 
745 										~ " Current size:" ~ to!string(pixelCount));
746 							}
747 							blockLength--;
748 							writeBuff[0] = blockLength;
749 							file.rawWrite(writeBuff[0..1]);
750 							file.rawWrite(writeBuff0[0..(blockLength + 1)]);
751 						}
752 					}
753 					break;
754 				default:
755 					ubyte* src = imageBuffer.ptr;
756 					const ubyte* dest = src + imageBuffer.length;
757 					writeBuff.length = 129;
758 					while(src < dest){
759 						ubyte* currBlockBegin = src, currBlockEnd = src;
760 						if(currBlockBegin[0] == currBlockBegin[1]){	//RLE block
761 							ubyte blockLength;
762 							//while(src < dest && currBlockEnd[0] == currBlockEnd[1]){
763 							do{
764 								src++;
765 								currBlockEnd++;
766 								blockLength++;
767 								static if(!ignoreScanlineBounds){
768 									currScanlineLength++;
769 									if(currScanlineLength == maxScanlineLength){
770 										currScanlineLength = 0;
771 										scanlineTable ~= cast(uint)file.tell;
772 										break;
773 									}
774 								}
775 								if(blockLength == 128)
776 									break;
777 							}while(src < dest && currBlockBegin[0] == currBlockEnd[0]);
778 							version(unittest){
779 								import std.conv : to;
780 								pixelCount += blockLength;
781 								assert(pixelCount <= imageBuffer.length, "Required size: " ~ to!string(imageBuffer.length) 
782 										~ " Current size:" ~ to!string(pixelCount));
783 							}
784 							blockLength--;
785 							blockLength |= 0b1000_0000;
786 							writeBuff[0] = blockLength;
787 							writeBuff[1] = currBlockBegin[0];
788 							file.rawWrite(writeBuff[0..2]);
789 						}else{		//literal block
790 							ubyte blockLength;
791 							
792 							//while(src < dest && currBlockEnd[0] != currBlockEnd[1]){
793 							do{
794 								writeBuff[1 + blockLength] = currBlockEnd[0];
795 								src++;
796 								currBlockEnd++;
797 								blockLength++;
798 								static if(!ignoreScanlineBounds){
799 									currScanlineLength++;
800 									if(currScanlineLength == maxScanlineLength){
801 										currScanlineLength = 0;
802 										break;
803 									}
804 								}
805 								if(blockLength == 128)
806 									break;
807 								//blockLength++;
808 							}while(src < dest && currBlockEnd[0] != currBlockEnd[1]);
809 							//writeBuff[1] = currBlockEnd[0];
810 							version(unittest){
811 								import std.conv : to;
812 								pixelCount += blockLength;
813 								assert(pixelCount <= imageBuffer.length, "Required size: " ~ to!string(imageBuffer.length) 
814 										~ " Current size:" ~ to!string(pixelCount));
815 							}
816 							blockLength--;
817 							writeBuff[0] = blockLength;
818 							file.rawWrite(writeBuff[0..blockLength + 2]);
819 						}
820 					}
821 					break;
822 			}
823 			version(unittest){
824 				import std.conv : to;
825 				assert(pixelCount == header.width * header.height, "Required size: " ~ to!string(header.width * 
826 						header.height) ~ " Current size:" ~ to!string(pixelCount));
827 			}
828 		}
829 		//write most of the data into the file
830 		file.rawWrite([header]);
831 		file.rawWrite(imageID);
832 		if (_palette) file.rawWrite(_palette.raw);
833 		if(header.imageType >= Header.ImageType.RLEMapped && header.imageType <= Header.ImageType.RLEGrayscale){
834 			compressRLE();
835 		}else{
836 			file.rawWrite(_imageData.raw);
837 		}
838 		static if(saveDevArea){
839 			if(developerAreaTags.length){
840 				//make all tags valid
841 				uint offset = cast(uint)file.tell;
842 				footer.developerAreaOffset = offset;
843 				offset += cast(uint)(developerAreaTags.length * DevAreaTag.sizeof);
844 				developerAreaTags[0].reserved = cast(ushort)developerAreaTags.length;
845 				for(int i; i < developerAreaTags.length; i++){
846 					developerAreaTags[i].offset = offset;
847 					developerAreaTags[i].fieldSize = developerArea[i].data.length;
848 					offset += developerArea[i].data.length;
849 				}
850 				file.rawWrite(developerAreaTags);
851 				foreach(d; developerArea){
852 					file.rawWrite(d.data);
853 				}
854 			}
855 		}
856 		static if(saveExtArea){
857 			if(extensionArea.length){
858 				uint offset = cast(uint)file.tell;
859 				footer.extensionAreaOffset = offset;
860 				offset += cast(uint)ExtArea.sizeof;
861 				if(colorCorrectionTable.length){
862 					extensionArea[0].colorCorrectionOffset = offset;
863 					offset += cast(uint)(colorCorrectionTable.length * ushort.sizeof);
864 				}else{
865 					extensionArea[0].colorCorrectionOffset = 0;
866 				}
867 				if(postageStampImage.length){
868 					extensionArea[0].postageStampOffset = offset;
869 					offset += cast(uint)(postageStampImage.length * ubyte.sizeof);
870 				}else{
871 					extensionArea[0].postageStampOffset = 0;
872 				}
873 				if(scanlineTable.length){
874 					extensionArea[0].scanlineOffset = offset;
875 					//offset += cast(uint)(scanlineTable.length * uint.sizeof);
876 				}else{
877 					extensionArea[0].scanlineOffset = 0;
878 				}
879 				file.rawWrite(extensionArea);
880 				assert(file.tell == footer.extensionAreaOffset);
881 				if (colorCorrectionTable.length){ 
882 					file.rawWrite(colorCorrectionTable);
883 					assert(file.tell == extensionArea[0].colorCorrectionOffset);
884 				}
885 				if (postageStampImage.length){ 
886 					file.rawWrite(postageStampImage);
887 					assert(file.tell == extensionArea[0].postageStampImage);
888 				}
889 				if (scanlineTable.length){ 
890 					file.rawWrite(scanlineTable);
891 					assert(file.tell == extensionArea[0].scanlineTable);
892 				}
893 			}
894 		}
895 		static if(saveExtArea || saveDevArea){
896 			file.rawWrite([footer]);
897 		}
898 	}
899 	override uint width() @nogc @safe @property const pure{
900 		return header.width;
901 	}
902 	override uint height() @nogc @safe @property const pure{
903 		return header.height;
904 	}
905 	override bool isIndexed() @nogc @safe @property const pure{
906 		return header.colorMapType != Header.ColorMapType.NoColorMapPresent;
907 	}
908 	override ubyte getBitdepth() @nogc @safe @property const pure{
909 		return header.pixelDepth;
910 	}
911 	override ubyte getPaletteBitdepth() @nogc @safe @property const pure {
912 		return header.colorMapDepth;
913 	}
914 	override uint getPixelFormat() @nogc @safe @property const pure {
915 		if (header.imageType == Header.ImageType.UncompressedMapped || header.imageType == Header.ImageType.RLEMapped || 
916 				header.imageType == Header.ImageType.RLE4BitMapped || header.imageType == Header.ImageType.RLE1BitMapped) {
917 			switch (header.pixelDepth) {
918 				case 1: return PixelFormat.Indexed1Bit;
919 				case 2: return PixelFormat.Indexed2Bit;
920 				case 4: return PixelFormat.Indexed4Bit;
921 				case 8: return PixelFormat.Indexed8Bit;
922 				case 16: return PixelFormat.Indexed16Bit;
923 				default: return PixelFormat.Undefined;
924 			}
925 		} else if (header.imageType == Header.ImageType.RLEGrayscale || 
926 				header.imageType == Header.ImageType.UncompressedGrayscale) {
927 			if (header.pixelDepth == 8) return PixelFormat.Grayscale8Bit;
928 			else return PixelFormat.Undefined;
929 		} else {		//Must be truecolor
930 			switch(header.pixelDepth){
931 				case 15, 16:
932 					return PixelFormat.RGBA5551;
933 				case 24:
934 					return PixelFormat.RGB888;
935 				case 32:
936 					return PixelFormat.ARGB8888;
937 				default:
938 					return PixelFormat.Undefined;
939 			}
940 		}
941 	}
942 	override uint getPalettePixelFormat() @nogc @safe @property const pure{
943 		if(Header.ColorMapType.NoColorMapPresent){
944 			return PixelFormat.Undefined;
945 		}else{
946 			switch(header.colorMapDepth){
947 				case 8: return PixelFormat.Grayscale8Bit;
948 				case 15, 16: return PixelFormat.RGBA5551;
949 				case 24: return PixelFormat.RGB888;
950 				case 32: return PixelFormat.ARGB8888;
951 				default: return PixelFormat.Undefined;
952 			}
953 		}
954 	}
955 	public string getID() @safe{
956 		return to!string(imageID);
957 	}
958 	public string getAuthor() @safe{
959 		if(extensionArea.length)
960 			return extensionArea[0].authorName;
961 		return null;
962 	}
963 	public string getComment() @safe{
964 		if(extensionArea.length)
965 			return extensionArea[0].authorComments;
966 		return null;
967 	}
968 	public string getJobName() @safe{
969 		if(extensionArea.length)
970 			return extensionArea[0].jobName;
971 		return null;
972 	}
973 	public string getSoftwareInfo() @safe{
974 		if(extensionArea.length)
975 			return extensionArea[0].softwareID;
976 		return null;
977 	}
978 	public string getSoftwareVersion() @safe{
979 		import std.conv : to;
980 		if(extensionArea.length)
981 			return to!string(extensionArea[0].softwareVersNum / 100) ~ "." ~ to!string((extensionArea[0].softwareVersNum % 100) 
982 					/ 10) ~ "." ~ to!string(extensionArea[0].softwareVersNum % 10) ~ extensionArea[0].softwareVersChar;
983 		return null;
984 	}
985 	public string getDescription() @safe pure {
986 		return null;
987 	}
988 	public string getCopyright() @safe pure {
989 		return null;
990 	}
991 	public string getSource() @safe pure {
992 		return null;
993 	}
994 	public string getCreationTimeStr() @safe pure {
995 		return null;
996 	}
997 
998 
999 	public string setID(string val) @safe{
1000 		if(val.length > 255)
1001 			throw new Exception("ID is too long");
1002 		imageID = val.dup;
1003 		header.idLength = cast(ubyte)val.length;
1004 		return val;
1005 	}
1006 	public string setAuthor(string val) @safe{
1007 		if(val.length > 41)
1008 			throw new Exception("Author name is too long");
1009 		if(extensionArea.length){
1010 			stringCpy(extensionArea[0].authorName, val);
1011 			return val;
1012 		}
1013 		return null;
1014 	}
1015 	public string setComment(string val) @safe{
1016 		if(val.length > 324)
1017 			throw new Exception("Comment is too long");
1018 		if(extensionArea.length){
1019 			stringCpy(extensionArea[0].authorComments, val);
1020 			return val;
1021 		}
1022 		return null;
1023 	}
1024 	public string setJobName(string val) @safe{
1025 		if(val.length > 41)
1026 			throw new Exception("Jobname is too long");
1027 		if(extensionArea.length){
1028 			stringCpy(extensionArea[0].jobName, val);
1029 			return val;
1030 		}
1031 		return null;
1032 	}
1033 	public string setSoftwareInfo(string val) @safe{
1034 		if(val.length > 41)
1035 			throw new Exception("SoftwareID is too long");
1036 		if(extensionArea.length){
1037 			stringCpy(extensionArea[0].softwareID, val);
1038 			return val;
1039 		}
1040 		return null;
1041 	}
1042 	///Format used: 0.0.0a
1043 	public string setSoftwareVersion(string val) @safe{
1044 		if(extensionArea.length){
1045 			//separate first part with dot, then parse the number
1046 			uint prelimiter;
1047 			for( ; prelimiter < val.length ; prelimiter++){
1048 				if(val[prelimiter] == '.')
1049 					break;
1050 			}
1051 			uint resultI = to!uint(val[0..prelimiter]);
1052 			resultI *= 10;
1053 			for( ; prelimiter < val.length ; prelimiter++){
1054 				if(val[prelimiter] == '.')
1055 					break;
1056 			}
1057 			resultI += to!uint([val[prelimiter-1]]);
1058 			resultI *= 10;
1059 			if(val.length > prelimiter+1)
1060 				resultI += to!uint([val[prelimiter+1]]);
1061 			extensionArea[0].softwareVersNum = cast(ushort)resultI;
1062 			if(val.length > prelimiter+2)
1063 				extensionArea[0].softwareVersChar = val[prelimiter+2];
1064 			return val;
1065 		}
1066 		return null;
1067 	}
1068 	public string setDescription(string val) @safe pure {
1069 		return null;
1070 	}
1071 	public string setSource(string val) @safe pure {
1072 		return null;
1073 	}
1074 	public string setCopyright(string val) @safe pure {
1075 		return null;
1076 	}
1077 	public string setCreationTime(string val) @safe pure {
1078 		return null;
1079 	}
1080 	/**
1081 	 * Adds extension area for the file.
1082 	 */
1083 	public void createExtensionArea(){
1084 		extensionArea.length = 1;
1085 	}
1086 	/**
1087 	 * Returns the developer area info for the field.
1088 	 */
1089 	public DevAreaTag getDevAreaInfo(size_t n){
1090 		return developerAreaTags[n];
1091 	}
1092 	/**
1093 	 * Returns the embedded field.
1094 	 */
1095 	public DevArea getEmbeddedData(size_t n){
1096 		return developerArea[n];
1097 	}
1098 	/**
1099 	 * Creates an embedded field within the TGA file.
1100 	 */
1101 	public void addEmbeddedData(ushort ID, ubyte[] data){
1102 		developerAreaTags ~= DevAreaTag(0, ID, 0, cast(uint)data.length);
1103 		developerArea ~= DevArea(data);
1104 	}
1105 	/**
1106 	 * Returns the image type.
1107 	 */
1108 	public ubyte getImageType() const @nogc @property @safe nothrow{
1109 		return header.imageType;
1110 	}
1111 	/**
1112 	 * Returns the header as a reference type.
1113 	 */
1114 	public ref Header getHeader() @nogc @safe nothrow{
1115 		return header;
1116 	}
1117 	/**
1118 	 * Flips the image on the vertical axis. Useful to set images to the correct top-left screen origin point.
1119 	 */
1120 	public override void flipVertical() @safe{
1121 		header.topOrigin = !header.topOrigin;
1122 		super.flipVertical;
1123 	}
1124 	/**
1125 	 * Flips the image on the vertical axis. Useful to set images to the correct top-left screen origin point.
1126 	 */
1127 	public override void flipHorizontal() @safe{
1128 		header.rightSideOrigin = !header.rightSideOrigin;
1129 		super.flipHorizontal;
1130 	}
1131 	///Returns true if the image originates from the top
1132 	public override bool topOrigin() @property @nogc @safe pure const {
1133 		return header.topOrigin;
1134 	}
1135 	///Returns true if the image originates from the right
1136 	public override bool rightSideOrigin() @property @nogc @safe pure const {
1137 		return header.rightSideOrigin;
1138 	}
1139 }
1140 
1141 unittest{
1142 	import std.conv : to;
1143 	import vfile;
1144 	assert(TGA.Header.sizeof == 18);
1145 	//void[] tempStream;
1146 	//test 8 bit RLE load for 8 bit greyscale and indexed
1147 	{
1148 		std.stdio.File greyscaleUncFile = std.stdio.File("test/tga/grey_8.tga");
1149 		std.stdio.writeln("Loading ", greyscaleUncFile.name);
1150 		TGA greyscaleUnc = TGA.load(greyscaleUncFile);
1151 		std.stdio.writeln("File `", greyscaleUncFile.name, "` successfully loaded");
1152 		std.stdio.File greyscaleRLEFile = std.stdio.File("test/tga/grey_8_rle.tga");
1153 		std.stdio.writeln("Loading ", greyscaleRLEFile.name);
1154 		TGA greyscaleRLE = TGA.load(greyscaleRLEFile);
1155 		std.stdio.writeln("File `", greyscaleRLEFile.name, "` successfully loaded");
1156 		compareImages(greyscaleUnc, greyscaleRLE);
1157 		//store the uncompressed one as a VFile in the memory using RLE, then restore it and check if it's working.
1158 		greyscaleUnc.getHeader.imageType = TGA.Header.ImageType.RLEGrayscale;
1159 		VFile virtualFile;// = VFile(tempStream);
1160 		//std.stdio.File virtualFile = std.stdio.File("test/tga/grey_8_rle_gen.tga", "wb");
1161 		greyscaleUnc.save!(VFile, false, false, true)(virtualFile);
1162 		std.stdio.writeln("Save to virtual file was successful");
1163 		std.stdio.writeln(virtualFile.size);
1164 		virtualFile.seek(0);
1165 		greyscaleRLE = TGA.load!VFile(virtualFile);
1166 		std.stdio.writeln("Load from virtual file was successful");
1167 		compareImages(greyscaleUnc, greyscaleRLE);
1168 	}
1169 	{
1170 		std.stdio.File mappedUncFile = std.stdio.File("test/tga/mapped_8.tga");
1171 		std.stdio.writeln("Loading ", mappedUncFile.name);
1172 		TGA mappedUnc = TGA.load(mappedUncFile);
1173 		std.stdio.writeln("File `", mappedUncFile.name, "` successfully loaded");
1174 		std.stdio.File mappedRLEFile = std.stdio.File("test/tga/mapped_8_rle.tga");
1175 		std.stdio.writeln("Loading ", mappedRLEFile.name);
1176 		TGA mappedRLE = TGA.load(mappedRLEFile);
1177 		std.stdio.writeln("File `", mappedRLEFile.name, "` successfully loaded");
1178 		compareImages(mappedUnc, mappedRLE);
1179 		//store the uncompressed one as a VFile in the memory using RLE, then restore it and check if it's working.
1180 		mappedUnc.getHeader.imageType = TGA.Header.ImageType.RLEMapped;
1181 		VFile virtualFile;// = VFile(tempStream);
1182 		//std.stdio.File virtualFile = std.stdio.File("test/tga/grey_8_rle_gen.tga", "wb");
1183 		mappedUnc.save!(VFile, false, false, true)(virtualFile);
1184 		std.stdio.writeln("Save to virtual file was successful");
1185 		std.stdio.writeln(virtualFile.size);
1186 		virtualFile.seek(0);
1187 		mappedRLE = TGA.load!VFile(virtualFile);
1188 		std.stdio.writeln("Load from virtual file was successful");
1189 		compareImages(mappedUnc, mappedRLE);
1190 		
1191 		{
1192 			Palette!RGBA5551 p = cast(Palette!RGBA5551)mappedUnc.palette;
1193 			assert(p.convTo(PixelFormat.ARGB8888).length == p.length, "Length mismatch!");
1194 			assert(p.convTo(PixelFormat.RGBA8888).length == p.length, "Length mismatch!");
1195 			//std.stdio.writeln(mappedUnc.getHeader);
1196 			foreach_reverse (c ; p) {
1197 			}
1198 			foreach (c ; p) {
1199 			}
1200 		}
1201 	}
1202 	{
1203 		std.stdio.File mappedUncFile = std.stdio.File("test/tga/concreteGUIE3.tga");
1204 		std.stdio.writeln("Loading ", mappedUncFile.name);
1205 		TGA mappedUnc = TGA.load(mappedUncFile);
1206 		std.stdio.writeln("File `", mappedUncFile.name, "` successfully loaded");
1207 		std.stdio.File mappedRLEFile = std.stdio.File("test/tga/concreteGUIE3_rle.tga");
1208 		std.stdio.writeln("Loading ", mappedRLEFile.name);
1209 		TGA mappedRLE = TGA.load(mappedRLEFile);
1210 		std.stdio.writeln("File `", mappedRLEFile.name, "` successfully loaded");
1211 		compareImages(mappedUnc, mappedRLE);
1212 		//store the uncompressed one as a VFile in the memory using RLE, then restore it and check if it's working.
1213 		mappedUnc.getHeader.imageType = TGA.Header.ImageType.RLEMapped;
1214 		VFile virtualFile;// = VFile(tempStream);
1215 		//std.stdio.File virtualFile = std.stdio.File("test/tga/grey_8_rle_gen.tga", "wb");
1216 		mappedUnc.save!(VFile, false, false, true)(virtualFile);
1217 		std.stdio.writeln("Save to virtual file was successful");
1218 		std.stdio.writeln(virtualFile.size);
1219 		virtualFile.seek(0);
1220 		mappedRLE = TGA.load!VFile(virtualFile);
1221 		std.stdio.writeln("Load from virtual file was successful");
1222 		compareImages(mappedUnc, mappedRLE);
1223 		
1224 		{
1225 			IPalette p = mappedUnc.palette;
1226 			assert(p.convTo(PixelFormat.ARGB8888).length == p.length, "Length mismatch!");
1227 			assert(p.convTo(PixelFormat.RGBA8888).length == p.length, "Length mismatch!");
1228 		}
1229 	}
1230 	{
1231 		std.stdio.File mappedUncFile = std.stdio.File("test/tga/sci-fi-tileset.tga");
1232 		std.stdio.writeln("Loading ", mappedUncFile.name);
1233 		TGA mappedUnc = TGA.load(mappedUncFile);
1234 		std.stdio.writeln("File `", mappedUncFile.name, "` successfully loaded");
1235 		{
1236 			IPalette p = mappedUnc.palette;
1237 			assert(p.convTo(PixelFormat.ARGB8888).length == p.length, "Length mismatch!");
1238 			assert(p.convTo(PixelFormat.RGBA8888).length == p.length, "Length mismatch!");
1239 		}
1240 	}
1241 	{
1242 		std.stdio.File greyscaleUncFile = std.stdio.File("test/tga/truecolor_16.tga");
1243 		std.stdio.writeln("Loading ", greyscaleUncFile.name);
1244 		TGA greyscaleUnc = TGA.load(greyscaleUncFile);
1245 		std.stdio.writeln("File `", greyscaleUncFile.name, "` successfully loaded");
1246 		std.stdio.File greyscaleRLEFile = std.stdio.File("test/tga/truecolor_16_rle.tga");
1247 		std.stdio.writeln("Loading ", greyscaleRLEFile.name);
1248 		TGA greyscaleRLE = TGA.load(greyscaleRLEFile);
1249 		std.stdio.writeln("File `", greyscaleRLEFile.name, "` successfully loaded");
1250 		compareImages(greyscaleUnc, greyscaleRLE);
1251 		//store the uncompressed one as a VFile in the memory using RLE, then restore it and check if it's working.
1252 		greyscaleUnc.getHeader.imageType = TGA.Header.ImageType.RLETrueColor;
1253 		VFile virtualFile;// = VFile(tempStream);
1254 		//std.stdio.File virtualFile = std.stdio.File("test/tga/grey_8_rle_gen.tga", "wb");
1255 		greyscaleUnc.save!(VFile, false, false, true)(virtualFile);
1256 		std.stdio.writeln("Save to virtual file was successful");
1257 		std.stdio.writeln(virtualFile.size);
1258 		virtualFile.seek(0);
1259 		greyscaleRLE = TGA.load!VFile(virtualFile);
1260 		std.stdio.writeln("Load from virtual file was successful");
1261 		compareImages(greyscaleUnc, greyscaleRLE);
1262 		//Test upconversion and handling of 16 bit images
1263 		ImageData!RGBA5551 imageData = cast(ImageData!RGBA5551)greyscaleUnc.imageData;
1264 		TGA upconvToRGB888 = new TGA(imageData.convTo(PixelFormat.RGB888), null, null, true);
1265 		
1266 		std.stdio.File output = std.stdio.File("test/tga/test.tga", "wb");
1267 		upconvToRGB888.save(output);
1268 	}
1269 	{
1270 		std.stdio.File greyscaleUncFile = std.stdio.File("test/tga/truecolor_24.tga");
1271 		std.stdio.writeln("Loading ", greyscaleUncFile.name);
1272 		TGA greyscaleUnc = TGA.load(greyscaleUncFile);
1273 		std.stdio.writeln("File `", greyscaleUncFile.name, "` successfully loaded");
1274 		std.stdio.File greyscaleRLEFile = std.stdio.File("test/tga/truecolor_24_rle.tga");
1275 		std.stdio.writeln("Loading ", greyscaleRLEFile.name);
1276 		TGA greyscaleRLE = TGA.load(greyscaleRLEFile);
1277 		std.stdio.writeln("File `", greyscaleRLEFile.name, "` successfully loaded");
1278 		compareImages(greyscaleUnc, greyscaleRLE);
1279 		//store the uncompressed one as a VFile in the memory using RLE, then restore it and check if it's working.
1280 		greyscaleUnc.getHeader.imageType = TGA.Header.ImageType.RLETrueColor;
1281 		VFile virtualFile;// = VFile(tempStream);
1282 		//std.stdio.File virtualFile = std.stdio.File("test/tga/grey_8_rle_gen.tga", "wb");
1283 		greyscaleUnc.save!(VFile, false, false, true)(virtualFile);
1284 		std.stdio.writeln("Save to virtual file was successful");
1285 		std.stdio.writeln(virtualFile.size);
1286 		virtualFile.seek(0);
1287 		greyscaleRLE = TGA.load!VFile(virtualFile);
1288 		std.stdio.writeln("Load from virtual file was successful");
1289 		compareImages(greyscaleUnc, greyscaleRLE);
1290 	}
1291 	{
1292 		std.stdio.File greyscaleUncFile = std.stdio.File("test/tga/truecolor_32.tga");
1293 		std.stdio.writeln("Loading ", greyscaleUncFile.name);
1294 		TGA greyscaleUnc = TGA.load(greyscaleUncFile);
1295 		std.stdio.writeln("File `", greyscaleUncFile.name, "` successfully loaded");
1296 		std.stdio.File greyscaleRLEFile = std.stdio.File("test/tga/truecolor_32_rle.tga");
1297 		std.stdio.writeln("Loading ", greyscaleRLEFile.name);
1298 		TGA greyscaleRLE = TGA.load(greyscaleRLEFile);
1299 		std.stdio.writeln("File `", greyscaleRLEFile.name, "` successfully loaded");
1300 		compareImages(greyscaleUnc, greyscaleRLE);
1301 		//store the uncompressed one as a VFile in the memory using RLE, then restore it and check if it's working.
1302 		greyscaleUnc.getHeader.imageType = TGA.Header.ImageType.RLETrueColor;
1303 		VFile virtualFile;// = VFile(tempStream);
1304 		//std.stdio.File virtualFile = std.stdio.File("test/tga/grey_8_rle_gen.tga", "wb");
1305 		greyscaleUnc.save!(VFile, false, false, true)(virtualFile);
1306 		std.stdio.writeln("Save to virtual file was successful");
1307 		std.stdio.writeln(virtualFile.size);
1308 		virtualFile.seek(0);
1309 		greyscaleRLE = TGA.load!VFile(virtualFile);
1310 		std.stdio.writeln("Load from virtual file was successful");
1311 		compareImages(greyscaleUnc, greyscaleRLE);
1312 	}
1313 }