如何在C#中实现手绘图形自动转为规整图形(直线、正方形)
手绘图形规整化实现(C# WinForms)
需求说明
用户在panelDraw(手绘区域)绘制线条或方形后,程序自动识别图形类型,并在结果展示区panelB生成对应的规整图形:
- 手绘线条 → 生成首尾点连接的规整直线
- 手绘方形 → 生成边长一致、角度为90度的完美正方形
现有代码问题分析
当前代码存在以下不足,无法实现需求:
- 使用
panelDraw.CreateGraphics()绘制,窗口重绘时手绘内容会丢失 - 未记录手绘轨迹点,无法识别图形类型
- 未实现
panelB的规整图形绘制逻辑
实现方案与完整代码
以下是修改后的完整代码,解决上述问题并实现需求:
public partial class frmPaint : Form { // 存储手绘轨迹点 private List<Point> _drawPoints = new List<Point>(); private Pen _penA = new Pen(Color.Red, 2); private Pen _eraser = new Pen(Color.White, 10); private bool _isDrawing = false; public frmPaint() { InitializeComponent(); // 绑定Paint事件处理重绘 panelDraw.Paint += PanelDraw_Paint; panelB.Paint += PanelB_Paint; } private void PanelDraw_Paint(object sender, PaintEventArgs e) { // 重绘手绘轨迹 if (_drawPoints.Count > 1) { e.Graphics.DrawLines(_penA, _drawPoints.ToArray()); } } private void PanelB_Paint(object sender, PaintEventArgs e) { if (_drawPoints.Count < 2) return; // 识别并绘制规整图形 if (IsSquare(_drawPoints)) { var square = GetRegularSquare(_drawPoints); e.Graphics.DrawRectangle(_penA, square); } else if (IsLine(_drawPoints)) { e.Graphics.DrawLine(_penA, _drawPoints.First(), _drawPoints.Last()); } } private void panelDraw_MouseDown(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { _isDrawing = true; _drawPoints.Clear(); _drawPoints.Add(e.Location); // 更新画笔宽度 if (rbLineWidth5.Checked) _penA.Width = 5; if (rbLineWidth10.Checked) _penA.Width = 10; if (rbLineWidth15.Checked) _penA.Width = 15; } } private void panelDraw_MouseMove(object sender, MouseEventArgs e) { if (_isDrawing && e.Button == MouseButtons.Left) { _drawPoints.Add(e.Location); panelDraw.Invalidate(); // 触发重绘 } if (e.Button == MouseButtons.Right) { // 橡皮擦功能:清除轨迹并重绘 _drawPoints.Clear(); panelDraw.Invalidate(); panelB.Invalidate(); } } private void panelDraw_MouseUp(object sender, MouseEventArgs e) { if (_isDrawing) { _isDrawing = false; panelB.Invalidate(); // 触发panelB绘制规整图形 } } private void btnColor_Click(object sender, EventArgs e) { using (ColorDialog colorDialog = new ColorDialog()) { if (colorDialog.ShowDialog() == DialogResult.OK) { _penA.Color = colorDialog.Color; panelDraw.Invalidate(); panelB.Invalidate(); } } } // 判断是否为直线(轨迹点偏差在阈值内) private bool IsLine(List<Point> points) { if (points.Count < 2) return false; var start = points.First(); var end = points.Last(); double maxDeviation = 15; // 可调整偏差阈值 foreach (var point in points) { // 计算点到直线的距离 double distance = Math.Abs((end.Y - start.Y) * point.X - (end.X - start.X) * point.Y + end.X * start.Y - end.Y * start.X) / Math.Sqrt(Math.Pow(end.Y - start.Y, 2) + Math.Pow(end.X - start.X, 2)); if (distance > maxDeviation) return false; } return true; } // 判断是否为正方形(四边长度接近,内角接近90度) private bool IsSquare(List<Point> points) { if (points.Count < 4) return false; // 简化处理:取轨迹的四个关键顶点(首尾及中间两个极值点) var keyPoints = GetKeyPoints(points); if (keyPoints.Count != 4) return false; // 计算四边长度 double[] lengths = new double[4]; lengths[0] = GetDistance(keyPoints[0], keyPoints[1]); lengths[1] = GetDistance(keyPoints[1], keyPoints[2]); lengths[2] = GetDistance(keyPoints[2], keyPoints[3]); lengths[3] = GetDistance(keyPoints[3], keyPoints[0]); // 判断四边长度是否接近(误差10%以内) double avgLength = lengths.Average(); foreach (var len in lengths) { if (Math.Abs(len - avgLength) > avgLength * 0.1) return false; } // 判断相邻边是否垂直(向量点积接近0) var v1 = new Point(keyPoints[1].X - keyPoints[0].X, keyPoints[1].Y - keyPoints[0].Y); var v2 = new Point(keyPoints[2].X - keyPoints[1].X, keyPoints[2].Y - keyPoints[1].Y); double dotProduct = v1.X * v2.X + v1.Y * v2.Y; if (Math.Abs(dotProduct) > 50) return false; // 阈值可调整 return true; } // 获取轨迹的关键顶点(简化处理) private List<Point> GetKeyPoints(List<Point> points) { // 这里简化实现,取X/Y的极值点,实际可使用更复杂的轮廓提取算法 List<Point> keyPoints = new List<Point>(); keyPoints.Add(points.OrderBy(p => p.X + p.Y).First()); // 左上角近似点 keyPoints.Add(points.OrderBy(p => -p.X + p.Y).First()); // 右上角近似点 keyPoints.Add(points.OrderBy(p => -p.X - p.Y).First()); // 右下角近似点 keyPoints.Add(points.OrderBy(p => p.X - p.Y).First()); // 左下角近似点 return keyPoints.DistinctBy(p => new { p.X, p.Y }).ToList(); } // 计算两点距离 private double GetDistance(Point p1, Point p2) { return Math.Sqrt(Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2)); } // 生成规整的正方形 private Rectangle GetRegularSquare(List<Point> points) { var keyPoints = GetKeyPoints(points); double sideLength = GetDistance(keyPoints[0], keyPoints[1]); int x = keyPoints.Min(p => p.X); int y = keyPoints.Min(p => p.Y); return new Rectangle(x, y, (int)Math.Round(sideLength), (int)Math.Round(sideLength)); } }
代码说明
- 轨迹记录:使用
List<Point>存储手绘的所有点,解决重绘丢失问题 - 重绘机制:通过
Paint事件处理panelDraw和panelB的绘制,确保窗口重绘时内容不丢失 - 图形识别:
- 直线判断:计算轨迹点到首尾点连线的距离,偏差在阈值内则判定为直线
- 正方形判断:通过关键顶点的边长一致性和邻边垂直性判断
- 规整化绘制:
- 直线:直接连接轨迹首尾点
- 正方形:根据关键顶点计算边长,生成完美正方形
- 优化功能:
- 橡皮擦功能改为清除轨迹并同步刷新两个面板
- 使用
using包裹ColorDialog,确保资源释放
内容的提问来源于stack exchange,提问作者Mardibak
相关产品推荐
相关产品推荐

