奇怪地使用Graphics.FillPath绘制GraphicsPath

时间:2013-10-03 19:19:11

标签: c# graphics gdi+ rectangles graphicspath

我编写了一些代码,根据自定义结构GraphicsPath创建了一个圆角矩形BorderRadius(这允许我定义左上角,右上角,左下角和右下角半径矩形)和初始Rectangle本身:

public static GraphicsPath CreateRoundRectanglePath(BorderRadius radius, Rectangle rectangle)
{
    GraphicsPath result = new GraphicsPath();

    if (radius.TopLeft > 0)
    {
        result.AddArc(rectangle.X, rectangle.Y, radius.TopLeft, radius.TopLeft, 180, 90);
    }
    else
    {
        result.AddLine(new System.Drawing.Point(rectangle.X, rectangle.Y), new System.Drawing.Point(rectangle.X, rectangle.Y));
    }
    if (radius.TopRight > 0)
    {
        result.AddArc(rectangle.X + rectangle.Width - radius.TopRight, rectangle.Y, radius.TopRight, radius.TopRight, 270, 90);
    }
    else
    {
        result.AddLine(new System.Drawing.Point(rectangle.X + rectangle.Width, rectangle.Y), new System.Drawing.Point(rectangle.X + rectangle.Width, rectangle.Y));
    }
    if (radius.BottomRight > 0)
    {
        result.AddArc(rectangle.X + rectangle.Width - radius.BottomRight, rectangle.Y + rectangle.Height - radius.BottomRight, radius.BottomRight, radius.BottomRight, 0, 90);
    }
    else
    {
        result.AddLine(new System.Drawing.Point(rectangle.X + rectangle.Width, rectangle.Y + rectangle.Height), new System.Drawing.Point(rectangle.X + rectangle.Width, rectangle.Y + rectangle.Height));
    }
    if (radius.BottomLeft > 0)
    {
        result.AddArc(rectangle.X, rectangle.Y + rectangle.Height - radius.BottomLeft, radius.BottomLeft, radius.BottomLeft, 90, 90);
    }
    else
    {
        result.AddLine(new System.Drawing.Point(rectangle.X, rectangle.Y + rectangle.Height), new System.Drawing.Point(rectangle.X, rectangle.Y + rectangle.Height));
    }

    return result;
}

现在,如果我将它与FillPath和DrawPath一起使用,我会注意到一些奇怪的结果:

GraphicsPath path = CreateRoundRectanglePath(new BorderRadius(8), new Rectangle(10, 10, 100, 100));
e.Graphics.DrawPath(new Pen(Color.Black, 1), path);
e.Graphics.FillPath(new SolidBrush(Color.Black), path);

我已缩放到每个结果Rectangle(右侧),以便您可以清楚地看到问题:

rectangles

我想知道的是:为什么绘制的矩形上的所有弧都相等,填充矩形上的所有弧都是奇数?

更好的是,它是否可以修复,以便填充的矩形正确绘制?

编辑:是否可以在不使用FillPath的情况下填充GraphicsPath的内部?

编辑:根据评论....这里是BorderRadius结构的一个例子

public struct BorderRadius
{
    public Int32 TopLeft { get; set; }
    public Int32 TopRight { get; set; }
    public Int32 BottomLeft { get; set; }
    public Int32 BottomRight { get; set; }

    public BorderRadius(int all) : this()
    {
        this.TopLeft = this.TopRight = this.BottomLeft = this.BottomRight = all;
    }
}

4 个答案:

答案 0 :(得分:3)

我遇到了同样的问题并找到了解决方案。对于你@seriesOne来说可能为时已晚,但如果他们遇到这个问题,它对其他人有用。 基本上在使用填充方法时(以及使用Graphics.SetClip将圆角矩形设置为剪切路径时),我们必须将右边和底部线移动一个像素。所以我想出了一个方法,接受一个参数来修复矩形是否使用填充。这是:

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;
}

这就是你如何使用它:

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

答案 1 :(得分:1)

我建议明确地在每个弧的末尾添加一行到下一个弧的开头。

您也可以尝试使用Flatten方法用线条逼近路径中的所有曲线。这应该消除任何歧义。

您从FillPath获得的结果看起来类似于我在路径上的点被错误解释的问题,基本上导致二次而不是三次贝塞尔样条曲线。

您可以使用GetPathData函数检查路径上的点:http://msdn.microsoft.com/en-us/library/ms535534%28v=vs.85%29.aspx

Bezier曲线(GDI +用来近似弧线)由4个点表示。第一个是终点,可以是任何类型。第二个和第三个是控制点,并具有类型PathPointBezier。最后一个是另一个端点,类型为PathPointBezier。换句话说,当GDI +看到PathPointBezier时,它使用路径的当前位置和3个Bezier点来绘制曲线。贝塞尔曲线可以串在一起,但贝塞尔点的数量应该总是可以被3整除。

你正在做的事情有点奇怪,因为你在不同的地方绘制曲线而没有明确的线条来加入它们。我猜它会创建一个这样的模式:

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

这看起来很合理。 GDI +应该从每条曲线的最后一个端点到下一条曲线的第一个端点绘制一条线。 DrawPath显然是这样做的,但我认为FillPath正在以不同的方式解释这些点。一些终点被视为控制点,反之亦然。

答案 2 :(得分:1)

行为的真正原因在Pixel behaviour of FillRectangle and DrawRectangle解释。

它与默认的像素舍入有关,并且FillRectangle / FillPath带有整数坐标的事实最终会在像素的中间绘制并得到舍入(根据Graphics.PixelOffsetMode)。

另一方面,DrawRectangle / DrawPath使用1px笔绘制,在像素边界上得到完美的圆角。

根据您的使用情况,解决方案可能是将FillRectangle / FillPath的矩形膨胀/收缩.5px。

答案 3 :(得分:0)

“是否可以在不使用FillPath的情况下填充GraphicsPath的内部?”

是的......但我认为这更像是一个客厅技巧(它可能不像你期望的更复杂的形状)。您可以将图形剪辑到路径,然后只填充整个包含的矩形:

        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();