2

I have a perfectly working code that creates 32bpp bitmap and I need to change it so that 8bpp bitmap is created.

Here's the piece of code that creates 32bpp bitmap, draws into it, then it creates a bitmap file and store it into the vector of bytes:

// prepare bitmap:
BYTE* bitmap_data = NULL;
HDC hDC = GetDC(NULL);
HDC memHDC = CreateCompatibleDC(hDC);
BITMAPINFO bmi;
memset(&bmi, 0, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = desiredWidth;                 // desiredWidth is 800
bmi.bmiHeader.biHeight = desiredHeight;               // desiredHeight is 202
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = (((desiredWidth * bmi.bmiHeader.biBitCount + 31) & ~31) >> 3) * desiredHeight;
HBITMAP bitmap = CreateDIBSection(hDC, &bmi, DIB_RGB_COLORS, (void**)&bitmap_data, NULL, NULL);
ReleaseDC(NULL, hDC);
DeleteDC(hDC);

... // drawing into bitmap

// prepare bitmap file header:
BITMAPFILEHEADER bf;
memset(&bf, 0, sizeof(BITMAPFILEHEADER));
bf.bfType = MAKEWORD('B', 'M');
bf.bfOffBits = sizeof(BITMAPFILEHEADER) + bmi.bmiHeader.biSize;
bf.bfSize = bf.bfOffBits + bmi.bmiHeader.biSizeImage;

// write bitmap file into the vector:
std::vector<BYTE> bitmapData;
bitmapData.insert(bitmapData.end(), (BYTE*)&bf, ((BYTE*)&bf) + sizeof(BITMAPFILEHEADER));
bitmapData.insert(bitmapData.end(), (BYTE*)&bmi.bmiHeader, ((BYTE*)&bmi.bmiHeader) + sizeof(BITMAPINFOHEADER));
bitmapData.insert(bitmapData.end(), bitmap_data, bitmap_data + bmi.bmiHeader.biSizeImage);

And later the vector is stored into the file:

std::ofstream of("picture.bmp", std::ofstream::out | std::ofstream::binary);
of.write((char*)&bitmapData[0], bitmapData.size());
of.close();

and here's the output image:

32bpp image

What I've tried:

First step was naturally replacing 32 with 8 in this line: bmi.bmiHeader.biBitCount = 32; which resulted into image filled with solid grey colour. Then based on this answer I made following changes:

BITMAPINFO bmi;
memset(&bmi, 0, sizeof(BITMAPINFO));

changed into:

struct BITMAPINFO256 {
    BITMAPINFOHEADER bmiHeader;
    RGBQUAD bmiColors[256];
} bmi;
memset(&bmi, 0, sizeof(BITMAPINFO256));

added this loop right before CreateDIBSection is called:

for (UINT i = 0; i < 256; i++) {
    bmi.bmiColors[i].rgbRed   = i;
    bmi.bmiColors[i].rgbGreen = i;
    bmi.bmiColors[i].rgbBlue  = i;
}

and when the bmi.bmiHeader is being written into the vector, the RGBQUAD array is included: so sizeof(BITMAPINFO256) expresses the size of the header.

The new code (full code here) produces this output:

8bpp image

Why the new image looks that way? What's going on there? What am I missing?

Any help will be appreciated.

Community
  • 1
  • 1
LihO
  • 41,190
  • 11
  • 99
  • 167
  • Use a debugger and start with a 2x2 bitmap so you don't get lost in the pixels. – Hans Passant Feb 13 '13 at 17:37
  • @HansPassant: If you don't see any reason why this would be happening, could you guess at least what could be the problem here? I'm stuck with this, can't really find out what's wrong here. Documentation of functions and structures that I use isn't helpful either and I haven't found any good tutorials that I would find some inspiration in. – LihO Feb 13 '13 at 17:43

3 Answers3

3

It looks like an alignment problem. Make sure you update bfOffBits in the BITMAPFILEHEADER so that it points to the first byte of the pixel data. (If you don't change it, then it probably points to the beginning of the palette.)

In other words, sizeof(RGBQUAD)*256 should be added here as well:

bf.bfOffBits = sizeof(BITMAPFILEHEADER) + bmi.bmiHeader.biSize;

Also makes sure the first scanline starts on a DWORD boundary. That is, its offset from the beginning of the file should be a multiple of four bytes. Likewise, each scanline should be padded out to a multiple of four bytes. (You may not see these problems if your widths are nice even numbers. It's good to have an odd-width image among your test cases.)

Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
  • Finally! Thank you, it was very well spotted indeed. Too bad that I can upvote only one time :) – LihO Feb 13 '13 at 18:09
  • @LihO, don't worry, I added another one for you. Sorry if my own answer wasted your time. – Mark Ransom Feb 13 '13 at 21:27
  • @MarkRansom: Wasted would be the time that I would spend looking for that mistake. Problem has been solved and that's what matters. Maybe some other programmers will deal with same problem in the future too and they will find this useful :) – LihO Feb 13 '13 at 21:33
  • If half an hour spent on Stack Overflow will save me few hours of debugging, then it's definitely worth it :) – LihO Feb 13 '13 at 21:46
0

You need to specify the size of the palette that you attached. Right now it's zero, so the palette is showing up as the first bunch of pixels in your image.

bmi.bmiHeader.biClrUsed = 256;
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
0

You need to generate a palette for your image. Each pixel in a 32bit image is stored as 8-bits for Alpha, Red, Green and Blue. Where as in a 8bit image, the value in each pixel an 8bit index into the palette.

Your for(i=0..255) { bmi.bmiColors[i].rgbRed = i; ....} code is generated an grey-scale palette.

If the whole image is coming out as grey then it sounds like an alignment error, from memory the width of a palettized image must be a multiple of 4.

Try saving a SMALL 256 colour (aka 8-bit image) from Paint and compare in a hex editor.

Anonymouse
  • 935
  • 9
  • 20