Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Desktop Screen Capture on Windows via Windows Desktop Duplication API with Drawing of Cursor's Image

0.00/5 (No votes)
2 Aug 2016 2  
Simple article about using of Desktop Duplication API for capture desktop screen WITH image of cursor

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:

  1. Getting desktop image - lAcquiredDesktopImage
  2. Drawing image of cursor - lGDIImage
  3. 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);

    // Get new frame
    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;

// QI for ID3D11Texture2D

hr = lDesktopResource->QueryInterface(IID_PPV_ARGS(&lAcquiredDesktopImage));

if (FAILED(hr))
    break;

Object lGDIImage is gotten by code:

// Create GUI drawing texture
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:

// Create CPU access texture

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:

  1. Getting reference on desktop screen image via AcquireNextFrame method.
  2. 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:
    // Copy image into GDI drawing texture
    
    lImmediateContext->CopyResource(lGDIImage, lAcquiredDesktopImage);
    
    // Draw cursor image into GDI drawing texture
    
    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);
        }
    }
    
  3. 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:
    // Copy image into CPU access texture
    lImmediateContext->CopyResource(lDestImage, lGDIImage);
    

At the end of the application, image is copied from texture and saved into the file ScreenShot.bmp:

// Copy from CPU access texture to bitmap buffer

D3D11_MAPPED_SUBRESOURCE resource;
UINT subresource = D3D11CalcSubresource(0, 0, 0);
lImmediateContext->Map(lDestImage, subresource, D3D11_MAP_READ_WRITE, 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 = 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;
}

// Save bitmap buffer into the file ScreenShot.bmp

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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here