用于在 GDI+ 中创建具有像素精度对称性的圆角的类






4.98/5 (22投票s)
这个类克服了在 GDI+ 中创建圆角矩形时相关的非对称问题。
引言
如果您曾经在 GDI+ 中创建过圆角矩形,您可能已经注意到它在像素级别上通常不是完全对称的。当以下任一条件为真时,就会出现不对称:(半径 = 10)或(笔宽 > 1)或(使用 FillPath
)。这可以在上面的图像中看到,该图像已放大 4 倍。对于半径大于 15 的大半径,不对称似乎不会发生。可能是因为有更多的像素可以使用,或者只是不明显。
背景
由于圆角矩形不是 GDI+ 的原生形状,因此通常使用 GraphicsPath
或其他一些机制来创建它们。如果您使用 AddArc
、AddBezeir
、多边形点、变换等,都没有关系,如果先前声明的任何条件为真,则存在不对称。所有这些方法都不能产生准确结果的原因是因为问题不在于点的定义。以上所有方法都将准确地定义点。问题发生在形状被渲染(绘制或填充)时。我不会推测这种不对称的根本原因是什么。
CRoundRect
CRoundRect
类完全在头文件中实现,并提供以下基本功能:GetRoundRectPath()
、DrawRoundRect()
和 FillRoundRect()
。 需要三个解决方法才能获得对称结果。
GetRoundRectPath
此函数使用 AddArc
方法定义圆角矩形路径。第一个解决方法处理半径为 10 的特殊情况。它在战略点偏移弧的矩形并增加其大小。我没有一个好的理论来解释为什么这有效,或者为什么它只适用于半径为 10。
void GetRoundRectPath(GraphicsPath *pPath, Rect r, int dia) { // diameter can't exceed width or height if(dia > r.Width) dia = r.Width; if(dia > r.Height) dia = r.Height; // define a corner Rect Corner(r.X, r.Y, dia, dia); // begin path pPath->Reset(); // top left pPath->AddArc(Corner, 180, 90); // tweak needed for radius of 10 (dia of 20) if(dia == 20) { Corner.Width += 1; Corner.Height += 1; r.Width -=1; r.Height -= 1; } // top right Corner.X += (r.Width - dia - 1); pPath->AddArc(Corner, 270, 90); // bottom right Corner.Y += (r.Height - dia - 1); pPath->AddArc(Corner, 0, 90); // bottom left Corner.X -= (r.Width - dia - 1); pPath->AddArc(Corner, 90, 90); // end path pPath->CloseFigure(); }
DrawRoundRect
此函数使用传递的矩形、半径、笔颜色和笔宽绘制一个圆角矩形。 第二个解决方法涉及使用笔宽为 1 并绘制“width”个矩形,每次都减小矩形的大小。 单独这样做是不够的,因为它会在角落留下孔。 相反,它只收缩 x,绘制矩形,然后收缩 y,然后再次绘制。
void DrawRoundRect(Graphics* pGraphics, Rect r, Color color, int radius, int width) { int dia = 2*radius; // set to pixel mode int oldPageUnit = pGraphics->SetPageUnit(UnitPixel); // define the pen Pen pen(color, 1); pen.SetAlignment(PenAlignmentCenter); // get the corner path GraphicsPath path; // get path GetRoundRectPath(&path, r, dia); // draw the round rect pGraphics->DrawPath(&pen, &path); // if width > 1 for(int i=1; i<width; i++) { // left stroke r.Inflate(-1, 0); // get the path GetRoundRectPath(&path, r, dia); // draw the round rect pGraphics->DrawPath(&pen, &path); // up stroke r.Inflate(0, -1); // get the path GetRoundRectPath(&path, r, dia); // draw the round rect pGraphics->DrawPath(&pen, &path); } // restore page unit pGraphics->SetPageUnit((Unit)oldPageUnit); }
FillRoundRect
此函数使用传递的矩形、半径和画笔颜色填充一个圆角矩形。第三个解决方法涉及填充矩形,然后绘制边框以修复边缘。
void FillRoundRect(Graphics* pGraphics, Brush* pBrush, Rect r, Color border, int radius) { int dia = 2*radius; // set to pixel mode int oldPageUnit = pGraphics->SetPageUnit(UnitPixel); // define the pen Pen pen(border, 1); pen.SetAlignment(PenAlignmentCenter); // get the corner path GraphicsPath path; // get path GetRoundRectPath(&path, r, dia); // fill pGraphics->FillPath(pBrush, &path); // draw the border last so it will be on top pGraphics->DrawPath(&pen, &path); // restore page unit pGraphics->SetPageUnit((Unit)oldPageUnit); }
FillRoundRect – 备选方案
此函数有一个备用版本,它将 Brush
作为其参数之一。 如果您想用 SolidBrush
以外的东西填充,这是必要的。 需要颜色参数,以便函数知道要将边框设置为哪种颜色。 假设您想要宽度为 1 的边框,此函数也可用于在单个调用中进行边框和填充。
void FillRoundRect(Graphics* pGraphics, Rect r, Color color, int radius) { SolidBrush sbr(color); FillRoundRect(pGraphics, &sbr, r, color, radius); }
演示程序
演示程序使用 VC7 编译,但此代码应适用于支持 GDI+ 的任何编译器和操作系统。 该演示程序还演示了一种创建角落没有孔的同心边框的技术。
附加评论
此类不设置任何模式,如 SmoothingModeAntiAlias
。 它将使用当前定义的状态。 它会将 PageUnit
设置为 UnitPixel
,但会在完成后恢复它。 获得此解决方案需要做很多工作,因为我认为这有点像黑客,并且我首先尝试了其他所有方法。 不幸的是,我必须让它为我正在开发的非常酷的类工作。 敬请关注…