在 WinForms / WPF 應用中處理圖像,System.Drawing.Bitmap 是核心類別。本文涵蓋圖片讀寫、高效像素存取 (LockBits/unsafe),並實作灰階轉換、均值/中值濾波、直方圖均衡等常用演算法。
// 讀取
private Bitmap _bmp;
private void BtnOpen_Click(object sender, EventArgs e)
{
using (var ofd = new OpenFileDialog
{
Filter = "圖片檔|*.bmp;*.jpg;*.jpeg;*.png|所有檔案|*.*"
})
{
if (ofd.ShowDialog() == DialogResult.OK)
{
_bmp = new Bitmap(ofd.FileName);
picBox.Image = _bmp;
}
}
}
// 儲存
private void BtnSave_Click(object sender, EventArgs e)
{
if (_bmp == null) return;
using (var sfd = new SaveFileDialog { Filter = "JPEG|*.jpg|BMP|*.bmp|PNG|*.png" })
{
if (sfd.ShowDialog() == DialogResult.OK)
_bmp.Save(sfd.FileName);
}
}
GetPixel/SetPixel 極慢,實務上使用 Bitmap.LockBits 鎖定記憶體,配合指標直接操作位元組陣列。
public class GrayBitmapData
{
public byte[,] Data; // 灰階像素矩陣 [Height, Width]
public int Width, Height;
public GrayBitmapData(Bitmap bmp)
{
Width = bmp.Width; Height = bmp.Height;
Data = new byte[Height, Width];
var bmpData = bmp.LockBits(
new Rectangle(0, 0, Width, Height),
ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
unsafe
{
byte* ptr = (byte*)bmpData.Scan0;
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
// BGR 順序,權重:R=0.299, G=0.587, B=0.114
int b = *ptr++; int g = *ptr++; int r = *ptr++;
Data[y, x] = (byte)(0.114 * b + 0.587 * g + 0.299 * r);
}
ptr += bmpData.Stride - Width * 3; // 跳過行填充
}
}
bmp.UnlockBits(bmpData);
}
// 轉回 Bitmap 顯示/儲存
public Bitmap ToBitmap()
{
var bmp = new Bitmap(Width, Height, PixelFormat.Format24bppRgb);
var bmpData = bmp.LockBits(
new Rectangle(0, 0, Width, Height),
ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
unsafe
{
byte* ptr = (byte*)bmpData.Scan0;
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
byte v = Data[y, x];
*ptr++ = v; *ptr++ = v; *ptr++ = v; // 灰階轉 BGR
}
ptr += bmpData.Stride - Width * 3;
}
}
bmp.UnlockBits(bmpData);
return bmp;
}
}
所有演算法直接操作 byte[,] Data,完成後呼叫 ToBitmap() 顯示。
public void AverageFilter(int windowSize)
{
if (windowSize % 2 == 0) return;
int half = windowSize / 2;
var newData = new byte[Height, Width];
for (int y = 0; y < Height; y++)
for (int x = 0; x < Width; x++)
{
int sum = 0, count = 0;
for (int dy = -half; dy <= half; dy++)
for (int dx = -half; dx <= half; dx++)
{
int ny = Math.Clamp(y + dy, 0, Height - 1);
int nx = Math.Clamp(x + dx, 0, Width - 1);
sum += Data[ny, nx];
count++;
}
newData[y, x] = (byte)(sum / count);
}
Data = newData;
}
public void MedianFilter(int windowSize)
{
if (windowSize % 2 == 0) return;
int half = windowSize / 2, kernelSize = windowSize * windowSize;
var newData = new byte[Height, Width];
var window = new int[kernelSize];
for (int y = 0; y < Height; y++)
for (int x = 0; x < Width; x++)
{
int idx = 0;
for (int dy = -half; dy <= half; dy++)
for (int dx = -half; dx <= half; dx++)
{
int ny = Math.Clamp(y + dy, 0, Height - 1);
int nx = Math.Clamp(x + dx, 0, Width - 1);
window[idx++] = Data[ny, nx];
}
Array.Sort(window, 0, kernelSize);
newData[y, x] = (byte)window[kernelSize / 2];
}
Data = newData;
}
public void HistogramEqualization()
{
double[] hist = new double[256];
foreach (byte v in Data) hist[v]++;
double[] cdf = new double[256];
cdf[0] = hist[0];
for (int i = 1; i < 256; i++) cdf[i] = cdf[i-1] + hist[i];
double total = Width * Height;
byte[] map = new byte[256];
for (int i = 0; i < 256; i++)
map[i] = (byte)Math.Round(cdf[i] / total * 255);
for (int y = 0; y < Height; y++)
for (int x = 0; x < Width; x++)
Data[y, x] = map[Data[y, x]];
}
private void BtnGray_Click(object sender, EventArgs e)
{
if (_bmp == null) return;
var gray = new GrayBitmapData(_bmp);
picBox.Image = gray.ToBitmap();
}
private void BtnMedian_Click(object sender, EventArgs e)
{
if (_bmp == null) return;
var gray = new GrayBitmapData(_bmp);
gray.MedianFilter(3); // 3x3 視窗
picBox.Image = gray.ToBitmap();
}
bmpData.Stride 計算下一行起始位址。PixelFormat.Format24bppRgb 記憶體排列為 BGR,非 RGB。Parallel.For 平行化外層迴圈。System.Drawing.Common 替代方案 (如 ImageSharp, SkiaSharp) 以支援跨平台。掌握 LockBits + unsafe 指標操作,是 C# 高效影像處理的基石。結合經典濾波演算法,即可構建功能完整的圖像編輯工具。