Optimize the performance of a GDI+ function
When profiling my GDI+ project I discovered that the following IsLineVisible
function is one of the "hottest" while the drawing and moving objec开发者_Go百科ts 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
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);
}
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).
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?
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.
精彩评论