淺談圖像格式 .bmp

 

 
 

位圖(Bitmap)格式其實並不能說是一種很常見的格式(從我們日常的使用頻率上來講,遠不如 .jpg .png .gif 等),因爲其數據沒有經過壓縮,或最多隻採用行程長度編碼(RLE,run-length encoding)來進行輕度的無損數據壓縮。以至於,LaTeX 並不能像插入 .jpg 甚至於矢量圖那樣便捷地插入 BMP 圖片,知乎的專欄封面上傳也不支持 BMP。

但是,.bmp 仍然發揮着很重要的角色,而且也確實有拿來聊一聊,進而學習一些更深入的知識的意義。正是因爲它沒有進行數據壓縮,其內部存儲的色彩信息(灰度圖,RGB 或 ARGB)直接以二進制的形式暴露在外,也十分方便藉助計算機軟件進行簡單或深入的分析。那麼,今天,我將帶領大家從二進制文件的角度,探索 .bmp 格式的奧祕。


文件頭

位圖格式的文件頭長度可變,而且其中參數繁多。但是我們日常生活中遇到的 .bmp 格式圖片的文件頭長度絕大多數都是 54 字節,其中包括 14 字節的 Bitmap 文件頭以及 40 字節的 DIB (Device Independent Bitmap) 數據頭,或稱位圖信息數據頭(BItmap Information Header)。其他形式的在此暫不討論。

以下兩張圖表大家不必認真觀看,因爲在後續章節中我們將會針對實際例子,藉助十六進制數據和表格進行具體分析。

位圖文件頭 Bitmap File Header (14 bytes)

 

這是一張漢化版的英文維基百科相關詞條(BMP file format - Wikipedia)中的表格。

 

位圖信息數據頭 DIB Header (54 bytes)

 

對於壓縮方式,雖然 Bitmap 格式提供簡單的壓縮功能,但是絕大多數情況下,並沒有採用任何壓縮手段。

 

原始位圖數據 Raw Bitmap Data

接下來纔是重頭戲——原始位圖數據。拿最常見的 24BPP RGB (24 比特每像素,紅綠藍三通道) 位圖來說,每種顏色需要 8 比特,或者說 1 字節,來存儲。在二進制文件中,通常情況下,RGB 按照藍、綠、紅的順序依次表示圖片中的像素點,而 RGBA 則按照藍、綠、紅、透明的順序(從左下開始,橫向逐行向上掃描)。特殊時候,也會出現順序與上述情況不同的特例,這時色彩順序會寫在 DIB Header 的 Bit Fields 中,以不同色彩通道的 Mask 的形式進行規定。由於 BI_BITFIELDS 也是一種壓縮方式,而通常 BMP 不採用任何壓縮方式,所以絕大多數時候,我們都是按照前面說的順序進行排序。

關於 Bit Fields,在此貼一張維基百科的圖片,便於理解。

這裏與別處不同,採用的是 big endian(事實上,整個 BMP 文件中,只有此處使用 big endian。big endian 指的是先出現的數字在高位)。因此,如果 Mask 呈現如下表中的數據:

則由於 Blue 對應數字最大,因此順序爲藍、綠、紅、透明。

數據按照像素行進行包裝,便於讀取。但是這並不是全部,因爲其中還可能會有補零(zero-padding)。這涉及到計算機的數據結構對齊(data structure alignment)的問題。

主流的 CPU 每次從內存中讀取並處理數據塊(chunk),且通常爲 32 比特(4 字節)。因此,爲了提升讀取效率,位圖每行的數據(字節)都需要是 4 的倍數。不可避免地,有些時候每行的結尾就會出現補零(其實補其他任意數字也是可以的,但常見都是補 0)。

對於每行補零的個數,是有計算方法的。公式如下(知乎的 LaTeX 公式不能很好地支持中文,因此無法進行翻譯,抱歉):

 

即,每行的字節數等於:每像素比特數乘以圖片寬度31 的和除以 32,並向下取整,最後乘以 4

得到了每行的字節數,進而就能夠得到原始位圖數據,或者說存儲圖片的所有像素的色彩信息的數據,的大小了:

 

即,原始位圖數據大小等於:每行的字節數乘以圖像高度(也就是總行數)

再加上之前說的文件頭的數據大小(通常爲 54 字節),就能夠得到文件大小了。

 

實踐:文件大小

說完了理論,我們來看一看實際效果吧。

這裏有一張 10x10 的 BMP:

在 Windows 畫圖的保存選項中,選擇 24BPP RGB(同時我們也可以看到, .bmp 和 .dib 是等效的):

 

 

從上到下顏色依次爲(每種顏色寬度 10 像素,高度 2 像素):

 

  1. 藍(0,0,255)
  2. 青(0,255,255)
  3. 綠(0,255,0)
  4. 黃(0,255,255)
  5. 紅(255,0,0)

首先,我們來計算一下每行的字節數:

 

所以,原始位圖數據的大小爲:

 

前面已經說過,文件頭的大小通常爲 54 字節。因此,整個文件的大小應該爲:

 

右鍵查看文件屬性:

 

看來我們的計算是完全正確的,的確是 374 字節。

 

 

實踐:頭文件內容

我們通過查看二進制文件來進一步分析上面這張圖。(藉助軟件 Sublime Text 進行簡易的二進制查詢)

爲了防止在尋找對應數字的時候出現迷惑,我們先來大致理一理基礎的進制轉換。圖中,每個數字都是十六進制,也就是 4 比特。每字節有 8 比特,也就是 2 個十六進制數字。每四個數字分爲一組,佔 2 字節。因此每行共 16 字節

假如我們想要找第 0x22 (十進制爲 34)個字節,方法如下:

首先,找到第三行。因爲每行 16 字節,所以在十六進制下的 22,直接看左邊第一位數字對應的就是第三行(行號從 0 開始)。然後,開始尋找第三個字節(同樣地,列號也從 0 開始)。因此答案是 40。

 

再試一次。假如我們想要找第 0xBC 個字節,方法如下:

 

首先,找到第 12 (B=11)行,然後找第 13 個字節。 不難找到吧?

相信看到這裏,大家都已經具備閱讀十六進制的能力了。我們現在步入正題。

首先,位圖頭文件有 14 字節,因此我們先看這一部分(爲方便觀察,標註了位置數字,並兩個數字爲一組,爲一字節)。

還記得它們分別是什麼意思嗎?我把圖片再貼一遍。

位置 00,尺寸 2,內容 42 4D,也就是 ASCII 表的“BM”兩個字母。

 

位置 02,尺寸 4,內容 76 01 00 00,表示文件大小。因爲是 little endian(靠前的數字對應低數位),所以實際十六進制數字爲 00000176。對應十進制數字爲:

 

這與我們先前的計算以及實際答案完全相同。

 

位置 06,尺寸 2,內容 00 00,預留字段,通常爲 0。

 

位置 08,尺寸 2,內容 00 00,同上。

 

位置 0A,尺寸 4,內容 36 00 00 00,表示位圖數據的開始位置。同樣爲 little endian,對應十進制數字爲 54(這也是文件頭的總長度)。

 

然後,再來看 DIB 數據頭的 40 字節內容。

 

位置 0E,尺寸 4,內容 28 00 00 00,意思是 DIB header 大小。對應十進制數字爲 40 (little endian)。

 

位置 12,尺寸 4,內容 0A 00 00 00,代表圖像寬度。對應十進制 10。

位置 16,尺寸 4,內容 0A 00 00 00,代表圖像高度,10。

位置 1A,尺寸 2,內容 01 00,表示色彩平面的數量,必須爲 1。

位置 1C,尺寸 2,內容 18 00,表示每像素用多少比特來表示。對應十進制 24。

位置 1E,尺寸 4,內容 00 00 00 00,表示採用何種壓縮方式。此處爲不壓縮。

位置 22,尺寸 4,內容 40 01 00 00,表示原始位圖數據的大小。對應十進制 320。還記得 little endian 怎麼計算嗎?

 

剩下的內容我們可以不看,因爲幾乎用不到,它們也“如我們所願”,都是 0。



實踐:原始位圖數據

分析完文件頭,剩下的內容就是我們期待已久的,以數組形式排列的所有像素的色彩信息。

大家還記得前面提到過的數據結構對齊嗎?如果每像素 24 比特(3 字節),那麼總共 10x10 大小的圖片,原始位圖數據的大小不應該是 300 字節嗎?但是實際卻是 320 字節。由此可見,每行都有額外的補零。

之前我們已經計算過了,每行字節數爲 32 字節。這是一個除不盡 3 的數字。

 

所以,每行有 10 個像素信息(30 字節),外加 2 字節的補零。也就是說,每 32 字節(在下圖中正好爲兩行)中都會有 2 字節的補零。實際是不是這樣呢?我們從 0x36 (54)位,也就是原始位圖數據的起始位置開始看,每兩行都會有用綠色方框括住的 2 字節補零。

然後,從圖片的最下面開始看,最先出現的是紅色,也就是(255,0,0)。前面已經說過,位圖文件中,每像素三種顏色的數據的出現順序依次爲藍,綠,紅。因此紅色對應的數據應當爲 00 00 FF。不難看出,前 20 個像素點(請注意跳過補零)內容都是 00 00 FF。

下一個顏色爲黃色,即 00 FF FF,再之後是綠色,即 00 FF 00……

 

是不是看起來清楚多了?

 

 

最後還有一點想說的,就是並不是所有位圖文件都需要補零。我們再來看另外一個例子。這裏有一張 1920x1080 尺寸的 BMP(也就是本篇文章的封面圖)。經計算:

 

而 5760 是 3 的倍數,因此對於這張圖片,就不需要補零了。也就是說,有 1920x1080 個像素點,原始位圖數據的大小就正好爲

字節。再加上 54 字節的頭文件,文件的大小應該爲 6,220,854 字節。實際上呢?

 

 

豈不美哉?一丁點字節都不浪費。

 





今天我們就先講到這裏。下次有機會的話,我將爲大家展示如何藉助 BMP 文件的格式做一些簡單有趣的圖像處理。

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