Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IndexOutOfRangeException when drawing line near top when Stroke > 1.5f #108

Closed
4 tasks done
atruskie opened this issue Nov 30, 2020 · 9 comments · Fixed by #119 or #171
Closed
4 tasks done

IndexOutOfRangeException when drawing line near top when Stroke > 1.5f #108

atruskie opened this issue Nov 30, 2020 · 9 comments · Fixed by #119 or #171
Labels
bug Something isn't working
Milestone

Comments

@atruskie
Copy link
Contributor

atruskie commented Nov 30, 2020

Prerequisites

  • I have written a descriptive issue title
  • I have verified that I am running the latest version of ImageSharp.Drawing
  • I have verified if the problem exist in both DEBUG and RELEASE mode
  • I have searched open and closed issues to ensure it has not already been reported

Description

Drawing a horizontal line across the top edge of an image results in an exception if the stroke width is greater than 1.5f and antialiasing is disabled.

Steps to Reproduce

The following test code was run and adapted from

public void DrawingLineAtTopShouldDisplay()
{
using var image = new Image<Rgba32>(Configuration.Default, 100, 100, Color.Black);
image.Mutate(x => x
.SetGraphicsOptions(g => g.Antialias = false)
.DrawLines(
this.red,
1f,
new PointF(0, 0),
new PointF(100, 0)
));
var locations = Enumerable.Range(0, 100).Select(i => (x: i, y: 0));
Assert.All(locations, l =>
{
Assert.Equal(this.red, image[l.x, l.y]);
});
}

        [Fact]
        public void DrawingLineAtTopWith1point5pxStrokeShouldDisplay()
        {
            using var image = new Image<Rgba32>(Configuration.Default, 100, 100, Color.Black);
            image.Mutate(x => x
                    .SetGraphicsOptions(g => g.Antialias = false)
                    .DrawLines(
                        this.red,
                        1.5f,
                        new PointF(0, 0),
                        new PointF(100, 0)
                    ));

            var locations = Enumerable.Range(0, 100).Select(i => (x: i, y: 0));
            Assert.All(locations, l =>
            {
                Assert.Equal(this.red, image[l.x, l.y]);
            });
        }

        [Fact]
        public void DrawingLineAtTopWith2pxStrokeShouldDisplay()
        {
            using var image = new Image<Rgba32>(Configuration.Default, 100, 100, Color.Black);
            image.Mutate(x => x
                    .SetGraphicsOptions(g => g.Antialias = false)
                    .DrawLines(
                        this.red,
                        2f,
                        new PointF(0, 0),
                        new PointF(100, 0)
                    ));

            var locations = Enumerable.Range(0, 100).Select(i => (x: i, y: 0));
            Assert.All(locations, l =>
            {
                Assert.Equal(this.red, image[l.x, l.y]);
            });
        }

        [Fact]
        public void DrawingLineAtTopWith3pxStrokeShouldDisplay()
        {
            using var image = new Image<Rgba32>(Configuration.Default, 100, 100, Color.Black);
            image.Mutate(x => x
                    .SetGraphicsOptions(g => g.Antialias = false)
                    .DrawLines(
                        this.red,
                        3f,
                        new PointF(0, 0),
                        new PointF(100, 0)
                    ));

            var locations = Enumerable.Range(0, 100).Select(i => (x: i, y: 0))
                .Concat(Enumerable.Range(0, 100).Select(i => (x: i, y: 1)));
            Assert.All(locations, l =>
            {
                Assert.Equal(this.red, image[l.x, l.y]);
            });
        }

Then: C:\Work\Github\ImageSharp.Drawing\tests\ImageSharp.Drawing.Tests> dotnet test -c Release --filter "Issue_28"

The existing tests pass, the altered stroke width tests fail:

<snip>
Error Message:
   SixLabors.ImageSharp.ImageProcessingException : An error occurred when processing the image using FillRegionProcessor`1. See the inner exception for more detail.
---- SixLabors.ImageSharp.ImageProcessingException : An error occurred when processing the image using FillRegionProcessor`1. See the inner exception for more detail.
-------- System.IndexOutOfRangeException : Index was outside the bounds of the array.
  Stack Trace:
     at SixLabors.ImageSharp.Processing.Processors.ImageProcessor`1.SixLabors.ImageSharp.Processing.Processors.IImageProcessor<TPixel>.Execute()
   at SixLabors.ImageSharp.Processing.DefaultImageProcessorContext`1.ApplyProcessor(IImageProcessor processor, Rectangle rectangle)
   at SixLabors.ImageSharp.Processing.DefaultImageProcessorContext`1.ApplyProcessor(IImageProcessor processor)
   at SixLabors.ImageSharp.Drawing.Processing.DrawLineExtensions.DrawLines(IImageProcessingContext source, IBrush brush, Single thickness, PointF[] points) in C:\Work\Github\ImageSharp.Drawing\src\ImageSharp.Drawing\Processing\Extensions\DrawLineExtensions.cs:line 43
   at SixLabors.ImageSharp.Drawing.Tests.Issues.Issue_28.<>c__DisplayClass3_0.<DrawingLineAtTopWith2pxStrokeShouldDisplay>b__0(IImageProcessingContext x) in C:\Work\Github\ImageSharp.Drawing\tests\ImageSharp.Drawing.Tests\Issues\Issue_28.cs:line 61
   at SixLabors.ImageSharp.Processing.ProcessingExtensions.Mutate[TPixel](Image`1 source, Configuration configuration, Action`1 operation)
   at SixLabors.ImageSharp.Drawing.Tests.Issues.Issue_28.DrawingLineAtTopWith2pxStrokeShouldDisplay() in C:\Work\Github\ImageSharp.Drawing\tests\ImageSharp.Drawing.Tests\Issues\Issue_28.cs:line 61
----- Inner Stack Trace -----
   at SixLabors.ImageSharp.Processing.Processors.ImageProcessor`1.Apply(ImageFrame`1 source)
   at SixLabors.ImageSharp.Processing.Processors.ImageProcessor`1.SixLabors.ImageSharp.Processing.Processors.IImageProcessor<TPixel>.Execute()
----- Inner Stack Trace -----
   at SixLabors.ImageSharp.Drawing.Shapes.Rasterization.PolygonScanner.SkipEdgesBeforeMinY() in C:\Work\Github\ImageSharp.Drawing\src\ImageSharp.Drawing\Shapes\Rasterization\PolygonScanner.cs:line 149
   at SixLabors.ImageSharp.Drawing.Shapes.Rasterization.PolygonScanner.Init() in C:\Work\Github\ImageSharp.Drawing\src\ImageSharp.Drawing\Shapes\Rasterization\PolygonScanner.cs:line 127
   at SixLabors.ImageSharp.Drawing.Shapes.Rasterization.PolygonScanner.Create(IPath polygon, Int32 minY, Int32 maxY, Int32 subsampling, IntersectionRule intersectionRule, MemoryAllocator allocator) in C:\Work\Github\ImageSharp.Drawing\src\ImageSharp.Drawing\Shapes\Rasterization\PolygonScanner.cs:line 104
   at SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing.FillRegionProcessor`1.OnFrameApply(ImageFrame`1 source) in C:\Work\Github\ImageSharp.Drawing\src\ImageSharp.Drawing\Processing\Processors\Drawing\FillRegionProcessor{TPixel}.cs:line 74
   at SixLabors.ImageSharp.Processing.Processors.ImageProcessor`1.Apply(ImageFrame`1 source)
<snip>
Failed!  - Failed:     3, Passed:     4, Skipped:     0, Total:     7, Duration: 154 ms

System Configuration

  • ImageSharp.Drawing version: master (3d737b5)
  • Other ImageSharp packages and versions:
  • Environment (Operating system, version and so on): Win 10 x64, 20H2
  • .NET Framework version: net5.0 (and ImageSharp defaults for dotnet test command)
  • Additional information:

Seems to be related to the new PolygonScanner SkipEdgesBeforeMinY code.

@antonfirsov antonfirsov added bug Something isn't working and removed needs triage labels Nov 30, 2020
@antonfirsov antonfirsov added this to the 1.0.0-rc.1 milestone Nov 30, 2020
@antonfirsov
Copy link
Member

We are likely dealing with some fallout from #96. (Not unexpected considering the volume of the change.) Need to look into this.

@atruskie
Copy link
Contributor Author

atruskie commented Nov 30, 2020

I just realized the Antialiasing = false part of this issue is misleading. The bug occurs either with ( Antialiasing = true) or without (Antialiasing = false) antialiasing.

@JimBobSquarePants
Copy link
Member

JimBobSquarePants commented Dec 22, 2020

@antonfirsov I can get all the tests to pass including the new ones listed here by adding && i0 < this.edges.Length to the while loop, however I have no idea whether this is a valid fix. (I'm really not confident.)

private void SkipEdgesBeforeMinY()
{
    if (this.edges.Length == 0)
    {
        return;
    }

    this.SubPixelY = this.edges[this.sorted0[0]].Y0;

    int i0 = 1;
    int i1 = 0;

    // Do fake scans for the lines belonging to edge start and endpoints before minY
    while (this.SubPixelY < this.minY && i0 < this.edges.Length)
    {
        this.EnterEdges();
        this.LeaveEdges();
        this.activeEdges.RemoveLeavingEdges();

        float y0 = this.edges[this.sorted0[i0]].Y0;
        float y1 = this.edges[this.sorted1[i1]].Y1;

        if (y0 < y1)
        {
            this.SubPixelY = y0;
            i0++;
        }
        else
        {
            this.SubPixelY = y1;
            i1++;
        }
    }
}

@Robertofon
Copy link

Just to mention. Also happens to me. I have AntiAlias= true
and this happens if I draw an ellipsis with one part outside of the image (eg. R=3 and X,Y =0,3)
or drawing lines (0,0)-(0,30) with line thickness = 2 for example.

@JimBobSquarePants
Copy link
Member

@Robertofon See #108 (comment)

@OleksandrKrutykh
Copy link

Just for your information, the issue can be reproduced even if the thickness of the line is 1. I got an ImageProcessingException when running this piece of code on Windows 10 v1903:

        static void Main()
        {
            Image<Rgba32> image = new Image<Rgba32>(width: 500, height: 400);
            var startPoint = new PointF(493.55447f, -87.83895f);
            var endPoint = new PointF(500.0656f, 174.81201f);
            image.Mutate(x => x.DrawLines(Color.Black, thickness: 1, startPoint, endPoint));
        }

The minimal Visual Studio solution to reproduce the issue: DrawLinesIssue.zip

@antonfirsov
Copy link
Member

antonfirsov commented Jan 21, 2021

This method was developed in rush, after realizing that I need to handle the cases where lines are outside the drawing bounds ... looks like testing was insufficient.

I need more time to debug into that crazy code and understand exactly why is the overflow happening. We should not block on this, so @JimBobSquarePants feel free to raise a PR with your fix, since it seems to make things better. Just let's include tests from both the OP and #108 (comment), and also have a look at the outputs.

@JimBobSquarePants
Copy link
Member

@antonfirsov Looks like my fix is bad. It's offsetting the values not actually processing the virtual edges.

What I'm seeing during debugging given a stroke width of 3.

while (this.SubPixelY < this.minY)
{
    this.EnterEdges();
    this.LeaveEdges();
    this.activeEdges.RemoveLeavingEdges();

    float y0 = this.edges[this.sorted0[i0]].Y0; // Always -1
    float y1 = this.edges[this.sorted1[i1]].Y1; // Always 2

    // Always true
    if (y0 < y1)
    {
        this.SubPixelY = y0;
        i0++;
    }
    else
    {
        this.SubPixelY = y1;
        i1++;
    }
}

@JimBobSquarePants
Copy link
Member

Looks like I have a fix. Will do some final testing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
5 participants