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):可選,如使用索引來表示圖像,調色板就是索引與其對應的顏色的映射表

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

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

 

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

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

  我們使用的圖像顯示如下:

                 

 

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

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

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

     

                                  

這種存儲方式稱為小端方式(little endian) , 與之相反的是大端方式(big endian)。對兩者的使用情況有興趣的可以深究一下,其中還是有學問的。

2.  以下所有分析均以字節為序號單位進行。

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

   

 

一、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個字節。

二、位圖信息頭
同樣地,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,與前面得到的結論一致。

 

三、調色板
下面的數據就是調色板了。前面也已經提過,調色板其實是一張映射表,標識顏色索引號與其代表的顏色的對應關系。它在文件中的布局就像一個二維數組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個字節一致!

四、位圖數據

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

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

 

        

也即我們見到的第一個像素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;

五、拾遺

至此,我們通過分析一個具體的位圖文件例子詳細地剖析了位圖文件的組成。需要注意的是:我們講的主要是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、6、5位分別就是r、g、b分量值。取出分量值后把r、g、b值分別乘以8、4、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、1、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的大權,於是……唉,這就是歷史


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM