1

So I am having a problem figuring out exactly what is going wrong with trying to read any 24bpp bitmap image and re-create it in the same folder. It works with one image, but not two others that I have tested it with. When reading from the bitmap, I use the information found in the header itself. It could be said I have three questions. 1) Am I reading from bitmap correctly? 2) Am I calculating/using/writing the padding correctly? 3) Am I outputting correctly?. Third is confirmed no with this image and its output.

Also the reason for allocating an 2d array for the Images is so that I can latter try to rotate bitmaps by 90 degrees.

Unfortunately I cannot post images, the image taken is from here, the rgb_24bpp.bmp http://pyglet.googlecode.com/svn/trunk/tests/image/

Here is the code used for reading from the image and to calculate the padding.

ifstream myBitmap("rgb_24bpp.bmp", ios::binary | ios::beg);

// Get the total file size in bytes, testing file access
begin = myBitmap.tellg();
myBitmap.seekg(0, ios::end);
end = myBitmap.tellg();

// Actually reading image file
myBitmap.seekg( 0, ios::beg);
myBitmap.read((char*)FileHeader, sizeof(BITMAPFILEHEADER));
myBitmap.read((char*)InfoHeader, sizeof(BITMAPINFOHEADER));
test = myBitmap.tellg();

RGBQUAD ** Image = new RGBQUAD*[InfoHeader->biWidth];
for (int i = 0; i < InfoHeader->biWidth; ++i) {
    Image[i] = new RGBQUAD[InfoHeader->biHeight];
}
int pitch = InfoHeader->biWidth * 3;

if (pitch % 4 != 0)
{
    pitch += 4 - (pitch % 4);
}

int padding = pitch - (InfoHeader->biWidth * 3);
cout << "padding: " << padding << endl;

myBitmap.seekg(FileHeader->bfOffBits, ios::beg);
for (int i = InfoHeader->biHeight; i > 0; --i) {
    for (int j = 0; j < InfoHeader->biWidth; ++j) {
        myBitmap.read((char*)&Image[j][i], sizeof(RGBQUAD));
    }
    if (padding != 0) myBitmap.read(PadBuffer, padding);
}
myBitmap.close();

begin/end/test are all of streampos and printed on console for debugging. And this is the code used to output/recreate the image.

ofstream BitmapOut("Output.bmp");
BitmapOut.write((char*)FileHeader, sizeof(BITMAPFILEHEADER));
BitmapOut.write((char*)InfoHeader, sizeof(BITMAPINFOHEADER));
for (int i = InfoHeader->biHeight; i > 0; --i) {
    for (int j = 0; j < InfoHeader->biWidth; ++j) {
        BitmapOut.write((char*)&Image[j][i], sizeof(RGBQUAD));
    }
    if (padding != 0) BitmapOut.write("\0\0\0\0\0\0\0", padding);
}

BitmapOut.close();

I have confirmed that both headers are indeed correct and can pull data from them properly in 3 different tests. Utilizing this guys code (sorry, this project is non-commercial and self-study only). reading a .bmp file in c++

With the exception of commenting out the reserved in the RGBQUAD and making effectively a RGBTRI instead.

Community
  • 1
  • 1
Draxe
  • 13
  • 3
  • Is there some reason that you allocated space on the heap individually for every pixel? – Logicrat Jul 23 '14 at 21:20
  • I do it this way for more simplicity/understanding, after looking here http://stackoverflow.com/questions/936687/how-do-i-declare-a-2d-array-in-c-using-new I realize this is heavy and inefficient but it simplifies using the 2d array for myself. – Draxe Jul 23 '14 at 21:48
  • I have worked with BMP files by making a few test files using tools such as MS PAINT, and creating solid white or solid black files with a single different color pixel in each corner. That makes it easy to step thru your code with a debugger and make sure you have the row alignment right. Row alignment has been the source of most of my problems when working with BMP. – Logicrat Jul 23 '14 at 22:18
  • Which input file your code works fine? – Cahit Gungor Jul 23 '14 at 22:24
  • Yes, your calculation for `padding` looks correct. Are you sure the images are 24-bit and not 32-bit? – Mark Ransom Jul 23 '14 at 22:50

1 Answers1

0

You can do it like this.. Also, if you don't want to make a temporary array to copy the pixels, you can easily read, seek, read, seek, etc.. OR you can just read all at once. There are so many ways to read a bitmap and be efficient/inefficient. It's up to you how you want to do it. Another efficient way to do it is to SAVE the BitmapInfoHeader and BitmapFileHeader. Then when you decide to write the bitmap to the disk, just write them headers first then the pixels. WAY faster and easier.. I did NOT do that in this example. I'll leave that up to you to figure out.

Here is a sample code I wrote for answering your question. I prefer to use 1-dimensional arrays.

#include <fstream>
#include <cstring>
#include <windows.h>

typedef struct
{
    unsigned int width, height;
    unsigned char* pixels;
} Bitmap;

void InitBitmap(Bitmap* bmp)
{
    if (bmp)
    {
        bmp->width = 0;
        bmp->height = 0;
        bmp->pixels = NULL;
    }
}

void FreeBitmap(Bitmap* bmp)
{
    if (bmp && bmp->pixels)
    {
        bmp->width = 0;
        bmp->height = 0;
        delete[] bmp->pixels;
        bmp->pixels = NULL;
    }
}

bool ReadBitmap(const char* FilePath, Bitmap* bmp)
{
    std::fstream hFile(FilePath, std::ios::in | std::ios::binary);

    if (!bmp || !hFile.is_open())
        return false;

    BITMAPINFO Info;
    BITMAPFILEHEADER Header;
    memset(&Info, 0, sizeof(Info));
    memset(&Header, 0, sizeof(Header));

    hFile.read((char*)&Header, sizeof(Header));
    hFile.read((char*)&Info.bmiHeader, sizeof(Info.bmiHeader));

    bmp->width = Info.bmiHeader.biWidth;
    bmp->height = Info.bmiHeader.biHeight < 0 ? -Info.bmiHeader.biHeight : Info.bmiHeader.biHeight;
    size_t size = Info.bmiHeader.biSizeImage;

    bmp->pixels = new unsigned char[size];
    hFile.seekg(Header.bfOffBits, std::ios::beg);
    hFile.read((char*)bmp->pixels, size);
    hFile.close();

    return true;
}

bool WriteBitmap(const char* FilePath, Bitmap* bmp)
{
    std::fstream hFile(FilePath, std::ios::out | std::ios::binary);

    if (!bmp || !hFile)
        return false;

    BITMAPINFO Info;
    BITMAPFILEHEADER Header;
    memset(&Info, 0, sizeof(Info));
    memset(&Header, 0, sizeof(Header));

    Info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    Info.bmiHeader.biWidth = bmp->width;
    Info.bmiHeader.biHeight = bmp->height;
    Info.bmiHeader.biPlanes = 1;
    Info.bmiHeader.biBitCount = 24;
    Info.bmiHeader.biCompression = BI_RGB;
    Info.bmiHeader.biSizeImage = 0;
    Header.bfType = 0x4D42;
    Header.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
    size_t size = (((24 * bmp->width + 31) & ~31) / 8) * bmp->height;

    hFile.write((char*)&Header, sizeof(Header));
    hFile.write((char*)&Info.bmiHeader, sizeof(Info.bmiHeader));
    hFile.write((char*)bmp->pixels, size);
    hFile.close();
    return true;
}

int main()
{
    Bitmap bmp;
    InitBitmap(&bmp);

    ReadBitmap("C:/Users/Brandon/Desktop/foo.bmp", &bmp);
    WriteBitmap("C:/Users/Brandon/Desktop/foo2.bmp", &bmp);

    FreeBitmap(&bmp);
}
Brandon
  • 22,723
  • 11
  • 93
  • 186