1 /*
2  * dimage - png.d
3  * by Laszlo Szeremi
4  *
5  * Copyright under Boost Software License.
6  */
7 
8 module dimage.png;
9 
10 import dimage.base;
11 
12 import zlib = etc.c.zlib;
13 import std.digest.crc;
14 import std.bitmanip;
15 import std.algorithm.mutation : reverse;
16 static import std.stdio;
17 static import std.string;
18 import std.string : fromStringz;
19 import core.stdc.stdint;
20 import bitleveld.datatypes;
21 import dimage.util;
22 import std.conv : to;
23 /**
24  * Implements the Portable Network Graphics file format as a class.
25  *
26  * Supports APNG extenstions.
27  */
28 public class PNG : Image, MultiImage, CustomImageMetadata {
29 	///Chunk initializer IDs.
30 	static enum ChunkInitializers : char[4] {
31 		Header				=	"IHDR",		///Standard, for header, before image data
32 		Palette				=	"PLTE",		///Standard, for palette, after header and before image data
33 		Data				=	"IDAT",		///Standard, for image data
34 		End					=	"IEND",		///Standard, file end marker
35 		//Ancillary chunks:
36 		Background			=	"bKGD",		///Background color
37 		Chromaticies		=	"cHRM",		///Primary chromaticities and white point
38 		Gamma				=	"gAMA",		///Image gamma
39 		Histogram			=	"hIST",		///Image histogram
40 		PixDim				=	"pHYs",		///Physical pixel dimensions
41 		SignifBits			=	"sBIT",		///Significant bits
42 		TextData			=	"tEXt",		///Textual data
43 		Time				=	"tIME",		///Last modification date
44 		Transparency		=	"tRNS",		///Transparency
45 		CompTextData		=	"zTXt",		///Compressed textual data
46 		IntTextData			=	"iTXt",		///International textual data
47 		//Chunks for APNG:
48 		AnimationControl	=	"acTL",		///Animation control chunk
49 		FrameControl		=	"fcTL",		///Frame control chunk
50 		FrameData			=	"fDAT",		///Frame data
51 	}
52 	/**
53 	 * Defines standard PNG filter types.
54 	 */
55 	static enum FilterType : ubyte {
56 		None,
57 		Sub,
58 		Up,
59 		Average,
60 		Paeth,
61 	}
62 	/**
63 	 * Defines the flags in the flag field.
64 	 */
65 	static enum Flags : uint {
66 		HasTransparency		=	0x01,
67 		HasAPNGext			=	0x02,
68 	}
69 	//static enum HEADER_INIT = "IHDR";		///Initializes header in the file
70 	//static enum PALETTE_INIT = "PLTE";		///Initializes palette in the file
71 	//static enum DATA_INIT = "IDAT";			///Initializes image data in the file
72 	//static enum END_INIT = "IEND";			///Initializes the end of the file
73 	static enum ubyte[8] PNG_SIGNATURE = [0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A];	///Used for checking PNG files
74 	static enum ubyte[4] PNG_CLOSER = [0xAE, 0x42, 0x60, 0x82];		///Final checksum of IEND
75 	/// Used for static function load. Selects checksum checking policy.
76 	static enum ChecksumPolicy : ubyte{
77 		DisableAll,
78 		DisableAncillary,
79 		Enable
80 	}
81 	/**
82 	 * Stores ancillary data embedded into PNG files. Handling these are not vital for opening PNG files, but available for various purposes.
83 	 * Please be aware that certain readers might have issues with nonstandard chunks
84 	 */
85 	public class EmbeddedData{
86 		/**
87 		 * Describes where this data stream should go within the file.
88 		 * This describe the exact location, and configured when loading from PNG files. WithinIDAT will result in dumping all of them before the last theoretical IDAT chunk.
89 		 */
90 		public enum DataPosition{
91 			BeforePLTE,
92 			BeforeIDAT,
93 			WithinIDAT,
94 			AfterIDAT
95 		}
96 		DataPosition	pos;	/// Describes the exact location of 
97 		char[4]		identifier;	/// Identifies the data of this chunk
98 		ubyte[]		data;		/// Contains the data of this chunk
99 		/**
100 		 * Creates an instance of this class.
101 		 */
102 		public this(DataPosition pos, char[4] identifier, ubyte[] data){
103 			this.pos = pos;
104 			this.identifier = identifier;
105 			this.data = data;
106 		}
107 	}
108 	/**
109 	 * PNG Chunk identifier
110 	 */
111 	struct Chunk{
112 		uint		dataLength;	///Length of chunk
113 		char[4] 	identifier;	///Identifies the data of this chunk
114 		/**
115 		 * Converts the struct to little endian on systems that need them.
116 		 */
117 		public void bigEndianToNative() {
118 			version(LittleEndian)
119 				dataLength = swapEndian(dataLength);
120 		}
121 		/**
122 		 * Returns a copy of the struct that is in big endian.
123 		 */
124 		public Chunk nativeToBigEndian() {
125 			version(LittleEndian)
126 				return Chunk(swapEndian(dataLength), identifier);
127 			else
128 				return this;
129 		}
130 	}
131 	/**
132 	 * Represents the possible types for PNG
133 	 * While bitmasking could be used, not every combination are valid
134 	 */
135 	enum ColorType : ubyte {
136 		Greyscale			=	0,
137 		TrueColor			=	2,
138 		Indexed				=	3,
139 		GreyscaleWithAlpha	=	4,
140 		TrueColorWithAlpha	=	6,
141 	}
142 	/**
143 	 * Contains most data related to PNG files.
144 	 */
145 	align(1) struct Header{
146 	align(1):
147 		uint		width;			/// Width of image in pixels 
148     	uint		height;			/// Height of image in pixels 
149     	ubyte		bitDepth;		/// Bits per pixel or per sample
150     	ColorType	colorType;		/// Color interpretation indicator
151     	ubyte		compression;	/// Compression type indicator
152     	ubyte		filter;			/// Filter type indicator
153     	ubyte		interlace;		/// Type of interlacing scheme used
154 		/**
155 		 * Converts the struct to little endian on systems that need them.
156 		 */
157 		public void bigEndianToNative() @safe @nogc pure nothrow {
158 			version(LittleEndian){
159 				width = swapEndian(width);
160 				height = swapEndian(height);
161 			}
162 		}
163 		/**
164 		 * Returns a copy of the struct that is in big endian.
165 		 */
166 		public Header nativeToBigEndian() @safe pure nothrow {
167 			version(LittleEndian)
168 				return Header(swapEndian(width), swapEndian(height), bitDepth, colorType, compression, filter, interlace);
169 			else
170 				return this;
171 		}
172 		/**
173 		 * For debugging purposes.
174 		 */
175 		public string toString() const {
176 			import std.conv : to;
177 			return "width: " ~ to!string(width) ~ "\n" ~
178 				"height: " ~ to!string(height) ~ "\n" ~
179 				"bitDepth: " ~ to!string(bitDepth) ~ "\n" ~
180 				"colorType: " ~ to!string(colorType) ~ "\n" ~
181  				"compression: " ~ to!string(compression) ~ "\n" ~
182  				"filter: " ~ to!string(filter) ~ "\n" ~
183  				"interlace: " ~ to!string(interlace) ~ "\n"; 
184 		}
185 	}
186 	/**
187 	 * Contains textual metadata embedded into the file.
188 	 */
189 	struct Text {
190 		string		keyword;			///The keyword, which the textual metadata is associated with. (Max 79 characters)
191 		string		text;				///The text itself.
192 		string		lang;				///Language in case of international textual data.
193 		string		ikeyword;			///Translated keyword if any.
194 		ubyte		compression;		///Compression method flag. (should be zero)
195 		ubyte		isCompressed;		///1 if field is compressed, 0 otherwise.
196 	}
197 	/**
198 	 * Animation control chunk.
199 	 * If found in a PNG file, it means it has the APNG extensions.
200 	 */
201 	struct AnimationControl {
202 		uint		numFrames;			///Number of animation frames.
203 		uint		numPlays;			///Number of repeats (0 means infinite loop).
204 		/**
205 		 * Converts the struct to little endian on systems that need them.
206 		 */
207 		public void bigEndianToNative() @safe @nogc pure nothrow {
208 			version(LittleEndian){
209 				numFrames = swapEndian(numFrames);
210 				numPlays = swapEndian(numPlays);
211 			}
212 		}
213 		/**
214 		 * Converts the struct to big endian for storage.
215 		 */
216 		public void nativeToBigEndian() @safe @nogc pure nothrow {
217 			version(LittleEndian){
218 				numFrames = swapEndian(numFrames);
219 				numPlays = swapEndian(numPlays);
220 			}
221 		}
222 	}
223 	/**
224 	 * Frame disposal operator.
225 	 */
226 	enum FrameDisposalOperator : ubyte {
227 		None,				///no disposal is done on this frame before rendering the next; the contents of the output buffer are left as is.
228 		Background,			///the frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame.
229 		Previous,			///the frame's region of the output buffer is to be reverted to the previous contents before rendering the next frame.
230 	}
231 	/**
232 	 * Frame blend operator.
233 	 */
234 	enum FrameBlendOperator : ubyte {
235 		Source,				///all color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region
236 		Over,				///the frame should be composited onto the output buffer based on its alpha, using a simple OVER operation as described in the "Alpha Channel Processing" section of the PNG specification
237 	}
238 	/**
239 	 * Frame control chunk.
240 	 */
241 	struct FrameControl {
242 		uint		seqNum;
243 		uint		width;
244 		uint		height;
245 		uint		xOffset;
246 		uint		yOffset;
247 		ushort		delayNum;
248 		ushort		delayDen;
249 		FrameDisposalOperator	disposeOp;
250 		FrameBlendOperator		blendOp;
251 		/**
252 		 * Converts the struct to little endian on systems that need them.
253 		 */
254 		public void bigEndianToNative() @safe @nogc pure nothrow {
255 			version(LittleEndian){
256 				seqNum = swapEndian(seqNum);
257 				width = swapEndian(width);
258 				height = swapEndian(height);
259 				xOffset = swapEndian(xOffset);
260 				yOffset = swapEndian(yOffset);
261 				delayNum = swapEndian(delayNum);
262 				delayDen = swapEndian(delayDen);
263 			}
264 		}
265 		/**
266 		 * Converts the struct to big endian for storage.
267 		 */
268 		public void nativeToBigEndian() @safe @nogc pure nothrow {
269 			version(LittleEndian){
270 				seqNum = swapEndian(seqNum);
271 				width = swapEndian(width);
272 				height = swapEndian(height);
273 				xOffset = swapEndian(xOffset);
274 				yOffset = swapEndian(yOffset);
275 				delayNum = swapEndian(delayNum);
276 				delayDen = swapEndian(delayDen);
277 			}
278 		}
279 	}
280 	protected Header		header;
281 	protected IImageData	baseImage;				///Base image if APNG chunks present.
282 	protected IImageData[]	frames;					///Extra frames for the APNG extension
283 	public EmbeddedData[]	ancillaryChunks;		///Stores ancilliary chunks that are not essential for image processing
284 	public ubyte[]			filterBytes;			///Filterbytes for each scanline
285 	public ubyte[][]		frameFilterBytes;		///Filterbytes for each frame (might be jagged)
286 	public Text[]			textData;				///Textual metadata
287 	protected int			bkgIndex = -1;			///Background index
288 	protected uint			flags;					///Stores property flags
289 	protected RGB16_16_16BE	bkgColor;				///Background color
290 	protected RGB16_16_16BE	trns;					///Transparency
291 	protected size_t		pitch;
292 	/**
293 	 * Creates an empty PNG file in memory
294 	 */
295 	public this(IImageData imgDat, IPalette pal = null) @safe pure {
296 		//header = Header(width, height, bitDepth, colorType, compression, 0, 0);
297 		_imageData = imgDat;
298 		_palette = pal;
299 		header.bitDepth = imgDat.bitDepth;
300 		header.width = imgDat.width;
301 		header.height = imgDat.height;
302 		filterBytes.length = imgDat.height;
303 		pitch = (header.width * imgDat.bitDepth) / 8;
304 		switch (imgDat.pixelFormat) {
305 			case PixelFormat.Indexed1Bit: .. case PixelFormat.Indexed8Bit:
306 				header.colorType = ColorType.Indexed;
307 				header.bitDepth = imgDat.bitDepth;
308 				break;
309 			case PixelFormat.Grayscale1Bit: .. case PixelFormat.Grayscale8Bit: case PixelFormat.Grayscale16Bit:
310 				header.colorType = ColorType.Greyscale;
311 				header.bitDepth = imgDat.bitDepth;
312 				break;
313 			case PixelFormat.YA88 | PixelFormat.BigEndian:
314 				header.colorType = ColorType.GreyscaleWithAlpha;
315 				header.bitDepth = imgDat.bitDepth / 2;
316 				break;
317 			case PixelFormat.RGB888 | PixelFormat.BigEndian, PixelFormat.RGBX5551 | PixelFormat.BigEndian, 
318 					PixelFormat.RGB16_16_16 | PixelFormat.BigEndian:
319 				header.colorType = ColorType.TrueColor;
320 				header.bitDepth = imgDat.bitDepth / 3;
321 				break;
322 			case PixelFormat.RGBA8888 | PixelFormat.BigEndian, PixelFormat.RGBA16_16_16_16 | PixelFormat.BigEndian:
323 				header.colorType = ColorType.TrueColorWithAlpha;
324 				header.bitDepth = imgDat.bitDepth / 4;
325 				break;
326 			default: throw new ImageFormatException("Image format currently not supported!");
327 		}
328 		switch (header.bitDepth) {
329 			case 1:
330 				if (header.width & 7) pitch++;
331 				break;
332 			case 2:
333 				if (header.width & 3) pitch++;
334 				break;
335 			case 4:
336 				if (header.width & 1) pitch++;
337 				break;
338 			default:
339 				break;
340 		}		
341 	}
342 	protected this(){
343 
344 	}
345 	/**
346 	 * Loads a PNG file.
347 	 * Currently interlaced mode is unsupported.
348 	 */
349 	static PNG load(F = std.stdio.File, ChecksumPolicy chksmTest = ChecksumPolicy.DisableAncillary)(ref F file){
350 		class PNGImgDecoder {
351 			zlib.z_stream strm;
352 			ubyte[] output;
353 			ubyte[]	filterBytes;
354 			~this() {
355 				zlib.inflateEnd(&strm);
356 			}
357 		}
358 		//import std.zlib : UnCompress;
359 		RGB16_16_16BE beToNative(RGB16_16_16BE val) {
360 			version(LittleEndian) {
361 				val.r = swapEndian(val.r);
362 				val.g = swapEndian(val.g);
363 				val.b = swapEndian(val.b);
364 			}
365 			return val;
366 		}
367 		///Decompresses a text chunk.
368 		///`extstrm` is used in case of an error during decompression. It'll fold it, to avoid memory leakage issues.
369 		string decompressText(ubyte[] src, zlib.z_streamp extstrm) {
370 			import std.math : nextPow2;
371 			ubyte[] output, workpad;
372 			zlib.z_stream strm;
373 			strm.zalloc = null;
374 			strm.zfree = null;
375 			strm.opaque = null;
376 			int ret = zlib.inflateInit(&strm);
377 			strm.next_in = src.ptr;
378 			strm.avail_in = cast(uint)src.length;
379 			output.length = nextPow2(src.length);
380 			strm.next_out = output.ptr;
381 			strm.avail_out = cast(uint)output.length;
382 			while (strm.avail_in) {
383 				ret = zlib.inflate(&strm, zlib.Z_FULL_FLUSH);
384 				if(!(ret == zlib.Z_OK || ret == zlib.Z_STREAM_END)){
385 					/* version(unittest) std.stdio.writeln(ret); */
386 					zlib.inflateEnd(&strm);
387 					zlib.inflateEnd(extstrm);
388 					throw new ImageFileException("Text data decompression error");
389 				}
390 				if (!strm.avail_out) {//decompress more data
391 					strm.next_out = output.ptr;
392 					strm.avail_out = cast(uint)output.length;
393 					workpad ~= output;
394 				}
395 			}
396 			workpad ~= output;
397 			workpad.length = cast(size_t)strm.total_out;
398 			zlib.inflateEnd(&strm);
399 			return reinterpretCast!char(workpad).idup;
400 		}
401 		/+ubyte[] decompressAnimChunk(ubyte[] src, AnimChunk context) {
402 			
403 		}+/
404 		PNG result = new PNG();
405 		bool iend;
406 		EmbeddedData.DataPosition pos = EmbeddedData.DataPosition.BeforePLTE;
407 		version (unittest) uint scanlineCounter;
408 		//initialize decompressor
409 		int ret;
410     	//uint have;
411     	zlib.z_stream strm;
412 		strm.zalloc = null;
413 		strm.zfree = null;
414 		strm.opaque = null;
415 		ret = zlib.inflateInit(&strm);
416 		if(ret != zlib.Z_OK)
417 			throw new Exception("Decompressor initialization error");
418 
419 		ubyte[] readBuffer, imageBuffer, imageTemp, paletteTemp, paletteTemp0;
420 		readBuffer.length = 8;
421 		file.rawRead(readBuffer);
422 		for(int i ; i < 8 ; i++)
423 			if(readBuffer[i] != PNG_SIGNATURE[i])
424 				throw new ImageFileException("Invalid PNG file signature");
425 		do{
426 			ubyte[4] crc;
427 			file.rawRead(readBuffer);
428 			Chunk curChunk = (cast(Chunk[])(cast(void[])readBuffer))[0];
429 			curChunk.bigEndianToNative();
430 			readBuffer.length = curChunk.dataLength;
431 			if(curChunk.dataLength)
432 				file.rawRead(readBuffer);
433 			//std.stdio.writeln("pos:", file.tell);
434 			switch(curChunk.identifier){
435 				case ChunkInitializers.Header:
436 					result.header = reinterpretGet!Header(readBuffer);
437 					result.header.bigEndianToNative;
438 					/* version (unittest) std.stdio.writeln(result.header); */
439 					result.pitch = (result.header.width * result.getBitdepth) / 8;
440 					/* version (unittest) std.stdio.writeln("Pitch length: ", result.pitch); */
441 					imageBuffer.length = result.pitch + 1;
442 					/* version (unittest) std.stdio.writeln(result.getPixelFormat); */
443 					strm.next_out = imageBuffer.ptr;
444 					strm.avail_out = cast(uint)imageBuffer.length;
445 					//result.pitch = result.header.width;
446 					break;
447 				case ChunkInitializers.Palette:
448 					paletteTemp = readBuffer.dup;
449 					pos = EmbeddedData.DataPosition.BeforeIDAT;
450 					break;
451 				case ChunkInitializers.Transparency:
452 					
453 					switch (result.header.colorType){
454 						case ColorType.Indexed:
455 							if(readBuffer.length == paletteTemp.length) paletteTemp0 = readBuffer.dup;
456 							else if (readBuffer.length == RGB16_16_16BE.sizeof) goto case ColorType.TrueColor;
457 							break;
458 						case ColorType.TrueColor:
459 							result.flags = 1;
460 							result.trns = reinterpretGet!RGB16_16_16BE(readBuffer);
461 							result.trns = beToNative(result.trns);
462 							break;
463 						case ColorType.Greyscale:
464 							result.flags = 1;
465 							const ushort value = reinterpretGet!ushort(readBuffer);
466 							result.trns = RGB16_16_16BE(value, value, value);
467 							result.trns = beToNative(result.trns);
468 							break;
469 						default:
470 							break;
471 					}
472 					break;
473 				case ChunkInitializers.Data:
474 					//if(result.header.compression)
475 					//imageBuffer ~= cast(ubyte[])decompressor.uncompress(cast(void[])readBuffer);
476 					//else
477 					//	imageBuffer ~= readBuffer.dup;
478 					pos = EmbeddedData.DataPosition.WithinIDAT;
479 					strm.next_in = readBuffer.ptr;
480 					strm.avail_in = cast(uint)readBuffer.length;
481 					while (strm.avail_in) {
482 						ret = zlib.inflate(&strm, zlib.Z_FULL_FLUSH);
483 						if(!(ret == zlib.Z_OK || ret == zlib.Z_STREAM_END)){
484 							/* version(unittest) std.stdio.writeln(ret); */
485 							zlib.inflateEnd(&strm);
486 							throw new ImageFileException("Decompression error");
487 						}else if(imageBuffer.length == strm.total_out){
488 							pos = EmbeddedData.DataPosition.AfterIDAT;
489 						}
490 						if (!strm.avail_out) {//flush scanline into imagedata
491 							version (unittest) scanlineCounter++;
492 							version (unittest) assert (scanlineCounter <= result.header.height, "Scanline overflow!");
493 							imageTemp ~= imageBuffer[1..$];
494 							result.filterBytes ~= imageBuffer[0];
495 							strm.next_out = imageBuffer.ptr;
496 							strm.avail_out = cast(uint)imageBuffer.length;
497 						}
498 					}
499 					
500 					break;
501 				case ChunkInitializers.Background:
502 					switch (result.header.colorType) {
503 						case ColorType.Indexed:
504 							result.bkgIndex = readBuffer[0];
505 							break;
506 						case ColorType.Greyscale, ColorType.GreyscaleWithAlpha:
507 							result.bkgIndex = -2;
508 							const ushort value = reinterpretGet!ushort(readBuffer);
509 							result.bkgColor = RGB16_16_16BE(value, value, value);
510 							result.bkgColor = beToNative(result.bkgColor);
511 							break;
512 						case ColorType.TrueColor, ColorType.TrueColorWithAlpha:
513 							result.bkgIndex = -2;
514 							result.bkgColor = reinterpretGet!RGB16_16_16BE(readBuffer);
515 							result.bkgColor = beToNative(result.bkgColor);
516 							break;
517 						default:
518 							break;
519 					}
520 					break;
521 				case ChunkInitializers.TextData:
522 					ubyte[] arr = readBuffer.dup;
523 					string keyword = getFirstString(arr);
524 					Text t = Text(keyword, reinterpretCast!char(arr).idup, null, null, 0, 0);
525 					result.textData ~= t;
526 					break;
527 				case ChunkInitializers.CompTextData:
528 					ubyte[] arr = readBuffer.dup;
529 					string keyword = getFirstString(arr);
530 					Text t = Text(keyword, decompressText(arr[1..$], &strm), null, null, 0, 1);
531 					result.textData ~= t;
532 					break;
533 				case ChunkInitializers.IntTextData:
534 					ubyte[] arr = readBuffer.dup;
535 					string keyword = getFirstString(arr);
536 					const ubyte cmprflag = arr[0];
537 					arr = arr[2..$];
538 
539 					string lngTag;
540 					if (arr[0] != '\n') lngTag = getFirstString(arr);
541 					else arr = arr[1..$];
542 
543 					string intrntKeyword;
544 					if (arr[0] != '\n') intrntKeyword = getFirstString(arr);
545 					else arr = arr[1..$];
546 
547 					if (cmprflag) {
548 						result.textData ~= Text(keyword, decompressText(arr, &strm), lngTag, intrntKeyword, 0, 1);
549 					} else {
550 						result.textData ~= Text(keyword, reinterpretCast!char(arr).idup, lngTag, intrntKeyword, 0, 0);
551 					}
552 					break;
553 				case ChunkInitializers.End:
554 					version (unittest) assert(result.header.height == scanlineCounter, "Scanline count mismatch");
555 					if(imageTemp.length + result.filterBytes.length > strm.total_out){
556 						zlib.inflateEnd(&strm);
557 						//version(unittest) std.stdio.writeln()
558 						throw new ImageFileCompressionException("Decompression error! Image ended at: " ~ to!string(strm.total_out) ~ 
559 						"; Required length: " ~ to!string(imageTemp.length));
560 					}
561 					imageTemp.reserve(result.height * result.pitch);
562 					result.filterBytes.reserve(result.height);
563 					iend = true;
564 					break;
565 				default:
566 					//Process any unknown chunk as embedded data
567 					//EmbeddedData chnk = new EmbeddedData(pos, curChunk.identifier, readBuffer.dup);
568 					result.addAncilliaryChunk(pos, curChunk.identifier, readBuffer.dup);
569 					/* version (unittest) {
570 						std.stdio.writeln ("Acilliary chunk found!");
571 						std.stdio.writeln ("ID: " , curChunk.identifier, " size: ", readBuffer.length, " pos: ", pos);
572 					} */
573 					break;
574 				
575 			}
576 			if (paletteTemp.length) {
577 				if(paletteTemp0.length) result._palette = new PaletteWithSepA!RGB888BE(reinterpretCast!RGB888BE(paletteTemp), 
578 						paletteTemp0, PixelFormat.RGB888 | PixelFormat.ValidAlpha | PixelFormat.BigEndian, 24);
579 				else result._palette = new Palette!RGB888BE(reinterpretCast!RGB888BE(paletteTemp), PixelFormat.RGB888 | 
580 						PixelFormat.BigEndian, 24);
581 			}
582 			//calculate crc
583 			//if(curChunk.dataLength){
584 			crc = crc32Of((cast(ubyte[])curChunk.identifier) ~ readBuffer);
585 			readBuffer.length = 4;
586 			file.rawRead(readBuffer);
587 			readBuffer.reverse;
588 			static if(chksmTest == ChecksumPolicy.Enable){
589 				if(readBuffer != crc)
590 					throw new ChecksumMismatchException("Checksum error");
591 			}else static if(chksmTest == ChecksumPolicy.DisableAncillary) {
592 				if(readBuffer != crc && (curChunk.identifier == ChunkInitializers.Header || curChunk.identifier == 
593 						ChunkInitializers.Palette || curChunk.identifier == ChunkInitializers.Data || curChunk.identifier == 
594 						ChunkInitializers.End))
595 					throw new ChecksumMismatchException("Checksum error");
596 			}
597 			//}
598 			readBuffer.length = 8;
599 		} while(!iend);
600 		zlib.inflateEnd(&strm);//should have no data remaining
601 		assert(imageTemp.length == result.pitch * result.header.height, "Image size mismatch. Expected size: " ~ 
602 				to!string(result.pitch * result.header.height) ~ "; Actual size: " ~ to!string(imageTemp.length) ~ 
603 				"; Pitch length: " ~ to!string(result.pitch) ~ "; N of scanlines: " ~ to!string(result.header.height));
604 		//reconstruct image from filtering
605 		ubyte[] scanline, prevScanline;
606 		int wordlength = result.getBitdepth > 8 ? result.getBitdepth / 8 : 1;
607 		for (uint y ; y < result.filterBytes.length ; y++) {
608 			scanline = imageTemp[(y * result.pitch)..((y + 1) * result.pitch)];
609 			switch (result.filterBytes[y]) {
610 				case FilterType.Sub:
611 					reconstructScanlineSub(scanline, wordlength);
612 					break;
613 				case FilterType.Up:
614 					reconstructScanlineUp(scanline, prevScanline);
615 					break;
616 				case FilterType.Average:
617 					reconstructScanlineAverage(scanline, prevScanline, wordlength);
618 					break;
619 				case FilterType.Paeth:
620 					reconstructScanlinePaeth(scanline, prevScanline, wordlength);
621 					break;
622 				default:
623 					/* version (unittest) {
624 						if(result.filterBytes[y]) std.stdio.writeln("Irregular filter type value of \"", result.filterBytes[y] 
625 							,"\" found at scanline ", y );
626 					} */
627 					break;
628 			}
629 			prevScanline = scanline;
630 		}
631 		//setup imagedata
632 		if (result.header.bitDepth == 16) {
633 			ushort[] arr = nativeStreamToBigEndian!ushort(reinterpretCast!ushort(imageTemp));
634 			imageTemp = reinterpretCast!ubyte(arr);
635 		}
636 		switch (result.getPixelFormat & ~PixelFormat.BigEndian) {
637 			case PixelFormat.Indexed1Bit: 
638 				result._imageData = new IndexedImageData1Bit(imageTemp, result._palette, result.width, result.height);
639 				break;
640 			case PixelFormat.Indexed2Bit:
641 				result._imageData = new IndexedImageData2Bit(imageTemp, result._palette, result.width, result.height);
642 				break;
643 			case PixelFormat.Indexed4Bit:
644 				result._imageData = new IndexedImageData4Bit(imageTemp, result._palette, result.width, result.height);
645 				break;
646 			case PixelFormat.Indexed8Bit:
647 				result._imageData = new IndexedImageData!ubyte(imageTemp, result._palette, result.width, result.height);
648 				break;
649 			case PixelFormat.YA88:
650 				result._imageData = new ImageData!YA88BE(reinterpretCast!YA88BE(imageTemp), result.width, result.height, 
651 						result.getPixelFormat, result.getBitdepth);
652 				break;
653 			case PixelFormat.YA16_16:
654 				//imageTemp = bigEndianStreamToNative(imageTemp);
655 				result._imageData = new ImageData!YA16_16BE(reinterpretCast!YA16_16BE(imageTemp), result.width, result.height, 
656 						result.getPixelFormat, result.getBitdepth);
657 				break;
658 			case PixelFormat.RGB888:
659 				result._imageData = new ImageData!RGB888BE(reinterpretCast!RGB888BE(imageTemp), result.width, result.height, 
660 						result.getPixelFormat, result.getBitdepth);
661 				break;
662 			case PixelFormat.RGB16_16_16:
663 				//imageTemp = bigEndianStreamToNative(imageTemp);
664 				result._imageData = new ImageData!RGB16_16_16BE(reinterpretCast!RGB16_16_16BE(imageTemp), result.width, result.height, 
665 						result.getPixelFormat, result.getBitdepth);
666 				break;
667 			case PixelFormat.RGBX5551:
668 				result._imageData = new ImageData!RGBA5551(reinterpretCast!RGBA5551(imageTemp), result.width, result.height, 
669 						result.getPixelFormat, result.getBitdepth);
670 				break;
671 			case PixelFormat.RGBA8888:
672 				result._imageData = new ImageData!RGBA8888BE(reinterpretCast!RGBA8888BE(imageTemp), result.width, result.height, 
673 						result.getPixelFormat, result.getBitdepth);
674 				break;
675 			case PixelFormat.RGBA16_16_16_16:
676 				//imageTemp = bigEndianStreamToNative(imageTemp);
677 				result._imageData = new ImageData!RGBA16_16_16_16BE(reinterpretCast!RGBA16_16_16_16BE(imageTemp), result.width, 
678 						result.height, result.getPixelFormat, result.getBitdepth);
679 				break;
680 			case PixelFormat.Grayscale1Bit:
681 				result._imageData = new MonochromeImageData1Bit(imageTemp, result.width, result.height);
682 				break;
683 			case PixelFormat.Grayscale2Bit:
684 				result._imageData = new MonochromeImageData2Bit(imageTemp, result.width, result.height);
685 				break;
686 			case PixelFormat.Grayscale4Bit:
687 				result._imageData = new MonochromeImageData4Bit(imageTemp, result.width, result.height);
688 				break;
689 			case PixelFormat.Grayscale8Bit:
690 				result._imageData = new MonochromeImageData!ubyte(reinterpretCast!ubyte(imageTemp), result.width, result.height, 
691 						result.getPixelFormat, result.getBitdepth);
692 				break;
693 			case PixelFormat.Grayscale16Bit:
694 				//imageTemp = bigEndianStreamToNative(imageTemp);
695 				result._imageData = new MonochromeImageData!ushort(reinterpretCast!ushort(imageTemp), result.width, result.height, 
696 						result.getPixelFormat, result.getBitdepth);
697 				break;
698 			default: throw new ImageFileException("Unsupported image format!");
699 		}
700 		return result;
701 	}
702 	/**
703 	 * Decompresses a block of graphics data.
704 	 */
705 	/**
706 	 * Reconstructs a scanline from `Sub` filtering.
707 	 */
708 	protected static ubyte[] reconstructScanlineSub(ubyte[] target, int bytedepth) @safe nothrow {
709 		for (size_t i ; i < target.length ; i++) {
710 			const ubyte a = i >= bytedepth ? target[i - bytedepth] : 0;
711 			target[i] += a;
712 		}
713 		return target;
714 	}
715 	/**
716 	 * Reconstructs a scanline from `Up` filtering.
717 	 */
718 	protected static ubyte[] reconstructScanlineUp(ubyte[] target, ubyte[] prevScanline) @safe nothrow {
719 		assert(target.length == prevScanline.length);
720 		for (size_t i ; i < target.length ; i++) {
721 			target[i] += prevScanline[i];
722 		}
723 		return target;
724 	}
725 	/**
726 	 * Reconstructs a scanline from `Average` filtering
727 	 */
728 	protected static ubyte[] reconstructScanlineAverage(ubyte[] target, ubyte[] prevScanline, int bytedepth) @safe nothrow {
729 		assert(target.length == prevScanline.length);
730 		for (size_t i ; i < target.length ; i++) {
731 			const uint a = i >= bytedepth ? target[i - bytedepth] : 0;
732 			target[i] += cast(ubyte)((a + prevScanline[i])>>>1);
733 			
734 		}
735 		return target;
736 	}
737 	/**
738 	 * Paeth function for filtering and reconstruction.
739 	 */
740 	protected static ubyte paethFunc(const ubyte a, const ubyte b, const ubyte c) @safe nothrow {
741 		import std.math : abs;
742 
743 		const int p = a + b - c;
744 		const int pa = abs(p - a);
745 		const int pb = abs(p - b);
746 		const int pc = abs(p - c);
747 
748 		if (pa <= pb && pa <= pc) return a;
749 		else if (pb <= pc) return b;
750 		else return c;
751 	}
752 	/**
753 	 * Reconstructs a scanline from `Paeth` filtering.
754 	 */
755 	protected static ubyte[] reconstructScanlinePaeth(ubyte[] target, ubyte[] prevScanline, int bytedepth) @safe nothrow {
756 		assert(target.length == prevScanline.length);
757 		for (size_t i ; i < target.length ; i++) {
758 			const ubyte a = i >= bytedepth ? target[i - bytedepth] : 0, b = prevScanline[i], 
759 					c = i >= bytedepth ? prevScanline[i - bytedepth] : 0;
760 			target[i] += paethFunc(a, b, c);
761 			
762 		}
763 		return target;
764 	}
765 	/**
766 	 * Saves the file to the disk.
767 	 * Currently interlaced mode is unsupported.
768 	 */
769 	public void save(F = std.stdio.File)(ref F file, int compLevel = 6) {
770 		RGB16_16_16BE fromNativeToBE(RGB16_16_16BE val) @nogc {
771 			version(LittleEndian) {
772 				val.r = swapEndian(val.r);
773 				val.g = swapEndian(val.g);
774 				val.b = swapEndian(val.b);
775 			}
776 			return val;
777 		}
778 		ubyte[] crc;
779 		//ubyte[] paletteData;
780 		//if (_palette) paletteData = _palette.raw;
781 		ubyte[] imageTemp0 = _imageData.raw, imageTemp;
782 		ubyte[] scanline, prevScanline;
783 		prevScanline.length = pitch;	//First filter should be either none or sub, but not all writers are obeying the standard
784 		int wordlength = getBitdepth > 8 ? getBitdepth / 8 : 1;
785 		for (uint y ; y < height ; y++) {
786 			scanline = imageTemp0[(y * pitch)..((y + 1) * pitch)];
787 			//version (unittest) std.stdio.writeln(scanline.ptr,";",prevScanline.ptr);
788 			switch(filterBytes[y]) {
789 				case FilterType.Sub:
790 					imageTemp ~= filterScanlineSub(scanline, wordlength);
791 					break;
792 				case FilterType.Up:
793 					imageTemp ~= filterScanlineUp(scanline, prevScanline);
794 					break;
795 				case FilterType.Average:
796 					imageTemp ~= filterScanlineAverage(scanline, prevScanline, wordlength);
797 					break;
798 				case FilterType.Paeth:
799 					imageTemp ~= filterScanlinePaeth(scanline, prevScanline, wordlength);
800 					break;
801 				default:
802 					imageTemp ~= scanline;
803 					break;
804 			}
805 			prevScanline = scanline;
806 		}
807 		assert(imageTemp0.length == imageTemp.length, "Image processing buffer error!");
808 		if (header.bitDepth == 16) {
809 			ushort[] arr = nativeStreamToBigEndian!ushort(reinterpretCast!ushort(imageTemp));
810 			imageTemp = reinterpretCast!ubyte(arr);
811 		}
812 		//write PNG signature into file
813 		file.rawWrite(PNG_SIGNATURE);
814 		//write Header into file
815 		void[] writeBuffer;
816 		//writeBuffer.length = 8;
817 		writeBuffer = cast(void[])[Chunk(header.sizeof, ChunkInitializers.Header).nativeToBigEndian];
818 		file.rawWrite(writeBuffer);
819 		//writeBuffer.length = 0;
820 		writeBuffer = cast(void[])[header.nativeToBigEndian];
821 		file.rawWrite(writeBuffer);
822 		crc = crc32Of((cast(ubyte[])ChunkInitializers.Header) ~ writeBuffer).dup.reverse;
823 		file.rawWrite(crc);
824 		//write any ancilliary chunks into the file that needs to stand before the palette
825 		foreach (curChunk; ancillaryChunks){
826 			if (curChunk.pos == EmbeddedData.DataPosition.BeforePLTE){
827 				writeBuffer = cast(void[])[Chunk(cast(uint)curChunk.data.length, curChunk.identifier).nativeToBigEndian];
828 				file.rawWrite(writeBuffer);
829 				file.rawWrite(curChunk.data);
830 				crc = crc32Of((cast(ubyte[])curChunk.identifier) ~ curChunk.data).dup.reverse;
831 				file.rawWrite(crc);
832 			}
833 		}
834 		//write palette into file if exists
835 		if (_palette) {
836 			ubyte[] paletteData, transparencyData;
837 			if (_palette.paletteFormat & PixelFormat.SeparateAlphaField) {
838 				paletteData = _palette.raw[0.._palette.length * 3];
839 				transparencyData = _palette.raw[_palette.length * 3..$];
840 			} else {
841 				paletteData = _palette.raw;
842 			}
843 			writeBuffer = cast(void[])[Chunk(cast(uint)paletteData.length, ChunkInitializers.Palette).nativeToBigEndian];
844 			file.rawWrite(writeBuffer);
845 			file.rawWrite(paletteData);
846 			crc = crc32Of((cast(ubyte[])ChunkInitializers.Palette) ~ paletteData).dup.reverse;
847 			file.rawWrite(crc);
848 			if (transparencyData.length) {
849 				writeBuffer = cast(void[])[Chunk(cast(uint)transparencyData.length, ChunkInitializers.Transparency).nativeToBigEndian];
850 				file.rawWrite(writeBuffer);
851 				file.rawWrite(transparencyData);
852 				crc = crc32Of((cast(ubyte[])ChunkInitializers.Transparency) ~ transparencyData).dup.reverse;
853 				file.rawWrite(crc);
854 			}
855 		}
856 		if (flags & 1) {
857 			ubyte[] transparencyData;
858 			RGB16_16_16BE nativeTrns = fromNativeToBE(trns);
859 			if(header.colorType == ColorType.Greyscale) {
860 				transparencyData = reinterpretAsArray!ubyte(nativeTrns.r);
861 			} else {
862 				transparencyData = reinterpretAsArray!ubyte(nativeTrns);
863 			}
864 			writeBuffer = cast(void[])[Chunk(cast(uint)transparencyData.length, ChunkInitializers.Transparency).nativeToBigEndian];
865 			file.rawWrite(writeBuffer);
866 			file.rawWrite(transparencyData);
867 			crc = crc32Of((cast(ubyte[])ChunkInitializers.Transparency) ~ transparencyData).dup.reverse;
868 			file.rawWrite(crc);
869 		}
870 		if (bkgIndex != -1) {
871 			if (bkgIndex == -2) {
872 				ubyte[] transparencyData;
873 				RGB16_16_16BE nativeTrns = fromNativeToBE(trns);
874 				if(header.colorType == ColorType.Greyscale) {
875 					transparencyData = reinterpretAsArray!ubyte(nativeTrns.r);
876 				} else {
877 					transparencyData = reinterpretAsArray!ubyte(nativeTrns);
878 				}
879 				writeBuffer = cast(void[])[Chunk(cast(uint)transparencyData.length, ChunkInitializers.Background).nativeToBigEndian];
880 				file.rawWrite(writeBuffer);
881 				file.rawWrite(transparencyData);
882 				crc = crc32Of((cast(ubyte[])ChunkInitializers.Transparency) ~ transparencyData).dup.reverse;
883 				file.rawWrite(crc);
884 			} else {
885 				writeBuffer = cast(void[])[Chunk(cast(uint)ubyte.sizeof, ChunkInitializers.Background).nativeToBigEndian];
886 				file.rawWrite(writeBuffer);
887 				writeBuffer = reinterpretAsArray!void(cast(ubyte)bkgIndex);
888 				file.rawWrite(writeBuffer);
889 				crc = crc32Of((cast(ubyte[])ChunkInitializers.Background) ~ cast(ubyte[])writeBuffer).dup.reverse;
890 				file.rawWrite(crc);
891 			}
892 		}
893 		//write any ancilliary chunks into the file that needs to stand before the imagedata
894 		foreach (curChunk; ancillaryChunks){
895 			if (curChunk.pos == EmbeddedData.DataPosition.BeforeIDAT){
896 				writeBuffer = cast(void[])[Chunk(cast(uint)curChunk.data.length, curChunk.identifier).nativeToBigEndian];
897 				file.rawWrite(writeBuffer);
898 				file.rawWrite(curChunk.data);
899 				crc = crc32Of((cast(ubyte[])curChunk.identifier) ~ curChunk.data).dup.reverse;
900 				file.rawWrite(crc);
901 			}
902 		}
903 		//compress imagedata if needed, then write it into the file
904 		{	
905 			int ret;
906 			ubyte[] input, output;
907 			input.reserve(imageTemp.length + filterBytes.length);
908 			for (int line ; line < height ; line++){
909 				const size_t offset = line * pitch;
910 				input ~= filterBytes[line];
911 				input ~= imageTemp[offset..offset+pitch];
912 			}
913     		
914     		zlib.z_stream strm;
915 			ret = zlib.deflateInit(&strm, compLevel);
916 			if (ret != zlib.Z_OK)
917 				throw new Exception("Compressor initialization error");	
918 			output.length = 32 * 1024;//cast(uint)imageTemp.length;
919 			strm.next_in = input.ptr;
920 			strm.avail_in = cast(uint)input.length;
921 			strm.next_out = output.ptr;
922 			strm.avail_out = cast(uint)output.length;
923 			do {
924 				//std.stdio.writeln(ret, ";", strm.avail_in, ";", strm.total_in, ";", strm.total_out);
925 				if (!strm.avail_out) {
926 					writeBuffer = cast(void[])[Chunk(cast(uint)output.length, ChunkInitializers.Data).nativeToBigEndian];
927 					file.rawWrite(writeBuffer);
928 					file.rawWrite(output);
929 					crc = crc32Of((cast(ubyte[])ChunkInitializers.Data) ~ output).dup.reverse;
930 					file.rawWrite(crc);
931 					strm.next_out = output.ptr;
932 					strm.avail_out = cast(uint)output.length;
933 				}
934 				ret = zlib.deflate(&strm, zlib.Z_FINISH);
935 				//std.stdio.writeln(ret, ";", strm.avail_out);
936 				if(ret < 0){
937 					zlib.deflateEnd(&strm);
938 					throw new Exception("Compressor output error: " ~ cast(string)std..string.fromStringz(strm.msg));
939 				}
940 			} while (ret != zlib.Z_STREAM_END);
941 			//write any ancilliary chunks into the file that needs to stand within the imagedata
942 			foreach (curChunk; ancillaryChunks){
943 				if (curChunk.pos == EmbeddedData.DataPosition.WithinIDAT){
944 					writeBuffer = cast(void[])[Chunk(cast(uint)curChunk.data.length, curChunk.identifier).nativeToBigEndian];
945 					file.rawWrite(writeBuffer);
946 					file.rawWrite(curChunk.data);
947 					crc = crc32Of((cast(ubyte[])curChunk.identifier) ~ curChunk.data).dup.reverse;
948 					file.rawWrite(crc);
949 				}
950 			}
951 			if (strm.avail_out != output.length) {
952 				writeBuffer = cast(void[])[Chunk(cast(uint)(output.length - strm.avail_out), ChunkInitializers.Data).nativeToBigEndian];
953 				file.rawWrite(writeBuffer);
954 				file.rawWrite(output[0..$-strm.avail_out]);
955 				crc = crc32Of((cast(ubyte[])ChunkInitializers.Data) ~ output[0..$-strm.avail_out]).dup.reverse;
956 				file.rawWrite(crc);
957 			}
958 			zlib.deflateEnd(&strm);
959 		}
960 		//write any ancilliary chunks into the file that needs to stand after the imagedata
961 		foreach (curChunk; ancillaryChunks){
962 			if (curChunk.pos == EmbeddedData.DataPosition.AfterIDAT){
963 				writeBuffer = cast(void[])[Chunk(cast(uint)curChunk.data.length, curChunk.identifier).nativeToBigEndian];
964 				file.rawWrite(writeBuffer);
965 				file.rawWrite(curChunk.data);
966 				crc = crc32Of((cast(ubyte[])curChunk.identifier) ~ curChunk.data).dup.reverse;
967 				file.rawWrite(crc);
968 			}
969 		}
970 		//write IEND chunk
971 		//writeBuffer.length = 0;
972 		writeBuffer = cast(void[])[Chunk(0, ChunkInitializers.End).nativeToBigEndian];
973 		file.rawWrite(writeBuffer);
974 		file.rawWrite(PNG_CLOSER);
975 	}
976 	/**
977 	 * Encodes a scanline using `Sub` filter.
978 	 */
979 	protected static ubyte[] filterScanlineSub(ubyte[] target, int bytedepth) @safe nothrow {
980 		ubyte[] result;
981 		result.length = target.length;
982 		for (size_t i ; i < target.length ; i++) {
983 			const ubyte a = i >= bytedepth ? target[i - bytedepth] : 0;
984 			result[i] = cast(ubyte)(target[i] - a);
985 		}
986 		return result;
987 	}
988 	/**
989 	 * Encodes a scanline using `Up` filter.
990 	 */
991 	protected static ubyte[] filterScanlineUp(ubyte[] target, ubyte[] prevScanline) @safe nothrow {
992 		ubyte[] result;
993 		result.length = target.length;
994 		for (size_t i ; i < target.length ; i++) {
995 			result[i] = cast(ubyte)(target[i] - prevScanline[i]);
996 		}
997 		return result;
998 	}
999 	/**
1000 	 * Encodes a scanline using `Average` filter.
1001 	 */
1002 	protected static ubyte[] filterScanlineAverage(ubyte[] target, ubyte[] prevScanline, int bytedepth) @safe nothrow {
1003 		ubyte[] result;
1004 		result.length = target.length;
1005 		for (size_t i ; i < target.length ; i++) {
1006 			const uint a = i >= bytedepth ? target[i - bytedepth] : 0;
1007 			result[i] = cast(ubyte)(target[i] - ((a + prevScanline[i])>>>1));
1008 		}
1009 		return result;
1010 	}
1011 	/**
1012 	 * Encodes a scanline using `Paeth` filter.
1013 	 */
1014 	protected static ubyte[] filterScanlinePaeth(ubyte[] target, ubyte[] prevScanline, int bytedepth) @safe nothrow {
1015 		ubyte[] result;
1016 		result.length = target.length;
1017 		for (size_t i ; i < target.length ; i++) {
1018 			const ubyte a = i >= bytedepth ? target[i - bytedepth] : 0, b = prevScanline[i], 
1019 					c = i >= bytedepth ? prevScanline[i - bytedepth] : 0;
1020 			result[i] = cast(ubyte)(target[i] - paethFunc(a, b, c));
1021 			
1022 		}
1023 		return result;
1024 	}
1025 	/**
1026 	 * Adds an ancillary chunk to the PNG file
1027 	 */
1028 	public void addAncilliaryChunk(EmbeddedData.DataPosition pos, char[4] identifier, ubyte[] data){
1029 		ancillaryChunks ~= new EmbeddedData(pos, identifier, data);
1030 	}
1031 	override uint width() @nogc @safe @property const pure{
1032 		return header.width;
1033 	}
1034 	override uint height() @nogc @safe @property const pure{
1035 		return header.height;
1036 	}
1037 	override bool isIndexed() @nogc @safe @property const pure{
1038 		return header.colorType == ColorType.Indexed;
1039 	}
1040 	override ubyte getBitdepth() @nogc @safe @property const pure{
1041 		switch(header.colorType){
1042 			case ColorType.GreyscaleWithAlpha:
1043 				return cast(ubyte)(header.bitDepth * 2);
1044 			case ColorType.TrueColor:
1045 				return cast(ubyte)(header.bitDepth * 3);
1046 			case ColorType.TrueColorWithAlpha:
1047 				return cast(ubyte)(header.bitDepth * 4);
1048 			default:
1049 				return header.bitDepth;
1050 		}
1051 	}
1052 	override ubyte getPaletteBitdepth() @nogc @safe @property const pure{
1053 		if (_palette) return _palette.bitDepth;
1054 		else return 0;
1055 	}
1056 	override uint getPixelFormat() @nogc @safe @property const pure{
1057 		switch (header.colorType){
1058 			case ColorType.Indexed:
1059 				switch (header.bitDepth) {
1060 					case 1: return PixelFormat.Indexed1Bit;
1061 					case 2: return PixelFormat.Indexed2Bit;
1062 					case 4: return PixelFormat.Indexed4Bit;
1063 					case 8: return PixelFormat.Indexed8Bit;
1064 					default: return PixelFormat.Undefined;
1065 				}
1066 			case ColorType.GreyscaleWithAlpha: 
1067 				if (header.bitDepth) return PixelFormat.YA88 | PixelFormat.BigEndian;
1068 				else return PixelFormat.YA16_16 | PixelFormat.BigEndian;
1069 			case ColorType.Greyscale: 
1070 				switch (header.bitDepth) {
1071 					case 1: return PixelFormat.Grayscale1Bit;
1072 					case 2: return PixelFormat.Grayscale2Bit;
1073 					case 4: return PixelFormat.Grayscale4Bit;
1074 					case 8: return PixelFormat.Grayscale8Bit;
1075 					case 16: return PixelFormat.Grayscale16Bit;
1076 					default: return PixelFormat.Undefined;
1077 				}
1078 			case ColorType.TrueColor:
1079 				switch (header.bitDepth) {
1080 					case 8: return PixelFormat.RGB888 | PixelFormat.BigEndian;
1081 					case 16: return PixelFormat.RGB16_16_16 | PixelFormat.BigEndian;
1082 					default: return PixelFormat.RGBX5551;
1083 				}
1084 			case ColorType.TrueColorWithAlpha:
1085 				switch (header.bitDepth) {
1086 					case 8: return PixelFormat.RGBA8888 | PixelFormat.BigEndian | PixelFormat.ValidAlpha;
1087 					case 16: return PixelFormat.RGBA16_16_16_16 | PixelFormat.BigEndian | PixelFormat.ValidAlpha;
1088 					default: return PixelFormat.Undefined;
1089 				}
1090 			default: return PixelFormat.Undefined;
1091 		}
1092 	}
1093 	override uint getPalettePixelFormat() @nogc @safe @property const pure{
1094 		if (_palette) return _palette.paletteFormat;
1095 		else return PixelFormat.Undefined;
1096 	}
1097 	/**
1098 	 * Returns the header.
1099 	 */
1100 	public ref Header getHeader() @nogc @safe pure{
1101 		return header;
1102 	}
1103 	
1104 	public uint getCurrentImage() @safe pure {
1105 		return uint.init; // TODO: implement
1106 	}
1107 	
1108 	public uint setCurrentImage(uint frame) @safe pure {
1109 		return uint.init; // TODO: implement
1110 	}
1111 	///Sets the current image to the static if available
1112 	public void setStaticImage() @safe pure {
1113 
1114 	}
1115 	public uint nOfImages() @property @safe @nogc pure const {
1116 		return cast(uint)(frames.length + 1);
1117 	}
1118 	
1119 	public uint frameTime() @property @safe @nogc pure const {
1120 		return uint.init; // TODO: implement
1121 	}
1122 	
1123 	public bool isAnimation() @property @safe @nogc pure const {
1124 		return frames.length ? true : false;
1125 	}
1126 	
1127 	public string getMetadata(string id) @safe pure {
1128 		foreach (Text key; textData) {
1129 			if (key.keyword == id)
1130 				return key.text;
1131 		}
1132 		return null;
1133 	}
1134 	
1135 	public string setMetadata(string id, string val) @safe pure {
1136 		foreach (ref Text key; textData) {
1137 			if (key.keyword == id)
1138 				return key.text = val;
1139 		}
1140 		textData ~= Text (id, val, null, null, 0, 0);
1141 		return val;
1142 	}
1143 	
1144 	public string getID() @safe pure {
1145 		return getMetadata("Title");
1146 	}
1147 	
1148 	public string getAuthor() @safe pure {
1149 		return getMetadata("Author");
1150 	}
1151 	
1152 	public string getComment() @safe pure {
1153 		return getMetadata("Comment");
1154 	}
1155 	
1156 	public string getJobName() @safe pure {
1157 		return getMetadata("Job Name");
1158 	}
1159 	
1160 	public string getSoftwareInfo() @safe pure {
1161 		return getMetadata("Software");
1162 	}
1163 	
1164 	public string getSoftwareVersion() @safe pure {
1165 		return getMetadata("Software Version");
1166 	}
1167 	
1168 	public string getDescription() @safe pure {
1169 		return getMetadata("Description");
1170 	}
1171 	
1172 	public string getSource() @safe pure {
1173 		return getMetadata("Source");
1174 	}
1175 	
1176 	public string getCopyright() @safe pure {
1177 		return getMetadata("Copyright");
1178 	}
1179 	
1180 	public string getCreationTimeStr() @safe pure {
1181 		return getMetadata("Creation Time");
1182 	}
1183 	
1184 	public string setID(string val) @safe pure {
1185 		return setMetadata("Title", val);
1186 	}
1187 	
1188 	public string setAuthor(string val) @safe pure {
1189 		return setMetadata("Author", val);
1190 	}
1191 	
1192 	public string setComment(string val) @safe pure {
1193 		return setMetadata("Comment", val);
1194 	}
1195 	
1196 	public string setJobName(string val) @safe pure {
1197 		return setMetadata("Job Name", val);
1198 	}
1199 	
1200 	public string setSoftwareInfo(string val) @safe pure {
1201 		return setMetadata("Software", val);
1202 	}
1203 	
1204 	public string setSoftwareVersion(string val) @safe pure {
1205 		return setMetadata("Software Version", val);
1206 	}
1207 	
1208 	public string setDescription(string val) @safe pure {
1209 		return setMetadata("Description", val);
1210 	}
1211 	
1212 	public string setSource(string val) @safe pure {
1213 		return setMetadata("Source", val);
1214 	}
1215 	
1216 	public string setCopyright(string val) @safe pure {
1217 		return setMetadata("Copyright", val);
1218 	}
1219 	
1220 	public string setCreationTime(string val) @safe pure {
1221 		return setMetadata("Creation Time", val);
1222 	}
1223 	
1224 	
1225 	
1226 }
1227 
1228 unittest{
1229 	import vfile;
1230 	import dimage.tga;
1231 	{
1232 		std.stdio.File indexedPNGFile = std.stdio.File("./test/png/MARBLE24.PNG");
1233 		std.stdio.writeln("Loading ", indexedPNGFile.name);
1234 		PNG a = PNG.load(indexedPNGFile);
1235 		std.stdio.writeln("File `", indexedPNGFile.name, "` successfully loaded");
1236 		assert(a.getBitdepth == 24, "Bitdepth error!");
1237 		//std.stdio.File output = std.stdio.File("./test/png/output.png", "wb");
1238 		//a.save(output);
1239 		VFile virtualIndexedPNGFile;
1240 		a.save(virtualIndexedPNGFile);
1241 		std.stdio.writeln("Successfully saved to virtual file ", virtualIndexedPNGFile.size);
1242 		virtualIndexedPNGFile.seek(0);
1243 		PNG b = PNG.load(virtualIndexedPNGFile);
1244 		std.stdio.writeln("Image restored from virtual file");
1245 		compareImages(a, b);
1246 		std.stdio.writeln("The two images' output match");
1247 	}
1248 	/+{
1249 		std.stdio.File indexedPNGFile = std.stdio.File("./test/png/MARBLE8.png");
1250 		std.stdio.writeln("Loading ", indexedPNGFile.name);
1251 		PNG a = PNG.load(indexedPNGFile);
1252 		std.stdio.writeln("File `", indexedPNGFile.name, "` successfully loaded");
1253 		//std.stdio.writeln(a.filterBytes);
1254 		//std.stdio.File output = std.stdio.File("./test/png/output.png", "wb");
1255 		//a.save(output);
1256 		VFile virtualIndexedPNGFile;
1257 		a.save(virtualIndexedPNGFile);
1258 		std.stdio.writeln("Successfully saved to virtual file ", virtualIndexedPNGFile.size);
1259 		virtualIndexedPNGFile.seek(0);
1260 		PNG b = PNG.load(virtualIndexedPNGFile);
1261 		std.stdio.writeln("Image restored from virtual file");
1262 		compareImages(a, b);
1263 		std.stdio.writeln("The two images' output match");
1264 	}+/
1265 	//test indexed png files and their palettes
1266 	{
1267 		std.stdio.File indexedPNGFile = std.stdio.File("./test/png/sci-fi-tileset.png");
1268 		std.stdio.writeln("Loading ", indexedPNGFile.name);
1269 		PNG a = PNG.load(indexedPNGFile);
1270 		std.stdio.writeln("File `", indexedPNGFile.name, "` successfully loaded");
1271 		//std.stdio.File output = std.stdio.File("./test/png/output.png", "wb");
1272 		//a.save(output);
1273 		IPalette p = a.palette;
1274 		assert(p.convTo(PixelFormat.ARGB8888).length == p.length);
1275 		assert(p.convTo(PixelFormat.ARGB8888 | PixelFormat.BigEndian).length == p.length);
1276 	}
1277 	//test loading and saving multiple images
1278 	const string[] filenames = ["basn0g01.png", "basn0g02.png", "basn0g04.png", "basn0g08.png", "basn0g16.png", 
1279 			"basn2c08.png", "basn2c16.png", "basn3p01.png", "basn3p02.png", "basn3p04.png", "basn3p08.png",
1280 			"basn4a08.png", "basn4a16.png", "basn6a08.png", "basn6a16.png"];
1281 	foreach (fn ; filenames) {
1282 		std.stdio.File sourceFile = std.stdio.File("./test/png/" ~ fn);
1283 		std.stdio.writeln("Loading ", sourceFile.name);
1284 		PNG a = PNG.load(sourceFile);
1285 		std.stdio.writeln("File `", sourceFile.name, "` successfully loaded");
1286 		//std.stdio.writeln(a.filterBytes);
1287 		//std.stdio.File output = std.stdio.File("./test/png/output_" ~ fn, "wb"); 
1288 		//a.save(output);
1289 		VFile virtualPNGFile;
1290 		a.save(virtualPNGFile);
1291 		std.stdio.writeln("Successfully saved to virtual file ", virtualPNGFile.size);
1292 		virtualPNGFile.seek(0);
1293 		PNG b = PNG.load(virtualPNGFile);
1294 		std.stdio.writeln("Image restored from virtual file");
1295 		compareImages(a, b);
1296 		std.stdio.writeln("The two images' output match");
1297 		std.stdio.writeln("Reconstructing PNG from image data");
1298 		PNG c = new PNG(a.imageData, a.palette);
1299 		compareImages(a, c);
1300 		virtualPNGFile = VFile.init;
1301 		c.save(virtualPNGFile);
1302 		//output.rewind();
1303 		virtualPNGFile.seek(0);
1304 		b = PNG.load(virtualPNGFile);
1305 		std.stdio.writeln("Image restored from virtual file");
1306 		compareImages(a, b);
1307 		std.stdio.writeln("The two images' output match");
1308 	}
1309 	//test against TGA versions of the same images
1310 	/* {
1311 		std.stdio.File tgaSource = std.stdio.File("./test/tga/mapped_8.tga");
1312 		std.stdio.File pngSource = std.stdio.File("./test/png/mapped_8.png");
1313 		std.stdio.writeln("Loading ", tgaSource.name);
1314 		TGA tgaImage = TGA.load(tgaSource);
1315 		std.stdio.writeln("Loading ", pngSource.name);
1316 		PNG pngImage = PNG.load(pngSource);
1317 		compareImages!true(tgaImage, pngImage);
1318 	} */
1319 	{
1320 		std.stdio.File tgaSource = std.stdio.File("./test/tga/truecolor_24.tga");
1321 		std.stdio.File pngSource = std.stdio.File("./test/png/truecolor_24.png");
1322 		std.stdio.writeln("Loading ", tgaSource.name);
1323 		TGA tgaImage = TGA.load(tgaSource);
1324 		std.stdio.writeln("Loading ", pngSource.name);
1325 		PNG pngImage = PNG.load(pngSource);
1326 		compareImages!true(tgaImage, pngImage);
1327 	}
1328 	{
1329 		std.stdio.File tgaSource = std.stdio.File("./test/tga/truecolor_32.tga");
1330 		std.stdio.File pngSource = std.stdio.File("./test/png/truecolor_32.png");
1331 		std.stdio.writeln("Loading ", tgaSource.name);
1332 		TGA tgaImage = TGA.load(tgaSource);
1333 		std.stdio.writeln("Loading ", pngSource.name);
1334 		PNG pngImage = PNG.load(pngSource);
1335 		compareImages!true(tgaImage, pngImage);
1336 	}
1337 }