關於BMP

關於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,則內存中存儲順序如下,在該模式下,內存中第一字節實際對應位圖的左下角,大部分位圖都按此方式存儲。



若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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章