2025-11-19
C#
00

目录

Nuget 安装包
基本图像裁剪
核心功能
矩形区域裁剪
圆形裁剪
裁剪注意事项
性能优化
错误处理
最佳实践
结语

SkiaSharp 是一个强大的跨平台 2D 图形库,提供了灵活且高效的图像处理能力。本文将深入探讨 SkiaSharp 中图像裁剪的各种技术和方法。

Nuget 安装包

C#
SkiaSharp SkiaSharp.Views.WindowsForms

基本图像裁剪

核心功能

  • 图像打开:支持打开JPG、JPEG、PNG和BMP格式的图像文件
  • 交互式选择区域:用户可以通过鼠标在图像上拖拽选择要裁剪的矩形区域
  • 实时预览:选择过程中实时显示选择框和区域尺寸信息
  • 图像裁剪:使用SkiaSharp库执行图像裁剪操作
  • 裁剪结果保存:支持将裁剪后的图像保存为新文件

矩形区域裁剪

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()); } } } } } }

image.png

圆形裁剪

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; }

image.png

裁剪注意事项

性能优化

  • 对大图像使用 Subset 方法
  • 避免不必要的内存分配
  • 使用 SKImage.Encode 进行高效编码

错误处理

  • 添加输入验证
  • 处理可能的异常情况
  • 检查图像尺寸和裁剪区域

最佳实践

  • 预先计算裁剪区域
  • 考虑目标分辨率和宽高比
  • 保留原始图像的高质量
  • 适当处理内存释放

结语

SkiaSharp 提供了强大且灵活的图像裁剪功能,通过合理使用 API,可以实现各种复杂的图像处理需求。

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!