Handling the CornerRadius for a RoundedRectangle Geometry in WPF

Recently I was pondering on how to create a RoundedRectangle Geometry in WPF. WPF does provide a RectangleGeometry class which has the RadiusX and RadiusY properties which allow you to curve the RectangleGeometry at the corners.  Each of the corners have the same curvature. But what if you need a different corner radius at each of the corners?

If you look closely at a border with rounded corners, it can be split into 4 straight lines and 4 curves.

Thus to create the RoundedRectangleGeometry you require four LineSegments and four ArcSegments. To determine the end points of the Arcs you need to calculate the rectangles that enclose them at each corner. The LineSegments will be drawn between the EndPoint of an ArcSegment and the StartPoint of the next ArcSegment.

The next question that comes to mind is what happens if the rectangles enclosing the ArcSegments overlap (i.e. the corner radius is larger than half of the width or height of the Rounded Rectangle). The sample program which I created to test it gave me the following output when the TopLeft and TopRight corner radii were large.

Then I looked into how the Border class handles this situation and I found out that in such scenarios the width and height of the corner rectangles were recalculated in such a way that they were in proportion to each other with respect with to the width and height of the base Rounded Rectangle.

Here is the code for the class GeometryHelper which generates a PathGeometry for a RoundedRectangle based on the given BaseRect size and CornerRadius.

namespace WPFSpark
{
    public static class GeometryHelper
    {
        public static Geometry GetRoundRectangleGeometry(Rect baseRect, CornerRadius cornerRadius)
        {
            if (cornerRadius.TopLeft < Double.Epsilon)
                cornerRadius.TopLeft = 0.0;
            if (cornerRadius.TopRight < Double.Epsilon)
                cornerRadius.TopRight = 0.0;
            if (cornerRadius.BottomLeft < Double.Epsilon)
                cornerRadius.BottomLeft = 0.0;
            if (cornerRadius.BottomRight < Double.Epsilon)
                cornerRadius.BottomRight = 0.0;                          

            // Create the rectangles for the corners that needs to be curved in the base rectangle
            // TopLeft Rectangle
            Rect topLeftRect = new Rect(baseRect.Location.X,
                                        baseRect.Location.Y,
                                        cornerRadius.TopLeft,
                                        cornerRadius.TopLeft);
            // TopRight Rectangle
            Rect topRightRect = new Rect(baseRect.Location.X + baseRect.Width - cornerRadius.TopRight,
                                         baseRect.Location.Y,
                                         cornerRadius.TopRight,
                                         cornerRadius.TopRight);
            // BottomRight Rectangle
            Rect bottomRightRect = new Rect(baseRect.Location.X + baseRect.Width - cornerRadius.BottomRight,
                                            baseRect.Location.Y + baseRect.Height - cornerRadius.BottomRight,
                                            cornerRadius.BottomRight,
                                            cornerRadius.BottomRight);
            // BottomLeft Rectangle
            Rect bottomLeftRect = new Rect(baseRect.Location.X,
                                           baseRect.Location.Y + baseRect.Height - cornerRadius.BottomLeft,
                                           cornerRadius.BottomLeft,
                                           cornerRadius.BottomLeft);             

            // Adjust the width of the TopLeft and TopRight rectangles so that they are proportional to the width of the baseRect
            if (topLeftRect.Right > topRightRect.Left)
            {
                double newWidth = (topLeftRect.Width / (topLeftRect.Width + topRightRect.Width)) * baseRect.Width;
                topLeftRect = new Rect(topLeftRect.Location.X, topLeftRect.Location.Y, newWidth, topLeftRect.Height);
                topRightRect = new Rect(baseRect.Left + newWidth, topRightRect.Location.Y, Math.Max(0.0, baseRect.Width - newWidth), topRightRect.Height);
            }

            // Adjust the height of the TopRight and BottomRight rectangles so that they are proportional to the height of the baseRect
            if (topRightRect.Bottom > bottomRightRect.Top)
            {
                double newHeight = (topRightRect.Height / (topRightRect.Height + bottomRightRect.Height)) * baseRect.Height;
                topRightRect = new Rect(topRightRect.Location.X, topRightRect.Location.Y, topRightRect.Width, newHeight);
                bottomRightRect = new Rect(bottomRightRect.Location.X, baseRect.Top + newHeight, bottomRightRect.Width, Math.Max(0.0, baseRect.Height - newHeight));
            }

            // Adjust the width of the BottomLeft and BottomRight rectangles so that they are proportional to the width of the baseRect
            if (bottomRightRect.Left < bottomLeftRect.Right)
            {
                double newWidth = (bottomLeftRect.Width / (bottomLeftRect.Width + bottomRightRect.Width)) * baseRect.Width;
                bottomLeftRect = new Rect(bottomLeftRect.Location.X, bottomLeftRect.Location.Y, newWidth, bottomLeftRect.Height);
                bottomRightRect = new Rect(baseRect.Left + newWidth, bottomRightRect.Location.Y, Math.Max(0.0, baseRect.Width - newWidth), bottomRightRect.Height);
            }

            // Adjust the height of the TopLeft and BottomLeft rectangles so that they are proportional to the height of the baseRect
            if (bottomLeftRect.Top < topLeftRect.Bottom)
            {
                double newHeight = (topLeftRect.Height / (topLeftRect.Height + bottomLeftRect.Height)) * baseRect.Height;
                topLeftRect = new Rect(topLeftRect.Location.X, topLeftRect.Location.Y, topLeftRect.Width, newHeight);
                bottomLeftRect = new Rect(bottomLeftRect.Location.X, baseRect.Top + newHeight, bottomLeftRect.Width, Math.Max(0.0, baseRect.Height - newHeight));
            }

            StreamGeometry roundedRectGeometry = new StreamGeometry();

            using (StreamGeometryContext context = roundedRectGeometry.Open())
            {
                // Begin from the Bottom of the TopLeft Arc and proceed clockwise
                context.BeginFigure(topLeftRect.BottomLeft, true, true);
                // TopLeft Arc
                context.ArcTo(topLeftRect.TopRight, topLeftRect.Size, 0, false, SweepDirection.Clockwise, true, true);
                // Top Line
                context.LineTo(topRightRect.TopLeft, true, true);
                // TopRight Arc
                context.ArcTo(topRightRect.BottomRight, topRightRect.Size, 0, false, SweepDirection.Clockwise, true, true);
                // Right Line
                context.LineTo(bottomRightRect.TopRight, true, true);
                // BottomRight Arc
                context.ArcTo(bottomRightRect.BottomLeft, bottomRightRect.Size, 0, false, SweepDirection.Clockwise, true, true);
                // Bottom Line
                context.LineTo(bottomLeftRect.BottomRight, true, true);
                // BottomLeft Arc
                context.ArcTo(bottomLeftRect.TopLeft, bottomLeftRect.Size, 0, false, SweepDirection.Clockwise, true, true);

                context.Close();
            }

            return roundedRectGeometry;
        }
    }
}
Advertisements
Categories WPF

3 thoughts on “Handling the CornerRadius for a RoundedRectangle Geometry in WPF

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s