为何BitBlt()复制同一位图自身像素时出现放大?
问题描述
我正在开发一个可复制桌面并对像素进行操作的窗口,通过BitBlt从桌面句柄向窗口句柄复制图像,此步骤可正常生成与桌面完全一致的窗口。但当再次调用BitBlt将窗口内某段像素复制到窗口另一区域时,像素出现放大效果。请问该现象的原因是什么?如何修复?
相关代码
#include <windows.h> int myWidth, myHeight; LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CREATE: { HDC DhWnd = GetDC(HWND_DESKTOP); HDC MhWnd = GetDC(hwnd); BitBlt(MhWnd, 0, 0, myWidth, myHeight, DhWnd, 0, 0, SRCCOPY); ShowWindow(hwnd, SW_SHOW); BitBlt(MhWnd, 300, 0, 100, 500, MhWnd, 300, 0, SRCCOPY); /*^^^^ 这段代码明明是把相同坐标的位图复制到自身,却出现了像素放大的效果 ^^^^*/ ReleaseDC(hwnd, DhWnd); ReleaseDC(hwnd, MhWnd); return 0; } case WM_PAINT: ValidateRect(hwnd, NULL); return 0; case WM_CLOSE: case WM_DESTROY: DestroyWindow(hwnd); PostQuitMessage(0); return 0; default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } } int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) { RECT Drc; MSG msg; HWND DhWnd = GetDesktopWindow(); HWND MyhWnd; GetWindowRect(DhWnd, &Drc); myWidth = Drc.right - Drc.left; myHeight = Drc.bottom - Drc.top; WNDCLASS wc; wc.style = 0; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = NULL; wc.hCursor = LoadCursor(NULL,IDC_ARROW); wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = "MyWindow"; if (!RegisterClass(&wc)) { MessageBox(NULL, "Window Registration Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK); return 0; } MyhWnd = CreateWindow("MyWindow", NULL, WS_POPUP, 0, 0, myWidth, myHeight, NULL, NULL, hInstance, NULL); if (MyhWnd == NULL) { MessageBox(NULL, "Window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK); return 0; } while (GetMessage(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; }
原因分析
- 高DPI缩放不匹配:如果系统开启了高DPI缩放(比如125%、150%),窗口的逻辑坐标和物理像素坐标会错位。你通过
GetWindowRect获取的桌面尺寸是物理像素,但创建窗口时使用的是逻辑坐标,导致窗口实际物理尺寸和桌面不一致,BitBlt操作时自然会出现拉伸放大。 - 同一DC的重叠复制冲突:当源DC和目标DC为同一个时,直接调用
BitBlt若源区域与目标区域有重叠(高DPI下实际坐标已错位,看似相同的坐标其实对应不同物理区域),会导致像素被重复覆盖复制,产生类似放大的异常效果。
修复方法
1. 适配高DPI环境
在WinMain函数开头添加DPI感知声明,让系统按物理像素处理窗口:
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
2. 避免同一DC的直接复制
需要在窗口内部复制像素时,先将源区域内容复制到内存DC中,再从内存DC复制到目标区域,绕过同一DC的重叠冲突:
case WM_CREATE: { HDC DhWnd = GetDC(HWND_DESKTOP); HDC MhWnd = GetDC(hwnd); // 复制桌面到窗口 BitBlt(MhWnd, 0, 0, myWidth, myHeight, DhWnd, 0, 0, SRCCOPY); // 创建内存DC和兼容位图 HDC memDC = CreateCompatibleDC(MhWnd); HBITMAP memBmp = CreateCompatibleBitmap(MhWnd, 100, 500); HBITMAP oldBmp = (HBITMAP)SelectObject(memDC, memBmp); // 先把窗口目标区域内容复制到内存DC BitBlt(memDC, 0, 0, 100, 500, MhWnd, 300, 0, SRCCOPY); // 再从内存DC复制回窗口目标区域 BitBlt(MhWnd, 300, 0, 100, 500, memDC, 0, 0, SRCCOPY); // 清理资源 SelectObject(memDC, oldBmp); DeleteObject(memBmp); DeleteDC(memDC); ShowWindow(hwnd, SW_SHOW); ReleaseDC(hwnd, DhWnd); ReleaseDC(hwnd, MhWnd); return 0; }
3. 修正WM_PAINT处理逻辑
当前WM_PAINT直接调用ValidateRect会导致窗口重绘时内容丢失,应该在WM_PAINT中重新执行绘制逻辑:
case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); HDC DhWnd = GetDC(HWND_DESKTOP); // 重新复制桌面到窗口 BitBlt(hdc, 0, 0, myWidth, myHeight, DhWnd, 0, 0, SRCCOPY); // 再次执行内部像素复制(使用内存DC方式) HDC memDC = CreateCompatibleDC(hdc); HBITMAP memBmp = CreateCompatibleBitmap(hdc, 100, 500); HBITMAP oldBmp = (HBITMAP)SelectObject(memDC, memBmp); BitBlt(memDC, 0, 0, 100, 500, hdc, 300, 0, SRCCOPY); BitBlt(hdc, 300, 0, 100, 500, memDC, 0, 0, SRCCOPY); SelectObject(memDC, oldBmp); DeleteObject(memBmp); DeleteDC(memDC); ReleaseDC(hwnd, DhWnd); EndPaint(hwnd, &ps); return 0; }
内容的提问来源于stack exchange,提问作者bugger
相关产品推荐
相关产品推荐

