serhio
serhio

Reputation: 28586

GraphicsPath and OutOfMemoryException

I have the following

private bool IsPathVisible(Rectangle detectorRectangle, GraphicsPath path, Pen pen)
{
    path.Widen(pen);
    return IsPathVisible(detectorRectangle, path);
}

When path points are the same point, I receive a OutOfMemoryException (using Widen function).

How can I manage it?

Upvotes: 3

Views: 1323

Answers (4)

artur.mazgarov
artur.mazgarov

Reputation: 189

I've also been suffering from this exception. Recommendations are as following:

  1. Save the points before widening to see exact points that cause OutOfMemoryException:

    private bool IsPathVisible(Rectangle detectorRectangle, GraphicsPath path, Pen pen)
    {
        var points = path.PathPoints.Clone() as PointF[];
        path.Widen(pen);
        return IsPathVisible(detectorRectangle, path);
    }
    

What you might see is that there are probably consequent points that have the same coordinates. They are actually causing the problem.

  1. Also, GraphicsPath can consist of multiple subpaths. To make a reliable hit testing, I would recommend the following:

    public Region[] CreateRegionFromGraphicsPath(GraphicsPath path, Pen wideningPen)
    {
        var regions = new List<Region>();
    
        var itPath = new GraphicsPathIterator(path);
        itPath.Rewind();
        var curSubPath = new GraphicsPath();
    
        for (int i = 0; i < itPath.SubpathCount; i++)
        {
            bool isClosed;
            itPath.NextSubpath(curSubPath, out isClosed);
    
            if (!isClosed && CanWiden(curSubPath)) curSubPath.Widen(wideningPen); // widen not closed paths
    
            int regionIndex = i / 100; // max region scan rectangles count
    
            if (regions.Count < regionIndex + 1)
            {
                regions.Add(new Region(curSubPath));
            }
            else
            {
                regions[regionIndex].Union(curSubPath);
            }
        }
    
        curSubPath.Dispose();
        itPath.Dispose();
    
        return regions.ToArray();
    }
    
    /// <summary>
    /// Determines whether widening this graphics path will not lead to an exception
    /// </summary>
    public static bool CanWiden(GraphicsPath gp)
    {
        const float regionPointsTolerance = 1e-8f;
        var pts = gp.PathPoints;
        if (pts.Length < 2) return false;
        for (int i = 1; i < pts.Length; i++)
        {
            if (Math.Abs(pts[i-1].X - pts[i].X) < regionPointsTolerance && Math.Abs(pts[i-1].Y - pts[i].Y) < regionPointsTolerance) return false;
        }
        return true;
    }
    

Then you simply call IsVisible for regions to find if any of them is hit

Upvotes: 1

Andrey K.
Andrey K.

Reputation: 91

The following code causes OutOfMemory in DrawPath in .Net 4.0 (and possibly higher). I was managed to bypass it using LineCap.Flat instead of LineCap.NoAnchor:

public void TestDrawPath()
{
    PointF[] points = new PointF[13]
    {
        new PointF(0.491141558f, 1.53909028f),
        new PointF(0.491141558f, 1.55148673f),
        new PointF(0.4808829f, 1.56153619f),
        new PointF(0.468228281f, 1.56153619f),
        new PointF(0.4555736f, 1.56153619f),
        new PointF(0.445314974f, 1.55148673f),
        new PointF(0.445314974f, 1.53909028f),
        new PointF(0.445314974f, 1.52669382f),
        new PointF(0.4555736f, 1.51664436f),
        new PointF(0.468228281f, 1.51664436f),
        new PointF(0.4808829f, 1.51664436f),
        new PointF(0.491141558f, 1.52669382f),
        new PointF(0.491141558f, 1.53909028f)
    };
    byte[] types = new byte[13] { 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 131 };

    using (Bitmap bitmap = new Bitmap(2, 2))
    using (Graphics g = Graphics.FromImage(bitmap))
    {
        using (Pen pen = new Pen(Color.Black))
        using (GraphicsPath path = new GraphicsPath(points, types))
        {
            pen.StartCap = LineCap.NoAnchor;
            pen.EndCap = LineCap.NoAnchor;
            g.DrawPath(pen, path);
        }
    }
}

Upvotes: 0

serhio
serhio

Reputation: 28586

if the path IsPoint, don't do Widen.

<System.Runtime.CompilerServices.Extension()> _
Public Function IsPoint(ByVal path As System.Drawing.Drawing2D.GraphicsPath) As Boolean
    If path Is Nothing Then Throw New ArgumentNullException("path")

    If path.PathPoints.Count < 2 Then Return True

    If path.PathPoints(0) <> path.PathPoints(path.PathPoints.Count - 1) Then Return False

    For i = 1 To path.PathPoints.Count - 1
     If path.PathPoints(i - 1) <> path.PathPoints(i) Then Return False
    Next i

    ' if all the points are the same
    Return True
End Function 

Upvotes: 0

LarsTech
LarsTech

Reputation: 81675

That's a bug with the pen and the widen method. Make sure your startpoint of the path and the endpoint of the path are not the same.

This is a demonstration:

private void panel1_Paint(object sender, PaintEventArgs e)
{
  //This works:
  using (GraphicsPath path = new GraphicsPath())
  {
    path.AddLine(new Point(16, 16), new Point(20, 20));
    path.Widen(Pens.Black);
    e.Graphics.DrawPath(Pens.Black, path);
  }

  //This does not:
  using (GraphicsPath path = new GraphicsPath())
  {
    path.AddLine(new Point(20, 20), new Point(20, 20));
    path.Widen(Pens.Black);
    e.Graphics.DrawPath(Pens.Black, path);
  }
}

Here is where it was reported to Microsoft: GraphicsPath.Widen throw OutOfMemoryException if the path has a single point

Upvotes: 6

Related Questions