更改与 HDC 关联的位图大小后,有没有办法更新 Graphics 对象?

Posted

技术标签:

【中文标题】更改与 HDC 关联的位图大小后,有没有办法更新 Graphics 对象?【英文标题】:Is there a way to update Graphics object after changing size of bitmap assosiated with HDC? 【发布时间】:2019-04-20 14:37:35 【问题描述】:

1。问题

我有两个缓冲区。主缓冲区,显示在屏幕上和一个辅助缓冲区,其中所有内容都被绘制然后传递给主缓冲区。

Graphics 对象是从辅助缓冲区创建的,该缓冲区与大小为 800x600 的位图相关联。自然地,当您调整窗口大小时,必须更改位图的大小以防止出现剪切问题。 次要 HDC 得到更新,位图被复制到主要的。

问题是在与生成剪辑的辅助 HDC 关联的 Graphics 对象中留下了一些东西。尽管已将绘图区域更新为更大的尺寸 (1000x1000),但绘图区域仍保持 800x600。

我的问题是我应该在 Graphics 对象内部更新什么(不必从现有 HDC 显式重新创建它)以使其绘图区域适合位图大小。

2。我尝试了什么

我尝试的第一件事是从更新的 HDC 重新创建 Graphics 对象。此方法有效,并且绘图区域适合位图的大小。但是不符合设计标准。图形应该是可重复使用的。

我还尝试使用 SetClip 方法更新图形对象的剪切区域。虽然这似乎不是问题。

这就是我创建缓冲区的方式。

HDC CoreWindowFrame::InitPaint(HWND hWnd)

    windowHdc = GetDC(hWnd);
    HDC secondaryBuffer = CreateCompatibleDC(windowHdc);
    HBITMAP map = CreateCompatibleBitmap(windowHdc, width, height);
    SelectObject(secondaryBuffer, map);
    return secondaryBuffer;

调整大小时调用此函数

void CoreWindowFrame::UpdateBitmap(int width, int height)

    HBITMAP map = CreateCompatibleBitmap(windowHdc, width, height);
    SelectObject(secondaryBuffer, map);


这是消息处理:

case WM_SIZE:
    ConsoleWrite("WM_SIZE RECIEVED");
    width = *((unsigned short*)&lParam);
    height = ((unsigned short*)&lParam)[1];
    UpdateBitmap(width, height);

break;
case WM_PAINT:
    ConsoleWrite("WM_PAINT RECIEVE");

    windowGraphics->Clear(Color(255, 255, 255));

    Pen* testPen = new Pen(Color(255, 0, 0), 1.0F);

    windowGraphics->DrawLine(testPen, 0, 0, calls*3, 100);
    BitBlt(windowHdc, 0, 0, updatedWidth, updatedHeight, secondaryBuffer, 0, 0, MERGECOPY);

3。预期结果

图形对象应该是可重复使用的,并且不应在每次刷新时将其丢弃并替换为新对象。因此,它必须相应地更新,以防任何东西被调整或改变。我希望该区域适合辅助 HDC 中当前更新的位图的大小。如代码中所示,位图已正确更新。不是图形对象。它的行为就像它仍然记得导致这种行为的前一个 BitMap 中的一些值。

【问题讨论】:

此代码缺少错误检查并泄漏资源。 WM_PAINT 应该包含 BeginPain / EndPaint 调用 我知道,我故意将其省略以使代码更易于阅读,因为这不是与资源泄漏或错误有关的问题。重写了 BeginPaint 和 EndPaint 函数。我不会故意使用它们。这是我转向堆栈溢出的原因之一,因为 MSDN 文档不足以满足我的需要。 您必须重新创建“windowsGraphics”对象。顺便说一句,这是不适当的微优化的标准案例,它非常便宜(约 1 微秒),因此您总是倾向于在需要时创建它并避免存储它。 @Hans Passant 谢谢,我认为这是次要选项,如果找不到满意的答案,可能会实施此方法。 【参考方案1】:
windowHdc = GetDC(hWnd);
HDC secondaryBuffer = CreateCompatibleDC(windowHdc);
HBITMAP map = CreateCompatibleBitmap(windowHdc, width, height);
SelectObject(secondaryBuffer, map);
return secondaryBuffer;

GetDCBeginPaint 获得的HDC 不能重复使用,如 cmets 中所述。

但是,您可以重用内存位图(来自 CreateCompatibleBitmap)和重用内存 dc(从 CreateCompatibleDC 获得),尽管重用内存 dc 通常没有意义。

此外,需要进行清理以避免资源泄漏。完成GetDC 后,请致电ReleaseDC。请参阅相关发布/删除功能的文档。

为一个简单的绘制例程执行此操作:

case WM_PAINT:

    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);

    Gdiplus::Graphics gr(hdc);
    Gdiplus::Pen testPen(Gdiplus::Color(255, 0, 0), 1.0F);
    gr.Clear(Gdiplus::Color::White);
    gr.DrawLine(&testPen, 0, 0, 100, 100);

    EndPaint(hwnd, &ps);
    return 0;

通常您不需要再做任何事情。只需致电InvalidateRect 回复WM_SIZE 即可更新绘画。

对于双缓冲或在画布上绘图,您可以创建内存位图并重复使用它。如果窗口大小发生变化,那么您必须为旧位图调用DeleteObject,并根据新大小创建一个新位图。或者您可以创建一个匹配最大窗口大小的位图,然后将此位图用于所有窗口大小。

请参阅下面的双缓冲绘制示例(但在这种情况下不需要重复使用 hbitmap

HBITMAP hbitmap = NULL;

void InitPaint(HWND hwnd)

    HDC hdc = GetDC(hwnd);
    //create a bitmap with max size:
    int w = GetSystemMetrics(SM_CXFULLSCREEN); 
    int h = GetSystemMetrics(SM_CYFULLSCREEN); 
    hbitmap = CreateCompatibleBitmap(hdc, w, h);
    ReleaseDC(hwnd, hdc);


void cleanup()

    if (hbitmap)
        DeleteObject(hbitmap);


...
case WM_PAINT:

    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);

    RECT rc; GetClientRect(hwnd, &rc);
    int w = rc.right;
    int h = rc.bottom;
    HDC memdc = CreateCompatibleDC(hdc);
    HBITMAP oldbmp = (HBITMAP)SelectObject(memdc, hbitmap);

    Gdiplus::Graphics gr(memdc);
    Gdiplus::Pen testPen(Gdiplus::Color(255, 0, 0), 1.0F);
    gr.Clear(Gdiplus::Color(255, 255, 255));
    gr.DrawLine(&testPen, 0, 0, w, h);

    BitBlt(hdc, 0, 0, w, h, memdc, 0, 0, SRCCOPY);

    //cleanup:
    SelectObject(memdc, oldbmp);
    DeleteDC(memdc);

    EndPaint(hwnd, &ps);
    return 0;


概括:

GetDC 不会创建任何东西。它只返回对系统使用的现有句柄的引用。完成此手柄后,松开它。没有办法进一步优化。

可以为您的程序创建其他 GDI 对象,例如笔或位图,并且可以重复使用它们。创建/销毁一支笔只需要几纳秒,因此存储这些对象并进行跟踪通常不值得增加复杂性。

主要的瓶颈是当你在屏幕上绘图时,例如画一条线。您必须与显卡对话并与显示器对话,这需要一段时间。您可以使用双缓冲在位图上绘制,然后在屏幕上使用BitBltBitBlt 也可能很慢,具体取决于系统。

对于动画,如果您看到闪烁,请使用双缓冲。

为了获得更好的性能,您可以使用更新的技术,例如 Direct2D

如果动画仍然太慢,请考虑使用第二个线程来运行任何数学类型的计算(第二个线程不应引用任何窗口句柄,例如来自GetDCBeginPaintHDC

【讨论】:

感谢您的回答。这似乎足够令人满意。但是,如果我想创建动画或加载条或类似的东西,这需要文字渲染,这不会使创建和删除 Graphics 对象的效率非常低吗? 我不确定您的问题,请参阅 cmets 总结。您可能希望对这些功能进行计时,并查看完成每个调用需要多长时间。不要假设GetDC 效率低下。这些是优化的功能。

以上是关于更改与 HDC 关联的位图大小后,有没有办法更新 Graphics 对象?的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法在使用 bitmapdata.floodfill 方法填充的位图中查找区域的大小?

什么是,为什么我必须在清理时将位图 hdc 或内存 dc 恢复到默认状态? [复制]

下拉更改后在多个 Highcharts 上调整大小不起作用

解码后的位图字节大小?

有没有办法将图像作为位图加载到 Glide

LWUIT 位图字体