diff --git a/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs b/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs index 6e45e765f..0095b9b79 100644 --- a/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs +++ b/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs @@ -139,24 +139,37 @@ private void SkipEdgesBeforeMinY() int i0 = 1; int i1 = 0; - // Do fake scans for the lines belonging to edge start and endpoints before minY + // Do fake scans of the lines that start before minY. + // Instead of fake scanning at every possible subpixel Y location, + // only "scan" at start edge Y positions (defined by values in sorted0) and end Y positions (defined by values in sorted1). + // Walk the two lists simultaneously following mergesort logic. while (this.SubPixelY < this.minY) { this.EnterEdges(); this.LeaveEdges(); this.activeEdges.RemoveLeavingEdges(); - float y0 = this.edges[this.sorted0[i0]].Y0; - float y1 = this.edges[this.sorted1[i1]].Y1; + bool hasMore0 = i0 < this.sorted0.Length; + bool hasMore1 = i1 < this.sorted1.Length; + + if (!hasMore0 && !hasMore1) + { + // The entire polygon is outside the scan region, we skipped all edges, + // scanning will not find any intersections. + break; + } + + float y0 = hasMore0 ? this.edges[this.sorted0[i0]].Y0 : float.MaxValue; + float y1 = hasMore1 ? this.edges[this.sorted1[i1]].Y1 : float.MaxValue; if (y0 < y1) { - this.SubPixelY = y1; + this.SubPixelY = y0; i0++; } else { - this.SubPixelY = y0; + this.SubPixelY = y1; i1++; } } diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/DrawPathTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/DrawPathTests.cs index 0e28dcda9..c12aea9eb 100644 --- a/tests/ImageSharp.Drawing.Tests/Drawing/DrawPathTests.cs +++ b/tests/ImageSharp.Drawing.Tests/Drawing/DrawPathTests.cs @@ -5,6 +5,7 @@ using System.Numerics; using SixLabors.ImageSharp.Drawing.Processing; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using Xunit; namespace SixLabors.ImageSharp.Drawing.Tests.Drawing @@ -74,5 +75,25 @@ public void PathExtendingOffEdgeOfImageShouldNotBeCropped(TestImageProvi appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); } + + [Theory] + [WithSolidFilledImages(40, 40, "White", PixelTypes.Rgba32)] + public void DrawPathClippedOnTop(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + PointF[] points = + { + new PointF(10f, -10f), + new PointF(20f, 20f), + new PointF(30f, -30f) + }; + + IPath path = new PathBuilder().AddLines(points).Build(); + + provider.VerifyOperation( + image => image.Mutate(x => x.Draw(Color.Black, 1, path)), + appendSourceFileOrDescription: false, + appendPixelTypeToFileName: false); + } } } diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/Scan/PolygonScannerTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/Scan/PolygonScannerTests.cs index 07896b84c..b8f580902 100644 --- a/tests/ImageSharp.Drawing.Tests/Shapes/Scan/PolygonScannerTests.cs +++ b/tests/ImageSharp.Drawing.Tests/Shapes/Scan/PolygonScannerTests.cs @@ -427,7 +427,7 @@ public void NegativeOrientation01(IntersectionRule intersectionRule) } [Fact] - public void OutOfBounds() + public void OutOfBounds1() { IPath poly = PolygonFactory.CreatePolygon((1, -5), (5, -5), (5, -3), (10, -1), (10, 2), (12, 4), (1, 4)); @@ -445,6 +445,34 @@ public void OutOfBounds() this.TestScan(poly, 0, 3, 2, expected); } + [Fact] + public void OutOfBounds2() + { + IPath poly = PolygonFactory.CreatePolygon((3, -3), (3, 1), (1, 1), (1, -1), (2, -1.5f), (2, 0.5f), (3, -3)); + FuzzyFloat[][] expected = + { + new FuzzyFloat[] { 1, 2, 2.14285707, 3 }, + new FuzzyFloat[] { 1, 2, 2, 3 }, + new FuzzyFloat[] { 1, 3 } + }; + + this.TestScan(poly, 0, 1, 2, expected); + } + + [Fact] + public void AllOutOfBounds() + { + IPath poly = PolygonFactory.CreatePolygon((1, -3), (3, -3), (2, -1)); + FuzzyFloat[][] expected = + { + Array.Empty(), + Array.Empty(), + Array.Empty(), + }; + + this.TestScan(poly, 0, 1, 2, expected); + } + private static (float Y, FuzzyFloat[] X) Empty(float y) => (y, Array.Empty()); private static FuzzyFloat F(float x, float eps) => new(x, eps); diff --git a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPathClippedOnTop.png b/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPathClippedOnTop.png new file mode 100644 index 000000000..1da2d573e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPathClippedOnTop.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:669d544b9bbef2eb694d4ffcb79b425fa0e2f84f06470b25b11b554fac55982b +size 421