Mircea Mircea - 22 days ago 4
C++ Question

C++: Write BMP image format error on WINDOWS

I have the most strange problem here... I'm using the same code(copy-paste) from Linux in Windows to READ and WRITE and BMP image. And from some reason in Linux every thing works perfectly fine, but when I'm coming to Windows 10 from some I can't open that images and I've receive an error message how said something like this:


"It looks like we don't support this file format."


Do you have any idea what should I do? I will put the code below.

EDIT:

I've solved the padding problem and now it's write the images but they are completely white, any idea why? I've update the code also.

struct BMP {
int width;
int height;
unsigned char header[54];
unsigned char *pixels;
int size;
int row_padded;
};

void writeBMP(string filename, BMP image) {
string fileName = "Output Files\\" + filename;
FILE *out = fopen(fileName.c_str(), "wb");
fwrite(image.header, sizeof(unsigned char), 54, out);

unsigned char tmp;
for (int i = 0; i < image.height; i++) {
for (int j = 0; j < image.width * 3; j += 3) {
// Convert (B, G, R) to (R, G, B)
tmp = image.pixels[j];
image.pixels[j] = image.pixels[j + 2];
image.pixels[j + 2] = tmp;
}
fwrite(image.pixels, sizeof(unsigned char), image.row_padded, out);
}
fclose(out);
}

BMP readBMP(string filename) {
BMP image;
string fileName = "Input Files\\" + filename;
FILE *f = fopen(fileName.c_str(), "rb");

if (f == NULL)
throw "Argument Exception";

fread(image.header, sizeof(unsigned char), 54, f); // read the 54-byte header

// extract image height and width from header
image.width = *(int *) &image.header[18];
image.height = *(int *) &image.header[22];

image.row_padded = (image.width * 3 + 3) & (~3);
image.pixels = new unsigned char[image.row_padded];
unsigned char tmp;

for (int i = 0; i < image.height; i++) {
fread(image.pixels, sizeof(unsigned char), image.row_padded, f);
for (int j = 0; j < image.width * 3; j += 3) {
// Convert (B, G, R) to (R, G, B)
tmp = image.pixels[j];
image.pixels[j] = image.pixels[j + 2];
image.pixels[j + 2] = tmp;
}
}
fclose(f);
return image;

}


In my point of view this code should be cross-platform... But it's not... why?

Thanks for help

Answer

Check the header

The header must start with the following two signature bytes: 0x42 0x4D. If it's something different a third party application will think that this file doesn't contain a bmp picture despite the .bmp file extension.

The size and the way pixels are stored is also a little bit more complex than what you expect: you assume that the number of bits per pixels is 24 and no no compression is used. This is not guaranteed. If it's not the case, you might read more data than available, and corrupt the file when writing it back.

Furthermore, the size of the header depends also on the BMP version you are using, which you can detect using the 4 byte integer at offset 14.

Improve your code

When you load a file, check the signature, the bmp version, the number of bits per pixel and the compression. For debugging purpose, consider dumping the header to check it manually:

for (int i=0; i<54; i++) 
    cout << hex << image.header[i] << " ";` 
cout <<endl; 

Furthermore, when you fread() check that the number of bytes read correspond to the size you wanted to read, so to be sure that you're not working with uninitialized buffer data.

Edit:

Having checked the dump, it appears that the format is as expected. But verifying the padded size in the header with the padded size that you have calculated it appears that the error is here:

image.row_padded = (image.width * 3 + 3) & (~3);     // ok size of a single row rounded up to multiple of 4
image.pixels = new unsigned char[image.row_padded];  // oops !  A little short ? 

In fact you read row by row, but you only keep the last one in memory ! This is different of your first version, where you did read the full pixels of the picture.

Similarly, you write the last row repeated height time.

Reconsider your padding, working with the total padded size.

image.row_padded = (image.width * 3 + 3) & (~3);     // ok size of a single row rounded up to multiple of 4
image.size_padded = image.row_padded * image.height;  // padded full size
image.pixels = new unsigned char[image.size_padded];  // yeah ! 
if (fread(image.pixels, sizeof(unsigned char), image.size_padded, f) !=  image.size_padded) {
    cout << "Error: all bytes couldn't be read"<<endl; 
}
else { 
    ... // process the pixels as expected
}
...