I'm struggling to figure out the proper way of dumping an array of plain RGBA values into the client area of a Win32 window during WM_PAINT. I have the following code but it already seems convoluted and I'm not even finished:
case WM_ERASEBKGND:
return 1;
case WM_PAINT:
{
PAINTSTRUCT paintInfo{};
HDC device = BeginPaint(window, &paintInfo);
if (device == nullptr)
throw runtime_error(RG_LOCATION());
ScopeExit endPaint([&] { EndPaint(window, &paintInfo); });
HDC offscreenDevice = CreateCompatibleDC(device);
ScopeExit deleteOffscreenDevice([&] { DeleteDC(offscreenDevice); });
HBITMAP offscreenBitmap = CreateCompatibleBitmap(device, Distance(paintInfo.rcPaint.left, paintInfo.rcPaint.right),
Distance(paintInfo.rcPaint.top, paintInfo.rcPaint.bottom));
ScopeExit deleteOffscreenBitmap([&] { DeleteObject(offscreenBitmap); });
HBITMAP previousBitmap = reinterpret_cast<HBITMAP>(SelectObject(offscreenDevice, offscreenBitmap));
// now I need to blit the available pixel data...
vector<array<uint8_t, 4>> mypixels;
// ...onto the client area of the window.
// What do I do next?
// CreateDIBSection ?
// BitBlt ?
return 0;
}
I have some wiggle room with regards to the source "image" memory format so I can make that match whatever the target requires.
Am I doing this correctly? Is there a better way?
P.S.: Obviously I would be storing and not recreating most of the objects each time a WM_PAINT comes along. This is just an example/WIP.
Edit: Added handling of WM_ERASEBKGND.
Edit 2: Ok, it seems I need to be more specific. I am not looking for actual issues with the code I posted. It is only an example of what I have so far in terms of workflow. That means I have the window HDC, an offscreen HDC, an offscreen HBITMAP and a pointer to my pixels which are in, let's say, a hypothetical R8G8B8A8 memory layout. What do I do with these objects? Do I create another HBITMAP via CreateDIBSection and write my pixels into that? What do I do with it after?
Edit 3: See answer from Barmak Shemirani for proper solution (my example code has issues). Check also Paul Sanders' answer for some modern WinAPI tips.
Thanks all!
To print mypixels
vector use SetDIBitsToDevice
to draw to device context. Or use SetDIBits
to create a new HBITMAP
object.
For simplicity, this example draw directly in to HDC
. But you can use CreateCompatibleDC
for buffering, or use the buffer method shown in the other answer.
case WM_PAINT:
{
//int w = width of source bitmap
//int h = height of source bitmap
//optional: make sure width and height are correct
assert(mypixels.size() == w * h);
PAINTSTRUCT ps;
auto hdc = BeginPaint(hwnd, &ps);
BITMAPINFOHEADER bi{ sizeof(bi) };
bi.biWidth = w;
bi.biHeight = h;
bi.biPlanes = 1;
bi.biBitCount = 32;
bi.biCompression = BI_RGB;
SetDIBitsToDevice(hdc, 0, 0, w, h, 0, 0, 0, h, &mypixels[0],
(BITMAPINFO*)&bi, DIB_RGB_COLORS);
EndPaint(hwnd, &ps);
return 0;
}
Using memory dc:
case WM_PAINT:
{
RECT rc;
GetClientRect(hwnd, &rc);
int canvas_width = rc.right;
int canvas_height = rc.bottom;
PAINTSTRUCT ps;
auto hdc = BeginPaint(hwnd, &ps);
//create memory dc:
auto memdc = CreateCompatibleDC(hdc);
auto hbmp = CreateCompatibleBitmap(hdc, canvas_width, canvas_height);
auto oldbmp = SelectObject(memdc, hbmp); //<- memdc is ready
//draw on memory dc:
BITMAPINFOHEADER bi{ sizeof(bi), w, h, 1, 32, BI_RGB };
SetDIBitsToDevice(memdc, 0, 0, w, h, 0, 0, 0, h, mypixels.data(),
(BITMAPINFO*)&bi, DIB_RGB_COLORS);
//draw on actual dc:
BitBlt(hdc, 0, 0, canvas_width, canvas_height, memdc, 0, 0, SRCCOPY);
//clean up:
SelectObject(memdc, oldbmp);
DeleteObject(hbmp);
DeleteDC(memdc);
EndPaint(hwnd, &ps);
return 0;
}