Repository: AvaloniaUI/Avalonia

Issue:
Add RadiusX/RadiusY to RectangleGeometry
In WPF RectangleGeometry has `RadiusX` and `RadiusY` properties to get rounded corners.

I'd like to use a rounded rectangle as a clipping to a control in Avalonia.

In [WPF](https://learn.microsoft.com/en-us/dotnet/desktop/wpf/graphics-multimedia/how-to-round-the-corners-of-a-rectanglegeometry?view=netframeworkdesktop-4.8):
```xaml
<!-- Create a rectangle with rounded corners
             by giving the RectangleGeometry a RadiusX
             and a RadiusY of 10.  -->
          <RectangleGeometry
            Rect="20,20,150,50" RadiusX="10" RadiusY="10" />
```
Non-uniform Border Thickness is rendered with corner radius
**Describe the bug**
Same code
```xaml
<Border
    Width="100"
    Height="100"
    BorderBrush="Red"
    BorderThickness="20,20,20,1" />
```
In Avalonia 
![image](https://github.com/AvaloniaUI/Avalonia/assets/14807942/c5dd11b8-5514-4629-a82e-f448159b1aa3)

In WPF
![image](https://github.com/AvaloniaUI/Avalonia/assets/14807942/36f36d42-3b87-4618-9729-1113f917990c)


**To Reproduce**

**Expected behavior**

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**

- OS: Windows11
- Version 11p8

**Additional context**



Hints:
I might get to this over the holidays along with the BackgroundSizing work. What is required here is also similar to what I did for Rectangle itself and I can use it there too.
This is coming in with BackgroundSizing. It's sharing the same code a few other places.
I think this is not quite a bug but something that can be defined in the render options. In SVG it's common to specify how corner should be rendered. If would really be great if we can define this in `RenderOptions`. Probably defualt to sharp edges. 

See: 
- https://inkscape-manuals.readthedocs.io/en/latest/strokes.html?highlight=Join%20type#id4
- https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linecap
- https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linejoin
- https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit


@Gillibald I think you are quite experienced in this kind of stuff. What do you think about it? 
For XAML frameworks with separate BorderThickness and CornerRadius properties WPF is correct. This appears to be a bug in Avalonia.

That said, both border render paths are going to have to change in order to support BackgroundSizing. It might not be worth the time to fix this right now.
WPF has a code path that uses line segments to draw individual borders so that might be the difference here

Patch:
diff --git a/samples/ControlCatalog/Pages/BorderPage.xaml b/samples/ControlCatalog/Pages/BorderPage.xaml
index 7ec7e81e805..4c2384d6869 100644
--- a/samples/ControlCatalog/Pages/BorderPage.xaml
+++ b/samples/ControlCatalog/Pages/BorderPage.xaml
@@ -5,6 +5,9 @@
              d:DesignWidth="400"
              x:Class="ControlCatalog.Pages.BorderPage">
   <StackPanel Orientation="Vertical" Spacing="4">
+    <StackPanel.Resources>
+      <SolidColorBrush x:Key="SemiTransparentSystemAccentBrush" Color="{DynamicResource SystemAccentColor}" Opacity="0.4" />
+    </StackPanel.Resources>
     <TextBlock Classes="h2">A control which decorates a child with a border and background</TextBlock>
 
     <StackPanel Orientation="Vertical"
@@ -15,10 +18,25 @@
         <TextBlock>Border</TextBlock>
       </Border>
       <Border Background="{DynamicResource SystemAccentColorDark1}" 
-              BorderBrush="{DynamicResource SystemAccentColor}" 
-              BorderThickness="4" 
-              Padding="16">
-        <TextBlock>Border and Background</TextBlock>
+              BorderBrush="{DynamicResource SemiTransparentSystemAccentBrush}" 
+              BackgroundSizing="CenterBorder"
+              BorderThickness="8" 
+              Padding="12">
+        <TextBlock>Background And CenterBorder</TextBlock>
+      </Border>
+      <Border Background="{DynamicResource SystemAccentColorDark1}" 
+              BorderBrush="{DynamicResource SemiTransparentSystemAccentBrush}" 
+              BackgroundSizing="InnerBorderEdge"
+              BorderThickness="8" 
+              Padding="12">
+        <TextBlock>Background And InnerBorder</TextBlock>
+      </Border>
+      <Border Background="{DynamicResource SystemAccentColorDark1}" 
+              BorderBrush="{DynamicResource SemiTransparentSystemAccentBrush}" 
+              BackgroundSizing="OuterBorderEdge"
+              BorderThickness="8" 
+              Padding="12">
+        <TextBlock>Background And OuterBorderEdge</TextBlock>
       </Border>
       <Border BorderBrush="{DynamicResource SystemAccentColor}"
               BorderThickness="4" 
diff --git a/src/Avalonia.Base/Media/BackgroundSizing.cs b/src/Avalonia.Base/Media/BackgroundSizing.cs
new file mode 100644
index 00000000000..bd079ce731c
--- /dev/null
+++ b/src/Avalonia.Base/Media/BackgroundSizing.cs
@@ -0,0 +1,38 @@
+﻿namespace Avalonia.Media
+{
+    /// <summary>
+    /// Defines how a background is drawn relative to its border.
+    /// </summary>
+    public enum BackgroundSizing
+    {
+        /// <summary>
+        /// The background is drawn up to the inside edge of the border.
+        /// </summary>
+        /// <remarks>
+        /// The background will never be drawn under the border itself and will not be visible
+        /// underneath the border regardless of border transparency.
+        /// </remarks>
+        InnerBorderEdge = 0,
+
+        /// <summary>
+        /// The background is drawn completely to the outside edge of the border.
+        /// </summary>
+        /// <remarks>
+        /// The background will be visible underneath the border if the border has transparency.
+        /// </remarks>
+        OuterBorderEdge = 1,
+
+        /// <summary>
+        /// The background is drawn to the midpoint (center) of the border.
+        /// </summary>
+        /// <remarks>
+        /// The background will be visible underneath half of the border if the border has transparency.
+        /// For this reason it is not recommended to use <see cref="CenterBorder"/> if transparency is involved.
+        /// <br/><br/>
+        /// This value does not exist in other XAML frameworks and only exists in Avalonia for backwards compatibility
+        /// with legacy code. Before <see cref="BackgroundSizing"/> was added, Avalonia would always render using this
+        /// value (Skia's default).
+        /// </remarks>
+        CenterBorder = 2,
+    }
+}
diff --git a/src/Avalonia.Base/Media/GeometryBuilder.cs b/src/Avalonia.Base/Media/GeometryBuilder.cs
new file mode 100644
index 00000000000..074c48d8eeb
--- /dev/null
+++ b/src/Avalonia.Base/Media/GeometryBuilder.cs
@@ -0,0 +1,608 @@
+﻿// Portions of this source file are adapted from the Windows Presentation Foundation (WPF) project.
+// (https://github.com/dotnet/wpf)
+//
+// Licensed to The Avalonia Project under the MIT License, courtesy of The .NET Foundation.
+//
+// Portions of this source file are adapted from the WinUI project.
+// (https://github.com/microsoft/microsoft-ui-xaml/tree/winui3/main)
+//
+// Licensed to The Avalonia Project under the MIT License.
+
+// Ignore Spelling: keypoints
+
+using System;
+using Avalonia.Utilities;
+
+namespace Avalonia.Media
+{
+    /// <summary>
+    /// Contains internal helpers used to build and draw various geometries.
+    /// </summary>
+    internal class GeometryBuilder
+    {
+        private const double PiOver2 = 1.57079633; // 90 deg to rad
+        private const double Epsilon = 0.00000153; // Same as LayoutHelper.LayoutEpsilon
+
+        /// <summary>
+        /// Draws a new rounded rectangle within the given geometry context.
+        /// Warning: The caller must manage and dispose the <see cref="StreamGeometryContext"/> externally.
+        /// </summary>
+        /// <remarks>
+        /// WinUI: https://github.com/microsoft/microsoft-ui-xaml/blob/93742a178db8f625ba9299f62c21f656e0b195ad/dxaml/xcp/core/core/elements/geometry.cpp#L1072-L1079
+        /// </remarks>
+        /// <param name="context">The geometry context to draw into.</param>
+        /// <param name="keypoints">The rounded rectangle keypoints defining the rectangle to draw.</param>
+        public static void DrawRoundedCornersRectangle(
+            StreamGeometryContext context,
+            ref RoundedRectKeypoints keypoints)
+        {
+            double radiusX;
+            double radiusY;
+
+            context.BeginFigure(keypoints.TopLeft, isFilled: true);
+
+            // Top
+            context.LineTo(keypoints.TopRight);
+
+            // TopRight corner
+            radiusX = keypoints.RightTop.X - keypoints.TopRight.X;
+            radiusY = keypoints.TopRight.Y - keypoints.RightTop.Y;
+            radiusX = radiusX > 0 ? radiusX : -radiusX;
+            radiusY = radiusY > 0 ? radiusY : -radiusY;
+
+            context.ArcTo(
+                keypoints.RightTop,
+                new Size(radiusX, radiusY),
+                rotationAngle: 0.0,
+                isLargeArc: false,
+                SweepDirection.Clockwise);
+
+            // Right
+            context.LineTo(keypoints.RightBottom);
+
+            // BottomRight corner
+            radiusX = keypoints.RightBottom.X - keypoints.BottomRight.X;
+            radiusY = keypoints.BottomRight.Y - keypoints.RightBottom.Y;
+            radiusX = radiusX > 0 ? radiusX : -radiusX;
+            radiusY = radiusY > 0 ? radiusY : -radiusY;
+
+            if (radiusX != 0 || radiusY != 0)
+            {
+                context.ArcTo(
+                    keypoints.BottomRight,
+                    new Size(radiusX, radiusY),
+                    rotationAngle: 0.0,
+                    isLargeArc: false,
+                    SweepDirection.Clockwise);
+            }
+
+            // Bottom
+            context.LineTo(keypoints.BottomLeft);
+
+            // BottomLeft corner
+            radiusX = keypoints.BottomLeft.X - keypoints.LeftBottom.X;
+            radiusY = keypoints.BottomLeft.Y - keypoints.LeftBottom.Y;
+            radiusX = radiusX > 0 ? radiusX : -radiusX;
+            radiusY = radiusY > 0 ? radiusY : -radiusY;
+
+            if (radiusX != 0 || radiusY != 0)
+            {
+                context.ArcTo(
+                    keypoints.LeftBottom,
+                    new Size(radiusX, radiusY),
+                    rotationAngle: 0.0,
+                    isLargeArc: false,
+                    SweepDirection.Clockwise);
+            }
+
+            // Left
+            context.LineTo(keypoints.LeftTop);
+
+            // TopLeft corner
+            radiusX = keypoints.TopLeft.X - keypoints.LeftTop.X;
+            radiusY = keypoints.TopLeft.Y - keypoints.LeftTop.Y;
+            radiusX = radiusX > 0 ? radiusX : -radiusX;
+            radiusY = radiusY > 0 ? radiusY : -radiusY;
+
+            if (radiusX != 0 || radiusY != 0)
+            {
+                context.ArcTo(
+                    keypoints.TopLeft,
+                    new Size(radiusX, radiusY),
+                    rotationAngle: 0.0,
+                    isLargeArc: false,
+                    SweepDirection.Clockwise);
+            }
+
+            context.EndFigure(isClosed: true);
+        }
+
+        /// <summary>
+        /// Draws a new rounded rectangle within the given geometry context.
+        /// Warning: The caller must manage and dispose the <see cref="StreamGeometryContext"/> externally.
+        /// </summary>
+        /// <param name="context">The geometry context to draw into.</param>
+        /// <param name="rect">The existing rectangle dimensions without corner radii.</param>
+        /// <param name="radiusX">The radius on the X-axis used to round the corners of the rectangle.</param>
+        /// <param name="radiusY">The radius on the Y-axis used to round the corners of the rectangle.</param>
+        public static void DrawRoundedCornersRectangle(
+            StreamGeometryContext context,
+            Rect rect,
+            double radiusX,
+            double radiusY)
+        {
+            var arcSize = new Size(radiusX, radiusY);
+
+            // The rectangle is constructed as follows:
+            //
+            //   (origin)
+            //   Corner 4            Corner 1
+            //   Top/Left  Line 1    Top/Right
+            //      \_   __________   _/
+            //          |          |
+            //   Line 4 |          | Line 2
+            //       _  |__________|  _
+            //      /      Line 3      \
+            //   Corner 3            Corner 2
+            //   Bottom/Left         Bottom/Right
+            //
+            // - Lines 1,3 follow the deflated rectangle bounds minus RadiusX
+            // - Lines 2,4 follow the deflated rectangle bounds minus RadiusY
+            // - All corners are constructed using elliptical arcs 
+
+            context.BeginFigure(new Point(rect.Left + radiusX, rect.Top), isFilled: true);
+
+            // Line 1 + Corner 1
+            context.LineTo(new Point(rect.Right - radiusX, rect.Top));
+            context.ArcTo(
+                new Point(rect.Right, rect.Top + radiusY),
+                arcSize,
+                rotationAngle: PiOver2,
+                isLargeArc: false,
+                SweepDirection.Clockwise);
+
+            // Line 2 + Corner 2
+            context.LineTo(new Point(rect.Right, rect.Bottom - radiusY));
+            context.ArcTo(
+                new Point(rect.Right - radiusX, rect.Bottom),
+                arcSize,
+                rotationAngle: PiOver2,
+                isLargeArc: false,
+                SweepDirection.Clockwise);
+
+            // Line 3 + Corner 3
+            context.LineTo(new Point(rect.Left + radiusX, rect.Bottom));
+            context.ArcTo(
+                new Point(rect.Left, rect.Bottom - radiusY),
+                arcSize,
+                rotationAngle: PiOver2,
+                isLargeArc: false,
+                SweepDirection.Clockwise);
+
+            // Line 4 + Corner 4
+            context.LineTo(new Point(rect.Left, rect.Top + radiusY));
+            context.ArcTo(
+                new Point(rect.Left + radiusX, rect.Top),
+                arcSize,
+                rotationAngle: PiOver2,
+                isLargeArc: false,
+                SweepDirection.Clockwise);
+
+            context.EndFigure(isClosed: true);
+        }
+
+        /// <summary>
+        /// Calculates the keypoints of a rounded rectangle based on the algorithm in WinUI.
+        /// These keypoints may then be drawn or transformed into other types.
+        /// </summary>
+        /// <param name="outerBounds">The outer bounds of the rounded rectangle.
+        /// This should be the overall bounds and size of the shape/control without any
+        /// corner radii or border thickness adjustments.</param>
+        /// <param name="borderThickness">The unadjusted border thickness of the rounded rectangle.</param>
+        /// <param name="cornerRadius">The unadjusted corner radii of the rounded rectangle.
+        /// The corner radius is defined to be the middle of the border stroke (center of the border).</param>
+        /// <param name="sizing">The sizing mode used to calculate the final rounded rectangle size.</param>
+        /// <returns>New rounded rectangle keypoints.</returns>
+        public static RoundedRectKeypoints CalculateRoundedCornersRectangleWinUI(
+            Rect outerBounds,
+            Thickness borderThickness,
+            CornerRadius cornerRadius,
+            BackgroundSizing sizing)
+        {
+            // This was initially derived from WinUI:
+            //  - CGeometryBuilder::CalculateRoundedCornersRectangle
+            //    https://github.com/microsoft/microsoft-ui-xaml/blob/93742a178db8f625ba9299f62c21f656e0b195ad/dxaml/xcp/core/core/elements/geometry.cpp#L862-L869
+            //
+            // It has been modified to accept a BackgroundSizing parameter directly as well
+            // as to support BackgroundSizing.CenterBorder.
+            //
+            // Keep in mind:
+            //   > In Xaml, the corner radius is defined to be the middle of the stroke
+            //   > (i.e. half the border thickness extends to either side).
+
+            bool fOuter;
+            Rect boundRect = outerBounds;
+
+            if (sizing == BackgroundSizing.InnerBorderEdge)
+            {
+                boundRect = outerBounds.Deflate(borderThickness);
+                fOuter = false;
+            }
+            else if (sizing == BackgroundSizing.OuterBorderEdge)
+            {
+                fOuter = true;
+            }
+            else // CenterBorder
+            {
+                // This is a trick to support a 3rd state (CenterBorder) using the same WinUI-based algorithm.
+                // The WinUI algorithm only supports the fOuter = True|False parameter.
+                boundRect = outerBounds.Deflate(borderThickness * 0.5);
+                fOuter = false;
+            }
+
+            // Start of WinUI converted code
+            // WinUI's Point struct fields can be modified directly, Avalonia's Point is read-only.
+            // Therefore, we will use doubles for calculation so multiple Point structs aren't
+            // required during calculations -- everything can be done with these double variables.
+            double fLeftTop;
+            double fLeftBottom;
+            double fTopLeft;
+            double fTopRight;
+            double fRightTop;
+            double fRightBottom;
+            double fBottomLeft;
+            double fBottomRight;
+
+            double left;
+            double right;
+            double top;
+            double bottom;
+
+            // If the caller wants to take the border into account
+            // initialize the borders variables
+            if (borderThickness != default)
+            {
+                left = 0.5 * borderThickness.Left;
+                right = 0.5 * borderThickness.Right;
+                top = 0.5 * borderThickness.Top;
+                bottom = 0.5 * borderThickness.Bottom;
+            }
+            else
+            {
+                left = 0.0;
+                right = 0.0;
+                top = 0.0;
+                bottom = 0.0;
+            }
+
+            // The following if/else block initializes the variables
+            // of which the points of the path will be created
+            // In case of outer, add the border - if any.
+            // Otherwise (inner rectangle) subtract the border - if any
+            if (fOuter)
+            {
+                if (MathUtilities.AreClose(cornerRadius.TopLeft, 0.0, Epsilon))
+                {
+                    fLeftTop = 0.0;
+                    fTopLeft = 0.0;
+                }
+                else
+                {
+                    fLeftTop = cornerRadius.TopLeft + left;
+                    fTopLeft = cornerRadius.TopLeft + top;
+                }
+
+                if (MathUtilities.AreClose(cornerRadius.TopRight, 0.0, Epsilon))
+                {
+                    fTopRight = 0.0;
+                    fRightTop = 0.0;
+                }
+                else
+                {
+                    fTopRight = cornerRadius.TopRight + top;
+                    fRightTop = cornerRadius.TopRight + right;
+                }
+
+                if (MathUtilities.AreClose(cornerRadius.BottomRight, 0.0, Epsilon))
+                {
+                    fRightBottom = 0.0;
+                    fBottomRight = 0.0;
+                }
+                else
+                {
+                    fRightBottom = cornerRadius.BottomRight + right;
+                    fBottomRight = cornerRadius.BottomRight + bottom;
+                }
+
+                if (MathUtilities.AreClose(cornerRadius.BottomLeft, 0.0, Epsilon))
+                {
+                    fBottomLeft = 0.0;
+                    fLeftBottom = 0.0;
+                }
+                else
+                {
+                    fBottomLeft = cornerRadius.BottomLeft + bottom;
+                    fLeftBottom = cornerRadius.BottomLeft + left;
+                }
+            }
+            else
+            {
+                fLeftTop = Math.Max(0.0, cornerRadius.TopLeft - left);
+                fTopLeft = Math.Max(0.0, cornerRadius.TopLeft - top);
+                fTopRight = Math.Max(0.0, cornerRadius.TopRight - top);
+                fRightTop = Math.Max(0.0, cornerRadius.TopRight - right);
+                fRightBottom = Math.Max(0.0, cornerRadius.BottomRight - right);
+                fBottomRight = Math.Max(0.0, cornerRadius.BottomRight - bottom);
+                fBottomLeft = Math.Max(0.0, cornerRadius.BottomLeft - bottom);
+                fLeftBottom = Math.Max(0.0, cornerRadius.BottomLeft - left);
+            }
+
+            double topLeftX = fLeftTop;
+            double topLeftY = 0;
+
+            double topRightX = boundRect.Width - fRightTop;
+            double topRightY = 0;
+
+            double rightTopX = boundRect.Width;
+            double rightTopY = fTopRight;
+
+            double rightBottomX = boundRect.Width;
+            double rightBottomY = boundRect.Height - fBottomRight;
+
+            double bottomRightX = boundRect.Width - fRightBottom;
+            double bottomRightY = boundRect.Height;
+
+            double bottomLeftX = fLeftBottom;
+            double bottomLeftY = boundRect.Height;
+
+            double leftBottomX = 0;
+            double leftBottomY = boundRect.Height - fBottomLeft;
+
+            double leftTopX = 0;
+            double leftTopY = fTopLeft;
+
+            // check keypoints for overlap and resolve by partitioning radii according to
+            // the percentage of each one.
+
+            // top edge
+            if (topLeftX > topRightX)
+            {
+                double v = (fLeftTop) / (fLeftTop + fRightTop) * boundRect.Width;
+                topLeftX = v;
+                topRightX = v;
+            }
+            // right edge
+            if (rightTopY > rightBottomY)
+            {
+                double v = (fTopRight) / (fTopRight + fBottomRight) * boundRect.Height;
+                rightTopY = v;
+                rightBottomY = v;
+            }
+            // bottom edge
+            if (bottomRightX < bottomLeftX)
+            {
+                double v = (fLeftBottom) / (fLeftBottom + fRightBottom) * boundRect.Width;
+                bottomRightX = v;
+                bottomLeftX = v;
+            }
+            // left edge
+            if (leftBottomY < leftTopY)
+            {
+                double v = (fTopLeft) / (fTopLeft + fBottomLeft) * boundRect.Height;
+                leftBottomY = v;
+                leftTopY = v;
+            }
+
+            // The above code does all calculations without taking into consideration X/Y absolute position.
+            // In WinUI, this is compensated for in DrawRoundedCornersRectangle(); however, we do this here directly
+            // when the final keypoints are being created.
+            var keypoints = new RoundedRectKeypoints();
+            keypoints.TopLeft = new Point(
+                boundRect.X + topLeftX,
+                boundRect.Y + topLeftY);
+            keypoints.TopRight = new Point(
+                boundRect.X + topRightX,
+                boundRect.Y + topRightY);
+
+            keypoints.RightTop = new Point(
+                boundRect.X + rightTopX,
+                boundRect.Y + rightTopY);
+            keypoints.RightBottom = new Point(
+                boundRect.X + rightBottomX,
+                boundRect.Y + rightBottomY);
+
+            keypoints.BottomRight = new Point(
+                boundRect.X + bottomRightX,
+                boundRect.Y + bottomRightY);
+            keypoints.BottomLeft = new Point(
+                boundRect.X + bottomLeftX,
+                boundRect.Y + bottomLeftY);
+
+            keypoints.LeftBottom = new Point(
+                boundRect.X + leftBottomX,
+                boundRect.Y + leftBottomY);
+            keypoints.LeftTop = new Point(
+                boundRect.X + leftTopX,
+                boundRect.Y + leftTopY);
+
+            return keypoints;
+        }
+
+        /// <summary>
+        /// Represents the keypoints of a rounded rectangle.
+        /// These keypoints can be shared between methods and turned into geometry.
+        /// </summary>
+        /// <remarks>
+        /// A rounded rectangle is the base geometric shape used when drawing borders.
+        /// It is a superset of a simple rectangle (which has corner radii set to zero).
+        /// These keypoints can be combined together to produce geometries for both background
+        /// and border elements.
+        /// </remarks>
+        internal struct RoundedRectKeypoints
+        {
+            // The following keypoints are defined for a rounded rectangle:
+            //
+            //       TopLeft                                  TopRight
+            //              *--------------------------------*
+            // (start)     /                                  \
+            //    LeftTop *                                    * RightTop
+            //            |                                    |
+            //            |                                    |
+            // LeftBottom *                                    * RightBottom
+            //             \                                  /
+            //              *--------------------------------*
+            //    BottomLeft                                  BottomRight
+            //
+            // Or, for a simple rectangle without corner radii:
+            //
+            //    TopLeft = LeftTop                   TopRight = RightTop
+            //  (start)   *------------------------------------*
+            //            |                                    |
+            //            |                                    |
+            //            *------------------------------------*
+            // BottomLeft = LeftBottom             BottomRight = RightBottom
+
+            /// <summary>
+            /// Initializes a new instance of the <see cref="RoundedRectKeypoints"/> struct.
+            /// </summary>
+            public RoundedRectKeypoints()
+            {
+            }
+
+            /// <summary>
+            /// Initializes a new instance of the <see cref="RoundedRectKeypoints"/> struct.
+            /// </summary>
+            /// <param name="roundedRect">An existing <see cref="RoundedRect"/> to initialize keypoints with.</param>
+            public RoundedRectKeypoints(RoundedRect roundedRect)
+            {
+                LeftTop = new Point(
+                    roundedRect.Rect.TopLeft.X,
+                    roundedRect.Rect.TopLeft.Y + roundedRect.RadiiTopLeft.Y);
+                TopLeft = new Point(
+                    roundedRect.Rect.TopLeft.X + roundedRect.RadiiTopLeft.X,
+                    roundedRect.Rect.TopLeft.Y);
+                TopRight = new Point(
+                    roundedRect.Rect.TopRight.X - roundedRect.RadiiTopRight.X,
+                    roundedRect.Rect.TopRight.Y);
+                RightTop = new Point(
+                    roundedRect.Rect.TopRight.X,
+                    roundedRect.Rect.TopRight.Y + roundedRect.RadiiTopRight.Y);
+                RightBottom = new Point(
+                    roundedRect.Rect.BottomRight.X,
+                    roundedRect.Rect.BottomRight.Y - roundedRect.RadiiBottomRight.Y);
+                BottomRight = new Point(
+                    roundedRect.Rect.BottomRight.X - roundedRect.RadiiBottomRight.X,
+                    roundedRect.Rect.BottomRight.Y);
+                BottomLeft = new Point(
+                    roundedRect.Rect.BottomLeft.X + roundedRect.RadiiBottomLeft.X,
+                    roundedRect.Rect.BottomLeft.Y);
+                LeftBottom = new Point(
+                    roundedRect.Rect.BottomLeft.X,
+                    roundedRect.Rect.BottomRight.Y - roundedRect.RadiiBottomLeft.Y);
+            }
+
+            /// <summary>
+            /// Gets the topmost point in the left line segment of the rectangle.
+            /// </summary>
+            public Point LeftTop { get; set; }
+
+            /// <summary>
+            /// Gets the leftmost point in the top line segment of the rectangle.
+            /// </summary>
+            public Point TopLeft { get; set; }
+
+            /// <summary>
+            /// Gets the rightmost point in the top line segment of the rectangle.
+            /// </summary>
+            public Point TopRight { get; set; }
+
+            /// <summary>
+            /// Gets the topmost point in the right line segment of the rectangle.
+            /// </summary>
+            public Point RightTop { get; set; }
+
+            /// <summary>
+            /// Gets the bottommost point in the right line segment of the rectangle.
+            /// </summary>
+            public Point RightBottom { get; set; }
+
+            /// <summary>
+            /// Gets the rightmost point in the bottom line segment of the rectangle.
+            /// </summary>
+            public Point BottomRight { get; set; }
+
+            /// <summary>
+            /// Gets the leftmost point in the bottom line segment of the rectangle.
+            /// </summary>
+            public Point BottomLeft { get; set; }
+
+            /// <summary>
+            /// Gets the bottommost point in the left line segment of the rectangle.
+            /// </summary>
+            public Point LeftBottom { get; set; }
+
+            /// <summary>
+            /// Gets a value indicating whether the rounded rectangle is actually rounded on
+            /// any corner. If false the key points represent a simple rectangle.
+            /// </summary>
+            public bool IsRounded
+            {
+                get
+                {
+                    return (TopLeft != LeftTop ||
+                            TopRight != RightTop ||
+                            BottomLeft != LeftBottom ||
+                            BottomRight != RightBottom);
+                }
+            }
+
+            /// <summary>
+            /// Converts the keypoints into a simple rectangle (with no corners).
+            /// This is equivalent to the outer rectangle with zero corner radii.
+            /// </summary>
+            /// <remarks>
+            /// Warning: This will force the keypoints into a simple rectangle without
+            /// any rounded corners. Use <see cref="IsRounded"/> to determine if corner
+            /// information is otherwise available.
+            /// </remarks>
+            /// <returns>A new rectangle representing the keypoints.</returns>
+            public Rect ToRect()
+            {
+                return new Rect(
+                    topLeft: new Point(
+                        x: LeftTop.X,
+                        y: TopLeft.Y),
+                    bottomRight: new Point(
+                        x: RightBottom.X,
+                        y: BottomRight.Y));
+            }
+
+            /// <summary>
+            /// Converts the keypoints into a rounded rectangle with elliptical corner radii.
+            /// </summary>
+            /// <remarks>
+            /// Elliptical corner radius (represented by <see cref="Vector"/>) is more powerful
+            /// than circular corner radius (represented by a <see cref="CornerRadius"/>).
+            /// Elliptical is a superset of circular.
+            /// </remarks>
+            /// <returns>A new rounded rectangle representing the keypoints.</returns>
+            public RoundedRect ToRoundedRect()
+            {
+                return new RoundedRect(
+                    ToRect(),
+                    radiiTopLeft: new Vector(
+                        x: TopLeft.X - LeftTop.X,
+                        y: LeftTop.Y - TopLeft.Y),
+                    radiiTopRight: new Vector(
+                        x: RightTop.X - TopRight.X,
+                        y: RightTop.Y - TopRight.Y),
+                    radiiBottomRight: new Vector(
+                        x: RightBottom.X - BottomRight.X,
+                        y: BottomRight.Y - RightBottom.Y),
+                    radiiBottomLeft: new Vector(
+                        x: BottomLeft.X - LeftBottom.X,
+                        y: BottomLeft.Y - LeftBottom.Y));
+            }
+        }
+    }
+}
diff --git a/src/Avalonia.Base/Media/RectangleGeometry.cs b/src/Avalonia.Base/Media/RectangleGeometry.cs
index 01771242a7c..e97a240fc4d 100644
--- a/src/Avalonia.Base/Media/RectangleGeometry.cs
+++ b/src/Avalonia.Base/Media/RectangleGeometry.cs
@@ -1,4 +1,3 @@
-using System;
 using Avalonia.Platform;
 
 namespace Avalonia.Media
@@ -8,6 +7,18 @@ namespace Avalonia.Media
     /// </summary>
     public class RectangleGeometry : Geometry
     {
+        /// <summary>
+        /// Defines the <see cref="RadiusX"/> property.
+        /// </summary>
+        public static readonly StyledProperty<double> RadiusXProperty =
+            AvaloniaProperty.Register<RectangleGeometry, double>(nameof(RadiusX));
+
+        /// <summary>
+        /// Defines the <see cref="RadiusY"/> property.
+        /// </summary>
+        public static readonly StyledProperty<double> RadiusYProperty =
+            AvaloniaProperty.Register<RectangleGeometry, double>(nameof(RadiusY));
+
         /// <summary>
         /// Defines the <see cref="Rect"/> property.
         /// </summary>
@@ -16,7 +27,10 @@ public class RectangleGeometry : Geometry
 
         static RectangleGeometry()
         {
-            AffectsGeometry(RectProperty);
+            AffectsGeometry(
+                RadiusXProperty,
+                RadiusYProperty,
+                RectProperty);
         }
 
         /// <summary>
@@ -35,6 +49,47 @@ public RectangleGeometry(Rect rect)
             Rect = rect;
         }
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RectangleGeometry"/> class.
+        /// </summary>
+        /// <param name="rect">The rectangle bounds.</param>
+        /// <param name="radiusX">The radius on the X-axis used to round the corners of the rectangle.</param>
+        /// <param name="radiusY">The radius on the Y-axis used to round the corners of the rectangle.</param>
+        public RectangleGeometry(Rect rect, double radiusX, double radiusY)
+        {
+            Rect = rect;
+            RadiusX = radiusX;
+            RadiusY = radiusY;
+        }
+
+        /// <summary>
+        /// Gets or sets the radius on the X-axis used to round the corners of the rectangle.
+        /// Corner radii are represented by an ellipse so this is the X-axis width of the ellipse.
+        /// </summary>
+        /// <remarks>
+        /// In order for this property to be used, <see cref="Rect"/> must not be set
+        /// (equal to the default <see cref="Avalonia.Rect"/> value).
+        /// </remarks>
+        public double RadiusX
+        {
+            get => GetValue(RadiusXProperty);
+            set => SetValue(RadiusXProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets the radius on the Y-axis used to round the corners of the rectangle.
+        /// Corner radii are represented by an ellipse so this is the Y-axis height of the ellipse.
+        /// </summary>
+        /// <remarks>
+        /// In order for this property to be used, <see cref="Rect"/> must not be set
+        /// (equal to the default <see cref="Avalonia.Rect"/> value).
+        /// </remarks>
+        public double RadiusY
+        {
+            get => GetValue(RadiusYProperty);
+            set => SetValue(RadiusYProperty, value);
+        }
+
         /// <summary>
         /// Gets or sets the bounds of the rectangle.
         /// </summary>
@@ -49,9 +104,25 @@ public Rect Rect
 
         private protected sealed override IGeometryImpl? CreateDefiningGeometry()
         {
+            double radiusX = RadiusX;
+            double radiusY = RadiusY;
             var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
 
-            return factory.CreateRectangleGeometry(Rect);
+            if (radiusX == 0 && radiusY == 0)
+            {
+                // Optimization when there are no corner radii
+                return factory.CreateRectangleGeometry(Rect);
+            }
+            else
+            {
+                var geometry = factory.CreateStreamGeometry();
+                using (var ctx = new StreamGeometryContext(geometry.Open()))
+                {
+                    GeometryBuilder.DrawRoundedCornersRectangle(ctx, Rect, radiusX, radiusY);
+                }
+
+                return geometry;
+            }
         }
     }
 }
diff --git a/src/Avalonia.Base/RoundedRect.cs b/src/Avalonia.Base/RoundedRect.cs
index 0b23a8c0caf..c39dbd1b1f2 100644
--- a/src/Avalonia.Base/RoundedRect.cs
+++ b/src/Avalonia.Base/RoundedRect.cs
@@ -36,8 +36,13 @@ public override int GetHashCode()
         public Vector RadiiTopRight { get; }
         public Vector RadiiBottomLeft { get; }
         public Vector RadiiBottomRight { get; }
-        
-        public RoundedRect(Rect rect, Vector radiiTopLeft, Vector radiiTopRight, Vector radiiBottomRight, Vector radiiBottomLeft)
+
+        public RoundedRect(
+            Rect rect,
+            Vector radiiTopLeft,
+            Vector radiiTopRight,
+            Vector radiiBottomRight,
+            Vector radiiBottomLeft)
         {
             Rect = rect;
             RadiiTopLeft = radiiTopLeft;
@@ -46,7 +51,11 @@ public RoundedRect(Rect rect, Vector radiiTopLeft, Vector radiiTopRight, Vector
             RadiiBottomLeft = radiiBottomLeft;
         }
 
-        public RoundedRect(Rect rect, double radiusTopLeft, double radiusTopRight, double radiusBottomRight,
+        public RoundedRect(
+            Rect rect,
+            double radiusTopLeft,
+            double radiusTopRight,
+            double radiusBottomRight,
             double radiusBottomLeft)
             : this(rect,
                 new Vector(radiusTopLeft, radiusTopLeft),
@@ -55,34 +64,28 @@ public RoundedRect(Rect rect, double radiusTopLeft, double radiusTopRight, doubl
                 new Vector(radiusBottomLeft, radiusBottomLeft)
             )
         {
-            
         }
 
         public RoundedRect(Rect rect, Vector radii) : this(rect, radii, radii, radii, radii) 
         {
-            
         }
 
         public RoundedRect(Rect rect, double radiusX, double radiusY) : this(rect, new Vector(radiusX, radiusY))
         {
-            
         }
 
         public RoundedRect(Rect rect, double radius) : this(rect, radius, radius)
         {
-            
         }
 
         public RoundedRect(Rect rect) : this(rect, 0)
         {
-            
         }
 
         public RoundedRect(in Rect bounds, in CornerRadius radius) : this(bounds,
             radius.TopLeft, radius.TopRight,
             radius.BottomRight, radius.BottomLeft)
         {
-
         }
 
         public static implicit operator RoundedRect(Rect r) => new RoundedRect(r);
@@ -99,7 +102,7 @@ public RoundedRect Inflate(double dx, double dy)
         {
             return Deflate(-dx, -dy);
         }
-        
+
         public unsafe RoundedRect Deflate(double dx, double dy)
         {
             if (!IsRounded)
@@ -143,7 +146,7 @@ public unsafe RoundedRect Deflate(double dx, double dy)
             return new RoundedRect(new Rect(left, top, right - left, bottom - top),
                 radii[0], radii[1], radii[2], radii[3]);
         }
-        
+
         /// <summary>
         /// This method should be used internally to check for the rect emptiness
         /// Once we add support for WPF-like empty rects, there will be an actual implementation
diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs
index e7373a813e4..4eb0274ec72 100644
--- a/src/Avalonia.Controls/Border.cs
+++ b/src/Avalonia.Controls/Border.cs
@@ -23,6 +23,14 @@ public partial class Border : Decorator, IVisualWithRoundRectClip
         public static readonly StyledProperty<IBrush?> BackgroundProperty =
             AvaloniaProperty.Register<Border, IBrush?>(nameof(Background));
 
+        /// <summary>
+        /// Defines the <see cref="BackgroundSizing"/> property.
+        /// </summary>
+        public static readonly StyledProperty<BackgroundSizing> BackgroundSizingProperty =
+            AvaloniaProperty.Register<Border, BackgroundSizing>(
+                nameof(BackgroundSizing),
+                BackgroundSizing.CenterBorder);
+
         /// <summary>
         /// Defines the <see cref="BorderBrush"/> property.
         /// </summary>
@@ -59,6 +67,7 @@ static Border()
         {
             AffectsRender<Border>(
                 BackgroundProperty,
+                BackgroundSizingProperty,
                 BorderBrushProperty,
                 BorderThicknessProperty,
                 CornerRadiusProperty,
@@ -91,6 +100,15 @@ public IBrush? Background
             set => SetValue(BackgroundProperty, value);
         }
 
+        /// <summary>
+        /// Gets or sets how the background is drawn relative to the border.
+        /// </summary>
+        public BackgroundSizing BackgroundSizing
+        {
+            get => GetValue(BackgroundSizingProperty);
+            set => SetValue(BackgroundSizingProperty, value);
+        }
+
         /// <summary>
         /// Gets or sets a brush with which to paint the border.
         /// </summary>
@@ -168,6 +186,7 @@ public sealed override void Render(DrawingContext context)
                 Bounds.Size,
                 LayoutThickness,
                 CornerRadius,
+                BackgroundSizing,
                 Background,
                 BorderBrush,
                 BoxShadow);
diff --git a/src/Avalonia.Controls/ContentControl.cs b/src/Avalonia.Controls/ContentControl.cs
index 4d32658e0dc..cf3f1c845a6 100644
--- a/src/Avalonia.Controls/ContentControl.cs
+++ b/src/Avalonia.Controls/ContentControl.cs
@@ -47,6 +47,7 @@ static ContentControl()
             {
                 Name = "PART_ContentPresenter",
                 [~BackgroundProperty] = new TemplateBinding(BackgroundProperty),
+                [~BackgroundSizingProperty] = new TemplateBinding(BackgroundSizingProperty),
                 [~BorderBrushProperty] = new TemplateBinding(BorderBrushProperty),
                 [~BorderThicknessProperty] = new TemplateBinding(BorderThicknessProperty),
                 [~CornerRadiusProperty] = new TemplateBinding(CornerRadiusProperty),
diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
index 02a8fb7d2c1..76a882df129 100644
--- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
@@ -25,6 +25,12 @@ public class ContentPresenter : Control
         public static readonly StyledProperty<IBrush?> BackgroundProperty =
             Border.BackgroundProperty.AddOwner<ContentPresenter>();
 
+        /// <summary>
+        /// Defines the <see cref="BackgroundSizing"/> property.
+        /// </summary>
+        public static readonly StyledProperty<BackgroundSizing> BackgroundSizingProperty =
+            Border.BackgroundSizingProperty.AddOwner<ContentPresenter>();
+
         /// <summary>
         /// Defines the <see cref="BorderBrush"/> property.
         /// </summary>
@@ -171,10 +177,12 @@ static ContentPresenter()
         {
             AffectsRender<ContentPresenter>(
                 BackgroundProperty,
+                BackgroundSizingProperty,
                 BorderBrushProperty,
                 BorderThicknessProperty,
-                CornerRadiusProperty,
-                BoxShadowProperty);
+                BoxShadowProperty,
+                CornerRadiusProperty);
+
             AffectsArrange<ContentPresenter>(HorizontalContentAlignmentProperty, VerticalContentAlignmentProperty);
             AffectsMeasure<ContentPresenter>(BorderThicknessProperty, PaddingProperty);
         }
@@ -184,45 +192,42 @@ public ContentPresenter()
             UpdatePseudoClasses();
         }
 
-        /// <summary>
-        /// Gets or sets a brush with which to paint the background.
-        /// </summary>
+        /// <inheritdoc cref="Border.Background"/>
         public IBrush? Background
         {
             get => GetValue(BackgroundProperty);
             set => SetValue(BackgroundProperty, value);
         }
 
-        /// <summary>
-        /// Gets or sets a brush with which to paint the border.
-        /// </summary>
+        /// <inheritdoc cref="Border.BackgroundSizing"/>
+        public BackgroundSizing BackgroundSizing
+        {
+            get => GetValue(BackgroundSizingProperty);
+            set => SetValue(BackgroundSizingProperty, value);
+        }
+
+        /// <inheritdoc cref="Border.BorderBrush"/>
         public IBrush? BorderBrush
         {
             get => GetValue(BorderBrushProperty);
             set => SetValue(BorderBrushProperty, value);
         }
 
-        /// <summary>
-        /// Gets or sets the thickness of the border.
-        /// </summary>
+        /// <inheritdoc cref="Border.BorderThickness"/>
         public Thickness BorderThickness
         {
             get => GetValue(BorderThicknessProperty);
             set => SetValue(BorderThicknessProperty, value);
         }
 
-        /// <summary>
-        /// Gets or sets the radius of the border rounded corners.
-        /// </summary>
+        /// <inheritdoc cref="Border.CornerRadius"/>
         public CornerRadius CornerRadius
         {
             get => GetValue(CornerRadiusProperty);
             set => SetValue(CornerRadiusProperty, value);
         }
 
-        /// <summary>
-        /// Gets or sets the box shadow effect parameters
-        /// </summary>
+        /// <inheritdoc cref="Border.BoxShadow"/>
         public BoxShadows BoxShadow
         {
             get => GetValue(BoxShadowProperty);
@@ -541,7 +546,14 @@ private void VerifyScale()
         /// <inheritdoc/>
         public sealed override void Render(DrawingContext context)
         {
-            _borderRenderer.Render(context, Bounds.Size, LayoutThickness, CornerRadius, Background, BorderBrush,
+            _borderRenderer.Render(
+                context,
+                Bounds.Size,
+                LayoutThickness,
+                CornerRadius,
+                BackgroundSizing,
+                Background,
+                BorderBrush,
                 BoxShadow);
         }
 
diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs
index c232fdcc12d..1fef3821592 100644
--- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs
+++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs
@@ -22,6 +22,12 @@ public class TemplatedControl : Control
         public static readonly StyledProperty<IBrush?> BackgroundProperty =
             Border.BackgroundProperty.AddOwner<TemplatedControl>();
 
+        /// <summary>
+        /// Defines the <see cref="BackgroundSizing"/> property.
+        /// </summary>
+        public static readonly StyledProperty<BackgroundSizing> BackgroundSizingProperty =
+            Border.BackgroundSizingProperty.AddOwner<TemplatedControl>();
+
         /// <summary>
         /// Defines the <see cref="BorderBrush"/> property.
         /// </summary>
@@ -131,6 +137,15 @@ public IBrush? Background
             set => SetValue(BackgroundProperty, value);
         }
 
+        /// <summary>
+        /// Gets or sets how the control's background is drawn relative to the control's border.
+        /// </summary>
+        public BackgroundSizing BackgroundSizing
+        {
+            get => GetValue(BackgroundSizingProperty);
+            set => SetValue(BackgroundSizingProperty, value);
+        }
+
         /// <summary>
         /// Gets or sets the brush used to draw the control's border.
         /// </summary>
diff --git a/src/Avalonia.Controls/Shapes/Rectangle.cs b/src/Avalonia.Controls/Shapes/Rectangle.cs
index d60180bab4b..cfccf0d588b 100644
--- a/src/Avalonia.Controls/Shapes/Rectangle.cs
+++ b/src/Avalonia.Controls/Shapes/Rectangle.cs
@@ -7,8 +7,6 @@ namespace Avalonia.Controls.Shapes
     /// </summary>
     public class Rectangle : Shape
     {
-        private const double PiOver2 = 1.57079633; // 90 deg to rad
-
         /// <summary>
         /// Defines the <see cref="RadiusX"/> property.
         /// </summary>
@@ -30,20 +28,14 @@ static Rectangle()
                 StrokeThicknessProperty);
         }
 
-        /// <summary>
-        /// Gets or sets the radius on the X-axis used to round the corners of the rectangle.
-        /// Corner radii are represented by an ellipse so this is the X-axis width of the ellipse.
-        /// </summary>
+        /// <inheritdoc cref="RectangleGeometry.RadiusX"/>
         public double RadiusX
         {
             get => GetValue(RadiusXProperty);
             set => SetValue(RadiusXProperty, value);
         }
 
-        /// <summary>
-        /// Gets or sets the radius on the Y-axis used to round the corners of the rectangle.
-        /// Corner radii are represented by an ellipse so this is the Y-axis height of the ellipse.
-        /// </summary>
+        /// <inheritdoc cref="RectangleGeometry.RadiusY"/>
         public double RadiusY
         {
             get => GetValue(RadiusYProperty);
@@ -53,85 +45,9 @@ public double RadiusY
         /// <inheritdoc/>
         protected override Geometry CreateDefiningGeometry()
         {
-            // TODO: If RectangleGeometry ever supports RadiusX/Y like in WPF,
-            // this code can be removed/combined with that implementation
-
-            double x = RadiusX;
-            double y = RadiusY;
-
-            if (x == 0 && y == 0)
-            {
-                // Optimization when there are no corner radii
-                var rect = new Rect(Bounds.Size).Deflate(StrokeThickness / 2);
-                return new RectangleGeometry(rect);
-            }
-            else
-            {
-                var rect = new Rect(Bounds.Size).Deflate(StrokeThickness / 2);
-                var geometry = new StreamGeometry();
-                var arcSize = new Size(x, y);
-
-                using (StreamGeometryContext context = geometry.Open())
-                {
-                    // The rectangle is constructed as follows:
-                    //
-                    //   (origin)
-                    //   Corner 4            Corner 1
-                    //   Top/Left  Line 1    Top/Right
-                    //      \_   __________   _/
-                    //          |          |
-                    //   Line 4 |          | Line 2
-                    //       _  |__________|  _
-                    //      /      Line 3      \
-                    //   Corner 3            Corner 2
-                    //   Bottom/Left         Bottom/Right
-                    //
-                    // - Lines 1,3 follow the deflated rectangle bounds minus RadiusX
-                    // - Lines 2,4 follow the deflated rectangle bounds minus RadiusY
-                    // - All corners are constructed using elliptical arcs 
-
-                    // Line 1 + Corner 1
-                    context.BeginFigure(new Point(rect.Left + x, rect.Top), true);
-                    context.LineTo(new Point(rect.Right - x, rect.Top));
-                    context.ArcTo(
-                        new Point(rect.Right, rect.Top + y),
-                        arcSize,
-                        rotationAngle: PiOver2,
-                        isLargeArc: false,
-                        SweepDirection.Clockwise);
-
-                    // Line 2 + Corner 2
-                    context.LineTo(new Point(rect.Right, rect.Bottom - y));
-                    context.ArcTo(
-                        new Point(rect.Right - x, rect.Bottom),
-                        arcSize,
-                        rotationAngle: PiOver2,
-                        isLargeArc: false,
-                        SweepDirection.Clockwise);
-
-                    // Line 3 + Corner 3
-                    context.LineTo(new Point(rect.Left + x, rect.Bottom));
-                    context.ArcTo(
-                        new Point(rect.Left, rect.Bottom - y),
-                        arcSize,
-                        rotationAngle: PiOver2,
-                        isLargeArc: false,
-                        SweepDirection.Clockwise);
-
-                    // Line 4 + Corner 4
-                    context.LineTo(new Point(rect.Left, rect.Top + y));
-                    context.ArcTo(
-                        new Point(rect.Left + x, rect.Top),
-                        arcSize,
-                        rotationAngle: PiOver2,
-                        isLargeArc: false,
-                        SweepDirection.Clockwise);
-
-                    context.EndFigure(true);
-                }
+            var rect = new Rect(Bounds.Size).Deflate(StrokeThickness / 2);
 
-                return geometry;
-            }
+            return new RectangleGeometry(rect, RadiusX, RadiusY);
         }
 
         /// <inheritdoc/>
diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
index 4df4fd738a0..f86ddb0d2dc 100644
--- a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
+++ b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
@@ -1,35 +1,38 @@
-﻿using System;
-using Avalonia.Collections;
-using Avalonia.Media;
-using Avalonia.Media.Immutable;
+﻿using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.Utilities;
 
 namespace Avalonia.Controls.Utils
 {
+    /// <summary>
+    /// Contains helper methods for rendering a <see cref="Border"/>'s background and border to a given context.
+    /// </summary>
     internal class BorderRenderHelper
     {
         private bool _useComplexRendering;
         private bool? _backendSupportsIndividualCorners;
-        private StreamGeometry? _backgroundGeometryCache;
-        private StreamGeometry? _borderGeometryCache;
+        private Geometry? _backgroundGeometryCache;
+        private Geometry? _borderGeometryCache;
         private Size _size;
         private Thickness _borderThickness;
         private CornerRadius _cornerRadius;
+        private BackgroundSizing _backgroundSizing;
         private bool _initialized;
         private IPen? _cachedPen;
 
-
-        void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius)
+        private void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius, BackgroundSizing backgroundSizing)
         {
             _backendSupportsIndividualCorners ??= AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>()
                 .SupportsIndividualRoundRects;
             _size = finalSize;
             _borderThickness = borderThickness;
             _cornerRadius = cornerRadius;
+            _backgroundSizing = backgroundSizing;
             _initialized = true;
 
-            if (borderThickness.IsUniform && (cornerRadius.IsUniform || _backendSupportsIndividualCorners == true))
+            if (borderThickness.IsUniform &&
+                (cornerRadius.IsUniform || _backendSupportsIndividualCorners == true) &&
+                backgroundSizing == BackgroundSizing.CenterBorder)
             {
                 _backgroundGeometryCache = null;
                 _borderGeometryCache = null;
@@ -41,17 +44,19 @@ void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius
 
                 var boundRect = new Rect(finalSize);
                 var innerRect = boundRect.Deflate(borderThickness);
-                BorderGeometryKeypoints? backgroundKeypoints = null;
-                StreamGeometry? backgroundGeometry = null;
 
                 if (innerRect.Width != 0 && innerRect.Height != 0)
                 {
-                    backgroundGeometry = new StreamGeometry();
-                    backgroundKeypoints = new BorderGeometryKeypoints(innerRect, borderThickness, cornerRadius, true);
+                    var backgroundOuterKeypoints = GeometryBuilder.CalculateRoundedCornersRectangleWinUI(
+                        boundRect,
+                        borderThickness,
+                        cornerRadius,
+                        backgroundSizing);
 
+                    var backgroundGeometry = new StreamGeometry();
                     using (var ctx = backgroundGeometry.Open())
                     {
-                        CreateGeometry(ctx, innerRect, backgroundKeypoints);
+                        GeometryBuilder.DrawRoundedCornersRectangle(ctx, ref backgroundOuterKeypoints);
                     }
 
                     _backgroundGeometryCache = backgroundGeometry;
@@ -63,21 +68,30 @@ void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius
 
                 if (boundRect.Width != 0 && boundRect.Height != 0)
                 {
-                    var borderGeometryKeypoints =
-                        new BorderGeometryKeypoints(boundRect, borderThickness, cornerRadius, false);
-                    var borderGeometry = new StreamGeometry();
-
-                    using (var ctx = borderGeometry.Open())
+                    var borderInnerKeypoints = GeometryBuilder.CalculateRoundedCornersRectangleWinUI(
+                        boundRect,
+                        borderThickness,
+                        cornerRadius,
+                        BackgroundSizing.InnerBorderEdge);
+                    var borderOuterKeypoints = GeometryBuilder.CalculateRoundedCornersRectangleWinUI(
+                        boundRect,
+                        borderThickness,
+                        cornerRadius,
+                        BackgroundSizing.OuterBorderEdge);
+
+                    var borderInnerGeometry = new StreamGeometry();
+                    using (var ctx = borderInnerGeometry.Open())
                     {
-                        CreateGeometry(ctx, boundRect, borderGeometryKeypoints);
+                        GeometryBuilder.DrawRoundedCornersRectangle(ctx, ref borderInnerKeypoints);
+                    }
 
-                        if (backgroundGeometry != null)
-                        {
-                            CreateGeometry(ctx, innerRect, backgroundKeypoints!);
-                        }
+                    var borderOuterGeometry = new StreamGeometry();
+                    using (var ctx = borderOuterGeometry.Open())
+                    {
+                        GeometryBuilder.DrawRoundedCornersRectangle(ctx, ref borderOuterKeypoints);
                     }
 
-                    _borderGeometryCache = borderGeometry;
+                    _borderGeometryCache = new CombinedGeometry(GeometryCombineMode.Exclude, borderOuterGeometry, borderInnerGeometry);
                 }
                 else
                 {
@@ -86,211 +100,51 @@ void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius
             }
         }
 
-        public void Render(DrawingContext context,
-            Size finalSize, Thickness borderThickness, CornerRadius cornerRadius,
-            IBrush? background, IBrush? borderBrush, BoxShadows boxShadows)
+        public void Render(
+            DrawingContext context,
+            Size finalSize,
+            Thickness borderThickness,
+            CornerRadius cornerRadius,
+            BackgroundSizing backgroundSizing,
+            IBrush? background,
+            IBrush? borderBrush,
+            BoxShadows boxShadows)
         {
             if (_size != finalSize
                 || _borderThickness != borderThickness
                 || _cornerRadius != cornerRadius
+                || _backgroundSizing != backgroundSizing
                 || !_initialized)
-                Update(finalSize, borderThickness, cornerRadius);
-            RenderCore(context, background, borderBrush, boxShadows);
-        }
+            {
+                Update(finalSize, borderThickness, cornerRadius, backgroundSizing);
+            }
 
-        void RenderCore(DrawingContext context, IBrush? background, IBrush? borderBrush, BoxShadows boxShadows)
-        {
             if (_useComplexRendering)
             {
-                var backgroundGeometry = _backgroundGeometryCache;
-                if (backgroundGeometry != null)
+                if (_backgroundGeometryCache != null)
                 {
-                    context.DrawGeometry(background, null, backgroundGeometry);
+                    context.DrawGeometry(background, null, _backgroundGeometryCache);
                 }
 
-                var borderGeometry = _borderGeometryCache;
-                if (borderGeometry != null)
+                if (_borderGeometryCache != null)
                 {
-                    context.DrawGeometry(borderBrush, null, borderGeometry);
+                    context.DrawGeometry(borderBrush, null, _borderGeometryCache);
                 }
             }
             else
             {
-                var borderThickness = _borderThickness.Top;
+                var thickness = _borderThickness.Top;
 
-                Pen.TryModifyOrCreate(ref _cachedPen, borderBrush, borderThickness);
+                Pen.TryModifyOrCreate(ref _cachedPen, borderBrush, thickness);
 
                 var rect = new Rect(_size);
-                if (!MathUtilities.IsZero(borderThickness))
-                    rect = rect.Deflate(borderThickness * 0.5);
+                if (!MathUtilities.IsZero(thickness))
+                    rect = rect.Deflate(thickness * 0.5);
                 var rrect = new RoundedRect(rect, _cornerRadius.TopLeft, _cornerRadius.TopRight,
                     _cornerRadius.BottomRight, _cornerRadius.BottomLeft);
 
                 context.DrawRectangle(background, _cachedPen, rrect, boxShadows);
             }
         }
-
-        private static void CreateGeometry(StreamGeometryContext context, Rect boundRect,
-            BorderGeometryKeypoints keypoints)
-        {
-            context.BeginFigure(keypoints.TopLeft, true);
-
-            // Top
-            context.LineTo(keypoints.TopRight);
-
-            // TopRight corner
-            var radiusX = boundRect.TopRight.X - keypoints.TopRight.X;
-            var radiusY = keypoints.RightTop.Y - boundRect.TopRight.Y;
-            if (radiusX != 0 || radiusY != 0)
-            {
-                context.ArcTo(keypoints.RightTop, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
-            }
-
-            // Right
-            context.LineTo(keypoints.RightBottom);
-
-            // BottomRight corner
-            radiusX = boundRect.BottomRight.X - keypoints.BottomRight.X;
-            radiusY = boundRect.BottomRight.Y - keypoints.RightBottom.Y;
-            if (radiusX != 0 || radiusY != 0)
-            {
-                context.ArcTo(keypoints.BottomRight, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
-            }
-
-            // Bottom
-            context.LineTo(keypoints.BottomLeft);
-
-            // BottomLeft corner
-            radiusX = keypoints.BottomLeft.X - boundRect.BottomLeft.X;
-            radiusY = boundRect.BottomLeft.Y - keypoints.LeftBottom.Y;
-            if (radiusX != 0 || radiusY != 0)
-            {
-                context.ArcTo(keypoints.LeftBottom, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
-            }
-
-            // Left
-            context.LineTo(keypoints.LeftTop);
-
-            // TopLeft corner
-            radiusX = keypoints.TopLeft.X - boundRect.TopLeft.X;
-            radiusY = keypoints.LeftTop.Y - boundRect.TopLeft.Y;
-
-            if (radiusX != 0 || radiusY != 0)
-            {
-                context.ArcTo(keypoints.TopLeft, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
-            }
-
-            context.EndFigure(true);
-        }
-
-        private class BorderGeometryKeypoints
-        {
-            internal BorderGeometryKeypoints(Rect boundRect, Thickness borderThickness, CornerRadius cornerRadius,
-                bool inner)
-            {
-                var left = 0.5 * borderThickness.Left;
-                var top = 0.5 * borderThickness.Top;
-                var right = 0.5 * borderThickness.Right;
-                var bottom = 0.5 * borderThickness.Bottom;
-
-                double leftTopY;
-                double topLeftX;
-                double topRightX;
-                double rightTopY;
-                double rightBottomY;
-                double bottomRightX;
-                double bottomLeftX;
-                double leftBottomY;
-
-                if (inner)
-                {
-                    leftTopY = Math.Max(0, cornerRadius.TopLeft - top) + boundRect.TopLeft.Y;
-                    topLeftX = Math.Max(0, cornerRadius.TopLeft - left) + boundRect.TopLeft.X;
-                    topRightX = boundRect.Width - Math.Max(0, cornerRadius.TopRight - top) + boundRect.TopLeft.X;
-                    rightTopY = Math.Max(0, cornerRadius.TopRight - right) + boundRect.TopLeft.Y;
-                    rightBottomY = boundRect.Height - Math.Max(0, cornerRadius.BottomRight - bottom) +
-                                   boundRect.TopLeft.Y;
-                    bottomRightX = boundRect.Width - Math.Max(0, cornerRadius.BottomRight - right) +
-                                   boundRect.TopLeft.X;
-                    bottomLeftX = Math.Max(0, cornerRadius.BottomLeft - left) + boundRect.TopLeft.X;
-                    leftBottomY = boundRect.Height - Math.Max(0, cornerRadius.BottomLeft - bottom) +
-                                  boundRect.TopLeft.Y;
-                }
-                else
-                {
-                    leftTopY = cornerRadius.TopLeft + top + boundRect.TopLeft.Y;
-                    topLeftX = cornerRadius.TopLeft + left + boundRect.TopLeft.X;
-                    topRightX = boundRect.Width - (cornerRadius.TopRight + right) + boundRect.TopLeft.X;
-                    rightTopY = cornerRadius.TopRight + top + boundRect.TopLeft.Y;
-                    rightBottomY = boundRect.Height - (cornerRadius.BottomRight + bottom) + boundRect.TopLeft.Y;
-                    bottomRightX = boundRect.Width - (cornerRadius.BottomRight + right) + boundRect.TopLeft.X;
-                    bottomLeftX = cornerRadius.BottomLeft + left + boundRect.TopLeft.X;
-                    leftBottomY = boundRect.Height - (cornerRadius.BottomLeft + bottom) + boundRect.TopLeft.Y;
-                }
-
-                var leftTopX = boundRect.TopLeft.X;
-                var topLeftY = boundRect.TopLeft.Y;
-                var topRightY = boundRect.TopLeft.Y;
-                var rightTopX = boundRect.Width + boundRect.TopLeft.X;
-                var rightBottomX = boundRect.Width + boundRect.TopLeft.X;
-                var bottomRightY = boundRect.Height + boundRect.TopLeft.Y;
-                var bottomLeftY = boundRect.Height + boundRect.TopLeft.Y;
-                var leftBottomX = boundRect.TopLeft.X;
-
-                LeftTop = new Point(leftTopX, leftTopY);
-                TopLeft = new Point(topLeftX, topLeftY);
-                TopRight = new Point(topRightX, topRightY);
-                RightTop = new Point(rightTopX, rightTopY);
-                RightBottom = new Point(rightBottomX, rightBottomY);
-                BottomRight = new Point(bottomRightX, bottomRightY);
-                BottomLeft = new Point(bottomLeftX, bottomLeftY);
-                LeftBottom = new Point(leftBottomX, leftBottomY);
-
-                // Fix overlap
-                if (TopLeft.X > TopRight.X)
-                {
-                    var scaledX = topLeftX / (topLeftX + topRightX) * boundRect.Width;
-                    TopLeft = new Point(scaledX, TopLeft.Y);
-                    TopRight = new Point(scaledX, TopRight.Y);
-                }
-
-                if (RightTop.Y > RightBottom.Y)
-                {
-                    var scaledY = rightBottomY / (rightTopY + rightBottomY) * boundRect.Height;
-                    RightTop = new Point(RightTop.X, scaledY);
-                    RightBottom = new Point(RightBottom.X, scaledY);
-                }
-
-                if (BottomRight.X < BottomLeft.X)
-                {
-                    var scaledX = bottomLeftX / (bottomLeftX + bottomRightX) * boundRect.Width;
-                    BottomRight = new Point(scaledX, BottomRight.Y);
-                    BottomLeft = new Point(scaledX, BottomLeft.Y);
-                }
-
-                if (LeftBottom.Y < LeftTop.Y)
-                {
-                    var scaledY = leftTopY / (leftTopY + leftBottomY) * boundRect.Height;
-                    LeftBottom = new Point(LeftBottom.X, scaledY);
-                    LeftTop = new Point(LeftTop.X, scaledY);
-                }
-            }
-
-            internal Point LeftTop { get; }
-
-            internal Point TopLeft { get; }
-
-            internal Point TopRight { get; }
-
-            internal Point RightTop { get; }
-
-            internal Point RightBottom { get; }
-
-            internal Point BottomRight { get; }
-
-            internal Point BottomLeft { get; }
-
-            internal Point LeftBottom { get; }
-        }
     }
 }


Test patch:
diff --git a/tests/Avalonia.Base.UnitTests/Media/GeometryBuilderTests.cs b/tests/Avalonia.Base.UnitTests/Media/GeometryBuilderTests.cs
new file mode 100644
index 00000000000..a7d390bffea
--- /dev/null
+++ b/tests/Avalonia.Base.UnitTests/Media/GeometryBuilderTests.cs
@@ -0,0 +1,61 @@
+﻿using Avalonia.Media;
+using Xunit;
+
+namespace Avalonia.Base.UnitTests.Media
+{
+    public class GeometryBuilderTests
+    {
+        [Theory]
+        [InlineData(20.0, 10.0)]
+        [InlineData(10.0, 5.0)]
+        [InlineData(2.0, 1.0)]
+        [InlineData(1.0, 0.0)]
+        public void CalculateRoundedCornersRectangleWinUI_InnerBorderEdge_Borders_Larger_Than_Corners_Test(
+            double uniformBorders,
+            double uniformCorners)
+        {
+            var bounds = new Rect(new Size(100, 100));
+            var borderThickness = new Thickness(uniformBorders);
+            var cornerRadius = new CornerRadius(uniformCorners);
+
+            var points = GeometryBuilder.CalculateRoundedCornersRectangleWinUI(bounds, borderThickness, cornerRadius, BackgroundSizing.InnerBorderEdge);
+
+            Assert.Equal(new Point(uniformBorders, uniformBorders), points.LeftTop);
+            Assert.Equal(new Point(uniformBorders, uniformBorders), points.TopLeft);
+            Assert.Equal(new Point(100 - uniformBorders, uniformBorders), points.TopRight);
+            Assert.Equal(new Point(100 - uniformBorders, uniformBorders), points.RightTop);
+            Assert.Equal(new Point(100 - uniformBorders, 100 - uniformBorders), points.RightBottom);
+            Assert.Equal(new Point(100 - uniformBorders, 100 - uniformBorders), points.BottomRight);
+            Assert.Equal(new Point(uniformBorders, 100 - uniformBorders), points.BottomLeft);
+            Assert.Equal(new Point(uniformBorders, 100 - uniformBorders), points.LeftBottom);
+
+            Assert.False(points.IsRounded);
+        }
+
+        [Theory]
+        [InlineData(20.0, 10.0)]
+        [InlineData(10.0, 5.0)]
+        [InlineData(2.0, 1.0)]
+        public void CalculateRoundedCornersRectangleWinUI_OuterBorderEdge_Borders_Larger_Than_Corners_Test(
+            double uniformBorders,
+            double uniformCorners)
+        {
+            var bounds = new Rect(new Size(100, 100));
+            var borderThickness = new Thickness(uniformBorders);
+            var cornerRadius = new CornerRadius(uniformCorners);
+
+            var points = GeometryBuilder.CalculateRoundedCornersRectangleWinUI(bounds, borderThickness, cornerRadius, BackgroundSizing.OuterBorderEdge);
+
+            Assert.Equal(new Point(0, uniformBorders), points.LeftTop);
+            Assert.Equal(new Point(uniformBorders, 0), points.TopLeft);
+            Assert.Equal(new Point(100 - uniformBorders, 0), points.TopRight);
+            Assert.Equal(new Point(100, uniformBorders), points.RightTop);
+            Assert.Equal(new Point(100, 100 - uniformBorders), points.RightBottom);
+            Assert.Equal(new Point(100 - uniformBorders, 100), points.BottomRight);
+            Assert.Equal(new Point(uniformBorders, 100), points.BottomLeft);
+            Assert.Equal(new Point(0, 100 - uniformBorders), points.LeftBottom);
+
+            Assert.True(points.IsRounded);
+        }
+    }
+}