C++和GDI使用时屏幕截图无法捕获整个屏幕

5

我在网上做了一些研究,并找到了一些有用的代码。我稍微修改了一下,试图捕获整个屏幕并生成一个可以通过UDP数据包发送的缓冲区:

#include <iostream>
#include <Windows.h>
#include <fstream>

void CapruteScreenAndSaveToFile()
{
    uint16_t BitsPerPixel = 24;
    uint32_t Width = GetSystemMetrics(SM_CXSCREEN);
    uint32_t Height = GetSystemMetrics(SM_CYSCREEN);

    // Create Header
    BITMAPFILEHEADER Header;
    memset(&Header, 0, sizeof(Header));
    Header.bfType = 0x4D42;
    Header.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

    // Create Info
    BITMAPINFO Info;
    memset(&Info, 0, sizeof(Info));
    Info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    Info.bmiHeader.biWidth = Width;
    Info.bmiHeader.biHeight = Height;
    Info.bmiHeader.biPlanes = 1;
    Info.bmiHeader.biBitCount = BitsPerPixel;
    Info.bmiHeader.biCompression = BI_RGB;
    Info.bmiHeader.biSizeImage = Width * Height * (BitsPerPixel > 24 ? 4 : 3);

    // Capture screen and save to Pixels
    char* Pixels = NULL;
    HDC MemDC = CreateCompatibleDC(0);//Context);
    HBITMAP Section = CreateDIBSection(MemDC, &Info, DIB_RGB_COLORS, (void**)&Pixels, 0, 0);
    DeleteObject(SelectObject(MemDC, Section));
    BitBlt(MemDC, 0, 0, Width, Height, GetDC(0), 0, 0, SRCCOPY);
    DeleteDC(MemDC);

    // Concatenate everything
    char * buffer = (char*)malloc(sizeof(Header) + sizeof(Info.bmiHeader) + (((BitsPerPixel * Width + 31) & ~31) / 8) * Height);

    memcpy(buffer, (char*)&Header, sizeof(Header));
    memcpy(buffer + sizeof(Header), (char*)&Info.bmiHeader, sizeof(Info.bmiHeader));
    memcpy(buffer + sizeof(Header) + sizeof(Info.bmiHeader), Pixels, (((BitsPerPixel * Width + 31) & ~31) / 8) * Height);

    // Save to file
    std::fstream hFile("Foo.bmp", std::ios::out | std::ios::binary);

    hFile.write(buffer, sizeof(Header) + sizeof(Info.bmiHeader) + (((BitsPerPixel * Width + 31) & ~31) / 8) * Height);

    // Clean up
    hFile.close();
    DeleteObject(Section);
    free(buffer);
}


int main()
{
    CapruteScreenAndSaveToFile();

    return 0;
}

但它似乎只捕获了我的桌面的一部分:

在此输入图片描述

尽管我使用了CreateCompatibleDC(0)。


我在我的电脑上测试了你的代码。它运行良好。生成的“bmp”具有全屏截图。 - Pavan Chandaka
不要忘记有SetWindowDisplayAffinity和OpenGL。两者都可能成为您的代码障碍,并导致其失败。虽然这不是您正在解决的问题,但这是您不可避免地会遇到的事情。 - IInspectable
1个回答

7
如果计算机设置为高DPI,并且应用程序不支持DPI,那么系统将欺骗应用程序并给出错误的屏幕尺寸。
您会发现以下代码显示的宽度和高度比实际屏幕尺寸小:
uint32_t Width = GetSystemMetrics(SM_CXSCREEN);
uint32_t Height = GetSystemMetrics(SM_CYSCREEN);
std::cout << Width << " x " << Height << "\n";

解决方案是将DPI感知添加到应用程序中。
要添加DPI兼容性:
在Visual Studio 2015中,转到项目属性->清单工具,将DPI感知设置为“每个监视器高DPI感知”或“高DPI感知”。
如果您正在使用一些旧编译器...
1)创建一个名为“myapp.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) 将以下内容添加到您的项目中的*.rc文件中:

1 24 "myapp.manifest"

3) 重新构建项目


这可能是正确的答案,但在我的Windows 8.1上却不起作用,显然比Vista更新..因此我仍需要进一步的帮助。它说SetProcessDPIAware在此范围内未声明,即使我已经包含了Windows.h并链接了User32.lib。 - W2a
SetProcessDPIAware 不起作用,因为您没有定义 _WIN32_WINNT 或者您正在使用不兼容的 MinGW-32。无论如何,正确的添加 DPI 感知的方法是通过清单文件。不要把这变成一个谜题,让我猜测一切,请提供一些最少的信息,以便您得到的错误可以被重现。 - Barmak Shemirani
是的,我正在使用MinGW-32。那么我应该使用什么呢?非常感谢! - W2a
我认为你只读了答案和评论的一部分。你必须使用清单文件,就像答案中所示。 - Barmak Shemirani
抱歉,我没有注意到您编辑了答案,但它仍然不起作用。我在项目文件夹中创建了一个dpi.manifest文件,并添加了一个“resource.rc”文件,其中包含1 24“dpi.manifest”的内容,但编译器仍然无法识别SetProcessDPIAware.. - W2a
不要使用 SetProcessDPIAware。如果清单文件设置正确,程序将报告正确的宽度/高度。 - Barmak Shemirani

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接