none
Graphics Path - not right RRS feed

  • Question

  • I'm making a jigsaw puzzle-er app that takes a bitmap, chops it into regular sized rectangles then places to adjacent pieces together on a temp bitmap to add/remove the jigsaw puzzle piece 'tongues'.

    when I use a graphicsPath to mask out the tongue and isolate each piece on either side of the tongue outline i end up with a problem.  the tongue itself is draw using three separate Ellipse Segments that are defined by two points and a 'height'(similar to diagram found at https://www.mathopenref.com/arcradius.html where the W is derived by taking the distance between the two points)

    here is the code that does that :

    public static void GraphicsPath_Add(ref GraphicsPath grPath, bool bolClockwise, PointF pt0, Point pt1, int intSegmentHeight)
    {
        ////////////////////////////////////////////// overwriting pt0 to equal last point in path ///////////////////////////////////////////////////////
        if (grPath.PathData.Points.Length > 0)
          ;//  pt0 = grPath.PathData.Points[grPath.PathData.Points.Length - 1];
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    
        int intGrPathIndexStart = grPath.PathData.Points.Length-1;
        double dblArc_Height = (double)intSegmentHeight;
        classRadialCoordinate cRad = new classRadialCoordinate(pt0, pt1);
        double dblArc_Width = cRad.Magnitude;
    
        Point ptBetweenInputs = new Point((int)(pt0.X + (pt1.X - pt0.X) / 2),(int)( pt0.Y + (pt1.Y - pt0.Y) / 2));
        float fltBaseAngle = (float)(cRad.Degrees 
                                    + (bolClockwise ? -90 : 90));
    
        if (bolClockwise)
            cRad.Radians += Math.PI / 2;
        else
            cRad.Radians -= Math.PI / 2;
    
        //https://www.mathopenref.com/arcradius.html 
        // R = h/2 + w^2/(8h)
        double dblRadius = dblArc_Height/ 2 + Math.Pow(dblArc_Width, 2) / (8 * dblArc_Height);
        double dblTheta = Math.Asin((dblArc_Width/2)/ dblRadius);
        float fltDegree_Start = fltBaseAngle - (float)(180.0 / Math.PI * dblTheta);
        float fltDegree_Sweep = (float)(360.0 / Math.PI * dblTheta);
        cRad.Magnitude = dblRadius - dblArc_Height;
    
        Point ptCenterEllipse = AddTwoPoints(ptBetweenInputs, cRad.toPoint());
        Rectangle recEllipse = new Rectangle(ptCenterEllipse.X - (int)dblRadius, ptCenterEllipse.Y - (int)dblRadius, (int)(2.0 * dblRadius), (int)(2.0 * dblRadius));
    
        if (fltDegree_Sweep > 0)
        {
            grPath.AddArc(recEllipse, fltDegree_Start, fltDegree_Sweep);
        }
    }

    this code does give me the necessary information to create an arc on the GraphicsPath but there are issues.

    the points around the tongue are first calculated.  Then they are used in sequence to generate the path

    pt0 with pt1

    pt1 with pt2

    pt2 with pt3 and so on around the tongue each call to the above function including a clockwise/counter-clockwise boolean as well as the arc-segment's 'height'.

    since all the arc-segments start with the end point of the previous arc-segment it was reasonable to expect them to create a smooth series of continuous arcs but the unexpected result is that it occasionally.  These transitions seem to reverse the sequence of the previous arc before starting the next one and creates an unsightly line between the two points of the previous arc.

    see image:

    in the image above you can see that the arc from points 2-3 is counter-clockwise(CC), then from 3-4 is clockwise(C) and from 4-5 is CC but there's an ugly line connecting points 4-5 which I don't want.

    so I added a line at the start of the function included above which sets the input parameter pt0 equal to the last point in the graphicsPath's list of Points to make certain that the next arc starts at exactly the same place as the previous arc(un-comment the third line of the code-above).  When I do this I wind up with something similar to the image below.

    which, as you can see, is even less desireable.  Here it appears to transition from 2-3(CC) to 3-4(C) by starting the 3-4 arc at location 2 ?!??  which is obviously not what I want.

    any idea how to solve this one?

    Christ


    my code is perfect until i don't find a bug


    • Edited by Christ Kennedy Wednesday, November 27, 2019 7:07 PM
    • Moved by CoolDadTx Monday, December 2, 2019 3:07 PM Winforms related
    Wednesday, November 27, 2019 7:02 PM

Answers

  • Greetings again.

    I've had another look, and here's my best guess at what's happening. The code is a bit difficult to follow, so I could be completely up the creek, but here goes.

    I think you are creating the figure like this.

    That is, it looks like the angles of the arcs (fltDegree_Sweep) are always positive, so always go in the same direction (clockwise). So instead of the end of Arc1 meeting the start of Arc2, it meets the end of Arc2.  This means (I think) that GraphicsPath will add a connecting line from the start of Arc2 to the end of Arc1, and a similar line will be added from the End of Arc2 to the start of Arc3.

    If these extra lines are perfectly horizontal or vertical they will (I think) cancel each other out, but if they are slightly off due to rounding, you will get bits of them appearing where they don't quite match.

    If I'm right, you need to reverse the direction of Arc2, so that it starts at the other end and its sweep angle is negative. That way, the end of each arc will be at the same place (or near enough) as the start of the next one.

    If I'm not right, I'm out of ideas.

    Monday, December 2, 2019 5:59 AM
  • yes.

    that was it.  The sweep was always either positive or the arc was not used.

    I made a few changes with your comments in mind and got it working.

    public static void GraphicsPath_Add(ref GraphicsPath grPath, bool bolClockwise, PointF pt0, Point pt1, int intSegmentHeight)
    {
    //            int intGrPathIndexStart = grPath.PathData.Points.Length-1;
        double dblArc_Height = (double)intSegmentHeight;
        classRadialCoordinate cRad = new classRadialCoordinate(pt0, pt1);
        double dblArc_Width = cRad.Magnitude;
    
        Point ptBetweenInputs = new Point((int)(pt0.X + (pt1.X - pt0.X) / 2),(int)( pt0.Y + (pt1.Y - pt0.Y) / 2));
        float fltBaseAngle = (float)(cRad.Degrees 
                                    + (bolClockwise ? -90 : 90));
    
        if (bolClockwise)
            cRad.Radians -= Math.PI / 2;
        else
            cRad.Radians += Math.PI / 2;
    
        //https://www.mathopenref.com/arcradius.html 
        // R = h/2 + w^2/(8h)
        double dblRadius = dblArc_Height/ 2 + Math.Pow(dblArc_Width, 2) / (8 * dblArc_Height);
        double dblTheta = Math.Asin((dblArc_Width/2)/ dblRadius);
        float fltDegree_Start = fltBaseAngle -(bolClockwise? 1:-1)* (float)(180.0 / Math.PI * dblTheta);
        float fltDegree_Sweep = (bolClockwise ? 1:-1)* (float)(360.0 / Math.PI * dblTheta);
        cRad.Magnitude = dblRadius - dblArc_Height;
        cRad.Radians += Math.PI;
        Point ptCenterEllipse = AddTwoPoints(ptBetweenInputs, cRad.toPoint());
        Rectangle recEllipse = new Rectangle(ptCenterEllipse.X - (int)dblRadius, ptCenterEllipse.Y - (int)dblRadius, (int)(2.0 * dblRadius), (int)(2.0 * dblRadius));
    
        //if (fltDegree_Sweep > 0)                  // THIS LINE SHOULD NEVER HAVE EVEN BEEN CONSIDERED
        {
            grPath.AddArc(recEllipse, fltDegree_Start, fltDegree_Sweep);
        }
    }

    thanks,

    Christ


    my code is perfect until i don't find a bug

    • Marked as answer by Christ Kennedy Wednesday, December 4, 2019 12:43 AM
    Monday, December 2, 2019 6:40 PM

All replies

  • Greetings CK.

    You might need to show us some code that calls GraphicsPath_Add, so we can see the order things are happening. On the face of it, there's no obvious reason for that spurious line to appear, so maybe the points are being mixed up in the call.

    And while I have your attention, why is pt0 a PointF but pt1 is a Point? That looks a little odd, and makes me think there might be rounding issues from floats to ints.


    • Edited by Ante Meridian Thursday, November 28, 2019 4:51 AM pt0 not p0.
    Thursday, November 28, 2019 4:48 AM
  • thank you for your reply,

    here is a simple sample function that re-creates the same problem.

    the source picture is the first image below.


    void testTongueCutter()
    {
        Bitmap bmpTest = new Bitmap(Properties.Resources.testDualImages);
        Point[] pts =
                {
            new Point(126,0),
            new Point(126,34),
            new Point(109, 34),
            new Point(109, 61),
            new Point(126, 61),
            new Point(126, 93)};
        List<Point> lstPts = pts.ToList<Point>();
        lstPts.Add(new Point(126, bmpTest.Height));
        lstPts.Add(new Point(bmpTest.Width, bmpTest.Height));
        lstPts.Add(new Point(bmpTest.Width, 0));

        GraphicsPath grPath = new GraphicsPath();
        grPath.AddLine(lstPts[0], lstPts[1]);
        classMath3.GraphicsPath_Add(ref grPath, true, lstPts[1], lstPts[2], 5);
        classMath3.GraphicsPath_Add(ref grPath, false, lstPts[2], lstPts[3], 15);
        classMath3.GraphicsPath_Add(ref grPath, true, lstPts[3], lstPts[4], 7);
        grPath.AddLine(lstPts[4], lstPts[5]);
        grPath.AddLine(lstPts[5], lstPts[6]);
        grPath.AddLine(lstPts[6], lstPts[7]);
        grPath.AddLine(lstPts[7], lstPts[8]);

        using (Graphics g = Graphics.FromImage(bmpTest))
        {
            g.FillPath(Brushes.Black, grPath);
        }

        bmpTest.Save(@"c:\debug\testTongue.bmp");
    }
    and this is the result

    I would appreciate any suggestion you can make.

    Christ

    my code is perfect until i don't find a bug




    Thursday, November 28, 2019 3:30 PM
  • Greetings again CK.

    It looks to me like the grid of dark pink and black lines might have something to do with it. What happens if you try a plain pink background?

    Sunday, December 1, 2019 11:09 PM
  • my project is well advanced.  almost done, except for this glitch.

    no matter what picture I plug into my Jigsaw Puzzler App.  they ALL have pieces with those tiny flaws.  I'm not sure what to do with it.

    I used the Log-Log graph because it was easier to detect flaws in my graphics but the white background was difficult to work with so I 'dyed' it pink.  

    so... unfortunately, no.  The pink and black is not the cause of the error.  

    but thanks anyway,

    Christ


    my code is perfect until i don't find a bug

    Monday, December 2, 2019 2:51 AM
  • Sorry to ask the obvious, but have you stepped through your code in the debugger and checked that what is going into the GraphicsPath is what you expect?

    I would try your code myself to see if I can spot anything wrong, but there looks to be a few things I don't have, like classRadialCoordinate.

    And when you say, "no matter what picture" you use, have you tried a plain background? I didn't mean the grid specifically was the problem, but that having a background that is generally not plain.

    Monday, December 2, 2019 3:13 AM
  • Greetings again.

    I've had another look, and here's my best guess at what's happening. The code is a bit difficult to follow, so I could be completely up the creek, but here goes.

    I think you are creating the figure like this.

    That is, it looks like the angles of the arcs (fltDegree_Sweep) are always positive, so always go in the same direction (clockwise). So instead of the end of Arc1 meeting the start of Arc2, it meets the end of Arc2.  This means (I think) that GraphicsPath will add a connecting line from the start of Arc2 to the end of Arc1, and a similar line will be added from the End of Arc2 to the start of Arc3.

    If these extra lines are perfectly horizontal or vertical they will (I think) cancel each other out, but if they are slightly off due to rounding, you will get bits of them appearing where they don't quite match.

    If I'm right, you need to reverse the direction of Arc2, so that it starts at the other end and its sweep angle is negative. That way, the end of each arc will be at the same place (or near enough) as the start of the next one.

    If I'm not right, I'm out of ideas.

    Monday, December 2, 2019 5:59 AM
  • yes.

    that was it.  The sweep was always either positive or the arc was not used.

    I made a few changes with your comments in mind and got it working.

    public static void GraphicsPath_Add(ref GraphicsPath grPath, bool bolClockwise, PointF pt0, Point pt1, int intSegmentHeight)
    {
    //            int intGrPathIndexStart = grPath.PathData.Points.Length-1;
        double dblArc_Height = (double)intSegmentHeight;
        classRadialCoordinate cRad = new classRadialCoordinate(pt0, pt1);
        double dblArc_Width = cRad.Magnitude;
    
        Point ptBetweenInputs = new Point((int)(pt0.X + (pt1.X - pt0.X) / 2),(int)( pt0.Y + (pt1.Y - pt0.Y) / 2));
        float fltBaseAngle = (float)(cRad.Degrees 
                                    + (bolClockwise ? -90 : 90));
    
        if (bolClockwise)
            cRad.Radians -= Math.PI / 2;
        else
            cRad.Radians += Math.PI / 2;
    
        //https://www.mathopenref.com/arcradius.html 
        // R = h/2 + w^2/(8h)
        double dblRadius = dblArc_Height/ 2 + Math.Pow(dblArc_Width, 2) / (8 * dblArc_Height);
        double dblTheta = Math.Asin((dblArc_Width/2)/ dblRadius);
        float fltDegree_Start = fltBaseAngle -(bolClockwise? 1:-1)* (float)(180.0 / Math.PI * dblTheta);
        float fltDegree_Sweep = (bolClockwise ? 1:-1)* (float)(360.0 / Math.PI * dblTheta);
        cRad.Magnitude = dblRadius - dblArc_Height;
        cRad.Radians += Math.PI;
        Point ptCenterEllipse = AddTwoPoints(ptBetweenInputs, cRad.toPoint());
        Rectangle recEllipse = new Rectangle(ptCenterEllipse.X - (int)dblRadius, ptCenterEllipse.Y - (int)dblRadius, (int)(2.0 * dblRadius), (int)(2.0 * dblRadius));
    
        //if (fltDegree_Sweep > 0)                  // THIS LINE SHOULD NEVER HAVE EVEN BEEN CONSIDERED
        {
            grPath.AddArc(recEllipse, fltDegree_Start, fltDegree_Sweep);
        }
    }

    thanks,

    Christ


    my code is perfect until i don't find a bug

    • Marked as answer by Christ Kennedy Wednesday, December 4, 2019 12:43 AM
    Monday, December 2, 2019 6:40 PM