FreeShape 的编辑点 - 减少路径

Edit points of FreeShape - reduce path

我有一些 GUI 让用户绘制成本优化的 GraphicsPath。 我使用 GraphicsPath AddLine 函数创建了它。

现在我想实现您在随附的 Microsoft Word 图像中看到的内容 - "Edit Points"。

我面临几个问题:

  1. 我的路径有数百个 "Lines"->每个只有一个像素大小。我只想 select "key Points"。我怎么做?有点像"Flatten"的反转,找不到这样的函数。

  2. 是否存在现有的 .Net 函数来绘制路径周围的蓝色小矩形和绿色小圆圈?每个 selected 点周围的矩形又如何呢?

我们将不胜感激,即使是部分帮助。

对于问题的第一部分,请查看 this post,它具有 List<Point> 的缩减功能。请注意,GraphicsPath.PathPoints 集合是只读的,因此您必须从减少的点列表中重新创建路径。

关于第二部分的几点说明:

  • 没有用于创建句柄的内置例程。也不想让他们做任何事情。所以你需要为他们编码。

  • 我附加了一个简单的 class MoveLabel 可以用于此。它可以放在控件上或添加到其 Controls 集合中。然后你可以移动它。我添加了一个回调函数MoveAction 来处理鼠标释放时的结果。

您可以添加一个..

public delegate void Moved(MoveLabel sender);

.. 到 class 形式,或者,为了避免 Form1 引用,在 class 形式之外但在 MoveLabel.[= 范围内43=]

可直接用于移动点列表中的点:

在面板上创建它:

var lab= new MoveLabel(Color.CadetBlue, 9, Point.Round(points[i]), i);
lab.Parent = panel;
lab.MoveAction = moved;

一个简单的处理函数:

void moved(MoveLabel sender)
{
    points[sender.PointIndex] = 
            new Point(sender.Left - sender.Width / 2, sender.Top - sender.Height / 2);
    panel.Invalidate();
}

请注意 GraphicsPath.PathPoints 是只读的,因此我们必须从新的点列表中重新创建路径!其实可以修改代码中的个别PathPoints,但是结果不粘;所以必须将 PathPoints 复制到 PointF[],在那里修改它们并重新创建路径。对于复杂路径,最好使用 this overload..

如果要实现旋转(或其他变换),可以使用 GraphicsPath.Transform 函数。您可以使用可移动标签来确定旋转或缩放数据。 这是我的最小 MoveLabel class:

public class MoveLabel : Label
{
    public Form1.Moved MoveAction { get; set; }
    public int PointIndex { get; set; }

    private Point mDown = Point.Empty;

    public MoveLabel()
    {
        MouseDown += (ss, ee) => { mDown = ee.Location; };
        MouseMove += (ss, ee) => {
            if (ee.Button.HasFlag(MouseButtons.Left))
            {
                Location = new Point(Left + ee.X - Width / 2, Top + ee.Y - Height / 2);
                mDown = Location;
            }
        };
        MouseUp += (ss, ee) => { if (MoveAction != null) MoveAction(this);  };
    }

    public MoveLabel(Color c, int size, Point location, int pointIndex) : this()
    {
        BackColor = Color.CadetBlue;
        Size = new Size(size, size);
        Location = location;
        PointIndex = pointIndex;
    }
}

这也适用于从贝塞尔曲线移动点。通过向 MouseMove linq 代码添加调用 MoveAction(this);,您可以获得实时更新。确保 Panels :-)

示例:


顺便说一句:我刚刚发现 this post,它展示了如何轻松地将曲线或任何其他 GDI+ 矢量绘图保存到 emf,保持矢量质量!


更新: 而不是 Panel,这是一个 Container 控件,并不是真正要在上面绘制,您可以使用 PictureboxLabelAutosize=false);两者都开箱即用 DoubleBuffered 属性 并且比 Panels 更好地支持绘图。

对于减少点部分 - 我最终使用了 Dougles-Packer 算法,在这里找到它: 我不记得我是否在某处找到了这个实现。如果有人知道它的来源 - 我很乐意 link 他的回答并向他提供反馈。

我的实现在这里:

    public static List<Point> DouglasPeuckerReduction
        (List<Point> Points, Double Tolerance)
    {
        if (Points == null || Points.Count < 3)
            return Points;

        Int32 firstPoint = 0;
        Int32 lastPoint = Points.Count - 1;
        List<Int32> pointIndexsToKeep = new List<Int32>();

        //Add the first and last index to the keepers
        pointIndexsToKeep.Add(firstPoint);
        pointIndexsToKeep.Add(lastPoint);

        //The first and the last point cannot be the same
        while (Points[firstPoint].Equals(Points[lastPoint]))
        {
            lastPoint--;
        }

        DouglasPeuckerReduction(Points, firstPoint, lastPoint,
        Tolerance, ref pointIndexsToKeep);

        List<Point> returnPoints = new List<Point>();
        pointIndexsToKeep.Sort();
        foreach (Int32 index in pointIndexsToKeep)
        {
            returnPoints.Add(Points[index]);
        }

        return returnPoints;
    }

    /// <summary>
    /// Douglases the peucker reduction.
    /// </summary>
    /// <param name="points">The points.</param>
    /// <param name="firstPoint">The first point.</param>
    /// <param name="lastPoint">The last point.</param>
    /// <param name="tolerance">The tolerance.</param>
    /// <param name="pointIndexsToKeep">The point index to keep.</param>
    private static void DouglasPeuckerReduction(List<Point>
        points, Int32 firstPoint, Int32 lastPoint, Double tolerance,
        ref List<Int32> pointIndexsToKeep)
    {
        Double maxDistance = 0;
        Int32 indexFarthest = 0;

        for (Int32 index = firstPoint; index < lastPoint; index++)
        {
            Double distance = PerpendicularDistance
                (points[firstPoint], points[lastPoint], points[index]);
            if (distance > maxDistance)
            {
                maxDistance = distance;
                indexFarthest = index;
            }
        }

        if (maxDistance > tolerance && indexFarthest != 0)
        {
            //Add the largest point that exceeds the tolerance
            pointIndexsToKeep.Add(indexFarthest);

            DouglasPeuckerReduction(points, firstPoint,
            indexFarthest, tolerance, ref pointIndexsToKeep);
            DouglasPeuckerReduction(points, indexFarthest,
            lastPoint, tolerance, ref pointIndexsToKeep);
        }
    }

    /// <summary>
    /// The distance of a point from a line made from point1 and point2.
    /// </summary>
    /// <param name="pt1">The PT1.</param>
    /// <param name="pt2">The PT2.</param>
    /// <param name="p">The p.</param>
    /// <returns></returns>
    public static Double PerpendicularDistance
        (Point Point1, Point Point2, Point Point)
    {
        //Area = |(1/2)(x1y2 + x2y3 + x3y1 - x2y1 - x3y2 - x1y3)|   *Area of triangle
        //Base = v((x1-x2)²+(x1-x2)²)                               *Base of Triangle*
        //Area = .5*Base*H                                          *Solve for height
        //Height = Area/.5/Base

        Double area = Math.Abs(.5 * (Point1.X * Point2.Y + Point2.X *
        Point.Y + Point.X * Point1.Y - Point2.X * Point1.Y - Point.X *
        Point2.Y - Point1.X * Point.Y));
        Double bottom = Math.Sqrt(Math.Pow(Point1.X - Point2.X, 2) +
        Math.Pow(Point1.Y - Point2.Y, 2));
        Double height = area / bottom * 2;

        return height;

        //Another option
        //Double A = Point.X - Point1.X;
        //Double B = Point.Y - Point1.Y;
        //Double C = Point2.X - Point1.X;
        //Double D = Point2.Y - Point1.Y;

        //Double dot = A * C + B * D;
        //Double len_sq = C * C + D * D;
        //Double param = dot / len_sq;

        //Double xx, yy;

        //if (param < 0)
        //{
        //    xx = Point1.X;
        //    yy = Point1.Y;
        //}
        //else if (param > 1)
        //{
        //    xx = Point2.X;
        //    yy = Point2.Y;
        //}
        //else
        //{
        //    xx = Point1.X + param * C;
        //    yy = Point1.Y + param * D;
        //}

        //Double d = DistanceBetweenOn2DPlane(Point, new Point(xx, yy));
    }

}