从屏幕捕获位图






2.98/5 (14投票s)
2006年2月7日
4分钟阅读

81471

1595
位图的结构。
引言
本文演示了如何从屏幕上的图像创建和保存位图,以及如何将一个位计数位图转换为另一个。该项目包含一个位图样本文件夹。不同大小的位图可供选择,名称从 Bitmap1.bmp 到 Bitmap32.bmp。BitmapDemo.bmp 是测试图像,用于创建样本的位图是:fish*.bmp、lake.bmp 和 star.bmp。fish 位图是 32 位的,lake 位图是 8 位的,star 是 4 位的。您会注意到 Bitmap8 到 Bitmap32 具有良好的颜色属性,Bitmap4 因为只使用 8 种颜色而失去了一些颜色,而 Bitmap1 是 2 种颜色,黑色或白色。程序的典型用法是按 Ctrl+O 打开位图文件,按 Ctrl+C 创建图像,按 Ctrl+S 保存文件,或按 Ctrl+A 使用“另存为”将图像保存到位图文件。按 Ctrl+I 和 Ctrl+T 可在文本或位图图像之间切换。默认图像大小为 24。
背景
我于 1998 年 9 月开始使用 Microsoft Visual C++ 6.0。很快我开始使用位图,但从未理解它们是如何创建的或它们的文件的格式。这就是我编写这个程序的原因。它创建标准大小的位图:1、4、8、16、24 和 32 位。后两者,24 位和 32 位,是从图像数组创建的,我从中获取屏幕上的像素,将它们保存在一个 BYTE
数组中,创建位图,然后将其保存到文件中。这些的格式非常简单,文件包含一个 14 字节的 BITMAPFILEHEADER
,后跟 BITMAPINFO
结构,它是一个 40 字节的 BITMAPINFOHEADER
,然后是位图数据,这是 4 字节的 RGB 颜色信息,只使用前三个,第四个被忽略。每个像素使用一个 32 位值。24 位位图与 32 位位图相同,只是没有创建第四个字节,从而为每个像素数据节省了 1 字节。文件格式是小端序,低位字节在前。前三种类型,32、24、16 永不压缩且不使用调色板。16 位位图颜色字段是 5 位的三字节或三个 DWORD
颜色掩码,取决于 BITMAPINFOHEADER
的 biCompression DWORD
值;因此,我决定不为此编写代码,而是使用了 Charles Petzold 的 "Programming Windows Fifth Edition" 中的 DIBBLE 位图和调色板程序。DIBBLE 是一个 "C" 程序,而我使用的是 "C++",这导致了一些问题(参见“趣味点”) 。
使用代码
代码最初是一个 Win32 应用程序,典型的“Hello World”应用程序。为了使代码编写更容易,我决定使用一些 "MFC" 类,例如 CMenu
、CBitmap
、CString
和通用对话框 CFileDialog
。读者请注意:要更改为 "MFC" 应用程序,在 项目设置 菜单的 常规 选项卡中,将其更改为 "在共享 DLL 中使用 MFC",然后在 C\C++ 选项卡中,对于 代码生成,使用 "多线程 DLL"。这些更改必须同时为 Debug 和 Release 配置设置。WinMain
、MyRegisterClass
、InitInstance
和 WndProc
函数是由 Visual Studio C++ Wizard 创建的。其余代码是添加的。消息循环需要进行更改,我正在使用键盘加速器。为了使消息循环能够处理它们并打开相应的菜单项,需要进行以下更改
int APIENTRY WinMain(...) { HACCEL hAccelTable; MSG msg; // // Main message loop // hAccelTable = LoadAccelerators (hInstance, szWindowClass); while (GetMessage (&msg, NULL, 0, 0)) { if (!TranslateAccelerator (msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return msg.wParam; }
这是从屏幕获取像素并创建图像数组的函数
bool CreateImageFromArray(HDC hdc) { long w = m_nWidth; long h = m_nHeight; BYTE c = m_nBitsPerPixel/8; // bitsperpixel. BYTE* pArray = (BYTE*)malloc(w*h*c); int cxCursor = cxClient/2 - w/2; int cyCursor = cyClient/2 - h/2; int cxCapture = cxCursor; int cyCapture = cyCursor; // ========================================== // Get the device context of the desktop // and from it get the color // of the pixel at the current position. // ========================================== BYTE rVal, gVal, bVal; COLORREF m_color; int n, x, y; obTimer.Start(); for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { m_color = ::GetPixel(hdc, cxCapture, cyCapture); rVal = GetRValue(m_color); gVal = GetGValue(m_color); bVal = GetBValue(m_color); n = c* (x + w*y); if (c == 4) // if bitsperpixel = 32. pArray[n + 3] = (BYTE)(0); pArray[n + 2] = (BYTE)(rVal); pArray[n + 1] = (BYTE)(gVal); pArray[n] = (BYTE)(bVal); cxCapture = cxCursor + x; cyCapture = cyCursor + y; } } CreateFromArray(pArray, w, h, 8*c, c*w, true); free(pArray);
在使用非 MFC 生成的程序中的 Afx 支持时遇到的一个问题是,在 Debug 模式下,当步入 CFileDialog(...)
时,您会收到来自 AfxGetResourceHandle()
的异常。我在 MSDN 库中找到了一个解决方法
BOOL PromptForFileName(BOOL bOpenFileDialog) { // Hack for AfxGetResourceHandle Exception HMODULE hMod; hMod = ::GetModuleHandle("BitmapFromScreen.exe"); AfxSetResourceHandle(hMod); BOOL bRet; CString szFilter = "Windows Bitmap Files" " (*.BMP; *.DIB) |*.BMP; *.DIB||"; LPSTR title; if (bOpenFileDialog) { title = "Open image file"; CFileDialog dlg(TRUE, _T("bmp"), _T("*.bmp"), OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, szFilter); dlg.m_ofn.lpstrTitle = title; CenterWindow(dlg.m_hWnd); if (dlg.DoModal() == IDOK) ... ... }
关注点
DIBBLE 的源代码文件都是 "C" 语言。当转换为 "C++" 进行编译时,会出现几个差异,"C++" 不处理 VOID*
,并且所有对 malloc
的调用都必须 强制转换为 适当的类型。所有变量都不会初始化为零。 "C" 例程 qsort
因为 Compare
回调中的 VOID*
而无法在 "C++" 中编译。我编写了一个 qsort
的替代品
// Sorts the array of structures // in decending order, largest first. VOID DibSortPal(BOXES* boxes, int iEntry) { int nCmp; BOXES box; for (int i=0; i<iEntry; i++) for (int j=0; j<iEntry; j++) { nCmp = boxes[j].iBoxCount - boxes[j+1].iBoxCount; if (nCmp < 0) { box.iBoxCount = boxes[j].iBoxCount; box.rgbBoxAv = boxes[j].rgbBoxAv; boxes[j].iBoxCount = boxes[j+1].iBoxCount; boxes[j].rgbBoxAv = boxes[j+1].rgbBoxAv; boxes[j+1].iBoxCount = box.iBoxCount; boxes[j+1].rgbBoxAv = box.rgbBoxAv; } } }
下一个问题是 DIBHELP.C 作者的一个错误,他有这段代码
int cEntries = 0 ; if (cColors != 0) cEntries = cColors ; else if (cBits <= 8) cEntries = 1 << cBits ; // here is the error, if cColors = 0 and cBits // is greater than 8, cEntries will = 0 // the sizeof BITMAPINFOHEADER = 40 + 0 - 1 = - 1 * sizeof of RGBQUAD = 4 // dwInfoSize will = 36, not 40 dwInfoSize = sizeof (BITMAPINFOHEADER) + (cEntries - 1) * sizeof (RGBQUAD) ; // the fix dwInfoSize = sizeof (BITMAPINFOHEADER) + (cEntries - 1) * sizeof (RGBQUAD) + sizeof (RGBQUAD) ; // = 40 // this call to malloc will alocate only 36 bytes. if (NULL == (pbmi = malloc (dwInfoSize))) { return NULL ; } // then: pbmi->bmiHeader.biSize = sizeof (BITMAPINFOHEADER) ; // this is 40. ... ... free (pbmi) // Heap corruption, the previous // operation wrote past the end of the buffer.
我第一次执行这段代码时,cEntries
被定义为 int cEntries;
,这是从 "C" 语言继承过来的,因此 cEntries
等于 0xcdcdcdcd。它试图将 300 多 MB 的数据写入页面文件。
致谢
- Charles Petzold - "Programming Windows - Fifth Edition"。
历史
- 2006 年 2 月 8 日 - 版本 1.0。