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.
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 , CDCGuard &captureGuard , CBitMapGuard & bmpGuard , HGDIOBJ & originalBmp , 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_);
}
}
So, here is the function for capturing screen of monitor:
void CaptureDesktop(CDCGuard &desktopGuard , CDCGuard &captureGuard , CBitMapGuard & bmpGuard , HGDIOBJ & originalBmp , 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;
HDC hCaptureDC = CreateCompatibleDC(desktopGuard.get());
if (!hCaptureDC)
{
throw std::runtime_error("CaptureDesktop: CreateCompatibleDC failed");
}
captureGuard.reset(hCaptureDC);
HBITMAP hCaptureBmp = CreateCompatibleBitmap
(desktopGuard.get(), nScreenWidth, nScreenHeight);
if(!hCaptureBmp)
{
throw std::runtime_error("CaptureDesktop: CreateCompatibleBitmap failed");
}
bmpGuard.reset(hCaptureBmp);
originalBmp = SelectObject(hCaptureDC, hCaptureBmp);
if (!originalBmp || (originalBmp == (HBITMAP)GDI_ERROR))
{
throw std::runtime_error("CaptureDesktop: SelectObject failed");
}
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:
- Enumerating display monitors using the
EnumDisplayMonitors()
function. - Creating a screenshot from each enumerated monitor using the
CaptureDesktop ()
function, which was described above. - 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, LPCRECT lprcClip, MONITORENUMPROC lpfnEnum, LPARAM dwData );
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");
}
}
};
where MonitorEnumProc
is a callback function:
BOOL CALLBACK MonitorEnumProc(
HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData )
{
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");
}
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.
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):