fireman
fireman

Reputation: 193

How to create Win32 HBITMAP from pixelbuffer

I am try to create an HBITMAP from pixel buffer and display it. Here is my code to create the HBITMAP

char buffer[640 * 480 * 3];
memset(buffer, 255, 640 * 480 * 3);
BITMAPINFO bm = { sizeof(BITMAPINFOHEADER),
        640,
        480, 1, 24,
        BI_RGB, 640 * 480 * 3, 0, 0, 0, 0 };
HBITMAP imageBmp = CreateDIBSection(hdc, &bm, DIB_RGB_COLORS, (void**)buffer, 0, 0);
if (imageBmp == NULL) {
    DWORD lastError = GetLastError();
    return;
}

Here is the code to display it:

 HDC imageDC = CreateCompatibleDC(NULL);     // create an offscreen DC
SelectObject(imageDC, imageBmp);  // put the loaded image into our DC
RECT rect;
GetClientRect(hDlg, &rect);
BitBlt(
    hdc,         // tell it we want to draw to the screen
    0, 0,            // as position 0,0 (upper-left corner)
    rect.right - rect.left,   // width of the rect to draw
    rect.bottom - rect.top,   // height of the rect
    imageDC,        // the DC to get the rect from (our image DC)
    0, 0,            // take it from position 0,0 in the image DC
    SRCCOPY         // tell it to do a pixel-by-pixel copy
);

I am expecting to see a white image, but what I got was a black window screen. I am pretty sure my display code is correct, but do not know why the code to create HBITMAP was wrong.

Upvotes: 1

Views: 762

Answers (1)

zett42
zett42

Reputation: 27756

CreateDIBSection already returns an allocated buffer through the ppvBits argument to you, so it overwrites your buffer variable. From the docs (emphasis mine):

ppvBits A pointer to a variable that receives a pointer to the location of the DIB bit values.

Fixes required to your code:

  • Remove code to create an array.
  • Pass the address of a pointer for the ppvBits parameter.
  • Set the pixels only after a successfull call to CreateDIBSection.
char* buffer = NULL;
BITMAPINFO bm = { sizeof(BITMAPINFOHEADER),
        640,
        480, 1, 24,
        BI_RGB, 640 * 480 * 3, 0, 0, 0, 0 };
HBITMAP imageBmp = CreateDIBSection(hdc, &bm, DIB_RGB_COLORS, (void**) &buffer, 0, 0);
if (imageBmp == NULL) {
    DWORD lastError = GetLastError();
    return;
}
memset(buffer, 255, 640 * 480 * 3);

Note:

Make sure that in production code, you properly calculate the size by aligning the bitmap width to the next DWORD boundary, as described by the article "DIBs and Their Use":

Calculating the size of a bitmap is not difficult:

biSizeImage = ((((biWidth * biBitCount) + 31) & ~31) >> 3) * biHeight

The crazy roundoffs and shifts account for the bitmap being DWORD-aligned at the end of every scanline.

In your sample, 640 * 480 * 3 gives the correct result only because the width of 640 is already divisable by 4. For a width of 641 your formula would fail, while the formula cited from the article would give the correct result.

Upvotes: 4

Related Questions