深度解析

BMP 格式解析:Windows 最單純的影像格式

koboshiCo-founder
·5 分鐘閱讀
BMP 格式解析:Windows 最單純的影像格式
概述

BMP 從 1990 年的 Windows 3.0 一路走到今天。它用極小的標頭儲存原始像素,沒有壓縮、沒有 Alpha 通道。這篇文章從 14 位元組的 BITMAPFILEHEADER 開始,介紹它的結構、優缺點,以及它至今仍出現在嵌入式系統和醫學影像等領域的原因。

用十六進位編輯器打開 BMP 檔案,前十四個位元組會長這樣:

42 4D 46 00 00 00 00 00 00 00 36 00 00 00

這就是完整的 BITMAPFILEHEADER42 4D 是 ASCII 簽名「BM」。接下來四個位元組記錄檔案大小——46 00 00 00 以小端序表示 70。再往後四個位元組是保留欄位,永遠為零。最後四個位元組是像素資料的偏移量——36 00 00 00 等於 54,所以像素陣列從第 54 位元組開始。

沒有壓縮標記、沒有色彩描述檔、沒有 Alpha 通道。只有簽名、大小與偏移量。BMP 就像它的名字一樣,是一張直接的位元映射。

BMP 是什麼

BMP 的全名是 Bitmap,更正式的說法是 Device Independent Bitmap(DIB,裝置獨立點陣圖)。Microsoft 在 1990 年隨 Windows 3.0 推出這個格式,用來儲存點陣影像,而不綁定特定顯示裝置。在 DIB 出現之前,Windows 使用的是 Device Dependent Bitmap(DDB,裝置相依點陣圖),其像素格式取決於當時安裝的顯示卡。在一台機器上儲存的 DDB,到了另一台機器可能根本無法讀取。

BMP 解決了這個問題,方法是在檔案裡存放足夠的詮釋資料,讓任何解碼器都能重建影像:寬度、高度、位元深度、調色盤、壓縮類型,以及原始像素陣列。作業系統讀取標頭、配置緩衝區,再把像素複製進記憶體。對於 24-bit 未壓縮影像,像素資料通常只是由下而上逐行寫入的 RGB 三元組,並對齊到四個位元組的邊界。

這種簡單性讓 BMP 在超過十年的時間裡都是 Windows 的預設影像格式。Paintbrush、Windows 桌布引擎、早期掃描器,以及無數內部工具都原生支援 BMP。

檔案結構

BMP 檔案分為四個部分,依序排列:

BITMAPFILEHEADER   (14 bytes)
BITMAPINFOHEADER   (40 bytes for the classic version)
Color Table        (optional, for indexed color modes)
Pixel Data         (the actual image)

BITMAPFILEHEADER(14 位元組)

Bytes 0-1:   Signature    "BM" (0x42 0x4D)
Bytes 2-5:   FileSize     (uint32, little-endian)
Bytes 6-9:   Reserved     (always 0)
Bytes 10-13: Offset       (uint32, offset to pixel data from file start)

簽名不一定永遠是「BM」。Windows 也支援「BA」、「CI」、「CP」、「IC」與「PT」,用於游標與圖示變體,但實務上你打開的每個 BMP 檔案都會以「BM」開頭。

BITMAPINFOHEADER(40 位元組,BITMAPINFOHEADER 類型)

Bytes 0-3:   HeaderSize        (40)
Bytes 4-7:   Width             (int32, pixels)
Bytes 8-11:  Height            (int32, pixels; negative means top-down)
Bytes 12-13: Planes            (1)
Bytes 14-15: BitCount          (1, 4, 8, 16, 24, or 32)
Bytes 16-19: Compression       (0 = none, 1 = RLE8, 2 = RLE4, 3 = bitfields)
Bytes 20-23: ImageSize         (0 if uncompressed)
Bytes 24-27: XpixelsPerMeter   (physical resolution)
Bytes 28-31: YpixelsPerMeter   (physical resolution)
Bytes 32-35: ColorsUsed        (0 means 2^BitCount)
Bytes 36-39: ColorsImportant   (0 means all)

後續 Windows 版本加入了更大的標頭——52、56、108 與 124 位元組——以支援 Alpha 遮罩、色彩空間與 ICC 描述檔。但你最可能遇到的仍然是 40 位元組這個版本。

高度同時也是方向

如果高度欄位為正數,影像以由下而上的方式儲存;檔案中的第 0 列對應影像的最底列。如果高度欄位為負數,則以由上而下儲存。這是 BMP 的一個低調特性——大多數其他格式預設都是由上而下。

調色盤

對於 1、4 或 8 bit 的深度,BMP 使用索引調色盤。每個調色盤項目為四個位元組:藍、綠、紅,以及一個幾乎總是被忽略的保留 Alpha 位元組。一張 256 色的 BMP 在標頭後緊接著一個 1,024 位元組的色彩表。後續的像素資料不是顏色,而是這張表的索引。

像素資料填補

每條掃描線必須是四個位元組的倍數。一張寬度為 5 像素的 24-bit 影像每列需要 15 位元組,會被補齊到 16。這些額外的填補位元組是浪費的空間,也是 BMP 效率不彰的另一個小原因。

以下是一個完整的 2 x 2 24-bit 未壓縮 BMP 範例:

Offset 0x00: 42 4D 46 00 00 00 00 00 00 00 36 00 00 00  -- BITMAPFILEHEADER
Offset 0x0E: 28 00 00 00 02 00 00 00 02 00 00 00 01 00 18 00  -- BITMAPINFOHEADER
Offset 0x22: 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00
Offset 0x32: 00 00 00 00 00 00 00 00
Offset 0x36: FF 00 00 00 FF 00 00 00 FF  -- bottom row: red, green, blue (with padding)
Offset 0x3F: 00 FF FF 00 FF FF 00 00 00  -- top row: cyan, magenta, yellow (with padding)

1990 年的格式生態

BMP 並不是憑空出現的。到了 1990 年,已經有多種影像格式在互相競爭:

格式壓縮方式色彩深度透明度專利風險典型用途
GIFLZW最多 256 色1-bit有(LZW)網頁圖形
JPEGDCT + Huffman24-bit多半無照片
TIFF多種可選最高 48-bit視情況印刷、掃描
PCXRLE最高 24-bitDOS 遊戲、剪貼圖
PICTRLE最高 32-bitClassic Mac OS
BMP無或 RLE最高 32-bitWindows 桌布、圖示

GIF 當時已是網頁預設格式,但其 LZW 專利讓商業用途充滿風險。JPEG 是為照片設計的,會不可逆地丟棄資料。TIFF 功能強大但過度設計——要寫出一個通用 TIFF 讀取器本身就是一個專案。PCX 則隨著 DOS 一起式微。

BMP 的賣點是簡單。它不比別人小、不比別人快,也不比別人強大,但它是 Windows 無需額外函式庫就能理解的格式。

BMP 為什麼好用

BMP 在當年的環境中確實有幾項實質優勢:

零解碼複雜度。 一張 24-bit 未壓縮 BMP 只要讀取固定大小的標頭,再把像素直接複製到 framebuffer 就能渲染。沒有 Huffman 表、沒有 DEFLATE 狀態機、沒有漸進式掃描。這讓它非常適合 CPU 有限的嵌入式系統。

無專利牽絆。 不同於 GIF 的 LZW,BMP 的壓縮要不是沒有,就是 RLE,兩者都不受專利限制。這對開源工具與想避開 Unisys GIF 授權的商業軟體來說很重要。

直接整合 Windows。 Win32 API 圍繞 DIB section 提供了 LoadBitmapBitBltStretchBlt。開發者可以把 BMP 載入記憶體,再用幾行 C 語言將它貼到螢幕上。

可預測的記憶體佈局。 因為未壓縮 BMP 像素直接對應螢幕記憶體,1990 年代的遊戲引擎與繪圖工具常把 BMP 當作材質交換格式。你可以記憶體對應檔案,把像素偏移量當作原始緩衝區使用。

BMP 的弱點

BMP 的優點同時也是弱點,而且這個格式沒能跟上產業的發展。

沒有有效的壓縮。 未壓縮 BMP 非常巨大。一張 1920 x 1080 的 24-bit 影像是 5.93 MB。同一張影像以 JPEG 品質 90 儲存約為 300–500 KB。當網路速度與儲存成本成為限制,BMP 就變得難以接受。

RLE 壓縮太弱。 BMP 支援 RLE8 與 RLE4 遊程編碼,但 RLE 只對大面積均勻顏色的影像有效。照片與漸層壓縮效果很差。實務上 RLE 編碼的 BMP 相當罕見。

沒有真正的透明度。 標準 BMP 沒有 Alpha 通道。32-bit BMP 變體雖然包含一個 Alpha 位元組,但許多工具會忽略它或將其視為保留欄位。對於網頁圖形與遊戲精靈圖來說,這讓 BMP 與 PNG 相比毫無優勢。

沒有動畫、沒有中繼資料、沒有色彩管理。 BMP 只儲存像素,僅此而已。沒有 EXIF、原始標頭沒有 ICC profile 支援、沒有動畫幀。當工作流程變得複雜,BMP 始終停留在原始狀態。

由下而上儲存。 預設的由下而上列順序會讓解碼器混淆,並浪費週期進行轉換。這是一個毫無益處卻增加摩擦的小細節。

取代 BMP 的格式

BMP 的限制直接塑造了後續格式。

PNG(1996) 解決了透明度與壓縮問題。它提供無損壓縮、8-bit Alpha,規格也比 TIFF 簡單。PNG 幾乎在網頁與跨平台應用中全面取代了 BMP。

JPEG(1992) 解決了照片問題。它基於 DCT 的有損壓縮讓照片比未壓縮 BMP 小 10–20 倍。對於任何連續色調影像,JPEG 都成為預設選擇。

WebP(2010) 在同個容器中同時提供有損、無損模式,以及動畫與 Alpha。一張無損 WebP 通常比 PNG 小 20–30%,而 PNG 本身又遠勝 BMP。

HEIC/HEIF(2013) 把 HEVC 壓縮帶入靜態影像。它在檔案大小上比 JPEG 更小、品質更好,支援 Alpha 與深度圖,現在是 Apple 裝置的預設格式。當 iPhone 儲存照片時,通常就是 HEIC。

這些格式之所以存在,都是因為 BMP 對某個特定用途來說已經不夠好。

BMP 還在哪些地方使用

BMP 並沒有消失,只是被推進那些「簡單性比效率更重要」的利基領域。

Windows 內部。 Windows 開機畫面、舊版對話框與部分系統資源仍使用 BMP。Win32 GDI API 預期 DIB 資料,而重寫這些程式碼路徑對 Microsoft 來說並不值得。

嵌入式系統與微控制器。 Arduino 或 STM32 上的小尺寸 LCD 沒有足夠的 RAM 或 CPU 解碼 PNG。一張 16-bit 未壓縮 BMP 可以直接複製到 framebuffer,所需程式碼極少。

醫學與科學影像。 部分舊版 DICOM 檢視器與實驗室設備會匯出 BMP,因為每個系統都能開啟它。格式的簡單性降低了互通性風險,這在受管制環境中很重要。

逆向工程與教育。 BMP 仍然是教授影像檔案結構的優秀格式之一。標頭很小、像素佈局明顯,一個下午就能做出可用的解碼器。

遊戲開發遺產。 舊版遊戲引擎與材質工具把 BMP 當作中間格式。部分模組社群仍然使用 BMP 資產,因為工具鏈圍繞它建立。

透過簽名偵測 BMP

不要信任 .bmp 副檔名。讀取前兩個位元組。

TypeScript

async function isBmp(file: File): Promise<boolean> {
  const buffer = await file.slice(0, 2).arrayBuffer()
  const bytes = new Uint8Array(buffer)
  return bytes.length === 2 && bytes[0] === 0x42 && bytes[1] === 0x4d
}

Python

def is_bmp(path: str) -> bool:
    with open(path, "rb") as f:
        return f.read(2) == b"BM"

Go

func isBmp(path string) bool {
	f, err := os.Open(path)
	if err != nil {
		return false
	}
	defer f.Close()

	buf := make([]byte, 2)
	if _, err := f.Read(buf); err != nil {
		return false
	}
	return bytes.Equal(buf, []byte("BM"))
}

PHP

function isBmp(string $path): bool {
    $header = file_get_contents($path, false, null, 0, 2);
    return $header === "BM";
}

ImageMagick CLI

magick identify -verbose image.bmp | grep "Format:"
# Format: BMP (Microsoft Windows bitmap image)

或更簡單:

file image.bmp
# image.bmp: PC bitmap, Windows 3.x format, 1920 x 1080 x 24

BMP 的未來

BMP 是一個已經定型的格式。核心規格自 Windows 95 時代以來就沒有實質改變。這既是限制也是保證:1995 年寫入的 BMP 今天仍然能正確開啟。

這個格式不會增加新功能。它不會獲得更好的壓縮、完整的 Alpha 處理或動畫支援。沒有人投資 BMP,因為沒有人需要更好的 BMP。PNG、WebP、AVIF 與 HEIC 已經在所有重視效率的場景中勝出。

但 BMP 會在已經存在的地方繼續被使用:Windows 內部、嵌入式顯示器、舊版醫療設備與教室。它不光鮮、效率差,卻很難被移除,因為太多系統知道如何讀取它。

如果你手邊有需要用在現代裝置上的舊版 BMP 檔案,務實的做法就是轉換。照片轉成 JPEG 或 HEIC 可以節省空間。需要透明度的圖形則應選擇 PNG 或 WebP。而當你需要銜接 Apple 生態系時,把 HEIC 轉成普遍可讀的格式是第一步。

我們的 HEIC to JPG 轉換器完全在瀏覽器中執行——檔案不會離開你的裝置。若要無損輸出並支援透明度,請使用 HEIC to PNG。若要針對網頁最佳化檔案大小,HEIC to WebP 能給你更小的檔案與廣泛的瀏覽器支援。

BMP 給業界上了一課:一種格式不必最好才能無所不在。它只要簡單到每個人都願意實作,並且普及到沒人敢移除,就夠了。

更多推薦閱讀