绘制围绕给定路径的路径

时间:2016-02-02 19:54:15

标签: c# graphics pdfsharp

*更新*

使用Clipper库找到解决方案。解决方案添加为答案。不过,欢迎新的/更好/更容易的想法!

给出这样的路径:

original black path

我想用给定的距离创建一条围绕此路径的路径,例如1厘米。下面的草图演示了 - 红色路径围绕黑色路径,距离为1厘米。

black path surrounded by a red path with a distance of 1 cm

如何使用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);
}

感谢您对此主题的任何帮助!

6 个答案:

答案 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);

这就是你得到的:

enter image description here

另一种方法可能是使用反射来检索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。这允许您绘制平行线和空格。使用此属性,您可以执行以下操作:

enter image description here

但问题是你只能使用一种颜色,无论如何这只是一个想法,我没有试过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

绘制原始和偏移路径:

enter image description here

由于各种数学原因,这仍然不完美,但已经相当不错了。

答案 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)));

enter image description here

答案 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);

<强>结果

enter image description here

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

来自评论中提到的示例 enter image description here