Kemal
Kemal

Reputation: 889

WinAPI/GDI: How to use GetDIBits() to get color table synthesized for a bitmap?

I find it difficult to understand the excerpt below from MSDN site on GetDIBits() function:

If lpvBits is NULL and the bit count member of BITMAPINFO is initialized to zero, GetDIBits fills in a BITMAPINFOHEADER structure or BITMAPCOREHEADER without the color table. This technique can be used to query bitmap attributes.

Question-1: What is meant by "the bit count member of BITMAPINFO"? Does it mean some_bmi.bmiHeader.biBitCount?

Question-2: What is meant by "GetDIBits fills in a BITMAPINFOHEADER structure or BITMAPCOREHEADER without the color table"? What color table is there to fill in those structures? None of them seems to have a member related to the color table. Is this about the array some_bmi.bmiColors?

Question-3: Is there a way to use GetDIBits() to get the color table(i.e. the array mapping indexes to colors) for a bitmap?


EDIT:

From the comments so far, it looks like breaking the question down into smaller parts was not effective. I will try it another way.

This is what I understand from the part I quoted from MSDN at the beginning:

Assuming the function call is GetDIBits(hdc, hbmp, uStartScan, cScanLines, lpvBits, lpbi, uUsage); if lpvBits is NULL and lpvBits->bmiHeader.biBitCount is initialized to zero, GetDIBits() fills in lpbi->bmiHeader only and lpbi->bmiColors is not modified.

Is this the correct way to understand it? And if so, is there a way to get GetDIBits() to fill in lpbi->bmiColors, such as initializing lpvBits->bmiHeader.biBitCount to bit-depth of the bitmap?


I tried testing Question-1's assumption as follows but GetDIBits() fails in that case:

void test(HWND hWnd) {
    // get a memory DC
    HDC hdc = GetDC(hWnd);
    HDC hdcmem = CreateCompatibleDC(hdc); // has 1x1 mono bitmap selected 
                                          // into it initially by default
    // select a 16x16 mono bmp into it
    const int bmp_h = 16, bmp_w = 16;
    const int bits_per_px = 1;
    HBITMAP hbmp = CreateCompatibleBitmap(hdcmem, bmp_h, bmp_w); // 16x16 mono bitmap
    HGDIOBJ hOldBmp = SelectObject(hdcmem, hbmp);

    // initialize BITMAPINFO ptr
    // (make sure to allocate a buffer large enough for 2 RGBQUADs 
    // in case color table is retured by GetDIBits() call)
    const int bmi_buf_sz =
        sizeof(BITMAPINFO) + sizeof(RGBQUAD) * (1 << bits_per_px); // 2 + 1(extra) RGBQUADs allocated for pbmi->bimColors
    BYTE* p_bmi_buf = new BYTE[bmi_buf_sz];
    BITMAPINFO* pbmi = reinterpret_cast<BITMAPINFO*>(p_bmi_buf);
    ZeroMemory(pbmi, bmi_buf_sz);

    // populate BITMAPINFO
    pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    pbmi->bmiHeader.biBitCount = 1; // set to 1 just to see if GetDIBits()
                                    // fills in pbmi->bmiColors too
                                   // (when set to 0, only pbmi->bmiHeader is filled)
    if(!GetDIBits(hdcmem, hbmp,
                  0, (UINT)bmp_h,
                  NULL, pbmi, DIB_PAL_COLORS)) {
        MessageBox(hWnd, L"GetDIBits() failed!", NULL, MB_OK);
    }

    // clean-up
    delete[] p_bmi_buf;
    SelectObject(hdcmem, hOldBmp); // push hbmp out
    DeleteObject(hbmp);
    DeleteDC(hdcmem);
    ReleaseDC(hWnd, hdc);
}

Upvotes: 1

Views: 2507

Answers (2)

Kemal
Kemal

Reputation: 889

Although the accepted answer covers the details, this is more of a direct answer to the questions in the OP.

Assuming the function call is GetDIBits(hdc, hbmp, uStartScan, cScanLines, lpvBits, lpbi, uUsage);

Question-1:

That is correct. "The bit count member of BITMAPINFO" refers to lpbi->bmiHeader.biBitCount.

Question-2:

When GetDIBits() is called to get DIB bits(i.e. with a non-null lpvBits and an appropriately initialized lpbi->bmiHeader), lpbi->bmiColors also gets filled by the function with the color table(if bit depth is less then 24 bpp). Unfortunately, this is not clear in the documentation of the function. With that in mind, what the quoted part means is that, when lpvBits is NULL and lpbi->bmiHeader.biBitCount is zero, the function fills lpbi->bmiHeader only, and does not modify lpbi->bimColor(as opposed to when caling the function to get DIB bits).

Question-3:

You can get the function to return color table(for bitmaps with 8-bbp or less) in lpbi->bmiColors by calling it with a non-null lpvBits and an appropriately initialized lpbi->bmiHeader. IOW, when you call the function to get DIB bits as usual, it fills lpbi->bmiColors as well.

Question in EDIT section:

If lpvBits is NULL and lpvBits->bmiHeader.biBitCount is initialized to zero, GetDIBits() fills in lpbi->bmiHeader only and lpbi->bmiColors is not modified.

Is this the correct way to understand it?

Yes, that is correct.

And if so, is there a way to get GetDIBits() to fill in lpbi->bmiColors, such as initializing lpvBits->bmiHeader.biBitCount to bit-depth of the bitmap?

Yes, there is a way to get the function to return the color table, but as explained in answer to Q2, initializing lpvBits->bmiHeader.biBitCount to bit-depth of the bitmap alone is not enough. All members of lpvBits->bmiHeader must be appropriately initialized and lpvBits must be non-null. This is basically the same as calling the function to get the DIB bits.

Upvotes: 1

Barmak Shemirani
Barmak Shemirani

Reputation: 31669

The easiest way to get the color table is with GetDibColorTable:

HDC memdc = CreateCompatibleDC(NULL);
HBITMAP oldbmp = (HBITMAP)SelectObject(memdc, hbitmap);
int ncolors = 1 << bm.bmBitsPixel;
std::vector<RGBQUAD> rgb(ncolors);
if(ncolors == GetDIBColorTable(memdc, 0, ncolors, &rgb[0]))
{
    //success!
}
SelectObject(memdc, oldbmp);
DeleteDC(memdc);

Back to your question: GetDIBits expects pbmi to contain all zeros (except for bmiHeader.biSize member). So pbmi->bmiHeader.biBitCount should be zero.

//pbmi->bmiHeader.biBitCount = 1; <<= comment out this line

This should work; however, as it is stated in documentation, this will only fill the info header, not the color table. To get the color table you have to make another call to GetDIBits with enough allocation for the dib bits.

Moreover DIB_PAL_COLORS will return palette index array (I am not sure what you can do with that). You may want to use DIB_RGB_COLORS flag which will return actual colors when a color table is present.

Try this with a bitmap loaded from file:

HBITMAP hbitmap = (HBITMAP)LoadImage(0, L"8bit.bmp",
    IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE);

BITMAP bm;
GetObject(hbitmap, sizeof(bm), &bm);

//don't continue for hi color bitmaps
if(bm.bmBitsPixel > 8) return;

int ncolors = 1 << bm.bmBitsPixel;
HDC memdc = CreateCompatibleDC(NULL);
int bmpinfo_size = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * ncolors;
std::vector<BYTE> buf(bmpinfo_size);
BITMAPINFO* bmpinfo = (BITMAPINFO*)buf.data();
bmpinfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
if(!GetDIBits(memdc, hbitmap, 0, bm.bmHeight, NULL, bmpinfo, DIB_RGB_COLORS))
{
    DWORD err = GetLastError();
    //...
}

int dibsize = ((bm.bmWidth * bm.bmBitsPixel + 31) / 32) * 4 * bm.bmHeight;
std::vector<BYTE> dib(dibsize);
if(!GetDIBits(memdc, hbitmap, 0, (UINT)bm.bmHeight, &dib[0], bmpinfo, DIB_RGB_COLORS))
{
    DWORD err = GetLastError();
    //...
}

Now bmpinfo->bmiColors should contain the same values as rgb array shown earlier.


Possible confusion between BITMAPINFO and BITMAPINFOHEADER:

The above structures are declared as follows:

typedef struct tagBITMAPINFO {
    BITMAPINFOHEADER    bmiHeader;
    RGBQUAD             bmiColors[1];
} BITMAPINFO, FAR *LPBITMAPINFO, *PBITMAPINFO;

typedef struct tagBITMAPINFOHEADER{
        DWORD      biSize;
        LONG       biWidth;
        LONG       biHeight;
        WORD       biPlanes;
        WORD       biBitCount;
        DWORD      biCompression;
        DWORD      biSizeImage;
        LONG       biXPelsPerMeter;
        LONG       biYPelsPerMeter;
        DWORD      biClrUsed;
        DWORD      biClrImportant;
} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;

So BITMAPINFO does not have biBitCount member. But it does have bmiHeader.biBitCount member.

When you declare a BITMAPINFOHEADER variable, you have to set biSize member (that's Windows' idea of version control). When you declare a BITMAPINFO variable, you have to make sure it's BITMAPINFOHEADER is taken care of.

Note that most of the time you don't have to worry about palette. For example LoadImage will return a compatible bitmap (if you don't specify LR_CREATEDIBSECTION) and you can use that right away.

Upvotes: 1

Related Questions