Back
PQCHub

BMP 檔解析 (II)

實戰 Node.js 讀寫 BMP 像素,掌握 Padding、二維陣列存取,並實現灰階與二值化演算法。


1 回顧 BMP 檔頭

在上一章,我們已經學會如何讀取 BMP 檔頭資訊,取得:

  • 影像寬度 (width)
  • 影像高度 (height)
  • 像素資料起始位置 (pixelOffset)
  • 每像素位元數 (bitsPerPixel)

這些資訊是後續讀取像素的基礎。


2 計算每列像素與 Padding

24-bit BMP 每個像素佔 3 bytes(BGR),且每一列的資料大小必須是 4 的倍數,多餘的位元組稱為 padding

import fs from "fs";

const buffer = fs.readFileSync("test.bmp");
const width = buffer.readInt32LE(18);
const height = buffer.readInt32LE(22);
const bitsPerPixel = buffer.readUInt16LE(28);
const pixelOffset = buffer.readUInt32LE(10);

// 每列的總位元組數 (含 padding)
const rowSize = Math.floor((bitsPerPixel * width + 31) / 32) * 4;

// 計算每列 padding
const padding = rowSize - width * (bitsPerPixel / 8);

console.log("每列總位元組數:", rowSize);
console.log("每列 Padding:", padding);

3 讀取像素資料到二維陣列

BMP 像素資料 從下到上存放,我們將它讀入二維陣列 pixels[y][x]

// 建立二維陣列
const pixels = [];

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("影像像素資料已讀取完成");

4 操作單一像素

我們可以直接操作二維陣列中的單一像素。例如:將左上角像素顏色改為紅色:

// BMP 是從下到上排列,y=0 對應最下方列
pixels[height - 1][0] = { r: 255, g: 0, b: 0 };

console.log("左上角像素已改為紅色:", pixels[height - 1][0]);

或讀取單一像素的 RGB 值:

const px = pixels[height - 1][0];
console.log("左上角像素 RGB:", px.r, px.g, px.b);

5 影像倒置 (上下翻轉)

如果想讓影像由 上到下排列,可簡單倒置陣列:

const flippedPixels = pixels.slice().reverse();
console.log("影像已上下翻轉");

6 建立灰階強度陣列

可以先準備灰階強度,方便後續影像處理:

const grayPixels = flippedPixels.map(row =>
  row.map(({ r, g, b }) => Math.floor((r + g + b) / 3))
);

console.log("灰階強度陣列已建立 (僅含強度值)");

7 將二維像素陣列寫回 BMP 檔案

修改完像素後,我們可以將二維陣列寫回 BMP 檔案:

import fs from "fs";

const outputBuffer = Buffer.from(buffer); // 複製原始檔案作為模板

for (let y = 0; y < height; y++) {
  const row = pixels[y];
  const rowStart = pixelOffset + y * rowSize;

  for (let x = 0; x < width; x++) {
    const pixelStart = rowStart + x * 3;
    const { r, g, b } = row[x];
    outputBuffer[pixelStart] = b;
    outputBuffer[pixelStart + 1] = g;
    outputBuffer[pixelStart + 2] = r;
  }
}

fs.writeFileSync("output.bmp", outputBuffer);
console.log("修改後的 BMP 已寫入 output.bmp");

8 改變整張影像的顏色

例如,把影像轉成 紅色調

for (let y = 0; y < height; y++) {
  for (let x = 0; x < width; x++) {
    pixels[y][x].r = 255;       // 最大紅色
    pixels[y][x].g = 0;         // 綠色清零
    pixels[y][x].b = 0;         // 藍色清零
  }
}

之後再用前面的方法寫回檔案,即可得到紅色影像。


9 簡單灰階演算法

將 RGB 轉成灰階:

for (let y = 0; y < height; y++) {
  for (let x = 0; x < width; x++) {
    const { r, g, b } = pixels[y][x];
    const gray = Math.floor((r + g + b) / 3);
    pixels[y][x] = { r: gray, g: gray, b: gray };
  }
}

寫回檔案後,影像會變成灰階。


10 簡單二值化演算法

將灰階影像轉成黑白二值:

const threshold = 128; // 閾值
for (let y = 0; y < height; y++) {
  for (let x = 0; x < width; x++) {
    const { r } = pixels[y][x]; // 灰階 r=g=b
    const value = r >= threshold ? 255 : 0;
    pixels[y][x] = { r: value, g: value, b: value };
  }
}

寫回檔案後,影像變成黑白二值化效果。


11 修改單一像素的應用

可以針對某個區域或像素做處理,例如把左上 10×10 區域改成藍色:

for (let y = height - 10; y < height; y++) {  // BMP 從下到上
  for (let x = 0; x < 10; x++) {
    pixels[y][x] = { r: 0, g: 0, b: 255 };
  }
}
SNPQ © 2025