设置窗口像素的正确(和无闪烁)方式?

时间:2018-06-02 10:46:21

标签: c++ winapi gdi

我很难弄清楚在WM_PAINT期间将一组普通RGBA值转储到Win32窗口的客户区域的正确方法。我有以下代码,但它似乎已经令人费解,我甚至没有完成:

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;
}

关于源“图像”内存格式,我有一些摆动空间,所以我可以根据目标要求进行匹配。

我这样做是否正确?还有更好的方法吗?

P.S。:显然,每次WM_PAINT出现时,我都会存储并且不会重新创建大多数对象。这只是一个示例/ WIP。

修改:添加了对WM_ERASEBKGND的处理。

编辑2:好的,似乎我需要更加具体。我不是在寻找我发布的代码的实际问题。这只是我目前在工作流程方面的一个例子。这意味着我有HDC窗口,一个屏幕外HDC,一个屏幕外HBITMAP和一个指向我的像素的指针,比如假设的R8G8B8A8内存布局。我该怎么处理这些物品?我是否通过CreateDIBSection创建另一个HBITMAP并将我的像素写入其中?我怎么办呢?

编辑3:请参阅Barmak Shemirani的回答以获得正确的解决方案(我的示例代码有问题)。另请参阅Paul Sanders对一些现代WinAPI技巧的回答。

全部谢谢!

2 个答案:

答案 0 :(得分:3)

要打印mypixels向量,请使用SetDIBitsToDevice绘制到设备上下文。或使用SetDIBits创建新的HBITMAP对象。

为简单起见,此示例直接绘制到HDC。但您可以使用CreateCompatibleDC进行缓冲,或使用另一个答案中显示的缓冲区方法。

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;
}

使用记忆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;
}

答案 1 :(得分:2)

关于无闪烁的绘制,Vista和更高版本在Win32 API中内置了双缓冲支持。我已经从this article调整了以下代码。有关详情,请访问MSDN。 Barmak的答案向您展示了如何绘制像素。

初始化(每个线程):

BufferedPaintInit();

终止(每个帖子):

BufferedPaintUnInit();

在你的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;
}

没什么,真的。