0

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.

Andy Stagg
  • 373
  • 5
  • 22

2 Answers2

0

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.

Idle_Mind
  • 38,363
  • 3
  • 29
  • 40
  • Thanks for the reply - For piece of mind, I plotted the output points from the Flatten() method, and can understand what that's doing now. However, I think I'm confused as to which point I should passing to your methods. Do I need to rotate the path, or can I leave it angled as input by the user- seems like you can leave it rotated? – Andy Stagg Feb 19 '17 at 21:14
  • You can leave it angled, the rotation is irrelevant. – Idle_Mind Feb 19 '17 at 21:18
  • got it, so px and py, are the result from the flattening, and the ax, ay, bx, by are the start and end location for the line. Thank you so much for your help. I really wasn't sure I would get an answer for this. – Andy Stagg Feb 19 '17 at 21:22
  • Exactly. Let me know how it goes. – Idle_Mind Feb 19 '17 at 21:59
  • 1
    It works great! only issue I ran into, was the return in PointToPointDist needed to be cast to float. return (float)Math.Sqrt((bX - aX) * (bX - aX) + (bY - aY) * (bY - aY)); – Andy Stagg Feb 19 '17 at 23:56
0

Here is the resulting code for those in the future:

private void processArray()
{

// this is for demo only - real points should come from mouse input, or otherwise
PointF[] inputArray = new PointF[2];

inputArray[0] = new PointF(537, 147);
inputArray[1] = new PointF(334, 180);
inputArray[2] = new PointF(150, 167);

GraphicsPath gP = new GraphicsPath();
gP.AddCurve(inputArray);

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

//draws original curve
g.DrawPath(new Pen(Color.Blue, 1.75f), gP);

// draw a straight line between the ends of the curve
//g.DrawLine(new Pen(Color.Red, 1.75f), lPoints[0], lPoints[lPoints.Count - 1]);

// create second path to flatten
GraphicsPath pathFlat = gP;
pathFlat.Flatten();

// get list of points to step through
PointF[] fP = pathFlat.PathPoints;

//variables to store max distance
float maxDistance = 0;
PointF maxDistP = new PointF(0, 0);

foreach (PointF p in fP)
{
    // get the distance from the point to the closet point on the line segment
    float curDist = PointToLineDist(p.X, p.Y, lPoints[0].X, lPoints[0].Y, lPoints[lPoints.Count - 1].X, lPoints[lPoints.Count - 1].Y);

    // check the value against and store the longest point
    if (curDist > maxDistance)
    {
        maxDistance = curDist;
        maxDistP = new PointF(p.X, p.Y);
    }
}

// mark a dot at the longest point
g.DrawRectangle(new Pen(Color.Red), new Rectangle(Convert.ToInt32(maxDistP.X), Convert.ToInt32(maxDistP.Y), 2, 2
}

This functionality should all get wrapped up into a class so you can create and edit the graphicpaths and get their properties as necessary.

Andy Stagg
  • 373
  • 5
  • 22