Windows bitmap文件解析

首先需要介绍的是.bmp文件的存储格式,.bmp文件由四部分组成,文件头部,文件信息头部,调色板和实际的像素信息。文件的这种组织形式类似于一种嵌套形式。

typedef struct tagBITMAPFILEHEADER {
        WORD    bfType;
        DWORD   bfSize;
        WORD    bfReserved1;
        WORD    bfReserved2;
        DWORD   bfOffBits;
} BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;

头部在磁盘上的存储的形式如上所示,其中第一个成员变量bfType类似于一个魔术字,用于标记文件是一个.bmp文件。而bfSize则是整个文件的大小,而bfOffBits则是用于表明像素信息开始相对于文件头部的偏移量。正因为有这一部分的存在使得.bmp文件的信息头部可以被改变而无需考虑兼容问题。

typedef struct tagBITMAPINFOHEADER{
        DWORD      biSize;
        LONG       biWidth;
        LONG       biHeight;
        WORD       biPlanes;
        WORD       biBitCount;
        DWORD      biCompression;
        DWORD      biSizeImage;
        LONG       biXPelsPerMeter;
        LONG       biYPelsPerMeter;
        DWORD      biClrUsed;
        DWORD      biClrImportant;
} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;

文件信息头部的结构如上,第一个成员变量biSize表明文件信息头部的大小,这一个变量可以用于判断.bmp文件的版本(不同的版本文件信息头部可以不同,不过一般默认等于40)。接下来两个成员变量表明图像的宽和高度,这两个变量的单位都是像素。然后接下来的是biPlanes成员变量必须等于1.biBitCount成员变量表明每一个像素值占用多少bit位,一般是等于24;不过该变量需要在biClrUsed等于0的情况下才有效,biClrUsed等于0则表示像素是真彩图,否则需要使用调色板,其中biClrUsed表示调色板总共有多少种颜色。biCompression表示图像是否经过压缩。biSizeImage则是图像中像素点的个数,不过这里不一定等于biWidth*biHeight,在计算biSizeImage之前需要对biWidth进行4字节对齐。biXPelsPerMeter表示分辨率,单位是每米多少个像素点。其实后面几个成员变量完全是为了设备无关性才需要保留的。

下面是一段代码用于二进制图像加载到内存中并且显示出来。注意这里.bmp图像中并没有使用调色板,所以不需要构建调色板,直接创建设备无关位图就可以了。

if (strFileName.IsEmpty())
{
return FALSE;
}
OFSTRUCT of;
HFILE hFile=::OpenFile(strFileName.GetBuffer(),&of,OF_READ);
if (hFile==(HFILE)INVALID_HANDLE_VALUE)
{
return FALSE;
}
BITMAPFILEHEADER bh;
DWORD dwCount;
::ReadFile((HANDLE)hFile,(char*)&bh,sizeof(BITMAPFILEHEADER),&dwCount,NULL);
if (bh.bfType!=0x4D42)
{
return FALSE;
}
BITMAPINFOHEADER bih;
if (sizeof(BITMAPINFOHEADER)==(bh.bfOffBits-sizeof(BITMAPFILEHEADER)))
{
::ReadFile((HANDLE)hFile,(char*)&bih,sizeof(BITMAPINFOHEADER),&dwCount,NULL);
}
else
{
MessageBox("the bitmap is extend by the default!!");
return FALSE;
}
HANDLE hDib = ::GlobalAlloc(GHND,bh.bfSize-sizeof(BITMAPINFOHEADER)-sizeof(BITMAPFILEHEADER)); 
char* pRGB=(char*)::GlobalLock(hDib);
HBITMAP hBmp=CreateBitmap(bih.biWidth, bih.biHeight,0, 24,pRGB);
HDC hDc=::GetDC(GetDlgItem(IDC_PICTURE)->m_hWnd);
BITMAPINFO bmi;
bmi.bmiHeader=bih;
bmi.bmiColors[0]=*(RGBQUAD*)pRGB;
HBITMAP hBmp=CreateDIBitmap(hDc,&bih,CBM_INIT,pRGB,(LPBITMAPINFO)&bmi, DIB_RGB_COLORS);

但是如果存在调色板,也就是biClrUsed存在的话,那么就需要处理调色板问题了,紧接着文件信息头部存在的是调色板信息。

if((hf=_lopen(strFileName,OF_READ))==HFILE_ERROR){
MessageBox("File bmp not found!","Error Message",
MB_OK|MB_ICONEXCLAMATION);
return FALSE; //打开文件错误,返回
}
_lread(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER));
_lread(hf,(LPSTR)&bi,sizeof(BITMAPINFOHEADER));
LineBytes=(DWORD)WIDTHBYTES(bi.biWidth*bi.biBitCount);
ImgSize=(DWORD)LineBytes*bi.biHeight;
if(bi.biClrUsed!=0)
//如果 bi.biClrUsed 不为零,即为实际用到的颜色数
NumColors=(DWORD)bi.biClrUsed;
else //否则,用到的颜色数为 2biBitCount。
switch(bi.biBitCount){
case 1:
NumColors=2;
break;
case 4:
NumColors=16;
break;
case 8:
NumColors=256;
break;
case 24:
NumColors=0; //对于真彩色图,没用到调色板
break;
default: //不处理其它的颜色数,认为出错。
MessageBox("Invalid color numbers!","Error Message",
MB_OK|MB_ICONEXCLAMATION);
_lclose(hf);
return FALSE; //关闭文件,返回 FALSE
}
if(bf.bfOffBits!=(DWORD)(NumColors*sizeof(RGBQUAD)+
sizeof(BITMAPFILEHEADER)+
sizeof(BITMAPINFOHEADER)))
{
MessageBox("Invalid color numbers!","Error Message",
MB_OK|MB_ICONEXCLAMATION);
_lclose(hf);
return FALSE; //关闭文件,返回 FALSE
}
bf.bfSize=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+
NumColors*sizeof(RGBQUAD)+ImgSize;
if((hImgData=GlobalAlloc(GHND,(DWORD)
(sizeof(BITMAPINFOHEADER)+
NumColors*sizeof(RGBQUAD)+
ImgSize)))==NULL)
{
MessageBox("Error alloc memory!","ErrorMessage",MB_OK|
MB_ICONEXCLAMATION);
_lclose(hf);
return FALSE; //关闭文件,返回 FALSE
}
lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData);
_llseek(hf,sizeof(BITMAPFILEHEADER),SEEK_SET);
_hread(hf,(char *)lpImgData,(long)sizeof(BITMAPINFOHEADER)
+(long)NumColors*sizeof(RGBQUAD)+ImgSize);
_lclose(hf); //关闭文件
if(NumColors!=0) //NumColors 不为零,说明用到了调色板
{
hPal=LocalAlloc(LHND,sizeof(LOGPALETTE)+
NumColors* sizeof(PALETTEENTRY));
pPal =(LOGPALETTE *)LocalLock(hPal);
pPal->palNumEntries = NumColors;
pPal->palVersion = 0x300;
lpRGB = (LPRGBQUAD)((LPSTR)lpImgData +
(DWORD)sizeof(BITMAPINFOHEADER));
for (i = 0; i < NumColors; i++)
{
pPal->palPalEntry[i].peRed=lpRGB->rgbRed;
pPal->palPalEntry[i].peGreen=lpRGB->rgbGreen;
pPal->palPalEntry[i].peBlue=lpRGB->rgbBlue;
pPal->palPalEntry[i].peFlags=(BYTE)0;
lpRGB++; //指针移到下一项
}
hPalette=CreatePalette(pPal);
LocalUnlock(hPal);
LocalFree(hPal);
}
hDc=::GetDC(GetDlgItem(IDC_PICTURE)->m_hWnd);
if(hPalette) //如果刚才产生了逻辑调色板
{
hPrevPalette=SelectPalette(hDc,hPalette,FALSE);
RealizePalette(hDc);
}
HBITMAP hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpImgData,
(LONG)CBM_INIT,
(LPSTR)lpImgData+sizeof(BITMAPINFOHEADER)+NumColors*sizeof(RGBQUAD),
(LPBITMAPINFO)lpImgData, DIB_RGB_COLORS);
if(hPalette && hPrevPalette)
{
::SelectPalette(hDc,hPrevPalette,FALSE);
::RealizePalette(hDc);
}
if (hBitmap) //hBitmap 一开始是 NULL,当不为 NULL 时表示有图
{
hMemDC = CreateCompatibleDC(hDc); //建立一个内存设备上下文
}
SelectObject(hMemDC, hBitmap);
StretchBlt(hDc, 0, 0, rect.right,rect.bottom, hMemDC, 0, 0, bi.biWidth, bi.biHeight,SRCCOPY);
DeleteDC(hMemDC);
::ReleaseDC(GetDlgItem(IDC_PICTURE)->m_hWnd,hDc); //释放设备上下文
::GlobalUnlock(hImgData); //解锁内存区
return TRUE; //成功返回

上面的代码整体流程是比较清楚的,不过有一些SDK程序设计方面的问题。其实主要是LOGPALETTE、HBITMAP、CBitmap、BITMAP和CreateBitmap以及CreateDIBitmap。

先讨论LOGPALETTE,这个东西并不一定是必须的,但是由于颜色存在着是否重要之分,所以为了节约内存在一段时间内才需要存在,比如说256色图片,这256中色彩的选择肯定不是随意选择的,而是选择那些对人眼的感官影响最大的颜色作为LOGPALETTE中的值,在显示图像时,将图像的颜色作为索引从LOGPALETTE中得到最终的颜色,然后将图像显示到内存中。LOGPALETTE主要起索引表的功能,和LOGPALETTE相对的是HPALETTE,后者实际上是用于系统对LOGPALETTE进行管理,用于将LOGPALETTE纳入到系统的管理模式之下。

HBITMAP和HPALETTE类似也是用于系统的统一管理,而BITMAP则是用于包含一些位图所必须的信息和HBITMAP相对应。而Cbitmap则是用于封装HBITMAP的一个类。

最后是两个函数CreateBitmap用于创建一个.bmp文件,这个文件用于用于创建一个位图,但是这个位图并不是和设备无关的也就是说不能用于直接进行展示,而后者CreateDIBitmap则用于创建一个与设备无关的位图,这个函数创建出来的位图直接选择到窗口的设备上下文就可以进行显示输出。

你可能感兴趣的:(windows,位图)