Reputation: 81
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!
Upvotes: 6
Views: 776
Reputation: 31659
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;
}
Upvotes: 7
Reputation: 25408
With regard to drawing flicker-free, Vista and later have double buffering support built into the Win32 API. I have adapted the code below from this article. More info at MSDN. Barmak's answer shows you how to draw your pixels.
Initialisation (per thread):
BufferedPaintInit();
Termination (per thread):
BufferedPaintUnInit();
In your WndProc:
case WM_PAINT:
{
// Set things up in the usual way
PAINTSTRUCT ps;
HDC hDC = BeginPaint (hWnd, &ps);
RECT rc;
GetClientRect (hWnd, &rc);
// Try to use buffered painting (may fail, so they say)
HDC hBufferedDC;
HPAINTBUFFER hBufferedPaint = BeginBufferedPaint (hDC, &rc, BPBF_COMPATIBLEBITMAP, NULL, &hBufferedDC);
if (hBufferedPaint)
hDC = hBufferedDC;
// Draw stuff into hDC
// Clean up
if (hBufferedPaint)
EndBufferedPaint (hBufferedPaint, TRUE);
// Finished
EndPaint (hWnd, &ps);
break;
}
Nothing to it, really.
Upvotes: 5