BMP图像的基本操作

作者在 2008-05-31 17:16:15 发布以下内容
在 实现数字图象处理的过程中,主要是通过对图像中的每一个像素点运用各种图像处理算法来达到预期的效果,所以进行图像处理的第一步,也是我们最关心的问题, 是如何得到图像中每一个像素点的亮度值;为了观察和验证处理的图像效果,另一个需要解决的问题是如何将处理前后的图像正确的显示出来。我们这章内容就是解 决这些问题。

  随着科技的发展,图像处理技术已经渗透到人类生活的各个领域并得到越来越多的应用,但是突出的一个矛盾是图像的格式 也是越来越多,目前图像处理所涉及的主要的图像格式就有很多种,如TIF、JEMP、BMP等等,一般情况下,为了处理简单方便,进行数字图像处理所采用 的都是BMP格式的图像文件(有时也称为DIB格式的图像文件),并且这种格式的文件是没有压缩的。我们通过操作这种格式的文件,可以获取正确显示图像所 需的调色板信息,图像的尺寸信息,图像中各个像素点的亮度信息等等,有了这些数据,开发人员就可以对图像施加各种处理算法,进行相应的处理。如果特殊情况 下需要处理其它某种格式的图像,如GIF、JEMP等格式的图象文件,可以首先将该格式转换为BMP格式,然后再进行相应的处理。这一点需要读者清楚。

  BMP格式的图像文件又可以分为许多种类,如真彩色位图、256色位图,采用RLE(游程编码)压缩格式的BMP位图等等。由于在实 际的工程应用和图像算法效果验证中经常要处理的是256级并且是没有压缩的BMP灰度图像,例如通过黑白采集卡采集得到的图像就是这种格式,所以我们在整 个讲座中范例所处理的文件格式都是BMP灰度图像。如果读者对这种格式的位图能够作到熟练的操作,那么对于其余形式的BMP位图的操作也不会很困难。

   BMP灰度图像作为Windows环境下主要的图像格式之一,以其格式简单,适应性强而倍受欢迎。正如我们在上一讲中介绍过的那样,这种文件格式就是每 一个像素用8bit表示,显示出来的图像是黑白效果,最黑的像素的灰度(也叫作亮度)值为"0",最白的像素的灰度值为"255",整个图像各个像素的灰 度值随机的分布在"0"到"255"的区间中,越黑的像素,其灰度值越接近于"0",越白(既越亮)的像素,其灰度值越接近于"255";与此对应的是在 该文件类型中的颜色表项的各个RGB分量值是相等的,并且颜色表项的数目是256个。

  在进行图像处理时,操作图像中的像素值就要得到图像阵列;经过处理后的图像的像素值需要存储起来;显示图像时要正确实现调色板、得到位图的尺寸信息等。结合这些问题,下面我们针对性的给出了操作灰度BMP图像时的部分函数实现代码及注释。


一、 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-&gt;bmiColors[i].rgbRed;
pPal-&gt;palPalentry[i].peGreen=lpbmi-&gt;bmiColors[i].rgbGreen;
pPal-&gt;palPalentry[i].peBlue= lpbmi-&gt;bmiColors[i].rgbBlue;;
pPal-&gt;palPalentry[i].peFlags=0;
}
m_palDIB-&gt;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-&gt;biWidth)*((DWORD)lpBI-&gt;biBitCount))
*lpBI-&gt;biHeight;// 存储时位图所有像素所占的总字节数
dwDIBSize += dwBmBitsSize; //BMP文件除文件信息结构外的所有数据占用的总字节数;
lpBI-&gt;biSizeImage = dwBmBitsSize; // 位图所有像素所占的总字节数
//以下五句为文件头结构填充值
bmfHdr.bfType =0x4d42; // 文件为"BMP"类型
bmfHdr.bfSize = dwDIBSize + sizeof(BITMAPFILEHEADER);//文件总长度
bmfHdr.bfReserved1 = 0;
bmfHdr.bfReserved2 = 0;
bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + lpBI-&gt;biSize
+ 256*sizeof(RGBQUAD);
//位图数据距离文件头的偏移量;
file.Write((LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER));//向文件中写文件头信息;
file.WriteHuge(lpBI, dwDIBSize);
//将位图信息(信息头结构、颜色表、像素数据)写入文件;
::GlobalUnlock((HGLOBAL) m_hDIB);
EndWaitCursor();
SetModifiedFlag(FALSE); // 将文档设为"干净"标志,表示此后文档不需要存盘提示;
return TRUE;
}



二、 调色板的操作

  通过上面的操作,我们已经可以获取图像中的数据了,现在的又一个问题是如何在窗口中显示出图像数据。灰度图像要正确显示,必须实现逻辑调色板和系统调色板。首先我们介绍一下逻辑调色板结构LOGPALETTE,该结构定义如下:

typedef struct tagLOGPALETTE
{
WORD palVersion;//调色板的板本号,应该指定该值为0x300;
WORD palNumEntries;//调色板中的表项数,对于灰度图像该值为256;
PALETEENTRY palPalEntry[1];//调色板中的颜色表项,由于该表项的数目不一定,所以这里数组长度定义为1,灰度图像对应的该数组的长度为256;
}LOGPALETTE;
颜色表项结构PALETTEENTRY定义了调色板中的每一个颜色表项的颜色和使用方式,定义如下:
typedef struct tagPALETTEENTRY
{
BYTE peRed; //R分量值;
BYTE peGreen; //G分量值;
BYTE peBlue; //B分量值;
BYTE peFlags; // 该颜色被使用的方式,一般情况下设为"0";
}PALETTEENTRY;

   Windows系统使用调色板管理器来管理与调色板有关的操作,通常活动窗口的调色板即是当前系统调色板,所有的非活动窗口都必须按照此系统调色板来显 示自己的颜色,此时调色板管理器将自动的用系统调色板中的最近似颜色来映射相应的显示颜色。如果窗口或应用程序按自己的调色板显示颜色,就必须将自己的调 色板载入到系统调色板中,这种操作叫作实现调色板,实现调色板包括两个步骤,既首先将调色板选择到设备上下文中,然后在设备上下文中实现它。可以通过 CDC::SelectPalette()、CDC::RealizePalette()或相应的API函数来实现上述的两个步骤。在实现调色板的过程 中,通过在主框架类中处理Windows定义的消息WM_QUERYNEWPALETTE 、WM_PALETTECHANGED及视图类中处理自定义消息WM_DOREALIZE(该消息在主框架窗口定义如下:#define WM_REALIZEPAL (WM_USER+101))来实现调色板的操作。当系统需要处理调色板的变化时,将向程序的主窗口发送WM_QUERYNEWPALETTE 、WM_PALETTECHANGED,例如当某一窗口即将激活时,主框架窗口将收到WM_QUERYNEWPALETTE消息,通知该窗口将要收到输入 焦点,给它一次机会实现其自身的逻辑调色板;当系统调色板改变后,主框架窗口将收到WM_PALETTECHANGED消息,通知其它窗口系统调色板已经 改变,此时每一窗口都应该实现其逻辑调色板,重画客户区。

  由于上述的调色板变更消息是发往主框架窗口的,所以我们只能在主窗口中 响应这两个消息,然后由主框架窗口通知各个视窗,使得程序激活时能自动装载自己的调色板。我们定义的用户消息WM_REALIZEPAL用于主框架窗口通 知视窗它已经收到调色板变更消息,视窗应该协调其调色板。下面我们给出了各个消息的响应处理函数的具体实现代码和注释:

//////////////////////////////////////////////////////////
void CMainFrame::OnPaletteChanged(CWnd* pFocusWnd)
{//总实现活动视的调色板
CMDIFrameWnd::OnPaletteChanged(pFocusWnd);
CMDIChildWnd* pMDIChildWnd = MDIGetActive();//得到活动的子窗口指针;
if (pMDIChildWnd == NULL)
return
CView* pView = pMDIChildWnd-&gt;GetActiveView();//得到视图的指针;
ASSERT(pView != NULL);
SendMessageToDescendants(WM_DOREALIZE, (WPARAM)pView-&gt;m_hWnd);
//通知所有子窗口系统调色板已改变
}
////////////////////////////////////////////////
BOOL CMainFrame::OnQueryNewPalette()//提供实现系统调色板的机会
{
// 实现活动视的调色板
CMDIChildWnd* pMDIChildWnd = MDIGetActive();//得到活动的子窗口指针;
if (pMDIChildWnd == NULL)
return FALSE;//no active MDI child frame (no new palette)
CView* pView = pMDIChildWnd-&gt;GetActiveView();//得到活动子窗口的视图指针;
ASSERT(pView != NULL);
//通知活动视图实现系统调色板
pView-&gt;SendMessage(WM_DOREALIZE, (WPARAM)pView-&gt;m_hWnd);
return TRUE;
}
/////////////////////////////////////////////////
BOOL CDibView::OnDoRealize(WPARAM wParam, LPARAM)//实现系统调色板
{
ASSERT(wParam != NULL);
CDibDoc* pDoc = GetDocument();
if (pDoc-&gt;m_hDIB == NULL)
return FALSE; // must be a new document
CPalette* pPal = pDoc-&gt;m_palDIB;
//调色板的颜色表数据在InitDIBData()函数中实现
if (pPal != NULL)
{
CMainFrame* pAppFrame = (CMainFrame*) AfxGetApp()-&gt;m_pMainWnd;//得到程序的主框架指针;
ASSERT_KINDOF(CMainFrame, pAppFrame);
CClientDC appDC(pAppFrame);//获取主框架的设备上下文;
CPalette* oldPalette = appDC.SelectPalette(pPal, ((HWND)wParam) != m_hWnd);
//只有活动视才可以设为"FALSE",即根据活动视的调色板设为"前景"调色板;
if (oldPalette != NULL)
{
UINT nColorsChanged = appDC.RealizePalette();//实现系统调色板
if (nColorsChanged &gt; 0)
pDoc-&gt;UpdateAllViews(NULL);//更新视图
appDC.SelectPalette(oldPalette, TRUE);
//将原系统调色板置为背景调色板
}
else
{
TRACE0("\\tSelectPalette failed in");
}
return TRUE;
}

   注:在调用API函数显示位图时,不要忘记设置逻辑调色板,即"背景"调色板,否则位图将无法正确显示,读者可以从后面的显示部分的实现看出我们在显示 时实现了逻辑调色板。上述的处理相对来说比较繁琐复杂,可能对于初学者来说也比较难于理解,所以如果我们的程序仅仅限于处理灰度图象,可以采用另外一种相 对简单的办法,即在文档类的初始化阶段定义一个灰度调色板,然后在设备上下文中实现它,这样作的好处是在度取灰度位图时可以不再考虑文件中的颜色表信息, 提高了文件读取速度,笔者在开发一个基于机器视觉的项目时采用的就是这种方法,取的了比较满意的效果。首先定义一个指向逻辑颜色表结构 LOGPALETTE的指针pPal,填充该指针,然后将该指针与调色板指针联系起来,该方法的具体实现如下:

/////////////////////////////////////////////////////////
CDibDoc::CDibDoc()
{
............................
LOGPALETTE *Pal;
Pal=new LOGPALETTE;
m_palDIB=new Cpalette;
pPal-&gt;palVersion=0x300;
pPal-&gt;palNumEntries=256;
for(int i=0;i<256;i++)
{//每个颜色表项的R、G、B值相等,并且各个值从"0"到"255"序列展开;
Pal->palPalentry[i].peRed=i;
pPal-&gt;palPalentry[i].peGreen=i;
pPal-&gt;palPalentry[i].peBlue=i;
pPal-&gt;palPalentry[i].peFlags=0;
}
m_palDIB-&gt;CreatePalette(pPal);
.......................
}

三、 图像的显示

   显示DIB位图数据可以通过设备上下文CDC对象的成员函数CDC::Bitblt()或CDC::StretchBlt()来实现,也可以通过API 函数SetDIBBitsToDevice()或StretchDIBBits()来实现,函数中具体所用到的各个参数的意义可以参考 MSDN。其中StretchDIBBits()和CDC::StretchBlt()可以将图像进行放大和缩小显示。当从文档中装入位图文件时, CDIBView类的OnInitialUpdate函数将被调用,因此可以在该函数中实现对视图尺寸的设置,用于正确的显示位图,然后就可以在视图类的 OnDraw()函数中正确的显示位图了。这两个函数的具体实现代码分别如下所示:

/////////////////////////////////////////////////////////////
void CDIBView::OnInitialUpdate()
{
CscrollView::OnInitalUpdate();
CDIBDoc *pDoc=GetDocument();
If(pDoc-&gt;m_hDIB==NULL)//如果位图数据为空,设置m_sizeDoc的默认尺寸;
pDoc-&gt;m_sizeDoc.cx=pDoc-&gt;m_sizeDoc.cy=100;
SetScrollSizes(MM_TEXT,pDoc-&gt; m_sizeDoc);
}
/////////////////////////////////////////////////////////////
void CDIBView::OnDraw(CDC *pDC)
{
BITMAPINFOHEADER *lpDIBHdr;//位图信息头结构指针;
BYTE *lpDIBBits;//指向位图像素灰度值的指针;
BOOL bSuccess=FALSE;
CPalette*OldPal=NULL;//调色板指针;
HDC hDC=pDC-&gt;GetSafeHdc();//获取当前设备上下文的句柄;
CDIBDoc *pDoc=GetDocument();//获取活动文档的指针;
If(pDoc-&gt;m_hDIB ==NULL)
{//判断图像数据是否为空;
AfxMessageBox("图像数据不能为空,请首先读取图像数据!");
return;
}
lpDIBHdr=( BITMAPINFOHEADER *)GlobalLock(pDoc-&gt;m_hDIB);//得到图像的位图头信息
lpDIBBits=lpDIBHdr+sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD);//获取保存图像像素值的缓冲区的指针;
if(pDoc-&gt; m_palDIB)
{//如果存在调色板信息,实现逻辑调色板;
OldPal=pDC-&gt; SelectPalette(pDoc-&gt; m_palDIB,TRUE);
PDC-&gt;RealizePalette();
}
else
{
AfxMessageBox("图像的调色板数据不能为空,请首先读取调色板信息!");
return ;
}
SetStretchBltMode(hDC,COLORONCOLOR);
//显示图像
BSuccess=StretchDIBBits(hDC,0,0,pDoc-&gt; m_sizeDoc.cx, pDoc-&gt; m_sizeDoc.cy,
0, pDoc-&gt; m_sizeDoc.cy,0, pDoc-&gt; m_sizeDoc.cy,
lpDIBBits,(LPBITMAPINFO)lpDIBHdr,
DIB_RGB_COLORS,
SRCCOPY);
GlobalUnlock(pDoc-&gt;m_hDIB);
If(OldPal)//恢复调色板;
PDC-&gt;SelectPalette(OldPal,FALSE);
retrun;
}
基础知识 | 阅读 3774 次
文章评论,共1条
vfdff(作者)
2009-08-05 22:29
1
BMP图像文件格式是微软公司为其Windows环境设置的标准图像格式,而且 Windows系统软件中还同时内含了一系列支持BMP图像处理的API函数,随着Windows 在世界范围内的不断普及,BMP文件格式无疑也已经成为PC机上的流行图像文件格 式。它的主要特点可以概括为:文件结构与PCX文件格式类似,每个文件只能存放一幅图像;图像数据是否采用压缩方式存放,取决于文件的大小与格式,即压缩处理成为图 像文件的一个选项,用户可以根据需要进行选择。其中,非压缩格式是BMP图像文件所 采用的一种通用格式。但是,如果用户确定将BMP文件格式压缩处理,则Windows设计 了两种压缩方式:如果图像为16色模式,则采用RLE4压缩方式,若图像为256色模式, 则采用RLE8压缩方式。同时,BMP图像文件格式可以存储单色、16色、256色以及真彩 色四种图像数据,,其数据的排列顺序与一般文件不同,它以图像的左下角为起点存储图 像,而不是以图像的左上角为起点;而且BMP图像文件格式中还存在另外一个与众不同 的特点,即其调色板数据所采用的数据结构中,红、绿、蓝三种基色数据的排列顺序也 恰好与其它图像文件格式相反。总之,BMP图像文件格式拥有许多适合于Windows环境 的新特色,而且随着Windows版本的不断更新,微软公司也在不断改进其BMP图像文件 格式,例如:当前BMP图像文件版本中允许采用32位颜色表,而且针对32位Windows 的产生,相应的API函数也在不断地报陈出新,这些无疑都同时促成了BMP文件格式的 不断风靡。但由于BMP文件格式只适合于Windows上的应用软件,而对于DOS环境中的 各种应用软件则无法提供相应的支持手段,因此这无疑是阻碍BMP文件格式的流通程度超过PCX文件格式的一个重要因素。<br />
<br />
Windows中定义了两种位图文件类型,即一般位图文件格式与设备无关位图文件格 式。其中,由于设备无关位图(DIB)文件格式具有更强的灵活性与完整的图像数据、 压缩方式等定义。BMP图像文件的结构可以分为如下三个部分:文件头、调色板数据以 及图像数据。其中文件头的长度为固定值54个字节;调色板数据对所有不超过256色的 图像模式都需要进行设置,即使是单色图像模式也不例外,但是对于真彩色图像模式, 其对应的BMP文件结构中却不存在相应调色板数据的设置信息;图像数据既可以采用一 定的压缩算法进行处理,也可以不必对图像数据进行压缩处理,这不仅与图像文件的大小相关,而且也与对应的图像处理软件是否支持经过压缩处理的BMP图像文件相关。以下将分别介绍BMP图像文件结构中的这三个重要组成部分。特别值得注意的是:BMP 图像文件结构设计得相当简单,这无疑有利于图像文件的处理速度,但是同时也使得 BMP图像文件格式具有一定的局限性,即一个BMP图像文件只能存储一幅图像。<br />
<br />
BMP图像文件的文件头定义<br />
<br />
Windows中将BMP图像文件的文件头分成两个数据结构,其中一个数据结构中包含 BMP文件的类型、大小和打印格式等信息,称为BITMAPFILEHEADERl另外一个数据 结构中则包含BMP文件的尺寸定义等信息,称为BITMAPINFOHEADERl如果图像文 件还需要调色板数据,则将其存放在文件头信息之后。 BITMAPFIlEHEADER数据结构在Windows.h中的定义为:<br />
<br />
typedef struCttagBITMAPFIlEHEADER<br />
<br />
{<br />
<br />
WORD bftype;<br />
<br />
DWORD bfsiZe:<br />
<br />
WORD bfReservedl;<br />
<br />
WORD bgReserved2:<br />
<br />
DWORD bfoffBits: <br />
<br />
}BITMAPFILEHEADER;<br />
<br />
其中,bfrype在图像文件存储空间中的数据地址为0,数据类型为unsignedchar,内 容为固定值“BM”,用于标志文件格式,表示该图像文件为BMP文件。<br />
<br />
bfsize的数据地址为2,类型为unsignedlong,它以字节为单位,定义位图文件的大 小。<br />
<br />
bfReservedl与bfReserved2的数据地址分别为6和8,数据类型则都为unsignedint,二 者都是BMP文件的保留字,没有任何意义,其值必须为0.<br />
<br />
bfoffBits的数据地址为10,数据类型为unsignedlong,它以字节为单位,指示图像 数据在文件内的起始地址,即图像数据针对文件头的偏移量。<br />
<br />
BITMAPINFOHEADER数据结构用于说明位图的大小,其定义为:<br />
<br />
type struttagBITMAPINFOHEADER<br />
<br />
{ <br />
<br />
DWORD biSize:<br />
<br />
DWORD biWldth;<br />
<br />
DWORD biHeight; <br />
<br />
WORD biPlanes: <br />
<br />
WORD biBitCount:<br />
<br />
DWORD biCOmpression;<br />
<br />
DWORD biSiZelmage; <br />
<br />
DWORD biXPelsPerMeter:<br />
<br />
DWORD biYPelsPerMeter:<br />
<br />
DWORD biClrUsed;<br />
<br />
DWORD biClrlmportant;<br />
<br />
}BITMAPINFOHEADER;<br />
<br />
其中,biSize的数据地址为14,数据类型为unsignedlong,它以字节为单位指定数据 结构BITMAPINFOHEADER所占用的存储容量,固定值为40。<br />
<br />
biWidth与biHeight的数据地址分别为18和22,数据类型都是unsignedlong,它们分 别以像素为单位,给出该BMP文件所描述位图的宽度与高度。若biHeight的取值为正数, 则表明位图为bottom—up类型的DIB位图,而且位图原点为左下角。若biHeight的取值为 负数,则表明位图为top—down类型的DIB位图,而且位图原点为左上角。 <br />
<br />
注意:一般位图定义中,这两个字段的取值必须为正数。<br />
<br />
biPlanes的数据地址为26,类型为unsignedint,它代表目标设备的平面数必须为1。<br />
<br />
biBitCount的数据地址为28,类型为utlsigned Int,它确定每个像素所需要的位数。 当图像为单色时,该字段的取值为1;当图像为16色时,该字段的取值为4;当图像为256 色时,该字段的取值为8;当图像为真彩色时,该字段的取值为24。<br />
<br />
biCompression的数据地址为30,类型为unsignedlong,它代表bottom—up类型位图的 压缩类型(注意:t叩—down类型位图不能进行压缩处理),其可能取值及其含义分别为: 若该字段的取值为BI—RGB,则表示文件内的图像数据没有经过压缩处理;若该字段的 取值为BI—RLE8,则表示所压缩的图像数据是256色,采用的压缩方法是RLE8;若该字 段的取值为BI—RLE4,则表示所压缩的图像数据是16色,采用的压缩方法是RLE4;若 该字段的取值为BI—BITFIELDS,则表明图像文件内的数据没有经过压缩处理,而且颜 色表由分别表示每个像素点的红、绿、蓝三原色的双字组成。 <br />
<br />
注意:BMP文件格式在处理单色或者真彩色图像时,不论图像数据多么庞大,都不 对图像数据进行任何压缩处理。<br />
<br />
biSizelmage的数据地址为34,类型为unsignedlong7它以字节为单位,给出该BMP 内图像数据占用的空间大小。若图像文件描述BI—RGB位图,则该字段的值必须设置为0。<br />
<br />
biXPelsPerMeter字段与biYPelsPerMeter字段的数据地址分别为38与42,类型都是 unsignedlong,它们分别以每米像素数为单位,给出位图目的设备水平以及垂直方向的 分辨率;其中,应用程序可以根据biXPelsPerMeter字段的值,从源位图组中选择与当前 设备特点最匹配的位图。<br />
<br />
biCkUsed的数据地址为46,类型为unsignedlong,给出位图实际使用的颜色表中的 颜色变址数。如果该字段的取值为0,则代表本位图使用了biBitCount字段值所代表的最 大颜色值,其中biBitCount字段的取值与biCompression所指定的压缩方法相关。例如: 如果图像为16色,而该字段的取值为10,则代表本位图共使用了12种颜色;如果该字段 的取值非零,而且biBitCount字段的取值小于16,则该字段指定图像或者设备驱动器存 取的实际颜色数。若biBitCount字段的取值大于或者等于16,则该字段指定使Window 系统调色板达到最优性能的颜色表大小。<br />
<br />
biChlmportant的数据地址为50,数据类型为unsignedlong,它给出位图显示过程中 重要颜色的变址数。若该字段的取值为0,则表示所有使用的颜色都是重要颜色。 <br />
<br />
调色板数据 <br />
<br />
如果位图的描述还需要调色板数据,则应该在BMP文件头之后定义一个颜色表,它 包含若干个表项。其中,每一个表项定义了一种颜色,Windows将其定义为如下的 RGBQUAD结构:<br />
<br />
typedef tagRGBQUAD<br />
<br />
{<br />
<br />
BYTE rgbBlue;<br />
<br />
BYTE rgbGreen;<br />
<br />
BYTE rgbRed; <br />
<br />
BYTE rgbReserved;<br />
<br />
}RGBQUAD;<br />
<br />
注意:RGBQUAD数据结构中,增加了一个保留字段rgbReserved,它不代表任何意 义,必须取固定值00同时,RGBQUAD结构定义的颜色值中,红色、绿色与蓝色的排 列顺序与一般图像文件的颜色数据排列顺序恰好相反,即:蓝色的亮度由rgbBlue字段 定义、绿色的亮度由rgbGreen字段定义,红色的亮度由rgbRed字段定义。若位图中某个 像素点的颜色描述为“00,00,FF,00”,则表示该点的颜色为纯红色,而不是纯 蓝色。<br />
<br />
综上,在DIB位图文件组成中,紧随BITMAPFILEHEADER结构其后的数据结构为 BITMAPINFO,两者共同构成完整的位图文件。Windows将BITMAPINFO结构定义为:<br />
<br />
tyPedef stmCt tagBITMAPINFO<br />
<br />
{<br />
<br />
BITMAPINFOHEADER bmiHeader:<br />
<br />
RGBQUAD bmiC010ur[1];<br />
<br />
}BITMAPINFO;<br />
<br />
其中,bmiHeader字段指向包含位图颜色格式以及大小定义的 BITMAPHEADERINFO结构。bmiCo1our[1]字段指向RGBQUAD结构数组或者定义位图 颜色值的双字数据结构,它定义了BMP图像文件的颜色表,它包含多少个表项是由 BITMAPINFOHEADER数据结构中的biBitCount字段定义的:若该字段的取值为1,则颜 色表中共包含两个表项;若该字段的取值为4,则颜色表中共包含16个表项;若该字段 的取值为8,则颜色表中共包含256个表项;若该字段的取值为16,而且 BITMAPINFOHEADER结构定义中指定bmiColors字段的取值为BI_RGB,则颜色表中的 表项为空,位图阵列中每个字代表一个像素,字中每5位上的值代表该像素点一种基色 的亮度,其中最低5位代表蓝色亮度,依次为绿色与红色,字的最高位没有任何意义。 若该字段的取值为24,则颜色表中的表项为空,而位图阵列的每三个字节代表一个像 素,这3个字节直接定义了像素颜色中蓝、绿、红三种基色的相对亮度。 若该字段的取 值为32,而且BITMAPINFOHEADER结构定义中bmiC010rs字段的取值为BI—RGB,则颜 色表中的表项为空,且位图阵列中的每个双字代表一个像素的三原色亮度构成。 注意:颜色表中的数据应该按照重要性的帧序进行排列。另外,如果应用程序中需 要使用DIB位图,则bmiColors字段可以为16位的无符号整数数组,这些整数指定当前逻 辑调色板的颜色索引,而不是显式的RGB值。当然,如果位图存储在文件中或者需要传 送到其它应用程序,则该字段中不能包含调色板索引值。<br />
<br />
一、BMP文件结构&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;BMP文件组成&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;BMP文件由文件头、位图信息头、颜色信息和图形数据四部分组成。&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;BMP文件头&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;BMP文件头数据结构含有BMP文件的类型、文件大小和位图起始位置等信息。&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;其结构定义如下:&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;typedef&nbsp;&nbsp;&nbsp;struct&nbsp;&nbsp;&nbsp;tagBITMAPFILEHEADER&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;{&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;WORDbfType;&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;位图文件的类型,必须为BM&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;DWORD&nbsp;&nbsp;&nbsp;bfSize;&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;位图文件的大小,以字节为单位&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;WORDbfReserved1;&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;位图文件保留字,必须为0&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;WORDbfReserved2;&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;位图文件保留字,必须为0&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;DWORD&nbsp;&nbsp;&nbsp;bfOffBits;&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;位图数据的起始位置,以相对于位图&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;文件头的偏移量表示,以字节为单位&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;BITMAPFILEHEADER;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;3.&nbsp;&nbsp;&nbsp;位图信息头&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;BMP位图信息头数据用于说明位图的尺寸等信息。&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;typedef&nbsp;&nbsp;&nbsp;struct&nbsp;&nbsp;&nbsp;tagBITMAPINFOHEADER{&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;DWORD&nbsp;&nbsp;&nbsp;biSize;&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;本结构所占用字节数&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;LONGbiWidth;&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;位图的宽度,以像素为单位&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;LONGbiHeight;&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;位图的高度,以像素为单位&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;WORD&nbsp;&nbsp;&nbsp;biPlanes;&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;目标设备的级别,必须为1&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;WORD&nbsp;&nbsp;&nbsp;biBitCount//&nbsp;&nbsp;&nbsp;每个像素所需的位数,必须是1(双色),&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;4(16色),8(256色)或24(真彩色)之一&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;DWORD&nbsp;&nbsp;&nbsp;biCompression;&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;位图压缩类型,必须是&nbsp;&nbsp;&nbsp;0(不压缩),&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;DWORD&nbsp;&nbsp;&nbsp;biSizeImage;&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;位图的大小,以字节为单位&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;LONGbiXPelsPerMeter;&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;位图水平分辨率,每米像素数&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;LONGbiYPelsPerMeter;&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;位图垂直分辨率,每米像素数&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;DWORD&nbsp;&nbsp;&nbsp;biClrUsed;//&nbsp;&nbsp;&nbsp;位图实际使用的颜色表中的颜色数&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;DWORD&nbsp;&nbsp;&nbsp;biClrImportant;//&nbsp;&nbsp;&nbsp;位图显示过程中重要的颜色数&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;BITMAPINFOHEADER;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;4.&nbsp;&nbsp;&nbsp;颜色表&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;  颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD类型的结构,定义一种颜色。RGBQUAD结构的定义如下:&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;typedef&nbsp;&nbsp;&nbsp;struct&nbsp;&nbsp;&nbsp;tagRGBQUAD&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;BYTErgbBlue;//&nbsp;&nbsp;&nbsp;蓝色的亮度(值范围为0-255)&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;BYTErgbGreen;&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;绿色的亮度(值范围为0-255)&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;BYTErgbRed;&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;红色的亮度(值范围为0-255)&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;BYTErgbReserved;//&nbsp;&nbsp;&nbsp;保留,必须为0&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;RGBQUAD;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;颜色表中RGBQUAD结构数据的个数有biBitCount来确定:&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;当biBitCount=1,4,8时,分别有2,16,256个表项;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;当biBitCount=24时,没有颜色表项。&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;位图信息头和颜色表组成位图信息,BITMAPINFO结构定义如下:&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;typedef&nbsp;&nbsp;&nbsp;struct&nbsp;&nbsp;&nbsp;tagBITMAPINFO&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;BITMAPINFOHEADER&nbsp;&nbsp;&nbsp;bmiHeader;&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;位图信息头&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;RGBQUAD&nbsp;&nbsp;&nbsp;bmiColors[1];&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;颜色表&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;BITMAPINFO;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;&nbsp; <br />
&nbsp;&nbsp;5.&nbsp;&nbsp;&nbsp;位图数据&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;  位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。位图的一个像素值所占的字节数:&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;当biBitCount=1时,8个像素占1个字节;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;当biBitCount=4时,2个像素占1个字节;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;当biBitCount=8时,1个像素占1个字节;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;当biBitCount=24时,1个像素占3个字节;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;Windows规定一个扫描行所占的字节数必须是&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;4的倍数(即以long为单位),不足的以0填充,&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;一个扫描行所占的字节数计算方法:&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;DataSizePerLine=&nbsp;&nbsp;&nbsp;(biWidth*&nbsp;&nbsp;&nbsp;biBitCount+31)/8;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;一个扫描行所占的字节数&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;DataSizePerLine=&nbsp;&nbsp;&nbsp;DataSizePerLine/4*4;&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;字节数必须是4的倍数&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;位图数据的大小(不压缩情况下):&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;DataSize=&nbsp;&nbsp;&nbsp;DataSizePerLine*&nbsp;&nbsp;&nbsp;biHeight; <br />
<br />
<br />
<br />
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/orangeman1982112/archive/2009/02/13/3887427.aspx
游客请输入验证码
浏览1941123次