Click here to Skip to main content
15,886,689 members
Articles / Programming Languages / C++

Creation of Multi-monitor Screenshots Using WinAPI

,
Rate me:
Please Sign up or sign in to vote.
4.90/5 (17 votes)
12 Aug 2010CPOL3 min read 54.9K   2.2K   45   3
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.

virtual-screen-shot/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:

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

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

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

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

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

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

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

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

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

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

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

virtual-screen-shot/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):

virtual-screen-shot/virtualScreen_spliced.png

License

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


Written By
Chief Technology Officer Apriorit Inc.
United States United States
ApriorIT is a software research and development company specializing in cybersecurity and data management technology engineering. We work for a broad range of clients from Fortune 500 technology leaders to small innovative startups building unique solutions.

As Apriorit offers integrated research&development services for the software projects in such areas as endpoint security, network security, data security, embedded Systems, and virtualization, we have strong kernel and driver development skills, huge system programming expertise, and are reals fans of research projects.

Our specialty is reverse engineering, we apply it for security testing and security-related projects.

A separate department of Apriorit works on large-scale business SaaS solutions, handling tasks from business analysis, data architecture design, and web development to performance optimization and DevOps.

Official site: https://www.apriorit.com
Clutch profile: https://clutch.co/profile/apriorit
This is a Organisation

33 members

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

Comments and Discussions

 
QuestionWindows 10 can't get really resolution if scale larger than 100% Pin
vannes11-Aug-21 22:00
vannes11-Aug-21 22:00 
SuggestionSimpler solution possible by now Pin
dlatikay29-Dec-15 2:17
dlatikay29-Dec-15 2:17 
SuggestionCDCGuard destruction. Pin
Petro Vodopyan20-Mar-12 11:52
Petro Vodopyan20-Mar-12 11:52 

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.