*更新*
使用Clipper库找到解决方案。解决方案添加为答案。不过,欢迎新的/更好/更容易的想法!
给出这样的路径:
我想用给定的距离创建一条围绕此路径的路径,例如1厘米。下面的草图演示了 - 红色路径围绕黑色路径,距离为1厘米。
如何使用PDFSharp以通用方式完成此操作? (意思是我想最终用PDFSharp绘制它,我不在乎计算的位置) 以下是黑色路径的代码:
// helper for easily getting an XPoint in centimeters
private XPoint cmPoint(double x, double y)
{
return new XPoint(
XUnit.FromCentimeter(x),
XUnit.FromCentimeter(y)
);
}
// the path to be drawn
private XGraphicsPath getMyPath()
{
XGraphicsPath path = new XGraphicsPath();
XPoint[] points = new XPoint[3];
points[0] = cmPoint(0, 0);
points[1] = cmPoint(5, 2);
points[2] = cmPoint(10,0);
path.AddCurve(points);
path.AddLine(cmPoint(10, 0), cmPoint(10, 10));
path.AddLine(cmPoint(10, 10), cmPoint(0, 10));
path.CloseFigure();
return path;
}
// generate the PDF file
private void button3_Click(object sender, RoutedEventArgs e)
{
// Create a temporary file
string filename = String.Format("{0}_tempfile.pdf", Guid.NewGuid().ToString("D").ToUpper());
XPen penBlack = new XPen(XColors.Black, 1);
XPen penRed = new XPen(XColors.Red, 1);
PdfDocument pdfDocument = new PdfDocument();
PdfPage page = pdfDocument.AddPage();
page.Size = PdfSharp.PageSize.A1;
XGraphics gfx = XGraphics.FromPdfPage(page);
//give us some space to the left and top
gfx.TranslateTransform(XUnit.FromCentimeter(3), XUnit.FromCentimeter(3));
// draw the desired path
gfx.DrawPath(penBlack, getMyPath());
// Save the pdfDocument...
pdfDocument.Save(filename);
// ...and start a viewer
Process.Start(filename);
}
感谢您对此主题的任何帮助!
答案 0 :(得分:8)
您可以使用Widen()
函数,该函数将曲线替换为包含指定笔绘制路径时填充区域的曲线,并为路径添加其他轮廓。
此函数作为参数接收XPen
,因此您可以使用所需的偏移量作为宽度创建此XPen
,并以恒定距离(笔的宽度)添加外部路径。
XGraphicsPath
类实际上是System.Drawing.Drawing2D.GraphicsPath
的包装器,因此您可以在Widen()
中使用XGraphicsPath
函数,获取内部对象并使用{{1}对其进行迭代}}来获取添加的路径。
这种方法可以胜任:
GraphicsPathIterator
您可以使用重载public XGraphicsPath GetSurroundPath(XGraphicsPath path, double width)
{
XGraphicsPath container = new XGraphicsPath();
container.StartFigure();
container.AddPath(path, false);
container.CloseFigure();
var penOffset = new XPen(XColors.Black, width);
container.StartFigure();
container.Widen(penOffset);
container.CloseFigure();
var iterator = new GraphicsPathIterator(container.Internals.GdiPath);
bool isClosed;
var outline = new XGraphicsPath();
iterator.NextSubpath(outline.Internals.GdiPath, out isClosed);
return outline;
}
处理曲线中的平坦度。拨打此电话Widen(XPen pen, XMatrix matrix, double flatness)
会产生更圆润的边缘。
然后使用此函数绘制外部路径:
container.Widen(penOffset, XMatrix.Identity, 0.05);
这就是你得到的:
另一种方法可能是使用反射来检索string filename = String.Format("{0}_tempfile.pdf", Guid.NewGuid().ToString("D").ToUpper());
XPen penBlack = new XPen(XColors.Black, 1);
XPen penRed = new XPen(XColors.Red, 1);
PdfDocument pdfDocument = new PdfDocument();
PdfPage page = pdfDocument.AddPage();
page.Size = PdfSharp.PageSize.A1;
XGraphics gfx = XGraphics.FromPdfPage(page);
//give us some space to the left and top
gfx.TranslateTransform(XUnit.FromCentimeter(3), XUnit.FromCentimeter(3));
var path = getMyPath();
// draw the desired path
gfx.DrawPath(penBlack, path);
gfx.DrawPath(penRed, GetSurroundPath(path, XUnit.FromCentimeter(1).Point));
// Save the pdfDocument...
pdfDocument.Save(filename);
// ...and start a viewer
Process.Start(filename);
和设置CompoundArray
属性中的内部Pen
。这允许您绘制平行线和空格。使用此属性,您可以执行以下操作:
但问题是你只能使用一种颜色,无论如何这只是一个想法,我没有试过XPen
此外,您应该搜索偏移折线曲线或偏移多边形算法。
答案 1 :(得分:2)
可以使用Clipper
完成此操作double scale = 1024.0;
List<IntPoint> points = new List<IntPoint>();
points.Add(new IntPoint(0*scale, 0*scale));
points.Add(new IntPoint(5*scale, 2*scale));
points.Add(new IntPoint(10*scale, 0*scale));
points.Add(new IntPoint(10*scale, 10*scale));
points.Add(new IntPoint(0*scale, 10*scale));
points.Reverse();
List<List<IntPoint>> solution = new List<List<IntPoint>>();
ClipperOffset co = new ClipperOffset();
co.AddPath(points, JoinType.jtMiter, EndType.etClosedPolygon);
co.Execute(ref solution, 1 * scale);
foreach (IntPoint point in solution[0])
{
Console.WriteLine("OUTPUT: " + point.X + "/" + point.Y + " -> " + point.X/scale + "/" + point.Y/scale);
}
输出:
OUTPUT: 11264/11264 -> 11/11
OUTPUT: -1024/11264 -> -1/11
OUTPUT: -1024/-1512 -> -1/-1,4765625
OUTPUT: 5120/945 -> 5/0,9228515625
OUTPUT: 11264/-1512 -> 11/-1,4765625
绘制原始和偏移路径:
由于各种数学原因,这仍然不完美,但已经相当不错了。
答案 2 :(得分:0)
XGraphicPath是密封类,它是用不良做法IMO实现的,所以唯一的方法是使用它周围的包装器。我试图使代码尽可能自我记录
public class OGraphicPath
{
private readonly ICollection<XPoint[]> _curves;
private readonly ICollection<Tuple<XPoint, XPoint>> _lines;
public OGraphicPath()
{
_lines = new List<Tuple<XPoint, XPoint>>();
_curves = new List<XPoint[]>();
}
public XGraphicsPath XGraphicsPath
{
get
{
var path = new XGraphicsPath();
foreach (var curve in _curves)
{
path.AddCurve(curve);
}
foreach (var line in _lines)
{
path.AddLine(line.Item1, line.Item2);
}
path.CloseFigure();
return path;
}
}
public void AddCurve(XPoint[] points)
{
_curves.Add(points);
}
public void AddLine(XPoint point1, XPoint point2)
{
_lines.Add(new Tuple<XPoint, XPoint>(point1, point2));
}
// Finds Highest and lowest X and Y to find the Center O(x,y)
private XPoint FindO()
{
var xs = new List<double>();
var ys = new List<double>();
foreach (var point in _curves.SelectMany(points => points))
{
xs.Add(point.X);
ys.Add(point.Y);
}
foreach (var line in _lines)
{
xs.Add(line.Item1.X);
xs.Add(line.Item2.X);
ys.Add(line.Item1.Y);
ys.Add(line.Item2.Y);
}
var OX = xs.Min() + xs.Max()/2;
var OY = ys.Min() + ys.Max()/2;
return new XPoint(OX, OY);
}
// If a point is above O, it's surrounded point is even higher, if it's below O, it's surrunded point is below O too...
private double FindPlace(double p, double o, double distance)
{
var dp = p - o;
if (dp < 0)
{
return p - distance;
}
if (dp > 0)
{
return p + distance;
}
return p;
}
public XGraphicsPath Surrond(double distance)
{
var path = new XGraphicsPath();
var O = FindO();
foreach (var curve in _curves)
{
var points = new XPoint[curve.Length];
for (var i = 0; i < curve.Length; i++)
{
var point = curve[i];
var x = FindPlace(point.X, O.X, distance);
var y = FindPlace(point.Y, O.Y, distance);
points[i] = new XPoint(x, y);
}
path.AddCurve(points);
}
foreach (var line in _lines)
{
var ax = FindPlace(line.Item1.X, O.X, distance);
var ay = FindPlace(line.Item1.Y, O.Y, distance);
var a = new XPoint(ax, ay);
var bx = FindPlace(line.Item2.X, O.X, distance);
var by = FindPlace(line.Item2.Y, O.Y, distance);
var b = new XPoint(bx, by);
path.AddLine(a, b);
}
path.CloseFigure();
return path;
}
}
并且像这样消费
// draw the desired path
var path = getMyPath();
gfx.DrawPath(penBlack, path.XGraphicsPath);
gfx.DrawPath(penRed, path.Surrond(XUnit.FromCentimeter(1)));
答案 3 :(得分:0)
如果我们制作了一个&#34; DrawOutline&#34;扩展到xGraphics?
public static class XGraphicsExtentions
{
public static void DrawOutline(this XGraphics gfx, XPen pen, XGraphicsPath path, int offset)
{
// finding the size of the original path so that we know how much to scale it in x and y
var points = path.Internals.GdiPath.PathPoints;
float minX, minY;
float maxX, maxY;
GetMinMaxValues(points, out minX, out minY, out maxX, out maxY);
var deltaY = XUnit.FromPoint(maxY - minY);
var deltaX = XUnit.FromPoint(maxX - minX);
var offsetInPoints = XUnit.FromCentimeter(offset);
var scaleX = XUnit.FromPoint((deltaX + offsetInPoints)/deltaX);
var scaleY = XUnit.FromPoint((deltaY + offsetInPoints)/deltaY);
var transform = -offsetInPoints/2.0;
gfx.TranslateTransform(transform, transform);
gfx.ScaleTransform(scaleX, scaleY);
gfx.DrawPath(pen, path);
// revert changes to graphics object before exiting
gfx.ScaleTransform(1/scaleX,1/scaleY);
gfx.TranslateTransform(-transform, -transform);
}
private static void GetMinMaxValues(PointF[] points, out float minX, out float minY, out float maxX, out float maxY)
{
minX = float.MaxValue;
maxX = float.MinValue;
minY = float.MaxValue;
maxY = float.MinValue;
foreach (var point in points)
{
if (point.X < minX)
minX = point.X;
if (point.X > maxX)
maxX = point.X;
if (point.Y < minY)
minY = point.Y;
if (point.Y > maxY)
maxY = point.Y;
}
}
}
<强>用法:
// draw the desired path
gfx.DrawPath(penBlack, getMyPath());
gfx.DrawOutline(penRed, getMyPath(), 2);
<强>结果:
答案 4 :(得分:0)
Clipper是一个很好的选择,但根据您的需要,它不会产生完美的偏移。 offset from edge is not equal to offset from corner 更好的解决方案,需要您删除任何beizer曲线并仅使用线基元,使用CGAL库进行轮廓偏移:http://doc.cgal.org/latest/Straight_skeleton_2/index.html
实现它的另一种方法,实际上非常酷(尽管需要大量内存),是将路径转换为位图,然后应用扩展操作https://en.wikipedia.org/wiki/Dilation_(morphology)。这将为您提供正确的转换,但是在位图分辨率中。 您可以使用https://en.wikipedia.org/wiki/Potrace
等工具将位图转换为矢量图形一个好的图像工具箱是OpenCV,而http://www.emgu.com/wiki/index.php/Main_Page用于.NET / C#。它包括扩张。
这将为您提供一种有限的分辨率方法,但最终结果将精确到位图分辨率(实际上要高得多,因为您使用的是轮廓偏移,实际上是限制了偏移的轮廓细节)。
答案 5 :(得分:-2)
试试这个:
public Lis<Point> Draw(Point[] points /*Current polygon*/, int distance /*distance to new polygon*/) {
List<Point> lResult = new List<Point>();
foreach(Point lPoint in points) {
Point lNewPoint = new Point(lPoint.X - distance, lPoint.Y);
if(!CheckCurrentPoint(lNewPoint, points)) {
lResult.Add(lNewPoint)
continue;
}
lNewPoint = new Point(lPoint.X + distance, lPoint.Y);
if(!CheckCurrentPoint(lNewPoint, points)) {
lResult.Add(lNewPoint)
continue;
}
lNewPoint = new Point(lPoint.X, lPoint.Y - distance);
if(!CheckCurrentPoint(lNewPoint, points)) {
lResult.Add(lNewPoint)
continue;
}
lNewPoint = new Point(lPoint.X, lPoint.Y + distance);
if(!CheckCurrentPoint(lNewPoint, points)) {
lResult.Add(lNewPoint)
continue;
}
}
return lResult; // Points of new polygon
}
private static int Crs(Point a1, Point a2, Point p, ref bool ans) {
const double e = 0.00000000000001;
int lCrsResult = 0;
if (Math.Abs(a1.Y - a2.Y) < e)
if ((Math.Abs(p.Y - a1.Y) < e) && ((p.X - a1.X) * (p.X - a2.X) < 0.0))
ans = false;
if ((a1.Y - p.Y) * (a2.Y - p.Y) > 0.0)
return lCrsResult;
double lX = a2.X - (a2.Y - p.Y) / (a2.Y - a1.Y) * (a2.X - a1.X);
if (Math.Abs(lX - p.X) < e)
ans = false;
else if (lX < p.X) {
lCrsResult = 1;
if ((Math.Abs(a1.Y - p.Y) < e) && (a1.Y < a2.Y))
lCrsResult = 0;
else if ((Math.Abs(a2.Y - p.Y) < e) && (a2.Y < a1.Y))
lCrsResult = 0;
}
return lCrsResult;
}
private static bool CheckCurrentPoint(Point p /*Point of new posible polygon*/, Points[] points /*points of current polygon*/) {
if (points.Count == 0)
return false;
int lC = 0;
bool lAns = true;
for (int lIndex = 1; lIndex < points.Count; lIndex++) {
lC += Crs(points[lIndex - 1], points[lIndex], p, ref lAns);
if (!lAns)
return false;
}
lC += Crs(points[points.Count - 1], points[0], p, ref lAns);
if (!lAns)
return false;
return (lC & 1) > 0;
}