Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 54 additions & 3 deletions src/System.Drawing.Common/src/System/Drawing/Graphics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -730,9 +730,41 @@ public void DrawRoundedRectangle(Pen pen, Rectangle rect, Size radius) =>
/// <param name="radius">The radius width and height used to round the corners of the rectangle.</param>
public void DrawRoundedRectangle(Pen pen, RectangleF rect, SizeF radius)
{
ArgumentNullException.ThrowIfNull(pen);

// Inset the stroke by half the pen width so the entire border sits inside the requested
// bounds on a consistent sub-pixel grid. Combined with high-quality pixel offset and
// anti-aliasing this realigns the corner arcs with the straight edges and removes the
// artifacts a centered, default-offset stroke would otherwise produce.
RectangleF strokeRect = rect;
SizeF strokeRadius = radius;

if (rect.Width > pen.Width && rect.Height > pen.Width)
{
float inset = pen.Width / 2f;
strokeRect = RectangleF.Inflate(rect, -inset, -inset);
strokeRadius = new(
Math.Max(0f, radius.Width - inset),
Math.Max(0f, radius.Height - inset));
}
Comment on lines +742 to +749

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can drop the inset logic entirely and keep just the  AntiAlias  +  PixelOffsetMode.HighQuality  change.

  1. It crashes on valid input. When the pen.Width = 8 and radius = 4, the strokeRadius will be 0, which causes an exception: System.ArgumentException: 'Parameter is not valid.'
  2. It deforms small shapes. The  rect > pen.Width  guard is a hard threshold, so geometry jumps discontinuously as size crosses it. With a 10px pen, a 12×12 rect insets down to a 2×2 path (radius 3) → visibly malformed next to the 8×8/10×10 ones that aren't inset at all.
  3. It breaks alignment with  DrawRectangle . Every other  Draw*  centers the pen on the bounds, insetting makes  DrawRoundedRectangle  the odd one out, so mixing it with  DrawRectangle  at the same bounds no longer lines up.
Image


using GraphicsPath path = new();
path.AddRoundedRectangle(rect, radius);
DrawPath(pen, path);
path.AddRoundedRectangle(strokeRect, strokeRadius);

Drawing2D.SmoothingMode previousSmoothingMode = this.SmoothingMode;
PixelOffsetMode previousPixelOffsetMode = this.PixelOffsetMode;

try
{
this.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias;
this.PixelOffsetMode = PixelOffsetMode.HighQuality;
DrawPath(pen, path);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"this" can be removed

}
finally
{
this.SmoothingMode = previousSmoothingMode;
this.PixelOffsetMode = previousPixelOffsetMode;
}
}
#endif

Expand Down Expand Up @@ -1204,9 +1236,28 @@ public void FillRoundedRectangle(Brush brush, Rectangle rect, Size radius) =>
/// <param name="radius">The radius width and height used to round the corners of the rectangle.</param>
public void FillRoundedRectangle(Brush brush, RectangleF rect, SizeF radius)
{
ArgumentNullException.ThrowIfNull(brush);

using GraphicsPath path = new();
path.AddRoundedRectangle(rect, radius);
FillPath(brush, path);

// Anti-aliasing plus high-quality pixel offset gives the filled corners the same crisp,
// uniform edge as the straight sides instead of the softer, offset arcs the default
// rendering mode produces.
Drawing2D.SmoothingMode previousSmoothingMode = this.SmoothingMode;
PixelOffsetMode previousPixelOffsetMode = this.PixelOffsetMode;

try
{
this.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias;
this.PixelOffsetMode = PixelOffsetMode.HighQuality;
FillPath(brush, path);
}
finally
{
this.SmoothingMode = previousSmoothingMode;
this.PixelOffsetMode = previousPixelOffsetMode;
}
}
#endif

Expand Down
39 changes: 39 additions & 0 deletions src/System.Drawing.Common/tests/System/Drawing/GraphicsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3002,5 +3002,44 @@ public void Graphics_FillRoundedRectangle_Float()
graphics.FillRoundedRectangle(Brushes.Green, new RectangleF(0, 0, 10, 10), new(2, 2));
VerifyBitmapNotEmpty(bitmap);
}

[Fact]
public void Graphics_DrawRoundedRectangle_ThickPen()
{
using Bitmap bitmap = new(20, 20);
using Graphics graphics = Graphics.FromImage(bitmap);
using Pen pen = new(Color.Blue, 3);
graphics.DrawRoundedRectangle(pen, new RectangleF(0, 0, 20, 20), new(6, 6));
VerifyBitmapNotEmpty(bitmap);
}

[Fact]
public void Graphics_DrawRoundedRectangle_RestoresRenderingModes()
{
using Bitmap bitmap = new(20, 20);
using Graphics graphics = Graphics.FromImage(bitmap);
graphics.SmoothingMode = SmoothingMode.None;
graphics.PixelOffsetMode = PixelOffsetMode.Half;

using Pen pen = new(Color.Blue, 3);
graphics.DrawRoundedRectangle(pen, new RectangleF(0, 0, 20, 20), new(6, 6));

graphics.SmoothingMode.Should().Be(SmoothingMode.None);
graphics.PixelOffsetMode.Should().Be(PixelOffsetMode.Half);
}

[Fact]
public void Graphics_FillRoundedRectangle_RestoresRenderingModes()
{
using Bitmap bitmap = new(20, 20);
using Graphics graphics = Graphics.FromImage(bitmap);
graphics.SmoothingMode = SmoothingMode.None;
graphics.PixelOffsetMode = PixelOffsetMode.Half;

graphics.FillRoundedRectangle(Brushes.Green, new RectangleF(0, 0, 20, 20), new(6, 6));

graphics.SmoothingMode.Should().Be(SmoothingMode.None);
graphics.PixelOffsetMode.Should().Be(PixelOffsetMode.Half);
}
#endif
}