From a5169731b3f9532a4c91a9d390c349b0ef571842 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 22 Oct 2021 16:09:13 +0200 Subject: [PATCH 1/3] fix SkipEdgesBeforeMinY algorithm --- .../Shapes/Rasterization/PolygonScanner.cs | 20 +++++++++---- .../Drawing/DrawPathTests.cs | 21 +++++++++++++ .../Shapes/Scan/PolygonScannerTests.cs | 30 ++++++++++++++++++- .../DrawPathTests/DrawPathClippedOnTop.png | 3 ++ 4 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPathClippedOnTop.png diff --git a/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs b/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs index 6e45e765f..c1a89da58 100644 --- a/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs +++ b/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs @@ -139,24 +139,34 @@ 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 for the lines that start before minY. belonging to edge start and endpoints before minY + // Only "scan" at start edge positions (defined by values in sorted0) and end positions (defined by values in sorted1). + // Walk both lists simultaneously following "merge sort" 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) + { + 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 From 45082880a412c70d0f1926acd3b9fd72876ab7ac Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 22 Oct 2021 16:18:46 +0200 Subject: [PATCH 2/3] add more comments --- src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs b/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs index c1a89da58..1c6aea10d 100644 --- a/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs +++ b/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs @@ -153,6 +153,8 @@ private void SkipEdgesBeforeMinY() if (!hasMore0 && !hasMore1) { + // The entire polygon is outside the scan region, we skipped all edges, + // scanning will not find any intersections. break; } From 8bb64072f20d062d39539ecfc6a8ddd82b245aef Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 22 Oct 2021 16:30:01 +0200 Subject: [PATCH 3/3] improve comments --- .../Shapes/Rasterization/PolygonScanner.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs b/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs index 1c6aea10d..0095b9b79 100644 --- a/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs +++ b/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs @@ -139,9 +139,10 @@ private void SkipEdgesBeforeMinY() int i0 = 1; int i1 = 0; - // Do fake scans for the lines that start before minY. belonging to edge start and endpoints before minY - // Only "scan" at start edge positions (defined by values in sorted0) and end positions (defined by values in sorted1). - // Walk both lists simultaneously following "merge sort" logic. + // 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();