關於BMP位圖的資料網上有很多,內容也比較基礎。本文實現BMP位圖的讀取、顯示、保存,並對一些重要的問題進行說明(包括字節對齊、內存中的存儲順序、調色板)。
BMP文件共包括文件頭、信息頭、調色板(位深<=8的圖像含有此項)、位圖數據四大部分:
各部分的具體說明可以參考[1]。
下面是位圖的讀取、顯示、保存實現的主體代碼。(完整工程下載:Bmptest)
CString Filename;
CStatic Picture;
long Width ;
long Height ;
unsigned short BitCount;
long Stride;
unsigned char* Img;
void OpenBmp();
void SaveBmp();
//打開位圖
void CBmptestDlg::OpenBmp()
{
CRect zcRect;
Picture.GetClientRect(&zcRect);
CDC* pDC=Picture.GetDC();
BITMAPFILEHEADER header;//文件頭
BITMAPINFOHEADER infoheader;//信息頭
BITMAPINFO *bitmapinfo=NULL;
FILE *fp=fopen(Filename,"rb");
if(fp==NULL){return;}
fread(&header,sizeof(BITMAPFILEHEADER),1,fp);
if(header.bfType != 0x4D42){return;}
fread(&infoheader,sizeof(BITMAPINFOHEADER),1,fp);
Width = infoheader.biWidth;
Height = infoheader.biHeight>0?infoheader.biHeight:-infoheader.biHeight;
BitCount = infoheader.biBitCount;
Stride=((Width*BitCount+31)>>5)<<2;//一行字節數,4字節對齊
int Imgsize=Stride*Height;
if (Img!=NULL){free(Img);}
Img=(unsigned char*)malloc(Imgsize);
if(BitCount<=8)
{
int Palettesize=header.bfOffBits-sizeof(BITMAPFILEHEADER)-sizeof(BITMAPINFOHEADER);//不能PaletteLen=1<<biBitCount,因調色板大小可在[2,256]取值
RGBQUAD *Palette=(RGBQUAD*)malloc(Palettesize);
fread(Palette,Palettesize,1,fp);
bitmapinfo=(BITMAPINFO*)malloc(sizeof(BITMAPINFOHEADER)+Palettesize);
bitmapinfo->bmiHeader=infoheader;
memcpy(bitmapinfo->bmiColors,Palette,Palettesize);
fread(Img,Imgsize,1,fp);
free(Palette);
}
if(BitCount>=16)
{
bitmapinfo=(BITMAPINFO*)malloc(sizeof(BITMAPINFOHEADER));
bitmapinfo->bmiHeader=infoheader;
fread(Img,Imgsize,1,fp);
}
SetStretchBltMode(pDC->m_hDC,COLORONCOLOR);
StretchDIBits(pDC->m_hDC,0,0,zcRect.Width(),zcRect.Height(),0,0,Width,Height,Img,bitmapinfo,DIB_RGB_COLORS,SRCCOPY);
ReleaseDC(pDC);
free(bitmapinfo);
fclose(fp);
return;
}
//保存位圖
//常見需求是由位圖數據、寬、高、位深將其保存爲位圖,故此函數只考慮8位灰度,16\24\32彩色位圖
void CBmptestDlg::SaveBmp()
{
if(BitCount<8)return;
if(Img==NULL)return;
FILE*fp=fopen(Filename,"wb");
if(fp==NULL)return;
BITMAPFILEHEADER hearder;
BITMAPINFOHEADER infohearder;
int ImgSize=Stride*Height;
if (BitCount==8)
{
int PaletteSize=sizeof(RGBQUAD)*256;
hearder.bfType=0X4D42;
hearder.bfSize=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+PaletteSize+ImgSize;//文件總大小
hearder.bfReserved1=0;
hearder.bfReserved2=0;
hearder.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+PaletteSize;
fwrite(&hearder,sizeof(BITMAPFILEHEADER),1,fp);
infohearder.biSize=sizeof(BITMAPINFOHEADER);
infohearder.biWidth=Width;
infohearder.biHeight=Height;//倒序
//infohearder.biHeight=-Height;//順序
infohearder.biPlanes=1;
infohearder.biBitCount=BitCount;
infohearder.biCompression=BI_RGB;
infohearder.biSizeImage=ImgSize;
infohearder.biXPelsPerMeter = 0;
infohearder.biYPelsPerMeter = 0;
infohearder.biClrUsed = 0;
infohearder.biClrImportant = 0;
fwrite(&infohearder,sizeof(BITMAPINFOHEADER),1,fp);
RGBQUAD * palette=(RGBQUAD*)malloc(PaletteSize);
for (int i=0;i<256;i++) //這裏針對8位灰度圖
{
palette[i].rgbRed=palette[i].rgbGreen=palette[i].rgbBlue=i;
palette[i].rgbReserved=0;
}
fwrite(palette,PaletteSize,1,fp);
fwrite(Img,ImgSize,1,fp);
}
if(BitCount>=16)
{
hearder.bfType=0X4D42;
hearder.bfSize=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+ImgSize;//文件總大小
hearder.bfReserved1=0;
hearder.bfReserved2=0;
hearder.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);
fwrite(&hearder,sizeof(BITMAPFILEHEADER),1,fp);
infohearder.biSize=sizeof(BITMAPINFOHEADER);
infohearder.biWidth=Width;
infohearder.biHeight=Height;//倒序
//infohearder.biHeight=-Height;//順序
infohearder.biPlanes=1;
infohearder.biBitCount=BitCount;
infohearder.biCompression=BI_RGB;
infohearder.biSizeImage=ImgSize;
infohearder.biXPelsPerMeter = 0;
infohearder.biYPelsPerMeter = 0;
infohearder.biClrUsed = 0;
infohearder.biClrImportant = 0;
fwrite(&infohearder,sizeof(BITMAPINFOHEADER),1,fp);
fwrite(Img,ImgSize,1,fp);
}
fclose(fp);
}
界面:
關於四字節對齊:
Windows爲了存取的效率,規定位圖存儲時必須滿足一行爲4字節的整數倍。我們處理位圖時通常需要求取一行對應的字節數,需要使用如下公式:
另一個常用的公式但並不適用於位深爲1、4的位圖:
原因在於第二個公式是以字節爲單位考慮的,而第一個公式以位爲單位考慮。
關於圖像存儲順序:
位圖信息頭BITMAPINFOHEADER中的biHeight不僅體現位圖高度,還標記此位圖的存儲方式。對於一幅位圖:
若biHeight>0,則內存中存儲順序如下,在該模式下,內存中第一字節實際對應位圖的左下角,大部分位圖都按此方式存儲。
此類情況模式常用的場合是:用戶自己產生一幅圖像,譬如在數據採集系統中常常需要將數據進行圖像顯示。這時用戶需要開闢一塊內存並按一定方式填充該內存。由於順序存儲方式對用戶來說更加直觀,操作更加方便,所有常使用第二種模式。這時,在顯示和保存位圖時將BITMAPINFOHEADER中的biHeight設爲負數即可。
關於索引圖像
位深<=8位的位圖纔有調色板。本在編程時犯過一個錯誤,就是誤認爲8位深度的索引圖調色板中就含有256(即2^8)種顏色,在讀取調色板數據時若按此長度讀會導致最終顯示圖像時發生偏移。後發現調色板顏色數可以是[2,256]範圍內的任何值。
如在PS中(圖像—》模式—》索引顏色)可以設置索引顏色數20:
實際經測試可以發現調色板中包含21項:
0:( 20, 50, 26)
1:( 45, 77, 44)
2:( 9, 19, 8)
3:( 12, 40, 8)
4:( 19, 61, 8)
5:( 72,107, 61)
6:( 37, 82, 20)
7:( 60,110, 32)
8:(104,127, 88)
9:( 90,139, 51)
10:(135,160, 86)
11:( 45, 48, 18)
12:(174,171,134)
13:( 85, 68, 39)
14:(245,195,163)
15:(246,127, 75)
16:(129, 77, 56)
17:(241, 62, 22)
18:(125, 28, 11)
19:(173, 20, 9)
20:( 0, 0, 0)
另外一點,調色板類型RGBQUAD定義爲:
typedef structtagRGBQUAD {
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;
即每一項四字節表示,每一字節分別表示R、G、B、A分量。這一點是重要的,也就是說在自定義調色板數據時,不管位深是1、4、8,調色板中每一分量都是在[0,255]間變化,而與位深大小無關。關於索引圖像有個帖子可以參考一下[2]。
參考:
[1]http://www.cnblogs.com/xiehy/archive/2011/06/07/2074405.html
[2]http://bbs.csdn.net/topics/110048102