Skip to content

Integrate SixLabors.PolygonClipper - #364

Merged
JimBobSquarePants merged 38 commits into
SixLabors:mainfrom
stefannikolei:sn/integrate-polygonclipper
Feb 19, 2026
Merged

Integrate SixLabors.PolygonClipper#364
JimBobSquarePants merged 38 commits into
SixLabors:mainfrom
stefannikolei:sn/integrate-polygonclipper

Conversation

@stefannikolei

@stefannikoleistefannikolei commented Sep 30, 2025

Copy link
Copy Markdown
Contributor

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following matches the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

This PR integrates SixLabors.PolygonClipper into ImageSharp.Drawing, replaces the legacy rasterization pipeline with a new scanner-based implementation, and introduces backend/rasterizer abstractions to support future alternate rendering paths (including non-CPU backends).

Main Changes

1. Polygon clipping/stroking integration

  • Replaced in-repo polygon clipping/offsetting implementation with SixLabors.PolygonClipper.
  • Added/updated polygon geometry adapters:
    • PolygonClipperFactory
    • ClippedShapeGenerator
    • StrokedShapeGenerator
  • Clip() now routes through PolygonClipper boolean operations.
  • Stroke generation now uses PolygonClipper PolygonStroker.

2. New stroke semantics and options

  • Added StrokeOptions.NormalizeOutput (default false) to control post-stroke normalization.
  • Updated stroke option model to LineJoin, LineCap, and InnerJoin.
  • Draw pipeline enforces robust behavior for non-normalized stroke output.

3. Rasterization architecture overhaul

  • Replaced legacy scanline internals with a new PolygonScanner-based path.
  • Added rasterizer abstraction:
    • IRasterizer
    • DefaultRasterizer
    • ScanlineRasterizer (explicit sequential/reference path)
    • RasterizerOptions / RasterizationMode
  • Removed old rasterizer files (ActiveEdgeList, ScanEdge, ScanEdgeCollection, old extension wrappers).

4. Drawing backend abstraction

  • Added internal backend boundary:
    • IDrawingBackend
    • CpuDrawingBackend
  • FillPathProcessor<TPixel> now submits operation-level data to backend instead of owning rasterizer details.
  • Backend contains compositing fast paths (including opaque solid run handling).

5. Fill rule and shape behavior updates

  • ShapeOptions.IntersectionRule default changed to NonZero.
  • Clip operation options now use BooleanOperation.
  • Added/update handling so clipped parent/child contours maintain winding relationships compatible with NonZero.

6. Build/test/docs updates

  • Updated package references and solution/build pipeline files.
  • Added/updated rasterizer and processor tests (including new scanner tests).
  • Updated PolygonScanning.MD for the new scanner behavior.
  • Updated many reference images to match new rasterization output.

Breaking / API Surface Changes

  • Removed old enums/types:
    • ClippingOperation (replaced by BooleanOperation)
    • EndCapStyle (replaced by LineCap)
    • JointStyle (replaced by LineJoin)
  • ShapeOptions now uses BooleanOperation + IntersectionRule = NonZero by default.
  • Internal rasterizer selection moved to backend/rasterizer abstractions.

Why this PR

  • Unifies clipping/stroking on a dedicated geometry engine (PolygonClipper).
  • Improves rasterizer throughput and scalability.
  • Establishes cleaner layering (processor -> backend -> rasterizer) for future GPU/custom backend work.
  • Makes stroke normalization optional for performance-sensitive paths while keeping deterministic behavior available when needed.

Benchmarks

The combination of the new stroker and tiled rasterizer makes ImageSharp highly competitive.

This benchmark measures rendering a large, complex polygon with a 10px stroke.
For ImageSharp, results are shown for both rasterizer implementations:

  • ImageSharp...Tiled: the default tiled/parallel rasterizer (optimized for throughput).
  • ImageSharp...ScanlineRasterizer: the single-pass non-tiled scanline rasterizer (useful as a baseline and for comparing scheduling overhead).

ImageSharpStrokeAndClip and FillPolygon are included intentionally:

  • ImageSharpStrokeAndClip isolates stroke geometry generation + clipping cost, independent of full draw compositing.
  • FillPolygon isolates pure polygon fill/rasterization cost without stroke expansion.

Together, these rows help separate where time is spent: geometry generation, clipping/normalization, and final rasterization/compositing.

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.26200
Unknown processor
.NET SDK=10.0.103
[Host] : .NET 8.0.24 (8.0.2426.7010), X64 RyuJIT
Toolchain=InProcessEmitToolchain InvocationCount=1 IterationCount=40
LaunchCount=3 UnrollFactor=1 WarmupCount=15
MethodMeanErrorStdDevMedianRatioRatioSD
SystemDrawing976.6 us13.82 us44.46 us970.5 us0.970.05
ImageSharpCombinedPathsScanlineRasterizer2,808.7 us91.98 us289.31 us2,721.4 us2.760.27
ImageSharpSeparatePathsScanlineRasterizer1,896.4 us313.35 us985.64 us1,246.3 us1.910.98
ImageSharpCombinedPathsTiled902.9 us16.50 us51.66 us908.4 us0.900.05
ImageSharpSeparatePathsTiled905.7 us11.63 us36.75 us902.0 us0.900.04
SkiaSharp1,007.6 us4.24 us13.07 us1,004.7 us1.000.00
ImageSharpStrokeAndClip288.8 us1.20 us3.69 us287.8 us0.290.00
FillPolygon521.5 us14.04 us44.78 us524.9 us0.520.05

@CLAassistant

CLAassistant commented Sep 30, 2025

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@stefannikolei
stefannikoleiforce-pushed the sn/integrate-polygonclipper branch from 0081dff to 2e76417CompareSeptember 30, 2025 18:17
Comment threadsrc/ImageSharp.Drawing/Processing/ShapeOptions.cs Outdated
stefannikoleiand others added 4 commits October 7, 2025 14:20
First draft of implementing PolygonOffsetter
Also added back the IntersectionRule to GenerateClippedShapes. It is not implemented yet
* Initialized clip and subject with empty polygon add a contour per path
Comment threadsrc/ImageSharp.Drawing/Shapes/PolygonClipper/PolygonOffsetter.cs Outdated
@JimBobSquarePants

Copy link
Copy Markdown
Member

@stefannikolei I made some huge progress tonight.

The new stroker ported from AGG is now integrated with path outline generation. I still need to replace the existing Pen end-cap and line-join options to use the new ones as we're adding inner-join and wire them up.

Output looks good. I had to tweak our pattern generating outline code to get things working but it resulted in more accurate stroke length (new is left)

image

@stefannikolei

Copy link
Copy Markdown
ContributorAuthor

What is AGG?

@JimBobSquarePants

JimBobSquarePants commented Oct 31, 2025

Copy link
Copy Markdown
Member

What is AGG?

https://en.wikipedia.org/wiki/Anti-Grain_Geometry

https://aggeom.github.io/doc/introduction/introduction.agdoc.html#toc0002

@JimBobSquarePants
JimBobSquarePants merged commit 46b1688 into SixLabors:mainFeb 19, 2026
12 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

APIarea:performancebreakingdependenciesPull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@stefannikolei@CLAassistant@JimBobSquarePants