Back
PQCHub

BMP 檔解析 (I)

深入理解 BMP 檔案結構,學會用 Node.js 讀取檔頭,解析影像基本資訊與像素結構。


1 BMP 格式簡介

在學習數位影像處理之前,必須先了解「影像檔案」是如何儲存的。 其中 BMP (Bitmap Image File) 是最常見、結構最單純的影像格式,適合教學。

1.1 什麼是 BMP?

  • 位元圖 (bitmap):將圖片拆解為一格格的像素 (pixel),每個像素用數值來表示顏色。

  • BMP 是 Windows 系統常用的圖片格式之一,優點是:

    • 結構簡單
    • 沒有壓縮(大多數情況)
    • 容易直接存取像素資料

1.2 BMP 檔案的組成

一個標準 BMP 檔案分為兩部分:

  1. 檔頭 (Header)

    • 描述檔案大小、影像寬高、色彩深度等資訊
  2. 像素資料 (Pixel Data)

    • 依序存放每個像素的顏色,通常為 BGR 排列 (藍、綠、紅)

2 BMP 檔頭結構

最常見的 BMP 為 24-bit BMP,其檔頭長度為 54 bytes,分為兩塊:

  1. BITMAPFILEHEADER (14 bytes)
  2. 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 = 44×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。

SNPQ © 2025