// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.PolygonClipper; using System.Collections.Generic; using PCPolygon = SixLabors.PolygonClipper.Polygon; using PolygonClipperAction = SixLabors.PolygonClipper.PolygonClipper; namespace SixLabors.ImageSharp.Drawing.PolygonGeometry { /// /// Generates clipped shapes from one or more input paths using polygon boolean operations. /// /// /// This class provides a high-level wrapper around the low-level . /// It accumulates subject and clip polygons, applies the specified , /// and converts the resulting polygon contours back into instances suitable /// for rendering or further processing. /// internal static class ClippedShapeGenerator { /// /// Generates the final clipped shapes from the previously provided subject and clip paths. /// /// /// The boolean operation to perform, such as , /// , or . /// /// The subject path. /// The clipping paths. /// /// The representing the result of the boolean operation. /// public static ComplexPolygon GenerateClippedShapes( BooleanOperation operation, IPath subject, IEnumerable clip) { Guard.NotNull(subject); Guard.NotNull(clip); PCPolygon s = PolygonClipperFactory.FromSimpleClosedPaths(subject.Flatten()); PCPolygon c = PolygonClipperFactory.FromClosedPaths(clip); PCPolygon result = operation switch { BooleanOperation.Xor => PolygonClipperAction.Xor(s, c), BooleanOperation.Difference => PolygonClipperAction.Difference(s, c), BooleanOperation.Union => PolygonClipperAction.Union(s, c), _ => PolygonClipperAction.Intersection(s, c), }; IPath[] shapes = new IPath[result.Count]; int index = 0; for (int i = 0; i < result.Count; i++) { shapes[index++] = new Polygon(CreateContourPoints(result, i)); } return new(shapes); } /// /// Converts a PolygonClipper contour to ImageSharp points and normalizes winding for parent/child rings. /// /// The polygon containing the contour hierarchy. /// The contour index to convert. /// The converted point array. private static PointF[] CreateContourPoints(PCPolygon polygon, int contourIndex) { Contour contour = polygon[contourIndex]; PointF[] points = new PointF[contour.Count]; bool reverse = ShouldReverseForNonZeroWinding(polygon, contourIndex); if (!reverse) { for (int i = 0; i < contour.Count; i++) { Vertex vertex = contour[i]; points[i] = new PointF((float)vertex.X, (float)vertex.Y); } return points; } for (int sourceIndex = contour.Count - 1, targetIndex = 0; sourceIndex >= 0; sourceIndex--, targetIndex++) { Vertex vertex = contour[sourceIndex]; points[targetIndex] = new PointF((float)vertex.X, (float)vertex.Y); } return points; } /// /// Ensures child contours (holes/islands) use opposite winding to their direct parent. /// This keeps clipped output deterministic when consumed with the NonZero fill rule. /// /// The polygon containing contour hierarchy information. /// The contour index to inspect. /// when the contour should be reversed. private static bool ShouldReverseForNonZeroWinding(PCPolygon polygon, int contourIndex) { Contour contour = polygon[contourIndex]; if (contour.ParentIndex is not int parentIndex || (uint)parentIndex >= (uint)polygon.Count) { return false; } Contour parentContour = polygon[parentIndex]; return contour.IsCounterClockwise() == parentContour.IsCounterClockwise(); } } }