SkiaSharp 是一个强大的跨平台 2D 图形库,提供了灵活且高效的图像处理能力。本文将深入探讨 SkiaSharp 中图像裁剪的各种技术和方法。
C#SkiaSharp SkiaSharp.Views.WindowsForms
C#using System.Windows.Forms;
using SkiaSharp;
namespace AppCut
{
public partial class Form1 : Form
{
private string currentImagePath;
private Image displayImage;
private Rectangle cropSelection = Rectangle.Empty;
private Point startPoint;
private bool isSelecting = false;
public Form1()
{
InitializeComponent();
}
private void openMenuItem_Click(object sender, EventArgs e)
{
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
openFileDialog.Filter = "图像文件|*.jpg;*.jpeg;*.png;*.bmp|所有文件|*.*";
openFileDialog.Title = "选择图像文件";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
try
{
// 清除之前的选择
cropSelection = Rectangle.Empty;
// 加载并显示图像
currentImagePath = openFileDialog.FileName;
displayImage = Image.FromFile(currentImagePath);
pictureBox1.Image = displayImage;
// 更新UI状态
statusLabel.Text = $"已加载图像: {Path.GetFileName(currentImagePath)}";
btnCrop.Enabled = false;
saveMenuItem.Enabled = false;
}
catch (Exception ex)
{
MessageBox.Show($"无法加载图像: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (isSelecting && displayImage != null)
{
Point currentPoint = ConvertCoordinates(e.Location);
// 计算选择矩形
int x = Math.Min(startPoint.X, currentPoint.X);
int y = Math.Min(startPoint.Y, currentPoint.Y);
int width = Math.Abs(currentPoint.X - startPoint.X);
int height = Math.Abs(currentPoint.Y - startPoint.Y);
cropSelection = new Rectangle(x, y, width, height);
// 更新状态栏显示选择区域信息
statusLabel.Text = $"选择区域: X={x}, Y={y}, 宽={width}, 高={height}";
pictureBox1.Invalidate();
}
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (displayImage != null)
{
// 开始新的选择
isSelecting = true;
startPoint = ConvertCoordinates(e.Location);
cropSelection = Rectangle.Empty;
pictureBox1.Invalidate();
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
if (isSelecting && displayImage != null)
{
isSelecting = false;
// 确保选择区域有效
if (cropSelection.Width > 10 && cropSelection.Height > 10)
{
btnCrop.Enabled = true;
}
else
{
cropSelection = Rectangle.Empty;
statusLabel.Text = "选择区域太小,请重新选择一个更大的区域";
}
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (displayImage != null && !cropSelection.IsEmpty)
{
// 绘制选择矩形
using (Pen pen = new Pen(Color.Red, 2))
{
// 转换回屏幕坐标
Rectangle displayRect = GetDisplayRectangle();
float ratioX = (float)displayRect.Width / displayImage.Width;
float ratioY = (float)displayRect.Height / displayImage.Height;
int x = displayRect.X + (int)(cropSelection.X * ratioX);
int y = displayRect.Y + (int)(cropSelection.Y * ratioY);
int width = (int)(cropSelection.Width * ratioX);
int height = (int)(cropSelection.Height * ratioY);
e.Graphics.DrawRectangle(pen, x, y, width, height);
}
}
}
// 将屏幕坐标转换为图像坐标
private Point ConvertCoordinates(Point screenPoint)
{
Rectangle displayRect = GetDisplayRectangle();
if (displayRect.Width <= 0 || displayRect.Height <= 0)
return new Point(0, 0);
float ratioX = (float)displayImage.Width / displayRect.Width;
float ratioY = (float)displayImage.Height / displayRect.Height;
int imageX = (int)((screenPoint.X - displayRect.X) * ratioX);
int imageY = (int)((screenPoint.Y - displayRect.Y) * ratioY);
// 确保坐标在图像范围内
imageX = Math.Max(0, Math.Min(imageX, displayImage.Width - 1));
imageY = Math.Max(0, Math.Min(imageY, displayImage.Height - 1));
return new Point(imageX, imageY);
}
// 获取图像在pictureBox1中的实际显示矩形
private Rectangle GetDisplayRectangle()
{
if (displayImage == null)
return Rectangle.Empty;
float imageRatio = (float)displayImage.Width / displayImage.Height;
float boxRatio = (float)pictureBox1.Width / pictureBox1.Height;
int x, y, width, height;
if (imageRatio > boxRatio)
{
// 图像更宽,适应宽度
width = pictureBox1.Width;
height = (int)(width / imageRatio);
x = 0;
y = (pictureBox1.Height - height) / 2;
}
else
{
// 图像更高,适应高度
height = pictureBox1.Height;
width = (int)(height * imageRatio);
y = 0;
x = (pictureBox1.Width - width) / 2;
}
return new Rectangle(x, y, width, height);
}
private void btnCrop_Click(object sender, EventArgs e)
{
if (displayImage != null && !cropSelection.IsEmpty)
{
try
{
// 创建一个临时文件来保存裁剪结果
string tempOutputPath = Path.Combine(Path.GetTempPath(), "cropped_" + Path.GetFileName(currentImagePath));
// 将Rectangle转换为SKRectI
SKRectI cropRect = new SKRectI(
cropSelection.Left,
cropSelection.Top,
cropSelection.Right,
cropSelection.Bottom
);
// 执行裁剪
CropImage(currentImagePath, tempOutputPath, cropRect);
// 显示裁剪结果
displayImage.Dispose();
displayImage = Image.FromFile(tempOutputPath);
pictureBox1.Image = displayImage;
// 更新当前图像路径为临时路径
currentImagePath = tempOutputPath;
// 更新UI状态
cropSelection = Rectangle.Empty;
btnCrop.Enabled = false;
saveMenuItem.Enabled = true;
statusLabel.Text = "裁剪完成,可以保存结果";
}
catch (Exception ex)
{
MessageBox.Show($"裁剪操作失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private void saveMenuItem_Click(object sender, EventArgs e)
{
if (displayImage != null)
{
using (SaveFileDialog saveFileDialog = new SaveFileDialog())
{
saveFileDialog.Filter = "PNG 图像|*.png|JPEG 图像|*.jpg|BMP 图像|*.bmp";
saveFileDialog.Title = "保存裁剪后的图像";
saveFileDialog.DefaultExt = "png";
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
try
{
// 复制临时文件到用户选择的位置
File.Copy(currentImagePath, saveFileDialog.FileName, true);
statusLabel.Text = $"已保存到: {saveFileDialog.FileName}";
}
catch (Exception ex)
{
MessageBox.Show($"保存文件失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
}
public void CropImage(string inputPath, string outputPath, SKRectI cropRect)
{
// 加载源图像
using (SKBitmap sourceBitmap = SKBitmap.Decode(inputPath))
{
// 创建裁剪后的图像
using (SKBitmap croppedBitmap = new SKBitmap(cropRect.Width, cropRect.Height))
{
// 使用 DrawBitmap 进行裁剪
using (SKCanvas canvas = new SKCanvas(croppedBitmap))
{
canvas.DrawBitmap(
sourceBitmap,
new SKRect(cropRect.Left, cropRect.Top, cropRect.Right, cropRect.Bottom),
new SKRect(0, 0, cropRect.Width, cropRect.Height)
);
}
// 保存裁剪后的图像
using (SKImage image = SKImage.FromBitmap(croppedBitmap))
using (SKData data = image.Encode(SKEncodedImageFormat.Png, 100))
{
File.WriteAllBytes(outputPath, data.ToArray());
}
}
}
}
}
}

C#// 圆形裁剪方法
public SKBitmap CropCircular(SKBitmap sourceBitmap)
{
int diameter = Math.Min(sourceBitmap.Width, sourceBitmap.Height);
int radius = diameter / 2;
// 创建圆形位图
SKBitmap circularBitmap = new SKBitmap(diameter, diameter);
using (SKCanvas canvas = new SKCanvas(circularBitmap))
{
// 清除背景为透明
canvas.Clear(SKColors.Transparent);
// 创建圆形裁剪路径
using (SKPath clipPath = new SKPath())
{
clipPath.AddCircle(radius, radius, radius);
canvas.ClipPath(clipPath);
// 计算居中绘制的源矩形
SKRect sourceRect = new SKRect(
(sourceBitmap.Width - diameter) / 2f,
(sourceBitmap.Height - diameter) / 2f,
(sourceBitmap.Width + diameter) / 2f,
(sourceBitmap.Height + diameter) / 2f
);
// 绘制图像
canvas.DrawBitmap(sourceBitmap, sourceRect, new SKRect(0, 0, diameter, diameter));
}
}
return circularBitmap;
}

Subset 方法SKImage.Encode 进行高效编码SkiaSharp 提供了强大且灵活的图像裁剪功能,通过合理使用 API,可以实现各种复杂的图像处理需求。
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!