1 /// bmp impl for MemoryImage 2 module arsd.bmp; 3 4 import arsd.color; 5 6 //version = arsd_debug_bitmap_loader; 7 8 9 MemoryImage readBmp(string filename) { 10 import core.stdc.stdio; 11 12 FILE* fp = fopen((filename ~ "\0").ptr, "rb".ptr); 13 if(fp is null) 14 throw new Exception("can't open save file"); 15 scope(exit) fclose(fp); 16 17 void specialFread(void* tgt, size_t size) { 18 version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf("ofs: 0x%08x\n", cast(uint)ftell(fp)); } 19 fread(tgt, size, 1, fp); 20 } 21 22 return readBmpIndirect(&specialFread); 23 } 24 25 MemoryImage readBmp(in ubyte[] data) { 26 const(ubyte)[] current = data; 27 void specialFread(void* tgt, size_t size) { 28 while(size) { 29 if (current.length == 0) throw new Exception("out of bmp data"); // it's not *that* fatal, so don't throw RangeError 30 *cast(ubyte*)(tgt) = current[0]; 31 current = current[1 .. $]; 32 tgt++; 33 size--; 34 } 35 } 36 37 return readBmpIndirect(&specialFread); 38 } 39 40 MemoryImage readBmpIndirect(scope void delegate(void*, size_t) fread) { 41 uint read4() { uint what; fread(&what, 4); return what; } 42 ushort read2(){ ushort what; fread(&what, 2); return what; } 43 ubyte read1(){ ubyte what; fread(&what, 1); return what; } 44 45 void require1(ubyte t, size_t line = __LINE__) { 46 if(read1() != t) 47 throw new Exception("didn't get expected byte value", __FILE__, line); 48 } 49 void require2(ushort t) { 50 if(read2() != t) 51 throw new Exception("didn't get expected short value"); 52 } 53 void require4(uint t, size_t line = __LINE__) { 54 auto got = read4(); 55 //import std.conv; 56 if(got != t) 57 throw new Exception("didn't get expected int value " /*~ to!string(got)*/, __FILE__, line); 58 } 59 60 require1('B'); 61 require1('M'); 62 63 auto fileSize = read4(); // size of file in bytes 64 require2(0); // reserved 65 require2(0); // reserved 66 67 auto offsetToBits = read4(); 68 version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf("pixel data offset: 0x%08x\n", cast(uint)offsetToBits); } 69 70 auto sizeOfBitmapInfoHeader = read4(); 71 if (sizeOfBitmapInfoHeader < 12) throw new Exception("invalid bitmap header size"); 72 73 int width, height, rdheight; 74 75 if (sizeOfBitmapInfoHeader == 12) { 76 width = read2(); 77 rdheight = cast(short)read2(); 78 } else { 79 if (sizeOfBitmapInfoHeader < 16) throw new Exception("invalid bitmap header size"); 80 sizeOfBitmapInfoHeader -= 4; // hack! 81 width = read4(); 82 rdheight = cast(int)read4(); 83 } 84 85 height = (rdheight < 0 ? -rdheight : rdheight); 86 rdheight = (rdheight < 0 ? 1 : -1); // so we can use it as delta (note the inverted sign) 87 88 if (width < 1 || height < 1) throw new Exception("invalid bitmap dimensions"); 89 90 require2(1); // planes 91 92 auto bitsPerPixel = read2(); 93 switch (bitsPerPixel) { 94 case 1: case 2: case 4: case 8: case 16: case 24: case 32: break; 95 default: throw new Exception("invalid bitmap depth"); 96 } 97 98 /* 99 0 = BI_RGB 100 1 = BI_RLE8 RLE 8-bit/pixel Can be used only with 8-bit/pixel bitmaps 101 2 = BI_RLE4 RLE 4-bit/pixel Can be used only with 4-bit/pixel bitmaps 102 3 = BI_BITFIELDS 103 */ 104 uint compression = 0; 105 uint sizeOfUncompressedData = 0; 106 uint xPixelsPerMeter = 0; 107 uint yPixelsPerMeter = 0; 108 uint colorsUsed = 0; 109 uint colorsImportant = 0; 110 111 sizeOfBitmapInfoHeader -= 12; 112 if (sizeOfBitmapInfoHeader > 0) { 113 if (sizeOfBitmapInfoHeader < 6*4) throw new Exception("invalid bitmap header size"); 114 sizeOfBitmapInfoHeader -= 6*4; 115 compression = read4(); 116 sizeOfUncompressedData = read4(); 117 xPixelsPerMeter = read4(); 118 yPixelsPerMeter = read4(); 119 colorsUsed = read4(); 120 colorsImportant = read4(); 121 } 122 123 if (compression > 3) throw new Exception("invalid bitmap compression"); 124 if (compression == 1 && bitsPerPixel != 8) throw new Exception("invalid bitmap compression"); 125 if (compression == 2 && bitsPerPixel != 4) throw new Exception("invalid bitmap compression"); 126 127 version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf("compression: %u; bpp: %u\n", compression, cast(uint)bitsPerPixel); } 128 129 uint redMask; 130 uint greenMask; 131 uint blueMask; 132 uint alphaMask; 133 if (compression == 3) { 134 if (sizeOfBitmapInfoHeader < 4*4) throw new Exception("invalid bitmap compression"); 135 sizeOfBitmapInfoHeader -= 4*4; 136 redMask = read4(); 137 greenMask = read4(); 138 blueMask = read4(); 139 alphaMask = read4(); 140 } 141 // FIXME: we could probably handle RLE4 as well 142 143 // I don't know about the rest of the header, so I'm just skipping it. 144 version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf("header bytes left: %u\n", cast(uint)sizeOfBitmapInfoHeader); } 145 foreach (skip; 0..sizeOfBitmapInfoHeader) read1(); 146 147 if(bitsPerPixel <= 8) { 148 // indexed image 149 version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf("colorsUsed=%u; colorsImportant=%u\n", colorsUsed, colorsImportant); } 150 if (colorsUsed == 0 || colorsUsed > (1 << bitsPerPixel)) colorsUsed = (1 << bitsPerPixel); 151 auto img = new IndexedImage(width, height); 152 img.palette.reserve(1 << bitsPerPixel); 153 foreach(idx; 0 .. /*(1 << bitsPerPixel)*/colorsUsed) { 154 auto b = read1(); 155 auto g = read1(); 156 auto r = read1(); 157 auto reserved = read1(); 158 159 img.palette ~= Color(r, g, b); 160 } 161 while (img.palette.length < (1 << bitsPerPixel)) img.palette ~= Color.transparent; 162 163 // and the data 164 int bytesPerPixel = 1; 165 auto offsetStart = (rdheight > 0 ? 0 : width * height * bytesPerPixel); 166 int bytesRead = 0; 167 168 if (compression == 1) { 169 // this is complicated 170 assert(bitsPerPixel == 8); // always 171 int x = 0, y = (rdheight > 0 ? 0 : height-1); 172 void setpix (int v) { 173 if (x >= 0 && y >= 0 && x < width && y < height) img.data.ptr[y*width+x] = v&0xff; 174 ++x; 175 } 176 version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf("width=%d; height=%d; rdheight=%d\n", width, height, rdheight); } 177 for (;;) { 178 ubyte codelen = read1(); 179 ubyte codecode = read1(); 180 version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf("x=%d; y=%d; len=%u; code=%u\n", x, y, cast(uint)codelen, cast(uint)codecode); } 181 bytesRead += 2; 182 if (codelen == 0) { 183 // special code 184 if (codecode == 0) { 185 // end of line 186 version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf(" EOL\n"); } 187 while (x < width) setpix(1); 188 x = 0; 189 y += rdheight; 190 if (y < 0 || y >= height) break; // ooops 191 } else if (codecode == 1) { 192 version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf(" EOB\n"); } 193 // end of bitmap 194 break; 195 } else if (codecode == 2) { 196 // delta 197 int xofs = read1(); 198 int yofs = read1(); 199 version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf(" deltax=%d; deltay=%d\n", xofs, yofs); } 200 bytesRead += 2; 201 x += xofs; 202 y += yofs*rdheight; 203 if (y < 0 || y >= height) break; // ooops 204 } else { 205 version(arsd_debug_bitmap_loader) { import core.stdc.stdio; printf(" LITERAL: %u\n", cast(uint)codecode); } 206 // literal copy 207 while (codecode-- > 0) { 208 setpix(read1()); 209 ++bytesRead; 210 } 211 version(arsd_debug_bitmap_loader) if (bytesRead%2) { import core.stdc.stdio; printf(" LITERAL SKIP\n"); } 212 if (bytesRead%2) { read1(); ++bytesRead; } 213 assert(bytesRead%2 == 0); 214 } 215 } else { 216 while (codelen-- > 0) setpix(codecode); 217 } 218 } 219 } else if (compression == 2) { 220 throw new Exception("4RLE for bitmaps aren't supported yet"); 221 } else { 222 for(int y = height; y > 0; y--) { 223 if (rdheight < 0) offsetStart -= width * bytesPerPixel; 224 int offset = offsetStart; 225 while (bytesRead%4 != 0) { 226 read1(); 227 ++bytesRead; 228 } 229 bytesRead = 0; 230 for(int x = 0; x < width; x++) { 231 auto b = read1(); 232 ++bytesRead; 233 if(bitsPerPixel == 8) { 234 img.data[offset++] = b; 235 } else if(bitsPerPixel == 4) { 236 img.data[offset++] = (b&0xf0) >> 4; 237 x++; 238 if(offset == img.data.length) 239 break; 240 img.data[offset++] = (b&0x0f); 241 } else if(bitsPerPixel == 2) { 242 img.data[offset++] = (b & 0b11000000) >> 6; 243 x++; 244 if(offset == img.data.length) 245 break; 246 img.data[offset++] = (b & 0b00110000) >> 4; 247 x++; 248 if(offset == img.data.length) 249 break; 250 img.data[offset++] = (b & 0b00001100) >> 2; 251 x++; 252 if(offset == img.data.length) 253 break; 254 img.data[offset++] = (b & 0b00000011) >> 0; 255 } else if(bitsPerPixel == 1) { 256 foreach(lol; 0 .. 8) { 257 img.data[offset++] = (b & (1 << lol)) >> (7 - lol); 258 x++; 259 if(offset == img.data.length) 260 break; 261 } 262 x--; // we do this once too many times in the loop 263 } else assert(0); 264 // I don't think these happen in the wild but I could be wrong, my bmp knowledge is somewhat outdated 265 } 266 if (rdheight > 0) offsetStart += width * bytesPerPixel; 267 } 268 } 269 270 return img; 271 } else { 272 if (compression != 0) throw new Exception("invalid bitmap compression"); 273 // true color image 274 auto img = new TrueColorImage(width, height); 275 276 // no palette, so straight into the data 277 int offsetStart = width * height * 4; 278 int bytesPerPixel = 4; 279 for(int y = height; y > 0; y--) { 280 offsetStart -= width * bytesPerPixel; 281 int offset = offsetStart; 282 int b = 0; 283 foreach(x; 0 .. width) { 284 if(compression == 3) { 285 ubyte[8] buffer; 286 assert(bitsPerPixel / 8 < 8); 287 foreach(lol; 0 .. bitsPerPixel / 8) { 288 if(lol >= buffer.length) 289 throw new Exception("wtf"); 290 buffer[lol] = read1(); 291 b++; 292 } 293 294 ulong data = *(cast(ulong*) buffer.ptr); 295 296 auto blue = data & blueMask; 297 auto green = data & greenMask; 298 auto red = data & redMask; 299 auto alpha = data & alphaMask; 300 301 if(blueMask) 302 blue = blue * 255 / blueMask; 303 if(greenMask) 304 green = green * 255 / greenMask; 305 if(redMask) 306 red = red * 255 / redMask; 307 if(alphaMask) 308 alpha = alpha * 255 / alphaMask; 309 else 310 alpha = 255; 311 312 img.imageData.bytes[offset + 2] = cast(ubyte) blue; 313 img.imageData.bytes[offset + 1] = cast(ubyte) green; 314 img.imageData.bytes[offset + 0] = cast(ubyte) red; 315 img.imageData.bytes[offset + 3] = cast(ubyte) alpha; 316 } else { 317 assert(compression == 0); 318 319 if(bitsPerPixel == 24 || bitsPerPixel == 32) { 320 img.imageData.bytes[offset + 2] = read1(); // b 321 img.imageData.bytes[offset + 1] = read1(); // g 322 img.imageData.bytes[offset + 0] = read1(); // r 323 if(bitsPerPixel == 32) { 324 img.imageData.bytes[offset + 3] = read1(); // a 325 b++; 326 } else { 327 img.imageData.bytes[offset + 3] = 255; // a 328 } 329 b += 3; 330 } else { 331 assert(bitsPerPixel == 16); 332 // these are stored xrrrrrgggggbbbbb 333 ushort d = read1(); 334 d |= cast(ushort)read1() << 8; 335 // we expect 8 bit numbers but these only give 5 bits of info, 336 // therefore we shift left 3 to get the right stuff. 337 img.imageData.bytes[offset + 0] = (d & 0b0111110000000000) >> (10-3); 338 img.imageData.bytes[offset + 1] = (d & 0b0000001111100000) >> (5-3); 339 img.imageData.bytes[offset + 2] = (d & 0b0000000000011111) << 3; 340 img.imageData.bytes[offset + 3] = 255; // r 341 b += 2; 342 } 343 } 344 345 offset += bytesPerPixel; 346 } 347 348 int w = b%4; 349 if(w) 350 for(int a = 0; a < 4-w; a++) 351 read1(); // pad until divisible by four 352 } 353 354 355 return img; 356 } 357 358 assert(0); 359 } 360 361 void writeBmp(MemoryImage img, string filename) { 362 import core.stdc.stdio; 363 FILE* fp = fopen((filename ~ "\0").ptr, "wb".ptr); 364 if(fp is null) 365 throw new Exception("can't open save file"); 366 scope(exit) fclose(fp); 367 368 void write4(uint what) { fwrite(&what, 4, 1, fp); } 369 void write2(ushort what){ fwrite(&what, 2, 1, fp); } 370 void write1(ubyte what) { fputc(what, fp); } 371 372 int width = img.width; 373 int height = img.height; 374 ushort bitsPerPixel; 375 376 ubyte[] data; 377 Color[] palette; 378 379 // FIXME we should be able to write RGBA bitmaps too, though it seems like not many 380 // programs correctly read them! 381 382 if(auto tci = cast(TrueColorImage) img) { 383 bitsPerPixel = 24; 384 data = tci.imageData.bytes; 385 // we could also realistically do 16 but meh 386 } else if(auto pi = cast(IndexedImage) img) { 387 // FIXME: implement other bpps for more efficiency 388 /* 389 if(pi.palette.length == 2) 390 bitsPerPixel = 1; 391 else if(pi.palette.length <= 16) 392 bitsPerPixel = 4; 393 else 394 */ 395 bitsPerPixel = 8; 396 data = pi.data; 397 palette = pi.palette; 398 } else throw new Exception("I can't save this image type " ~ img.classinfo.name); 399 400 ushort offsetToBits; 401 if(bitsPerPixel == 8) 402 offsetToBits = 1078; 403 if (bitsPerPixel == 24 || bitsPerPixel == 16) 404 offsetToBits = 54; 405 else 406 offsetToBits = cast(ushort)(54 + 4 * 1 << bitsPerPixel); // room for the palette... 407 408 uint fileSize = offsetToBits; 409 if(bitsPerPixel == 8) 410 fileSize += height * (width + width%4); 411 else if(bitsPerPixel == 24) 412 fileSize += height * ((width * 3) + (!((width*3)%4) ? 0 : 4-((width*3)%4))); 413 else assert(0, "not implemented"); // FIXME 414 415 write1('B'); 416 write1('M'); 417 418 write4(fileSize); // size of file in bytes 419 write2(0); // reserved 420 write2(0); // reserved 421 write4(offsetToBits); // offset to the bitmap data 422 423 write4(40); // size of BITMAPINFOHEADER 424 425 write4(width); // width 426 write4(height); // height 427 428 write2(1); // planes 429 write2(bitsPerPixel); // bpp 430 write4(0); // compression 431 write4(0); // size of uncompressed 432 write4(0); // x pels per meter 433 write4(0); // y pels per meter 434 write4(0); // colors used 435 write4(0); // colors important 436 437 // And here we write the palette 438 if(bitsPerPixel <= 8) 439 foreach(c; palette[0..(1 << bitsPerPixel)]){ 440 write1(c.b); 441 write1(c.g); 442 write1(c.r); 443 write1(0); 444 } 445 446 // And finally the data 447 448 int bytesPerPixel; 449 if(bitsPerPixel == 8) 450 bytesPerPixel = 1; 451 else if(bitsPerPixel == 24) 452 bytesPerPixel = 4; 453 else assert(0, "not implemented"); // FIXME 454 455 int offsetStart = cast(int) data.length; 456 for(int y = height; y > 0; y--) { 457 offsetStart -= width * bytesPerPixel; 458 int offset = offsetStart; 459 int b = 0; 460 foreach(x; 0 .. width) { 461 if(bitsPerPixel == 8) { 462 write1(data[offset]); 463 b++; 464 } else if(bitsPerPixel == 24) { 465 write1(data[offset + 2]); // blue 466 write1(data[offset + 1]); // green 467 write1(data[offset + 0]); // red 468 b += 3; 469 } else assert(0); // FIXME 470 offset += bytesPerPixel; 471 } 472 473 int w = b%4; 474 if(w) 475 for(int a = 0; a < 4-w; a++) 476 write1(0); // pad until divisible by four 477 } 478 } 479 480 /+ 481 void main() { 482 import arsd.simpledisplay; 483 //import std.file; 484 //auto img = readBmp(cast(ubyte[]) std.file.read("/home/me/test2.bmp")); 485 auto img = readBmp("/home/me/test2.bmp"); 486 import std.stdio; 487 writeln((cast(Object)img).toString()); 488 displayImage(Image.fromMemoryImage(img)); 489 //img.writeBmp("/home/me/test2.bmp"); 490 } 491 +/