BMP 檔解析 (I)
深入理解 BMP 檔案結構,學會用 Node.js 讀取檔頭,解析影像基本資訊與像素結構。
1 BMP 格式簡介
在學習數位影像處理之前,必須先了解「影像檔案」是如何儲存的。 其中 BMP (Bitmap Image File) 是最常見、結構最單純的影像格式,適合教學。
1.1 什麼是 BMP?
位元圖 (bitmap):將圖片拆解為一格格的像素 (pixel),每個像素用數值來表示顏色。
BMP 是 Windows 系統常用的圖片格式之一,優點是:
- 結構簡單
- 沒有壓縮(大多數情況)
- 容易直接存取像素資料
1.2 BMP 檔案的組成
一個標準 BMP 檔案分為兩部分:
檔頭 (Header)
- 描述檔案大小、影像寬高、色彩深度等資訊
像素資料 (Pixel Data)
- 依序存放每個像素的顏色,通常為 BGR 排列 (藍、綠、紅)
2 BMP 檔頭結構
最常見的 BMP 為 24-bit BMP,其檔頭長度為 54 bytes,分為兩塊:
- BITMAPFILEHEADER (14 bytes)
- BITMAPINFOHEADER (40 bytes)
這 54 個位元組,提供我們解析圖片所需的全部基本資訊。
2.1 BITMAPFILEHEADER (14 bytes)
位元組位置 | 長度 | 名稱 | 說明 |
---|---|---|---|
0–1 | 2 bytes | Signature | 檔案標誌,固定為 "BM" |
2.2 BITMAPINFOHEADER (40 bytes)
位元組位置 | 長度 | 名稱 | 說明 |
---|---|---|---|
14–17 | 4 bytes | Header size | 固定為 40 |
18–21 | 4 bytes | Width | 影像寬度 (像素數) |
22–25 | 4 bytes | Height | 影像高度 (像素數) |
26–27 | 2 bytes | Planes | 固定為 1 |
28–29 | 2 bytes | Bits per pixel | 每像素位元數,常見為 24 |
30–33 | 4 bytes | Compression | 壓縮方式,0 表示無壓縮 |
34–37 | 4 bytes | Image size | 像素資料大小 |
38–41 | 4 bytes | X pixels per meter | 水平解析度 |
42–45 | 4 bytes | Y pixels per meter | 垂直解析度 |
46–49 | 4 bytes | Colors used | 調色盤顏色數 |
50–53 | 4 bytes | Important colors | 重要顏色數 |
2.3 Bit 與 Byte
2.3.1 Bit (位元)
電腦中最小的資料單位
只有兩種可能的狀態:
0
(關、低電位)1
(開、高電位)
2.3.2 Byte (位元組)
1 Byte = 8 Bits
一個 byte 可以表示 256 種不同的數值
- 無號整數範圍:
0 ~ 255
- 有號整數範圍:
-128 ~ 127
- 無號整數範圍:
3 Node.js 讀取 BMP 檔頭
我們可以用 Node.js 的 fs
模組 讀取 BMP 檔案,並解析出前 54 bytes 的檔頭。
3.1 建立專案並放置檔案
建立資料夾
mkdir bmp-parser
切換目錄
cd bmp-parser
在本目錄初始化專案
npm init -y
修改 package.json,加入: (ES6 模組)
{
"type": "module"
}
將一張 test.bmp
放到資料夾中(建議使用小尺寸、24-bit BMP 測試)。
3.2 程式碼範例
import fs from "node:fs";
// 讀取 BMP 檔案 (請確保同資料夾內有 test.bmp)
const buffer = fs.readFileSync("test.bmp");
// 解析 BITMAPFILEHEADER
let signature = buffer.toString("ascii", 0, 2); // 0-1
let fileSize = buffer.readUInt32LE(2); // 2-5
let pixelOffset = buffer.readUInt32LE(10); // 10-13
// 解析 BITMAPINFOHEADER
let headerSize = buffer.readUInt32LE(14); // 14-17
let width = buffer.readInt32LE(18); // 18-21
let height = buffer.readInt32LE(22); // 22-25
let planes = buffer.readUInt16LE(26); // 26-27
let bitsPerPixel = buffer.readUInt16LE(28); // 28-29
// 印出資訊
console.log("=== BMP 檔頭資訊 ===");
console.log("Signature:", signature);
console.log("File Size:", fileSize, "bytes");
console.log("Pixel Data Offset:", pixelOffset, "bytes");
console.log("Header Size:", headerSize);
console.log("Width:", width, "px");
console.log("Height:", height, "px");
console.log("Planes:", planes);
console.log("Bits Per Pixel:", bitsPerPixel);
4 範例輸出
假設讀取一張 100×100 的 24-bit BMP,會看到:
=== BMP 檔頭資訊 ===
Signature: BM
File Size: 30054 bytes
Pixel Data Offset: 54 bytes
Header Size: 40
Width: 100 px
Height: 100 px
Planes: 1
Bits Per Pixel: 24
5 像素資料結構 (Pixel Data)
在 BMP 檔案中,檔頭之後就是 像素資料 (Pixel Data)。 這裡存放的是一個個像素的顏色值。
在 24-bit BMP 中,每個像素由 3 個 byte 組成:
B (藍色) | G (綠色) | R (紅色)
👉 注意:這是 BGR 排列,不是常見的 RGB。
6 一列像素的大小與 Padding
在 24-bit BMP 中:
每像素大小 = bitsPerPixel / 8 = 24 / 8 = 3 bytes
如果影像寬度為 width
,一列像素需要:
rowSize = width × 3
⚠️ 但 BMP 規定每列必須補齊到 4 的倍數,稱為 Row Padding。 正確公式:
rowSize = Math.ceil((bitsPerPixel × width) / 32) × 4
例:
width = 3
→ 每列像素需3×3=9 bytes
,必須補到 12 bytes。width = 4
→4×3=12 bytes
,剛好是 4 的倍數,不需補齊。
7 BMP 像素資料解析程式碼
我們用 ES6 模組來讀取像素資料,並存放在二維陣列 [y][x] = {r,g,b}
方便後續演算法處理。
import fs from "node:fs";
const buffer = fs.readFileSync("test.bmp");
// 檔頭資訊
const pixelOffset = buffer.readUInt32LE(10);
const width = buffer.readInt32LE(18);
const height = buffer.readInt32LE(22);
const bitsPerPixel = buffer.readUInt16LE(28);
// 計算每列大小 (含 padding)
const rowSize = Math.ceil((bitsPerPixel * width) / 32) * 4;
// 建立二維陣列存像素
const pixels = [];
// BMP 的像素資料由「下到上」儲存
for (let y = 0; y < height; y++) {
const row = [];
const rowStart = pixelOffset + y * rowSize;
for (let x = 0; x < width; x++) {
const pixelStart = rowStart + x * 3;
const b = buffer[pixelStart];
const g = buffer[pixelStart + 1];
const r = buffer[pixelStart + 2];
row.push({ r, g, b });
}
pixels.push(row);
}
console.log("=== 影像像素資料 (部分) ===");
console.log("左上角像素:", pixels[height - 1][0]); // 注意上下顛倒
8 像素存取注意事項
- 垂直方向:BMP 的像素是「由下到上」排列,因此第 0 列其實是圖片的最底部。
- Padding:要記得每列結尾可能會有補齊的空 bytes,不要誤讀。
- 顏色順序:BGR,而不是 RGB。