Introduction
We may know there are many methods to take a screenshot of the desktop. But sometime we may want to capture the desktop without some specified windows. If these windows are layered, we may use the BitBlt function without the CAPTUREBLT
flag when running on Windows XP. But nowadays we are running into Windows 7 or Windows 8, in which situation the BitBlt
function cannot perform this filter anymore. Therefore, we may want to exclude more non-layered windows. So we have to find a new method to do this.
In this sample, I will introduce a method to take a screenshot with the Magnification library. This library is available from Windows Vista so we cannot use it on Windows XP. But with many user switching from XP to a newer version of Windows, this will not be a huge problem.
The sample program contains only a Capture button. When the user clicks, it will take a screenshot and open a dialog for the user to specify a file name and save the captured image to bitmap file.
Using the Magnification library
The complete documentation of this library is available on MSDN.
According to the documentation, the screen capture process can be simply done by the following steps:
- Use
MagInitialize()
function to initialize the magnification library.
if (!MagInitialize())
{
return FALSE;
}
- Create the host dialog with layered attribute, set it to full screen and invisible. We use MFC to create a dialog and use it as the host dialog. Because we use it to store the captured image,
but don't want to show it to the user, so we hide it from being displayed.
RECT rect;
HWND hDesktop = ::GetDesktopWindow();
::GetWindowRect(hDesktop, &rect);
SetWindowPos(NULL, 0, 0, rect.right, rect.bottom, SWP_HIDEWINDOW);
SetWindowLong(this->GetSafeHwnd(), GWL_EXSTYLE,
GetWindowLong(this->GetSafeHwnd(), GWL_EXSTYLE) | WS_EX_LAYERED);
SetLayeredWindowAttributes(0, 255, LWA_ALPHA);
- Create the magnification window as the child window of the host. Note
that the window class is
MagnifierWindow
. If you want the mouse cursor to be captured, create the magnification window with MS_SHOWMAGNIFIEDCURSOR
enabled.
hwndMag = CreateWindow(WC_MAGNIFIER, TEXT("MagnifierWindow"),
WS_CHILD | WS_VISIBLE,
0, 0, m_ScreenX, m_ScreenY,
hostDlg->GetSafeHwnd(), NULL, hInstance, NULL );
- Call the
MagSetWindowFilterList()
function to exclude specified windows from being captured. Because of this powerful function, this is the main reason we want to use magnification library. In this program, we filter the main dialog, but we may filter as much as we want.
pFilterList = new HWND[1];
pFilterList[0] = this->GetSafeHwnd();
if (!MagSetWindowFilterList(hwndMag, MW_FILTERMODE_EXCLUDE, 1, pFilterList))
{
return;
}
- Whenever the
MagSetWindowSource()
function is called, the entire desktop is captured into the magnification window. In the above code, we set the host window invisible, but if we show the host window, we will see the desktop's image in it.
RECT sourceRect;
sourceRect.top = 0;
sourceRect.left = 0;
sourceRect.right = m_ScreenX;
sourceRect.bottom = m_ScreenY;
if (!MagSetWindowSource(hwndMag, sourceRect))
{
return;
}
Saving the data
Normally, we could save the content of a window into a file, or copy its content into memory by using the BitBlt function. The main problem while saving the captured data by the magnification library is that we cannot access the bitmap of the host window or the magnification window with the BitBlt function as usual. So we use a work around by using the MagSetImageScalingCallback()
function, which is described as below.
- Call the
MagSetImageScalingCallback()
function before performing the screenshot to set the callback function.
if (!MagSetImageScalingCallback(hwndMag, (MagImageScalingCallback)MagImageScaling))
{
return FALSE;
}
- The callback function has the following syntax:
BOOL MagImageScaling(HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader,
void *destdata, MAGIMAGEHEADER destheader,
RECT unclipped, RECT clipped, HRGN dirty)
The srcdata
parameter is the pointer to the bitmap source, and the srcheader
contains the information of the captured image, so we can use these two parameters to create the BITMAPINFOHEADER
and the BITMAPFILEHEADER
.
The BITMAPINFOHEADER
can be set as below:
BITMAPINFOHEADER bmif;
bmif.biSize = sizeof(BITMAPINFOHEADER);
bmif.biHeight = srcheader.height;
bmif.biWidth = srcheader.width;
bmif.biSizeImage = srcheader.cbSize;
bmif.biPlanes = 1;
bmif.biBitCount = (WORD)(bmif.biSizeImage / bmif.biHeight / bmif.biWidth * 8);
bmif.biCompression = BI_RGB;
The BITMAPFILEHEADER
can be set as below:
BITMAPFILEHEADER bmfh;
LONG offBits = sizeof(BITMAPFILEHEADER) + bmif.biSize;
bmfh.bfType = 0x4d42; bmfh.bfOffBits = offBits;
bmfh.bfSize = offBits + bmif.biSizeImage;
bmfh.bfReserved1 = 0;
bmfh.bfReserved2 = 0;
With the bitmap pixels, note that the captured data has reversed order with the normal bitmap data, so we have to convert it to the correct order before saving, otherwise the bitmap will be horizontally flipped.
LONG lineSize = bmif.biWidth * bmif.biBitCount / 8;
BYTE* pLineData = new BYTE[lineSize];
BYTE* pStart;
BYTE* pEnd;
LONG lineStart = 0;
LONG lineEnd = bmif.biHeight - 1;
while (lineStart < lineEnd)
{
pStart = pData + (lineStart * lineSize);
pEnd = pData + (lineEnd * lineSize);
memcpy(pLineData, pStart, lineSize);
memcpy(pStart, pEnd, lineSize);
memcpy(pEnd, pLineData, lineSize);
lineStart++;
lineEnd--;
}
delete pLineData;
The last thing is save all of above to a file. This sample only support BMP files, but we could to save it into other formats by using compression libraries.
CFile pFile;
if(!pFile.Open((LPCTSTR)fileName, CFile::modeCreate | CFile::modeWrite))
{
return;
}
pFile.Write(&bmfh, sizeof(BITMAPFILEHEADER)); pFile.Write(&bmif, sizeof(BITMAPINFOHEADER)); pFile.Write(pData, bmif.biSizeImage); pFile.Close();
Using the sample source code
This sample source code is written with Visual Studio 2012. Because used functions in the magnification library are generally available from Windows Vista, so this code should work with Visual Studio 2008 or above.
Conclusion
This is a simple way to take a screenshot using the powerful Magnification library. However there is a problem that is need to be solved. The MagImageScalingCallback()
function is deprecated, and could be removed from newer versions of Windows. So we need to find another way to access the data. Currently I haven't not found any solution yet. If anyone could find anyway, please let me know.