diff --git a/src/System.Drawing.Common/src/System/Drawing/Graphics.cs b/src/System.Drawing.Common/src/System/Drawing/Graphics.cs index f58c6bdfc32..76a26022c6c 100644 --- a/src/System.Drawing.Common/src/System/Drawing/Graphics.cs +++ b/src/System.Drawing.Common/src/System/Drawing/Graphics.cs @@ -730,9 +730,41 @@ public void DrawRoundedRectangle(Pen pen, Rectangle rect, Size radius) => /// The radius width and height used to round the corners of the rectangle. 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)); + } + 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); + } + finally + { + this.SmoothingMode = previousSmoothingMode; + this.PixelOffsetMode = previousPixelOffsetMode; + } } #endif @@ -1204,9 +1236,28 @@ public void FillRoundedRectangle(Brush brush, Rectangle rect, Size radius) => /// The radius width and height used to round the corners of the rectangle. 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 diff --git a/src/System.Drawing.Common/tests/System/Drawing/GraphicsTests.cs b/src/System.Drawing.Common/tests/System/Drawing/GraphicsTests.cs index e921aedb521..ed39da837fc 100644 --- a/src/System.Drawing.Common/tests/System/Drawing/GraphicsTests.cs +++ b/src/System.Drawing.Common/tests/System/Drawing/GraphicsTests.cs @@ -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 }