Oddly drawn GraphicsPath with Graphics.FillPath

我只是一个虾纸丫 提交于 2019-11-29 11:56:18

I experienced the same problem and found a solution. Might be too late for you @seriesOne but it can be useful to other people if they have this problem. Basically when using the fill methods (and also when setting the rounded rectangle as the clipping path with Graphics.SetClip) we have to move by one pixel the right and bottom lines. So I came up with a method that accepts a parameter to fix the rectangle is using the fill or not. Here it is:

private static GraphicsPath CreateRoundedRectangle(Rectangle b, int r, bool fill = false)
{
    var path = new GraphicsPath();
    var r2 = (int)r / 2;
    var fix = fill ? 1 : 0;

    b.Location = new Point(b.X - 1, b.Y - 1);
    if (!fill)
        b.Size = new Size(b.Width - 1, b.Height - 1);

    path.AddArc(b.Left, b.Top, r, r, 180, 90);
    path.AddLine(b.Left + r2, b.Top, b.Right - r2 - fix, b.Top);

    path.AddArc(b.Right - r - fix, b.Top, r, r, 270, 90);
    path.AddLine(b.Right, b.Top + r2, b.Right, b.Bottom - r2);

    path.AddArc(b.Right - r - fix, b.Bottom - r - fix, r, r, 0, 90);
    path.AddLine(b.Right - r2, b.Bottom, b.Left + r2, b.Bottom);

    path.AddArc(b.Left, b.Bottom - r - fix, r, r, 90, 90);
    path.AddLine(b.Left, b.Bottom - r2, b.Left, b.Top + r2);

    return path;
}

So this is how you use it:

g.DrawPath(new Pen(Color.Red), CreateRoundedRectangle(rect, 24, false));
g.FillPath(new SolidBrush(Color.Red), CreateRoundedRectangle(rect, 24, true));

I'd suggest explicitly adding a line from the end of each arc to the beginning of the next one.

You could also try using the Flatten method to approximate all curves in your path with lines. That should remove any ambiguity.

The result you're getting from FillPath looks similar to an issue I had where the points on a Path were interpreted incorrectly, essentially leading to a quadratic instead of a cubic bezier spline.

You can examine the points on your path using the GetPathData function: http://msdn.microsoft.com/en-us/library/ms535534%28v=vs.85%29.aspx

Bezier curves (which GDI+ uses to approximate arcs) are represented by 4 points. The first is an end point and can be any type. The second and third are control points and have type PathPointBezier. The last is the other end point and has type PathPointBezier. In other words, when GDI+ sees PathPointBezier, it uses the path's current position and the 3 Bezier points to draw the curve. Bezier curves can be strung together but the number of bezier points should always be divisible by 3.

What you're doing is a bit strange, in that you are drawing curves in different places without explicit lines to join them. I'd guess it creates a pattern like this:

PathPointStart - end point of first arc
PathPointBezier - control point of first arc
PathPointBezier - control point of first arc
PathPointBezier - end point of first arc
PathPointLine - end point of second arc
PathPointBezier - control point of second arc
PathPointBezier - control point of second arc
PathPointBezier - end point of second arc
PathPointLine - end point of third arc
PathPointBezier - control point of third arc
PathPointBezier - control point of third arc
PathPointBezier - end point of third arc

That looks reasonable. GDI+ should be drawing a line from the last endpoint of each curve to the first endpoint of the next one. DrawPath clearly does this, but I think FillPath is interpreting the points differently. Some end points are being treated as control points and vice versa.

The real reason for the behavior is explained at Pixel behaviour of FillRectangle and DrawRectangle.

It has to do with the default pixel rounding and the fact that FillRectangle/FillPath with integer coordinates end up drawing in the middle of a pixel and get rounded (according to Graphics.PixelOffsetMode).

On the other hand, DrawRectangle/DrawPath draw with a 1px pen that gets perfectly rounded on the pixel boundaries.

Depending on your usage the solution could be to inflate/deflate the rectangle for FillRectangle/FillPath by .5px.

"Is it possible to fill the inside of a GraphicsPath without using FillPath?"

Yes...but I think this is more of a parlor trick (and it might not work as you expect for more complex shapes). You can clip the graphics to the path, and then just fill the entire encompassing rectangle:

        Rectangle rc = new Rectangle(10, 10, 100, 100);
        GraphicsPath path = CreateRoundRectanglePath(new BorderRadius(8), rc);
        e.Graphics.SetClip(path);
        e.Graphics.FillRectangle(Brushes.Black, rc);
        e.Graphics.ResetClip();
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!