1 /*
2  * dimage - bmp.d
3  * by Laszlo Szeremi
4  *
5  * Copyright under Boost Software License.
6  */
7 
8 module dimage.bmp;
9 
10 import dimage.base;
11 import dimage.util;
12 static import std.stdio;
13 import bitleveld.datatypes;
14 import std.bitmanip;
15 
16 /**
17  * Implements *.BMP file handling.
18  */
19 public class BMP : Image {
20 	static enum ushort WIN1XTYPE = 0;			///Defines Windows 1.x files
21 	static enum ushort BMPTYPE = 0x4D42;		///Defines later versions of the file
22 	/**
23 	 * Mostly used during construction of new instances and version changing.
24 	 */
25 	public enum BMPVersion : uint {
26 		Win1X	=	0,		///256 color max
27 		Win2X	=	4 + Win2xBitmapHeader.sizeof,		///256 color max
28 		Win3X	=	4 + Win3xBitmapHeader.sizeof,		///24M color, no alpha channel
29 		WinNT	=	4 + Win3xBitmapHeader.sizeof + WinNTBitmapHeaderExt.sizeof,		///24M color, no alpha channel
30 		Win4X	=	4 + Win3xBitmapHeader.sizeof + Win4xBitmapHeaderExt.sizeof,		///Alpha channel enabled
31 	}
32 	/**
33 	 * Stores compression type identificator.
34 	 */
35 	public enum CompressionType {
36 		None		=	0,
37 		RLE8Bit		=	1,
38 		RLE4Bit		=	2,
39 		Bitfields	=	3,
40 	}
41 	protected ushort fileVersionIndicator;	///Null if Win1x
42 	/**
43 	 * Windows 1.x header
44 	 */
45 	public struct Win1xHeader {
46 		ushort		width;		///Width of the bitmap
47 		ushort		height;		///Height of the bitmap
48 		ushort		byteWidth;	///Width of the bitmap in bytes
49 		ubyte		planes;		///Number of color planes
50 		ubyte		bitsPerPixel;	///Number of bits per pixel
51 	}
52 	/**
53 	 * Header used in later versions
54 	 */
55 	public struct WinBMPHeader {
56 		uint		fileSize;	///Size of the file in bytes
57 		ushort		reserved1;	///Always 0
58 		ushort		reserved2;	///Always 0
59 		uint		bitmapOffset;	///Starting position of bitmap in file in bytes
60 	}
61 	protected union Header{
62 		Win1xHeader		oldType;
63 		WinBMPHeader	newType;
64 	}
65 	protected Header header;
66 	protected uint bitmapHeaderLength;
67 	/**
68 	 * Bitmap header for Win 2.x
69 	 */
70 	public struct Win2xBitmapHeader {
71 		//uint		size = Win2XBitmapHeader.sizeof;	///Size of this header in bytes
72 		short		width; 		///Width of the bitmap
73 		short		height;		///Height of the bitmap
74 		ushort		planes;		///Number of color planes
75 		ushort		bitsPerPixel;	///Number of bits per pixel
76 	}
77 	/**
78 	 * Bitmap header for Win 3.x and 4.x
79 	 */
80 	public struct Win3xBitmapHeader {
81 		int			width; 		///Width of the bitmap
82 		int			height;		///Height of the bitmap
83 		ushort		planes;		///Number of color planes
84 		ushort		bitsPerPixel;	///Number of bits per pixel
85 
86 		uint		compression;	///Compression methods used
87 		uint		sizeOfBitmap;	///Size of bitmap in bytes
88 		int			horizResolution;///Pixels per meter
89 		int			vertResolution;	///Pixels per meter
90 		uint		colorsUsed;		///Colors used in image
91 		uint		colorsimportant;///Colors important to display the image
92 	}
93 	protected union BitmapHeader {
94 		Win2xBitmapHeader	oldType;
95 		Win3xBitmapHeader	newType;
96 	}
97 	protected BitmapHeader bitmapHeader;
98 	/**
99 	 * Bitmap header extension for Win 4.x
100 	 */
101 	public struct Win4xBitmapHeaderExt {
102 		uint		redMask;		///Mask identifying red component
103 		uint		greenMask;		///Mask identifying green component
104 		uint		blueMask;		///Mask identifying blue component
105 		uint		alphaMask;		///Mask identifying alpha component
106 		uint		csType;			///Color space type
107 		int			redX;			///X coordinate of red endpoint
108 		int			redY;			///Y coordinate of red endpoint
109 		int			redZ;			///Z coordinate of red endpoint
110 		int			greenX;			///X coordinate of green endpoint
111 		int			greenY;			///Y coordinate of green endpoint
112 		int			greenZ;			///Z coordinate of green endpoint
113 		int			blueX;			///X coordinate of blue endpoint
114 		int			blueY;			///Y coordinate of blue endpoint
115 		int			blueZ;			///Z coordinate of blue endpoint
116 		uint		gammaRed;		///Gamma red coordinate scale
117 		uint		gammaGreen;		///Gamma green coordinate scale
118 		uint		gammaBlue;		///Gamma blue coordinate scale
119 	}
120 	/**
121 	 * Bitmap header extension for WinNT
122 	 */
123 	public struct WinNTBitmapHeaderExt {
124 		uint		redMask;		///Mask identifying red component
125 		uint		greenMask;		///Mask identifying green component
126 		uint		blueMask;		///Mask identifying blue component
127 	}
128 	protected union HeaderExt {
129 		Win4xBitmapHeaderExt	longext;
130 		WinNTBitmapHeaderExt	shortext;
131 	}
132 	protected HeaderExt headerExt;
133 	//protected size_t pitch;
134 	/**
135 	 * Creates a blank for loading.
136 	 */
137 	protected this () {
138 
139 	}
140 	/**
141 	 * Creates a new bitmap from supplied data.
142 	 */
143 	public this (IImageData imgDat, IPalette pal = null, BMPVersion vers = BMPVersion.Win4X) @safe pure {
144 		if (vers == BMPVersion.Win2X) {
145 			if (pal) {
146 				if (pal.paletteFormat != PixelFormat.RGB888) throw new ImageFormatException("Unsupported palette format!");
147 				if (!(imgDat.pixelFormat == PixelFormat.Indexed1Bit || imgDat.pixelFormat == PixelFormat.Indexed4Bit || 
148 						imgDat.pixelFormat == PixelFormat.Indexed8Bit)) throw new ImageFormatException("Image format not supported by this
149 						version of BMP!");
150 				bitmapHeaderLength = vers;
151 				_imageData = imgDat;
152 				_palette = pal;
153 				bitmapHeader.oldType.width = cast(short)_imageData.width;
154 				bitmapHeader.oldType.height = cast(short)_imageData.height;
155 				bitmapHeader.oldType.planes = 1;
156 				bitmapHeader.oldType.bitsPerPixel = _imageData.bitDepth;
157 				header.newType.bitmapOffset = cast(uint)pal.raw.length;
158 			} else throw new ImageFormatException("This version of BMP must have a palette!");
159 		} else if (vers == BMPVersion.Win1X) {
160 			if (pal) throw new ImageFormatException("This version of BMP doesn't have a palette!");
161 			_imageData = imgDat;
162 			header.oldType.width = cast(ushort)_imageData.width;
163 			header.oldType.height = cast(ushort)_imageData.height;
164 			header.oldType.byteWidth = cast(ushort)(_imageData.width * 8 / _imageData.bitDepth);
165 			if (_imageData.bitDepth == 4 && _imageData.width & 1) header.oldType.byteWidth++;
166 			if (_imageData.bitDepth == 1 && _imageData.width & 7) header.oldType.byteWidth++;
167 			header.oldType.bitsPerPixel = _imageData.bitDepth;
168 			header.oldType.planes = 1;
169 		} else {
170 			if (pal) {
171 				if (pal.paletteFormat != PixelFormat.XRGB8888) throw new ImageFormatException("Unsupported palette format!");
172 				if (!(imgDat.pixelFormat == PixelFormat.Indexed1Bit || imgDat.pixelFormat == PixelFormat.Indexed4Bit || 
173 						imgDat.pixelFormat == PixelFormat.Indexed8Bit)) throw new ImageFormatException("Unsupported indexed image type!");
174 				_palette = pal;
175 				header.newType.bitmapOffset = cast(uint)pal.raw.length;
176 			} else {
177 				if (!(imgDat.pixelFormat == PixelFormat.RGB888 || imgDat.pixelFormat == PixelFormat.ARGB8888 || 
178 						imgDat.pixelFormat == PixelFormat.RGB565 || imgDat.pixelFormat == PixelFormat.RGBA5551)) throw new 
179 						ImageFormatException("Unsupported truecolor image type!");
180 			}
181 			_imageData = imgDat;
182 			bitmapHeaderLength = vers;
183 			bitmapHeader.newType.planes = 1;
184 			bitmapHeader.newType.compression = 0;
185 			bitmapHeader.newType.bitsPerPixel = _imageData.bitDepth;
186 			bitmapHeader.newType.width = _imageData.width;
187 			bitmapHeader.newType.height = _imageData.height;
188 			bitmapHeader.newType.horizResolution = 72;
189 			bitmapHeader.newType.vertResolution = 72;
190 			bitmapHeader.newType.colorsUsed = 1 << bitmapHeader.newType.bitsPerPixel;
191 			bitmapHeader.newType.colorsimportant = bitmapHeader.newType.colorsUsed; //ALL THE COLORS!!! :)
192 			if (vers == BMPVersion.Win4X || vers == BMPVersion.WinNT) {
193 				switch(_imageData.pixelFormat) {
194 					case PixelFormat.RGB565:
195 						headerExt.longext.redMask = 0xF8_00_00_00;
196 						headerExt.longext.greenMask = 0x07_E0_00_00;
197 						headerExt.longext.blueMask = 0x00_1F_00_00;
198 						break;
199 					case PixelFormat.RGBX5551:
200 						headerExt.longext.redMask = 0xF8_00_00_00;
201 						headerExt.longext.greenMask = 0x07_C0_00_00;
202 						headerExt.longext.blueMask = 0x00_3E_00_00;
203 						break;
204 					case PixelFormat.RGB888:
205 						headerExt.longext.redMask = 0xff_00_00_00;
206 						headerExt.longext.greenMask = 0x00_ff_00_00;
207 						headerExt.longext.blueMask = 0x00_00_ff_00;
208 						break;
209 					case PixelFormat.ARGB8888:
210 						headerExt.longext.alphaMask = 0xff_00_00_00;
211 						headerExt.longext.redMask = 0x00_ff_00_00;
212 						headerExt.longext.greenMask = 0x00_00_ff_00;
213 						headerExt.longext.blueMask = 0x00_00_00_ff;
214 						break;
215 					default: break;
216 				}
217 			}
218 		}
219 		if (vers != BMPVersion.Win1X) {
220 			header.newType.bitmapOffset += 2 + WinBMPHeader.sizeof + bitmapHeaderLength;
221 			header.newType.fileSize = header.newType.bitmapOffset + cast(uint)_imageData.raw.length;
222 		}
223 	}
224 	/**
225 	 * Loads an image from a file.
226 	 * Only uncompressed and 8bit RLE are supported.
227 	 */
228 	public static BMP load (F = std.stdio.file) (ref F file) {
229 		import std.math : abs;
230 		BMP result = new BMP();
231 		ubyte[] buffer, imageBuffer;
232 		void loadUncompressedImageData (int bitsPerPixel, size_t width, size_t height) {
233 			size_t scanlineSize = (width * bitsPerPixel) / 8;
234 			scanlineSize += ((width * bitsPerPixel) % 32) / 8;	//Padding
235 			ubyte[] localBuffer;
236 			localBuffer.length = scanlineSize;
237 			for(int i ; i < height ; i++) {
238 				file.rawRead(localBuffer);
239 				assert(localBuffer.length == scanlineSize, "Scanline mismatch");
240 				imageBuffer ~= localBuffer[0..(width * bitsPerPixel) / 8];
241 			}
242 			//assert(imageBuffer.length == (width * height) / 8, "Scanline mismatch");
243 			if (result.bitmapHeaderLength >> 16)
244 				assert(imageBuffer.length == result.bitmapHeader.newType.sizeOfBitmap, "Size mismatch");
245 		}
246 		void load8BitRLEImageData (size_t width, size_t height) {
247 			size_t remaining = width * height;
248 			ubyte[] localBuffer;
249 			ubyte[] scanlineBuffer;
250 			localBuffer.length = 2;
251 			scanlineBuffer.reserve(width);
252 			imageBuffer.reserve(width * height);
253 			while (remaining) {
254 				localBuffer = file.rawRead(localBuffer);
255 				assert(localBuffer.length == 2, "End of File error");
256 				if (localBuffer[0]) {	//Run length encoding
257 					while (localBuffer[0]) {
258 						localBuffer[0]--;
259 						scanlineBuffer ~= localBuffer[1];
260 					}
261 				} else if (localBuffer[1] == 1) {	//End of bitmap data marker
262 					//flush current scanline
263 					scanlineBuffer.length = width;
264 					imageBuffer ~= scanlineBuffer;
265 					break;
266 				} else if (localBuffer[1] == 2) {	//Run offset marker
267 					localBuffer = file.rawRead(localBuffer);
268 					assert(localBuffer.length == 2, "End of File error");
269 					remaining -= localBuffer[0] + (localBuffer[1] * width);
270 					//flush current scanline
271 					scanlineBuffer.length = width;
272 					imageBuffer ~= scanlineBuffer;
273 					while (localBuffer[1]) {
274 						localBuffer[1]--;
275 						imageBuffer ~= new ubyte[](width);
276 					}
277 					//clear current scanline
278 					scanlineBuffer.length = 0;
279 					while (localBuffer[0]) {
280 						localBuffer[0]--;
281 						scanlineBuffer ~= 0;
282 					}
283 				} else if (localBuffer[1]) {		//Raw data
284 					buffer.length = localBuffer[1];
285 					buffer = file.rawRead(buffer);
286 					scanlineBuffer ~= buffer;
287 					if (localBuffer[1] & 1)
288 						file.seek(1, std.stdio.SEEK_CUR);
289 				} else {	//End of scanline
290 					scanlineBuffer.length += width - (scanlineBuffer.length % width);
291 					//flush current scanline
292 					scanlineBuffer.length = width;
293 					imageBuffer ~= scanlineBuffer;
294 					//clear current scanline
295 					scanlineBuffer.length = 0;
296 				}
297 			}
298 			imageBuffer.length = width * height;
299 		}
300 		void loadImageDataWin3x () {
301 			switch (result.bitmapHeader.newType.compression) {
302 				case CompressionType.None:
303 					loadUncompressedImageData (result.bitmapHeader.newType.bitsPerPixel, abs(result.bitmapHeader.newType.width), 
304 							abs(result.bitmapHeader.newType.height));
305 					break;
306 				default:
307 					break;
308 			}
309 		}
310 		void loadHeaderWin3x () {
311 			buffer.length = Win3xBitmapHeader.sizeof;
312 			buffer = file.rawRead(buffer);
313 			result.bitmapHeader.newType = reinterpretGet!Win3xBitmapHeader(buffer);
314 		}
315 		void loadPalette (int bitsPerPixel, int bytesPerPaletteEntry) {
316 			ubyte[] paletteBuffer;
317 			switch (bitsPerPixel) {
318 				case 1:
319 					paletteBuffer.length = 2 * bytesPerPaletteEntry;
320 					break;
321 				case 4:
322 					paletteBuffer.length = 16 * bytesPerPaletteEntry;
323 					break;
324 				case 8:
325 					paletteBuffer.length = 256 * bytesPerPaletteEntry;
326 					break;
327 				default:
328 					return;
329 			}
330 			paletteBuffer = file.rawRead(paletteBuffer);
331 			if(bytesPerPaletteEntry == 3) {
332 				result._palette = new Palette!RGB888(reinterpretCast!RGB888(paletteBuffer), PixelFormat.RGB888, 24);
333 			} else {
334 				result._palette = new Palette!ARGB8888(reinterpretCast!ARGB8888(paletteBuffer), PixelFormat.XRGB8888, 32);
335 			}
336 		}
337 		buffer.length = 2;
338 		buffer = file.rawRead(buffer);
339 		result.fileVersionIndicator = reinterpretGet!ushort(buffer);
340 		//Decide file version, if first two byte is "BM" it's 2.0 or later, if not it's 1.x
341 		if (result.fileVersionIndicator) {
342 			buffer.length = WinBMPHeader.sizeof;
343 			buffer = file.rawRead(buffer);
344 			result.header.newType = reinterpretGet!WinBMPHeader(buffer);
345 			buffer.length = 4;
346 			buffer = file.rawRead(buffer);
347 			result.bitmapHeaderLength = reinterpretGet!uint(buffer);
348 			switch (result.bitmapHeaderLength) {
349 				case Win2xBitmapHeader.sizeof + 4:
350 					buffer.length = Win2xBitmapHeader.sizeof;
351 					buffer = file.rawRead(buffer);
352 					result.bitmapHeader.oldType = reinterpretGet!Win2xBitmapHeader(buffer);
353 					if (result.isIndexed) {
354 						loadPalette(result.bitmapHeader.oldType.bitsPerPixel, 3);
355 					}
356 					loadUncompressedImageData(result.bitmapHeader.oldType.bitsPerPixel, abs(result.bitmapHeader.oldType.width), 
357 							abs(result.bitmapHeader.oldType.height));
358 					break;
359 				case Win3xBitmapHeader.sizeof + 4:
360 					loadHeaderWin3x();
361 					if (result.isIndexed) {
362 						loadPalette(result.bitmapHeader.newType.bitsPerPixel, 4);
363 					}
364 					loadImageDataWin3x();
365 					break;
366 				//Check for WinNT or Win4x header extensions
367 				case Win3xBitmapHeader.sizeof + 4 + WinNTBitmapHeaderExt.sizeof:
368 					loadHeaderWin3x();
369 					buffer.length = WinNTBitmapHeaderExt.sizeof;
370 					buffer = file.rawRead(buffer);
371 					result.headerExt.shortext = reinterpretGet!WinNTBitmapHeaderExt(buffer);
372 					if (result.isIndexed) {
373 						loadPalette(result.bitmapHeader.newType.bitsPerPixel, 4);
374 					}
375 					loadImageDataWin3x();
376 					break;
377 				case Win3xBitmapHeader.sizeof + 4 + Win4xBitmapHeaderExt.sizeof:
378 					loadHeaderWin3x();
379 					buffer.length = Win4xBitmapHeaderExt.sizeof;
380 					buffer = file.rawRead(buffer);
381 					result.headerExt.longext = reinterpretGet!Win4xBitmapHeaderExt(buffer);
382 					if (result.isIndexed) {
383 						loadPalette(result.bitmapHeader.newType.bitsPerPixel, 4);
384 					}
385 					loadImageDataWin3x();
386 					break;
387 				default:
388 					throw new Exception("File error!");
389 			}
390 			
391 		} else {
392 			buffer.length = Win1xHeader.sizeof;
393 			buffer = file.rawRead(buffer);
394 			result.header.oldType = reinterpretGet!Win1xHeader(buffer);
395 			loadUncompressedImageData(result.header.oldType.bitsPerPixel, result.header.oldType.width, 
396 					result.header.oldType.height);
397 		}
398 		//Set up image data
399 		//std.stdio.writeln(result.getPixelFormat);
400 		switch (result.getPixelFormat) {
401 			case PixelFormat.Indexed1Bit:
402 				result._imageData = new IndexedImageData1Bit(imageBuffer, result._palette, result.width, result.height);
403 				break;
404 			case PixelFormat.Indexed4Bit:
405 				result._imageData = new IndexedImageData4Bit(imageBuffer, result._palette, result.width, result.height);
406 				break;
407 			case PixelFormat.Indexed8Bit:
408 				result._imageData = new IndexedImageData!ubyte(imageBuffer, result._palette, result.width, result.height);
409 				break;
410 			case PixelFormat.RGB565:
411 				result._imageData = new ImageData!RGB565(reinterpretCast!RGB565(imageBuffer), result.width, result.height, 
412 						PixelFormat.RGB565, 16);
413 				break;
414 			case PixelFormat.RGBX5551:
415 				result._imageData = new ImageData!RGBA5551(reinterpretCast!RGBA5551(imageBuffer), result.width, result.height, 
416 						PixelFormat.RGBA5551, 16);
417 				break;
418 			case PixelFormat.RGB888:
419 				result._imageData = new ImageData!RGB888(reinterpretCast!RGB888(imageBuffer), result.width, result.height, 
420 						PixelFormat.RGB888, 24);
421 				break;
422 			case PixelFormat.ARGB8888:
423 				result._imageData = new ImageData!ARGB8888(reinterpretCast!ARGB8888(imageBuffer), result.width, result.height, 
424 						PixelFormat.ARGB8888, 32);
425 				break;
426 			default: throw new ImageFileException("Unknown image format!");
427 		}
428 		return result;
429 	}
430 	///Saves the image into the given file.
431 	///Only uncompressed bitmaps are supported currently.
432 	public void save (F = std.stdio.file)(ref F file) {
433 		ubyte[] buffer, paletteData;
434 		if (_palette) paletteData = _palette.raw;
435 		void saveUncompressed () {
436 			const size_t pitch = (width * getBitdepth) / 8;
437 			ubyte[] imageBuffer = _imageData.raw;
438 			for (int i ; i < height ; i++) {
439 				buffer = imageBuffer[pitch * i .. pitch * (i + 1)];
440 				while (buffer.length & 0b0000_0011) 
441 					buffer ~= 0b0;
442 				file.rawWrite(buffer);
443 			}
444 		}
445 		void saveWin3xHeader () {
446 			buffer = reinterpretAsArray!ubyte(BMPTYPE);
447 				file.rawWrite(buffer);
448 				buffer = reinterpretAsArray!ubyte(header.newType);
449 				file.rawWrite(buffer);
450 				buffer = reinterpretAsArray!ubyte(bitmapHeaderLength);
451 				buffer ~= reinterpretAsArray!ubyte(bitmapHeader.newType);
452 				file.rawWrite(buffer);
453 		}
454 		buffer.length = 2;
455 		switch (bitmapHeaderLength) {
456 			case BMPVersion.Win2X:
457 				buffer = reinterpretAsArray!ubyte(BMPTYPE);
458 				file.rawWrite(buffer);
459 				buffer = reinterpretAsArray!ubyte(header.newType);
460 				file.rawWrite(buffer);
461 				buffer = reinterpretAsArray!ubyte(bitmapHeaderLength);
462 				buffer ~= reinterpretAsArray!ubyte(bitmapHeader.oldType);
463 				file.rawWrite(buffer);
464 				if (paletteData.length)
465 					file.rawWrite(paletteData);
466 				saveUncompressed;
467 				break;
468 			case BMPVersion.Win3X:
469 				saveWin3xHeader();
470 				if (paletteData.length)
471 					file.rawWrite(paletteData);
472 				saveUncompressed;
473 				break;
474 			case BMPVersion.Win4X:
475 				saveWin3xHeader();
476 				buffer = reinterpretAsArray!ubyte(headerExt.longext);
477 				file.rawWrite(buffer);
478 				if (paletteData.length)
479 					file.rawWrite(paletteData);
480 				saveUncompressed;
481 				break;
482 			case BMPVersion.WinNT:
483 				saveWin3xHeader();
484 				buffer = reinterpretAsArray!ubyte(headerExt.shortext);
485 				file.rawWrite(buffer);
486 				if (paletteData.length)
487 					file.rawWrite(paletteData);
488 				saveUncompressed;
489 				break;
490 			default:		//Must be Win1X
491 				file.rawWrite(buffer);
492 				buffer = reinterpretAsArray!ubyte(header.oldType);
493 				file.rawWrite(buffer);
494 				saveUncompressed;
495 				break;
496 		}
497 	}
498 	override uint width() @nogc @safe @property const pure {
499 		import std.math : abs;
500 		if (fileVersionIndicator) {
501 			if (bitmapHeaderLength == Win2xBitmapHeader.sizeof + 4) {
502 				return abs(bitmapHeader.oldType.width);
503 			} else {
504 				return abs(bitmapHeader.newType.width);
505 			}
506 		}
507 		return header.oldType.width;
508 	}
509 	override uint height() @nogc @safe @property const pure {
510 		import std.math : abs;
511 		if (fileVersionIndicator) {
512 			if (bitmapHeaderLength == Win2xBitmapHeader.sizeof + 4) {
513 				return abs(bitmapHeader.oldType.height);
514 			} else {
515 				return abs(bitmapHeader.newType.height);
516 			}
517 		}
518 		return header.oldType.height;
519 	}
520 	override bool isIndexed() @nogc @safe @property const pure {
521 		if (fileVersionIndicator) {
522 			if (bitmapHeaderLength == Win2xBitmapHeader.sizeof + 4) {
523 				if (bitmapHeader.oldType.bitsPerPixel != 24)
524 					return true;
525 				else
526 					return false;
527 			} else {
528 				if (bitmapHeader.newType.bitsPerPixel <= 8)
529 					return true;
530 				else
531 					return false;
532 			}
533 		}
534 		return true;
535 	}
536 	override ubyte getBitdepth() @nogc @safe @property const pure {
537 		if (fileVersionIndicator) {
538 			if (bitmapHeaderLength == Win2xBitmapHeader.sizeof + 4) {
539 				return cast(ubyte)bitmapHeader.oldType.bitsPerPixel;
540 			} else {
541 				return cast(ubyte)bitmapHeader.newType.bitsPerPixel;
542 			}
543 		}
544 		return header.oldType.bitsPerPixel;
545 	}
546 	override ubyte getPaletteBitdepth() @nogc @safe @property const pure {
547 		if (fileVersionIndicator) {
548 			if (bitmapHeaderLength == Win2xBitmapHeader.sizeof + 4) {
549 				if (isIndexed)
550 					return 24;
551 			} else {
552 				if (isIndexed)
553 					return 32;
554 			}
555 		}
556 		return 0;
557 	}
558 	override uint getPixelFormat() @nogc @safe @property const pure {
559 		switch (getBitdepth()) {
560 			case 1:
561 				return PixelFormat.Indexed1Bit;
562 			case 4:
563 				return PixelFormat.Indexed4Bit;
564 			case 8:
565 				return PixelFormat.Indexed8Bit;
566 			case 16:
567 				if (headerExt.shortext.redMask == 0xF8000000 && headerExt.shortext.greenMask == 0x07E00000 && 
568 						headerExt.shortext.blueMask == 0x001F0000)
569 					return PixelFormat.RGB565 | (bitmapHeaderLength == 4 + Win3xBitmapHeader.sizeof + WinNTBitmapHeaderExt.sizeof ? 
570 							PixelFormat.BigEndian : 0);
571 				else
572 					return PixelFormat.RGBX5551 | (bitmapHeaderLength == 4 + Win3xBitmapHeader.sizeof + WinNTBitmapHeaderExt.sizeof ? 
573 							PixelFormat.BigEndian : 0);
574 			case 24:
575 				return PixelFormat.RGB888;
576 			case 32:
577 				return PixelFormat.ARGB8888;
578 			default:
579 				return PixelFormat.Undefined;
580 		}
581 	}
582 	override uint getPalettePixelFormat() @nogc @safe @property const pure {
583 		if (fileVersionIndicator) {
584 			if (bitmapHeaderLength == Win2xBitmapHeader.sizeof + 4) {
585 				if (isIndexed)
586 					return PixelFormat.RGB888;
587 			} else {
588 				if (isIndexed)
589 					return PixelFormat.XRGB8888;
590 			}
591 		}
592 		return PixelFormat.Undefined;
593 	}
594 }
595 
596 unittest {
597 	import vfile;
598 	{
599 		std.stdio.File testFile1 = std.stdio.File("./test/bmp/TRU256.BMP");
600 		std.stdio.File testFile2 = std.stdio.File("./test/bmp/TRU256_I.bmp");
601 		BMP test1 = BMP.load(testFile1);
602 		BMP test2 = BMP.load(testFile2);
603 		compareImages!true(test1, test2);
604 		VFile backup1, backup2;
605 		test1.save(backup1);
606 		test2.save(backup2);
607 		backup1.seek(0);
608 		backup2.seek(0);
609 		BMP test01 = BMP.load(backup1);
610 		BMP test02 = BMP.load(backup2);
611 		compareImages!true(test1, test01);
612 		compareImages!true(test2, test02);
613 	}
614 }