一、 BMP位图操作
首先我们回顾一下上讲中的重要信息:BMP位图包括位图文件头结构BITMAPFILEHEADER、位图信息头结构BITMAPINFOHEADER、位图颜色表RGBQUAD和位图像素数据四部分。处理位图时要根据文件的这些结构得到位图文件大小、位图的宽、高、实现调色板、得到位图像素值等等。这里要注意的一点是在BMP位图中,位图的每行像素值要填充到一个四字节边界,即位图每行所占的存储长度为四字节的倍数,不足时将多余位用0填充。 有了上述知识,可以开始编写图像处理的程序了,关于在VC的开发平台上如何开发程序的问题这里不再赘述,笔者假定读者都具有一定的VC开发经验。在开发该图像处理程序的过程中,笔者没有采用面向对象的方法,虽然面向对象的方法可以将数据封装起来,保护类中的数据不受外界的干扰,提高数据的安全性,但是这种安全性是以降低程序的执行效率为代价的,为此,我们充分利用了程序的文档视图结构,在程序中直接使用了一些API函数来操作图像。在微软的MSDN中有一个名为Diblook的例子,该例子演示了如何操作Dib位图,有兴趣的读者可以参考一下,相信一定会有所收获。 启动Visual C++,生成一个名为Dib的多文档程序,将CDibView类的基类设为CscrollView类,这样作的目的是为了在显示位图时支持滚动条,另外在处理图像应用程序的文档类(CDibDoc.h)中声明如下宏及公有变量:
#define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4)//计算图像每行象素所占的字节数目; HANDLE m_hDIB;//存放位图数据的句柄; CPalette* m_palDIB;//指向调色板Cpalette类的指针; CSize m_sizeDoc;//初始化视图的尺寸,该尺寸为位图的尺寸; |
最后将程序的字符串表中的字符串资源IDR_DibTYPE修改为:“\nDib\nDib\nDib Files(*.bmp;*.dib)\n.bmp\nDib.Document\nDib Document”。这样作的目的是为了在程序文件对话框中可以选择BMP或DIB格式的位图文件。 1、 读取灰度BMP位图 可以根据BMP位图文件的结构,操作BMP位图文件并读入图像数据,为此我们充分利用了VC的文档视图结构,重载了文挡类的OnOpenDocument()函数,这样用户就可以在自动生成程序的打开文件对话框中选择所要打开的位图文件,然后程序将自动调用该函数执行读取数据的操作。该函数的实现代码如下所示:
BOOL CDibDoc::OnOpenDocument(LPCTSTR lpszPathName) { LOGPALETTE *pPal;//定义逻辑调色板指针; pPal=new LOGPALETTE;//初始化该指针; CFile file; CFileException fe; if (!file.Open(lpszPathName, CFile::modeRead | CFile::shareDenyWrite, &fe)) {//以“读”的方式打开文件; AfxMessageBox("图像文件打不开!"); return FALSE; } DeleteContents();//删除文挡; BeginWaitCursor(); BITMAPFILEHEADER bmfHeader;//定义位图文件头结构; LPBITMAPINFO lpbmi; DWORD dwBitsSize; HANDLE hDIB; LPSTR pDIB;//指向位图数据的指针; BITMAPINFOHEADER *bmhdr;//指向位图信息头结构的指针 dwBitsSize = file.GetLength();//得到文件长度 if (file.Read((LPSTR)&bmfHeader, sizeof(bmfHeader)) !=sizeof(bmfHeader)) return FALSE;//读取位图文件的文件头结构信息; if (bmfHeader.bfType != 0x4d42) //检查该文件是否为BMP格式的文件; return FALSE; hDIB=(HANDLE) ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, dwBitsSize); //为读取图像文件数据申请缓冲区 if (hDIB == 0) { return FALSE; } pDIB = (LPSTR) ::GlobalLock((HGLOBAL)hDIB); //得到申请的缓冲区的指针; if (file.ReadHuge(pDIB, dwBitsSize - sizeof(BITMAPFILEHEADER)) != dwBitsSize - sizeof(BITMAPFILEHEADER) ) { ::GlobalUnlock((HGLOBAL)hDIB); hDIB=NULL; return FALSE; }//此时pDIB数据块中读取的数据包括位图头信息、位图颜色表、图像像素的灰度值; bmhdr=(BITMAPINFOHEADER*)pDIB;//为指向位图信息头结构的指针赋值; ::GlobalUnlock((HGLOBAL)hDIB); if ((*bmhdr).biBitCount!=8)//验证是否为8bit位图 { AfxMessageBox("该文件不是灰度位图格式!"); return FALSE; } m_hDIB=hDIB;//将内部变量数据赋于全局变量; //下面是记录位图的尺寸; m_sizeDoc.x=bmhdr->biWidth; m_sizeDoc.y=bmhdr->biHeight; //下面是根据颜色表生成调色板; m_palDIB=new Cpalette; pPal->palVersion=0x300;//填充逻辑颜色表 pPal->palNumEntries=256; lpbmi=(LPBITMAPINFO)bmhdr; for(int i=0;i<256;i++) {//每个颜色表项的R、G、B值相等,并且各个值从“0”到“255”序列展开; Pal->palPalentry[i].peRed=lpbmi->bmiColors[i].rgbRed; pPal->palPalentry[i].peGreen=lpbmi->bmiColors[i].rgbGreen; pPal->palPalentry[i].peBlue= lpbmi->bmiColors[i].rgbBlue;; pPal->palPalentry[i].peFlags=0; } m_palDIB->CreatePalette(pPal); //根据读入的数据得到位图的宽、高、颜色表; if(pPal) delete pPal; EndWaitCursor(); SetPathName(lpszPathName);//设置存储路径 SetModifiedFlag(FALSE); // 设置文件修改标志为FALSE return TRUE; } |
上面的方法是通过CFile类对象的操作来读取位图文件的,它需要分析位图中的文件头信息,从而确定需要读取的图像长度。这种方法相对来说有些繁琐,其实还可以以一种相对简单的方法读取位图数据,首先在程序的资源中定义DIB类型资源,然后添加位图到该类型中,将图像数据以资源的形式读取出来,这时候就可以根据所获取的数据中的位图信息结构来获取、显示图像数据了。下面的函数实现了以资源形式装载图像文件数据,该函数的实现代码如下所示:
///////////////////////////////////////////////////////////////// HANDLE LoadDIB(UINT uIDS, LPCSTR lpszDibType) { LPCSTR lpszDibRes =MAKEINTRESOURCE(uIDS);//根据资源标志符确定资源的名字; HINSTANCE hInst=AfxGetInstanceHandle();//得到应用程序的句柄; HRSRC hRes=::FindResource(hInst,lpszDibRes, lpszDibType);//获取资源的句柄,这里lpszDibType为资源的名字“DIB”; If(hRes==NULL) return NULL HGLOBAL hData=::LoadResource(hInst, hRes);//转载资源数据并返回该句柄; return hData; } |
2、 灰度位图数据的存储 为了将图像处理后所得到的像素值保存起来,我们重载了文档类的OnSaveDocument()函数,这样用户在点击Save或SaveAs子菜单后程序自动调用该函数,实现图像数据的存储。该函数的具体实现如下:
/////////////////////////////////////////////////////////////////// BOOL CDibDoc::OnSaveDocument(LPCTSTR lpszPathName) { CFile file; CFileException fe; BITMAPFILEHEADER bmfHdr; // 位图文件头结构; LPBITMAPINFOHEADER lpBI;//指向位图头信息结构的指针; DWORD dwDIBSize;; if (!file.Open(lpszPathName, CFile::modeCreate |CFile::modeReadWrite | CFile::shareExclusive, &fe)) { AfxMessageBox("文件打不开"); return FALSE; }//以读写的方式打开文件; BOOL bSuccess = FALSE; BeginWaitCursor(); lpBI = (LPBITMAPINFOHEADER) ::GlobalLock((HGLOBAL) m_hDIB); if (lpBI == NULL) return FALSE; dwDIBSize = *(LPDWORD)lpBI + 256*sizeof(RGBQUAD); //图像的文件信息所占用的字节数; DWORD dwBmBitsSize;//BMP文件中位图的像素所占的字节数 dwBmBitsSize=WIDTHBYTES((lpBI->biWidth)*((DWORD)lpBI->biBitCount)) *lpBI->biHeight;// 存储时位图所有像素所占的总字节数 dwDIBSize += dwBmBitsSize; //BMP文件除文件信息结构外的所有数据占用的总字节数; lpBI->biSizeImage = dwBmBitsSize; // 位图所有像素所占的总字节数 //以下五句为文件头结构填充值 bmfHdr.bfType =0x4d42; // 文件为"BMP"类型 bmfHdr.bfSize = dwDIBSize + sizeof(BITMAPFILEHEADER);//文件总长度 bmfHdr.bfReserved1 = 0; bmfHdr.bfReserved2 = 0; bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + lpBI->biSize + 256*sizeof(RGBQUAD); //位图数据距离文件头的偏移量; file.Write((LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER));//向文件中写文件头信息; file.WriteHuge(lpBI, dwDIBSize); //将位图信息(信息头结构、颜色表、像素数据)写入文件; ::GlobalUnlock((HGLOBAL) m_hDIB); EndWaitCursor(); SetModifiedFlag(FALSE); // 将文档设为“干净”标志,表示此后文档不需要存盘提示; return TRUE; } |
|