Подтвердить что ты не робот

Рисование пути, окружающего данный путь

* Обновление *

Найден решение с использованием библиотеки Clipper. Решение добавлено в качестве ответа. Новые/лучшие/более простые идеи по-прежнему приветствуются, хотя!


Учитывая такой путь:

оригинальный черный путь

Я хочу создать путь, окружающий этот путь, с заданным расстоянием, например. 1 см. Следующий рисунок показывает, что красный путь окружает черную дорожку с расстоянием 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);
}

Спасибо за любую помощь по этой теме!

4b9b3361

Ответ 1

Вы можете использовать функцию Widen(), которая заменяет путь кривыми, которые заключают область, которая заполняется, когда путь рисован указанное перо, добавив дополнительный контур к пути.

Эта функция получает как параметр a XPen, поэтому вы можете создать этот XPen, используя желаемое смещение как ширину, и внешний путь будет добавлен с постоянным расстоянием (ширина пера).

Класс

XGraphicsPath на самом деле является оберткой System.Drawing.Drawing2D.GraphicsPath, поэтому вы можете использовать функцию Widen() в XGraphicsPath, получить внутренний объект и выполнить итерацию с помощью класса 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);

Это то, что вы получаете:

введите описание изображения здесь

Другим способом может быть использование отражения для извлечения внутреннего Pen в XPen и настройка свойства CompoundArray. Это позволяет рисовать параллельные линии и пробелы. Используя это свойство, вы можете сделать что-то вроде этого:

введите описание изображения здесь

Но проблема в том, что вы можете использовать только один цвет, так или иначе, это просто идея, я не пробовал в PDFsharp

Кроме того, вы должны искать алгоритмы offset polyline curve или смещения полигона.

Ответ 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

Нарисованный путь оригинала и смещения:

введите описание изображения здесь

Это все еще не идеально подходит для различных математических причин, но уже неплохо.

Ответ 3

  • Это запрошенный ответ.

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 surrounded point is even higher, if it below O, it 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)));

введите описание изображения здесь

Ответ 4

Что делать, если мы сделали расширение DrawOutline для 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);

Результат

введите описание изображения здесь

Ответ 5

Клипер - отличный выбор, но в зависимости от ваших потребностей он не приведет к идеальному офсету. смещение от края не равно смещению от угла Лучшее решение, которое потребует от вас удалить любые кривые из бисера и использовать только примитивы линий, использует библиотеку 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/С#. Он включает расширение.

Это даст вам подход с ограниченным разрешением, но конечный результат будет точным для разрешения растрового изображения (и на самом деле намного выше, поскольку вы используете смещение контура, которое на самом деле ограничивает детали, связанные с контуром).

Ответ 6

попробуйте следующее:

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

Из упомянутого примера в комментариях введите описание изображения здесь