Search code examples
c#graphicspath

C# longest distance from a straight line to a user input graphicpath


I have a GraphicsPath defined by a PointF array (created by mouse clicks), which is drawn by Graphics.DrawCurve. The Path is then Transformed to rotate the start and end points to 0 degrees. The transformed and rotated curve (and the original curve) PointF arrays are still available.

I need to find the longest perpendicular distance from the path's straight line end points to the curve itself.

transformed output example

Being that I've rotated the curve, finding the longest distance should be the same as the height of the bounds... But I also need to know what distance along (what is now) the 0 axis is (which will be easy if I can find the curve). I've searched far and wide- I've tried custom spline functions to try and get more fixed points along the curve, but this ultimately ended with the same result- I don't know what the curve is between the points, and that's 90% likely to be the case given the interpolation between the points.

Now that it's rotated, I've tried a for loop to count across the top, and in each column, tried to see if each point down IsVisible against the transformed path, but it always returns false.

Input points (for ref): {X=499,Y=64} {X=305,Y=117} {X=149,Y=114}

(From left to right, I know I need to do some checking to see which way they are entered in the future, but for now, just trying to make it work)

    GraphicsPath gP = new GraphicsPath();
gP.AddCurve(lPoints.ToArray());

Graphics g = pbImage.CreateGraphics();
g.SmoothingMode = SmoothingMode.AntiAlias;

//draws original curve (hiding for now to validate and visualize rotation:
//g.DrawPath(new Pen(Color.Blue, 1.75f), gP);
//g.DrawLine(new Pen(Color.Red, 1.75f), lPoints[0], lPoints[lPoints.Count - 1]);

// get angle in radians
double theta2 = Math.Atan2(lPoints[0].Y - lPoints[lPoints.Count - 1].Y, lPoints[0].X - lPoints[lPoints.Count - 1].X);

// convert radians to degrees
double angle = -1 * (theta2 * (180.0 / Math.PI));

//set a new path to the old one, to keep both sets of data
GraphicsPath newPath = gP;

//create rotational matrix, and transform
Matrix m = new Matrix();
m.RotateAt(Convert.ToSingle(angle), lPoints[0]);
newPath.Transform(m);

//draw transformed path to picture box
g.DrawPath(new Pen(Color.Green, 1f), newPath);

//get new points from transformed curve (interestingly enough, it adds more points than the oringial input)
PointF[] tP = newPath.PathPoints;

//create some temp variables to make the next section easier
PointF pS = tP[0];
PointF pE = tP[tP.Length - 1];

//draw the straight line for visual validation
g.DrawLine(new Pen(Color.Red, 1f), pS, pE);

//get the bounds of the new path
RectangleF bRect = newPath.GetBounds();
a
// loop through x's
for (float i = pE.X; i < pS.X; i = i + 5)
{
    float h = pS.Y - bRect.Y;
    bool bFound = false;

    // loop through y's - do loop until found
    do
    {
        if (newPath.IsVisible(i, h, g))
        {
            // never found
            tbOutPt.Text = tbOutPt.Text + "found!!!!";
            bFound = true;
        }
        h++;
    } while (bFound = false && h < bRect.Height);

}

The only other thing I can think of, would be to create a new bitmap based on the bounds, redraw the curve to it, and go column by column and get the pixel color until it matches the color the curve was drawn in.

Hoping someone has a better solution than that.


Solution

  • Flatten() the GraphicsPath, then walk the resulting points and find the largest distance using the standard "Point to Line Distance" measurement:

    private float PointToLineDist(float Px, float Py, float Ax, float Ay, float Bx, float By)
    {
        float q = 0;
        if ((Ax == Bx) & (Ay == By)) {
            // A and B passed in define a point, not a line.
            // Point to Point Distance
            return PointToPointDist(Px, Py, Ax, Ay);
        } else {
            // Distance is the length of the line needed to connect the point to
            // the(segment)such that the two lines would be perpendicular.
    
            // q is the parameterized value needed to get to the intersection
            q = ((Px - Ax) * (Bx - Ax) + (Py - Ay) * (By - Ay)) / ((Bx - Ax) * (Bx - Ax) + (By - Ay) * (By - Ay));
    
            // Limit q to 0 <= q <= 1
            // If q is outside this range then the Point is somewhere past the 
            // endpoints of our segment.  By setting q = 0 or q = 1 we are 
            // measuring the actual distacne from the point to one of the 
            // endpoints(instead)
            if (q < 0)
                q = 0;
            if (q > 1)
                q = 1;
    
            // Distance
            return PointToPointDist(Px, Py, (1 - q) * Ax + q * Bx, (1 - q) * Ay + q * By);
        }
    }
    
    private float PointToPointDist(float Ax, float Ay, float Bx, float By)
    {
        // PointToPointDist = SquareRoot((Bx - Ax)^2 + (By - Ay)^2)
        return Math.Sqrt((Bx - Ax) * (Bx - Ax) + (By - Ay) * (By - Ay));
    }
    

    In PointToLineDist(), (Px, Py) is the point in question, and (Ax, Ay), (Bx, By) are the endpoints of the line segment.