BMP文件詳解

說到圖片,位圖(Bitmap)當然是最簡單的,它Windows顯示圖片的基本格式,其文件擴展名爲*.BMP。在Windows下,任何各式的圖片文件(包括視頻播放)都要轉化爲位圖個時候才能顯示出來,各種格式的圖片文件也都是在位圖格式的基礎上採用不同的壓縮算法生成的(Flash中使用了適量圖,是按相同顏色區域存儲的)。

一、下面我們來看看位圖文件(*.BMP)的格式。

位圖文件主要分爲如下3個部分:

塊名稱

對應Windows結構體定義

大小(Byte)

文件信息頭

BITMAPFILEHEADER

14

位圖信息頭

BITMAPINFOHEADER

40

RGB顏色陣列

BYTE*

由圖像長寬尺寸決定

1、   文件信息頭BITMAPFILEHEADER

結構體定義如下:

typedef struct tagBITMAPFILEHEADER { /* bmfh */

UINT bfType;  
DWORD bfSize; 
UINT bfReserved1; 
UINT bfReserved2; 
DWORD bfOffBits;

} BITMAPFILEHEADER;

其中:

bfType

說明文件的類型,該值必需是0x4D42,也就是字符'BM'。

bfSize

說明該位圖文件的大小,用字節爲單位

bfReserved1

保留,必須設置爲0

bfReserved2

保留,必須設置爲0

bfOffBits

說明從文件頭開始到實際的圖象數據之間的字節的偏移量。這個參數是非常有用的,因爲位圖信息頭和調色板的長度會根據不同情況而變化,所以你可以用這個偏移值迅速的從文件中讀取到位數據。

2、位圖信息頭BITMAPINFOHEADER

結構體定義如下:

typedef struct tagBITMAPINFOHEADER { /* bmih */

DWORD biSize; 
LONG biWidth; 
LONG biHeight; 
WORD biPlanes; 
WORD biBitCount; 
DWORD biCompression; 
DWORD biSizeImage; 
LONG biXPelsPerMeter; 
LONG biYPelsPerMeter; 
DWORD biClrUsed; 
DWORD biClrImportant;

} BITMAPINFOHEADER;

其中:

biSize

說明BITMAPINFOHEADER結構所需要的字數。

biWidth

說明圖象的寬度,以象素爲單位。

biHeight

說明圖象的高度,以象素爲單位。注:這個值除了用於描述圖像的高度之外,它還有另一個用處,就是指明該圖像是倒向的位圖,還是正向的位圖。如果該值是一個正數,說明圖像是倒向的,如果該值是一個負數,則說明圖像是正向的。大多數的BMP文件都是倒向的位圖,也就是時,高度值是一個正數。

biPlanes

爲目標設備說明位面數,其值將總是被設爲1。

biBitCount

說明比特數/象素,其值爲1、4、8、16、24、或32。但是由於我們平時用到的圖像絕大部分是24位和32位的,所以我們討論這兩類圖像。

biCompression

說明圖象數據壓縮的類型,同樣我們只討論沒有壓縮的類型:BI_RGB。

biSizeImage

說明圖象的大小,以字節爲單位。當用BI_RGB格式時,可設置爲0。

biXPelsPerMeter

說明水平分辨率,用象素/米表示。

biYPelsPerMeter

說明垂直分辨率,用象素/米表示。

biClrUsed

說明位圖實際使用的彩色表中的顏色索引數(設爲0的話,則說明使用所有調色板項)。

biClrImportant

說明對圖象顯示有重要影響的顏色索引的數目,如果是0,表示都重要。

3、RGB顏色陣列

有關RGB三色空間我想大家都很熟悉,這裏我想說的是在Windows下,RGB顏色陣列存儲的格式其實BGR。也就是說,對於24位的RGB位圖像素數據格式是:

藍色B值

綠色G值

紅色R值

對於32位的RGB位圖像素數據格式是:

藍色B值

綠色G值

紅色R值

透明通道A值

透明通道也稱Alpha通道,該值是該像素點的透明屬性,取值在0(全透明)到255(不透明)之間。對於24位的圖像來說,因爲沒有Alpha通道,故整個圖像都不透明。




將RGB數據保存爲bmp文件

int bmp_write(unsigned char *image, int xsize, int ysize, char *filename)
{
	unsigned char header[54] = {
		/**********************文件頭BITMAPFILEHEADER*************************/
		0x42, 0x4d,    //2字節,文件標識, 必須爲BM
		0, 0, 0, 0,       //4字節,位圖的文件大小,以字節爲單位
		0, 0,              //2字節,保留字,必須爲0
		0, 0,				//2字節,保留字,必須爲0
		54, 0, 0, 0,		//4字節,位圖數據的起始位置,以相對於位圖文件頭的偏移量表示,以字節爲單位
		/********************位圖信息頭BITMAPINFOHEADER************************/
		40, 0, 0, 0,		//4字節,位圖描述信息塊的大小
		0, 0, 0, 0,		//4字節,位圖的寬度
		0, 0, 0, 0,		//4字節,位圖的高度
		1, 0,				//2字節,爲目標設備說明位面數,其值將總是被設爲1
		24, 0,				//2字節,每個像素所需的位數,必須是1(雙色),4(16色),8(256色)或24(真彩色)之一
		0, 0, 0, 0,		//4字節,數據的壓縮方式,必須是 0(不壓縮),1(BI_RLE8壓縮類型)或2(BI_RLE4壓縮類型)之一
		0, 0, 0, 0,		//4字節,數據區的大小,以字節爲單位,必須是4的倍數
		0, 0, 0, 0,		//4字節,水平每米有多少個像素
		0, 0, 0, 0,		//4字節,垂直每米有多少個像素
		0, 0, 0, 0,		//4字節,實際使用的彩色表中的顏色索引數,固定爲0
		0, 0, 0, 0		//4字節,對圖象顯示有重要影響的顏色索引的數目,固定爲0
	};

	long file_size = (long)xsize * (long)ysize * 3 + 54;
	header[2] = (unsigned char)(file_size &0x000000ff);
	header[3] = (file_size >> 8) & 0x000000ff;
	header[4] = (file_size >> 16) & 0x000000ff;
	header[5] = (file_size >> 24) & 0x000000ff;

	long width = xsize;
	header[18] = width & 0x000000ff;
	header[19] = (width >> 8) &0x000000ff;
	header[20] = (width >> 16) &0x000000ff;
	header[21] = (width >> 24) &0x000000ff;

	long height = ysize;
	header[22] = height &0x000000ff;
	header[23] = (height >> 8) &0x000000ff;
	header[24] = (height >> 16) &0x000000ff;
	header[25] = (height >> 24) &0x000000ff;

	char fname_bmp[128];
	sprintf(fname_bmp, "%s.bmp", filename);

	FILE *fp;
	if (!(fp = fopen(fname_bmp, "wb"))) 
		return -1;

	fwrite(header, sizeof(unsigned char), 54, fp);
	fwrite(image, sizeof(unsigned char), (size_t)(long)xsize * ysize * 3, fp);

	fclose(fp);
	return 0;
}



'1BMP文件頭:BMP文件頭數據結構含有BMP文件的類型、文件大小和位圖起始位置等信息                 字節數
Public Type BITMAPFILEHEADER                                                              '2+4+2+2+4=14
        bfType As Integer '位圖文件的類型,必須爲BM                                             2
        bfSize As Long '位圖文件的大小,以字節爲單位                                            4
        bfReserved1 As Integer '位圖文件保留字,必須爲0                                         2
        bfReserved2 As Integer '位圖文件保留字,必須爲0                                         2
        bfOffBits As Long '位圖數據的起始位置,以相對於位圖文件頭的偏移量表示,以字節爲單位     4
End Type
'2位圖信息頭:BMP位圖信息頭數據用於說明位圖的尺寸等信息
Public Type BITMAPINFOHEADER
        biSize As Long '本結構(BITMAPINFOHEADER)所佔用字節數
        biWidth As Long '位圖的寬度,以像素爲單位
        biHeight As Long '位圖的高度,以像素爲單位
        biPlanes As Integer '目標設備的級別,必須爲1 Specifies the number of planes for the target device. This value must be set to 1.
        biBitCount As Integer '每個像素所需的位數,必須是1(雙色),4(16色),8(256色)或24(真彩色)之一
                              '                    分爲1 4 8 16 24 32 本文沒對1 4 進行研究
        biCompression As Long ' 位圖壓縮類型,必須是 0(不壓縮),1(BI_RLE8壓縮類型)或2(BI_RLE4壓縮類型)之一
                              ' 本以爲壓縮類型,但是卻另外有作用,稍候解釋***************
        biSizeImage As Long '表示位圖數據區域的大小以字節爲單位
        biXPelsPerMeter As Long '位圖水平分辨率,每米像素數
        biYPelsPerMeter As Long '位圖垂直分辨率,每米像素數
        biClrUsed As Long '位圖實際使用的顏色表中的顏色數
        biClrImportant As Long '位圖顯示過程中重要的顏色數
End Type
'第一塊是bmp的文件頭用於描述整個bmp文件的情況(BITMAPFILEHEADER).第二塊是位圖信息頭,即BITMAPINFOHEADER,用於描述整個位圖文件的情況.
'第三塊就是調色板信息或者掩碼部分,如果是8位位圖則存放調色板 ;16 與32位 位圖則存放RGB顏色的掩碼,這些掩碼以DWORD大小來存放。
'3顏色表:顏色表用於說明位圖中的顏色,它有若干個表項,每一個表項是一個RGBQUAD類型的結構,定義一種顏色.最後一塊就是位圖的數據實體。
Public Type RGBQUAD
        rgbBlue As Byte '藍色的亮度(值範圍爲0-255)
        rgbGreen As Byte '綠色的亮度(值範圍爲0-255)
        rgbRed As Byte '紅色的亮度(值範圍爲0-255)
        rgbReserved As Byte '保留,必須爲0
End Type
'顏色表中RGBQUAD結構數據的個數有biBitCount來確定:
'當biBitCount=1,4,8時,分別有2,16,256個表項;
'當biBitCount=24時,沒有顏色表項。
'位圖信息頭和顏色表組成位圖信息,BITMAPINFO結構定義如下:
Public Type BITMAPINFO
        bmiHeader As BITMAPINFOHEADER '位圖信息頭
        bmiColors As RGBQUAD ' 顏色表
End Type
'4、 位圖數據:位圖數據記錄了位圖的每一個像素值,記錄順序是在掃描行內是從左到右,
'掃描行之間是從下到上。位圖的一個像素值所佔的字節數:
'當biBitCount=1時,8個像素佔1個字節;
'當biBitCount=4時,2個像素佔1個字節;
'當biBitCount=8時,1個像素佔1個字節;
'當biBitCount=24時,1個像素佔3個字節;
'Windows規定一個掃描行所佔的字節數必須是4的倍數(即以long爲單位),不足的以0填充,
'一個掃描行所佔的字節數計算方法:
'DataSizePerLine= (biWidth* biBitCount+31)/8; // 一個掃描行所佔的字節數
'DataSizePerLine= DataSizePerLine/4*4; // 字節數必須是4的倍數
'位圖數據的大小 (不壓縮情況下):
'DataSize= DataSizePerLine* biHeight;


'二、BMP文件分析
'分析:首先請注意所有的數值在存儲上都是按"高位放高位、低位放低位的原則",
'如12345678h放在存儲器中就是7856 3412)。下圖是一張圖16進制數據,以此爲例
'進行分析。在分析中爲了簡化敘述,以一個字(兩個字節爲單位,如424D就是一個
'字)爲序號單位進行,"h"表示是16進制數。
'424D 4690 0000 0000 0000 4600 0000 2800
'0000 8000 0000 9000 0000 0100 1000 0300
'0000 0090 0000 A00F 0000 A00F 0000 0000
'0000 0000 0000 00F8 0000 E007 0000 1F00
'0000 0000 0000 02F1 84F1 04F1 84F1 84F1
'06F2 84F1 06F2 04F2 86F2 06F2 86F2 86F2
'1:圖像文件頭。424Dh='BM',表示是Windows支持的BMP格式。
'2-3:整個文件大小。4690 0000,爲00009046h=36934。
'4-5:保留,必須設置爲0
'6-7:從文件開始到位圖數據之間的偏移量。4600 0000,爲00000046h=70,上面的文件頭就是35字=70字節
'8-9:位圖圖信息頭長度
'10-11:位圖寬度,以像素爲單位。8000 0000,爲00000080h=128
'12-13:位圖高度,以像素爲單位。9000 0000,爲00000090h=144
'14:位圖的位面數,該值總是1。0100,爲0001h=1
'15:每個像素的位數。有1(單色),4(16色),8(256色),16(64K色,高彩色),24(16M色,真彩色),
'    32(4096M色,增強型真彩色)。T408支持的是16位格式。1000爲0010h=16
'16-17:壓縮說明:有0(不壓縮),1(RLE 8,8位RLE壓縮),2(RLE 4,4位RLE壓縮),3(Bitfields,位域存放)。
'       RLE簡單地說是採用像素數+像素值的方式進行壓縮。T408採用的是位域存放方式,用兩個字節表示一個像素,
'       位域分配爲r5b6g5。圖中0300 0000爲00000003h=3。
'18-19:用字節數表示的位圖數據的大小,該數必須是4的倍數,數值上等於位圖寬度×位圖高度×每個像素位數。
'       0090 0000爲00009000h=80×90×2h=36864。
'20-21:用象素/米表示的水平分辨率。A00F 0000爲0000 0FA0h=4000。
'22-23:用象素/米表示的垂直分辨率。A00F 0000爲0000 0FA0h=4000。
'2:位圖使用的顏色索引數。設爲0的話,則說明使用所有調色板項。
'26-27:對圖象顯示有重要影響的顏色索引的數目。如果是0,表示都重要。
'28-35:彩色板規範。對於調色板中的每個表項,用下述方法來描述RGB的值:
'1 字節用於藍色分量
'1 字節用於綠色分量
'1 字節用於紅色分量
'1 字節用於填充符 (設置爲0)
'對於24-位真彩色圖像就不使用彩色表,因爲位圖中的RGB值就代表了每個象素的顏色。但是16位r5g6b5位域彩色圖像需
'要彩色表,看前面的圖,與上面的解釋不太對得上,應以下面的解釋爲準。
'圖中彩色板爲00F8 0000 E007 0000 1F00 0000 0000 0000,其中:
'00FB 0000爲FB00h=1111100000000000(二進制),是紅色分量的掩碼。
'E007 0000爲 07E0h=0000011111100000(二進制),是綠色分量的掩碼。
'1F00 0000爲001Fh=0000000000011111(二進制),是紅色分量的掩碼。
'0000 0000總設置爲0
'將掩碼跟像素值進行"與"運算再進行移位操作就可以得到各色分量值。看看掩碼,就可以明白事實上在每個像素值的兩
'個字節16位中,按從高到低取5、6、5位分別就是r、g、b分量值。取出分量值後把r、g、b值分別乘以8、4、8就可以補
'齊第個分量爲一個字節,再把這三個字節按rgb組合,放入存儲器(同樣要反序),就可以轉換爲24位標準BMP格式了








'4 字節對其問題
'關於數據讀取。Bmp文件有個重要特性,那就是對於數據區域而言,每行的數據它必須湊滿4字節,如果沒有滿,則用冗餘
'的數據來補齊。這個特性直接影響到我們讀取位圖數據的方法,因爲在我們看來(x,y)的數據應該在 y*width+x這樣的位
'置上 但是因爲會有冗餘信息 那麼必須將width用width+該行的冗餘量來處理,而由於位圖文件有不同的位數,所以這樣
'的計算也不盡相同。
'     下面列出計算偏移量的一般公式。
'     首先將位圖信息讀入一個UCHAR 的buffer中:
'8 位:
'int pitch;
'        if(width%4==0){
'           pitch=width;
'        }else{
'           pitch=width+4-width%4;
'       }
'        index=buffer[y*pitch+x]; 因爲8位位圖的數據區域存放的是調色板索引值,所以只需讀取這個index
'16    位
'       int pitch=width+width%2;
'       buffer [(y*pitch+x)*2]
'       buffer [(i*pitch+j)*2+1]
'兩個UCHAR內,存放的是(x,y)處的顏色信息
'24   位
'       int pitch=width%4;
'        buffer[(y*width+x)*3+y*pitch];
'        buffer[(y*width+x)*3+y*pitch+1];
'        buffer[(y*width+x)*3+y*pitch+2];
'32   位
'       由於一個象素就是4字節 所以無需補齊
'     雖然計算比較繁瑣,但是這些計算是必須的,否則當你的位圖每行的象素數不是4的倍數,那麼y*width+x帶給你的是
'     一個扭曲的圖片,當然如果你想做這樣的旋轉,也不錯啊,至少我因爲一開始沒有考慮(不知道這個特性)讓一個每
'     行象素少1字節的16位圖片變成了扭曲的菱形




'有了數據分離RGB分量。
'     由於我的測試代碼用了GDI,所以我必須講得到的某一個點的值分離成 24位模式下的RGB分離,這不是一件容易的工作。
'位圖麻煩的地方之一就是他的格式太多,所以我們還是要分格式再討論。


'8     位
'     通過第二部分提到的操作我們得到了一個index,這個值的範圍是0~255 一共256個 正好是調色板的顏色數量。
'     在8位bmp圖片中 數據信息前256個RGBQUAD的大小開始就是調色板的信息。不過如果要組織成調色板還要一定的轉換因
'爲裏面是RGBQUAD信息 r b 兩個與調色板中的順序是顛倒的。因爲我不需要調色板設置所以我字節讀取到RGBQUAD數組中,並
'且通過下面的表達式獲取RGB值:
' UCHAR r=quad[index].rgbRed;
' UCHAR g=quad[index].rgbGreen;
' UCHAR b=quad[index].rgbBlue;


'16 位
'這是最麻煩的一個。因爲在處理時有555 565 兩種格式的區別,而且還有所謂壓縮類型的區別。
'之前的bitmapinfoheader裏面提到一個biCompression
'現在我們分兩種情況討論: BI_RGB和BI_BITFIELDS
'當他等於BI_RGB時 只有555 這種格式,所以可以放心大膽的進行如下的數據分離:
'UCHAR b=buffer[(i*pitch+j)*2]&0x1F;
'UCHAR g=(((buffer[(i*pitch+j)*2+1]<<6)&0xFF)>>3)+(buffer[(i*pitch+j)*2]>>5);
'UCHAR r=(buffer[(i*pitch+j)*2+1]<<1)>>3;
'希望不要被這個表達式折磨的眼花繚亂,我想既然你在看這篇文章,你就有能力閱讀這樣的代碼,否則只能說你還沒有到閱讀
'這方面的地步,需要去學習基礎的語法了。
'有一點值得提醒的是由於有較多的位操作 ,所以在處理的時候在前一次操作的上面加上一對括號,我就曾經因爲沒有加而導
'致出現誤差,另外雖然buffer中一個元素代表的是一個UCHAR 但是右移操作會自動增長爲兩字節 所以需要在進行一次與操作
'截取低位的1字節數據。


'現在討論BI_BITFIELDS。
'這個模式下 既可以有555 也可以有565 。
'555 格式 xrrrrrgggggbbbbb
'565 格式 rrrrrggggggbbbbb
'顯然不同的格式處理不同,所以我們要首先判斷處到底屬於那種格式。
'Bitmapinfoheader的biCompression爲BI_BITFIELDS時,在位圖數據區域前存在一個RGB掩碼的描述是3個DWORD值,我們只需要
'讀取其中的R或者G的掩碼,來判斷是那種格式。
'以紅色掩碼爲例 0111110000000000的時候就是555格式 1111100000000000就是565格式。
'以下是565格式時的數據分離:
'UCHAR b=buffer[(i*pitch+j)*2]&0x1F;
'UCHAR g=(((buffer[(i*pitch+j)*2+1]<<5)&0xFF)>>2)+(buffer[(i*pitch+j)*2]>>5);
'UCHAR r=buffer[(i*pitch+j)*2+1]>>3;


'現在我們得到了RGB各自的分量,但是還有一個新的問題,那就是由於兩字節表示了3個顏色  555下每個顏色最多到0x1F
'565格式下最大的綠色分量也就0x3F。所以我們需要一個轉換 color=color*255/最大顏色數 即可
'如565下RGB(r*0xFF/0x1F,g*0xFF/0x3F,b*0xFF/0x1F)


'24 位
'UCHAR b=buffer[(i*width+j)*3+realPitch];
'UCHAR g=buffer[(i*width+j)*3+1+realPitch];
'UCHAR r=buffer[(i*width+j)*3+2+realPitch];


'32    位
'UCHAR b=buffer[(i*width+j)*4];
'UCHAR g=buffer[(i*width+j)*4+1];
'UCHAR r=buffer[(i*width+j)*4+2];


'剩餘的問題
'    當數據取到了,顏色也分離出來了 ,但是可能你繪出的位圖是倒轉的,這是因爲有些位圖的確是翻轉的。通過bitmapinfoheader的biHeight可以判斷是正常還是翻轉,當biHeight>0的時候顛倒,它小於0的時候正常,不過測試寫到現在看到的文件都是顛倒過來的。



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