WPF动画/ UI具有性能和基准测试功能

时间:2009-06-07 13:20:00

标签: .net wpf performance

我正在开发一些相对较小的概念验证,用于某些业务线,并且有一些花哨的WPF UI工作。甚至没有太疯狂,我已经看到一些非常糟糕的性能,当我使用很多功能时,我认为这是首先考虑WPF用于UI构建的主要原因。我在这里问了一个关于为什么我的动画在第一次运行时被停止的问题,最后我发现一个非常简单的UserControl花了将近半秒来构建它的可视化树。我能够找到解决症状的方法,但是初始化一个简单的控件需要很长时间才能让我感到烦恼。现在,我正在使用和不使用DropShadowEffect测试我的动画,结果是白天和黑夜。一个微妙的阴影使我的控制看起来更好,但它完全破坏了动画的流畅度。我甚至不开始使用字体渲染。当控件有一堆渐变画笔和一个投影时,我的动画计算会使文本模糊大约一整秒,然后慢慢聚焦。

所以,我想我的问题是,是否有已知的研究,博客文章或文章详细说明哪些功能在当前版本的WPF中对于关键业务应用程序是一种危险。像Effects(即DropShadowEffect),渐变画笔,关键帧动画等会对渲染质量(或者这些东西的组合)产生太大的负面影响吗? WPF 4.0的最终版本是否会纠正其中一些问题?我已经读过VS2010 beta有一些相同的问题,它们应该在最终版本中得到解决。是因为WPF本身的改进还是因为一半的应用程序将使用以前的技术重建?

3 个答案:

答案 0 :(得分:5)

答案 1 :(得分:4)

我还在研究与WPF中的DropShadow效果相关的性能问题。

基本上,经验法则总是“不要使用它”。 WPF中的PixelShader和位图DropShadow效果永远不适用于真实世界的应用......除非您的UI保持100%静态且没有移动部件,否则您的程序将在95%的客户群中像垃圾一样运行。

我要做的就是伪造它。

这是我刚刚提出的一个快速'肮脏的DropShadow装饰器,它可以作为PixelShader阴影效果的合理传容。你想通过缓存画笔等使它变得更干净,但这应该给你一个想法:

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 ..用法就是这样的;

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

..输出应该非常接近PS drophadow的作用..显然只适用于“四四方方”元素,但运行时的速度差异应该是夜晚&amp;一天,动画应该在高分辨率下如丝般顺滑,并且你可以保持你的UI看起来很漂亮。

答案 2 :(得分:0)

Sichbo 的答案仍然很好,但仅适用于正方形。 我对其进行了调整以添加 CornerRadius 属性,以便能够在圆角矩形上绘制阴影,以便它与 Border 控件配合使用。

我还将梯度计算更改为类似于计算机上实际 DropShadowEffect 的内容,但由于该渲染具有非常奇特的渲染效果,因此似乎无法完美再现。在任何情况下,您都可以尝试使用 expFactorattenuationFactor 使其适应您的需求。 expFactor 控制阴影消失的速度,attenuationFactor 通过偏移范围使其更亮(不是从 100% 到 0% 阴影,我们从例如 95% 到 -5% ,上限为 0%)。

渐变是在设置相关属性时计算的,而不是在每次渲染调用时计算。

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;
        }
    }
}