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

C# 實現任意多邊形圖片裁剪:GraphicsPath 與像素級遮罩算法

     編輯:Java 相關知識

需求背景

在 WinForms 應用中,用戶通過鼠標繪製任意多邊形區域,需將圖片按該區域裁剪並保存,區域外透明化。本文提供基於 GraphicsPath 像素級遍歷的完整實現方案。

核心思路

  1. 用戶點擊確定多邊形頂點,構建 GraphicsPath。
  2. 獲取路徑邊界矩形 RectangleF bounds = path.GetBounds() 作為裁剪輸出圖尺寸。
  3. 創建目標位圖 outputBitmap,遍歷邊界矩形內每個像素:
  4. 調用 path.IsVisible(x, y) 判斷像素是否在多邊形內。
  5. 內部像素複製原圖對應像素;外部像素設置為透明 (ARGB: 0, 255, 255, 255)。

完整代碼實現

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

public class PolygonCropper
{
    /// 
    /// 任意多邊形裁剪圖片
    /// 
    /// 源圖片
    /// 裁剪路徑 (多邊形)
    /// 輸出裁剪後圖片 (透明背景)
    /// 處理後的源圖副本 (裁剪區域已置透明)
    public static Bitmap CropByPolygon(Bitmap source, GraphicsPath path, out Bitmap output)
    {
        // 1. 獲取路徑邊界
        RectangleF bounds = path.GetBounds();
        int left = (int)Math.Floor(bounds.Left);
        int top = (int)Math.Floor(bounds.Top);
        int width = (int)Math.Ceiling(bounds.Width);
        int height = (int)Math.Ceiling(bounds.Height);

        // 2. 複製源圖避免修改原圖
        Bitmap workingCopy = (Bitmap)source.Clone();
        
        // 3. 創建輸出位圖 (支援透明的 Format32bppArgb)
        output = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

        // 4. 像素級遍歷
        for (int x = left; x < left + width; x++)
        {
            for (int y = top; y < top + height; y++)
            {
                // 邊界檢查 (防止路徑超出圖片範圍)
                if (x >= 0 && x < source.Width && y >= 0 && y < source.Height)
                {
                    if (path.IsVisible(x, y))
                    {
                        // 內部: 複製像素到輸出圖 (相對坐標)
                        Color pixel = workingCopy.GetPixel(x, y);
                        output.SetPixel(x - left, y - top, pixel);
                        
                        // 可選: 在工作副本中將該區域置透明 (演示用)
                        workingCopy.SetPixel(x, y, Color.FromArgb(0, pixel));
                    }
                    else
                    {
                        // 外部: 設置透明像素
                        output.SetPixel(x - left, y - top, Color.FromArgb(0, 255, 255, 255));
                    }
                }
                else
                {
                    // 超出源圖範圍填充透明
                    output.SetPixel(x - left, y - top, Color.FromArgb(0, 255, 255, 255));
                }
            }
        }

        // 5. 釋放資源
        workingCopy.Dispose();
        return workingCopy; // 返回已挖空的副本 (視業務需求決定是否使用)
    }
}

// --- WinForms 調用示例 ---
public partial class MainForm : Form
{
    private void btnCrop_Click(object sender, EventArgs e)
    {
        // 1. 定義多邊形頂點 (實際來自用戶鼠標點擊收集)
        Point[] points = {
            new Point(424, 244), new Point(240, 440), new Point(340, 552),
            new Point(550, 526), new Point(478, 366), new Point(348, 660),
            new Point(424, 244) // 首尾閉合
        };
        
        GraphicsPath path = new GraphicsPath();
        path.AddLines(points);

        // 2. 選擇圖片
        using (OpenFileDialog ofd = new OpenFileDialog())
        {
            ofd.Filter = "圖片文件|*.bmp;*.jpg;*.png;*.jpeg";
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                using (Bitmap src = new Bitmap(ofd.FileName))
                {
                    // 3. 執行裁剪
                    PolygonCropper.CropByPolygon(src, path, out Bitmap result);
                    
                    // 4. 保存結果
                    result.Save(@"C:\Temp\cropped_result.png", 
                        System.Drawing.Imaging.ImageFormat.Png); // 必須存 PNG 保留透明通道
                    
                    MessageBox.Show("裁剪完成!");
                    result.Dispose();
                }
            }
        }
    }
}

性能優化關鍵點

⚡ GetPixel/SetPixel 極慢!生產環境必須用 LockBits

// 高性能版本骨架
BitmapData srcData = src.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
BitmapData dstData = dst.LockBits(new Rectangle(0,0,width,height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);

unsafe {
    byte* srcPtr = (byte*)srcData.Scan0;
    byte* dstPtr = (byte*)dstData.Scan0;
    int stride = srcData.Stride;
    
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int srcX = left + x, srcY = top + y;
            bool inside = (srcX >= 0 && srcX < src.Width && srcY >= 0 && srcY < src.Height) 
                          && path.IsVisible(srcX, srcY);
            
            int dstIdx = y * dstData.Stride + x * 4;
            if (inside) {
                int srcIdx = srcY * stride + srcX * 4;
                dstPtr[dstIdx] = srcPtr[srcIdx];       // B
                dstPtr[dstIdx+1] = srcPtr[srcIdx+1];   // G
                dstPtr[dstIdx+2] = srcPtr[srcIdx+2];   // R
                dstPtr[dstIdx+3] = srcPtr[srcIdx+3];   // A
            } else {
                dstPtr[dstIdx+3] = 0; // Alpha = 0 透明
            }
        }
    }
}
src.UnlockBits(srcData);
dst.UnlockBits(dstData);

進階擴展建議

  • 貝塞爾曲線支援:path.AddBezier() 替代直線,實現平滑邊緣裁剪。
  • 羽化邊緣:邊緣像素按距離計算 Alpha 漸變,避免鋸齒。
  • GPU 加速:WPF/UWP 可用 WriteableBitmap + Shader 或 Direct2D 實現毫秒級裁剪。
  • 撤銷/重做:保存操作歷史路徑而非位圖,節省記憶體。

總結

基於 GraphicsPath.IsVisible 的像素級裁剪邏輯清晰、幾何精確,適合中小圖片、離線處理場景。大圖/實時預覽場景務必採用 LockBits 非安全代碼或 GPU 方案提升百倍性能。輸出格式必須選 PNG 以保留 Alpha 透明通道,JPEG 不支援透明度。

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