屏幕截图特定窗口

18

能否截屏指定窗口(也可能是其他进程的窗口)?

目前我正在捕捉特定监视器的整个桌面,但我真正想要的是捕捉特定窗口的内容(无论其位置如何)。


7
很简单:你可以使用 BitBlt 将桌面 DC 中的内容复制到位图中。对于特定的窗口,你只需要复制感兴趣的矩形区域(可以使用 GetWindowRect 获取指定窗口的矩形区域)。是的,它可以是另一个进程的窗口。 - Roman R.
Roman R. 我如何找到感兴趣的矩形?有没有办法迭代所有窗口并找到它们的矩形? - ronag
如果您有特定的窗口,则可以获得其HWND句柄。GetWindowRect函数可提供其屏幕坐标。 - Roman R.
我没有任何句柄,只有窗口的名称。 - ronag
2
然后使用FindWindow获取HWND。或者,您需要使用EnumWindows来查找所需的窗口及其句柄。 - Roman R.
4个回答

14

是的。你需要获取要捕获的窗口的句柄,然后使用WinAPI函数PrintWindow,例如:

// Get the window handle of calculator application.
HWND hWnd = ::FindWindow( 0, _T( "Calculator" ));

// Take screenshot.
PrintWindow( hWnd, getDC(hWnd), 0 );

这里有PrintWindow文档,可以点击链接查看。

如果这个窗口没有被聚焦或者被其他窗口遮挡,那么它还能正常工作吗? - Phil
2
如何在PrintWindow之后进行截屏并保存截图(或获取数据)? - mbaros
8
根据PrintWindow文档:“拥有hWnd引用的窗口的应用程序将收到WM_PRINT消息或WM_PRINTCLIENT消息。” 换句话说,源窗口必须实现WM_PRINT/WM_PRINTCLIENT消息处理程序。如果没有实现,PrintWindow将无法工作。这不是一种通用解决方案。 - IInspectable

12

是的,与捕获全屏幕一样简单。您只需要在所需的窗口上使用GetWindowDC()而不是GetDesktopWindow(),然后从该窗口向目标DC进行BitBlt()操作。您还可以通过使用GetWindowRect()来获取正确的大小。

请注意,此方法还允许您从隐藏/覆盖的窗口中进行捕获,而全屏幕截图带有边框的方法则无法实现。

有关更多详细信息,请参见此问题


请回答这个问题:如何使用Windows GDI捕获特定窗口? - Alok

3
在Windows 10中有一个新的API,位于winrt/Windows.Graphics.Capture.h。Microsoft提供了一个非常复杂的示例:https://github.com/microsoft/Windows.UI.Composition-Win32-Samples/tree/master/cpp/ScreenCaptureforHWND。我想创建一个简单(非常简单)的示例,在我的示例中,你可以使用一个简单的函数获取窗口的HWND并进行截图,并将其保存为ScreenShot.bmp
请注意,该函数中的错误处理不好。欢迎改进这个答案!
注意: 代码:
#include <iostream>
#include <Windows.h>
#include <dxgi.h>
#include <inspectable.h>
#include <dxgi1_2.h>
#include <d3d11.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.System.h>
#include <winrt/Windows.Graphics.Capture.h>
#include <windows.graphics.capture.interop.h>
#include <windows.graphics.directx.direct3d11.interop.h>
#include <roerrorapi.h>
#include <shlobj_core.h>
#include <dwmapi.h>
#pragma comment(lib,"Dwmapi.lib")
#pragma comment(lib,"windowsapp.lib")


void CaptureWindow(HWND hwndTarget)
{
    // Init COM
    winrt::init_apartment(winrt::apartment_type::multi_threaded);
    
    // Create Direct 3D Device
    winrt::com_ptr<ID3D11Device> d3dDevice;

    winrt::check_hresult(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
                                           nullptr, 0,D3D11_SDK_VERSION, d3dDevice.put(), nullptr, nullptr));


    winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice device;
    const auto dxgiDevice = d3dDevice.as<IDXGIDevice>();
    {
        winrt::com_ptr<::IInspectable> inspectable;
        winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.get(), inspectable.put()));
        device = inspectable.as<winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice>();
    }


    auto idxgiDevice2 = dxgiDevice.as<IDXGIDevice2>();
    winrt::com_ptr<IDXGIAdapter> adapter;
    winrt::check_hresult(idxgiDevice2->GetParent(winrt::guid_of<IDXGIAdapter>(), adapter.put_void()));
    winrt::com_ptr<IDXGIFactory2> factory;
    winrt::check_hresult(adapter->GetParent(winrt::guid_of<IDXGIFactory2>(), factory.put_void()));

    ID3D11DeviceContext* d3dContext = nullptr;
    d3dDevice->GetImmediateContext(&d3dContext);

    RECT rect{};
    DwmGetWindowAttribute(hwndTarget, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(RECT));
    const auto size = winrt::Windows::Graphics::SizeInt32{rect.right - rect.left, rect.bottom - rect.top};

    winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool m_framePool =
        winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::Create(
            device,
            winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized,
            2,
            size);

    const auto activationFactory = winrt::get_activation_factory<
        winrt::Windows::Graphics::Capture::GraphicsCaptureItem>();
    auto interopFactory = activationFactory.as<IGraphicsCaptureItemInterop>();
    winrt::Windows::Graphics::Capture::GraphicsCaptureItem captureItem = {nullptr};
    interopFactory->CreateForWindow(hwndTarget, winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
                                    reinterpret_cast<void**>(winrt::put_abi(captureItem)));

    auto isFrameArrived = false;
    winrt::com_ptr<ID3D11Texture2D> texture;
    const auto session = m_framePool.CreateCaptureSession(captureItem);
    m_framePool.FrameArrived([&](auto& framePool, auto&)
    {
        if (isFrameArrived) return;
        auto frame = framePool.TryGetNextFrame();

        struct __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1"))
            IDirect3DDxgiInterfaceAccess : ::IUnknown
        {
            virtual HRESULT __stdcall GetInterface(GUID const& id, void** object) = 0;
        };

        auto access = frame.Surface().as<IDirect3DDxgiInterfaceAccess>();
        access->GetInterface(winrt::guid_of<ID3D11Texture2D>(), texture.put_void());
        isFrameArrived = true;
        return;
    });


    session.IsCursorCaptureEnabled(false);
    session.StartCapture();


    // Message pump
    MSG msg;
    clock_t timer = clock();
    while (!isFrameArrived)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0)
            DispatchMessage(&msg);

        if (clock() - timer > 20000)
        {
            // TODO: try to make here a better error handling
            return;
        }
    }

    session.Close();

    D3D11_TEXTURE2D_DESC capturedTextureDesc;
    texture->GetDesc(&capturedTextureDesc);

    capturedTextureDesc.Usage = D3D11_USAGE_STAGING;
    capturedTextureDesc.BindFlags = 0;
    capturedTextureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
    capturedTextureDesc.MiscFlags = 0;

    winrt::com_ptr<ID3D11Texture2D> userTexture = nullptr;
    winrt::check_hresult(d3dDevice->CreateTexture2D(&capturedTextureDesc, NULL, userTexture.put()));

    d3dContext->CopyResource(userTexture.get(), texture.get());


    D3D11_MAPPED_SUBRESOURCE resource;
    winrt::check_hresult(d3dContext->Map(userTexture.get(), NULL, D3D11_MAP_READ, 0, &resource));

    BITMAPINFO lBmpInfo;

    // BMP 32 bpp
    ZeroMemory(&lBmpInfo, sizeof(BITMAPINFO));
    lBmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    lBmpInfo.bmiHeader.biBitCount = 32;
    lBmpInfo.bmiHeader.biCompression = BI_RGB;
    lBmpInfo.bmiHeader.biWidth = capturedTextureDesc.Width;
    lBmpInfo.bmiHeader.biHeight = capturedTextureDesc.Height;
    lBmpInfo.bmiHeader.biPlanes = 1;
    lBmpInfo.bmiHeader.biSizeImage = capturedTextureDesc.Width * capturedTextureDesc.Height * 4;
    
    std::unique_ptr<BYTE> pBuf(new BYTE[lBmpInfo.bmiHeader.biSizeImage]);
    UINT lBmpRowPitch = capturedTextureDesc.Width * 4;
    auto sptr = static_cast<BYTE*>(resource.pData);
    auto dptr = pBuf.get() + lBmpInfo.bmiHeader.biSizeImage - lBmpRowPitch;

    UINT lRowPitch = std::min<UINT>(lBmpRowPitch, resource.RowPitch);

    for (size_t h = 0; h < capturedTextureDesc.Height; ++h)
    {
        memcpy_s(dptr, lBmpRowPitch, sptr, lRowPitch);
        sptr += resource.RowPitch;
        dptr -= lBmpRowPitch;
    }

    // Save bitmap buffer into the file ScreenShot.bmp
    WCHAR lMyDocPath[MAX_PATH];

    winrt::check_hresult(SHGetFolderPath(nullptr, CSIDL_PERSONAL, nullptr, SHGFP_TYPE_CURRENT, lMyDocPath));

    std::wstring lFilePath = L"ScreenShot.bmp";

    FILE* lfile = nullptr;

    if (auto lerr = _wfopen_s(&lfile, lFilePath.c_str(), L"wb"); lerr != 0)
        return;

    if (lfile != nullptr)
    {
        BITMAPFILEHEADER bmpFileHeader;

        bmpFileHeader.bfReserved1 = 0;
        bmpFileHeader.bfReserved2 = 0;
        bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + lBmpInfo.bmiHeader.biSizeImage;
        bmpFileHeader.bfType = 'MB';
        bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

        fwrite(&bmpFileHeader, sizeof(BITMAPFILEHEADER), 1, lfile);
        fwrite(&lBmpInfo.bmiHeader, sizeof(BITMAPINFOHEADER), 1, lfile);
        fwrite(pBuf.get(), lBmpInfo.bmiHeader.biSizeImage, 1, lfile);

        fclose(lfile);
    }

    return;
}

使用方法:

auto targetHwnd = FindWindow(L"Notepad",NULL); // Or user any other HWND
CaptureWindow(targetHwnd); // If it worked, it will create ScreenShot.bmp file

谢谢这个代码示例,它有效。然而,更多的问题是屏幕截图代码的性能非常差(400-800毫秒),而且代码相当复杂难以理解。此外,它似乎会大大增加编译时间,这是不幸的。另外,是否有一种聪明的方法可以获得PNG而不是BMP,而不必写入磁盘并使用例如GDIPlus加载回来?https://stackoverflow.com/questions/68749555 - BullyWiiPlaza
提醒一下,可以通过在会话对象上设置 IsBorderRequired(false) 来移除黄色边框限制。 - n0p

0
当我想在记事本上绘制游戏屏幕时,使用以下方法只会显示黑色屏幕。
hdcGame := GetDC(hwndGame)
BitBlt(hdcNotpad, 0, 0, rectGame.Width(), rectGame.Height(),
       hdcGame, 0, 0,
       SRCCOPY
)

所以我尝试了@Roman R.的建议来捕获桌面DCGetDC(GetDesktopWindow()),然后计算我们屏幕的目标位置GetWindowRect,只绘制该位置。BitBltStretchBlt

示例

以下代码是用Go实现的

// draw the calculator's screen on the notepad
func Example_captureWindow() {
    user32dll := w32.NewUser32DLL()
    gdi32dll := w32.NewGdi32DLL()

    hwndScreen := user32dll.GetDesktopWindow()
    hdcScreen := user32dll.GetDC(hwndScreen)
    defer user32dll.ReleaseDC(hwndScreen, hdcScreen) // release when exit function

    hwndCalculator := user32dll.FindWindow("ApplicationFrameWindow", "小算盤")
    hwndNotepad := user32dll.FindWindow("Notepad", "")

    hdcNotepad := user32dll.GetWindowDC(hwndNotepad)
    defer user32dll.ReleaseDC(hwndNotepad, hdcNotepad)

    var rectNotepad w32.RECT
    var rectC w32.RECT
    gdi32dll.SetStretchBltMode(hdcNotepad, w32.HALFTONE)
    user32dll.GetWindowRect(hwndNotepad, &rectNotepad)
    user32dll.GetWindowRect(hwndCalculator, &rectC)
    gdi32dll.StretchBlt(
        hdcNotepad, rectNotepad.Width()/4, rectNotepad.Height()/4, rectNotepad.Width()/2, rectNotepad.Height()/2, // draw screen on the center
        hdcScreen, rectC.Left, rectC.Top, rectC.Width(), rectC.Height(), w32.SRCCOPY,
    )
}

其他示例:

  1. 每0.1秒捕获计算器的屏幕,然后在记事本上绘制:源链接

  2. 按F9可以捕获特定设备的屏幕并在记事本上显示。源链接


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