在 WinForms 應用中,用戶通過鼠標繪製任意多邊形區域,需將圖片按該區域裁剪並保存,區域外透明化。本文提供基於 GraphicsPath 像素級遍歷的完整實現方案。
GraphicsPath。RectangleF bounds = path.GetBounds() 作為裁剪輸出圖尺寸。outputBitmap,遍歷邊界矩形內每個像素:path.IsVisible(x, y) 判斷像素是否在多邊形內。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();
}
}
}
}
}
// 高性能版本骨架
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() 替代直線,實現平滑邊緣裁剪。WriteableBitmap + Shader 或 Direct2D 實現毫秒級裁剪。基於 GraphicsPath.IsVisible 的像素級裁剪邏輯清晰、幾何精確,適合中小圖片、離線處理場景。大圖/實時預覽場景務必採用 LockBits 非安全代碼或 GPU 方案提升百倍性能。輸出格式必須選 PNG 以保留 Alpha 透明通道,JPEG 不支援透明度。