问题
Every sample code I've ever found for drawing rounded rectangles using GDI+ goes something like this (lifted and slightly modified from 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
This produces a rounded rectangle where only the top left corner is accurate.
AntiAliasing has to be turned off because it is going through a remote desktop connection, and I can't depend on it being available. Besides, I am looking for a crisp rounded rectangle.
I've tried resizing the other corners and changing the pen alignments, but nothing seems to produce a simple, accurate rounded rectangle.
Is there a way to draw a better rounded rectangle than this in good old winforms?
回答1:
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:
回答2:
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:
回答3:
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();
}
回答4:
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.
来源:https://stackoverflow.com/questions/6060525/rounded-rectangle-not-accurate