Today we are announcing PolygonClipper 1.0.0, a new Six Labors geometry library for polygon boolean operations, contour normalization, and stroke-outline generation in managed .NET.
PolygonClipper started as the lower-level geometry engine needed by ImageSharp.Drawing. Drawing systems do not only see neat rectangles and simple convex shapes. They see imported paths, overlapping contours, holes, multiple disjoint regions, self-intersections, vertical edges, and strokes that need to become filled geometry before they can be rasterized. We needed a focused library that could handle that kind of input without pushing native dependencies or a large object model onto callers.
With 1.0.0, that geometry layer is now available as its own package. ImageSharp.Drawing 3 uses PolygonClipper internally for boolean path work and stroke generation, but applications that need the geometry layer directly can now use it without taking a dependency on the higher-level drawing stack.
What's in PolygonClipper 1 #
- Polygon boolean operations for
Intersection,Union,Difference, andXor. - A dedicated normalization path for resolving self-intersections and overlaps into positive-winding output.
- Stroke-outline generation through
PolygonStroker. - Support for non-convex polygons, holes, multiple disjoint regions, overlapping edges, and vertical segments.
- Double-precision geometry without an integer-grid conversion step.
- A small public model built around
Vertex,Contour,Polygon,PolygonClipper, andPolygonStroker. - Static entry points for common clipping, normalization, and stroking operations.
The package targets .NET 8+ and is licensed under the Six Labors Split License. Projects that directly depend on SixLabors.PolygonClipper must provide a valid Six Labors license at build time, following the same model described in our recent license enforcement changes.
A Small Geometry Model #
The public model is intentionally small:
Vertexis one 2D point.Contouris one ring of vertices.Polygonis a collection of contours.
PolygonClipper works with regions, not drawing commands. A returned polygon describes the filled area produced by an operation, so it may contain new contours, fewer contours, holes, or multiple disjoint islands. That is expected behavior for clipping and normalization work.
Coordinates are unitless doubles. Your application decides whether a vertex represents pixels, points, millimeters, tiles, or some world coordinate system. PolygonClipper keeps those coordinates in the same space; there is no coordinate quantization step before the algorithm runs.
Boolean Operations #
At a high level, clipping means taking two input regions, usually called the subject and the clip, and combining them according to one of four rules:
Unionkeeps anything covered by either shape.Intersectionkeeps only the shared overlap.Differencesubtracts the clip shape from the subject shape.Xorkeeps the non-overlapping parts and removes the shared middle.
Difference is the one operation where argument order matters most. Difference(a, b) is not the same as Difference(b, a).
Here are the four operations using a star and a circle:
Union
Intersection
Difference
Xor
In code, the common entry points look like this:
using SixLabors.PolygonClipper;
Polygon merged = PolygonClipper.Union(subject, clip);
Polygon overlap = PolygonClipper.Intersection(subject, clip);
Polygon remaining = PolygonClipper.Difference(subject, clip); // The clip polygon is subtracted from the subject.
Polygon exclusive = PolygonClipper.Xor(subject, clip);
The boolean core uses a MartÃnez-Rueda-style sweep-line pipeline for complex polygon clipping. One of the key references behind the implementation is A Simple Algorithm for Boolean Operations on Polygons. You do not need to understand the paper to use the API, but it helps explain why the library is comfortable with holes, multiple contours, non-convex input, overlapping edges, and vertical segment relationships.
Most applications should use the static methods shown above. They keep the public surface small and make the common operations easy to spot at call sites.
Normalization #
Boolean operations combine two regions. Normalization solves a different problem: it cleans up one polygon by resolving self-intersections and overlaps into a canonical positive-winding result.
That makes PolygonClipper.Normalize(...) useful when geometry has come from a drawing tool, importer, user edit, or any source that can produce self-overlapping contour data. It is not a visual simplifier and it is not a general-purpose path optimizer. It converts messy regional input into polygon output that downstream geometry code can reason about more consistently.
using SixLabors.PolygonClipper;
Polygon clean = PolygonClipper.Normalize(input); // Resolves self-overlap into positive-winding output.
Normalization has its own implementation path, inspired by Vatti/Clipper2-style cleanup. Keeping it separate from two-input boolean operations lets callers decide when they want to pay for canonical output. In many applications that is an import, export, cache, or rendering boundary rather than every single edit.
Stroking #
PolygonClipper also includes PolygonStroker, which turns path-like input into filled outline geometry. Instead of returning a centerline plus styling information, the stroker returns the polygon region covered by the stroke.
That is useful for renderers, exporters, hit-testing systems, and geometry pipelines that want the stroke as actual filled geometry.
using SixLabors.PolygonClipper;
StrokeOptions options = new()
{
LineJoin = LineJoin.Round,
LineCap = LineCap.Round,
MiterLimit = 4,
ArcDetailScale = 1,
NormalizeOutput = true // Ask the stroker to clean up overlaps before returning.
};
Polygon outline = PolygonStroker.Stroke(source, 12, options);
Stroke options control joins, caps, miter limits, round-arc detail, and whether the generated outline should be normalized before returning. NormalizeOutput is explicit because not every caller needs that cleanup step. When it is disabled, callers should render the result with a non-zero winding fill rule.
For stroking, input contours can behave as open polylines or closed loops. If the last vertex returns to the first vertex, the stroker treats the contour as closed and does not emit end caps. Otherwise, it treats the contour as open and emits caps according to the selected LineCap.
A Complete First Operation #
The basic flow is to create contours, add them to polygons, and run the operation you need:
using SixLabors.PolygonClipper;
static Contour Rectangle(double x, double y, double width, double height)
{
Contour contour = new(4);
contour.Add(new Vertex(x, y));
contour.Add(new Vertex(x + width, y));
contour.Add(new Vertex(x + width, y + height));
contour.Add(new Vertex(x, y + height));
// Contours are implicitly closed for polygon operations.
return contour;
}
Polygon subject = new();
subject.Add(Rectangle(0, 0, 80, 60));
Polygon clip = new();
clip.Add(Rectangle(40, 20, 80, 60));
Polygon result = PolygonClipper.Intersection(subject, clip);
Contours used for normal polygon operations are implicitly closed, so there is no need to repeat the first vertex at the end of the contour.
Returned polygons can contain more than one contour, including holes. Production code should inspect the returned polygon instead of assuming a single-contour result:
using System;
using SixLabors.PolygonClipper;
for (int i = 0; i < result.Count; i++)
{
Contour contour = result[i];
// Results can contain holes and nested contours, so inspect every returned contour.
Console.WriteLine($"Contour {i}: Count={contour.Count}");
Console.WriteLine($"Parent={contour.ParentIndex}, Depth={contour.Depth}, Holes={contour.HoleCount}");
}
That hierarchy metadata is important when exporting to formats or renderers that distinguish exterior rings from holes.
Where It Fits #
PolygonClipper is useful anywhere you need the region itself rather than a raster mask or a high-level drawing command. That includes map work, diagramming, CAD-adjacent processing, export pipelines, hit regions, tool-path preparation, vector cleanup, and generated graphics.
The most important split is simple:
- Use boolean operations when combining two regions.
- Use normalization when cleaning one messy region.
- Use stroking when a path or polyline needs to become filled outline geometry.
The PolygonClipper documentation covers getting started, contour hierarchy, boolean operations, normalization, and stroking in more detail.
Operational Notes #
This is a new library, so there is no previous stable major version to migrate from. The baseline target is .NET 8+.
PolygonClipper 1.0 uses the Six Labors Split License, and direct package dependencies must provide a sixlabors.lic file or SixLaborsLicenseKey MSBuild property at build time. Enforcement applies to direct dependencies only. If you qualify for community licensing, you can apply via licensing.sixlabors.com.
Closing #
PolygonClipper 1.0 gives .NET developers a focused managed geometry library for boolean operations, self-intersection cleanup, and stroke-outline generation. It uses double-precision coordinates, keeps the public API small, and brings the geometry layer behind ImageSharp.Drawing 3 to applications that need it directly.
PolygonClipper 1.0.0 is licensed under the Six Labors Split License. This means that it is free to use in both non-commercial and certain commercial applications. However, if you are using it in a commercial application and your organization has annual gross revenue exceeding 1M USD per year, you must purchase a commercial license. Please see the Pricing page for more information.
- Previous: Announcing ImageSharp.Web 4.0.0