6

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!

Asesh
  • 3,186
  • 2
  • 21
  • 31
nythrix
  • 81
  • 6
  • We can't see what you used for the WNDCLASSEX.hbrBackground. Or if you handle WM_ERASEBKGND. But those are the standard reasons for observable flicker, you see the window getting erased first before the bitmap is painted. – Hans Passant Jun 02 '18 at 11:06
  • Info added. I'm not clearing the window background since it would be pointless. And I did not set up a background brush. – nythrix Jun 02 '18 at 11:16
  • The only other thing you could do wrong is doing too much work before the BitBlt() call. There is a // CreateDIBSection ? comment that looks fishy. Don't do that in the paint handler, do that when you get the data. – Hans Passant Jun 02 '18 at 11:27
  • Well, that is part of the question. Do I _have_ to do that? What is the shortest and hopefully fastest path between my const void* data and the HDC? The code is just a concise example for the sake of discussion. I will not be creating these objects each time. – nythrix Jun 02 '18 at 11:34
  • You are seemingly destroying your `offscreenBitmap`, while it is still selected into a device context (`offscreenDevice`). That's not good. You also don't have a C tag listed on your profile. Naturally, accessing a C API will always look tedious/convoluted/you-name-it, to **you**. The verbosity is just not what you are used to seeing. At any rate, I can't really make much out of your question. *"Is there a better way"* will not produce helpful responses without known, what *"better"* is to you. It could be argued to close this question as *"primarily opinion-based"*. – IInspectable Jun 02 '18 at 12:24
  • @IInspectable thanks for the heads up regarding that bitmap. By convoluted I don't mean the C API. I have updated the question. – nythrix Jun 02 '18 at 14:03
  • 1
    I'm also not sure about the wisdom of throwing an error from a WndProc. I really don't know what Windows would make of that. – Paul Sanders Jun 02 '18 at 18:05
  • @PaulSanders +1 Nice catch, I did not think of that. – nythrix Jun 02 '18 at 18:56

2 Answers2

6

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;
}
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • This is exactly what I was hoping for. Works great for rendering directly to the window DC but not if I go via the memory DC. Is there anything else required? – nythrix Jun 02 '18 at 19:11
  • 1
    Silly me. After all the refactoring, I'd forgotten to SelectObject the memory bitmap into the memory DC. – nythrix Jun 02 '18 at 19:44
5

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.

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • Thanks, I was unaware of these *Buffered* functions. I'll have to check them out. – nythrix Jun 02 '18 at 14:04
  • It seems I cannot quite use these. My MinGW64 installation is complaining about functions in uxtheme.h and I don't know why. I guess I'll have to stick with CreateCompatibleDC. – nythrix Jun 02 '18 at 19:07
  • 1
    Oh is that where they're declared? That's a bit bizarre. Try copy-and-pasting the relevant declarations out of there into your own code. That should work. – Paul Sanders Jun 02 '18 at 19:10
  • They seem to be. MinGW64 implementation has them decorated with some macro-fu which I don't really want to mess with. Anyway, I'm fine with old school. Thanks for your time. – nythrix Jun 02 '18 at 20:15
  • 1
    I realized that these functions are not available unless macros `WINVER` and/or `_WIN32_WINNT` are defined as `0x0600` or higher as described in https://msdn.microsoft.com/en-us/library/6sehtctf.aspx – nythrix Jun 04 '18 at 17:52
  • 1
    Ah yes, because they're not supported on XP. Terriblty sorry, I forgot all about that. I don't actually use them myself because my code still runs on XP - I have rolled my own. So are you able to use them now? – Paul Sanders Jun 04 '18 at 18:26
  • Yes, they are usable once the macros are properly defined and uxtheme.lib is linked in. This might not be required for the default MS SDK. But it is definitely required for the MinGW-w64 SDK I'm using. – nythrix Jun 04 '18 at 18:43
  • 1
    Yes, default these days (VS 2017, latest 'platform toolset') seems to be `#define WINVER 0x0601`. And they're in `uxtheme.lib`!? I suppose they would be, given the header file they're defined in. I guess they had no better place to put them. – Paul Sanders Jun 04 '18 at 18:53
  • 1
    They are actually defined in uxtheme.dll but the *.lib is needed for compile time linkage. The article you pointed to uses a `#pragma comment(lib, "uxtheme.lib")` directive but does not mention `WINVER`. As you wrote it's probably a non issue if you use a recent-ish Visual Studio but it can catch you out if you're not. Minorities beware, lol! – nythrix Jun 04 '18 at 19:15