PolygonClipper

High-performance polygon boolean operations, contour hierarchy, normalization, and stroke-outline geometry for modern .NET.

PolygonClipper performs polygon boolean operations, contour normalization, hierarchy tracking, and stroke-outline generation, whether you are combining complex regions, resolving self-intersections, generating stroke outlines, or preparing clean geometry for rendering, export, or further processing.

Built for difficult 2D geometry

PolygonClipper is designed for the cases that usually break naive implementations: non-convex shapes, holes, multiple contours, overlapping edges, self-intersections, and geometry that needs to be turned into clean filled regions.

It runs anywhere that supports .NET 8+, giving .NET teams a focused, fully managed geometry library for clipping and outline generation.

dotnet add package SixLabors.PolygonClipper

Boolean operations without geometry pain

Union, intersection, difference, and XOR are the core jobs PolygonClipper is built for. Feed it polygons made of contours and vertices, and get back region-aware output that can include disjoint islands, holes, and parent-child contour hierarchy.

That makes it a strong fit for drawing systems, CAD-style workflows, map or layout tooling, clipping regions, hit testing, geometry cleanup, and any pipeline that needs region operations to be predictable under real inputs.

Returned polygons describe filled regions, not drawing history, so callers can inspect every contour and preserve hole hierarchy when rendering or exporting the result.

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));
    return contour;
}

Polygon subject = new();
subject.Add(Rectangle(0, 0, 80, 60));

Polygon clip = new();
clip.Add(Rectangle(40, 20, 80, 60));

Polygon overlap = PolygonClipper.Intersection(subject, clip);
Polygon merged = PolygonClipper.Union(subject, clip);

Normalize messy geometry

Normalization cleans one polygon by resolving self-intersections and overlaps into canonical positive-winding output. It is the right tool when imported, user-authored, or procedurally generated geometry needs to become a clean region before another system consumes it.

Normalize at import, export, or cache boundaries when you need stable geometry, and expect contour count and hierarchy to change as the library resolves the filled area.

using SixLabors.PolygonClipper;

Contour bowTie = new();
bowTie.Add(new Vertex(0, 0));
bowTie.Add(new Vertex(80, 80));
bowTie.Add(new Vertex(0, 80));
bowTie.Add(new Vertex(80, 0));

Polygon input = new();
input.Add(bowTie);

// Normalize converts self-overlapping edges into clean region geometry.
Polygon normalized = PolygonClipper.Normalize(input);

foreach (Contour contour in normalized)
{
    Console.WriteLine(
        $"Parent={contour.ParentIndex}, Depth={contour.Depth}, Holes={contour.HoleCount}");
}

Stroke outlines for downstream systems

PolygonClipper also turns open or closed path-like input into filled outline geometry. Instead of drawing centerlines directly, PolygonStroker returns the actual region the stroke would cover.

  • Use PolygonStroker.Stroke(...) to turn centerlines into filled outline geometry, with explicit open and closed contour handling.
  • Control outer joins, end caps, miter limits, arc detail, and optional output normalization for renderers and geometry exporters.
  • Inspect ParentIndex, Depth, and HoleCount on returned contours so holes and parent-child relationships stay intact downstream.
  • Use geometry operations before rendering, exporting, or rasterizing when you need the region itself rather than a pixel mask.
  • Work in double-precision coordinates without integer quantization, so transformed and floating-point input round-trips cleanly.
using SixLabors.PolygonClipper;

Contour polyline = new();
polyline.Add(new Vertex(0, 0));
polyline.Add(new Vertex(60, 20));
polyline.Add(new Vertex(120, 0));

Polygon source = new();
source.Add(polyline);

StrokeOptions options = new()
{
    LineJoin = LineJoin.Round,
    LineCap = LineCap.Round,
    MiterLimit = 4,
    ArcDetailScale = 1,
    NormalizeOutput = true
};

Polygon outline = PolygonStroker.Stroke(source, 12, options);