首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# GDI+ 影像處理實戰:負片、灰階、亮度調整完整教學與原始碼

     編輯:關於C語言

前言:像素級操作的基石

數位影像本質上是像素矩陣,每個像素包含 R、G、B 三個通道(24bpp 格式)。GDI+ 的 Bitmap 雖不直接暴露像素陣列,但透過 LockBits 取得 BitmapData,配合 unsafe 指標操作,即可實現高效能的逐像素處理。本文以三個經典濾鏡——負片反轉、灰階轉換、亮度調整 為例,完整展示從 UI 到演算法的實作細節。

專案架構

  • 主表單:繼承 Form,啟用 AutoScroll 支援大圖瀏覽,覆寫 OnPaint 繪製 Bitmap。
  • 選單:開啟/儲存影像(支援 BMP/JPG)、三大濾鏡命令。
  • Filters 靜態類別:封裝三個 public static bool 方法,回傳是否成功,供 UI 決定是否 Invalidate() 重繪。

核心技術點

1. LockBits / UnlockBits 模式

BitmapData bmData = bmp.LockBits(
    new Rectangle(0, 0, bmp.Width, bmp.Height),
    ImageLockMode.ReadWrite,
    PixelFormat.Format24bppRgb); // 強制 24bpp,簡化指標運算

int stride = bmData.Stride;          // 一行跨度(含記憶體對齊補齊)
IntPtr scan0 = bmData.Scan0;         // 像素資料起始指標

// unsafe 區塊操作...

bmp.UnlockBits(bmData); // 必須釋放

2. 指標運算與 stride 處理

  • stride 可能大於 width * 3(4 位元組對齊),每行結束需跳過 nOffset = stride - width * 3 位元組。
  • 雙層迴圈:外層 y < height,內層 x < width * 3(逐字節)或 x < width(逐像素,每次 +3 字節)。

三大濾鏡演算法詳解

1. 負片反轉

公式:newVal = 255 - oldVal,對 R、G、B 三通道獨立運算。

public static bool Invert(Bitmap bmp)
{
    var bmData = bmp.LockBits(...);
    int stride = bmData.Stride;
    IntPtr scan0 = bmData.Scan0;
    int nOffset = stride - bmp.Width * 3;

    unsafe
    {
        byte* p = (byte*)scan0;
        for (int y = 0; y < bmp.Height; y++)
        {
            for (int x = 0; x < bmp.Width * 3; x++)
            {
                p[0] = (byte)(255 - p[0]);
                p++;
            }
            p += nOffset;
        }
    }
    bmp.UnlockBits(bmData);
    return true;
}

2. 灰階轉換

依人眼對 RGB 敏感度差異,採用加權平均:
Gray = 0.299 * R + 0.587 * G + 0.114 * B
注意:GDI+ 記憶體順序為 BGR(非 RGB),讀取時須對應 p[0]=B, p[1]=G, p[2]=R。


public static bool Gray(Bitmap bmp)
{
    var bmData = bmp.LockBits(...);
    int stride = bmData.Stride;
    IntPtr scan0 = bmData.Scan0;
    int nOffset = stride - bmp.Width * 3;

    unsafe
    {
        byte* p = (byte*)scan0;
        for (int y = 0; y < bmp.Height; y++)
        {
            for (int x = 0; x < bmp.Width; x++)
            {
                byte blue  = p[0];
                byte green = p[1];
                byte red   = p[2];
                byte gray = (byte)(0.299 * red + 0.587 * green + 0.114 * blue);
                p[0] = p[1] = p[2] = gray;
                p += 3;
            }
            p += nOffset;
        }
    }
    bmp.UnlockBits(bmData);
    return true;
}

3. 亮度調整

參數 nBrightness 範圍 [-255, 255],對每通道加上增量並 Clamp(0, 255)。


public static bool Brightness(Bitmap bmp, int nBrightness)
{
    if (nBrightness < -255 || nBrightness > 255) return false;

    var bmData = bmp.LockBits(...);
    int stride = bmData.Stride;
    IntPtr scan0 = bmData.Scan0;
    int nOffset = stride - bmp.Width * 3;

    unsafe
    {
        byte* p = (byte*)scan0;
        for (int y = 0; y < bmp.Height; y++)
        {
            for (int x = 0; x < bmp.Width * 3; x++)
            {
                int val = p[0] + nBrightness;
                if (val < 0) val = 0;
                if (val > 255) val = 255;
                p[0] = (byte)val;
                p++;
            }
            p += nOffset;
        }
    }
    bmp.UnlockBits(bmData);
    return true;
}

UI 整合範例


// 開啟影像
private void Open_Click(object sender, EventArgs e)
{
    using (var dlg = new OpenFileDialog { Filter = "影像檔|*.bmp;*.jpg;*.png" })
    if (dlg.ShowDialog() == DialogResult.OK)
    {
        _bmp = new Bitmap(dlg.FileName);
        AutoScrollMinSize = _bmp.Size;
        Invalidate();
    }
}

// 儲存影像
private void Save_Click(object sender, EventArgs e)
{
    using (var dlg = new SaveFileDialog { Filter = "BMP|*.bmp|JPG|*.jpg" })
    if (dlg.ShowDialog() == DialogResult.OK)
        _bmp.Save(dlg.FileName);
}

// 負片
private void Invert_Click(object sender, EventArgs e)
{
    if (Filters.Invert(_bmp)) Invalidate();
}

// 灰階
private void Gray_Click(object sender, EventArgs e)
{
    if (Filters.Gray(_bmp)) Invalidate();
}

// 亮度(彈出輸入對話框)
private void Brightness_Click(object sender, EventArgs e)
{
    using (var dlg = new ParameterForm()) // 自訂簡單 Form,含 NumericUpDown
    if (dlg.ShowDialog() == DialogResult.OK)
        if (Filters.Brightness(_bmp, dlg.Value)) Invalidate();
}

專案設定提醒

  • 專案屬性 → Build → 勾選 Allow unsafe code(允許不安全程式碼)。
  • 引用 System.Drawing.Common(.NET Core / .NET 5+ 需額外安裝 NuGet 套件)。
  • 大圖處理時建議加上 GC.TryStartNoGCRegion 或分塊處理,避免大物件堆積(LOH)壓力。

延伸練習建議

  1. 新增對比度調整:newVal = (oldVal - 128) * contrast + 128。
  2. 實作雙邊濾波或高斯模糊(需鄰域像素,引入暫存緩衝區)。
  3. 支援 PixelFormat.Format32bppArgb,處理 Alpha 通道。
  4. 將 Filters 改為泛型 IImageFilter 介面,配合策略模式動態載入濾鏡外掛。

掌握 LockBits + unsafe 指標操作,即可在 C# 中實現媲美 C++ 的影像處理效能,為電腦視覺、文件掃描、醫療影像等應用打下堅實基礎。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved