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

Is there a way to update Graphics object after changing size of bitmap assosiated with HDC?

本文关键字:位图 更新 对象 图形 关联 HDC 有没有      更新时间:2023-10-16

1. 问题

我有两个缓冲区。主缓冲区,显示在屏幕上,辅助缓冲区用于绘制所有内容,然后传递到主缓冲区。

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

问题是图形对象中还残留了一些与生成剪切的辅助 HDC 关联的内容。绘图区域仍保持 800x600,尽管已更新为更大的 (1000x1000)。

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

2. 我尝试过什么

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

我还尝试使用 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 中当前更新的位图的大小。如代码所示,位图已正确更新。图形对象没有。它的行为就像它仍然记得导致此行为的上一个位图中的一些值一样。

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

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

但是,您可以重用内存位图(从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)