Click here to Skip to main content
14,550,567 members

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

Rate this:
4.90 (9 votes)
Please Sign up or sign in to vote.
4.90 (9 votes)
2 Aug 2016CPOL
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:

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

Share

About the Author

Evgeny Pereguda
Software Developer
Australia Australia
No Biography provided

Comments and Discussions

 
QuestionCursor captured twice Pin
Super Garrison14-May-20 16:07
MemberSuper Garrison14-May-20 16:07 
QuestionEfficiently Share Date Over Wire Pin
umitcel29-Jan-17 5:46
Memberumitcel29-Jan-17 5:46 
AnswerRe: Efficiently Share Date Over Wire Pin
Evgeny Pereguda29-Jan-17 14:47
MemberEvgeny Pereguda29-Jan-17 14:47 
QuestionNot working when NVIDIA is primary graphics card Pin
Member 1280815529-Oct-16 21:45
MemberMember 1280815529-Oct-16 21:45 
AnswerRe: Not working when NVIDIA is primary graphics card Pin
Evgeny Pereguda30-Oct-16 12:02
MemberEvgeny Pereguda30-Oct-16 12:02 
GeneralRe: Not working when NVIDIA is primary graphics card Pin
Member 1280815531-Oct-16 18:10
MemberMember 1280815531-Oct-16 18:10 
GeneralRe: Not working when NVIDIA is primary graphics card Pin
Evgeny Pereguda31-Oct-16 18:16
MemberEvgeny Pereguda31-Oct-16 18:16 
QuestionHow to do it in infitie loop ? Pin
Member 126420268-Aug-16 21:32
MemberMember 126420268-Aug-16 21:32 
AnswerRe: How to do it in infitie loop ? Pin
Evgeny Pereguda8-Aug-16 22:12
MemberEvgeny Pereguda8-Aug-16 22:12 
GeneralRe: How to do it in infitie loop ? Pin
surfman1922-Oct-16 18:06
Membersurfman1922-Oct-16 18:06 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda22-Oct-16 18:12
MemberEvgeny Pereguda22-Oct-16 18:12 
GeneralRe: How to do it in infitie loop ? Pin
surfman1922-Oct-16 18:20
Membersurfman1922-Oct-16 18:20 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda22-Oct-16 18:25
MemberEvgeny Pereguda22-Oct-16 18:25 
GeneralRe: How to do it in infitie loop ? Pin
surfman1922-Oct-16 18:33
Membersurfman1922-Oct-16 18:33 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda22-Oct-16 18:43
MemberEvgeny Pereguda22-Oct-16 18:43 
GeneralRe: How to do it in infitie loop ? Pin
surfman1922-Oct-16 18:59
Membersurfman1922-Oct-16 18:59 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda22-Oct-16 19:08
MemberEvgeny Pereguda22-Oct-16 19:08 
GeneralRe: How to do it in infitie loop ? Pin
surfman1922-Oct-16 19:20
Membersurfman1922-Oct-16 19:20 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda22-Oct-16 19:31
MemberEvgeny Pereguda22-Oct-16 19:31 
GeneralRe: How to do it in infitie loop ? Pin
surfman1922-Oct-16 19:41
Membersurfman1922-Oct-16 19:41 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda22-Oct-16 19:50
MemberEvgeny Pereguda22-Oct-16 19:50 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda22-Oct-16 19:59
MemberEvgeny Pereguda22-Oct-16 19:59 
GeneralRe: How to do it in infitie loop ? Pin
surfman1923-Oct-16 9:07
Membersurfman1923-Oct-16 9:07 
GeneralRe: How to do it in infitie loop ? Pin
Evgeny Pereguda23-Oct-16 15:24
MemberEvgeny Pereguda23-Oct-16 15:24 
GeneralRe: How to do it in infitie loop ? Pin
surfman1924-Oct-16 4:11
Membersurfman1924-Oct-16 4:11 

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.

Tip/Trick
Posted 2 Aug 2016

Stats

62.2K views
1.8K downloads
10 bookmarked