2

我有一些 GUI 让用户绘制 costimized GraphicsPath。我使用 GraphicsPath AddLine 函数创建了它。

现在我想实现您在附加的 Microsoft Word 图像中看到的内容 - “编辑点”。

在此处输入图像描述

我面临几个问题:

  1. 我的路径有数百条“线”-> 每条只有一个像素大小。我只想选择“关键点”。我怎么做?这是“Flatten”的一种反转,找不到这样的功能。

  2. 是否有现有的 .Net 函数来绘制蓝色小矩形和路径周围的绿色小圆圈?每个选定点周围的矩形呢? 在此处输入图像描述

每一个帮助,即使是部分的,将不胜感激。

4

2 回答 2

2

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

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

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

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

您可以添加一个 ..

public delegate void Moved(MoveLabel sender);

.. 到表单类,或者,为了避免Form1引用,在表单类之外但在MoveLabel.

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

在面板上创建它:

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到 a PointF[],在那里修改它们并重新创建路径。对于复杂路径,最好使用此重载..

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

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

这也可以很好地从贝塞尔曲线移动点。通过添加MoveAction(this);MouseMovelinq 代码的调用,您可以获得实时更新.. 确保为此Panels使用DoubleBuffered :-)

例子:

在此处输入图像描述


顺便说一句:我刚刚看到这篇文章,它显示了曲线或任何其他 GDI+ 矢量图可以很容易地保存到emf,保持矢量质量!


更新:代替 a Panel,它是一个Container控件,并不是真的要吸引你,可以使用 aPicturebox或 a Label(with Autosize=false); 两者都具有DoubleBuffered开箱即用的属性,并且比做更好地支持绘图Panels

于 2018-06-06T11:02:31.440 回答
0

对于减少点部分 - 我最终使用了 Dougles-Packer 算法,在这里找到它:https ://stackoverflow.com/a/7982089/3225391 我不记得我是否在某个地方找到了这个实现。如果有人知道它来自哪里 - 我很乐意链接他的答案并向他提供反馈。

我的实现在这里:

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

}
于 2021-10-25T09:05:56.193 回答