You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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));
    }
}

代码说明

  1. 轨迹记录:使用List<Point>存储手绘的所有点,解决重绘丢失问题
  2. 重绘机制:通过Paint事件处理panelDraw和panelB的绘制,确保窗口重绘时内容不丢失
  3. 图形识别:
    • 直线判断:计算轨迹点到首尾点连线的距离,偏差在阈值内则判定为直线
    • 正方形判断:通过关键顶点的边长一致性和邻边垂直性判断
  4. 规整化绘制:
    • 直线:直接连接轨迹首尾点
    • 正方形:根据关键顶点计算边长,生成完美正方形
  5. 优化功能:
    • 橡皮擦功能改为清除轨迹并同步刷新两个面板
    • 使用using包裹ColorDialog,确保资源释放

内容的提问来源于stack exchange,提问作者Mardibak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 22:45:47