我有一些 GUI 让用户绘制 costimized GraphicsPath。我使用 GraphicsPath AddLine 函数创建了它。
现在我想实现您在附加的 Microsoft Word 图像中看到的内容 - “编辑点”。
我面临几个问题:
我的路径有数百条“线”-> 每条只有一个像素大小。我只想选择“关键点”。我怎么做?这是“Flatten”的一种反转,找不到这样的功能。
每一个帮助,即使是部分的,将不胜感激。
我有一些 GUI 让用户绘制 costimized GraphicsPath。我使用 GraphicsPath AddLine 函数创建了它。
现在我想实现您在附加的 Microsoft Word 图像中看到的内容 - “编辑点”。
我面临几个问题:
我的路径有数百条“线”-> 每条只有一个像素大小。我只想选择“关键点”。我怎么做?这是“Flatten”的一种反转,找不到这样的功能。
每一个帮助,即使是部分的,将不胜感激。
对于您问题的第一部分,请看一下这篇文章,它有一个缩减功能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);
对MouseMove
linq 代码的调用,您可以获得实时更新.. 确保为此Panels
使用DoubleBuffered :-)
例子:
顺便说一句:我刚刚看到这篇文章,它显示了曲线或任何其他 GDI+ 矢量图可以很容易地保存到emf
,保持矢量质量!
更新:代替 a Panel
,它是一个Container
控件,并不是真的要吸引你,可以使用 aPicturebox
或 a Label
(with Autosize=false
); 两者都具有DoubleBuffered
开箱即用的属性,并且比做更好地支持绘图Panels
。
对于减少点部分 - 我最终使用了 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));
}
}