BMP文件格式解析
作者:水木子
文章目錄
一、圖像概述
1.1 位圖
位圖(Bitmap),又稱柵格圖(英語:Raster graphics)或點陣圖,是使用像素陣列來表示的圖像。 ----維基百科
簡單地說,位圖就是由一個一個像素點構成的圖像。常見的圖像格式有jpg(jpeg)、png、bmp都是位圖。
1.2 矢量圖
矢量圖形是計算機圖形學中用點、直線或者多邊形等基於數學方程的幾何圖元表示圖像。
----維基百科
矢量圖與位圖不同,它不是由一個一個像素點構成的,它的實質是數學表達式。svg格式文件是矢量圖。
1.3 位圖與矢量圖的區別
位圖與矢量圖最明顯的區別是:位圖放大會出現馬賽克,畫質變差;矢量圖可以在不降低畫質的條件下無限放大。
圖像來源:維基百科
圖中a表示原圖像。若a爲矢量圖,則放大紅框中圖像時,效果如b,可以看見圖像質量並沒有下降;如果a是位圖,則放大紅框中圖像時,效果如c,可以明顯看見有一個一個小方格,圖像質量明顯下降。
1.4 如何表示像素點的顏色
選擇一張位圖,在PS中放大到3200%,如下所見:
可以很明顯得看見一個一個的小方塊,這就是像素點。
一個像素點有特定的位置和顏色值。每個像素的顏色由RGB組合或灰度值表示。
本小節重點介紹如何表示顏色。
根據位深度,可以將位圖分爲1、4、8、16、24及32位圖像等。這裏的位深度是指用於表示像素點顏色的比特位個數。如果一個像素點由一個比特位表示顏色,那麼其位深度爲1,如果一個像素點由四個比特位表示顏色,則其位深度爲4,依次類推。
- 如果一張圖片的像素點由1個比特位來表示顏色,則這個比特位爲0或1,則可以表示21種顏色,即黑與白,則這張照片是純粹的黑白照片。
-
如果一張圖片的像素點由8個比特位來表示顏色,則這八個比特位共可以表示28種顏色,即256。這種圖像通常(有例外,下面會講)稱爲灰度圖,因爲這256種顏色是黑白灰(這裏的灰是指244種不同程度的灰)。如下爲灰度圖:
-
如果一張圖片的像素點由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時,並不一定代表這張圖片爲灰度圖,如下:
這張圖片的位深度爲8,但其並不是灰度圖,我們稱之爲僞彩色圖。
下面這張圖是位深度爲24的真彩色圖,可以作爲對比:
可以看出真彩色圖的質量明顯高於僞彩色圖。
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,如果沒有可自行選擇插件管理進行安裝,最終界面如下:
這是以十六進制的形式顯示圖像信息,一個十六進制數字佔4個比特位,故一行表示十六個字節。
在對BMP文件進行具體解析前,我們還要先了解一下數據的存放順序:
在BMP文件中,如果一個數據需要用幾個字節來表示的話,那麼該數據的存放字節順序爲“低地址存放低位數據,高地址存放高位數據”。如數據0x1756在內存中的存儲順序爲:
這種存儲方式稱爲小端方式(little endian) , 與之相反的是大端方式(big endian)。
3.1 位圖文件頭
下圖紅框中爲位圖文件頭:
-
頭兩個字節(0,1)表示位圖文件的類型,即
0x4d42
表示類型,爲BMP,這與DUMP中的頭兩個字節表示的符號BM一致
。 -
後面的四個字節(2,3,4,5)表示位圖文件的大小,即
0x0000c436
表示位圖文件大小,換算爲十進制爲50230,我們打開grey8.bmp的屬性,發現其大小確爲50230字節:位圖文件的大小,即bfSize的值需要如何計算呢?
我們打開grey8.bmp的屬性,點擊詳細信息,可以看到grey8.bmp的大小爲256x192像素,位深度爲8,又因爲其位深度爲8,故其顏色表項有256項(每項佔據4字節),故grey8.bmp的大小爲:
-
再後面的兩個字節(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 位圖信息頭
-
頭四個字節(第一行的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,其實就是計算像素陣列的大小,計算方式爲:
在grey8.bmp圖像中
-
後四個字節(第三行的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灰度圖的顏色表項修改爲隨機值,將原灰度圖變爲僞彩色圖。
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 結果
利用我們的程序,生成了以下圖片,還挺漂亮的!
3.7 完整代碼
四、擴展實驗
- 編寫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格式”的介紹