Rounded Rectangle Not Accurate

佐手、 提交于 2019-11-29 06:56:38

1) Resize your source image to a binary multiple of its original size. Typically, I'll resample to a width and height 4 times greater (or 8, or 16) than the original.

2) Perform all my GDI+ drawing operations (taking into account, of course, that my co-ordinates will need to be multiplied by a factor of 4). There is no need to use any fancy anti-aliasing.

3) Re-sample the image back down to the original dimensions. Shrinking the image results in a nice smoothing effect, and minimizes any rounding errors in lines, curves, etc.

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

Without oversampling:

With 8 times oversampling:

I have found the best solution to be just old-school 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

This produces the symmetrical rounded rectangle I've been looking for:

Because no-one's answered you yet here is a trick I have used in the past. It works reasonably well, and definitely looks better than the classic implementation with AddArc().

It uses circles and clipping to achieve the result you want. It may show slight artefacts when using pens with a width greater than 1px, but other than that it works well.

I hope it will be good enough for your project.

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

The method's interface is straightforward, but post a comment if you need assistance.

Edit: Something else that should work is to draw the same arc four times, but flipped using TranslateTransform and TranslateScale. That should mean the arc appears identical in each corner.

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

This is similar to the old Computer Graphics method of drawing a circle, where you'd draw a quarter circle four times to avoid rounding errors such as the one in GDI.

Another alternative is to draw the first arc onto an image, and then draw the image four times, flipping as required. Below is a variation on the second method, using an image to draw the arcs.

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

On occasion, I've used a "low tech" approach to deal with the rounding errors in GDI+

1) Resize your source image to a binary multiple of its original size. Typically, I'll resample to a width and height 4 times greater (or 8, or 16) than the original.

2) Perform all my GDI+ drawing operations (taking into account, of course, that my co-ordinates will need to be multiplied by a factor of 4). There is no need to use any fancy anti-aliasing.

3) Re-sample the image back down to the original dimensions. Shrinking the image results in a nice smoothing effect, and minimizes any rounding errors in lines, curves, etc.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!