Click here to Skip to main content
15,886,362 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.9K   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
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 
Hi,
I wrote previously that Windows OS is not real time OS - as a result it is impossible get high precision timing. It is possible to get timing enough close to 33 ms - for example use Multimedia Class Scheduler Service. It is developed for :
Quote:
The Multimedia Class Scheduler service (MMCSS) enables multimedia applications to ensure that their time-sensitive processing receives prioritized access to CPU resources. This service enables multimedia applications to utilize as much of the CPU as possible without denying CPU resources to lower-priority applications.

However, it needs deep understanding low level programming on Windows OS. Another way to use timer with 1/4 duration - the idea to divide 33 ms on 4 ticks of timer. On the first tick measure start time, on the next ticks measure current time. If the difference between of start time and current time will equal or more 33 ms than start screen grabbing and record current time as start time for the next ticks. The difference between start time - current time duration time and 33 ms time is stored as delta time and is used for calibration of the next ticks - for example if the first 4 ticks take 40 ms then the next 4 ticks must have duration about 26 ms - as a result an average duration of 4 ticks will 33 ms.

About:
surfman19 wrote:
also each recorded images is around 4MB. is there a way to record with lower resolution? if not do you suggest some compression algorithm (which is fast)? ... i still need to measure how long storing the image on the SSD takes...

I cannot understand what do you really need. Windows Desktop Duplication API is used for effective implementation of a remote desktop application and it does not include image processing. If you really want to decrease resolution then I can recommend use DirectCompute technique - it allows to sample image into the low resolution image in VideoMemory of graphic card GPU - it is more effective than coping image from video memory into the system memory and process on CPU. If you want to compress image then you must use DirectShow or Media Foundation frameworks. Only they can have effective implementation of video encoders. Another way - you can use VideoLAN open source solution. Theoretically it is possible write JPEG compression algorithm on DirectCompute or OpenCL and executes main part of code on GPU - it allows to stream image in MotionJPEG format.
Regards,
Evgeny Pereguda

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 
QuestionZip file can't be UnZipped Pin
xhubobo4-Aug-16 15:12
xhubobo4-Aug-16 15:12 
AnswerRe: Zip file can't be UnZipped Pin
Evgeny Pereguda4-Aug-16 15:16
Evgeny Pereguda4-Aug-16 15:16 

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.