奇怪地使用Graphics.FillPath绘制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 (右侧),所以你可以清楚地看到问题:

矩形

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

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

编辑:是否可以在不使用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; } } 

我遇到了同样的问题并找到了解决方案。 @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(bX - 1, bY - 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)); 

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

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

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

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

贝塞尔曲线(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正在以不同的方式解释这些点。 一些终点被视为控制点,反之亦然。

“是否可以在不使用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(); 

在FillRectangle和DrawRectangle的Pixel行为中解释了该行为的真正原因。

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

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

根据您的使用情况,解决方案可能是通过.5px对FillRectangle / FillPath的矩形进行充气/放气。