As @IInspectable said, DWM maintains video surfaces for top-level windows, but not for child windows. Therefore, you can only clip the image of the parent window to the child window by yourself.
The following code captures the image of the child window by capturing the parent window screen and calculating the child window rectangle. The captured image will be displayed in the upper left corner of the screen for immediate viewing.
#include <iostream>
#include <vector>
#include <memory>
#include <Windows.h>
#include <dwmapi.h>
#include <dxgi1_2.h>
#include <d3d11.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Graphics.Capture.h>
#include <windows.graphics.capture.interop.h>
#include <windows.graphics.directx.direct3d11.interop.h>
#pragma comment(lib,"Dwmapi.lib")
#pragma comment(lib,"windowsapp.lib")
using namespace winrt;
using namespace winrt::Windows::Graphics::Capture;
using namespace winrt::Windows::Graphics::DirectX;
using namespace winrt::Windows::Graphics::DirectX::Direct3D11;
//Display the captured image(ignore padding) on the screen. Just for Debug
static void ShowImage(const BYTE* pdata, int width, int height, UINT RowPitch)
{
std::cout << width << 'x' << height << '\n';
HDC hdc = GetDC(0);
HDC memDC = CreateCompatibleDC(hdc);
HBITMAP bitmap = CreateCompatibleBitmap(hdc, RowPitch, height);
SelectObject(memDC, bitmap);
SetBitmapBits(bitmap, height * RowPitch * sizeof(RGBQUAD), pdata);
BitBlt(hdc, 0, 0, width, height, memDC, 0, 0, SRCCOPY);
DeleteObject(bitmap);
DeleteDC(memDC);
ReleaseDC(0, hdc);
}
static void ClipToChildWindow(BYTE* pdata, int parentWidth,int parentHeight, UINT RowPitch, HWND parent, HWND child) {
RECT rect;
GetClientRect(child, &rect);
MapWindowPoints(child, parent, reinterpret_cast<LPPOINT>(&rect), 2);
if (rect.left<0 || rect.top<0 || rect.right>parentWidth || rect.bottom>parentHeight) {
//throw("The child window not be located inside the parent window");
if (rect.left < 0) rect.left = 0;
if (rect.top < 0) rect.top = 0;
if (rect.right > parentWidth) rect.right = parentWidth;
if (rect.bottom > parentHeight) rect.bottom = parentHeight;
}
const int width = rect.right - rect.left;
const int height = rect.bottom - rect.top;
std::vector<BYTE> image(width * height * sizeof(RGBQUAD));
for (BYTE* src = pdata + (rect.left + rect.top * RowPitch) * sizeof(RGBQUAD),
*end = src + height * RowPitch * sizeof(RGBQUAD),
*dst= image.data();
src < end;
src += RowPitch * sizeof(RGBQUAD),dst+=width* sizeof(RGBQUAD)) {
memcpy(dst, src, width * sizeof(RGBQUAD));
}
ShowImage(image.data(), width, height, width);
}
void CALLBACK CountdownTimerProc(HWND unnamedParam1, UINT unnamedParam2, UINT_PTR unnamedParam3, DWORD unnamedParam4) {
static int time_left = 10;
--time_left;
printf("\rCountdown:%ds ", time_left);
if (time_left == 0) {
PostQuitMessage(0);
}
}
void CaptureChildWindow(HWND hwndTarget, HWND hwndChild)
{
winrt::init_apartment(apartment_type::multi_threaded);
winrt::com_ptr<ID3D11Device> d3dDevice;
HRESULT hr = D3D11CreateDevice(
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
nullptr, 0, D3D11_SDK_VERSION,
d3dDevice.put(), nullptr, nullptr);
if (FAILED(hr)) { std::cerr << "D3D11CreateDevice failed.\n"; return; }
winrt::com_ptr<ID3D11DeviceContext> d3dContext;
d3dDevice->GetImmediateContext(d3dContext.put());
if (!d3dContext) { std::cerr << "Failed to get D3D context.\n"; return; }
auto dxgiDevice = d3dDevice.as<IDXGIDevice>();
winrt::com_ptr<IInspectable> inspectable;
hr = CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.get(), inspectable.put());
if (FAILED(hr)) { std::cerr << "CreateDirect3D11DeviceFromDXGIDevice failed.\n"; return; }
IDirect3DDevice device = inspectable.as<IDirect3DDevice>();
RECT rect{};
hr = DwmGetWindowAttribute(hwndTarget, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(RECT));
if (FAILED(hr)) { std::cerr << "DwmGetWindowAttribute failed.\n"; return; }
winrt::Windows::Graphics::SizeInt32 frameSize{ rect.right - rect.left, rect.bottom - rect.top };
auto interopFactory = get_activation_factory<GraphicsCaptureItem>().as<IGraphicsCaptureItemInterop>();
GraphicsCaptureItem item = nullptr;
hr = interopFactory->CreateForWindow(
hwndTarget,
__uuidof(ABI::Windows::Graphics::Capture::IGraphicsCaptureItem),
reinterpret_cast<void**>(put_abi(item)));
if (FAILED(hr) || !item) { std::cerr << "CreateForWindow failed.\n"; return; }
auto framePool = Direct3D11CaptureFramePool::Create(
device,
DirectXPixelFormat::B8G8R8A8UIntNormalized,
2,
frameSize);
auto session = framePool.CreateCaptureSession(item);
session.IsCursorCaptureEnabled(false);
winrt::com_ptr<ID3D11Texture2D> reusableStagingTexture;
std::vector<BYTE> imageBuffer;
// FrameArrived callback
framePool.FrameArrived([=, &reusableStagingTexture, &imageBuffer, &frameSize, &framePool](auto& pool, auto&)
{
auto frame = pool.TryGetNextFrame();
if (!frame) return;
auto newSize = frame.ContentSize();
if (newSize.Width != frameSize.Width || newSize.Height != frameSize.Height)
{
std::cout << "Frame size changed: " << newSize.Width << "x" << newSize.Height << "\n";
frameSize = newSize;
framePool.Recreate(
device,
DirectXPixelFormat::B8G8R8A8UIntNormalized,
2,
frameSize);
reusableStagingTexture = nullptr;
return;
}
auto surface = frame.Surface();
struct __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")) IDirect3DDxgiInterfaceAccess : IUnknown {
virtual HRESULT __stdcall GetInterface(GUID const& id, void** object) = 0;
};
auto access = surface.as<IDirect3DDxgiInterfaceAccess>();
winrt::com_ptr<ID3D11Texture2D> texture;
HRESULT hr = access->GetInterface(__uuidof(ID3D11Texture2D), texture.put_void());
if (FAILED(hr)) { std::cerr << "GetInterface(ID3D11Texture2D) failed.\n"; return; }
// Check if staging texture needs to be rebuilt
D3D11_TEXTURE2D_DESC desc;
texture->GetDesc(&desc);
bool needNewTexture = false;
if (!reusableStagingTexture)
{
needNewTexture = true;
}
else
{
D3D11_TEXTURE2D_DESC existingDesc;
reusableStagingTexture->GetDesc(&existingDesc);
if (existingDesc.Width != desc.Width || existingDesc.Height != desc.Height)
needNewTexture = true;
}
if (needNewTexture)
{
desc.Usage = D3D11_USAGE_STAGING;
desc.BindFlags = 0;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
desc.MiscFlags = 0;
hr = d3dDevice->CreateTexture2D(&desc, nullptr, reusableStagingTexture.put());
if (FAILED(hr)) { std::cerr << "CreateTexture2D for staging failed.\n"; return; }
}
d3dContext->CopyResource(reusableStagingTexture.get(), texture.get());
D3D11_MAPPED_SUBRESOURCE mapped{};
hr = d3dContext->Map(reusableStagingTexture.get(), 0, D3D11_MAP_READ, 0, &mapped);
if (FAILED(hr)) { std::cerr << "Map failed.\n"; return; }
ClipToChildWindow((BYTE*)mapped.pData, frameSize.Width, frameSize.Height, mapped.RowPitch / 4, hwndTarget, hwndChild);
/*This code is used to capture the full window image, include padding
size_t totalBytes = mapped.RowPitch * desc.Height;
if (imageBuffer.size() != totalBytes)
imageBuffer.resize(totalBytes);
memcpy(imageBuffer.data(), mapped.pData, totalBytes);
ShowImage(imageBuffer.data(), desc.Width, desc.Height, mapped.RowPitch / 4);
*/
d3dContext->Unmap(reusableStagingTexture.get(), 0);
});
session.StartCapture();
MSG msg;
UINT_PTR timerId = SetTimer(nullptr, 1, 1000, CountdownTimerProc);
while (GetMessage(&msg, nullptr, 0, 0))
{
DispatchMessage(&msg);
}
KillTimer(nullptr, timerId);
session.Close();
framePool.Close();
}
int main() {
HWND parent = FindWindowW(L"Notepad",nullptr);
HWND child = FindWindowExW(parent,nullptr,L"NotepadTextBox", nullptr);
if (!parent || !child) {
std::cerr << "FindWindow failed";
return -1;
}
CaptureChildWindow(parent, child);
return 0;
}