BMP檔案詳解

masikkk發表於2013-08-06

說到圖片,點陣圖(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的時候正常,不過測試寫到現在看到的檔案都是顛倒過來的。