65.9K
CodeProject 正在变化。 阅读更多。
Home

使用放大库进行屏幕截图

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (8投票s)

2013年6月16日

CPOL

4分钟阅读

viewsIcon

65892

downloadIcon

1962

使用放大库进行屏幕截图的简单方法

引言

我们可能知道有很多方法可以截取桌面屏幕。 但有时我们可能想捕获桌面,而不需要一些指定的窗口。 如果这些窗口是分层的,我们可以在 Windows XP 上运行时使用不带 CAPTUREBLT 标志的 BitBlt 函数。 但现在我们正在运行 Windows 7 或 Windows 8,在这种情况下,BitBlt 函数无法再执行此过滤。 因此,我们可能希望排除更多非分层窗口。 所以我们必须找到一种新的方法来做到这一点。

在这个例子中,我将介绍一种使用 Magnification 库进行截图的方法。 这个库从 Windows Vista 开始可用,所以我们不能在 Windows XP 上使用它。 但是,随着许多用户从 XP 切换到更新版本的 Windows,这不会是一个大问题。

示例程序只包含一个 Capture 按钮。 当用户点击时,它将截取屏幕截图并打开一个对话框,供用户指定文件名并将捕获的图像保存为位图文件。

使用 Magnification 库

该库的完整文档可在 MSDN 上找到。

根据文档,屏幕截图过程可以通过以下步骤简单地完成:

  1. 使用 MagInitialize() 函数初始化 magnification 库。
  2. if (!MagInitialize())
    { 
    	return FALSE;
    } 
  3. 创建带有分层属性的宿主对话框,将其设置为全屏且不可见。 我们使用 MFC 创建一个对话框并将其用作宿主对话框。 因为我们使用它来存储捕获的图像,但不想将其显示给用户,所以我们将其隐藏而不显示。
  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. 将 magnification 窗口创建为宿主的子窗口。 请注意,窗口类是 MagnifierWindow。 如果您希望捕获鼠标光标,请创建启用 MS_SHOWMAGNIFIEDCURSOR 的 magnification 窗口。
  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. 调用 MagSetWindowFilterList() 函数以排除要捕获的指定窗口。 由于这个强大的功能,这是我们想要使用 magnification 库的主要原因。 在这个程序中,我们过滤了主对话框,但我们可以根据需要过滤任意数量的对话框。
  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. 每当调用 MagSetWindowSource() 函数时,整个桌面都会被捕获到 magnification 窗口中。 在上面的代码中,我们将宿主窗口设置为不可见,但如果我们显示宿主窗口,我们将看到桌面图像在其中。
  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;
    }

保存数据

通常,我们可以将窗口的内容保存到文件中,或者使用 BitBlt 函数将其内容复制到内存中。 通过 magnification 库保存捕获的数据的主要问题是,我们无法像往常一样使用 BitBlt 函数访问宿主窗口或 magnification 窗口的位图。 所以我们使用了一个解决方法,即使用 MagSetImageScalingCallback() 函数,如下所述。

  1. 在执行屏幕截图之前调用 MagSetImageScalingCallback() 函数来设置回调函数。
  2. // Set the callback function
    if (!MagSetImageScalingCallback(hwndMag, (MagImageScalingCallback)MagImageScaling))
    { 
    	return FALSE;
    }  
  3. 回调函数具有以下语法:
  4. BOOL MagImageScaling(HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader, 
    					 void *destdata, MAGIMAGEHEADER destheader, 
    					 RECT unclipped, RECT clipped, HRGN dirty)

srcdata 参数是指向位图源的指针,srcheader 包含捕获图像的信息,所以我们可以使用这两个参数来创建 BITMAPINFOHEADERBITMAPFILEHEADER

BITMAPINFOHEADER 可以如下设置:

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; 

BITMAPFILEHEADER 可以如下设置:

// 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; 

对于位图像素,请注意捕获的数据与普通位图数据的顺序相反,因此我们必须在保存之前将其转换为正确的顺序,否则位图将被水平翻转。

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;  

最后一件事是将以上所有内容保存到文件中。 此示例仅支持 BMP 文件,但我们可以使用压缩库将其保存为其他格式。

// 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(); 

使用示例源代码

这个示例源代码是用 Visual Studio 2012 编写的。 因为 magnification 库中使用的函数通常从 Windows Vista 开始可用,所以这段代码应该可以在 Visual Studio 2008 或更高版本中使用。

结论 

这是使用强大的 Magnification 库进行屏幕截图的一个简单方法。 但是,有一个问题需要解决。 MagImageScalingCallback() 函数已被弃用,可能会从较新版本的 Windows 中删除。 所以我们需要找到另一种访问数据的方式。 目前我还没有找到任何解决方案。 如果有人能找到任何方法,请告诉我。

© . All rights reserved.