18

Is it possible to screen capture a specific window (also possibly of another process)?

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).

ronag
  • 49,529
  • 25
  • 126
  • 221
  • 7
    It is straightforward: you do a `BitBlt` from desktop DC into your bitmap. For specific window you copy not the entire desktop, but just the rectangle of your interest (which is `GetWindowRect` of your specific window). Yes it can be a window of another process. – Roman R. Jul 01 '12 at 15:04
  • Roman R. How do I find the rectangle of interest? Is there any way to iterate over all windows and find their rectangles? – ronag Jul 01 '12 at 15:05
  • If you have specific window, then you have its `HWND` handle. `GetWindowRect` gives you its screen coordinates. – Roman R. Jul 01 '12 at 15:06
  • I don't have any handle, only the name of the window. – ronag Jul 01 '12 at 15:06
  • 2
    `FindWindow` gets you the `HWND` then. Or, you need to `EnumWindows` to find the one you need, and its handle. – Roman R. Jul 01 '12 at 15:06

4 Answers4

14

Yes it is. All what you need is get handle to window which you want to capture and use WinAPI function PrintWindow for example:

// 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.

Blood
  • 4,126
  • 3
  • 27
  • 37
  • What if this window is not focused or is hidden, behind other windows, can this still work? – Phil Mar 21 '13 at 04:59
  • 2
    How to proceed and save the screenshot (or get the data) after PrintWindow? – mbaros May 11 '17 at 14:26
  • 8
    From the [PrintWindow](https://msdn.microsoft.com/en-us/library/windows/desktop/dd162869.aspx) documentation: *"The application that owns the window referenced by hWnd [...] receives a WM_PRINT message or [...] a WM_PRINTCLIENT message."* - In other words: The source window must implement a `WM_PRINT`/`WM_PRINTCLIENT` message handler. If it doesn't, `PrintWindow` won't work. This is not a general solution. – IInspectable May 11 '17 at 21:43
11

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. You can also get the correct size by using 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.

Community
  • 1
  • 1
Deanna
  • 23,876
  • 7
  • 71
  • 156
  • please answer this https://stackoverflow.com/questions/66007306/how-to-capture-specific-window-using-windows-gdi – Alok Feb 02 '21 at 10:12
2

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

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

Note that the error handling in this function is bad. You are welcome to improve this answer!

NOTES:

  • It should work perfectly for any Window. Even if the window does not have WDA_NONE display affinity (https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowdisplayaffinity)
  • It should work also when the window is offscreen or covered by another window. It will get the full window frame even if part of it is not visible on the screen
  • There is a limitation that you will see a yellow border around the window for a very short time during the capture process

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
gil123
  • 512
  • 6
  • 12
  • Thanks for this code example, it works. However, more issues are that the performance of this screen dumping code is very bad (400 - 800 ms) and that the code is pretty complex to understand. Also it seems to increase compilation time by a lot which is unfortunate. Also, is there a clever way to get a PNG instead of a BMP without writing to the disk and loading it back using e.g. `GDIPlus`? https://stackoverflow.com/questions/68749555 – BullyWiiPlaza Aug 12 '21 at 07:01
  • jfyi, the yellow border limitation can be removed by setting `IsBorderRequired(false)` on session object. – n0p Jan 28 '22 at 13:02
0

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 DCGetDC(GetDesktopWindow()) and then calculate the target positionGetWindowRect of our screen and draw only that position.BitBlt or StretchBlt

Example

The following code is implemented by 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

  2. Press F9 can capture the specific device screen and show it on the notepad. source link

Carson
  • 6,105
  • 2
  • 37
  • 45