BMP文件格式解析

BMP文件格式解析

作者:水木子

一、圖像概述

1.1 位圖

位圖Bitmap),又稱柵格圖(英語:Raster graphics)或點陣圖,是使用像素陣列來表示的圖像。 ----維基百科

簡單地說,位圖就是由一個一個像素點構成的圖像。常見的圖像格式有jpg(jpeg)、png、bmp都是位圖。

1.2 矢量圖

矢量圖形是計算機圖形學中用點、直線或者多邊形等基於數學方程的幾何圖元表示圖像。

​ ----維基百科

矢量圖與位圖不同,它不是由一個一個像素點構成的,它的實質是數學表達式。svg格式文件是矢量圖。

1.3 位圖與矢量圖的區別

位圖與矢量圖最明顯的區別是:位圖放大會出現馬賽克,畫質變差;矢量圖可以在不降低畫質的條件下無限放大。

在這裏插入圖片描述

圖像來源:維基百科


圖中a表示原圖像。若a爲矢量圖,則放大紅框中圖像時,效果如b,可以看見圖像質量並沒有下降;如果a是位圖,則放大紅框中圖像時,效果如c,可以明顯看見有一個一個小方格,圖像質量明顯下降。

1.4 如何表示像素點的顏色

選擇一張位圖,在PS中放大到3200%,如下所見:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-MESatFy9-1573204554252)(img/image-20191102143341677.png)]

可以很明顯得看見一個一個的小方塊,這就是像素點。

一個像素點有特定的位置和顏色值。每個像素的顏色由RGB組合或灰度值表示。

本小節重點介紹如何表示顏色。

根據位深度,可以將位圖分爲1、4、8、16、24及32位圖像等。這裏的位深度是指用於表示像素點顏色的比特位個數。如果一個像素點由一個比特位表示顏色,那麼其位深度爲1,如果一個像素點由四個比特位表示顏色,則其位深度爲4,依次類推。

  • 如果一張圖片的像素點由1個比特位來表示顏色,則這個比特位爲0或1,則可以表示21種顏色,即黑與白,則這張照片是純粹的黑白照片。
  • 如果一張圖片的像素點由8個比特位來表示顏色,則這八個比特位共可以表示28種顏色,即256。這種圖像通常(有例外,下面會講)稱爲灰度圖,因爲這256種顏色是黑白灰(這裏的灰是指244種不同程度的灰)。如下爲灰度圖:

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XND0Cdem-1573204554252)(img/grey8.bmp)]

  • 如果一張圖片的像素點由24個比特位來表示顏色,則這24個比特位共可以表示224種顏色,即有1600萬個以上的顏色。這種圖像稱爲真彩色圖。這24個比特位以8位分爲三個通道,分別表示紅、綠、藍。這就是RGB顏色編碼方式,用紅、綠、藍三原色的光學強度來表示一種顏色。這是最常見的位圖編碼方法,可以直接用於屏幕顯示。

二、BMP文件格式

2.1 BMP簡介

BMP取自位圖Bitmap的縮寫,也稱爲DIB(與設備無關的位圖),是一種獨立於顯示器的位圖數字圖像文件格式。常見於微軟視窗和OS/2操作系統。 ----維基百科

BMP格式就是表示位圖的格式。

BMP格式圖像中的像素點,其位深度可以是1,4,8,24,32,但常見的BMP位深度還是8和24。

選擇一張BMP圖像,右鍵打開屬性 --> 詳細信息,可以查看其位深度。

當BMP文件位深度爲8時,並不一定代表這張圖片爲灰度圖,如下:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Mo8accMD-1573204554254)(img/swift8.bmp)]

這張圖片的位深度爲8,但其並不是灰度圖,我們稱之爲僞彩色圖

下面這張圖是位深度爲24的真彩色圖,可以作爲對比:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-wwYgzFde-1573204554258)(img/swift24.bmp)]

可以看出真彩色圖的質量明顯高於僞彩色圖。

2.2 BMP文件格式組成

BMP文件由以下四部分組成:

  • 位圖文件頭(BITMAPFILEHEADER)
  • 位圖信息頭(BITMAPINFOHEADER)
  • 顏色表*(RGBQUAD[])
  • 像素陣列(Pixels[][])

由於顏色表並不一定存在,故加*說明。

下面先將各部分的信息簡單說明一下:

2.2.1 位圖文件頭

用於描述整個bmp文件的情況,具體包括BMP文件的類型、文件大小和位圖起始位置等信息。

typedef struct tagBITMAPFILEHEADER{
    WORD	bfType;                  // 位圖文件的類型,必須爲BMP (2個字節)
    DWORD	bfSize;                  // 位圖文件的大小,以字節爲單位 (4個字節)
    WORD	bfReserved1;             // 位圖文件保留字,必須爲0 (2個字節)
    WORD	bfReserved2;             // 位圖文件保留字,必須爲0 (2個字節)
    DWORD	bfOffBits;               // 位圖數據的起始位置,以相對於位圖 (4個字節)
} BITMAPFILEHEADER;

位圖文件頭總共有14個字節

2.2.2 位圖信息頭

用於描述位圖的尺寸等信息。

typedef struct tagBITMAPINFOHEADER{
	DWORD biSize;     		// 本結構所佔用字節數  (4個字節)
	LONG biWidth;      		// 位圖的寬度,以像素爲單位(4個字節)
	LONG biHeight;     		// 位圖的高度,以像素爲單位(4個字節)
	WORD biPlanes;    		// 目標設備的級別,必須爲1(2個字節)
	WORD biBitCount; 		// 每個像素所需的位數,必須是1(雙色)、// 4(16色)、8(256色)、
	    					//24(真彩色)或32(增強真彩色)之一 (2個字節)
	DWORD biCompression; 	// 位圖壓縮類型,必須是 0(不壓縮)、 1(BI_RLE8 
	                     	// 壓縮類型)或2(BI_RLE4壓縮類型)之一 ) (4個字節)
	DWORD biSizeImage;     	// 位圖的大小,以字節爲單位(4個字節)
	LONG biXPelsPerMeter;  	// 位圖水平分辨率,每米像素數(4個字節)
	LONG biYPelsPerMeter;   // 位圖垂直分辨率,每米像素數(4個字節)
	DWORD biClrUsed;        // 位圖實際使用的顏色表中的顏色數(4個字節)
	DWORD biClrImportant;   // 位圖顯示過程中重要的顏色數(4個字節)
} BITMAPINFOHEADER;

位圖信息頭一共有40個字節

2.2.3 顏色表

用於說明位圖中的顏色,它有若干個表項,每一個表項是一個RGBQUAD類型的結構,定義一種顏色。

typedef struct tagRGBQUAD 
{
  BYTE rgbBlue;          // 藍色的亮度(值範圍爲0-255)
  BYTE rgbGreen;         // 綠色的亮度(值範圍爲0-255)
  BYTE rgbRed;           // 紅色的亮度(值範圍爲0-255)
  BYTE rgbReserved;      // 保留,必須爲0
} RGBQUAD;

可以看到一個RGB表項爲4個字節

顏色表中RGBQUAD結構數據的個數由位圖信息頭中的biBitCount來確定:

  • 當biBitCount=1, 4, 8時,分別有2, 16,256個表項
  • 當biBitCount=24時,沒有顏色表項

2.2.4 像素陣列

l記錄了位圖的每一個像素值,記錄順序是在掃描行內是從左到右,掃描行之間是從下到上。位圖的一個像素值所佔的字節數如下:

  • 當biBitCount=1時,8個像素佔1個字節;

  • 當biBitCount=4時,2個像素佔1個字節;

  • 當biBitCount=8時,1個像素佔1個字節;

  • 當biBitCount=24時,1個像素佔3個字節,分別是R、G、B;

Windows規定一個掃描行所佔的字節數必須是4的倍數(即以long爲單位),不足的以0填充。

三、實例解析BMP文件格式

用notepad++打開grey8.bmp文件,選擇插件 --> HEX-Editor --> View in HEX,如果沒有可自行選擇插件管理進行安裝,最終界面如下:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-MYsNmSvS-1573204554260)(img/image-20191102154654330.png)]

這是以十六進制的形式顯示圖像信息,一個十六進制數字佔4個比特位,故一行表示十六個字節。

在對BMP文件進行具體解析前,我們還要先了解一下數據的存放順序:

在BMP文件中,如果一個數據需要用幾個字節來表示的話,那麼該數據的存放字節順序爲“低地址存放低位數據,高地址存放高位數據”。如數據0x1756在內存中的存儲順序爲:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NSD9dSVY-1573204554262)(img/bmp_5.png)]

這種存儲方式稱爲小端方式(little endian) , 與之相反的是大端方式(big endian)。

3.1 位圖文件頭

下圖紅框中爲位圖文件頭:

  • 頭兩個字節(0,1)表示位圖文件的類型,即0x4d42表示類型,爲BMP,這與DUMP中的頭兩個字節表示的符號BM一致

  • 後面的四個字節(2,3,4,5)表示位圖文件的大小,即0x0000c436表示位圖文件大小,換算爲十進制爲50230,我們打開grey8.bmp的屬性,發現其大小確爲50230字節:

    位圖文件的大小,即bfSize的值需要如何計算呢?
    bfSize=14+40+4+/8 bfSize=位圖文件頭的大小(14字節)+位圖信息頭的大小(40字節)\\+顏色表項*4字節+圖像的寬度*高度*位深度/8(字節)
    我們打開grey8.bmp的屬性,點擊詳細信息,可以看到grey8.bmp的大小爲256x192像素,位深度爲8,又因爲其位深度爲8,故其顏色表項有256項(每項佔據4字節),故grey8.bmp的大小爲:
    bfSize=14+40+2564+2561928/8=50230() bfSize=14+40+256*4+256*192*8/8=50230(字節)

  • 再後面的兩個字節(6,7)是位圖文件保留字1,必須爲0,即爲0x0000。

  • 再後面的兩個字節(8,9)是位圖文件保留字2,必須爲0,即爲0x0000。

  • 最後的四個字節(a,b,c,d)是位圖數據的起始位置,其值爲0x00000436,換算爲十進制爲1078。這個表示的是從文件開始到像素陣列之間的字節數,即其大小爲:位圖文件頭(14字節)+位圖信息頭(40字節)+[256個表項*4字節],因爲顏色表項並不一定存在,故用[]括起來。在grey8.bmp圖像中存在顏色表,故位圖數據的起始值爲1078。

3.2 位圖信息頭

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-7pzsYKPp-1573204554264)(img/BITMAPINFOHEADER .png)]

  • 頭四個字節(第一行的e,f,第二行的0,1)表示位圖信息頭所佔用的字節數,即爲0x00000028,換算爲十進制是40;

  • 後四個字節(第二行的2,3,4,5)爲位圖的以像素爲單位的寬度,即0x00000100,換算爲十進制爲256,與實際相符。

  • 後四個字節(第二行的6,7,8,9)爲位圖的以像素爲單位的高度,即0x000000c0,換算爲十進制爲192,與實際相符。

  • 後兩個字節(第二行的a,b)爲目標設備的級別,必須爲1,其值爲0x0001,相符。

  • 後兩個字節(第二行的c,d)爲每個像素所需的位數,其值爲0x0008,與實際的位深度相一致。

  • 後四個字節(第二行的e,f,第三行的0,1)表示位圖的壓縮類型,其值爲0x00000000,即不壓縮。

  • 後四個字節(第三行的2,3,4,5)爲位圖的大小,以字節爲單位,其值爲0x0000c000,換算爲十進制爲49152,其實就是計算像素陣列的大小,計算方式爲:
    biSizeImage=/8 biSizeImage=圖像的寬度*高度*位深度/8
    在grey8.bmp圖像中
    biSizeImage=2561928/8=49152 biSizeImage=256*192*8/8=49152

  • 後四個字節(第三行的6,7,8,9)表示位圖的水平分辨率,其值爲0x00002e23。

  • 後四個字節(第三行的a,b,c,d)表示位圖的垂直分辨率,其值爲0x00002e23。

  • 後四個字節(第三行的e,f,第四行的0,1)表示位圖實際使用的顏色表中的顏色數,其值爲0x00000000,一般置爲0。

  • 最後的四個字節(第四行的2,3,4,5)表示位圖顯示過程中重要的顏色數,其值爲0x00000000,一般置爲0。

3.3 顏色表

當位深度爲24時,沒有顏色表,位圖信息頭緊接着就是像素陣列;

當位深度不爲24時,有顏色表,並且有2位深度個顏色表項,每個表項佔據4個字節。

在如下圖的黃框中,共有256個顏色表項,共由256*4個字節(只顯示了一部分):

在這裏插入圖片描述

由於每個顏色表項爲4個字節,因此我們以4個字節爲單位劃分爲一個個的顏色表項,即一個黑框。

由於grey8.bmp是灰度圖,因此其顏色表項是由規律可循的。

在顏色表項內部,RGB三個分量相等,第4個分量爲0;在整個顏色表中,顏色表項的前三個分量數值由0到255依次遞增1。其實,rgb(0,0,0)代表黑色,rgb(255,255,255)代表白色,rgb(x,x,x)(x不等於0或255,x爲0到255之間的整數)代表不同程度的灰色。

當BMP文件爲僞彩色圖時,其位深度是8位,但並沒有規律可循。比如第一個顏色表項是rgb(1,22,3,0),第二個顏色表項是rgb(10,89,90,0),依次類推。這些顏色並不是黑白灰,而是會顯示出其他顏色,但是,最多隻能顯示256種顏色,遠遠不如真彩色圖的1600萬多種顏色,因此這類圖像叫做僞彩色圖。

3.4 像素陣列

顏色表(或位圖信息頭)後就是像素陣列,在本例中,位深度爲8,故一個字節就代表一個像素點。那如何確定這個像素點的顏色呢?一個字節表示的範圍爲[0,255],這下你該明白了吧!根據這個字節的值找到相應的顏色表項,這個顏色表項所對應的顏色就是這個像素的顏色。對於有顏色表的BMP,其運作方式就是如此。

對於真彩色圖,其位深度爲24,一個像素就自然而然地對應着R、G、B三個顏色分量,故不需要顏色表了,並且,對於真彩色圖,如果有顏色表,那麼其有1600萬多個顏色表項,整個文件將會非常龐大。

三、代碼實驗

3.1 實驗環境

  • 操作系統:Windows 10
  • 編譯器:Dev-cpp,Visual Studio 2017

3.2 實驗內容

將以下grey8_test.bmp灰度圖的顏色表項修改爲隨機值,將原灰度圖變爲僞彩色圖。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fPGLoDqK-1573204554267)(img/grey8_test-1572943023710.bmp "grey8_test.bmp")]

3.3 其他信息

BITMAPFILEHEADER、BITMAPINFOHEADER、RGBQUAD 這三個結構體在windows.h中都有定義

3.4 關鍵代碼

因爲代碼都有詳細註釋,故在此處不再詳細說明。

  • 讀取相關信息:
	BITMAPFILEHEADER *fileHeader;
	BITMAPINFOHEADER *infoHeader;
	//爲位圖文件頭、位圖信息頭指針分配空間 
	fileHeader = (BITMAPFILEHEADER *)malloc(sizeof(BITMAPFILEHEADER));
	infoHeader = (BITMAPINFOHEADER *)malloc(sizeof(BITMAPINFOHEADER));
	//讀取灰度圖src的位圖文件頭、位圖信息頭信息 
	fread(fileHeader, sizeof(BITMAPFILEHEADER), 1, src);
	fread(infoHeader, sizeof(BITMAPINFOHEADER), 1, src);
	//爲顏色表指針分配空間 
	RGBQUAD *rgb = (RGBQUAD *)malloc(sizeof(RGBQUAD) * 256);
	//讀取顏色表項
	fread(rgb, sizeof(RGBQUAD), 256, src);
  • 利用隨機數函數修改顏色表項
	//修改顏色表項的RGB三分量的值 
	srand((unsigned)time(NULL));
	for (int i = 0; i < 256; i++) {
		rgb[i].rgbBlue = rand() % 255;
		rgb[i].rgbGreen = rand() % 255;
		rgb[i].rgbRed = rand() % 255;
	}
  • 將讀取到的信息寫入目標文件
	//將位圖文件頭、位圖信息頭、顏色表寫入目標文件
	fwrite(fileHeader, sizeof(BITMAPFILEHEADER), 1, target);
	fwrite(infoHeader, sizeof(BITMAPINFOHEADER), 1, target);
	fwrite(rgb, sizeof(RGBQUAD), 256, target);
  • 讀取像素信息並寫入,注意四字節對齊
	//讀取灰度圖像的像素
	unsigned char *ch;
	ch = (unsigned char*)malloc(sizeof(unsigned char));

	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < ((width + 3) / 4) * 4; j++)
		{
			fread(ch, sizeof(unsigned char), 1, src);
			fwrite(ch, sizeof(unsigned char), 1, target);
		}
	}

3.5 注意點!!!

  • 以二進制形式打開文件!!!!!!!
  • 注意一行的像素所佔的字節數是4的倍數。讀文件時,要多讀取後面補充的0字節;寫文件時,也要多寫需要填充的0字節。否則可能會出現圖像錯亂的現象。

3.6 結果

利用我們的程序,生成了以下圖片,還挺漂亮的!

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CPtn37fu-1573204554269)(img/1.bmp)][外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-eTnc5MNE-1573204554271)(img/2.bmp)][外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-7PEO2f0U-1573204554273)(img/3.bmp)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-PMaqaxgH-1573204554275)(img/4.bmp)][外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-7bQn5VRs-1573204554277)(img/5.bmp)][外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oMHLZ8xG-1573204554281)(img/pseudo.bmp)]

3.7 完整代碼

參考code文件中的源.cpp和question3文件

四、擴展實驗

  • 編寫C/C++程序,讀取一個24位真彩色BMP文件,然後轉化爲灰色圖像,最後存儲爲8位僞彩色BMP文件。
  • 編寫C/C++程序,讀取一個8位僞彩色BMP文件,轉化爲24位真彩色BMP文件,最後存儲。

備註:從RGB到灰度圖的轉換公式:Gray = R*0.299 + G*0.587 + B*0.114

五、參考資料

[1] https://www.cnblogs.com/Matrix_Yao/archive/2009/12/02/1615295.html

[2] 多媒體基礎課程相關資料

[3] 維基百科關於“位圖”、“矢量圖”、“BMP格式”的介紹

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