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):
private GraphicsPath UnionAll(GraphicsPath gp)
{
var m1 = new Matrix();
m1.Scale(100, 100);
gp.Flatten(m1, 0.01f);
var bounds = gp.GetBounds();
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);
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);
}
var solution = new List<ExPolygon>();
var result = c.Execute(ClipType.ctDifference,
solution, PolyFillType.pftPositive, PolyFillType.pftPositive);
if (!result) throw new Exception("Clipper.Execute failed");
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