MFC BitBlt and SetDIBits vs. SetBitmapBits
Asked Answered
B

2

6

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?

Bedouin answered 3/7, 2014 at 8:21 Comment(1)
Have you checked input of SetDIBits, when you log in with Remote Desktop? For example, is it true, that dispBMP!=NULL in this case?Shocking
B
5

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.

Bedouin answered 2/9, 2014 at 15:37 Comment(4)
There was nothing wrong with the GetSafeHdc you were using, the problem was in casting the pointer to CBitmap dispBMP rather than the object itself *dispBMP. I'm curious why you made it a pointer in the first place rather than a local object?Denotation
@MarkRansom: dispBMP is a class member variable because I have found in my experience with C that allocating memory ends up often time being very expensive in the types of application I work on (data processing). So since this is a new project starting from scratch I'm being very careful to reuse large memory objects like bitmaps and bitmap byte arrays.Bedouin
That strategy isn't buying you much in this case. Do a sizeof(dispBMP) to see what I mean - it doesn't contain the bitmap, it holds a handle to the bitmap, which is allocated separately.Denotation
@MarkRansom: right. So if I lose that handle I need to create a new bitmap every time. Isn't CreateCompatibleBitmap the one allocating the memory? I don't show it in my snippet, but in the actual code I only call that if I know the image changed size or if dispBMP is NULL.Bedouin
L
7

According to the documentation for SetDIBits:

The bitmap identified by the hbmp parameter must not be selected into a device context when the application calls this function.

In your example code you select it into device context after creating it, so presumably that's why SetDIBits is failing.

Lysin answered 24/8, 2014 at 1:7 Comment(1)
This is not the reason, apparently. I now have the SelectObject call just before the StretchBLT call with SetDIBits in between and still get black. SetDIBits returns zero and obviously does nothing because if I leave both it and the SetBitmapBits call, I get the proper image (until I go into remote desktop).Bedouin
B
5

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.

Bedouin answered 2/9, 2014 at 15:37 Comment(4)
There was nothing wrong with the GetSafeHdc you were using, the problem was in casting the pointer to CBitmap dispBMP rather than the object itself *dispBMP. I'm curious why you made it a pointer in the first place rather than a local object?Denotation
@MarkRansom: dispBMP is a class member variable because I have found in my experience with C that allocating memory ends up often time being very expensive in the types of application I work on (data processing). So since this is a new project starting from scratch I'm being very careful to reuse large memory objects like bitmaps and bitmap byte arrays.Bedouin
That strategy isn't buying you much in this case. Do a sizeof(dispBMP) to see what I mean - it doesn't contain the bitmap, it holds a handle to the bitmap, which is allocated separately.Denotation
@MarkRansom: right. So if I lose that handle I need to create a new bitmap every time. Isn't CreateCompatibleBitmap the one allocating the memory? I don't show it in my snippet, but in the actual code I only call that if I know the image changed size or if dispBMP is NULL.Bedouin

© 2022 - 2024 — McMap. All rights reserved.