Windows因为dpi缩放导致代码截图不全的问题

⌚Time: 2025-12-25 22:59:00

👨‍💻Author: Jack Ge

我做了程序需要截图发现全屏截图不全。检查获得的分辨率大小1280x720小于真正分辨率1600x900,这是进行了1.25缩放

因为自己显示设置开启了屏幕缩放125%

获取的分辨率不够全屏,是虚拟屏幕的分辨率。所以截图只截取了部分。

有办法获取包括虚拟屏幕、物理屏幕分辨率,还有缩放比例的代码

    // 获取窗口当前显示的监视器
    HWND hWnd = GetDesktopWindow();//根据需要可以替换成自己程序的句柄 
    HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);

    // 获取监视器逻辑宽度与高度
    MONITORINFOEX miex;
    miex.cbSize = sizeof(miex);
    GetMonitorInfo(hMonitor, &miex);
    int cxLogical = (miex.rcMonitor.right - miex.rcMonitor.left);
    int cyLogical = (miex.rcMonitor.bottom - miex.rcMonitor.top);

    // 获取监视器物理宽度与高度
    DEVMODE dm;
    dm.dmSize = sizeof(dm);
    dm.dmDriverExtra = 0;
    EnumDisplaySettings(miex.szDevice, ENUM_CURRENT_SETTINGS, &dm);
    int cxPhysical = dm.dmPelsWidth;
    int cyPhysical = dm.dmPelsHeight;

    //缩放比例计算
    double horzScale = ((double)cxPhysical / (double)cxLogical);
    double vertScale = ((double)cyPhysical / (double)cyLogical);

之后截图,只需要用到物理显示器的分辨率设置截取大小就行了。一个测试的代码是这个

#include <windows.h>
#include <gdiplus.h>
#include <fstream>

#pragma comment(lib, "gdiplus.lib")

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) {
    UINT num = 0;
    UINT size = 0;
    
    Gdiplus::GetImageEncodersSize(&num, &size);
    if (size == 0) return -1;
    
    Gdiplus::ImageCodecInfo* pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
    if (pImageCodecInfo == NULL) return -1;
    
    Gdiplus::GetImageEncoders(num, size, pImageCodecInfo);
    
    for (UINT j = 0; j < num; ++j) {
        if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0) {
            *pClsid = pImageCodecInfo[j].Clsid;
            free(pImageCodecInfo);
            return j;
        }
    }
    
    free(pImageCodecInfo);
    return -1;
}

bool SaveBitmapToFile(HBITMAP hBitmap, const wchar_t* filename) {
    Gdiplus::Bitmap bitmap(hBitmap, NULL);
    
    CLSID clsid;
    GetEncoderClsid(L"image/png", &clsid);
    
    return bitmap.Save(filename, &clsid, NULL) == Gdiplus::Ok;
}

bool CaptureScreen(const wchar_t* filename) {
    // 获取屏幕尺寸
    //int screenWidth = GetSystemMetrics(SM_CXSCREEN);
    //int screenHeight = GetSystemMetrics(SM_CYSCREEN);
    // 获取窗口当前显示的监视器
    HWND hWnd = GetDesktopWindow();
    HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);

    // 获取监视器逻辑宽度与高度
    MONITORINFOEX miex;
    miex.cbSize = sizeof(miex);
    GetMonitorInfo(hMonitor, &miex);
    int cxLogical = (miex.rcMonitor.right - miex.rcMonitor.left);
    int cyLogical = (miex.rcMonitor.bottom - miex.rcMonitor.top);

    // 获取监视器物理宽度与高度
    DEVMODE dm;
    dm.dmSize = sizeof(dm);
    dm.dmDriverExtra = 0;
    EnumDisplaySettings(miex.szDevice, ENUM_CURRENT_SETTINGS, &dm);
    int screenWidth = dm.dmPelsWidth;
    int screenHeight = dm.dmPelsHeight;
    
    // 获取设备上下文
    HDC hScreenDC = GetDC(NULL);
    HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
    
    // 创建位图
    HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, screenWidth, screenHeight);
    HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);
    
    // 复制屏幕内容到位图
    BitBlt(hMemoryDC, 0, 0, screenWidth, screenHeight, hScreenDC, 0, 0, SRCCOPY);
    
    // 恢复并清理
    SelectObject(hMemoryDC, hOldBitmap);
    
    // 保存位图
    bool result = SaveBitmapToFile(hBitmap, filename);
    
    // 清理资源
    DeleteObject(hBitmap);
    DeleteDC(hMemoryDC);
    ReleaseDC(NULL, hScreenDC);
    
    return result;
}

int main() {
    // 初始化GDI+
    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    
    // 截图并保存
    if (CaptureScreen(L"screenshot.png")) {
        printf("save success\n");
    } else {
        printf("save fail\n");
    }
    
    // 关闭GDI+
    Gdiplus::GdiplusShutdown(gdiplusToken);
    
    return 0;
}



使用mingw编译需要链接库参数-lgdi32 -lgdiplus,之后运行能够截取到全屏截图并保存到当前目录png文件。

之前用的这2个代码获取的屏幕尺寸在屏幕缩放后就是错的。

//int screenWidth = GetSystemMetrics(SM_CXSCREEN);
//int screenHeight = GetSystemMetrics(SM_CYSCREEN);