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 }