Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Screenshot using the Magnification library

0.00/5 (No votes)
14 Aug 2013 1  
A simple way to take a screenshot using the Magnification library

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:

  1. Use MagInitialize() function to initialize the magnification library.  
  2. if (!MagInitialize())
    { 
    	return FALSE;
    } 
  3. 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.  
  4. // Get screen resolution
    RECT rect;
    HWND hDesktop = ::GetDesktopWindow();
    ::GetWindowRect(hDesktop, &rect);
    
    // Set window position
    SetWindowPos(NULL, 0, 0, rect.right, rect.bottom, SWP_HIDEWINDOW);
    
    // Set window layered attribute
    SetWindowLong(this->GetSafeHwnd(), GWL_EXSTYLE, 
    	GetWindowLong(this->GetSafeHwnd(), GWL_EXSTYLE) | WS_EX_LAYERED);
    SetLayeredWindowAttributes(0, 255, LWA_ALPHA);  
  5. 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. 
  6. hwndMag = CreateWindow(WC_MAGNIFIER, TEXT("MagnifierWindow"), 
    		WS_CHILD /*| MS_SHOWMAGNIFIEDCURSOR */| WS_VISIBLE,
    		0, 0, m_ScreenX, m_ScreenY, 
    		hostDlg->GetSafeHwnd(), NULL, hInstance, NULL ); 
  7. 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. 
  8. // Setup the filter list to exclude the main window
    pFilterList = new HWND[1];
    pFilterList[0] = this->GetSafeHwnd();
    
    if (!MagSetWindowFilterList(hwndMag, MW_FILTERMODE_EXCLUDE, 1, pFilterList))
    {
    	return;
    }    
  9. 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. 
  10. // Get the screen rectangle
    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.  

  1. Call the MagSetImageScalingCallback() function before performing the screenshot to set the callback function. 
  2. // Set the callback function
    if (!MagSetImageScalingCallback(hwndMag, (MagImageScalingCallback)MagImageScaling))
    { 
    	return FALSE;
    }  
  3. The callback function has the following syntax: 
  4. 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;
// Setup the bitmap info header
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:

// Setup the bitmap file header
BITMAPFILEHEADER bmfh;
LONG offBits = sizeof(BITMAPFILEHEADER) + bmif.biSize;
bmfh.bfType = 0x4d42; // "BM"
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)
{
	// Get the address of the swap line
	pStart = pData + (lineStart * lineSize);
	pEnd = pData + (lineEnd * lineSize);
	// Swap the top with the bottom
	memcpy(pLineData, pStart, lineSize);
	memcpy(pStart, pEnd, lineSize);
	memcpy(pEnd, pLineData, lineSize);
	// Adjust the line index
	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. 

// File open
CFile pFile;
if(!pFile.Open((LPCTSTR)fileName, CFile::modeCreate | CFile::modeWrite))
{
	return;
}
//Write data to file
pFile.Write(&bmfh, sizeof(BITMAPFILEHEADER)); // bitmap file header
pFile.Write(&bmif, sizeof(BITMAPINFOHEADER)); // bitmap info header
pFile.Write(pData, bmif.biSizeImage); // converted bitmap data
// File close
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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here