Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

GraphicsPath Outline in C# (Alternative to GdipWindingModeOutline)

5.00/5 (1 vote)
19 Jul 2013CPOL 13.2K  
GraphicsPath outline in C# (alternative to GdipWindingModeOutline)

Introduction

I was recently working on an interesting exercise when I had to manipulate complex polygons. On the input, I had a collection of text blocks in different fonts and sizes manually positioned over a rectangular region. I had to produce an outline of all the chars, something that looks like this:

The first step was to convert text to paths, then try to get outline of the path. I initially used GdipWindingModeOutline function, but for some configurations (about 50% of the time actually), it failed with "GenericError". I was not able to detect so I had to come up with a custom solution.

There is a very nice open source polygon clipping library for C#. I came up with the following function (simplified):

C#
private GraphicsPath UnionAll(GraphicsPath gp)
{
    var m1 = new Matrix();
    m1.Scale(100, 100);

    gp.Flatten(m1, 0.01f);
    var bounds = gp.GetBounds();

    // add background rectangle

    var c = new Clipper();
    var rect = new List<IntPoint>
                    {
                        new IntPoint((int)bounds.Left - 10, (int)bounds.Top - 10),
                        new IntPoint((int)bounds.Right + 10, (int)bounds.Top - 10),
                        new IntPoint((int)bounds.Right + 10, (int)bounds.Bottom + 10),
                        new IntPoint((int)bounds.Left - 10, (int)bounds.Bottom + 10)
                    };

    c.AddPolygon(rect, PolyType.ptSubject);

    // add all polygons

    var iter = new GraphicsPathIterator(gp);

    while (true)
    {
        var subPath = new GraphicsPath();
        bool isClosed;

        if (iter.NextSubpath(subPath, out isClosed) == 0) break;

        var poly = subPath.PathPoints.Select
        (x => new IntPoint((int) x.X, (int) x.Y)).ToList();

        c.AddPolygon(poly, PolyType.ptClip);
    }

    // execute clipping

    var solution = new List<ExPolygon>();
    var result = c.Execute(ClipType.ctDifference, 
    solution, PolyFillType.pftPositive, PolyFillType.pftPositive);
    if (!result) throw new Exception("Clipper.Execute failed");

    // convert back to GraphicsPath

    var retval = new GraphicsPath();

    foreach (var s in solution)
    {
        retval.AddPolygon(s.outer.Select(x => new PointF(x.X, x.Y)).ToArray());

        foreach (var h in s.holes)
        {
            retval.AddPolygon(h.Select(x => new PointF(x.X, x.Y)).ToArray());
        }
    }

    var m2 = new Matrix();
    m2.Scale(0.01f, 0.01f);

    retval.Transform(m2);
    return retval;
}

As you can see, it:

  • scales path by 100 (to fix integer rounding)
  • adds base rectangle polygon
  • converts path into collection of polygons
  • executes the difference
  • converts polygons back into path
  • scales path back to original size

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)