【问题标题】:Rounded Rectangle Not Accurate圆角矩形不准确
【发布时间】:2011-08-28 23:11:25
【问题描述】:

我找到的使用 GDI+ 绘制圆角矩形的每个示例代码都类似于以下内容(从 BobPowell.net 提取并稍作修改):

  Private Sub Panel1_Paint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles Panel1.Paint
    e.Graphics.Clear(SystemColors.Window)
    e.Graphics.SmoothingMode = SmoothingMode.None

    Call DrawRoundRect(e.Graphics, Pens.Red, 10, 10, 48, 24, 6)
  End Sub

  Public Sub DrawRoundRect(ByVal g As Graphics, ByVal p As Pen, ByVal x As Single, ByVal y As Single, ByVal width As Single, ByVal height As Single, ByVal radius As Single)
    Using gp As New GraphicsPath()
      gp.StartFigure()
      gp.AddArc(x + width - radius, y, radius * 2, radius * 2, 270, 90)
      gp.AddArc(x + width - radius, y + height - radius, radius * 2, radius * 2, 0, 90)
      gp.AddArc(x, y + height - radius, radius * 2, radius * 2, 90, 90)
      gp.AddArc(x, y, radius * 2, radius * 2, 180, 90)
      gp.CloseFigure()
      g.DrawPath(p, gp)
    End Using
  End Sub

这会生成一个圆角矩形,其中只有左上角是准确的。

AntiAliasing 必须关闭,因为它正在通过远程桌面连接,我不能依赖它是否可用。此外,我正在寻找一个清晰的圆角矩形。

我已尝试调整其他角的大小并更改笔的对齐方式,但似乎无法生成简单、准确的圆角矩形。

有没有办法在旧的winforms中画出比这更好的圆角矩形?

【问题讨论】:

  • 定义:not accurate enough.
  • @LarsTech:上传到你想要的任何地方,我们会为你内联。
  • 不幸的是,圆弧例程是出了名的不准确,尤其是对于小尺寸(例如圆角矩形...
  • 数学是错误的。将 AddArc() 的第一个和第二个参数的表达式中的 radius*2 替换为 radius。
  • @Hans 我更新了代码,但它并没有改变圆角的变形——只有左上角看起来正确,其他三个都搞砸了。

标签: vb.net winforms rounded-corners


【解决方案1】:

我发现最好的解决方案就是老式的 Windows API:

Private Sub DrawRoundRect(ByVal g As Graphics, ByVal r As Rectangle)
  Dim hDC As IntPtr = g.GetHdc
  Dim hPen As IntPtr = CreatePen(PS_SOLID, 0, ColorTranslator.ToWin32(Color.Red))
  Dim hOldPen As IntPtr = SelectObject(hDC, hPen)
  SelectObject(hDC, GetStockObject(NULL_BRUSH))
  RoundRect(hDC, r.Left, r.Top, r.Right - 1, r.Bottom - 1, 12, 12)
  SelectObject(hDC, hOldPen)
  DeleteObject(hPen)
  g.ReleaseHdc(hDC)
End Sub

这会产生我一直在寻找的对称圆角矩形:

【讨论】:

    【解决方案2】:

    1) 将源图像的大小调整为其原始大小的二进制倍数。通常,我会重新采样到比原来大 4 倍(或 8 或 16)的宽度和高度。

    2) 执行我所有的 GDI+ 绘图操作(当然要考虑到我的坐标需要乘以 4 倍)。无需使用任何花哨的抗锯齿。

    3) 将图像重新采样回原始尺寸。缩小图像会产生很好的平滑效果,并最大限度地减少线条、曲线等中的任何舍入误差。

    private Bitmap GenerateButton(int overSampling) {
    
        int overSampling = 8;
        int width=(48 + 10 + 10 + 6) * overSampling;
        int height=(24 + 10 + 10 + 6) * overSampling;
    
        // Draw the button with the rounded corners, but do
        // so at 8 times the normal size.
        Bitmap bitmap=new Bitmap(width,height);
        using (Graphics g = Graphics.FromImage(bitmap)) {
            g.Clear(Color.White);
            g.SmoothingMode = SmoothingMode.None;
            DrawRoundRect(overSampling, g, new Pen(Color.Red, overSampling), 10, 10, 48, 24, 6);
        }
    
        // Shrink the image down to its intended size
        Bitmap shrunkVersion=new Bitmap(bitmap.Width / overSampling, bitmap.Height / overSampling);
        using (Graphics g = Graphics.FromImage(shrunkVersion)) {
            // Use hi-quality resampling for a nice, smooth image.
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
            g.DrawImage(bitmap, 0, 0, shrunkVersion.Width, shrunkVersion.Height);
        }
    
        return shrunkVersion;
    }
    
    private void DrawRoundRect(int overSampling, Graphics g, Pen p, float x, float y, float width, float height, float radius)
    {
        using (GraphicsPath gp = new GraphicsPath())
        {
            gp.StartFigure();
            gp.AddArc((x + width - radius) * overSampling, y * overSampling, (radius * 2) * overSampling, (radius * 2) * overSampling, 270, 90);
            gp.AddArc((x + width - radius) * overSampling, (y + height - radius) * overSampling, (radius * 2) * overSampling, (radius * 2) * overSampling, 0, 90);
            gp.AddArc(x * overSampling, (y + height - radius) * overSampling, radius * 2 * overSampling, radius * 2 * overSampling, 90, 90);
            gp.AddArc(x * overSampling, y * overSampling, radius * 2 * overSampling, radius * 2 * overSampling, 180, 90);
            gp.CloseFigure();
            g.DrawPath(p, gp);
        }
    }
    

    没有过采样:

    8 倍过采样:

    【讨论】:

      【解决方案3】:

      因为没有人回答你,这是我过去使用的一个技巧。它工作得相当好,而且看起来肯定比使用 AddArc() 的经典实现更好。

      它使用圆形和剪裁来实现您想要的结果。使用宽度大于 1 像素的笔时,它可能会显示轻微伪影,但除此之外它也能正常工作。

      我希望它对你的项目足够好。

          private void DrawRoundedRectangle(Graphics g, Pen pen, Rectangle rect, int radius)
          {
              g.DrawLine(pen, rect.Left + radius, rect.Top, rect.Right - radius, rect.Top);
              g.DrawLine(pen, rect.Right, rect.Top+radius, rect.Right, rect.Bottom - radius);
              g.DrawLine(pen, rect.Left + radius, rect.Bottom, rect.Right - radius, rect.Bottom);
              g.DrawLine(pen, rect.Left, rect.Top + radius, rect.Left, rect.Bottom - radius);
      
              g.SetClip(new Rectangle(rect.Left, rect.Top, radius, radius));
              g.DrawEllipse(pen, rect.Left, rect.Top, radius * 2, radius * 2);
              g.ResetClip();
      
              g.SetClip(new Rectangle(rect.Right-radius, rect.Top, radius+1, radius+1));
              g.DrawEllipse(pen, rect.Right - radius * 2, rect.Top, radius * 2, radius * 2);
              g.ResetClip();
      
              g.SetClip(new Rectangle(rect.Right - radius, rect.Bottom-radius, radius+1, radius+1));
              g.DrawEllipse(pen, rect.Right - radius * 2, rect.Bottom - (radius * 2), radius * 2, radius * 2);
              g.ResetClip();
      
              g.SetClip(new Rectangle(rect.Left, rect.Bottom - radius, radius+1, radius+1));
              g.DrawEllipse(pen, rect.Left, rect.Bottom - (radius * 2), radius * 2, radius * 2);
              g.ResetClip();
          }
      

      该方法的界面很简单,但如果您需要帮助,请发表评论。

      编辑:应该工作的其他方法是绘制 same 弧四次,但使用 TranslateTransform 和 TranslateScale 翻转。那应该意味着弧线在每个角落看起来都是一样的。

          private void DrawRoundedRectangle(Graphics g, Pen pen, Rectangle rect, int radius)
          {
              g.DrawLine(pen, rect.Left + radius, rect.Top, rect.Right - radius, rect.Top);
              g.DrawLine(pen, rect.Right-1, rect.Top+radius, rect.Right-1, rect.Bottom - radius);
              g.DrawLine(pen, rect.Left + radius, rect.Bottom-1, rect.Right - radius, rect.Bottom-1);
              g.DrawLine(pen, rect.Left, rect.Top + radius, rect.Left, rect.Bottom - radius);
      
              g.TranslateTransform(rect.Left, rect.Top);
              g.DrawArc(pen, 0, 0, radius * 2, radius * 2, 180, 90);
              g.ResetTransform();
      
              g.TranslateTransform(rect.Right, rect.Top);
              g.ScaleTransform(-1, 1);
              g.DrawArc(pen, 1, 0, radius * 2, radius * 2, 180, 90);
              g.ResetTransform();
      
              g.TranslateTransform(rect.Right, rect.Bottom);
              g.ScaleTransform(-1, -1);
              g.DrawArc(pen, 1, 1, radius * 2, radius * 2, 180, 90);
              g.ResetTransform();
      
              g.TranslateTransform(rect.Left, rect.Bottom);
              g.ScaleTransform(1, -1);
              g.DrawArc(pen, 0, 1, radius * 2, radius * 2, 180, 90);
              g.ResetTransform();
          }
      

      这类似于绘制圆的旧计算机图形学方法,在这种方法中,您将绘制四分之一圆四次以避免诸如 GDI 中的舍入错误。

      另一种方法是在图像上绘制第一条弧线,然后将图像绘制四次,根据需要进行翻转。下面是第二种方法的变体,使用图像绘制弧线。

          private void DrawRoundedRectangle(Graphics g, Pen pen, Rectangle rect, int radius)
          {
              g.DrawLine(pen, rect.Left + radius, rect.Top, rect.Right - radius, rect.Top);
              g.DrawLine(pen, rect.Right - 1, rect.Top + radius, rect.Right - 1, rect.Bottom - radius);
              g.DrawLine(pen, rect.Left + radius, rect.Bottom - 1, rect.Right - radius, rect.Bottom - 1);
              g.DrawLine(pen, rect.Left, rect.Top + radius, rect.Left, rect.Bottom - radius);
      
              Bitmap arc = new Bitmap(radius, radius, g);
              Graphics.FromImage(arc).DrawArc(pen, 0, 0, radius * 2, radius * 2, 180, 90);
      
              g.TranslateTransform(rect.Left, rect.Top);
              g.DrawImage(arc, 0, 0);
              g.ResetTransform();
      
              g.TranslateTransform(rect.Right, rect.Top);
              g.ScaleTransform(-1, 1);
              g.DrawImage(arc, 0, 0);
              g.ResetTransform();
      
              g.TranslateTransform(rect.Right, rect.Bottom);
              g.ScaleTransform(-1, -1);
              g.DrawImage(arc, 0, 0);
              g.ResetTransform();
      
              g.TranslateTransform(rect.Left, rect.Bottom);
              g.ScaleTransform(1, -1);
              g.DrawImage(arc, 0, 0);
              g.ResetTransform();
      
              arc.Dispose();
          }
      

      【讨论】:

      • @EdwinGroenendaal 很好的尝试,但是当我使用您的代码时,DrawEllipse 似乎有与 DrawArc 相同的算法问题。椭圆的左上角不同于其他三个角(在这种情况下,我猜是象限)。
      • @LarsTech 哦,真可惜-我从来没有失败过,但我想这取决于椭圆/圆的大小。你用的是什么参数?
      • @EdwinGroenendaal DrawRoundedRectangle(e.Graphics, Pens.Blue, New Rectangle(10, 34, 52, 20), 4)。使用 DrawArc 或 DrawEllipse 时,尺寸似乎很重要。
      • @LarsTech 有了这些参数,我实际上得到了一个很好的均匀结果。 (顺便说一句,我正在绘制到屏幕而不是图像)
      • @EdwinGroenendaal 我在这里上传了我用这些参数绘制的矩形的放大版本:link 我知道我很挑剔,但这就是我最初问题的重点。
      【解决方案4】:

      有时,我使用“低技术”方法来处理 GDI+ 中的舍入误差

      1) 将源图像的大小调整为原始大小的二进制倍数。通常,我会重新采样到比原来大 4 倍(或 8 或 16)的宽度和高度。

      2) 执行我所有的 GDI+ 绘图操作(当然要考虑到我的坐标需要乘以 4 倍)。无需使用任何花哨的抗锯齿。

      3) 将图像重新采样回原始尺寸。缩小图像会产生很好的平滑效果,并最大限度地减少线条、曲线等中的任何舍入误差。

      【讨论】:

      • 您有可以在帖子中编辑的小代码示例吗?我尝试了你的聪明主意,但我仍然得到一个不完美的矩形。
      猜你喜欢
      • 2022-01-17
      • 1970-01-01
      • 2019-06-15
      • 1970-01-01
      • 1970-01-01
      • 2010-12-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多