StephiCpp
StephiCpp

Reputation: 43

C++: Grayscale bitmap header and live painting + opencv image processing

I am trying to display live images coming from a monochrome camera (Adimec N5A/CXP, with GenIcam standard).

From an example coming from the supplier (but in RGB 24), I am more or less able to display the image but the color rendering is very strange (colors and shadows instead of grayscale). I guess I did something wrong in the bitmap header declaration:

    bitmapInfo = (LPBITMAPINFO)malloc(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD));
    bitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bitmapInfo->bmiHeader.biPlanes = 1;
    bitmapInfo->bmiHeader.biBitCount = 8; // 24
    bitmapInfo->bmiHeader.biCompression = BI_RGB;
    bitmapInfo->bmiHeader.biSizeImage = 0;
    bitmapInfo->bmiHeader.biXPelsPerMeter = 0;
    bitmapInfo->bmiHeader.biYPelsPerMeter = 0;
    bitmapInfo->bmiHeader.biClrUsed = 256;
    bitmapInfo->bmiHeader.biClrImportant = 0;
    bitmapInfo->bmiHeader.biWidth = (LONG)width;
    bitmapInfo->bmiHeader.biHeight = -(LONG)height;
    /*
    RGBQUAD* bmiColors = (RGBQUAD*)(bitmapInfo->bmiColors);
    for (size_t index = 0; index < 256; ++index)
    {
        bmiColors[index].rgbBlue = (BYTE)index;
        bmiColors[index].rgbGreen = (BYTE)index;
        bmiColors[index].rgbRed = (BYTE)index;
        bmiColors[index].rgbReserved = 0;
    }
    */

I found in bmiColors field of BITMAPINFO structure that the 'biClrUsed' should be set to 256. Then I do not know if I need to write a block to describe 'bmiColors'. I would like to use only one byte per pixel instead of the r,g and b components.

Then further in the program (in the function "OnPaint"), it uses the function "SetDIBitsToDevice" to display in a window previously created. The image pointer is first retrieved:

unsigned char *imagePtr = liveState.currentBuffer->getInfo<unsigned char *>(liveState.grabber, gc::BUFFER_INFO_BASE);

Then the image is displayed:

::SetDIBitsToDevice(dc, 0, 0, (DWORD)liveState.width, (DWORD)liveState.height, 0, 0, 0, (UINT)liveState.height, imagePtr, liveState.bitmapInfo, DIB_RGB_COLORS);

I don't know what to put instead of DIB_RGB_COLORS as the last parameter. I only found another value for this parameter that is DIB_PAL_COLORS. I guess there should be an option for grayscale?

This is the first step of my program... if you have any suggestion on how to push the image pointer into an opencv container I would also be very happy :-).

Many thanks in advance !

Upvotes: 4

Views: 835

Answers (1)

Dan Mašek
Dan Mašek

Reputation: 19071

It seems you were quite close. The way to display grayscale images is to use a palette. This is simply 256 RGB entries representing all the shades between black and white:

std::vector<RGBQUAD> pal(256);
for (int32_t i(0); i < 256; ++i) {
    pal[i].rgbRed = pal[i].rgbGreen = pal[i].rgbBlue = i;
    pal[i].rgbReserved = 0;
}

First of all, you need to allocate enough memory to hold BITMAPINFOHEADER as well as 256 RGBQUAD entries defining the palette to use.

int32_t const bmi_size(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256);

Allocate the structure. I put it on stack using _alloca, so I don't need to worry about cleanup.

BITMAPINFO* bmi(static_cast<BITMAPINFO*>(alloca(bmi_size)));

You need to set the following members of BITMAPINFOHEADER, the rest can be left as zeros.

bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi->bmiHeader.biWidth = static_cast<LONG>(width);
bmi->bmiHeader.biHeight = static_cast<LONG>(-height);
bmi->bmiHeader.biPlanes = 1;
bmi->bmiHeader.biBitCount = 8;
bmi->bmiHeader.biCompression = BI_RGB;

Note: Since we have a complete 256 entry palette, biClrUsed can be left set to 0. From docs:

If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member...

Next, set up the palette (that's basically the bit of your code that's commented out).

for (uint32_t i(0); i < 256; ++i) {
    if (pal.size() > i) {
        bmi->bmiColors[i] = pal[i];
    } else {
        bmi->bmiColors[i].rgbRed
            = bmi->bmiColors[i].rgbGreen
            = bmi->bmiColors[i].rgbBlue
            = bmi->bmiColors[i].rgbReserved = 0;
    }
}

Note: The above code is from a generic paletted image rendering function. For smaller palettes it fills the unused colours with black. I suppose this could be refactored to use fewer entries along with biClrUsed set to appropriate value.

Now the bitmap header is ready. In your case, the call to SetDIBitsToDevice would still use DIB_RGB_COLORS since "The color table contains literal RGB values."

I use CreateDIBitmap to create a DDB, which I can later render using BitBlt.

HBITMAP bitmap = ::CreateDIBitmap(dc
    , &bmi->bmiHeader
    , CBM_INIT
    , data // Pointer to raw pixel data
    , bmi
    , DIB_RGB_COLORS);

Upvotes: 1

Related Questions