SetPixel绘制像素偏离光标位置及像素复原问题求解
解决鼠标光标位置像素绘制偏移与自动恢复的问题
先来说你遇到的两个问题,我帮你逐一拆解解决:
问题1:绘制像素位于光标位置下方(偏移问题)
这个问题大概率是高DPI缩放导致的坐标不匹配。Windows在高分辨率屏幕下会启用DPI缩放,GetCursorPos返回的是物理屏幕的像素坐标,但GetDC(NULL)获取的屏幕DC默认使用逻辑坐标,两者未做适配就会出现偏移。
另外还要纠正你代码里的一个小错误:用DeleteObject(hdc)释放屏幕DC是不对的,GetDC获取的DC应该用ReleaseDC释放,DeleteObject是用来删除画笔、画刷这类GDI对象的,用错会导致资源泄漏。
修复后的代码片段:
#include <windows.h> POINT p; int main(void) { HDC hdc = GetDC(NULL); // 获取当前屏幕的DPI值(系统默认DPI为96) int dpiX = GetDeviceCaps(hdc, LOGPIXELSX); int dpiY = GetDeviceCaps(hdc, LOGPIXELSY); float scaleX = (float)dpiX / 96.0f; float scaleY = (float)dpiY / 96.0f; while(!GetAsyncKeyState(VK_F1)) { GetCursorPos(&p); // 将物理坐标转换为DC适配的逻辑坐标 int drawX = (int)(p.x / scaleX); int drawY = (int)(p.y / scaleY); SetPixel(hdc, drawX, drawY, RGB(73, 214, 0)); // 加个小延迟,降低CPU占用 Sleep(10); } ReleaseDC(NULL, hdc); // 正确释放屏幕DC return 0; }
问题2:绘制的像素一段时间后恢复默认颜色
这是因为直接在屏幕DC上绘制的内容是非持久化的——当桌面刷新、窗口移动/重绘、系统触发屏幕更新时,这些临时绘制的像素就会被底层内容覆盖。
要彻底解决这个问题,更靠谱的方式是创建一个全屏透明的顶层窗口,在窗口的客户区进行绘制,由窗口负责维护绘制内容,不会被轻易覆盖。完整实现代码如下:
#include <windows.h> LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); POINT currentCursorPos; int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // 注册窗口类 const wchar_t CLASS_NAME[] = L"PixelDrawWindowClass"; WNDCLASS wc = {0}; wc.lpfnWndProc = WindowProc; wc.hInstance = hInstance; wc.lpszClassName = CLASS_NAME; wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH); // 设置透明背景 wc.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&wc); // 创建全屏无边框透明顶层窗口 HWND hwnd = CreateWindowEx( WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST, CLASS_NAME, L"", WS_POPUP, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), NULL, NULL, hInstance, NULL ); if (hwnd == NULL) return 0; // 设置窗口完全透明,不遮挡底层操作 SetLayeredWindowAttributes(hwnd, 0, 0, LWA_ALPHA); ShowWindow(hwnd, nCmdShow); // 定时器:每隔10ms获取光标位置并触发重绘 SetTimer(hwnd, 1, 10, NULL); // 消息循环 MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); // 按F1键退出程序 if (GetAsyncKeyState(VK_F1) & 0x8000) break; } KillTimer(hwnd, 1); DestroyWindow(hwnd); return 0; } LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_TIMER: // 获取光标位置并转换为窗口客户区坐标 GetCursorPos(¤tCursorPos); ScreenToClient(hwnd, ¤tCursorPos); // 触发窗口重绘 InvalidateRect(hwnd, NULL, FALSE); return 0; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); // 在光标位置绘制像素 SetPixel(hdc, currentCursorPos.x, currentCursorPos.y, RGB(73, 214, 0)); EndPaint(hwnd, &ps); return 0; } case WM_DESTROY: PostQuitMessage(0); return 0; default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } }
这个方案的优势:
- 窗口全屏透明,完全不影响底层操作和内容显示
- 绘制内容由窗口维护,即使桌面刷新也不会丢失
- 用定时器控制绘制频率,大幅降低CPU占用
额外提示
如果是多屏幕场景,可以用GetDpiForMonitor获取对应显示器的精准DPI值,进一步优化坐标适配。
内容的提问来源于stack exchange,提问作者Michal




