Click here to Skip to main content
15,881,027 members
Articles / Multimedia / DirectX
Tip/Trick

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

Rate me:
Please Sign up or sign in to vote.
5.00/5 (11 votes)
2 Aug 2016CPOL3 min read 119.6K   2.7K   12   49
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:

Image 1

Object lAcquiredDesktopImage is gotten by code:

C++
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:

C++
// 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:

C++
// 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:
    C++
    // 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:
    C++
    // 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:

C++
// 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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Australia Australia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda22-Oct-16 19:08
Evgeny Pereguda22-Oct-16 19:08 
GeneralRe: How to do it in infitie loop ? Pin
surfman1922-Oct-16 19:20
surfman1922-Oct-16 19:20 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda22-Oct-16 19:31
Evgeny Pereguda22-Oct-16 19:31 
GeneralRe: How to do it in infitie loop ? Pin
surfman1922-Oct-16 19:41
surfman1922-Oct-16 19:41 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda22-Oct-16 19:50
Evgeny Pereguda22-Oct-16 19:50 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda22-Oct-16 19:59
Evgeny Pereguda22-Oct-16 19:59 
GeneralRe: How to do it in infitie loop ? Pin
surfman1923-Oct-16 9:07
surfman1923-Oct-16 9:07 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda23-Oct-16 15:24
Evgeny Pereguda23-Oct-16 15:24 
Hi,

I fix your code - it is workable:
screen_capture.
From previous discuss I see that you have some problems with multi-threading programming and this code is linear - it takes screen shots in loop with 60 circles. It allows simplify code and allows to use it without timer, but the bottleneck of code is the section for writing image into the file on hard-disk. Timer can launch each screen shot in the separated thread and the section for writing image into the file on hard-disk can be executed without influence on the next screen shot capturing. However, it needs deep knowledge about multi-threading programming.
Regards,
Evgeny Pereguda

GeneralRe: How to do it in infitie loop ? Pin
surfman1924-Oct-16 4:11
surfman1924-Oct-16 4:11 
GeneralRe: How to do it in infitie loop ? Pin
surfman1924-Oct-16 11:20
surfman1924-Oct-16 11:20 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda24-Oct-16 14:21
Evgeny Pereguda24-Oct-16 14:21 
GeneralRe: How to do it in infitie loop ? Pin
surfman1924-Oct-16 16:32
surfman1924-Oct-16 16:32 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda24-Oct-16 23:00
Evgeny Pereguda24-Oct-16 23:00 
GeneralRe: How to do it in infitie loop ? Pin
surfman1925-Oct-16 3:54
surfman1925-Oct-16 3:54 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda25-Oct-16 13:13
Evgeny Pereguda25-Oct-16 13:13 
GeneralRe: How to do it in infitie loop ? Pin
surfman1926-Oct-16 17:30
surfman1926-Oct-16 17:30 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda26-Oct-16 17:57
Evgeny Pereguda26-Oct-16 17:57 
GeneralRe: How to do it in infitie loop ? Pin
surfman1931-Oct-16 9:24
surfman1931-Oct-16 9:24 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda31-Oct-16 13:13
Evgeny Pereguda31-Oct-16 13:13 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda22-Oct-16 18:19
Evgeny Pereguda22-Oct-16 18:19 
QuestionRe Pin
xhubobo4-Aug-16 15:42
xhubobo4-Aug-16 15:42 
AnswerRe: Re Pin
xhubobo4-Aug-16 15:48
xhubobo4-Aug-16 15:48 
GeneralRe: Re Pin
Evgeny Pereguda4-Aug-16 15:50
Evgeny Pereguda4-Aug-16 15:50 
GeneralRe: Re Pin
xhubobo4-Aug-16 15:59
xhubobo4-Aug-16 15:59 
GeneralRe: Re Pin
xhubobo4-Aug-16 16:07
xhubobo4-Aug-16 16:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.