BMP格式詳解

BMP格式詳解程序員

BMP文件格式詳解(BMP file format)

BMP文件格式,又稱爲Bitmap(位圖)或是DIB(Device-Independent Device,設備無關位圖),是Windows系統中普遍使用的圖像文件格式。因爲它能夠不做任何變換地保存圖像像素域的數據,所以成爲咱們取得RAW數據的重要來源。Windows的圖形用戶界面(graphical user interfaces)也在它的內建圖像子系統GDI中對BMP格式提供了支持。算法

下面以Notepad++爲分析工具,結合Windows的位圖數據結構對BMP文件格式進行一個深度的剖析。數組

BMP文件的數據按照從文件頭開始的前後順序分爲四個部分:數據結構

Ø         bmp文件頭(bmp file header):提供文件的格式、大小等信息工具

Ø         位圖信息頭(bitmap information):提供圖像數據的尺寸、位平面數、壓縮方式、顏色索引等信息佈局

Ø         調色板(color palette):可選,如使用索引來表示圖像,調色板就是索引與其對應的顏色的映射表post

Ø         位圖數據(bitmap data):就是圖像數據啦^_^優化

下面結合Windows結構體的定義,經過一個表來分析這四個部分。ui

 

咱們通常見到的圖像以24位圖像爲主,即R、G、B三種顏色各用8個bit來表示,這樣的圖像咱們稱爲真彩色,這種狀況下是不須要調色板的,也就是所位圖信息頭後面緊跟的就是位圖數據了。所以,咱們經常見到有這樣一種說法:位圖文件從文件頭開始偏移54個字節就是位圖數據了,這其實說的是24或32位圖的狀況。這也就解釋了咱們按照這種程序寫出來的程序爲何對某些位圖文件沒用了。編碼

  下面針對一幅特定的圖像進行分析,來看看在位圖文件中這四個數據段的排布以及組成。

  咱們使用的圖像顯示以下:

                 

 

   這是一幅16位的位圖文件,所以它是含有調色板的。

   在拉出圖像數據進行分析以前,咱們首先進行幾個約定:

   1. 在BMP文件中,若是一個數據須要用幾個字節來表示的話,那麼該數據的存放字節順序爲「低地址村存放低位數據,高地址存放高位數據」。如數據0x1756在內存中的存儲順序爲:

     

                                  

這種存儲方式稱爲小端方式(little endian) , 與之相反的是大端方式(big endian)。對二者的使用狀況有興趣的能夠深究一下,其中仍是有學問的。

2.  如下全部分析均以字節爲序號單位進行。

   下面咱們對從文件中拉出來的數據進行剖析:

   

 

1、bmp文件頭
Windows爲bmp文件頭定義了以下結構體:

  

 

複製代碼
typedef struct tagBITMAPFILEHEADER 
{  
UINT16 bfType;    
DWORD bfSize; 
UINT16 bfReserved1; 
UINT16 bfReserved2; 
DWORD bfOffBits;
} BITMAPFILEHEADER; 
複製代碼

 

其中:

   

 

 對照文件數據咱們看到:

 

1-2  :424dh = 'BM',表示這是Windows支持的位圖格式。有不少聲稱開頭兩個字節必須爲'BM'纔是位圖文件,從上表來看應爲開頭兩個字節必須爲'BM'纔是Windows位圖文件。

3-5  :00010436h = 66614 B = 65.05 kB,經過查詢文件屬性發現一致。

6-9  :這是兩個保留段,爲0。

A-D:00000436h = 1078。即從文件頭到位圖數據需偏移1078字節。咱們稍後將驗證這個數據。

共有14個字節。

2、位圖信息頭
一樣地,Windows爲位圖信息頭定義了以下結構體:

  

代碼  

 

對照數據文件: 

 

0E-11:00000028h = 40,這就是說我這個位圖信息頭的大小爲40個字節。前面咱們已經說過位圖信息頭通常有40個字節,既然是這樣,爲何這裏還要給一個字段來講明呢?這裏涉及到一些歷史,其實位圖信息頭本來有不少大小的版本的。咱們看一下下表:

 

                

    出於兼容性的考慮,大多數應用使用了舊版的位圖信息頭來保存文件。而 OS/2 已通過時了,所以如今最經常使用的格式就僅有V3 header了。所以,咱們在前面說位圖信息頭的大小爲40字節。

12-15:00000100h = 256,圖像寬爲255像素,與文件屬性一致。

16-19:00000100h = 256,圖像高爲255像素,與文件屬性一致。這是一個正數,說明圖像數據是從圖像左下角到右上角排列的。

1A-1B:0001h, 該值總爲1。

1C-1D:0008h = 8, 表示每一個像素佔8個比特,即該圖像共有256種顏色。

1E-21:00000000h,BI_RGB, 說明本圖像不壓縮。

22-25:00000000h,圖像的大小,由於使用BI_RGB,因此設置爲0。

26-29:00000000h,水平分辨率,缺省。

2A-2D:00000000h,垂直分辨率,缺省。

2E-31:00000100h = 256,說明本位圖實際使用的顏色索引數爲256,與1C-ID獲得的結論一致。

32-35:00000100h = 256,說明本位圖重要的顏色索引數爲256,與前面獲得的結論一致。

 

3、調色板
下面的數據就是調色板了。前面也已經提過,調色板實際上是一張映射表,標識顏色索引號與其表明的顏色的對應關係。它在文件中的佈局就像一個二維數組palette[N][4],其中N表示總的顏色索引數,每行的四個元素分別表示該索引對應的B、G、R和Alpha的值,每一個份量佔一個字節。如不設透明通道時,Alpha爲0。由於前面知道,本圖有256個顏色索引,所以N = 256。索引號就是所在行的行號,對應的顏色就是所在行的四個元素。這裏截取一些數據來講明:

                           

 

 

索引:(藍,綠,紅,Alpha)

0號:(fe,fa,fd,00)

1號:(fd,f3,fc,00)

2號:(f4,f3,fc,00)

3號:(fc,f2,f4,00)

4號:(f6,f2,f2,00)

                                                           5號:(fb,f9,f6,00) 等等。

 一共有256種顏色,每一個顏色佔用4個字節,就是一共1024個字節,再加上前面的文件信息頭和位圖信息頭的54個字節加起來一共是1078個字節。也就是說在位圖數據出現以前一共有1078個字節,與咱們在文件信息頭獲得的信息:文件頭到文圖數據區的偏移爲1078個字節一致!

4、位圖數據

下面就是位圖數據了,每一個像素佔一個字節,取得這個字節後,以該字節爲索引查詢相應的顏色,並顯示到相應的顯示設備上就能夠了。

注意:因爲位圖信息頭中的圖像高度是正數,因此位圖數據在文件中的排列順序是從左下角到右上角,以行爲主序排列的。

 

        

也即咱們見到的第一個像素60是圖像最左下角的數據,第二我的像素60爲圖像最後一行第二列的數據,…一直到最後一行的最後一列數據,後面緊接的是倒數第二行的第一列的數據,依此類推。

 若是圖像是24位或是32位數據的位圖的話,位圖數據區就不是索引而是實際的像素值了。下面說明一下,此時位圖數據區的每一個像素的RGB顏色陣列排布:

24位RGB按照BGR的順序來存儲每一個像素的各顏色通道的值,一個像素的全部顏色份量值都存完後才存下一個下一個像素,不進行交織存儲。

32位數據按照BGRA的順序存儲,其他與24位位圖的方式同樣。

像素的排布規則與前述一致。

對齊規則

講完了像素的排列規則以及各像素的顏色份量的排列規則,最後咱們談談數據的對齊規則。咱們知道Windows默認的掃描的最小單位是4字節,若是數據對齊知足這個值的話對於數據的獲取速度等都是有很大的增益的。所以,BMP圖像順應了這個要求,要求每行的數據的長度必須是4的倍數,若是不夠須要進行比特填充(以0填充),這樣能夠達到按行的快速存取。這時,位圖數據區的大小就未必是 圖片寬×每像素字節數×圖片高 能表示的了,由於每行可能還須要進行比特填充。

填充後的每行的字節數爲:

      ,其中BPP(Bits Per Pixel)爲每像素的比特數。

在程序中,咱們能夠表示爲:

int iLineByteCnt = (((m_iImageWidth * m_iBitsPerPixel) + 31) >> 5) << 2;

這樣,位圖數據區的大小爲:

  m_iImageDataSize = iLineByteCnt * m_iImageHeight;

咱們在掃描完一行數據後,也可能接下來的數據並非下一行的數據,可能須要跳過一段填充數據:

  skip = 4 - ((m_iImageWidth * m_iBitsPerPixel)>>3) & 3;

5、拾遺

至此,咱們經過分析一個具體的位圖文件例子詳細地剖析了位圖文件的組成。須要注意的是:咱們講的主要是PC機上的位圖文件的構成,對於嵌入式平臺,可能在調色板數據段與PC機的不一樣。如在嵌入式平臺上常見的16位r5g6b5位圖實際上採用的掩模的方式而不是索引的方式來表示圖像。此時,在調色板數據段共有四個部分,每一個部分爲四個字節,實際表示的是彩色版規範。即:

  第一個部分是紅色份量的掩模

  第二個部分是綠色份量的掩模

 第三個部分是藍色份量的掩模

 第四個部分是Alpha份量的掩模(缺省爲0)

典型的調色板規範在文件中的順序爲爲:

  00F8 0000 E007 0000 1F00 0000 0000 0000

其中

    00F8 0000爲FB00h=1111100000000000(二進制),是藍紅份量的掩碼。 
  E007 0000爲 07E0h=0000011111100000(二進制),是綠色份量的掩碼。 
   1F00 0000爲001Fh=0000000000011111(二進制),是藍色份量的掩碼。 
    0000 0000設置爲0。

將掩碼跟像素值進行「與」運算再進行移位操做就能夠獲得各色份量值。看看掩碼,就能夠明白事實上在每一個像素值的兩個字節16位中,按從高到低取五、六、5位分別就是r、g、b份量值。取出份量值後把r、g、b值分別乘以八、四、8就能夠補齊每一個份量爲一個字節,再把這三個字節按BGR組合,放入存儲器,就能夠轉換爲24位標準BMP格式了。

這樣咱們假設在位圖數據區有一個像素的數據在文件中表示爲02 F1。這個數據實際上應爲F102:

 r = (F102 AND F800) >> 8 = F0h = 240

 g= (F102 AND 07E0)>> 3 = 20h = 32 
  b=(F102 AND 001F) << 3 = 10h =16

至此咱們就能夠顯示了。(本文結束)

 

前言

記得本科時候講《計算機體系結構》的老師(很遺憾忘了他姓名)評價過中外教材的差異,他說按照老外的體系結構教材,你就真的可以作出一個CPU來(雖然只能作出很老很老的CPU),但國內的教材就很難教到這個程度。

幾個月前我從零開始寫了一個簡單的bmp解碼庫,如今用一篇文章把其中的關鍵內容記錄下來,但願可以達到讓別人照着文章就能夠開發出任何語言綁定的bmp解碼庫的程度,以便往後我能夠放心地忘卻,由於個人腦子裏老是不能同時記住太多的東西。 

BMP簡介

BMP是一種與硬件設備無關的圖像文件格式,使用很是廣。BMP是Windows環境中交換與圖有關的數據的一種標準,在Windows環境中運行的圖形圖像軟件都支持BMP圖像格式。

相對來說,BMP格式比較簡單,它只包含兩個重要參數:編碼格式(Encoding)和像素位數(bpp, bit-per-pixel)。到目前爲止,BMP格式所支持的全部像素位數與編碼格式的組合以下:

序號

像素位數(bpp)

編碼格式(Encoding)

1

1

bit

2

4

bgr(blue-green-red)

3

4

rle(run-length encode)

4

8

bgr

5

8

rle

6

grayscale

bgr

7

grayscale

rle

8

16

bgr

9

16

bitfields-555

10

16

bitfields-565

11

16

bitfields-customized

12

24

bgr

13

32

bgr

14

32

bitfields-888

15

32

bitfields-customized

其中24bpp稱爲真彩(true-color)圖像,應用最爲普遍。16bpp的bmp圖像擁有存儲空間小,解析速度快,仿真彩效果好等特色,常常出如今遊戲軟件中。grayscale(灰度)圖像實際上是8bpp的一種狀況。 

文件結構

典型的BMP圖像文件由四部分組成:
1:文件頭,它包含BMP圖像文件的類型、內容尺寸和起始偏移量等信息;
2:圖像參數,它包含圖像的寬、高、壓縮方法,以及顏色定義等信息;
3:調色板,可選部分,bpp較小的位圖須要調色板;有些位圖,好比24bpp(真彩色)圖就不須要調色板;
4:位圖數據,這部分的內容因位圖實際像素位數和編碼格式而不一樣,在真彩位圖中直接使用RGB真彩色值;而有調色板的位圖則使用調色板中顏色索引值。 

數據類型

先定義幾個數據類型以便於描述。
byte —— 8 bits
word —— 16 bits
int/uint/dword —— 32 bits 

文件頭

BMP的文件頭共14個字節。

 

字節順序

數據結構

描述

1,2

word

高8位爲字母’B’,低8位爲字母’M’

3,4,5,6

uint

文件尺寸

7,8

word

保留字1

9,10

word

保留字2

11,12,13,14

uint

位圖數據部分相對於文件首的起始偏移量

 

 

數據部分偏移量的存在,說明圖像數據部分並不必定要緊隨圖像參數或調色板以後放置,BMP圖片的製做者其實能夠在調色板以後、數據部分以前填充任何內容,只要正確地設置偏移量便可。 

圖像參數信息

這一個數據塊共40字節或56字節。前40字節的內容以下:

 

字節順序

數據結構

描述

15,16,17,18

uint

當前結構體的大小,一般是40或56

19,20,21,22

int

圖像寬度(像素)

23,24,25,26

int

圖像高度(像素)

27,28

word

這個字的值永遠是1

29,30

word

每像素佔用的位數,即bpp

31,32,33,34

uint

壓縮方式

35,36,37,38

uint

圖像的尺寸(字節數)

39,40,41,42

int

水平分辨率,pixels-per-meter

43,44,45,46

int

垂直分辨率,pixels-per-meter

47,48,49,50

uint

引用色彩數

51,52,53,54

uint

關鍵色彩數

 

 

水平分辨率和垂直分辨率我歷來沒用過。看上去應該是與設備相關的參數。

若是你是一個有優化癖的程序員,你必定會問,圖像的寬和高爲何是int型而不是uint型呢?由於想象中負數寬和高彷佛沒有意義。比較滑稽的是,在BMP格式中,負數的高是有意義的。爲了與高搭配,所以圖像的寬也定義爲int型。負數高的意義咱們將在圖像數據塊中討論。

第31-34字節存儲着一個uint型參數,它表明圖像數據的壓縮方式。該參數的取值範圍是0、一、2或3等等。這些取值的含義分別是:

0 —— RGB方式
1 —— 8bpp的run-length-encoding方式
2 —— 4bpp的run-length-encoding方式
3 —— bit-fields方式

只有壓縮方式選項被置爲bit-fields時,當前結構體的大小爲56字節,不然爲40字節。 

調色板

當bpp小於等於8時,BMP使用調色板記錄色彩信息,調色板中每條數據(即每種色彩值)都是一個uint型數據。當調色板存在時,圖像數據塊中存儲的只是各個像素的色彩在調色板中的索引值,必須經過在調色板中查表,才能獲知各個像素的真實顏色。若引入調色板,則調色板數據塊緊隨在圖像參數數據塊以後。

當bpp == 1時,調色板合法索引值只有0和1。所以調色板中只有2個色彩值,分別表示索引值爲0和1時的色彩信息。

當bpp == 4或bpp == 8時,合法索引值範圍擴大爲[0,15]和[0,255]。但圖像中不必定使用到了所有16種或256種顏色。第47-50字節存儲的uint型數據指出圖像中實際應用的色彩數,也即調色板中的色彩值數目。固然,它不該超出調色板的合法索引值的範圍。

當bpp == 4或bpp == 8時,能夠採用Run-Length-Encoding方式壓縮圖像的存儲空間,即壓縮方式選項的值爲1或2(當選項值爲0時,不壓縮)。這種編碼格式所考慮的狀況是,若4bpp或8bpp位圖的尺寸較大時,因爲色彩總數很是有限,因此圖像中必然會有不少顏色重複的像素。所以BMP圖像格式的設計者決定採起一個簡單的措施來挽回一些被浪費掉的存儲空間。這個簡單的措施就是RLE壓縮方法。 

RLE, Run-Length-Encoding

若是你是一個有優化癖的程序員,當你發現一張8bpp、寬300像素的位圖中有一行像素只有兩種顏色:前100個像素是紅色,後200個像素是藍色,然而你的位圖卻忠實地用100字節來存儲前半行重複的紅色,又用200字節來存儲後半行重複的藍色,那麼你必定會抓狂到大罵BMP格式的設計者是白癡。

爲了不被罵,BMP格式的設計者想出了這樣的辦法:先用一個字節來存儲重複色彩的數量,再用一個字節來存儲這個色彩的值,即用兩個字節表明一段顏色重複的像素。而且,他們給重複色彩的數量起了個名字,叫作Run-Length,多是由於只有在運行時咱們才能知道這段重複色彩的長度。因爲runlength爲0時沒有意義,所以設計者把runlength=0當作每行的終止符。因而,一樣存儲一行300個像素,原先須要300字節,如今只用5個字節就搞定!

字節

1

2

3

4

5

內容

100

red

200

blue

 0 

因爲你是一個有着嚴重優化癖的程序員,因此你對這樣粗製濫造的優化方法並不知足,由於你很快發現,若是一張位圖中沒有連續重複的像素(例如紅藍像素點陣),那麼用剛纔發明的這個辦法存儲300個像素,竟然要用601字節!固然,這種狀況下最好的辦法是不用壓縮算法。但是,若是既有重複像素,又有點陣的狀況呢?好比前150像素是重複的綠色,後150像素是紅藍相間的像素點陣。

爲了知足你變態的優化癖,BMP格式的設計者只好繼續發展這個算法。首先,他們保留了「用一個字節來存儲重複色彩的數量,再用一個字節來存儲這個色彩的值」的設計思路,而後修改了runlength爲0的含義。設計者規定,當遇到runlength==0時,咱們要繼續讀取下一個字節,若該字節值爲n,意味着後面的n個像素將採用「逐字翻譯」的方式來解析,也就是說,這n個像素的前面沒有runlength這個字節。用這種方法壓縮「前150像素是重複的綠色,後150像素是紅藍相間的像素點陣」的300個像素,只須要154個字節。

字節

1

2

3

4

後150個字節

內容

150

green

 0 

150

red,blue,…,red,blue

這個近似完美的結果中有個小問題:設計BMP格式的天才們把runlength==0的含義修改後,咱們就沒有行終結符了。不過天才終歸是天才,他們發現「逐字翻譯」的像素數必須大於2纔有意義(想一想這是爲何?),所以runlength==0以後的那個字節的值爲0、1或2時,目前尚未意義。因而天才們規定,當這個值爲0時,表示行結束符;當這個值爲1時,表示文件結束符;當這個值爲2時,彷佛仍然沒有什麼意義;只有當該值大於等於3時,纔是「逐字翻譯」。完整的壓縮結果是:

字節

1

2

3

4

後150個字節

155

156

內容

150

green

 0 

150

red,blue,…,red,blue

0

0

這就是傳說中的Run-Length-Encoding for 8bpp。4bpp的RLE跟8bpp時沒有本質差異。
 

RGB與Bit-Fields

當圖像中引用的色彩超過256種時,咱們就須要16bpp或更高bpp的位圖。調色板不適合bpp較大的位圖,所以16bpp以上的位圖都不使用調色板。不使用調色板的位圖圖像有兩種編碼格式:RGB和Bit-Fields(下稱BF)。

RGB編碼格式是一種均分的思想,使Red、Green、Blue三個顏色份量所包含的信息容量儘量同樣大。

16bpp-RGB:在每一個像素所佔的16bits中,低5位表示Blue份量;中5爲表示Green份量;高5位表示Red份量;最高1位無心義(後來有些應用程序將其視爲透明度Alpha份量,但這並非標準)。因此從低到高的順序其實是B-G-R,這也是我在BMP簡介的表格裏,把RGB的編碼方式都寫成BGR的緣由。

24bpp-RGB:24bpp的位圖又稱爲真彩位圖,它一般只有這一種編碼格式,在24bits中,低8位表示Blue份量;中8爲表示Green份量;高8位表示Red份量。

32bpp-RGB:在32bits中,低24位的編碼方式與24bpp位圖相同,最高8位用來表示透明度Alpha份量。32bpp的位圖尺寸太大,通常只有在圖像處理的中間過程當中使用。對於須要半透過效果的圖像,更好的選擇是PNG格式。

BF編碼格式與RGB不一樣,它利用位域操做,人爲地肯定RGB三份量所包含的信息容量。在圖像參數信息模塊的介紹中說起,當壓縮方式選項置爲BF時,圖像參數結構體將比平時多出16字節。這16字節其實是4個dword的位域掩碼。按照前後順序,它們分別是R、G、B、A四個份量的位域掩碼。固然若是沒有Alpha份量,則Alpha掩碼沒有實際意義。

位域掩碼的做用是:指出像素色彩值中RGB份量,就像子網掩碼指出子網網段同樣。

16bpp-BF-565:這是BF編碼格式最著名和最廣泛的應用。它的Red、Green和Blue份量的位域掩碼分別是0xF800、0x07E0和0x001F。

咱們平時所可以見到的位圖中使用BF編碼格式的並很少,由於它看上去比較麻煩,而效果也不見得比RGB要好(你能用肉眼分辨出16bpp-RGB和16bpp-BF-565之間的區別嗎?)。

BF編碼格式的重要應用在於遊戲軟件。遊戲軟件一般包含數量龐大的小尺寸圖片。若是一張圖片中幾乎沒有Blue份量,那麼使用16bpp-RGB格式顯然會浪費掉B份量所佔的5位。此時若採用16bpp-BF-772格式,只給B份量2位,那麼R與G份量都擁有7位的容量,幾乎接近真彩圖像。所以存儲空間小、仿真彩能力強的特色使BF編碼格式仍然有着獨特的用武之地。

32bpp-BF-xxx:我一直不明白爲何會存在32bpp的位圖。若是說32bpp-RGB格式的存在是由於在圖像處理過程當中存儲起來比較高效(不用壓縮),那麼32bpp-BF又是爲何存在呢?

圖像數據塊

圖像數據塊從文件頭中起始偏移量字段所指出的位置開始,其中存放着位圖圖像的數據,數據格式由圖像參數信息塊中的壓縮方式選項的取值決定。操做圖像數據塊時,有一些注意事項:

當壓縮方式爲RGB時,圖像數據塊以「行」爲單位雙字對齊。例如一張寬度爲5像素的8bpp的圖像,其實際使用的存儲空間是每行8個字節。又如一幅4bpp、寬度爲5像素的位圖圖像,其實際使用的存儲空間是每行4個字節。

當bpp < 8時,每一個字節將存放多個像素的色彩索引,則先出現的像素存放在高位中。例如某4bpp圖像第一行像素的順序是red, green, blue, yellow, …則圖像數據塊中第一個字節的高4位值爲red,低4位值爲green;第二字節高4位值爲blue,低4位值爲yellow。1bpp時的狀況以此類推。

還記得前面提到,圖像參數裏,高度有多是負值嗎?這看上去很逗,但事實是,你見過的大多數位圖,其圖像參數裏的高度都是負值。BMP格式設計者規定,當高度爲正值時,圖像數據塊中記錄的第一行像素數據是圖像的最後一行;而數據塊中最後一行數據纔是實際圖像的第一行,也就是說,數據塊中的行記錄與實際圖像反序。而當高度爲負值時,數據塊中的行記錄與實際圖像纔是同序的。

若是你以爲這太奇怪了,我很理解。不過咱們必須懷着無比崇敬之情接受這個看似滑稽的規定。這是由於在那個年代裏,那些設計BMP格式的天才首先都是數學家,讓天才的數學家們習慣左上角爲原點,而且y軸方向向下的二維直角座標系的格局顯然是很困難的,既然他們手上又有設計BMP的大權,因而……唉,這就是歷史

相關文章
相關標籤/搜索