简体   繁体   English

截屏专用 Window

[英]Screen Capture Specific Window

Is it possible to screen capture a specific window (also possibly of another process)?是否可以截屏特定的 window(也可能是另一个进程)?

Currently I am capturing the entire desktop of a specific monitor, however what I truly want is to capture the content of a specific window (regardless of its position).目前我正在捕获特定显示器的整个桌面,但我真正想要的是捕获特定 window 的内容(无论其位置如何)。

Yes it is.是的。 All what you need is get handle to window which you want to capture and use WinAPI function PrintWindow for example:您所需要的只是获取要捕获的窗口的句柄并使用 WinAPI 函数 PrintWindow 例如:

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

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

Here you have PrintWindow documentation. 这里有 PrintWindow 文档。

Yes, Just as easy as capturing the full screen.是的,就像捕获全屏一样简单。 You just use GetWindowDC() on the required window rather than GetDesktopWindow() , then BitBlt() from that to your target DC.您只需在所需窗口上使用GetWindowDC()而不是GetDesktopWindow() ,然后使用BitBlt()从该窗口到您的目标 DC。 You can also get the correct size by using GetWindowRect() .您还可以使用GetWindowRect()获得正确的大小。

Note that this method also allows you to capture from hidden/covered windows where a full screenshot with a bounding rectangle doesn't.请注意,此方法还允许您从隐藏/覆盖的窗口中捕获带有边界矩形的完整屏幕截图。

See this question for some more details.有关更多详细信息,请参阅此问题

There is a new API in Windows 10 in winrt/Windows.Graphics.Capture.h There is a very complex example here by Microsoft: https://github.com/microsoft/Windows.UI.Composition-Win32-Samples/tree/master/cpp/ScreenCaptureforHWND Windows 10 中有一个新的 API,在winrt/Windows.Graphics.Capture.h微软这里有一个非常复杂的例子: https : //github.com/microsoft/Windows.UI.Composition-Win32-Samples/tree/ master/cpp/Scr​​eenCaptureforHWND

I wanted to create some dirty example (very dirty) and in my example, you have a simple function that gets HWND of a window and captures it, and saves it as ScreenShot.bmp我想创建一些脏示例(非常脏),在我的示例中,您有一个简单的函数可以获取窗口的HWND并捕获它,并将其保存为ScreenShot.bmp

Note that the error handling in this function is bad.请注意,此函数中的错误处理很糟糕。 You are welcome to improve this answer!欢迎您改进此答案!

NOTES:笔记:

Code:代码:

#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;
}

How to use:如何使用:

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

When I want to draw the screen of a game on a notepad, only the black screen will be displayed by using the following method.当我想在记事本上绘制游戏画面时,使用以下方法只会显示黑屏

hdcGame := GetDC(hwndGame)
BitBlt(hdcNotpad, 0, 0, rectGame.Width(), rectGame.Height(),
       hdcGame, 0, 0,
       SRCCOPY
)

So I try @Roman R.'s suggestion to capture the desktop DC GetDC(GetDesktopWindow()) and then calculate the target position GetWindowRect of our screen and draw only that position. BitBlt or StretchBlt所以我尝试@Roman R. 的建议来捕获桌面 DC GetDC(GetDesktopWindow())然后计算我们屏幕的目标 position GetWindowRect并仅绘制 position.BitBlt StretchBlt

Example例子

The following code is implemented by go以下代码由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,
    )
}

other example:其他例子:

  1. Capture the Calculator's screen every 0.1 seconds and then draw it on the notepad: source link每 0.1 秒捕获一次计算器的屏幕,然后将其绘制在记事本上: 源链接

  2. Press F9 can capture the specific device screen and show it on the notepad.按 F9 可以捕获特定设备屏幕并将其显示在记事本上。 source link 源链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM