利用放大库进行屏幕截图

在Windows操作系统中,经常需要进行屏幕截图。通常情况下,可能会使用一些常见的方法,如使用BitBlt函数。但是,如果需要排除特定的窗口,或者在Windows 7或Windows 8等较新的操作系统中,BitBlt函数可能无法满足需求。因此,需要寻找新的方法来实现这一功能。

本文将介绍一种使用WindowsVista及以上版本中的放大库(Magnification library)进行屏幕截图的方法。这种方法不适用于WindowsXP,但随着越来越多的用户从XP升级到更新的Windows版本,这并不是一个大问题。

示例程序中只包含一个“捕获”按钮。当用户点击此按钮时,程序将捕获屏幕截图,并弹出一个对话框,让用户指定文件名,并将捕获的图像保存为位图文件。

使用放大库

放大库的完整文档可在MSDN上找到。根据文档,屏幕捕获过程可以通过以下步骤简单完成:

使用MagInitialize函数初始化放大库。

if (!MagInitialize()) { return FALSE; }

创建具有分层属性的宿主对话框,并将其设置为全屏且不可见。使用MFC创建对话框,并将其用作宿主对话框。因为用它来存储捕获的图像,但不想将其显示给用户,所以将其隐藏。

// 获取屏幕分辨率 RECT rect; HWND hDesktop = ::GetDesktopWindow(); ::GetWindowRect(hDesktop, ▭); // 设置窗口位置 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);

将放大窗口作为宿主窗口的子窗口创建。注意,窗口类是MagnifierWindow。如果想捕获鼠标光标,创建放大窗口时启用MS_SHOWMAGNIFIEDCURSOR。

hwndMag = CreateWindow(WC_MAGNIFIER, TEXT("MagnifierWindow"), WS_CHILD | MS_SHOWMAGNIFIEDCURSOR | WS_VISIBLE, 0, 0, m_ScreenX, m_ScreenY, hostDlg->GetSafeHwnd(), NULL, hInstance, NULL);

调用MagSetWindowFilterList函数,从捕获中排除指定窗口。由于这个强大的功能,这是想要使用放大库的主要原因。在这个程序中,过滤了主对话框,但可以根据需要过滤更多。

// 设置过滤列表以排除主窗口 pFilterList = new HWND[1]; pFilterList[0] = this->GetSafeHwnd(); if (!MagSetWindowFilterList(hwndMag, MW_FILTERMODE_EXCLUDE, 1, pFilterList)) { return; }

每当调用MagSetWindowSource函数时,整个桌面都会被捕获到放大窗口中。在上述代码中,将宿主窗口设置为不可见,但如果显示宿主窗口,将看到桌面的图像在其中。

// 获取屏幕矩形 RECT sourceRect; sourceRect.top = 0; sourceRect.left = 0; sourceRect.right = m_ScreenX; sourceRect.bottom = m_ScreenY; if (!MagSetWindowSource(hwndMag, sourceRect)) { return; }

保存数据

通常,可以通过使用BitBlt函数将窗口内容保存到文件中,或者将其内容复制到内存中。使用放大库捕获数据的主要问题是,无法像通常那样使用BitBlt函数访问宿主窗口或放大窗口的位图。因此,使用MagSetImageScalingCallback函数作为解决方案。

// 设置回调函数 if (!MagSetImageScalingCallback(hwndMag, (MagImageScalingCallback)MagImageScaling)) { return FALSE; }

回调函数的语法如下:

BOOL MagImageScaling(HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader, void *destdata, MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty)

srcdata参数是指向位图源的指针,srcheader包含捕获图像的信息,因此可以使用这两个参数来创建BITMAPINFOHEADER和BITMAPFILEHEADER。

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;

位图文件头可以设置如下:

// 设置位图文件头 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) { // 获取交换行的地址 pStart = pData + (lineStart * lineSize); pEnd = pData + (lineEnd * lineSize); // 交换顶部与底部 memcpy(pLineData, pStart, lineSize); memcpy(pStart, pEnd, lineSize); memcpy(pEnd, pLineData, lineSize); // 调整行索引 lineStart++; lineEnd--; } delete pLineData;

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

// 打开文件 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();
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485