C++截图程序仅捕获屏幕左上角,如何修改实现全屏截图?
全屏截图仅捕获左上角区域的解决方法
我编写了一个C++程序用于捕获屏幕截图并保存为PNG格式,其他功能正常,但只能捕获屏幕的左上角区域。尝试用GetDpiForSystem()做DPI感知后问题依旧,请问如何修改代码实现全屏截图?

初始代码
#define _CRT_SECURE_NO_WARNINGS #include <windows.h> #include <objidl.h> #include <stdio.h> #include <gdiplus.h> using namespace Gdiplus; #pragma comment (lib,"Gdiplus.lib") int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) { UINT num = 0; UINT size = 0; Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL; Gdiplus::GetImageEncodersSize(&num, &size); if (size == 0) { return -1; } pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size)); if (pImageCodecInfo == NULL) { return -1; } 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; } void TakeScreenshot(const wchar_t* file_name) { // 获取整个桌面的尺寸 int width = GetSystemMetrics(SM_CXVIRTUALSCREEN); int height = GetSystemMetrics(SM_CYVIRTUALSCREEN); // 创建用于存储截图的位图 HDC screen_dc = GetDC(NULL); HDC mem_dc = CreateCompatibleDC(screen_dc); HBITMAP bitmap = CreateCompatibleBitmap(screen_dc, width, height); HGDIOBJ old_bitmap = SelectObject(mem_dc, bitmap); // 将屏幕内容复制到位图 BitBlt(mem_dc, 0, 0, width, height, screen_dc, 0, 0, SRCCOPY); // 将位图保存为文件 Gdiplus::Bitmap image(bitmap, NULL); CLSID png_clsid; GetEncoderClsid(L"image/png", &png_clsid); image.Save((WCHAR*)file_name, &png_clsid, NULL); // 清理资源 SelectObject(mem_dc, old_bitmap); DeleteObject(bitmap); DeleteDC(mem_dc); ReleaseDC(NULL, screen_dc); } int main() { Gdiplus::GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); TakeScreenshot(L"test.png"); GdiplusShutdown(gdiplusToken); }
更新后的尝试代码
#define _CRT_SECURE_NO_WARNINGS #include <windows.h> #include <objidl.h> #include <stdio.h> #include <gdiplus.h> using namespace Gdiplus; #pragma comment (lib,"Gdiplus.lib") int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) { UINT num = 0; UINT size = 0; Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL; Gdiplus::GetImageEncodersSize(&num, &size); if (size == 0) { printf("Error: GetImageEncodersSize returned size 0\n"); return -1; } pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size)); if (pImageCodecInfo == NULL) { printf("Error: malloc failed to allocate memory for ImageCodecInfo\n"); return -1; } if (GetImageEncoders(num, size, pImageCodecInfo) != Ok) { printf("Error: GetImageEncoders failed\n"); free(pImageCodecInfo); return -1; } 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; } void TakeScreenshot(const wchar_t* file_name) { // 获取整个桌面的尺寸 int dpi_x = GetDpiForSystem(); int dpi_y = GetDpiForSystem(); int width = GetSystemMetricsForDpi(SM_CXSCREEN, dpi_x); int height = GetSystemMetricsForDpi(SM_CYSCREEN, dpi_y); if (width == 0 || height == 0) { printf("Error: GetSystemMetrics returned invalid screen dimensions\n"); return; } printf("Width: %d\n", width); printf("Height: %d\n", height); // 创建用于存储截图的位图 HDC screen_dc = GetDC(NULL); if (screen_dc == NULL) { printf("Error: GetDC failed to get a handle to the screen device context\n"); return; } HDC mem_dc = CreateCompatibleDC(screen_dc); if (mem_dc == NULL) { printf("Error: CreateCompatibleDC failed to create a compatible device context\n"); ReleaseDC(NULL, screen_dc); return; } // 创建适配DPI的位图 HBITMAP bitmap = CreateBitmap(width, height, 1, GetDeviceCaps(screen_dc, BITSPIXEL), NULL); if (bitmap == NULL) { printf("Error: CreateCompatibleBitmap failed to create a compatible bitmap\n"); DeleteDC(mem_dc); ReleaseDC(NULL, screen_dc); return; } HGDIOBJ old_bitmap = SelectObject(mem_dc, bitmap); // 设置内存DC的DPI匹配系统DPI SetGraphicsMode(mem_dc, GM_ADVANCED); XFORM xform; xform.eM11 = (FLOAT)dpi_x / 96; xform.eM12 = xform.eM21 = xform.eM22 = 0; xform.eDx = xform.eDy = 0; SetWorldTransform(mem_dc, &xform); // 将屏幕内容复制到位图 if (BitBlt(mem_dc, 0, 0, width, height, screen_dc, 0, 0, SRCCOPY) == 0) { printf("Error:BitBlt failed to copy screen contents to bitmap\n"); return; } // 将位图保存为文件 Gdiplus::Bitmap image(bitmap, NULL); if (image.GetLastStatus() != Ok) { printf("Error: Bitmap constructor failed to create a Bitmap object\n"); return; } CLSID png_clsid; int r = GetEncoderClsid(L"image/png", &png_clsid); if (r == -1) { printf("Error: unable to find image encoder for MIME type 'image/png'\n"); return; } if (image.Save(file_name, &png_clsid, NULL) != Ok) { printf("Error: Bitmap::Save failed to save image\n"); return; } // 清理资源 SelectObject(mem_dc, old_bitmap); DeleteObject(bitmap); DeleteDC(mem_dc); ReleaseDC(NULL, screen_dc); } int main() { GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); TakeScreenshot(L"test.png"); GdiplusShutdown(gdiplusToken); return 0; }
解决方法
问题根源是程序未正确声明DPI感知,仅调用GetDpiForSystem()不足以让系统正确识别程序的DPI适配能力,导致获取的屏幕尺寸和实际绘制不匹配。
步骤1:添加DPI感知清单
在程序中嵌入DPI感知清单,告诉Windows程序支持高DPI。最简单的方式是在代码中添加编译指令:
#pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") #pragma comment(linker, "/manifest:\"<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'><application xmlns='urn:schemas-microsoft-com:asm.v3'><windowsSettings><dpiAware xmlns='http://schemas.microsoft.com/SMI/2005/WindowsSettings'>true/PM</dpiAware></windowsSettings></application></assembly>\"")
或者创建独立的app.manifest文件并添加到项目中,内容如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware> </windowsSettings> </application> </assembly>
步骤2:修正屏幕尺寸获取与绘制逻辑
移除更新代码中不必要的SetWorldTransform操作,因为声明DPI感知后,系统会自动处理坐标转换。修改TakeScreenshot函数:
void TakeScreenshot(const wchar_t* file_name) { // 获取真实全屏尺寸(多显示器用SM_CXVIRTUALSCREEN/SM_CYVIRTUALSCREEN) int width = GetSystemMetrics(SM_CXVIRTUALSCREEN); int height = GetSystemMetrics(SM_CYVIRTUALSCREEN); // 单显示器也可以用SM_CXSCREEN/SM_CYSCREEN if (width == 0 || height == 0) { printf("Error: GetSystemMetrics returned invalid screen dimensions\n"); return; } printf("Width: %d\n", width); printf("Height: %d\n", height); HDC screen_dc = GetDC(NULL); if (screen_dc == NULL) { printf("Error: GetDC failed\n"); return; } HDC mem_dc = CreateCompatibleDC(screen_dc); if (mem_dc == NULL) { printf("Error: CreateCompatibleDC failed\n"); ReleaseDC(NULL, screen_dc); return; } // 使用CreateCompatibleBitmap保证和屏幕DC格式一致 HBITMAP bitmap = CreateCompatibleBitmap(screen_dc, width, height); if (bitmap == NULL) { printf("Error: CreateCompatibleBitmap failed\n"); DeleteDC(mem_dc); ReleaseDC(NULL, screen_dc); return; } HGDIOBJ old_bitmap = SelectObject(mem_dc, bitmap); // 直接BitBlt,无需额外坐标转换 if (!BitBlt(mem_dc, 0, 0, width, height, screen_dc, 0, 0, SRCCOPY)) { printf("Error: BitBlt failed\n"); SelectObject(mem_dc, old_bitmap); DeleteObject(bitmap); DeleteDC(mem_dc); ReleaseDC(NULL, screen_dc); return; } Gdiplus::Bitmap image(bitmap, NULL); if (image.GetLastStatus() != Ok) { printf("Error: Bitmap constructor failed\n"); SelectObject(mem_dc, old_bitmap); DeleteObject(bitmap); DeleteDC(mem_dc); ReleaseDC(NULL, screen_dc); return; } CLSID png_clsid; int r = GetEncoderClsid(L"image/png", &png_clsid); if (r == -1) { printf("Error: unable to find PNG encoder\n"); SelectObject(mem_dc, old_bitmap); DeleteObject(bitmap); DeleteDC(mem_dc); ReleaseDC(NULL, screen_dc); return; } if (image.Save(file_name, &png_clsid, NULL) != Ok) { printf("Error: Save failed\n"); } // 资源清理 SelectObject(mem_dc, old_bitmap); DeleteObject(bitmap); DeleteDC(mem_dc); ReleaseDC(NULL, screen_dc); }
步骤3:验证效果
编译运行修改后的程序,此时程序会正确识别系统DPI,获取真实的全屏尺寸并完整捕获屏幕内容。
内容的提问来源于stack exchange,提问作者ErlingHaaland
相关产品推荐
相关产品推荐

