reading more common bmp files

This commit is contained in:
Adam D. Ruppe 2013-08-29 21:38:49 -04:00
parent 6f5b0c18ee
commit cb59e42aba
1 changed files with 363 additions and 1 deletions

1
bmp.d
View File

@ -1 +0,0 @@
/home/me/program/bmp/bmp.d

363
bmp.d Normal file
View File

@ -0,0 +1,363 @@
import core.stdc.stdio;
import arsd.color;
MemoryImage readBmp(string filename) {
FILE* fp = fopen((filename ~ "\0").ptr, "rb".ptr);
if(fp is null)
throw new Exception("can't open save file");
scope(exit) fclose(fp);
uint read4() { uint what; fread(&what, 4, 1, fp); return what; }
ushort read2(){ ushort what; fread(&what, 2, 1, fp); return what; }
ubyte read1() { return cast(ubyte) fgetc(fp); }
void require1(ubyte t, size_t line = __LINE__) {
if(read1() != t)
throw new Exception("didn't get expected byte value", __FILE__, line);
}
void require2(ushort t) {
if(read2() != t)
throw new Exception("didn't get expected short value");
}
void require4(uint t, size_t line = __LINE__) {
auto got = read4();
//import std.conv;
if(got != t)
throw new Exception("didn't get expected int value " /*~ to!string(got)*/, __FILE__, line);
}
require1('B');
require1('M');
auto fileSize = read4(); // size of file in bytes
require2(0); // reserved
require2(0); // reserved
auto offsetToBits = read4();
auto sizeOfBitmapInfoHeader = read4();
auto width = read4();
auto height = read4();
require2(1); // planes
auto bitsPerPixel = read2();
/*
0 = BI_RGB
3 = BI_BITFIELDS
*/
auto compression = read4();
auto sizeOfUncompressedData = read4();
auto xPixelsPerMeter = read4();
auto yPixelsPerMeter = read4();
auto colorsUsed = read4();
auto colorsImportant = read4();
int additionalRead = 0;
uint redMask;
uint greenMask;
uint blueMask;
uint alphaMask;
if(compression == 3) {
redMask = read4();
greenMask = read4();
blueMask = read4();
alphaMask = read4();
additionalRead += 4 * 4;
}
// FIXME: we could probably handle RLE as well
// I don't know about the rest of the header, so I'm just skipping it.
// 40 is the size of the basic info header that I did read.
foreach(skip; 0 .. sizeOfBitmapInfoHeader - 40 - additionalRead)
read1();
if(bitsPerPixel <= 8) {
// indexed image
auto img = new IndexedImage(width, height);
foreach(idx; 0 .. (1 << bitsPerPixel)) {
auto b = read1();
auto g = read1();
auto r = read1();
auto reserved = read1();
img.palette ~= Color(r, g, b);
}
// and the data
int bytesPerPixel = 1;
auto offsetStart = width * height * bytesPerPixel;
for(int y = height; y > 0; y--) {
offsetStart -= width * bytesPerPixel;
int offset = offsetStart;
int bytesRead = 0;
for(int x = 0; x < width; x++) {
auto b = read1();
bytesRead++;
if(bitsPerPixel == 8) {
img.data[offset++] = b;
} else if(bitsPerPixel == 4) {
img.data[offset++] = (b&0xf0) >> 4;
x++;
if(offset == img.data.length)
break;
img.data[offset++] = (b&0x0f);
} else if(bitsPerPixel == 2) {
img.data[offset++] = (b & 0b11000000) >> 6;
x++;
if(offset == img.data.length)
break;
img.data[offset++] = (b & 0b00110000) >> 4;
x++;
if(offset == img.data.length)
break;
img.data[offset++] = (b & 0b00001100) >> 2;
x++;
if(offset == img.data.length)
break;
img.data[offset++] = (b & 0b00000011) >> 0;
} else if(bitsPerPixel == 1) {
foreach(lol; 0 .. 8) {
img.data[offset++] = (b & (1 << lol)) >> (7 - lol);
x++;
if(offset == img.data.length)
break;
}
x--; // we do this once too many times in the loop
} else assert(0);
// I don't think these happen in the wild but I could be wrong, my bmp knowledge is somewhat outdated
}
int w = bytesRead%4;
if(w)
for(int a = 0; a < 4-w; a++)
require1(0); // pad until divisible by four
}
return img;
} else {
// true color image
auto img = new TrueColorImage(width, height);
// no palette, so straight into the data
int offsetStart = width * height * 4;
int bytesPerPixel = 4;
for(int y = height; y > 0; y--) {
offsetStart -= width * bytesPerPixel;
int offset = offsetStart;
int b = 0;
foreach(x; 0 .. width) {
if(compression == 3) {
ubyte[8] buffer;
assert(bitsPerPixel / 8 < 8);
foreach(lol; 0 .. bitsPerPixel / 8) {
if(lol >= buffer.length)
throw new Exception("wtf");
buffer[lol] = read1();
b++;
}
ulong data = *(cast(ulong*) buffer.ptr);
auto blue = data & blueMask;
auto green = data & greenMask;
auto red = data & redMask;
auto alpha = data & alphaMask;
if(blueMask)
blue = blue * 255 / blueMask;
if(greenMask)
green = green * 255 / greenMask;
if(redMask)
red = red * 255 / redMask;
if(alphaMask)
alpha = alpha * 255 / alphaMask;
else
alpha = 255;
img.imageData.bytes[offset + 2] = cast(ubyte) blue;
img.imageData.bytes[offset + 1] = cast(ubyte) green;
img.imageData.bytes[offset + 0] = cast(ubyte) red;
img.imageData.bytes[offset + 3] = cast(ubyte) alpha;
} else {
assert(compression == 0);
if(bitsPerPixel == 24 || bitsPerPixel == 32) {
img.imageData.bytes[offset + 2] = read1(); // b
img.imageData.bytes[offset + 1] = read1(); // g
img.imageData.bytes[offset + 0] = read1(); // r
if(bitsPerPixel == 32) {
img.imageData.bytes[offset + 3] = read1(); // a
b++;
} else {
img.imageData.bytes[offset + 3] = 255; // a
}
b += 3;
} else {
assert(bitsPerPixel == 16);
// these are stored xrrrrrgggggbbbbb
ushort d = read1();
d |= cast(ushort)read1() << 8;
// we expect 8 bit numbers but these only give 5 bits of info,
// therefore we shift left 3 to get the right stuff.
img.imageData.bytes[offset + 0] = (d & 0b0111110000000000) >> (10-3);
img.imageData.bytes[offset + 1] = (d & 0b0000001111100000) >> (5-3);
img.imageData.bytes[offset + 2] = (d & 0b0000000000011111) << 3;
img.imageData.bytes[offset + 3] = 255; // r
b += 2;
}
}
offset += bytesPerPixel;
}
int w = b%4;
if(w)
for(int a = 0; a < 4-w; a++)
require1(0); // pad until divisible by four
}
return img;
}
assert(0);
}
void writeBmp(MemoryImage img, string filename) {
FILE* fp = fopen((filename ~ "\0").ptr, "wb".ptr);
if(fp is null)
throw new Exception("can't open save file");
scope(exit) fclose(fp);
void write4(uint what) { fwrite(&what, 4, 1, fp); }
void write2(ushort what){ fwrite(&what, 2, 1, fp); }
void write1(ubyte what) { fputc(what, fp); }
int width = img.width;
int height = img.height;
ushort bitsPerPixel;
ubyte[] data;
Color[] palette;
// FIXME we should be able to write RGBA bitmaps too, though it seems like not many
// programs correctly read them!
if(auto tci = cast(TrueColorImage) img) {
bitsPerPixel = 24;
data = tci.imageData.bytes;
// we could also realistically do 16 but meh
} else if(auto pi = cast(IndexedImage) img) {
// FIXME: implement other bpps for more efficiency
/*
if(pi.palette.length == 2)
bitsPerPixel = 1;
else if(pi.palette.length <= 16)
bitsPerPixel = 4;
else
*/
bitsPerPixel = 8;
data = pi.data;
palette = pi.palette;
} else throw new Exception("I can't save this image type " ~ img.classinfo.name);
ushort offsetToBits;
if(bitsPerPixel == 8)
offsetToBits = 1078;
if (bitsPerPixel == 24 || bitsPerPixel == 16)
offsetToBits = 54;
else
offsetToBits = cast(ushort)(54 + 4 * 1 << bitsPerPixel); // room for the palette...
uint fileSize = offsetToBits;
if(bitsPerPixel == 8)
fileSize += height * (width + width%4);
else if(bitsPerPixel == 24)
fileSize += height * ((width * 3) + (!((width*3)%4) ? 0 : 4-((width*3)%4)));
else assert(0, "not implemented"); // FIXME
write1('B');
write1('M');
write4(fileSize); // size of file in bytes
write2(0); // reserved
write2(0); // reserved
write4(offsetToBits); // offset to the bitmap data
write4(40); // size of BITMAPINFOHEADER
write4(width); // width
write4(height); // height
write2(1); // planes
write2(bitsPerPixel); // bpp
write4(0); // compression
write4(0); // size of uncompressed
write4(0); // x pels per meter
write4(0); // y pels per meter
write4(0); // colors used
write4(0); // colors important
// And here we write the palette
if(bitsPerPixel <= 8)
foreach(c; palette[0..(1 << bitsPerPixel)]){
write1(c.b);
write1(c.g);
write1(c.r);
write1(0);
}
// And finally the data
int bytesPerPixel;
if(bitsPerPixel == 8)
bytesPerPixel = 1;
else if(bitsPerPixel == 24)
bytesPerPixel = 4;
else assert(0, "not implemented"); // FIXME
int offsetStart = data.length;
for(int y = height; y > 0; y--) {
offsetStart -= width * bytesPerPixel;
int offset = offsetStart;
int b = 0;
foreach(x; 0 .. width) {
if(bitsPerPixel == 8) {
write1(data[offset]);
b++;
} else if(bitsPerPixel == 24) {
write1(data[offset + 2]); // blue
write1(data[offset + 1]); // green
write1(data[offset + 0]); // red
b += 3;
} else assert(0); // FIXME
offset += bytesPerPixel;
}
int w = b%4;
if(w)
for(int a = 0; a < 4-w; a++)
write1(0); // pad until divisible by four
}
}
/*
void main() {
import simpledisplay;
auto img = readBmp("/home/me/test2.bmp");
import std.stdio;
writeln((cast(Object)img).toString());
displayImage(Image.fromMemoryImage(img));
//img.writeBmp("/home/me/test2.bmp");
}
*/