Click here to Skip to main content
Click here to Skip to main content

Tagged as

Creation of Multi-monitor Screenshots Using WinAPI

, , 12 Aug 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
This article includes a description of the simple multi-monitor screenshot creation and splicing screenshots from the display monitor into the one virtual screen sized bitmap using Windows GDI functions.

Table of Contents

Introduction

This article describes the process of getting screenshots of all monitors while working in the multi-monitor mode, and locating them in one bitmap programmatically, keeping the arrangement of monitors on a virtual screen. For these purposes, the GDI functions are used in the code. In the final part of the article, the described algorithm is presented as the developed code piece for your attention.

The Virtual Screen

The bounding rectangle of all the monitors is the virtual screen. The desktop covers the virtual screen instead of a single monitor. The following illustration shows a possible arrangement of three monitors (c) MSDN.

virtualScreen.png

The primary monitor (Monitor 1) contains the origin (0,0). The primary monitor does not have to be in the upper left corner of the virtual screen. When it is not there, parts of the virtual screen have negative coordinates.

Creating a Screenshot from the Desktop

It is, definitely, a well known task that has a rather simple implementation. But I just want to remind how to get a screenshot of the current display monitor, especially because it will be necessary when we get screenshots from all display monitors.

The following function uses Windows GDI functions:

void CaptureDesktop(CDCGuard &desktopGuard      // handle to monitor device context (DC)
                    , CDCGuard &captureGuard    // handle to destination DC
                    , CBitMapGuard & bmpGuard   // handle to BITMAP
                    , HGDIOBJ & originalBmp     // handle to GDIOBJ
                    , int * width
                    , int * height
                    , int left
                    , int top);

It implies that we already have a handle to monitor device context (DC) (CDCGuard &desktopGuard in the parameters list). In case of one monitor handle to DC, we get it using the GetDC() function:

HDC hDesktopDC = GetDC(NULL);

Other handles are out parameters, they will serve us for the creation of the bitmap file with the proper BITMAPFILEHEADER. The usage of this function will be described below.

CDCGuard is my class-wrapper that deletes handle to DC in its destructor:

class CDCGuard
{
    HDC h_;
    CDCGuard(const CDCGuard&);
    CDCGuard& operator=(CDCGuard&);
public:
    explicit CDCGuard(HDC h)
        :h_(h){}
        ~CDCGuard(void)
        {
            if(h_)DeleteDC(h_);
        }
        void reset(HDC h)
        {
            if(h_ == h)
                return;
            if(h_)DeleteDC(h_);
            h_ = h;
        }
        void release()
        {
            h_ = 0;
        }
        HDC get()
        {
                return h_;
        }
};

CBitmapGuard is also a class-wrapper and has similar implementation, but deletes the HBITMAP object in its destructor:

class CBitMapGuard
{
    HBITMAP h_;
public:
        ~CBitMapGuard(void)
        {
            if(h_)DeleteObject(h_);
        }
// other methods
}

So, here is the function for capturing screen of monitor:

void CaptureDesktop(CDCGuard &desktopGuard    // handle to monitor DC
                              , CDCGuard &captureGuard    // handle to destination DC
                              , CBitMapGuard & bmpGuard  // handle to BITMAP
                              , HGDIOBJ & originalBmp        // handle to GDIOBJ
                              , int * width
                              , int * height
                              , int left
                              , int top)
{
    unsigned int nScreenWidth=GetDeviceCaps(desktopGuard.get(),HORZRES);
    unsigned int nScreenHeight=GetDeviceCaps(desktopGuard.get(),VERTRES);
    *height = nScreenHeight;
    *width = nScreenWidth;
   
    // Creating a memory device context (DC) compatible with the specified device
    HDC hCaptureDC = CreateCompatibleDC(desktopGuard.get()); 
    if (!hCaptureDC)
    {
        throw std::runtime_error("CaptureDesktop: CreateCompatibleDC failed");
    }
    captureGuard.reset(hCaptureDC);

    // Creating a bitmap compatible with the device 
    // that is associated with the specified DC
    HBITMAP hCaptureBmp = CreateCompatibleBitmap
	(desktopGuard.get(), nScreenWidth, nScreenHeight);
    if(!hCaptureBmp)
    {
        throw std::runtime_error("CaptureDesktop: CreateCompatibleBitmap failed");
    }
    bmpGuard.reset(hCaptureBmp);

    // Selecting an object for the specified DC
    originalBmp = SelectObject(hCaptureDC, hCaptureBmp); 
    if (!originalBmp || (originalBmp == (HBITMAP)GDI_ERROR))
    {
        throw std::runtime_error("CaptureDesktop: SelectObject failed");
    }

    // Blitting the contents of the Desktop DC into the created compatible DC
    if (!BitBlt(hCaptureDC, 0, 0, nScreenWidth, nScreenHeight, 
		desktopGuard.get(), left, top, SRCCOPY|CAPTUREBLT))
    {
        throw std::runtime_error("CaptureDesktop: BitBlt failed"
		);
    }
}

Enumerating Desktops and Splicing Images

Getting one image from all display monitors is a rather easy and small task. We have to get the DC from each monitor on the virtual screen, and then capture its content. The steps are the following:

  1. Enumerating display monitors using the EnumDisplayMonitors() function.
  2. Creating a screenshot from each enumerated monitor using the CaptureDesktop () function, which was described above.
  3. Splicing screenshots from all monitors into the single virtual screen sized bitmap.

Declaration of the EnumDisplayMonitors() Windows GDI function is the following:

BOOL EnumDisplayMonitors(
  HDC hdc,                    // handle to display DC 
  LPCRECT lprcClip,           // clipping rectangle 
  MONITORENUMPROC lpfnEnum,   // callback function
  LPARAM dwData               // data for callback function 
); 

In this article and in the project code, LPARAM dwData is a pointer to my class encapsulating the list of pairs of handles to the monitors’ DCs and corresponding monitors’ coordinates (RECT structure):

typedef std::vector<std::pair<HDC, RECT>> HDCPoolType;

The EnumDisplayMonitors() function is called in its constructor, and the class implementation provides the treatment with the declared above list (HDCPoolType) for the safety and convenience.

class CDisplayHandlesPool
{
private:
    HDCPoolType m_hdcPool;

public:
    typedef HDCPoolType::iterator iterator;

    CDisplayHandlesPool()
    {
        guards::CDCGuard captureGuard(0);
        HDC hDesktopDC = GetDC(NULL);
        if (!hDesktopDC)
        {
            throw std::runtime_error("CDisplayHandlesPool: GetDC failed");
        }
        guards::CDCGuard desktopGuard(hDesktopDC);

        if(!EnumDisplayMonitors(hDesktopDC, NULL, MonitorEnumProc, 
				reinterpret_cast<LPARAM>(this)))
        {
            throw std::runtime_error
			("CDisplayHandlesPool: EnumDisplayMonitors failed");
        }
    }

        // Other methods

};

where MonitorEnumProc is a callback function:

BOOL CALLBACK MonitorEnumProc(
  HMONITOR hMonitor,    // handle to display monitor
  HDC hdcMonitor,       // handle to monitor DC
  LPRECT lprcMonitor,   // monitor intersection rectangle
  LPARAM dwData         // data
)
{
    CBitMapGuard bmpGuard(0);
    HGDIOBJ originalBmp = NULL;
    int height = 0;
    int width = 0;
    CDCGuard desktopGuard(hdcMonitor);
    CDCGuard captureGuard(0);

    CaptureDesktop(desktopGuard, captureGuard, bmpGuard, 
	originalBmp, &width, &height, lprcMonitor->left, lprcMonitor->top);

    RECT rect = *lprcMonitor;
    ScreenShooter::CDisplayHandlesPool * hdcPool = 
	reinterpret_cast<ScreenShooter::CDisplayHandlesPool *>(dwData);
    hdcPool->AddHdcToPool(captureGuard, rect);
    return true;
} 

Now, all we need is to merge the captures of all monitors into the single virtual screen sized bitmap. For the creation of the bitmap, the SpliceImages() function follows the same algorithm in the beginning as the CaptureDesktop() function does.Then, we have to copy data from the defined DC to the same DC of the vitrual screen using the BitBlt() function.

void SpliceImages( ScreenShooter::CDisplayHandlesPool * pHdcPool
                        , CDCGuard &captureGuard
                        , CBitMapGuard & bmpGuard
                        , HGDIOBJ & originalBmp
                        , int * width
                        , int * height ) 
{
    HDC hDesktopDC = GetDC(NULL);
    CDCGuard desktopGuard(hDesktopDC);

    unsigned int nScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
    unsigned int nScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
    * width = nScreenWidth;
    * height = nScreenHeight;

    HDC hCaptureDC = CreateCompatibleDC(desktopGuard.get());
    if (!hCaptureDC)
    {
        throw std::runtime_error("SpliceImages: CreateCompatibleDC failed");
    }
    captureGuard.reset(hCaptureDC);

    HBITMAP hCaptureBmp = CreateCompatibleBitmap
			(desktopGuard.get(), nScreenWidth, nScreenHeight);
    if(!hCaptureBmp)
    {
        throw std::runtime_error("SpliceImages: CreateCompatibleBitmap failed");
    }
    bmpGuard.reset(hCaptureBmp);

    originalBmp = SelectObject(hCaptureDC, hCaptureBmp);
    if (!originalBmp || (originalBmp == (HBITMAP)GDI_ERROR))
    {
        throw std::runtime_error("SpliceImages: SelectObject failed");
    }

    // Calculating coordinate shift if any monitor has negative coordinates
    long shiftLeft = 0;
    long shiftTop = 0;
    for(ScreenShooter::HDCPoolType::iterator it = 
			pHdcPool->begin(); it != pHdcPool->end(); ++it)
    {
        if( it->second.left < shiftLeft)
            shiftLeft = it->second.left;
        if(it->second.top < shiftTop)
            shiftTop = it->second.top;
    }

    for(ScreenShooter::HDCPoolType::iterator it = 
			pHdcPool->begin(); it != pHdcPool->end(); ++it)
    {
        if (!BitBlt(hCaptureDC, it->second.left - shiftLeft, 
		it->second.top - shiftTop, it->second.right - it->second.left, 
		it->second.bottom - it->second.top, it->first, 0, 0, SRCCOPY))
        {
            throw std::runtime_error("SpliceImages: BitBlt failed");
        }
    } 
}

The example of the arrangement of the monitors on the virtual screen is presented below. Monitor 2 has a negative top coordinate value. By calculating y coordinate shift, we'll locate the captures of the monitors keeping the location of windows and desktops on the bitmap.

virtualScreen_set.PNG

Conclusion

The result of this sample project work (with the arrangement of the monitors presented on the picture above) is shown on the following picture (we used MaxiVista to emulate the multi-monitor):

virtualScreen_spliced.png

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Authors

Apriorit Inc
Apriorit Inc.
Ukraine Ukraine
ApriorIT is a Software Research and Development company that works in advanced knowledge-intensive scopes.
 
Company offers integrated research&development services for the software projects in such directions as Corporate Security, Remote Control, Mobile Development, Embedded Systems, Virtualization, Drivers and others.
 
Official site http://www.apriorit.com
Group type: Organisation

31 members

Follow on   LinkedIn

Elizaveta Golub

Ukraine Ukraine
No Biography provided

Comments and Discussions

 
SuggestionCDCGuard destruction. [modified] PinmemberPetro Vodopyan20-Mar-12 12:52 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.1411028.1 | Last Updated 12 Aug 2010
Article Copyright 2010 by Apriorit Inc, Elizaveta Golub
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid