Search code examples
c++bitmapmfcbitbltdib

MFC BitBlt and SetDIBits vs. SetBitmapBits


I have a bitmap stored as a BGRA array of bytes. This is the code I've been using to paint the bitmap:

CDC *dispDC = new CDC();
dispDC->CreateCompatibleDC(pDC);
CBitmap *dispBMP = new CBitmap();
dispBMP->CreateCompatibleBitmap(pDC, sourceImage->GetWidth(), sourceImage->GetHeight());
dispDC->SelectObject(this->dispBMP);

The actual copying of the pixels in the translatedImage array happens with this:

dispBMP->SetBitmapBits(sourceImage->GetArea() * 4, translatedImage);

Then after some more processing I call pDC->StretchBlt with dispDC as the source CDC. This works fine when logged in locally because the display is also set to 32bpp.

Once I log in with Remote Desktop, the display goes to 16bpp and the image is mangled. The culprit is SetBitmapBits; i.e. for it to work, I have to properly fill translatedImage with the 16bpp version of what I want to show. Rather than do this myself, I searched the documentation and found SetDIBits which sounds like it does what I want:

The SetDIBits function sets the pixels in a compatible bitmap (DDB) using the color data found in the specified DIB.

In my case, the DIB is the 32bpp RGBA array, and the DDB is dispBMP which I create with CreateCompatibleBitmap.

So instead of my call to SetBitmapBits, this is what I did:

BITMAPINFO info;
ZeroMemory(&info, sizeof(BITMAPINFO));
info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
info.bmiHeader.biBitCount = 32;
info.bmiHeader.biPlanes = 1;
info.bmiHeader.biCompression = BI_RGB;
info.bmiHeader.biSizeImage = sourceImage->GetArea()*4;
info.bmiHeader.biWidth = sourceImage->GetWidth();
info.bmiHeader.biHeight = sourceImage->GetHeight();
info.bmiHeader.biClrUsed = 0;

int r = SetDIBits(pDC->GetSafeHdc(), (HBITMAP)dispBMP,
                  0, sourceImage->GetHeight(), translatedImage, 
                  &info, DIB_PAL_COLORS);

However, r is always zero and, naturally, I get nothing but black in my window. What is wrong with the code?


Solution

  • Ross Ridge was correct in pointing out the code order mistake. However, this didn't solve the problem.

    The problem was in the parameters I was passing. I am new to C++ and MFC and often forget all the "operators" which can act on types to automatically convert them.

    Previously I had this:

    int r = SetDIBits(pDC->GetSafeHdc(), (HBITMAP)dispBMP,
                  0, sourceImage->GetHeight(), translatedImage, 
                  &info, DIB_PAL_COLORS);
    

    The correct call is this:

    int r = SetDIBits(*pDC, *dispBMP,
                  0, sourceImage->GetHeight(), translatedImage, 
                  &info, DIB_PAL_COLORS);
    

    (Note I pass dereferenced pointers in the first two parameters.) Everything else was correct, including the counter-intuitive DIB_PAL_COLORS flag for a bitmap which has not palette.

    After obviously missing some key points in the documentation I reread it and then found this which has sample code showing that I was simply passing the parameters incorrectly.