You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

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(&currentCursorPos);
            ScreenToClient(hwnd, &currentCursorPos);
            // 触发窗口重绘
            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

火山引擎 最新活动