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 };
}
}