2

I'm working on a relatively small proof-of-concept for some line of business stuff with some fancy WPF UI work. Without even going too crazy, I'm already seeing some really poor performance when using a lot of the features that I thought were the main reason to consider WPF for UI building in the first place. I asked a question on here about why my animation was being stalled the first time it was run, and at the end what I found was that a very simple UserControl was taking almost half a second just to build its visual tree. I was able to get a work around to the symptom, but the fact that it takes that long to initialize a simple control really bothers me. Now, I'm testing my animation with and without the DropShadowEffect, and the result is night and day. A subtle drop shadow makes my control look so much nicer, but it completely ruins the smoothness of the animation. Let me not even start with the font rendering either. The calculation of my animations when the control has a bunch of gradient brushes and a drop shadow make the text blurry for about a full second and then slowly come into focus.

So, I guess my question is if there are known studies, blog posts, or articles detailing which features are a hazard in the current version of WPF for business critical applications. Are things like Effects (ie. DropShadowEffect), gradient brushes, key frame animations, etc going to have too much of a negative effect on render quality (or maybe the combinations of these things)? Is the final version of WPF 4.0 going to correct some of these issues? I've read that VS2010 beta has some of these same issues and that they are supposed to be resolved by final release. Is that because of improvements to WPF itself or because half of the application will be rebuilt with the previous technology?

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Rich
  • 36,270
  • 31
  • 115
  • 154

3 Answers3

7

I was also researching performance problems related to DropShadow effects in WPF.

Basically the rule of thumb is always going to be "don't use it". Both PixelShader and bitmap DropShadow effects in WPF never work for a real-world app... unless your UI remains 100% static with no moving parts, your program is going to run like garbage on 95% of your customer base.

What I'm going to do is fake it instead.

Here's a quick 'n dirty DropShadow Decorator I've come up with just now which acts as a reasonable facimile of the PixelShader dropshadow effect. You'll want to make it cleaner by caching brushes etc, but this should give you an idea:

using System.Windows.Shapes;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media;
using System;
using System.ComponentModel; 

namespace SichboPVR
{
    /// <summary>
    /// Emulates the System.Windows.Media.Effects.DropShadowEffect using
    /// rectangles and gradients, which performs a million times better
    /// and won't randomly crash a good percentage of your end-user's 
    /// video drivers.
    /// </summary>
    class FastShadow : Decorator 
    {

        #region Dynamic Properties

        public static readonly DependencyProperty ColorProperty =
                DependencyProperty.Register(
                        "Color",
                        typeof(Color),
                        typeof(FastShadow),
                        new FrameworkPropertyMetadata(
                                Color.FromArgb(0x71, 0x00, 0x00, 0x00),
                                FrameworkPropertyMetadataOptions.AffectsRender));

        /// <summary>
        /// The Color property defines the Color used to fill the shadow region. 
        /// </summary> 
        [Category("Common Properties")]
        public Color Color
        {
            get { return (Color)GetValue(ColorProperty); }
            set { SetValue(ColorProperty, value); }
        }

        /// <summary>
        /// Distance from centre, why MS don't call this "distance" beats
        /// me.. Kept same as other Effects for consistency.
        /// </summary>
        [Category("Common Properties"), Description("Distance from centre")]
        public double ShadowDepth
        {
            get { return (double)GetValue(ShadowDepthProperty); }
            set { SetValue(ShadowDepthProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ShadowDepth.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ShadowDepthProperty =
            DependencyProperty.Register("ShadowDepth", typeof(double), typeof(FastShadow), 
            new FrameworkPropertyMetadata(
                5.0, FrameworkPropertyMetadataOptions.AffectsRender,
                    new PropertyChangedCallback((o, e) => {
                        FastShadow f = o as FastShadow;
                        if ((double)e.NewValue < 0)
                            f.ShadowDepth = 0;
                    })));


        /// <summary>
        /// Size of the shadow
        /// </summary>
        [Category("Common Properties"), Description("Size of the drop shadow")]
        public double BlurRadius
        {
            get { return (double)GetValue(BlurRadiusProperty); }
            set { SetValue(BlurRadiusProperty, value); }
        }

        // Using a DependencyProperty as the backing store for BlurRadius.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BlurRadiusProperty =
            DependencyProperty.Register("BlurRadius", typeof(double), typeof(FastShadow), 
            new FrameworkPropertyMetadata(10.0, 
                FrameworkPropertyMetadataOptions.AffectsRender,
                    new PropertyChangedCallback((o, e) => {
                        FastShadow f = o as FastShadow;
                        if ((double)e.NewValue < 0)
                            f.BlurRadius = 0;
                    })));


        /// <summary>
        /// Angle of the shadow
        /// </summary>
        [Category("Common Properties"), Description("Angle of the shadow")]
        public int Direction
        {
            get { return (int)GetValue(DirectionProperty); }
            set { SetValue(DirectionProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Direction.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DirectionProperty =
            DependencyProperty.Register("Direction", typeof(int), typeof(FastShadow), 
            new FrameworkPropertyMetadata(315, FrameworkPropertyMetadataOptions.AffectsRender));


        #endregion Dynamic Properties

        #region Protected Methods

        protected override void OnRender(DrawingContext drawingContext)
        {

            double distance = Math.Max(0, ShadowDepth);
            double blurRadius = Math.Max(BlurRadius, 0);
            double angle = Direction + 45; // Make it behave the same as DropShadowEffect

            Rect shadowBounds = new Rect(new Point(0, 0),
                             new Size(RenderSize.Width, RenderSize.Height));

            shadowBounds.Inflate(blurRadius, blurRadius);

            Color color = Color;

            // Transform angle for "Direction"
            double angleRad = angle * Math.PI / 180.0;
            double xDispl = distance;
            double yDispl = distance;
            double newX = xDispl * Math.Cos(angleRad) - yDispl * Math.Sin(angleRad);
            double newY = yDispl * Math.Cos(angleRad) + xDispl * Math.Sin(angleRad);

            TranslateTransform translate = new TranslateTransform(newX, newY);
            Rect transformed = translate.TransformBounds(shadowBounds);

            // Hint: you can make the blur radius consume more "centre"
            //       region of the bounding box by doubling this here
            // blurRadius = blurRadius * 2;

            // Build a set of rectangles for the shadow box
            Rect[] edges = new Rect[] { 
                new Rect(new Point(transformed.X,transformed.Y), new Size(blurRadius,blurRadius)), // TL
                new Rect(new Point(transformed.X+blurRadius,transformed.Y), new Size(Math.Max(transformed.Width-(blurRadius*2),0),blurRadius)), // T
                new Rect(new Point(transformed.Right-blurRadius,transformed.Y), new Size(blurRadius,blurRadius)), // TR
                new Rect(new Point(transformed.Right-blurRadius,transformed.Y+blurRadius), new Size(blurRadius,Math.Max(transformed.Height-(blurRadius*2),0))), // R
                new Rect(new Point(transformed.Right-blurRadius,transformed.Bottom-blurRadius), new Size(blurRadius,blurRadius)), // BR
                new Rect(new Point(transformed.X+blurRadius,transformed.Bottom-blurRadius), new Size(Math.Max(transformed.Width-(blurRadius*2),0),blurRadius)), // B
                new Rect(new Point(transformed.X,transformed.Bottom-blurRadius), new Size(blurRadius,blurRadius)), // BL
                new Rect(new Point(transformed.X,transformed.Y+blurRadius), new Size(blurRadius,Math.Max(transformed.Height-(blurRadius*2),0))), // L
                new Rect(new Point(transformed.X+blurRadius,transformed.Y+blurRadius), new Size(Math.Max(transformed.Width-(blurRadius*2),0),Math.Max(transformed.Height-(blurRadius*2),0))), // C
            };

            // Gradient stops look a lot prettier than
            // a perfectly linear gradient..
            GradientStopCollection gsc = new GradientStopCollection();
            Color stopColor = color;
            stopColor.A = (byte)(color.A);
            gsc.Add(new GradientStop(color, 0.0));
            stopColor.A = (byte)(.74336 * color.A);
            gsc.Add(new GradientStop(stopColor, 0.1));
            stopColor.A = (byte)(.38053 * color.A);
            gsc.Add(new GradientStop(stopColor, 0.3));
            stopColor.A = (byte)(.12389 * color.A);
            gsc.Add(new GradientStop(stopColor, 0.5));
            stopColor.A = (byte)(.02654 * color.A);
            gsc.Add(new GradientStop(stopColor, 0.7));
            stopColor.A = (byte)(0);
            gsc.Add(new GradientStop(stopColor, 0.9));

            gsc.Freeze(); 

            Brush[] colors = new Brush[]{
                // TL
                new RadialGradientBrush(gsc){ Center = new Point(1, 1), GradientOrigin = new Point(1, 1), RadiusX=1, RadiusY=1},
                // T
                new LinearGradientBrush(gsc, 0){ StartPoint = new Point(0,1), EndPoint=new Point(0,0)},
                // TR
                new RadialGradientBrush(gsc){ Center = new Point(0, 1), GradientOrigin = new Point(0, 1), RadiusX=1, RadiusY=1},
                // R
                new LinearGradientBrush(gsc, 0){ StartPoint = new Point(0,0), EndPoint=new Point(1,0)},
                // BR
                new RadialGradientBrush(gsc){ Center = new Point(0, 0), GradientOrigin = new Point(0, 0), RadiusX=1, RadiusY=1},
                // B
                new LinearGradientBrush(gsc, 0){ StartPoint = new Point(0,0), EndPoint=new Point(0,1)},
                // BL
                new RadialGradientBrush(gsc){ Center = new Point(1, 0), GradientOrigin = new Point(1, 0), RadiusX=1, RadiusY=1},
                // L
                new LinearGradientBrush(gsc, 0){ StartPoint = new Point(1,0), EndPoint=new Point(0,0)},
                // C
                new SolidColorBrush(color), 
            };

            // This is a test pattern, uncomment to see how I'm drawing this
            //Brush[] colors = new Brush[]{
            //    Brushes.Red,
            //    Brushes.Green,
            //    Brushes.Blue,
            //    Brushes.Fuchsia,
            //    Brushes.Gainsboro,
            //    Brushes.LimeGreen,
            //    Brushes.Navy,
            //    Brushes.Orange,
            //    Brushes.White,
            //};
            double[] guidelineSetX = new double[] { transformed.X,
                                                    transformed.X+blurRadius,
                                                    transformed.Right-blurRadius,
                                                    transformed.Right};

            double[] guidelineSetY = new double[] { transformed.Y,
                                                    transformed.Y+blurRadius,
                                                    transformed.Bottom-blurRadius,
                                                    transformed.Bottom};

            drawingContext.PushGuidelineSet(new GuidelineSet(guidelineSetX, guidelineSetY)); 
            for (int i = 0; i < edges.Length; i++)
            {
                drawingContext.DrawRoundedRectangle(colors[i], null, edges[i], 0.0, 0.0);
            }
            drawingContext.Pop();
        }

        #endregion

    }
}

Soo.. usage would be something like this;

<Sichbo:FastShadow Color="Black" ShadowDepth="0" BlurRadius="30">
<Grid>.. content here ..</Grid>
</Sichbo:FastShadow>

.. the output should be pretty close to what the PS dropshadow does.. obviously only works on "boxy" elements, but the difference in speed during runtime should be night & day, animations should be silky smooth at high resolution, and you get to keep your UI looking beautiful.

Sichbo
  • 438
  • 4
  • 8
  • Holy crap this is amazing! I've been banging my head trying to get better performance out of the standard drop shadow effect. So glad I found this answer! – Chris Lees Oct 30 '15 at 14:25
4

Have you looked at Microsoft's articles?

Hans Van Slooten
  • 2,257
  • 2
  • 19
  • 18
  • What is the current alternative for WPF Performance Suite for profiling .NET 4.5 apps? I would very appreciate you if you take a look at my question [here](http://stackoverflow.com/q/33468572/3345644). – Alexander Abakumov Nov 02 '15 at 17:41
1

The answer by Sichbo is still excellent, but only works with square rectangles. I have tweaked it to add the CornerRadius property, in order to be able to draw shadows on round rectangles so that it works well with Border controls.

I also changed the gradient calculation to something that resembles the actual DropShadowEffect on my computer, but since that one has a very peculiar rendering it seems impossible to reproduce perfectly. In any case, you can try to play with the expFactor and attenuationFactor to adapt it to your needs. expFactor controls the speed at which the drop shadow vanishes, and attenuationFactor makes it lighter by offsetting the range (instead of going from 100% to 0% shadow, we go from e.g. 95% to -5%, capped at 0%).

The gradients are calculated when the related properties are set, instead of at each render call.

namespace CustomControls
{
    using System;
    using System.ComponentModel;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;

    /// <summary>
    /// Emulates the System.Windows.Media.Effects.DropShadowEffect using rectangles and 
    /// gradients, which performs a lot better.
    /// </summary>
    public class FastShadow : Decorator
    {
        public static readonly DependencyProperty ColorProperty =
            DependencyProperty.Register("Color", typeof(Color), typeof(FastShadow),
            new FrameworkPropertyMetadata(
                Color.FromArgb(0x71, 0x00, 0x00, 0x00),
                FrameworkPropertyMetadataOptions.AffectsRender,
                new PropertyChangedCallback((o, e) =>
                {
                    FastShadow f = o as FastShadow;
                    f.CalculateGradientStops();
                })));

        public static readonly DependencyProperty BlurRadiusProperty =
            DependencyProperty.Register("BlurRadius", typeof(double), typeof(FastShadow),
            new FrameworkPropertyMetadata(
                10.0,
                FrameworkPropertyMetadataOptions.AffectsRender,
                new PropertyChangedCallback((o, e) =>
                {
                    FastShadow f = o as FastShadow;
                    if ((double)e.NewValue < 0)
                        f.BlurRadius = 0;
                    f.CalculateGradientStops();
                })));

        public static readonly DependencyProperty CornerRadiusProperty =
            DependencyProperty.Register("CornerRadius", typeof(double), typeof(FastShadow),
            new FrameworkPropertyMetadata(
                0.0,
                FrameworkPropertyMetadataOptions.AffectsRender,
                new PropertyChangedCallback((o, e) =>
                {
                    FastShadow f = o as FastShadow;
                    if ((double)e.NewValue < 0)
                        f.BlurRadius = 0;
                    f.CalculateGradientStops();
                })));

        public static readonly DependencyProperty ShadowDepthProperty =
            DependencyProperty.Register("ShadowDepth", typeof(double), typeof(FastShadow),
            new FrameworkPropertyMetadata(
                0.0,
                FrameworkPropertyMetadataOptions.AffectsRender,
                new PropertyChangedCallback((o, e) => {
                    FastShadow f = o as FastShadow;
                    if ((double)e.NewValue < 0)
                        f.ShadowDepth = 0;
                })));

        public static readonly DependencyProperty DirectionProperty =
            DependencyProperty.Register("Direction", typeof(int), typeof(FastShadow),
            new FrameworkPropertyMetadata(315, FrameworkPropertyMetadataOptions.AffectsRender));

        private GradientStopCollection gradientStops;
        private Brush[] edgeBrushes;
        private Brush[] cornerBrushes;

        public FastShadow()
            : base()
        {
            this.CalculateGradientStops();
        }

        /// <summary>
        /// Color used to fill the shadow region.
        /// </summary>
        [Category("Common Properties")]
        public Color Color
        {
            get => (Color)this.GetValue(ColorProperty);
            set
            {
                this.SetValue(ColorProperty, value);
            }
        }

        /// <summary>
        /// Size of the shadow.
        /// </summary>
        [Category("Common Properties")]
        [Description("Size of the drop shadow")]
        public double BlurRadius
        {
            get => (double)this.GetValue(BlurRadiusProperty);
            set
            {
                this.SetValue(BlurRadiusProperty, value);
            }
        }

        /// <summary>
        /// Radius of the corners.
        /// </summary>
        [Category("Common Properties")]
        [Description("Radius of the corners of the shadow")]
        public double CornerRadius
        {
            get => (double)this.GetValue(CornerRadiusProperty);
            set
            {
                this.SetValue(CornerRadiusProperty, value);
            }
        }

        /// <summary>
        /// Distance from centre.
        /// </summary>
        [Category("Common Properties")]
        [Description("Distance from centre")]
        public double ShadowDepth
        {
            get { return (double)this.GetValue(ShadowDepthProperty); }
            set { this.SetValue(ShadowDepthProperty, value); }
        }

        /// <summary>
        /// Angle of the shadow
        /// </summary>
        [Category("Common Properties")]
        [Description("Angle of the shadow")]
        public int Direction
        {
            get { return (int)this.GetValue(DirectionProperty); }
            set { this.SetValue(DirectionProperty, value); }
        }

        /// <summary>
        /// Calculate gradient stops for an exponential gradient.
        /// It is designed to look similar to the WPF DropShadowEffect, but since that one renders
        /// differently depending on how zoomed in you are, a perfect fit seems impossible.
        /// </summary>
        protected void CalculateGradientStops()
        {
            double blurRadius = Math.Max(this.BlurRadius, 0);
            double cornerRadius = Math.Max(this.CornerRadius, 0);
            
            // Portion of the gradient which is drawn "inside" the border
            double innerPart = (cornerRadius - blurRadius / 20) / (cornerRadius + blurRadius); 
            double remaining = 1.0 - innerPart;
            double expFactor = 1.5 + blurRadius / 50;
            double attenuationFactor = Math.Max(0.3 - blurRadius / 50, 0.05);

            GradientStopCollection gsc = new GradientStopCollection();
            float[] stops = new float[] { 0.0f, 0.025f, 0.05f, 0.075f, 0.1f, 0.125f,
            0.015f, 0.2f, 0.25f, 0.3f, 0.35f, 0.4f, 0.45f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f };
            Color stopColor = this.Color;

            gsc.Add(new GradientStop(stopColor, 0.0));
            gsc.Add(new GradientStop(stopColor, innerPart));
            foreach (float stop in stops)
            {
                // Exponential gradient from 1 to 0
                double num = (Math.Exp(1 - expFactor * (stop + attenuationFactor)) - Math.Exp(1 - expFactor));
                double det = (Math.E - Math.Exp(1 - expFactor));
                double factor = Math.Max(0, num / det);
                stopColor.A = (byte)(factor * this.Color.A);
                gsc.Add(new GradientStop(stopColor, innerPart + stop * remaining));
            }

            stopColor.A = 0;
            gsc.Add(new GradientStop(stopColor, innerPart + 1.0 * remaining));
            gsc.Freeze();

            this.gradientStops = gsc;

            this.edgeBrushes = new Brush[]
            {
                new LinearGradientBrush(this.gradientStops, 0)
                    { StartPoint = new Point(0, 1), EndPoint = new Point(0, 0) }, // T
                new LinearGradientBrush(this.gradientStops, 0)
                    { StartPoint = new Point(0, 0), EndPoint = new Point(1, 0) }, // R
                new LinearGradientBrush(this.gradientStops, 0)
                    { StartPoint = new Point(0, 0), EndPoint = new Point(0, 1) }, // B
                new LinearGradientBrush(this.gradientStops, 0)
                    { StartPoint = new Point(1, 0), EndPoint = new Point(0, 0) }, // L
                new SolidColorBrush(this.Color),
            };

            for (int i = 0; i < this.edgeBrushes.Length; i++)
                this.edgeBrushes[i].Freeze();

            this.cornerBrushes = new Brush[]
            {
                new RadialGradientBrush(this.gradientStops) { Center = new Point(1, 1),
                    GradientOrigin = new Point(1, 1), RadiusX = 1, RadiusY = 1 }, // TL
                new RadialGradientBrush(this.gradientStops) { Center = new Point(0, 1),
                    GradientOrigin = new Point(0, 1), RadiusX = 1, RadiusY = 1 }, // TR
                new RadialGradientBrush(this.gradientStops) { Center = new Point(0, 0),
                    GradientOrigin = new Point(0, 0), RadiusX = 1, RadiusY = 1 }, // BR
                new RadialGradientBrush(this.gradientStops) { Center = new Point(1, 0),
                    GradientOrigin = new Point(1, 0), RadiusX = 1, RadiusY = 1 }, // BL
            };

            for (int i = 0; i < this.cornerBrushes.Length; i++)
                this.cornerBrushes[i].Freeze();
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            double distance = Math.Max(this.ShadowDepth, 0);
            double blurRadius = Math.Max(this.BlurRadius, 0);
            double cornerRadius = Math.Max(this.CornerRadius, 0);
            double totalRadius = blurRadius + cornerRadius;
            double angle = this.Direction + 45; // Make it behave the same as DropShadowEffect

            Rect shadowBounds = new Rect(new Point(0, 0),
                new Size(this.RenderSize.Width, this.RenderSize.Height));
            shadowBounds.Inflate(blurRadius, blurRadius);

            double angleRad = angle * Math.PI / 180.0;
            double xDispl = distance;
            double yDispl = distance;
            double newX = xDispl * Math.Cos(angleRad) - yDispl * Math.Sin(angleRad);
            double newY = yDispl * Math.Cos(angleRad) + xDispl * Math.Sin(angleRad);

            TranslateTransform translate = new TranslateTransform(newX, newY);
            Rect shadowRenderRect = translate.TransformBounds(shadowBounds);

            Color color = this.Color;

            // Build a set of rectangles for the shadow box
            Rect[] edges = new Rect[]
            {
                new Rect(new Point(shadowRenderRect.X + totalRadius, shadowRenderRect.Y),
                    new Size(Math.Max(shadowRenderRect.Width - (totalRadius * 2), 0), totalRadius)), // T
                new Rect(new Point(shadowRenderRect.Right - totalRadius, shadowRenderRect.Y + totalRadius),
                    new Size(totalRadius, Math.Max(shadowRenderRect.Height - (totalRadius * 2), 0))), // R
                new Rect(new Point(shadowRenderRect.X + totalRadius, shadowRenderRect.Bottom - totalRadius),
                    new Size(Math.Max(shadowRenderRect.Width - (totalRadius * 2), 0), totalRadius)), // B
                new Rect(new Point(shadowRenderRect.X, shadowRenderRect.Y + totalRadius),
                    new Size(totalRadius, Math.Max(shadowRenderRect.Height - (totalRadius * 2), 0))), // L
                new Rect(new Point(shadowRenderRect.X + totalRadius, shadowRenderRect.Y + totalRadius),
                    new Size(
                        Math.Max(shadowRenderRect.Width - (totalRadius * 2), 0),
                        Math.Max(shadowRenderRect.Height - (totalRadius * 2), 0))), // C
            };

            Rect[] corners = new Rect[]
            {
                new Rect(shadowRenderRect.X, shadowRenderRect.Y, totalRadius * 2, totalRadius * 2), // TL
                new Rect(shadowRenderRect.Right - totalRadius * 2, shadowRenderRect.Y,
                    totalRadius * 2, totalRadius * 2), // TR
                new Rect(shadowRenderRect.Right - totalRadius * 2, shadowRenderRect.Bottom - totalRadius * 2,
                    totalRadius * 2, totalRadius * 2), // BR
                new Rect(shadowRenderRect.X, shadowRenderRect.Bottom - totalRadius * 2,
                    totalRadius * 2, totalRadius * 2), // BL
            };

            double[] guidelineSetX = new double[] { 
                shadowRenderRect.X,
                shadowRenderRect.X + totalRadius,
                shadowRenderRect.Right - totalRadius,
                shadowRenderRect.Right, };
            double[] guidelineSetY = new double[] {
                shadowRenderRect.Y,
                shadowRenderRect.Y + totalRadius,
                shadowRenderRect.Bottom - totalRadius,
                shadowRenderRect.Bottom, };

            drawingContext.PushGuidelineSet(new GuidelineSet(guidelineSetX, guidelineSetY));
            for (int i = 0; i < edges.Length; i++)
                drawingContext.DrawRectangle(this.edgeBrushes[i], null, edges[i]);

            drawingContext.DrawGeometry(this.cornerBrushes[0], null, CreateArcDrawing(corners[0], 180, 90));
            drawingContext.DrawGeometry(this.cornerBrushes[1], null, CreateArcDrawing(corners[1], 270, 90));
            drawingContext.DrawGeometry(this.cornerBrushes[2], null, CreateArcDrawing(corners[2], 0, 90));
            drawingContext.DrawGeometry(this.cornerBrushes[3], null, CreateArcDrawing(corners[3], 90, 90));

            drawingContext.Pop();
        }

        /// <summary>
        /// Create an Arc geometry drawing of an ellipse or circle.
        /// </summary>
        /// <param name="rect">Box to hold the whole ellipse described by the arc</param>
        /// <param name="startDegrees">Start angle of the arc degrees within the ellipse. 0 degrees is a line to the right.</param>
        /// <param name="sweepDegrees">Sweep angle, -ve = Counterclockwise, +ve = Clockwise</param>
        /// <returns>GeometryDrawing object</returns>
        private static PathGeometry CreateArcDrawing(Rect rect, double startDegrees, double sweepDegrees)
        {
            // degrees to radians conversion
            double startRadians = startDegrees * Math.PI / 180.0;
            double sweepRadians = sweepDegrees * Math.PI / 180.0;

            // x and y radius
            double dx = rect.Width / 2;
            double dy = rect.Height / 2;

            // determine the center point
            double xc = rect.X + dx;
            double yc = rect.Y + dy;

            // determine the start point
            double xs = rect.X + dx + (Math.Cos(startRadians) * dx);
            double ys = rect.Y + dy + (Math.Sin(startRadians) * dy);

            // determine the end point
            double xe = rect.X + dx + (Math.Cos(startRadians + sweepRadians) * dx);
            double ye = rect.Y + dy + (Math.Sin(startRadians + sweepRadians) * dy);

            bool isLargeArc = Math.Abs(sweepDegrees) > 180;
            SweepDirection sweepDirection = sweepDegrees < 0 ? SweepDirection.Counterclockwise : SweepDirection.Clockwise;

            PathGeometry pathGeometry = new PathGeometry();
            PathFigure pathFigure = new PathFigure();

            pathFigure.StartPoint = new Point(xc, yc);

            LineSegment line = new LineSegment(new Point(xs, ys), true);
            pathFigure.Segments.Add(line);

            pathFigure.StartPoint = new Point(xs, ys);
            ArcSegment arc = new ArcSegment(new Point(xe, ye), new Size(dx, dy), 0, isLargeArc, sweepDirection, true);
            pathFigure.Segments.Add(arc);

            line = new LineSegment(new Point(xc, yc), true);
            pathFigure.Segments.Add(line);

            pathFigure.IsFilled = true;
            pathGeometry.Figures.Add(pathFigure);

            return pathGeometry;
        }
    }
}
cboittin
  • 161
  • 9