2

When profiling my GDI+ project I discovered that the following IsLineVisible function is one of the "hottest" while the drawing and moving objects on my custom panel.

Is there a possibility to optimize it?

  Private Function IsLineVisible(ByVal detectorRectangle As Rectangle, 
                                 ByVal pen As Pen, 
                                 ByVal ParamArray points() As Point) As Boolean
    Using path As New GraphicsPath()
      path.AddLines(points)
      Return IsPathVisible(detectorRectangle, path, pen)
    End Using
  End Function

  ' Helper functions '''''''''''''''''''''''''''''''''''''
  Private Function IsPathVisible(ByVal detectorRectangle As Rectangle, 
                                 ByVal path As GraphicsPath, 
                                 ByVal pen As Pen) As Boolean
    If Not path.IsPoint Then
      path.Widen(pen)
    End If
    Return IsPathVisible(detectorRectangle, path)
  End Function


  Private Function IsPathVisible(ByVal detectorRectangle As Rectangle, 
                                 ByVal path As GraphicsPath) As Boolean
    Using r As New Region(path)
      If r.IsVisible(detectorRectangle) Then
        Return True
      Else
        Return False
      End If
    End Using
  End Function

enter image description here

serhio
  • 28,010
  • 62
  • 221
  • 374

4 Answers4

5

UPDATE 2:

    public bool AreLinesVisible(Point[] p, int width, Rectangle rect)
    {
        for (var i = 1; i < p.Length; i++)
            if (IsLineVisible(p[i - 1], p[i], width, rect))
                return true;
        return false;
    }

UPDATED to include thickness/width.

This is completely untested code, but it should give you the basic idea for a hyper-fast solution with no expensive framwork calls:

public bool IsLineVisible(Point p1, Point p2, int width, Rectangle rect)
{
    var a = Math.Atan2(p1.Y - p2.Y, p1.X - p2.X) + Math.PI/2;
    var whalf = (width + 1)*0.5;
    var dx = (int) Math.Round(whalf*Math.Sin(a));
    var dy = (int) Math.Round(whalf*Math.Cos(a));
    return IsLineVisible( new Point(p1.X - dx, p1.Y - dy), new Point(p2.X - dx, p2.Y - dy), rect)
        || IsLineVisible( new Point(p1.X + dx, p1.Y + dy), new Point(p2.X + dx, p2.Y + dy), rect);
}

public bool IsLineVisible(Point p1, Point p2, Rectangle rect)
{
    if (p1.X > p2.X)  // make sure p1 is the leftmost point
        return IsLineVisible(p2, p1, rect);

    if (rect.Contains(p1) || rect.Contains(p2))
        return true; // one or both end-points within the rect -> line is visible

    //if both points are simultaneously left or right or above or below -> line is NOT visible
    if (p1.X < rect.X && p2.X < rect.X)
        return false;
    if (p1.X >= rect.Right && p2.X >= rect.Right)
        return false;
    if (p1.Y < rect.Y && p2.Y < rect.Y)
        return false;
    if (p1.Y >= rect.Bottom && p2.Y >= rect.Bottom)
        return false;

    // now recursivley break down the line in two part and see what happens
    // (this is an approximation...)
    var pMiddle = new Point((p1.X + p2.X)/2, (p1.Y + p2.Y)/2);
    return IsLineVisible(p1, new Point(pMiddle.X - 1, pMiddle.Y), rect)
           || IsLineVisible(new Point(pMiddle.X + 1, pMiddle.Y), p2, rect);
}
Dan Byström
  • 9,067
  • 5
  • 38
  • 68
  • Yes, but i said the basic idea - you just have to calculate the two contour lines that makes up your thick line and pass them both. If one of them is visible - then your thick line is visible! – Dan Byström Oct 11 '11 at 17:26
  • Is a dashed line any more or less visible within a rectangle??? Are you after the exact pixel overlap when the dashed line just touch the corner of the rectangle? – Dan Byström Oct 11 '11 at 19:45
  • ok, I can admit to leave the style restriction... but my method accepted an array of points, not just 2... ) – serhio Oct 12 '11 at 09:50
  • What?? I didn't realize this was a "plz send me the codes" question. I just showed you how you could get your code running several 100 times faster and you can't even bother to write the simplest loop. :-( – Dan Byström Oct 12 '11 at 17:52
  • Why not? It compiles just fine in my Visual Studio! – Dan Byström Oct 13 '11 at 09:14
  • you pass double (when /2) when should pass integer. How can it compile? – serhio Oct 13 '11 at 13:43
  • 1
    C# and VB.Net differs here. An integer divided by an integer doesn't result in a double in C#. In VB.Net I guess you could use "\" for integer division. You could in plain old VB, at least. – Dan Byström Oct 19 '11 at 08:36
  • I should recognize, the method is not perfect. When a pen Width is > 1 then it should be tested intersection with a *rectangle* formed by the line and its width and the decetorrectangle from parameter. – serhio Oct 21 '11 at 20:11
  • That would be a good interpretation of "This is completely untested code, but it should give you the basic idea for a hyper-fast solution with no expensive framwork calls". :-) – Dan Byström Oct 22 '11 at 08:01
0

The only thing I can see is perhaps using a wider/thicker Pen.

This will let the method recurse less and cut down the calls to Widen without losing too much of the effect (I hope on the last one).

leppie
  • 115,091
  • 17
  • 196
  • 297
  • I cand use other pen that I pass as argument... this is all the point. I hae some wide lines, other thick lines, so I should detect different types of line... – serhio Oct 11 '11 at 16:26
0

Instead of creating a path, which is a very expensive GDI construct, how about looping through your points, connecting that point with the previous point, and checking to see if that line intersects with your rectangle?

It should be less computationally expensive, with the bonus of being able to stop the loop on the first segment to intersect the rectangle.

This other post should help with the intersection test. How to find the intersection point between a line and a rectangle?

Community
  • 1
  • 1
Bradley Uffner
  • 16,641
  • 3
  • 39
  • 76
0

There is no need to create a Region; GraphicsPath.IsVisible can be used instead. I would widen the GraphicsPath and cache it to be reused, per object that needs hit testing.

Frank Hileman
  • 1,159
  • 9
  • 19