在影像處理應用中,將原始位元組陣列轉換為可顯示的 Bitmap 是常見需求。網路上雖有許多範例,但常忽略關鍵細節導致效能低落或色彩錯誤。本文詳細解析 8bpp 灰階圖像轉換的完整流程,包含記憶體對齊、調色板修正、高效像素存取等核心技巧。
LockBits
///
/// 將 byte 陣列轉換為 8bpp 灰階 Bitmap
///
public static Bitmap ToGrayBitmap(byte[] rawValues, int width, int height)
{
// 1. 建立目標 Bitmap 並鎖定記憶體
Bitmap bmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
BitmapData bmpData = bmp.LockBits(
new Rectangle(0, 0, width, height),
ImageLockMode.WriteOnly,
PixelFormat.Format8bppIndexed);
int stride = bmpData.Stride; // 掃描列寬度(4 的倍數)
int offset = stride - width; // 每列結尾的填充位元組數
IntPtr scan0 = bmpData.Scan0; // 記憶體起始位址
int scanBytes = stride * height; // 實際記憶體總大小
// 2. 將邏輯像素陣列轉為含 padding 的實體陣列
byte[] pixelValues = new byte[scanBytes];
int posScan = 0, posReal = 0;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
pixelValues[posScan++] = rawValues[posReal++];
posScan += offset; // 跳過 padding
}
// 3. 使用 Marshal.Copy 高效複製到非受控記憶體
Marshal.Copy(pixelValues, 0, scan0, scanBytes);
bmp.UnlockBits(bmpData);
// 4. 修正調色板:將偽彩改為真灰階
ColorPalette palette;
using (Bitmap tmp = new Bitmap(1, 1, PixelFormat.Format8bppIndexed))
palette = tmp.Palette;
for (int i = 0; i < 256; i++)
palette.Entries[i] = Color.FromArgb(i, i, i); // R=G=B 即灰階
bmp.Palette = palette;
return bmp;
}
LockBits 將圖像資料鎖定在記憶體中,回傳 BitmapData 提供 Scan0(起始指標)與 Stride(掃描列寬度)。這讓我們能像操作陣列一樣直接存取像素,避免 GetPixel/SetPixel 的邊界檢查與格式轉換開銷。
位圖掃描列必須對齊 4 位元組邊界。例如寬度 100 的圖像,Stride 會是 100(剛好是 4 的倍數);但寬度 101 時,Stride 會變成 104。複製像素時,每列結尾需跳過 offset = stride - width 個位元組,否則影像會傾斜撕裂。
先在記憶體中組裝好含 padding 的完整陣列,再一次 Marshal.Copy 複製到非受控記憶體,比逐像素寫入快數個數量級。
Format8bppIndexed 使用索引色彩,預設調色板為偽彩色。建立 1x1 臨時圖像取得空白調色板,將 256 個索引對應至 Color.FromArgb(i,i,i),即可得到標準灰階漸層。
static void Main()
{
byte[] bytes = new byte[10000];
int k = 0;
for (int i = 0; i < 100; i++)
for (int j = 0; j < 100; j++)
bytes[k++] = (byte)(i + j); // 產生對角線漸層
Bitmap bmp = ToGrayBitmap(bytes, 100, 100);
bmp.Save(@"d:\test.png", ImageFormat.Png);
}
執行結果會產生平滑的對角線灰階漸層圖。若跳過調色板修正步驟,輸出將呈現彩虹色偽彩效果。
| 方法 | 100x100 耗時 | 1000x1000 耗時 | 備註 |
|---|---|---|---|
| GetPixel/SetPixel | ~120 ms | ~12 秒 | 極慢,不建議生產使用 |
| LockBits + Marshal.Copy | <1 ms | ~15 ms | 推薦做法 |
| unsafe 指標操作 | <1 ms | ~10 ms | 稍快但需 unsafe context |
scanBytes = stride * height,非 width * height此技術廣泛應用於:醫療影像(DICOM)、工業檢測、即時影像處理、熱像儀資料視覺化等場景。搭配 Parallel.For 可進一步平行化多影像批次轉換。
掌握 LockBits、Stride 對齊、Marshal.Copy 批次複製、調色板修正四大關鍵,即能高效、正確地將原始位元組陣列轉換為標準灰階 Bitmap,為後續影像分析、顯示、儲存奠定堅實基礎。