FreeShape 的编辑点 - 减少路径
Edit points of FreeShape - reduce path
我有一些 GUI 让用户绘制成本优化的 GraphicsPath。
我使用 GraphicsPath AddLine 函数创建了它。
现在我想实现您在随附的 Microsoft Word 图像中看到的内容 - "Edit Points"。
我面临几个问题:
我的路径有数百个 "Lines"->每个只有一个像素大小。我只想 select "key Points"。我怎么做?有点像"Flatten"的反转,找不到这样的函数。
是否存在现有的 .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
控件,并不是真正要在上面绘制,您可以使用 Picturebox
或 Label
(Autosize=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));
}
}
我有一些 GUI 让用户绘制成本优化的 GraphicsPath。 我使用 GraphicsPath AddLine 函数创建了它。
现在我想实现您在随附的 Microsoft Word 图像中看到的内容 - "Edit Points"。
我面临几个问题:
我的路径有数百个 "Lines"->每个只有一个像素大小。我只想 select "key Points"。我怎么做?有点像"Flatten"的反转,找不到这样的函数。
是否存在现有的 .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
控件,并不是真正要在上面绘制,您可以使用 Picturebox
或 Label
(Autosize=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));
}
}