Introduction
This article presents description of using of Windows Desktop Duplication API for capturing desktop screen image. This article can be interesting, because it presents a solution with drawing of current image of cursor into the captured desktop screen image.
Background
This article presents my solution for capturing of desktop screen image. I have spent much time on research of functionality for capturing of desktop screen image on Windows OSs. All successful solutions have become of parts of my project CaptureManager SDK. It includes three implementations on GDI API, DirectX9 API and Desktop Duplication API. Implementations on GDI API, DirectX9 API are well known and can be found on this site, but with Desktop Duplication API, I faced a problem - there was only one example for working with that API - Desktop Duplication Sample. It includes complex code with multithreading capturing and drawing desktop image. It DOES NOT allow to get raw data image and DOES NOT allow easy draw image of cursor into the raw data image. After some research, I have developed solution with ONE LINEAR thread capturing of desktop screen image and drawing image of cursor into the raw data image. I think that such article and code allows to simplify integration of Desktop Duplication API into your projects.
Using the Code
I have researched code of Desktop Duplication Sample and found its difficult for integration into my project CaptureManager SDK. I needed more linear code processing with getting raw data. Another problem was drawing image of cursor. Desktop Duplication Sample presents image of cursor on duplicated desktop image via drawing in DirectX11. I needed a more simple and effective solution. I have decided to use THREE ID3D11Texture2D
objects for three stages of processing:
- Getting desktop image -
lAcquiredDesktopImage
- Drawing image of cursor -
lGDIImage
- Getting raw data of image -
lDestImage
Interacting between these objects can be presented in the next image:
Object lAcquiredDesktopImage
is gotten by code:
CComPtrCustom<IDXGIResource> lDesktopResource;
DXGI_OUTDUPL_FRAME_INFO lFrameInfo;
int lTryCount = 4;
do
{
Sleep(100);
hr = lDeskDupl->AcquireNextFrame(
250,
&lFrameInfo,
&lDesktopResource);
if (SUCCEEDED(hr))
break;
if (hr == DXGI_ERROR_WAIT_TIMEOUT)
{
continue;
}
else if (FAILED(hr))
break;
} while (--lTryCount > 0);
if (FAILED(hr))
break;
hr = lDesktopResource->QueryInterface(IID_PPV_ARGS(&lAcquiredDesktopImage));
if (FAILED(hr))
break;
Object lGDIImage
is gotten by code:
lDeskDupl->GetDesc(&lOutputDuplDesc);
D3D11_TEXTURE2D_DESC desc;
desc.Width = lOutputDuplDesc.ModeDesc.Width;
desc.Height = lOutputDuplDesc.ModeDesc.Height;
desc.Format = lOutputDuplDesc.ModeDesc.Format;
desc.ArraySize = 1;
desc.BindFlags = D3D11_BIND_FLAG::D3D11_BIND_RENDER_TARGET;
desc.MiscFlags = D3D11_RESOURCE_MISC_GDI_COMPATIBLE;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.MipLevels = 1;
desc.CPUAccessFlags = 0;
desc.Usage = D3D11_USAGE_DEFAULT;
hr = lDevice->CreateTexture2D(&desc, NULL, &lGDIImage);
if (FAILED(hr))
break;
if (lGDIImage == nullptr)
break;
Object lDestImage
is gotten by code:
desc.Width = lOutputDuplDesc.ModeDesc.Width;
desc.Height = lOutputDuplDesc.ModeDesc.Height;
desc.Format = lOutputDuplDesc.ModeDesc.Format;
desc.ArraySize = 1;
desc.BindFlags = 0;
desc.MiscFlags = 0;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.MipLevels = 1;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;
desc.Usage = D3D11_USAGE_STAGING;
hr = lDevice->CreateTexture2D(&desc, NULL, &lDestImage);
if (FAILED(hr))
break;
if (lDestImage == nullptr)
break;
Processing has three stages:
- Getting reference on desktop screen image via
AcquireNextFrame
method.
- Copy desktop screen image from
lAcquiredDesktopImage
into lGDIImage
. lGDIImage
is a special texture - it is created with MiscFlag
- D3D11_RESOURCE_MISC_GDI_COMPATIBLE
. It allows GDI rendering on the surface via IDXGISurface1::GetDC and using Windows function DrawIconEx
for drawing cursor's image:
lImmediateContext->CopyResource(lGDIImage, lAcquiredDesktopImage);
CComPtrCustom<IDXGISurface1> lIDXGISurface1;
hr = lGDIImage->QueryInterface(IID_PPV_ARGS(&lIDXGISurface1));
if (FAILED(hr))
break;
CURSORINFO lCursorInfo = { 0 };
lCursorInfo.cbSize = sizeof(lCursorInfo);
auto lBoolres = GetCursorInfo(&lCursorInfo);
if (lBoolres == TRUE)
{
if (lCursorInfo.flags == CURSOR_SHOWING)
{
auto lCursorPosition = lCursorInfo.ptScreenPos;
auto lCursorSize = lCursorInfo.cbSize;
HDC lHDC;
lIDXGISurface1->GetDC(FALSE, &lHDC);
DrawIconEx(
lHDC,
lCursorPosition.x,
lCursorPosition.y,
lCursorInfo.hCursor,
0,
0,
0,
0,
DI_NORMAL | DI_DEFAULTSIZE);
lIDXGISurface1->ReleaseDC(nullptr);
}
}
- Copy desktop screen image from
lGDIImage
into lDestImage
. lDestImage
is a special texture - it is created with CPUAccessFlags
- D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE
. It allows copy raw data from texture into the local memory of application:
lImmediateContext->CopyResource(lDestImage, lGDIImage);
At the end of the application, image is copied from texture and saved into the file ScreenShot.bmp:
D3D11_MAPPED_SUBRESOURCE resource;
UINT subresource = D3D11CalcSubresource(0, 0, 0);
lImmediateContext->Map(lDestImage, subresource, D3D11_MAP_READ_WRITE, 0, &resource);
BITMAPINFO lBmpInfo;
ZeroMemory(&lBmpInfo, sizeof(BITMAPINFO));
lBmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
lBmpInfo.bmiHeader.biBitCount = 32;
lBmpInfo.bmiHeader.biCompression = BI_RGB;
lBmpInfo.bmiHeader.biWidth = lOutputDuplDesc.ModeDesc.Width;
lBmpInfo.bmiHeader.biHeight = lOutputDuplDesc.ModeDesc.Height;
lBmpInfo.bmiHeader.biPlanes = 1;
lBmpInfo.bmiHeader.biSizeImage = lOutputDuplDesc.ModeDesc.Width
* lOutputDuplDesc.ModeDesc.Height * 4;
std::unique_ptr<BYTE> pBuf(new BYTE[lBmpInfo.bmiHeader.biSizeImage]);
UINT lBmpRowPitch = lOutputDuplDesc.ModeDesc.Width * 4;
BYTE* sptr = reinterpret_cast<BYTE*>(resource.pData);
BYTE* dptr = pBuf.get() + lBmpInfo.bmiHeader.biSizeImage - lBmpRowPitch;
UINT lRowPitch = std::min<UINT>(lBmpRowPitch, resource.RowPitch);
for (size_t h = 0; h < lOutputDuplDesc.ModeDesc.Height; ++h)
{
memcpy_s(dptr, lBmpRowPitch, sptr, lRowPitch);
sptr += resource.RowPitch;
dptr -= lBmpRowPitch;
}
WCHAR lMyDocPath[MAX_PATH];
hr = SHGetFolderPath(nullptr, CSIDL_PERSONAL, nullptr, SHGFP_TYPE_CURRENT, lMyDocPath);
if (FAILED(hr))
break;
std::wstring lFilePath = std::wstring(lMyDocPath) + L"\\ScreenShot.bmp";
FILE* lfile = nullptr;
auto lerr = _wfopen_s(&lfile, lFilePath.c_str(), L"wb");
if (lerr != 0)
break;
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);
ShellExecute(0, 0, lFilePath.c_str(), 0, 0, SW_SHOW);
lresult = 0;
}
Points of Interest
I think that some of the readers of this article can say that using of GDI and DrawIconEx
function cannot be effective. However, I think that using of GDI functionality has effective implementation and I cannot imagine other solutions. Of course, it is possible to create implementation simular Desktop Duplication Sample - create offscreen texture, set that texture as target render texture, define 3D primitive with desktop screen image texturing, then define 3D primitive with cursor's image texturing, then draw first 3D primitive, then draw second 3D primitive, then copy the result offscreen texture from GPU memory into the CPU memory. It is a possible solution, but the result code will be more complex than code in this project and I have doubts about the effectiveness of such solution - for my project CaptureManager SDK time delay is very critical.
History
- 03/08/2016: First published