在影像處理、掃描儀驅動、醫療影像等場景,常需將原始灰階像素資料(byte[],每像素 1 byte,0-255)轉為標準 Bitmap 物件。本文提供完整實作,處理 BMP 檔頭、調色盤、資料列對齊(DWORD padding)、上下翻轉等細節,直接可用於生產環境。
CreateBitmappublic static Bitmap CreateBitmap(byte[] originalImageData, int originalWidth, int originalHeight)
{
// 1. 建立 8bpp 空白 Bitmap
var resultBitmap = new Bitmap(originalWidth, originalHeight, PixelFormat.Format8bppIndexed);
// 2. 存入 MemoryStream 以取得 BMP 標頭結構
var ms = new MemoryStream();
resultBitmap.Save(ms, ImageFormat.Bmp);
ms.Flush();
// 3. 計算每列補齊位元組數(DWORD 對齊)
int stride = ((originalWidth * 8 + 31) / 32) * 4; // 位元組/列
int padBytes = stride - originalWidth; // 每列補 0 數
int bitmapDataSize = stride * originalHeight; // 總像素資料大小
// 4. 讀取檔頭中資料區偏移量(位移 10,長度 4)
int dataOffset = ReadInt32(ms, 10);
// 5. 重寫調色盤(位移 54 到 dataOffset)
for (int i = 54, color = 0; i < dataOffset; i += 4, color++)
{
ms.Position = i;
ms.Write(new byte[4] { (byte)color, (byte)color, (byte)color, 0 }, 0, 4);
}
// 6. 準備翻轉後的像素資料
byte[] destData = new byte[bitmapDataSize];
for (int srcRow = originalHeight - 1, dstRow = 0; srcRow >= 0; srcRow--, dstRow++)
{
int srcOffset = srcRow * originalWidth;
int dstOffset = dstRow * stride;
Buffer.BlockCopy(originalImageData, srcOffset, destData, dstOffset, originalWidth);
// 補齊區域保持 0(new byte[] 預設為 0)
}
// 7. 寫入像素資料
ms.Position = dataOffset;
ms.Write(destData, 0, bitmapDataSize);
ms.Flush();
// 8. 從記憶體流重建 Bitmap
return new Bitmap(ms);
}
// 輔助:從 MemoryStream 讀取 Int32 (Little Endian)
public static int ReadInt32(MemoryStream ms, int position)
{
ms.Position = position;
byte[] buf = new byte[4];
ms.Read(buf, 0, 4);
return BitConverter.ToInt32(buf, 0);
}
// 輔助:寫入 Int32
public static void WriteInt32(MemoryStream ms, int position, int value)
{
ms.Position = position;
ms.Write(BitConverter.GetBytes(value), 0, 4);
}
// 模擬 100x100 灰階漸層資料
byte[] raw = new byte[100 * 100];
for (int y = 0; y < 100; y++)
for (int x = 0; x < 100; x++)
raw[y * 100 + x] = (byte)(x * 255 / 100);
Bitmap bmp = CreateBitmap(raw, 100, 100);
bmp.Save("gray_gradient.bmp");
| 項目 | 說明 |
|---|---|
PixelFormat.Format8bppIndexed | 指定 8 位元索引色彩模式(256 色) |
stride 計算 | ((width * bitsPerPixel + 31) / 32) * 4 為標準 BMP 列對齊公式 |
| 垂直翻轉 | BMP 底部為第 0 列,陣列索引 0 為影像頂部,故需倒序複製 |
| 調色盤 | 8bpp 預設調色盤為 32bpp 彩色,必須改寫為灰階漸層 |
| 記憶體效率 | 全程使用 MemoryStream + Buffer.BlockCopy,避免頻繁物件配置 |
若不需相容舊版 BMP 格式,可直接使用 Bitmap.LockBits 寫入像素資料,省去檔頭解析:
var bmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
// 設定調色盤
var pal = bmp.Palette;
for (int i = 0; i < 256; i++) pal.Entries[i] = Color.FromArgb(i, i, i);
bmp.Palette = pal;
var bd = bmp.LockBits(new Rectangle(0,0,width,height), ImageLockMode.WriteOnly, bmp.PixelFormat);
for (int y = 0; y < height; y++)
{
IntPtr rowPtr = bd.Scan0 + y * bd.Stride;
Marshal.Copy(raw, y * width, rowPtr, width);
}
bmp.UnlockBits(bd);
此法更簡潔、效能更高,建議新專案優先採用。