BrushProxy.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ DotNET / DotNET / 8.0 / untmp / WIN_WINDOWS / lh_tools_devdiv_wpf / Windows / wcp / Print / Reach / AlphaFlattener / BrushProxy.cs / 1 / BrushProxy.cs

                            //------------------------------------------------------------------------------ 
//  Microsoft Printing
//  Copyright (c) Microsoft Corporation, 2004
//
//  File:       BrushProxy.cs 
//
//  History: 
//      [....]: 05/18/2004  Created 
//-----------------------------------------------------------------------------
 
using System;
using System.Collections;              // for ArrayList
using System.Diagnostics;
 
#if DEBUG_RASTERIZATION
using System.IO; 
#endif 

using System.Windows;                  // for Rect                        WindowsBase.dll 
using System.Windows.Media;            // for Geometry, Brush, ImageData. PresentationCore.dll
using System.Windows.Media.Imaging;
using System.Security;
using System.Security.Permissions; 

using System.Windows.Xps.Serialization; 
 
namespace Microsoft.Internal.AlphaFlattener
{ 
    /// 
    ///
    /// 
    internal static class Configuration 
    {
        ///  
        /// Treat all alpha as opaque 
        /// 
        public static bool ForceAlphaOpaque;    // = false; 

        /// 
        /// Blend all alpha with white background
        ///  
        public static bool BlendAlphaWithWhite; // = false;
 
        ///  
        /// Controls how one cycle of gradient brush into N rings/slides of solid color
        ///  
        public static double GradientDecompositionDensity = 1;

#if DEBUG
        ///  
        /// Print out more trace information for checked build
        ///  
        public static int Verbose; // = 0; 

        ///  
        /// Serializes flattened primitives to XAML as debugging information.
        /// 
        public static bool SerializePrimitives = false;
#endif 

        ///  
        /// Displays debugging text at the top in GDI page output. 
        /// 
        public static bool DisplayPageDebugHeader = true; 

        /// 
        /// Maximum number of brushes to decompose before choosing rasterization
        ///  
        public static int DecompositionDepth = 3;
 
        ///  
        /// Maximum number of transparency layers to consider before ignore alpha flattening
        ///  
        public static int MaximumTransparencyLayer = 12;

        /// 
        /// Resolution for rasterization when brushes are too complicated 
        /// 
        public static int RasterizationDPI = 150; 
 
        /// 
        /// Output file to be passed to StartDoc 
        /// 
        public static string OutputFile; // = null;

     // public static bool ForceGrayScale    ; //= false; 
     // public static bool AlwaysUnfoldDB    ; //= false;
     // public static bool PreserveText      ; //= false; 
     // public static bool SupportAlphaBlend ; //= false; 

        ///  
        /// Maximum number of gradient steps allowed in gradient decomposition
        /// 
        public const int MaxGradientSteps = 4096;
 
        public static bool SetValue(string key, object val)
        { 
            switch (key) 
            {
#if DEBUG 
                case "Verbose":
                    Verbose = (int) val;
                    return true;
 
                case "SerializePrimitives":
                    SerializePrimitives = (bool)val; 
                    return true; 
#endif
 
                case "DisplayPageDebugHeader":
                    DisplayPageDebugHeader = (bool)val;
                    return true;
 
                case "ForceAlphaOpaque":
                    ForceAlphaOpaque = (bool) val; 
                    return true; 

                case "BlendAlphaWithWhite": 
                    BlendAlphaWithWhite = (bool) val;
                    return true;

                case "GradientDecompositionDensity": 
                    GradientDecompositionDensity = (double) val;
                    return true; 
 
                case "MaximumTransparencyLayer":
                    MaximumTransparencyLayer = (int) val; 
                    return true;

                case "RasterizationDPI":
                    RasterizationDPI = (int)val; 
                    return true;
 
                case "OutputFile": 
                    OutputFile = (string) val;
                    return true; 

                default:
                    return false;
            } 
        }
 
        ///  
        /// Estimate the cost of rasterizing an area
        ///  
        /// 
        /// 
        /// 
        static internal double RasterizationCost(double width, double height) 
        {
            return  1024 + width / 96 * RasterizationDPI * 
                           height / 96 * RasterizationDPI * 
                            3;
        } 

        static internal double RasterizationCost(double size)
        {
            return 1024 + size / 96 * RasterizationDPI * 3; 
        }
    }; 
 
    internal class PenProxy
    { 
        #region Constructors

        private PenProxy()
        { 
        }
 
        private PenProxy(Pen pen, BrushProxy brush) 
        {
            Debug.Assert(pen != null, "pen expected"); 
            Debug.Assert(brush != null, "brush expected");

            _pen   = pen;
            _brush = brush; 
        }
 
        #endregion 

        #region Public Methods 

        /// 
        /// Gets Avalon Pen represented by this PenProxy.
        ///  
        /// Ignores internal BrushProxy
        ///  
        public Pen GetPen(bool ignoreBrushProxy) 
        {
            if (ignoreBrushProxy) 
            {
                return _pen;
            }
            else 
            {
                Debug.Assert(_brush.BrushList == null, "Simple brush expected"); 
 
                Pen p = _pen.CloneCurrentValue();
 
                p.Brush = _brush.GetRealBrush();

                return p;
            } 
        }
 
        public bool IsOpaque() 
        {
            return _brush.IsOpaque(); 
        }

        public bool IsTransparent()
        { 
            return _brush.IsTransparent();
        } 
 
        #endregion
 
        #region Public Properties

        public BrushProxy StrokeBrush
        { 
            get
            { 
                return _brush; 
            }
            set 
            {
                _brush = value;
            }
        } 

        #endregion 
 
        #region Public Methods
 
        public void Scale(double ratio)
        {
            if (! Utility.AreClose(ratio, 1.0))
            { 
                _pen = _pen.CloneCurrentValue();
                _pen.Thickness *= ratio; 
            } 
        }
 
        public void PushOpacity(double opacity, BrushProxy opacityMask)
        {
            if ((_brush.Brush != null) && (BrushProxy.IsOpaqueWhite(_brush.Brush) || BrushProxy.IsOpaqueBlack(_brush.Brush)))
            { 
                _brush = _brush.Clone();
            } 
 
            _brush = _brush.PushOpacity(opacity, opacityMask);
        } 

        public PenProxy Clone()
        {
            PenProxy pen = new PenProxy(); 

            pen._pen   = this._pen; 
            pen._brush = this._brush; 

            return pen; 
        }

        #endregion
 
        #region Public Static Methods
 
        ///  
        /// Creates a PenProxy wrapper.
        ///  
        /// 
        /// 
        /// May return null if Brush is an empty brush
        public static PenProxy CreatePen(Pen pen, Rect bounds) 
        {
            Debug.Assert(pen != null, "pen expected"); 
            Debug.Assert(pen.Brush != null, "pen expected to have a brush"); 

            if (IsNull(pen)) 
            {
                return null;
            }
 
            BrushProxy brush = BrushProxy.CreateBrush(pen.Brush, bounds);
 
            if (brush == null) 
            {
                return null; 
            }
            else
            {
                return new PenProxy(pen, brush); 
            }
        } 
 
        /// 
        /// Creates a PenProxy wrapper around a user-provided Pen. 
        /// 
        /// 
        /// 
        /// Transformation hint to help determine rasterization bitmap size if needed 
        /// May return null if Brush is an empty brush
        ///  
        /// Attempts to simplify Pen.Brush via BrushProxy.ReduceBrush. 
        /// 
        public static PenProxy CreateUserPen(Pen pen, Rect bounds, Matrix brushToWorldTransformHint) 
        {
            Debug.Assert(pen != null, "pen expected");
            Debug.Assert(pen.Brush != null, "pen expected to have a brush");
 
            if (IsNull(pen))
            { 
                return null; 
            }
 
            BrushProxy brush = BrushProxy.CreateUserBrush(pen.Brush, bounds, brushToWorldTransformHint);

            if (brush == null)
            { 
                return null;
            } 
            else 
            {
                return new PenProxy(pen, brush); 
            }
        }

        ///  
        /// Determines if a pen is equivalent to a null pen.
        ///  
        ///  
        /// 
        /// A pen with a transparent brush is not considered empty unless Thickness is 0, since 
        /// the non-zero thickness will shrink the geometry fill despite being invisible.
        /// 
        public static bool IsNull(Pen pen)
        { 
            if (pen == null || pen.Thickness == 0)
            { 
                return true; 
            }
 
            return false;
        }

        #endregion 

        #region Private Fields 
 
        private BrushProxy _brush; // Brush within the pen, may change
        private Pen _pen; 

        #endregion
    }
 
    internal class BrushProxy
    { 
        #region Constructors 

        public BrushProxy() 
        {
            _brushList = new ArrayList();
            _opacity = 1.0;
        } 

        ///  
        /// Private constructor called by CreateBrush. 
        /// 
        ///  
        private BrushProxy(Brush brush)
        {
            _brush   = brush;
            _opacity = Utility.NormalizeOpacity(brush.Opacity); 
        }
 
        #endregion 

        #region Public Methods 

        public override string ToString()
        {
            string str = null; 

            if (_opacityOnly) 
            { 
                str = "^";
            } 

            if (_brush != null)
            {
                str = str + _brush.GetType(); 
            }
            else if (_brushList != null) 
            { 
                str = str + "BrushList[" + _brushList.Count + "]";
            } 

            if (_opacityMask != null)
            {
                str = str + "^" + _opacityMask.ToString(); 
            }
 
            return str; 
        }
 
        /// 
        /// Returns false if the brush has become empty.
        /// 
        ///  
        /// 
        public bool MakeBrushAbsolute(Rect bounds) 
        { 
            bool copied = false;
 
            _bounds = bounds;

            if (! (_brush is SolidColorBrush) && ! Utility.IsIdentity(_brush.RelativeTransform))
            { 
                _brush = _brush.CloneCurrentValue();
                copied = true; 
 
                Matrix mat = Utility.MergeTransform(_brush.Transform, _brush.RelativeTransform, bounds);
 
                _brush.Transform         = new MatrixTransform(mat);
                _brush.RelativeTransform = Transform.Identity;
            }
 
            // If brush is relative to a bounding box, make it absolute so that it
            // can be used for drawing primitives with other bounding boxes. 
 
            if (_brush is TileBrush)
            { 
                TileBrush tb = _brush as TileBrush;

                if (tb.ViewportUnits == BrushMappingMode.RelativeToBoundingBox)
                { 
                    if (!copied)
                    { 
                        tb = tb.CloneCurrentValue(); 
                        copied = true;
                    } 

                    Rect viewport = Utility.GetTileAbsoluteViewport(tb, bounds);

                    if (!Utility.IsRenderVisible(viewport)) 
                    {
                        // brush not visible anymore with new viewport 
                        return false; 
                    }
 
                    tb.ViewportUnits = BrushMappingMode.Absolute;
                    tb.Viewport = viewport;

                    _brush = tb; 
                }
 
                if (tb.ViewboxUnits == BrushMappingMode.RelativeToBoundingBox) 
                {
                    // Fix bug 1463955: Cloning DrawingBrush may cause its Drawing's bounds to become not visible. 
                    // Therefore clone before getting absolute viewbox, which'll return Empty upon invisible viewbox.
                    if (!copied)
                    {
                        tb = tb.CloneCurrentValue(); 
                        copied = true;
                    } 
 
                    Rect viewbox = Utility.GetTileAbsoluteViewbox(tb);
 
                    if (!Utility.IsValidViewbox(viewbox, tb.Stretch != Stretch.None))
                    {
                        // brush not visible anymore with new viewbox
                        return false; 
                    }
 
                    tb.ViewboxUnits = BrushMappingMode.Absolute; 
                    tb.Viewbox = viewbox;
 
                    _brush = tb;
                }
            }
 
            if (_brush is LinearGradientBrush)
            { 
                LinearGradientBrush lb = _brush as LinearGradientBrush; 

                if (lb.MappingMode == BrushMappingMode.RelativeToBoundingBox) 
                {
                    if (!copied)
                    {
                        lb = lb.CloneCurrentValue(); 
                        copied = true;
                    } 
 
                    lb.StartPoint = Utility.MapPoint(bounds, lb.StartPoint);
                    lb.EndPoint = Utility.MapPoint(bounds, lb.EndPoint); 

                    lb.MappingMode = BrushMappingMode.Absolute;

                    _brush = lb; 
                }
            } 
 
            if (_brush is RadialGradientBrush)
            { 
                RadialGradientBrush rb = _brush as RadialGradientBrush;

                if (rb.MappingMode == BrushMappingMode.RelativeToBoundingBox)
                { 
                    if (!copied)
                    { 
                        rb = rb.CloneCurrentValue(); 
                        copied = true;
                    } 

                    rb.Center = Utility.MapPoint(bounds, rb.Center);
                    rb.GradientOrigin = Utility.MapPoint(bounds, rb.GradientOrigin);
 
                    rb.RadiusX = Math.Abs(rb.RadiusX * bounds.Width);
                    rb.RadiusY = Math.Abs(rb.RadiusY * bounds.Height); 
 
                    rb.MappingMode = BrushMappingMode.Absolute;
 
                    _brush = rb;
                }
            }
 
            return true;
        } 
 
        /// 
        /// Add current brush to an ArrayList of BrushProxy 
        /// 
        /// 
        public void AddTo(BrushProxy bp)
        { 
            if (_brush != null)
            { 
                ArrayList list = bp._brushList; 

                if (list.Count == 0) 
                {
                    bp._opacityOnly = _opacityOnly;
                }
                else 
                {
                    Debug.Assert(bp._opacityOnly == _opacityOnly, "Brush and OpacityMask can't mix in a single list"); 
                } 

                list.Add(this); 
            }
            else
            {
                foreach (BrushProxy b in _brushList) 
                {
                    b.AddTo(bp); 
                } 
            }
        } 

        public BrushProxy Clone()
        {
            return MemberwiseClone() as BrushProxy; 
        }
 
        public BrushProxy PushOpacity(double opacity, BrushProxy opacityMask) 
        {
            _opacity *= Utility.NormalizeOpacity(opacity); 

            if (opacityMask != null)
            {
                _opacityMask = BrushProxy.BlendBrush(_opacityMask, opacityMask); 
            }
 
            if ((_opacityMask != null) && (_brush != null)) 
            {
                BrushProxy om = _opacityMask; 

                _opacityMask = null;

                // Try to blend OpacityMask into brush 
                BrushProxy result = this.BlendBrush(om);
 
                _opacityMask = om; 

                if (result != null) 
                {
                    return result;
                }
            } 

            return this; 
        } 

        ///  
        /// Check if a brush is opaque
        /// 
        /// True if brush is totally opaque (opacity==1)
        public bool IsOpaque() 
        {
            if ((_opacityMask != null) && !_opacityMask.IsOpaque()) 
            { 
                return false;
            } 

            if (!Utility.IsOpaque(_opacity))
            {
                return false; 
            }
 
            if (_brush is SolidColorBrush) 
            {
                SolidColorBrush y = _brush as SolidColorBrush; 

                return Utility.IsOpaque(y.Color.ScA);
            }
 
            if (_brush is GradientBrush)
            { 
                GradientBrush y = _brush as GradientBrush; 

                foreach (GradientStop gs in y.GradientStops) 
                {
                    if (!Utility.IsOpaque(gs.Color.ScA))
                    {
                        return false; 
                    }
                } 
 
                return true;
            } 

            if (_brush is TileBrush)
            {
                TileBrush tb = _brush as TileBrush; 

                // 
                // A TileBrush that does not completely cover a region may be regarded as 
                // effectively non-opaque, since underlying region may show through.
                // 
                if (!IsTileCompleteCover(tb))
                {
                    return false;
                } 

                // 
                // TileBrush may still completely cover target region, and so it 
                // may still be completely opaque. Check other TileBrush cases...
                // 
            }

            if (_brush is ImageBrush)
            { 
                ImageBrush ib = _brush as ImageBrush;
 
                if (_image == null) 
                {
                    _image = new ImageProxy((BitmapSource)ib.ImageSource); 
                }

                return _image.IsOpaque();
            } 

            if (_brush is DrawingBrush) 
            { 
                DrawingBrush db = _brush as DrawingBrush;
 
                if (db.Drawing == null)
                {
                    return false;
                } 

                Rect vb = db.Viewbox; 
 
                Debug.Assert(Utility.IsRenderVisible(vb), "TileBrush.Viewbox area must be positive");
 
                return IsDrawingOpaque(GetDrawingPrimitive(), new RectangleGeometry(vb), Matrix.Identity);
            }

            if (_brush != null) 
            {
                Debug.Assert(false, "IsOpaque(" + _brush.GetType() + ") not handled"); 
            } 

            if ((_brushList != null) && (_brushList.Count != 0)) 
            {
                // Check the first brush
                return (_brushList[0] as BrushProxy).IsOpaque();
            } 

            return false; 
        } 

        ///  
        /// Check if a brush is totally transparent
        /// 
        /// 
        public bool IsTransparent() 
        {
            if (_brush is SolidColorBrush) 
            { 
                SolidColorBrush y = _brush as SolidColorBrush;
 
                double opacity = _opacity * Utility.NormalizeOpacity(y.Color.ScA);

                return Utility.IsTransparent(opacity);
            } 

            if (_brush is GradientBrush) 
            { 
                GradientBrush y = _brush as GradientBrush;
 
                foreach (GradientStop gs in y.GradientStops)
                {
                    double opacity = _opacity * Utility.NormalizeOpacity(gs.Color.ScA);
 
                    if (!Utility.IsTransparent(opacity))
                    { 
                        return false; 
                    }
                } 

                return true;
            }
 
            if (_brush is DrawingBrush)
            { 
                if (Utility.IsTransparent(_opacity)) 
                {
                    return true; 
                }

                DrawingBrush db = _brush as DrawingBrush;
 
                if (db.Drawing == null)
                { 
                    return true; 
                }
 
                Rect vb = db.Viewbox;

                Debug.Assert(Utility.IsRenderVisible(vb), "TileBrush.Viewbox must be visible");
 
                // Fix bug 1505766: Ensure primitive geometric comparisons are done in world space,
                // otherwise accuracy issues arise if geometries are too small. 
                Matrix viewboxToViewportTransformHint = Utility.CreateViewboxToViewportTransform(db); 

                // viewbox geometry must have drawingToWorldTransformHint applied 
                Geometry viewboxGeometry = new RectangleGeometry(vb, 0, 0, new MatrixTransform(viewboxToViewportTransformHint));

                return IsDrawingTransparent(GetDrawingPrimitive(), viewboxGeometry, viewboxToViewportTransformHint);
            } 

            if (_brush is ImageBrush) 
            { 
                if (Utility.IsTransparent(_opacity))
                { 
                    return true;
                }

                ImageBrush ib = _brush as ImageBrush; 

                if (ib.ImageSource == null) 
                { 
                    return true;
                } 

                if (_image == null)
                {
                    _image = new ImageProxy((BitmapSource)ib.ImageSource); 
                }
 
                return _image.IsTransparent(); 
            }
 
            if (_brush != null)
            {
                Debug.Assert(false, "IsTransparent not handled " + _brush.GetType());
            } 

            return false; 
        } 

        public void ApplyTransform(Matrix trans) 
        {
            if (!trans.IsIdentity)
            {
                if (!_bounds.IsEmpty) 
                {
                    _bounds.Transform(trans); 
                } 

                if (_brushList == null) 
                {
                    if (!(_brush is SolidColorBrush))
                    {
                        _brush = _brush.CloneCurrentValue(); 

                        Matrix mat = Matrix.Identity; 
 
                        if (_brush.Transform != null)
                        { 
                            mat = _brush.Transform.Value;
                        }

                        mat.Append(trans); 

                        _brush.Transform = new MatrixTransform(mat); 
                    } 
                }
                else 
                {
                    foreach (BrushProxy brush in _brushList)
                    {
                        brush.ApplyTransform(trans); 
                    }
                } 
 
                if (_opacityMask != null)
                { 
                    _opacityMask.ApplyTransform(trans);
                }
            }
        } 

        public BrushProxy ApplyTransformCopy(Matrix trans) 
        { 
            BrushProxy result = this;
 
            if (!trans.IsIdentity)
            {
                result = result.Clone();
                result.ApplyTransform(trans); 
            }
 
            return result; 
        }
 
        /// 
        /// Calculate the blended brush of two brushes, the brush which can achieve the same
        /// result as drawing two brushes seperately
        ///  
        /// 
        ///  
        public BrushProxy BlendBrush(BrushProxy brushB) 
        {
            if (brushB.IsOpaque()) 
            {
                if (brushB._opacityOnly)
                {
                    // Ignore opaque OpacityMask 
                    return this;
                } 
                else if (!OpacityOnly) 
                {
                    // If the second brush is opaque, ignore the first one 
                    return brushB;
                }
            }
 
            // If there is no OpacitMask, blend two brushes when possible
            if ((this._opacityMask == null) && (brushB._opacityMask == null)) 
            { 
                SolidColorBrush sA = _brush as SolidColorBrush;
 
                if (sA != null)
                {
                    return BlendColorWithBrush(_opacityOnly, Utility.Scale(sA.Color, _opacity), brushB, false);
                } 

                SolidColorBrush sB = brushB.Brush as SolidColorBrush; 
 
                if (sB != null)
                { 
                    return BlendColorWithBrush(brushB._opacityOnly, Utility.Scale(sB.Color, brushB._opacity), this, true);
                }

                // Blend ImageBrush with compatible brush 
                if (_brush is ImageBrush)
                { 
                    BrushProxy bp = BlendImageBrush(brushB, true); 

                    if (bp != null) 
                    {
                        return bp;
                    }
                } 

                // Blend ImageBrush with compatible brush 
                if (brushB.Brush is ImageBrush) 
                {
                    BrushProxy bp = brushB.BlendImageBrush(this, false); 

                    if (bp != null)
                    {
                        return bp; 
                    }
                } 
 
                // Blend compatible LinearGradientBrushes
                BrushProxy p = BlendLinearGradientBrush(brushB); 

                if (p != null)
                {
                    return p; 
                }
 
                // Blend compatible RadialGradientBrushes 
                p = BlendRadialGradientBrush(brushB);
 
                if (p != null)
                {
                    return p;
                } 
            }
 
            // Abort if only one of them is an OpacityMask 
            if (this._opacityOnly ^ brushB._opacityOnly)
            { 
                return null;
            }

            // Construct a list of brushes 
            BrushProxy rslt = new BrushProxy();
 
            this.AddTo(rslt); 
            brushB.AddTo(rslt);
 
            return rslt;
        }

        public BitmapSource CreateBrushImage_ID(Matrix mat, int width, int height) 
        {
            Toolbox.StartEvent(Toolbox.DRXRASTERGUID); 
 
            RenderTargetBitmap brushImage = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
 
            if (this.BrushList != null)
            {
                foreach (BrushProxy b in this.BrushList)
                { 
                    brushImage.Render(new FillVisual(b, mat, width, height));
                } 
            } 
            else if (Brush != null)
            { 
                brushImage.Render(new FillVisual(this, mat, width, height));
            }

            Toolbox.EndEvent(Toolbox.DRXRASTERGUID); 

            return brushImage; 
        } 

#if DEBUG_RASTERIZATION 
        static int s_seq = 0;
#endif

        ///  
        /// Critical: This code calls an inernal PresentationCore function CriticalCopyPixels
        ///  
        [SecurityCritical] 
        public Byte[] CreateBrushImage(Matrix mat, int width, int height)
        { 
            BitmapSource brushImage = CreateBrushImage_ID(mat, width, height);

#if DEBUG_RASTERIZATION
            s_seq ++; 

            string filename = "file" + s_seq + ".png"; 
 
            BitmapEncoder encoder = new BitmapEncoderPng();
 
            encoder.Frames.Add(BitmapFrame.Create(brushImage));

            Stream imageStreamDest = new System.IO.FileStream(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
 
            encoder.Save(imageStreamDest);
#endif 
 
            int stride = width * 4;
 
            Byte[] brushPixels = new Byte[stride * height];
            FormatConvertedBitmap converter = new FormatConvertedBitmap();
            converter.BeginInit();
            converter.Source = brushImage; 
            converter.DestinationFormat = PixelFormats.Pbgra32;
            converter.EndInit(); 
 
            converter.CriticalCopyPixels(new Int32Rect(0, 0, width, height), brushPixels, stride, 0);
 
            return brushPixels;
        }

        ///  
        /// Convert Color + TileBrush + Color into a self-contained DrawingBrush for drawing with Avalon
        ///  
        ///  
        public Brush GetRealBrush()
        { 
            // do self-contained brush update
            UpdateRealBrush(true);

            return _brush; 
        }
 
        ///  
        /// Updates the Avalon brush, possibly making it self-contained.
        ///  
        /// 
        /// Self-contained updates generate a Brush that can be used by itself during rendering.
        /// Otherwise properties of BrushProxy are needed to properly render, and thus the update
        /// will only be useful for Primitive.OnRender. 
        /// 
        public void UpdateRealBrush(bool selfContained) 
        { 
            double oldOpacity = _opacity;
 
            if (!selfContained)
            {
                // we can keep opacity outside in BrushProxy to avoid rebuilding Brush.
                // Primitive.OnRender will push opacity for us. 
                _opacity = 1.0;
            } 
 
            if (
                _beforeDrawing.A != 0 ||            // merge before/after brush color into brush 
                _afterDrawing.A != 0 ||
                _drawingBrushChanged ||             // drawing Primitive has changed
                (_brushList != null && _brush == null))   // combine brushlist into one brush
            { 
                _brush = BuildBrush();
 
                // reset properties that have been merged into brush 
                _beforeDrawing = Colors.Transparent;
                _afterDrawing = Colors.Transparent; 
                _opacity = 1.0;

                // _drawing needs to be rebuilt to reflect new _brush
                _drawing = null; 
            }
            else if (!Utility.IsOpaque(_opacity)) 
            { 
                // push opacity into brush without rebuilding the brush
                if (_opacity != Utility.GetOpacity(_brush)) 
                {
                    _brush = _brush.CloneCurrentValue();

                    _brush.Opacity = _opacity; 
                }
            } 
 
            if (!selfContained)
            { 
                // keep opacity in BrushProxy
                _opacity = oldOpacity;
            }
        } 

        public int GetBrushDepth() 
        { 
            int depth = 0;
 
            if (_brushList != null)
            {
                foreach (BrushProxy b in _brushList)
                { 
                    depth += b.GetBrushDepth();
                } 
            } 
            else if (_brush is SolidColorBrush)
            { 
                depth = 0;
            }
            else if (_brush is GradientBrush)
            { 
                depth = 1;
            } 
            else if (_brush is ImageBrush) 
            {
                depth = 2; 
            }
            else if (_brush is DrawingBrush)
            {
                depth = 2; 
            }
            else 
            { 
                Debug.Assert(false, "Unexpected brush type");
                depth = 2; 
            }

            if (_opacityMask != null)
            { 
                depth += _opacityMask.GetBrushDepth();
            } 
 
            return depth;
        } 

        /// 
        /// Gets cost of printing this brush, roughly the number of pixels rasterized.
        ///  
        /// Size of fill region.
        /// Returns 0 if no pixels are rasterized and no complicatd flattening expected. 
        public double GetDrawingCost(Size size) 
        {
            if (Utility.IsTransparent(_opacity)) 
            {
                return 0;
            }
 
            double cost = 0;
 
            if (_brushList != null) 
            {
                // sum costs of individual brushes 
                foreach (BrushProxy brush in _brushList)
                {
                    cost += brush.GetDrawingCost(size);
                } 
            }
            else if (!(_brush is SolidColorBrush)) 
            { 
                // Calculate base cost of drawing through GDIExporter.
                bool isOpaque = IsOpaque(); 

                if (isOpaque && (_brush.Transform == null || Utility.IsScaleTranslate(_brush.Transform.Value)))
                {
                    LinearGradientBrush linearBrush = _brush as LinearGradientBrush; 

                    if (linearBrush != null) 
                    { 
                        // Check for axis-aligned linear gradients. As an optimization we collapse one of the
                        // dimensions to 1 pixel during rasterization. 
                        cost = Configuration.RasterizationCost(
                            Utility.AreClose(linearBrush.StartPoint.X, linearBrush.EndPoint.X) ? 1 : size.Width,
                            Utility.AreClose(linearBrush.StartPoint.Y, linearBrush.EndPoint.Y) ? 1 : size.Height
                            ); 
                    }
                } 
 
                if (cost == 0)
                { 
                    // All other brushes are rasterized by GDIExporter.
                    cost = Configuration.RasterizationCost(size.Width, size.Height);
                }
 
                // When not opaque, adjust cost to account for possible blending with other brushes
                // during flattening. 
                if (!isOpaque) 
                {
                    cost *= Utility.TransparencyCostFactor; 
                }
            }
            else
            { 
                // SolidColorBrush or null brush, assume zero cost
            } 
 
            return cost;
        } 

        public bool IsWhite()
        {
            if (_brush != null) 
            {
                SolidColorBrush scb = _brush as SolidColorBrush; 
 
                if (scb != null)
                { 
                    Color c = scb.Color;

                    if ((c.R == 255) && (c.G == 255) && (c.B == 255))
                    { 
                        return true;
                    } 
                } 
            }
 
            return false;
        }

        public void CloneRealBrush() 
        {
            if (_brush != null) 
            { 
                _brush = _brush.CloneCurrentValue();
            } 
        }

        /// 
        /// Determines if TileBrush viewport covers rectangle bounds. 
        /// 
        ///  
        ///  
        /// 
        /// Coordinates are in world space. 
        /// 
        public bool IsViewportCoverBounds(Rect bounds)
        {
            bool result = true; 
            TileBrush tileBrush = (TileBrush)Brush;
 
            Debug.Assert(tileBrush.ViewportUnits == BrushMappingMode.Absolute); 
            Rect viewport = tileBrush.Viewport;
 
            if (tileBrush.Transform != null && !tileBrush.Transform.IsIdentity)
            {
                viewport.Transform(tileBrush.Transform.Value);
            } 

            // compare viewport with geometry bounds 
            if (!Utility.AreClose(bounds, viewport)) 
            {
                // viewport dosen't cover entire geometry, multiple tiles are rendered 
                result = false;
            }

            return result; 
        }
 
        ///  
        /// Determines if TileBrush is tiled with respect to geometry bounds in world space.
        ///  
        /// 
        /// TileBrush is tiled if TileMode is not None, and viewport doesn't cover entire bounds.
        /// 
        ///  
        public bool IsTiled(Rect bounds)
        { 
            bool result = false; 

            if (Brush != null) 
            {
                TileBrush tileBrush = Brush as TileBrush;
                Debug.Assert(tileBrush.ViewportUnits == BrushMappingMode.Absolute);
 
                if (tileBrush != null &&
                    tileBrush.TileMode != TileMode.None && 
                    !IsViewportCoverBounds(bounds)) 
                {
                    // viewport doesn't cover geometry, then multiple tiles are rendered 
                    result = true;
                }
            }
 
            return result;
        } 
 
        /// 
        /// Unfolded DrawingBrush converted to Primitive. 
        /// 
        public Primitive GetDrawingPrimitive()
        {
            if (_drawing == null) 
            {
                DrawingBrush drawingBrush = _brush as DrawingBrush; 
 
                if (drawingBrush != null)
                { 
                    Debug.Assert(drawingBrush.Drawing != null, "DrawingBrush where Drawing == null should've been culled");

                    // Calculate transformation from Drawing to world space. This is needed to estimate
                    // size of Drawing objects in world space for rasterization bitmap dimensions. 
                    Matrix viewboxToViewportTransformHint = Utility.CreateViewboxToViewportTransform(drawingBrush);
 
                    _drawing = Primitive.DrawingToPrimitive(drawingBrush.Drawing, viewboxToViewportTransformHint); 
                }
            } 

            return _drawing;
        }
 
        /// 
        /// Render a Geometry using a BrushProxy, handling OpacityMask properly 
        ///  
        /// 
        ///  
        /// 
        public void DrawGeometry(DrawingContext dc, Pen pen, Geometry geo)
        {
            if (_brushList != null) 
            {
                foreach (BrushProxy b in _brushList) 
                { 
                    b.DrawGeometry(dc, null, geo);
                } 
            }
            else
            {
                UpdateRealBrush(true); 

                if (_opacityMask != null) 
                { 
                    dc.PushOpacityMask(_opacityMask.GetRealBrush());
                } 

                dc.DrawGeometry(_brush, null, geo);

                if (_opacityMask != null) 
                {
                    dc.Pop(); 
                } 
            }
 
            if (pen != null)
            {
                dc.DrawGeometry(null, pen, geo);
            } 
        }
 
        #endregion 

        #region Private Methods 

        /// 
        /// Builds an Avalon Brush from BrushProxy.
        ///  
        /// 
        private Brush BuildBrush() 
        { 
            Brush brush;
 
            // rebuild DrawingBrush _brush from Primitive _drawing if it has changed
            if (_drawingBrushChanged)
            {
                Debug.Assert(_drawing != null, "_drawing primitive changed, but it's null"); 

                // convert Primitive back to Drawing 
                DrawingGroup drawing = new DrawingGroup(); 

                using (DrawingContext context = drawing.Open()) 
                {
                    _drawing.OnRender(context);
                }
 
                //
                // Create DrawingBrush from Drawing, preserving current brush's TileBrush properties. 
                // Brush properties are pulled out into the BrushProxy. 
                //
                // Cannot use CreateDrawingBrush since it creates untiled brushes. _brush 
                // may be tiled.
                //
                DrawingBrush currentBrush = (DrawingBrush)_brush;
 
                DrawingBrush newBrush = Utility.CreateNonInheritingDrawingBrush(drawing);
 
                newBrush.AlignmentX = currentBrush.AlignmentX; 
                newBrush.AlignmentY = currentBrush.AlignmentY;
                newBrush.Stretch = currentBrush.Stretch; 
                newBrush.TileMode = currentBrush.TileMode;
                newBrush.Viewbox = currentBrush.Viewbox;
                newBrush.ViewboxUnits = currentBrush.ViewboxUnits;
                newBrush.Viewport = currentBrush.Viewport; 
                newBrush.ViewportUnits = currentBrush.ViewportUnits;
 
                newBrush.Opacity = currentBrush.Opacity; 
                newBrush.RelativeTransform = currentBrush.RelativeTransform;
                newBrush.Transform = currentBrush.Transform; 

                _brush = newBrush;
                _drawingBrushChanged = false;
            } 

            // build new brush 
            if (_opacityOnly) 
            {
                brush = BuildOpacityBrush(); 
            }
            else
            {
                brush = BuildRegularBrush(); 
            }
 
            return brush; 
        }
 
        /// 
        /// Gets brush's fill region bounds.
        /// 
        ///  
        /// 
        /// Empty resulting bounds indicates that bounds aren't needed, and that this brush 
        /// should simply fill entire target region. This is the case with SolidColorBrushes. 
        /// 
        private Rect GetBrushFillBounds() 
        {
            Rect bounds = Rect.Empty;

            // Remember that a brush list may still have _brush != null, due to building 
            // avalon brush from brush list and caching it.
            if (_brushList == null) 
            { 
                if (!(_brush is SolidColorBrush))
                { 
                    Debug.Assert(!_bounds.IsEmpty);

                    bounds = _bounds;
                } 
            }
            else 
            { 
                // brush list: get union of children brush bounds
                Debug.Assert(_brushList != null && _bounds.IsEmpty); 

                foreach (BrushProxy child in _brushList)
                {
                    bounds.Union(child.GetBrushFillBounds()); 
                }
            } 
 
            return bounds;
        } 

        /// 
        /// Creates drawing brush from drawing and fill region bounds.
        ///  
        /// 
        ///  
        ///  
        private static DrawingBrush CreateDrawingBrush(Drawing drawing, Rect bounds)
        { 
            DrawingBrush brush = Utility.CreateNonInheritingDrawingBrush(drawing);

            brush.ViewboxUnits = BrushMappingMode.Absolute;
            brush.Viewbox = drawing.Bounds; 

            if (bounds.IsEmpty) 
            { 
                // Empty bounds indiciates fill of entire region, so keep viewport as
                // relative unit rectangle. Do nothing. 
            }
            else
            {
                // Drawing was performed in absolute coordinates, use those as viewport. 
                brush.ViewportUnits = BrushMappingMode.Absolute;
                brush.Viewport = brush.Viewbox; 
            } 

            return brush; 
        }

        /// 
        /// Builds brush that is opacity mask. 
        /// 
        ///  
        private Brush BuildOpacityBrush() 
        {
            DrawingGroup drawing = new DrawingGroup(); 

            drawing.Opacity = _opacity;

            Rect bounds = GetBrushFillBounds(); 

            using (DrawingContext context = drawing.Open()) 
            { 
                // push before/after color and children brushes as opacity masks
                if (!Utility.IsTransparent(_beforeDrawing.ScA)) 
                {
                    context.PushOpacityMask(new SolidColorBrush(_beforeDrawing));
                }
 
                if (_brushList == null)
                { 
                    context.PushOpacityMask(_brush); 
                }
                else 
                {
                    foreach (BrushProxy child in _brushList)
                    {
                        context.PushOpacityMask(child.GetRealBrush()); 
                    }
                } 
 
                if (!Utility.IsTransparent(_afterDrawing.ScA))
                { 
                    context.PushOpacityMask(new SolidColorBrush(_afterDrawing));
                }

                // fill opacity mask bounds with opaqueness 
                Geometry geometry;
                if (bounds.IsEmpty) 
                { 
                    // unit rectangle representing entire brush fill region
                    geometry = new RectangleGeometry(new Rect(0, 0, 1, 1)); 
                }
                else
                {
                    // we have rect specifying fill bounds 
                    geometry = new RectangleGeometry(bounds);
                } 
 
                context.DrawGeometry(Brushes.Black, null, geometry);
            } 

            return CreateDrawingBrush(drawing, bounds);
        }
 
        /// 
        /// Renders drawing brush Primitive to DrawingBrush. 
        ///  
        private Brush BuildRegularBrush()
        { 
            DrawingGroup drawing = new DrawingGroup();

            Rect bounds = GetBrushFillBounds();
 
            using (DrawingContext context = drawing.Open())
            { 
                // construct geometry representing brush bounds if needed 
                RectangleGeometry geometry = null;
 
                if (!Utility.IsTransparent(_beforeDrawing.ScA) || !Utility.IsTransparent(_afterDrawing.ScA) || _brushList == null)
                {
                    if (bounds.IsEmpty)
                    { 
                        // unit rectangle representing entire brush fill region
                        geometry = new RectangleGeometry(new Rect(0, 0, 1, 1)); 
                    } 
                    else
                    { 
                        // we have rect specifying fill bounds
                        geometry = new RectangleGeometry(bounds);
                    }
                } 

                // Compose brush from before/after colors and brush/brushlist. 
                // Brush opacity does not apply to before/after colors. 
                if (!Utility.IsTransparent(_beforeDrawing.ScA))
                { 
                    context.DrawGeometry(new SolidColorBrush(_beforeDrawing), null, geometry);
                }

                bool opacityPushed = false; 

                if (_brushList == null) 
                { 
                    double inheritedOpacity = _opacity;
 
                    if (_brushList == null && !Utility.IsTransparent(_brush.Opacity))
                    {
                        // push only inherited opacity, since brush opacity will be applied
                        // during DrawGeometry. 
                        inheritedOpacity /= _brush.Opacity;
                    } 
 
                    if (!Utility.IsOpaque(inheritedOpacity))
                    { 
                        context.PushOpacity(inheritedOpacity);
                        opacityPushed = true;
                    }
 
                    context.DrawGeometry(_brush, null, geometry);
                } 
                else 
                {
                    if (!Utility.IsOpaque(_opacity)) 
                    {
                        context.PushOpacity(_opacity);
                        opacityPushed = true;
                    } 

                    foreach (BrushProxy child in _brushList) 
                    { 
                        Brush childBrush = child.GetRealBrush();
                        Rect childBounds = child.GetBrushFillBounds(); 

                        Geometry childGeometry;

                        if (childBounds.IsEmpty) 
                        {
                            // child brush fills entire region, use parent brush's geometry 
                            childGeometry = geometry; 
                        }
                        else 
                        {
                            // child brush has its own fill region
                            childGeometry = new RectangleGeometry(childBounds);
                        } 

                        context.DrawGeometry( 
                            childBrush, 
                            null,
                            childGeometry 
                            );
                    }
                }
 
                if (opacityPushed)
                { 
                    context.Pop(); 
                }
 
                if (!Utility.IsTransparent(_afterDrawing.ScA))
                {
                    context.DrawGeometry(new SolidColorBrush(_afterDrawing), null, geometry);
                } 
            }
 
            return CreateDrawingBrush(drawing, bounds); 
        }
 
        /// 
        /// Check if a Drawing is opaque within a rectangular area.
        /// Being opaque means rendering using it will not depending on any background color.
        ///  
        /// 
        ///  
        /// Approximate transformation from Drawing to world space 
        /// True if the drawing is definitely opaque within viewbox
        private bool IsDrawingOpaque(Primitive p, Geometry viewbox, Matrix transform) 
        {
            if (p == null)
            {
                return false; 
            }
 
            if (!Utility.IsOpaque(p.Opacity)) 
            {
                return false; 
            }

            CanvasPrimitive cp = p as CanvasPrimitive;
 
            if (cp != null)
            { 
                // recursively check children opaqueness 
                transform = p.Transform * transform;
 
                foreach (Primitive c in cp.Children)
                {
                    if (IsDrawingOpaque(c, viewbox, transform))
                    { 
                        return true;
                    } 
                } 

                return false; 
            }
            else if (p.IsOpaque)
            {
                // Get primitive geometry transformed to world space. GetShapeGeometry should 
                // already transform by Primitive.Transform.
                Geometry shape = Utility.TransformGeometry(p.GetShapeGeometry(), transform); 
 
                shape = Utility.Exclude(viewbox, shape, p.Transform);
 
                if (shape == null)
                {
                    return true;
                } 

                Rect bounds = shape.Bounds; 
 
                if (bounds.IsEmpty)
                { 
                    return true;
                }
            }
 
            return false;
        } 
 
        /// 
        /// Check if a Drawing is transparent within a rectangular area. 
        /// Being opaque means rendering using it will not depending on any background color.
        /// 
        /// 
        /// Viewbox in world space, must have drawingToWorldTransformHint applied 
        /// Approximate transformation from Drawing to world space
        /// True if the drawing is definitely transparent within viewbox 
        ///  
        /// Fix bug 1505766: drawingToWorldTransformHint is used to transform primitives to world space before doing
        /// geometric comparisons. Comparing geometry that's too small may result in false emptiness detection. 
        /// 
        private bool IsDrawingTransparent(Primitive p, Geometry viewbox, Matrix drawingToWorldTransformHint)
        {
            if (p == null) 
            {
                return true; 
            } 

            if (Utility.IsTransparent(p.Opacity)) 
            {
                return true;
            }
 
            CanvasPrimitive cp = p as CanvasPrimitive;
 
            if (cp != null) 
            {
                // recursively check children transparency 
                drawingToWorldTransformHint.Prepend(p.Transform);

                foreach (Primitive c in cp.Children)
                { 
                    if (!IsDrawingTransparent(c, viewbox, drawingToWorldTransformHint))
                    { 
                        return false; 
                    }
                } 

                return true;
            }
            else if (p.IsTransparent) 
            {
                return true; 
            } 
            else
            { 
                // Get primitive geometry transformed to world space. GetShapeGeometry should
                // already transform by Primitive.Transform.
                Geometry shape = Utility.TransformGeometry(p.GetShapeGeometry(), drawingToWorldTransformHint);
 
                bool empty;
 
                shape = Utility.Intersect(viewbox, shape, Matrix.Identity, out empty); 

                if (shape == null) 
                {
                    return true;
                }
 
                if (!Utility.IsRenderVisible(shape.Bounds))
                { 
                    return true; 
                }
            } 

            return false;
        }
 
        /// 
        /// Determines if a TileBrush brush completely fills target region. 
        /// A completely filling brush eliminates need to fill region with 
        /// background color prior to rendering the brush.
        ///  
        /// 
        /// 
        internal static bool IsTileCompleteCover(TileBrush brush)
        { 
            Debug.Assert(brush.ViewboxUnits == BrushMappingMode.Absolute);
            Debug.Assert(brush.ViewportUnits == BrushMappingMode.Absolute); 
 
            bool result = true;
 
            Rect content = Utility.GetTileContentBounds(brush);

            // Transform content to viewport. Content must cover entire viewport for TileBrush
            // to be completely covered (whether tiled or not). Otherwise the viewport will 
            // have transparent areas.
            Matrix viewboxToViewportTransform = Utility.CreateViewboxToViewportTransform(brush); 
 
            Rect worldContent = content;
            worldContent.Transform(viewboxToViewportTransform); 

            if (!worldContent.Contains(brush.Viewport))
            {
                // viewport has transparent areas 
                result = false;
            } 
 
            return result;
        } 

        /// 
        /// Blend an ImageBrush with a solid color
        ///  
        /// 
        ///  
        ///  
        private BrushProxy BlendImage(Color color, bool pre)
        { 
            ImageBrush ib = _brush.CloneCurrentValue() as ImageBrush;

            ImageProxy image = new ImageProxy((BitmapSource)ib.ImageSource);
 
            if (pre)
            { 
                image.BlendUnderColor(color, _opacity, _opacityOnly); 
            }
            else 
            {
                image.BlendOverColor(color, _opacity, _opacityOnly);
            }
 
            ib.ImageSource = image.GetImage();
            ib.Opacity = 1; 
 
            BrushProxy proxy = BrushProxy.CreateBrush(ib, _bounds);
 
            return proxy;
        }

        private BrushProxy BlendDrawingBrush(Color color, bool after) 
        {
            if (_opacityOnly) 
            { 
                Primitive drawing = GetDrawingPrimitive();
 
                if (drawing == null)
                {
                    return EmptyBrush; // return EmptyBrush instead of null to avoid possible null reference
                } 

                BrushProxy b = this.Clone(); 
 
                // Order is not important when blending with OpacityMask
                b._drawing = drawing.BlendOpacityMaskWithColor(BrushProxy.CreateColorBrush(color)); 
                b._drawingBrushChanged = true;
                b.OpacityOnly = false;

                return b; 
            }
            else 
            { 
                // fill Drawing bounds with the color
                return BlendComplexColor(color, after); 
            }
        }

        ///  
        /// Blend a non-filling TileBrush (such as when Stretch == None) with a solid color
        ///  
        ///  
        /// 
        ///  
        private BrushProxy BlendTileBrush(Color color, bool pre)
        {
            // fill the region not covered by TileBrush content with the color
            return BlendComplexColor(color, pre); 
        }
 
        ///  
        /// Performs a blend of color with brush for generic case that results in complex BrushProxy
        /// that needs reduction to Avalon brush. 
        /// 
        /// 
        /// 
        ///  
        /// 
        /// For some brush types (DrawingBrush, TileBrush where content doesn't completely 
        /// fill target geometry), we can't easily blend color directly into brush. Instead we 
        /// save color in BeforeFill and AfterFill, and upon rendering we build a DrawingBrush
        /// composed of the fill colors and the original brush. 
        /// 
        private BrushProxy BlendComplexColor(Color color, bool pre)
        {
            BrushProxy b = this.Clone(); 

            if (pre) 
            { 
                b._afterDrawing = Utility.BlendColor(b._afterDrawing, color);
            } 
            else
            {
                b._beforeDrawing = Utility.BlendColor(color, b._beforeDrawing);
            } 

            return b; 
        } 

        ///  
        /// Blends a gradient brush stop color with solid color.
        /// 
        /// 
        ///  
        /// 
        ///  
        private Color BlendStopColor(Color color, Color stopColor, bool pre) 
        {
            Color result; 

            if (_opacityOnly)
            {
                result = Utility.Scale(color, Utility.NormalizeOpacity(stopColor.ScA) * _opacity); 
            }
            else 
            { 
                if (pre)
                { 
                    result = Utility.BlendColor(Utility.Scale(stopColor, _opacity), color);
                }
                else
                { 
                    result = Utility.BlendColor(color, Utility.Scale(stopColor, _opacity));
                } 
            } 

            return result; 
        }

        /// 
        /// Calculates the number of stops blending two existing stops to generate. 
        /// 
        ///  
        /// Index of first stop 
        /// Index of second stop
        private static int CalculateBlendingStopCount( 
            GradientBrush brush,
            int firstIndex,
            int secondIndex
            ) 
        {
            GradientStop first = brush.GradientStops[firstIndex]; 
            GradientStop second = brush.GradientStops[secondIndex]; 

            // Calculate distance between stops in world space. 
            double stopDistance = 100.0;
            bool brushHandled = false;

            { 
                LinearGradientBrush b = brush as LinearGradientBrush;
 
                if (b != null) 
                {
                    brushHandled = true; 

                    // calculate gradient length
                    double dx = b.EndPoint.X - b.StartPoint.X;
                    double dy = b.EndPoint.Y - b.StartPoint.Y; 
                    double length = Math.Sqrt(dx * dx + dy * dy);
 
                    // map offsets to absolute coordinates 
                    stopDistance = (second.Offset - first.Offset) * length;
                } 
            }

            {
                RadialGradientBrush b = brush as RadialGradientBrush; 

                if (b != null) 
                { 
                    brushHandled = true;
 
                    stopDistance = Math.Max(b.RadiusX, b.RadiusY) * (second.Offset - first.Offset);

                    // use diamater
                    stopDistance *= 2; 
                }
            } 
 
            if (!brushHandled)
            { 
                Debug.Assert(false, "Unhandled GradientBrush type");
            }

            // 
            // Calculate stop count. Factors were experimentally determined for best appearance.
            // 
            // At small stop distances, the number of stops matters considerably, but it stabilizes 
            // to about 24 stops at large distances (including page-sized gradients).
            // 
            int stopCount = (int)Math.Ceiling(-6.297427 + 4.591693 * Math.Log(stopDistance));

            if (stopCount > 24)
            { 
                return 24;
            } 
            else if (stopCount < 3) 
            {
                // anything less looks obviously wrong even for 5x5 gradients 
                return 3;
            }
            else
            { 
                return stopCount;
            } 
        } 

        ///  
        /// Blend a gradient brush with a solid color
        /// 
        /// 
        ///  
        /// 
        ///  
        private BrushProxy BlendGradient(Color color, bool pre, ColorInterpolationMode interpolationMode) 
        {
            GradientBrush g = _brush as GradientBrush; 

            bool ScRgb   = interpolationMode == ColorInterpolationMode.ScRgbLinearInterpolation;
            bool addStop = false;
 
            //
            // Fix bug 1511960/1693561: Avalon no longer premultiplies alpha when calculating gradient color. 
            // When two neighboring stops exist where color differs in both alpha and color, or when 
            // stop colors differ in alpha and the color we're blending with is not opaque, this results
            // in a gradient whose stops can't be blended with solid color. We detect these cases and fall back 
            // to insert more Gradient stops.
            //
            // Example case: Gradient from 0xff0000ff to 0x00ff0000 on white background. With premultiplied
            // alpha when blending the stops, the resulting gradient is purely from blue to white. Without 
            // premultiplied alpha the gradient is from blue to red to white.
            // 
            if (!_opacityOnly && ! ScRgb) 
            {
                Debug.Assert(g.GradientStops != null); 

                bool colorOpaque = Utility.IsOpaque(color.ScA);

                for (int i = 1; i < g.GradientStops.Count; i++) 
                {
                    GradientStop stop0 = g.GradientStops[i - 1]; 
                    GradientStop stop1 = g.GradientStops[i]; 

                    Color color0 = stop0.Color; 
                    Color color1 = stop1.Color;

                    if (color0.A != color1.A)
                    { 
                        // alpha differs
                        if (!colorOpaque || 
                            color0.R != color1.R || 
                            color0.G != color1.G ||
                            color0.B != color1.B) 
                        {
                            // blend color isn't opaque, or color channels also differ.
                            // need to do stops.
                            addStop = true; 
                            break;
                        } 
                    } 
                }
            } 

            // Otherwise blend stops with color.
            g = g.CloneCurrentValue();
            GradientStopCollection gsc = new GradientStopCollection(); 

            g.Opacity = 1.0f; 
 
            if (! ScRgb && ! addStop)
            { 
                // Blend color into gradient stops.
                foreach (GradientStop gs in g.GradientStops)
                {
                    Color c = BlendStopColor(color, gs.Color, pre); 
                    gsc.Add(new GradientStop(c, gs.Offset));
                } 
            } 
            else
            { 
                //
                // Fix bug 1039871: NGCPP - radial gradient flattening, color interpolation doesn't match avalon
                //
                // This bug is due to incorrectness of blending color into gradient stops when using ScRgb 
                // gradient color interpolation. Such interpolation is mathematically incorrect due to non-linearity
                // of ScRgb. We fix by manually approximating the interpolation through the addition of stops. 
                // 
                g.ColorInterpolationMode = ColorInterpolationMode.SRgbLinearInterpolation;
 
                Debug.Assert(g.GradientStops.Count > 0);

                // Get the first stop.
                GradientStop prevStop = g.GradientStops[0]; 
                Color prevColor = Utility.NormalizeColor(prevStop.Color);
 
                GradientStop currentStop; 
                Color currentColor;
 
                for (int stopIndex = 1; stopIndex < g.GradientStops.Count; stopIndex++)
                {
                    //
                    // Get current stop, and generate stops interpolating in ScRgb space at positions 
                    // between prevStop inclusive and currentStop exclusive.
                    // 
                    currentStop = g.GradientStops[stopIndex]; 
                    currentColor = Utility.NormalizeColor(currentStop.Color);
 
                    int blendCount = CalculateBlendingStopCount(g, stopIndex - 1, stopIndex);

                    if (addStop)    // reducing addStop count for srgb
                    { 
                        blendCount = (blendCount + 1 ) / 2;
                    } 
 
                    for (int blendIndex = 0; blendIndex < (blendCount - 1); blendIndex++)
                    { 
                        float b = (float)blendIndex / (float)(blendCount - 1);
                        float a = 1.0f - b;

                        // Blend stop colors. 
                        Color blend;
 
                        if (ScRgb) 
                        {
                            blend = Color.FromScRgb( 
                                        a * prevColor.ScA + b * currentColor.ScA,
                                        a * prevColor.ScR + b * currentColor.ScR,
                                        a * prevColor.ScG + b * currentColor.ScG,
                                        a * prevColor.ScB + b * currentColor.ScB 
                                    );
                        } 
                        else 
                        {
                            blend = Color.FromArgb( 
                                        (Byte) (a * prevColor.A + b * currentColor.A),
                                        (Byte) (a * prevColor.R + b * currentColor.R),
                                        (Byte) (a * prevColor.G + b * currentColor.G),
                                        (Byte) (a * prevColor.B + b * currentColor.B) 
                                    );
                        } 
 
                        // Blend with the solid color we're blending gradient with.
                        blend = BlendStopColor( 
                            color,
                            blend,
                            pre
                            ); 

                        // Add the stop. 
                        double offset = prevStop.Offset + b * (currentStop.Offset - prevStop.Offset); 
                        gsc.Add(new GradientStop(blend, offset));
                    } 

                    // Next stop.
                    prevStop = currentStop;
                    prevColor = currentColor; 
                }
 
                // Add the last stop, which will be prevStop. 
                prevColor = BlendStopColor(color, prevStop.Color, pre);
                gsc.Add(new GradientStop(prevColor, prevStop.Offset)); 
            }

            g.GradientStops = gsc;
 
            BrushProxy bp = BrushProxy.CreateBrush(g, _bounds);
 
            return bp; 
        }
 
        /// 
        /// Blend a solid color brush with the first or last brush in a brush list.
        /// 
        ///  
        /// 
        ///  
        private BrushProxy BlendBrushList(BrushProxy b, bool first) 
        {
            Debug.Assert(b._brush is SolidColorBrush, "SolidColorBrush expected"); 
            Debug.Assert(!b._opacityOnly, "OpacityMask not expected");

            int count = _brushList.Count;
 
            // SolidColorBrush ^ [b1 b2 ... bn] -> b1' ^ [b2 ... bn]
            if (_opacityOnly) 
            { 
                if (count == 0)
                { 
                    return b;
                }

                Debug.Assert(first, "prefix only"); 

                b = b.BlendBrush(_brushList[0] as BrushProxy); 
 
                if (count == 2)
                { 
                    b._opacityMask = _brushList[1] as BrushProxy;
                }
                else if (count > 2)
                { 
                    b._opacityMask = new BrushProxy();
 
                    for (int i = 1; i < count; i++) 
                    {
                        (_brushList[i] as BrushProxy).AddTo(b._opacityMask); 
                    }
                }

                return b; 
            }
            else 
            { 
                BrushProxy list = new BrushProxy();
 
                foreach (BrushProxy bp in _brushList)
                {
                    if (first && (count == _brushList.Count))
                    { 
                        b.BlendBrush(bp).AddTo(list); // Blend current with first in list
                    } 
                    else if (!first && (count == 1)) 
                    {
                        bp.BlendBrush(b).AddTo(list); // Blend current with last in list 
                    }
                    else
                    {
                        bp.AddTo(list); 
                    }
 
                    count--; 
                }
 
                return list;
            }
        }
 
        /// 
        /// Check if brushA 'supercedes' brushB 
        ///  
        /// 
        ///  
        /// 
        private static bool Supercede(Brush brushA, Brush brushB)
        {
            TileBrush tA = brushA as TileBrush; 

            if ((tA != null) && (tA.Stretch == Stretch.Fill) && (tA.TileMode == TileMode.Tile)) 
            { 
                if (brushB is SolidColorBrush)
                { 
                    return true;
                }

                Matrix matA = brushA.Transform.Value; 
                Matrix matB = brushB.Transform.Value;
 
                matA.Invert(); 

                Matrix B2A = matB * matA; 

                if (Utility.IsScaleTranslate(B2A))
                {
                    Rect viewportA = tA.Viewport; 

                    TileBrush tB = brushB as TileBrush; 
 
                    if ((tB != null) && (tB.Stretch == Stretch.Fill))
                    { 
                        Rect viewportB = tB.Viewport;

                        viewportB.Transform(B2A);
 
                        double width = viewportB.Width;
                        double height = viewportB.Height; 
 
                        switch (tB.TileMode)
                        { 
                            case TileMode.Tile:
                                break;

                            case TileMode.FlipX: 
                                width *= 2;
                                break; 
 
                            case TileMode.FlipY:
                                height *= 2; 
                                break;

                            case TileMode.FlipXY:
                                width *= 2; 
                                height *= 2;
                                break; 
 
                            default:
                                return false; 
                        }

                        if (Utility.IsMultipleOf(viewportA.Width, width) &&
                            Utility.IsMultipleOf(viewportA.Height, height)) 
                        {
                            return true; 
                        } 
                    }
 
                    LinearGradientBrush lB = brushB as LinearGradientBrush;

                    if (lB != null)
                    { 
                        double multiplier = 1;
 
                        switch (lB.SpreadMethod) 
                        {
                            case GradientSpreadMethod.Reflect: 
                                multiplier = 2;
                                break;

                            case GradientSpreadMethod.Repeat: 
                                break;
 
                            default: 
                                return false;
                        } 

                        Point start = B2A.Transform(lB.StartPoint);
                        Point end = B2A.Transform(lB.EndPoint);
 
                        if (Utility.IsZero(start.X - end.X))
                        { 
                            double height = Math.Abs(start.Y - end.Y) * multiplier; 

                            if (Utility.IsMultipleOf(viewportA.Height, height)) 
                            {
                                return true;
                            }
                        } 
                        else if (Utility.IsZero(start.Y - end.Y))
                        { 
                            double width = Math.Abs(start.X - end.X) * multiplier; 

                            if (Utility.IsMultipleOf(viewportA.Width, width)) 
                            {
                                return true;
                            }
                        } 
                    }
                } 
            } 

            return false; 
        }

        /// 
        /// Blend a 'compatible' brush with an ImageBrush to form a new ImageBrush 
        /// 
        /// Brush to blend with 
        ///  
        /// New brush if successful
        private BrushProxy BlendImageBrush(BrushProxy brushB, bool pre) 
        {
            ImageBrush ib = _brush as ImageBrush;

            if ((ib != null) && (brushB.Brush != null) && Supercede(ib, brushB.Brush)) // Check for compatibility 
            {
                BitmapSource bs = (BitmapSource)(ib.ImageSource); 
 
                if (bs != null)
                { 
                    // Increase resolution for small image, to avoid losing information when blend with another brush
                    int imageWidth = bs.PixelWidth;
                    int imageHeight = bs.PixelHeight;
 
                    // Scale up the image if width or height is less than 128. Using 128 is just a heuristic.
                    // A better way would be finding the actual destination size and consider rasterization resolution 
                    int scalex = (128 + imageWidth - 1) / imageWidth; 
                    int scaley = (128 + imageHeight - 1) / imageHeight;
 
                    if ((scalex != 1) || (scaley != 1))
                    {
                        bs = new TransformedBitmap(bs, new ScaleTransform(scalex, scaley));
 
                        imageWidth *= scalex;
                        imageHeight *= scaley; 
                    } 

                    ImageProxy image = new ImageProxy(bs); 

                    Rect viewport = ib.Viewport;

                    Matrix mat = (ib.Transform == null) ? Matrix.Identity : ib.Transform.Value; 
                    mat.Invert();
 
                    mat.Translate(-viewport.Left, -viewport.Top); 

                    double tileWidth = viewport.Width; 
                    double tileHeight = viewport.Height;

                    mat.Scale(imageWidth / tileWidth, imageHeight / tileHeight);
 
                    image.PushOpacity(_opacity, _opacityMask, ib.Viewport, Matrix.Identity);
 
                    if (pre) 
                    {
                        image.BlendUnderBrush(_opacityOnly, brushB, mat); 
                    }
                    else
                    {
                        image.BlendOverBrush(_opacityOnly, brushB, mat); 
                    }
 
                    ImageBrush ibnew = ib.CloneCurrentValue() as ImageBrush; 

                    ibnew.Opacity      = 1.0; 
                    ibnew.ImageSource  = image.GetImage();
                    ibnew.ViewboxUnits = BrushMappingMode.RelativeToBoundingBox;
                    ibnew.Viewbox      = new Rect(0, 0, 1, 1);
 
                    BrushProxy bp = BrushProxy.CreateBrush(ibnew, _bounds);
 
                    Debug.Assert(bp != null, "Blending visible ImageBrush with another brush should yield non-empty brush"); 

                    bp._opacityOnly = _opacityOnly & brushB._opacityOnly; 

                    return bp;
                }
            } 

            return null; 
        } 

        ///  
        /// Blend GradientStopCollection if two GradientBrushes has the same GradientStop positions
        /// 
        /// 
        ///  
        /// 
        ///  
        ///  
        private static GradientStopCollection BlendGradientStops(GradientBrush a, bool opacityOnlyA, GradientBrush b, bool opacityOnlyB)
        { 
            if (a.ColorInterpolationMode != b.ColorInterpolationMode)
            {
                return null;
            } 

            GradientStopCollection gcA = a.GradientStops; 
            GradientStopCollection gcB = b.GradientStops; 

            if ((gcA != null) && (gcB != null) && (gcA.Count == gcB.Count)) 
            {
                for (int i = 0; i < gcA.Count; i++)
                {
                    GradientStop gsA = gcA[i]; 
                    GradientStop gsB = gcB[i];
 
                    if ((gsA == null) || (gsB == null) || !Utility.IsZero(gsA.Offset - gsB.Offset)) 
                    {
                        return null; 
                    }
                }

                GradientStopCollection g = new GradientStopCollection(); 

                for (int i = 0; i < gcA.Count; i++) 
                { 
                    GradientStop gsA = gcA[i];
                    GradientStop gsB = gcB[i]; 

                    GradientStop gs = new GradientStop();

                    gs.Offset = gsA.Offset; 

                    if (opacityOnlyB) 
                    { 
                        gs.Color = Utility.Scale(gsA.Color, gsB.Color.ScA);
                    } 
                    else if (opacityOnlyA)
                    {
                        gs.Color = Utility.Scale(gsB.Color, gsA.Color.ScA);
                    } 
                    else
                    { 
                        gs.Color = Utility.BlendColor(gsA.Color, gsB.Color); 
                    }
 
                    g.Add(gs);
                }

                return g; 
            }
 
            return null; 
        }
 
        /// 
        /// Blend two LinearGradientBrushes together, if they are compatible
        /// 
        ///  
        /// 
        private BrushProxy BlendLinearGradientBrush(BrushProxy brushB) 
        { 
            LinearGradientBrush lbA = this._brush as LinearGradientBrush;
            LinearGradientBrush lbB = brushB._brush as LinearGradientBrush; 

            if ((lbA == null) || (lbB == null))
            {
                return null; 
            }
 
            // 1. Same SpreadMethod 
            GradientSpreadMethod spread = lbA.SpreadMethod;
 
            if (spread != lbB.SpreadMethod)
            {
                return null;
            } 

            // 2. Vectors from StartPoint to EndPoint are the same 
            Point sA = lbA.Transform.Value.Transform(lbA.StartPoint); 
            Point eA = lbA.Transform.Value.Transform(lbA.EndPoint);
 
            double dxA = sA.X - eA.X;
            double dyA = sA.Y - eA.Y;

            Point sB = lbB.Transform.Value.Transform(lbB.StartPoint); 
            Point eB = lbB.Transform.Value.Transform(lbB.EndPoint);
 
            double dxB = sB.X - eB.X; 
            double dyB = sB.Y - eB.Y;
 
            if (!Utility.IsZero(dxA - dxB) || !Utility.IsZero(dyA - dyB))
            {
                return null;
            } 

            // 3. Check distance between two StartPoints 
 
            double dX = sA.X - sB.X;
            double dY = sA.Y - sB.Y; 

            int factor = 1;

            switch (spread) 
            {
                case GradientSpreadMethod.Pad: 
                    factor = 0;     // StartPoints must be same 
                    break;
 
                case GradientSpreadMethod.Reflect:
                    factor = 2;     // Double the cycle
                    break;
 
                case GradientSpreadMethod.Repeat:
                    factor = 1;     // one cycle 
                    break; 

                default: 
                    return null;
            }

            if ((Utility.IsZero(dX) || Utility.IsMultipleOf(dX, dxA * factor)) && 
                (Utility.IsZero(dY) || Utility.IsMultipleOf(dY, dyA * factor)))
            { 
                // 4. GradientStops have the same stop positions 
                GradientStopCollection g = BlendGradientStops(lbA, _opacityOnly, lbB, brushB._opacityOnly);
 
                if (g != null)
                {
                    BrushProxy bp = this.Clone();
 
                    LinearGradientBrush b = lbA.CloneCurrentValue();
 
                    b.GradientStops = g; 

                    bp._brush = b; 

                    return bp;
                }
            } 

            return null; 
        } 

        ///  
        /// Blend two RadialGradientBrushes together, if they are compatible
        /// 
        /// 
        ///  
        private BrushProxy BlendRadialGradientBrush(BrushProxy brushB)
        { 
            RadialGradientBrush rbA = this._brush as RadialGradientBrush; 
            RadialGradientBrush rbB = brushB._brush as RadialGradientBrush;
 
            if ((rbA == null) || (rbB == null))
            {
                return null;
            } 

            // 1. Same SpreadMethod 
            GradientSpreadMethod spread = rbA.SpreadMethod; 

            if (spread != rbB.SpreadMethod) 
            {
                return null;
            }
 
            // 2. Same center
            if (!Utility.AreClose(rbA.Center * rbA.Transform.Value, rbB.Center * rbB.Transform.Value)) 
            { 
                return null;
            } 

            // 3. Same Focus
            if (!Utility.AreClose(rbA.GradientOrigin * rbA.Transform.Value, rbB.GradientOrigin * rbB.Transform.Value))
            { 
                return null;
            } 
 
            // 4. Same Radiuses
            if (Utility.AreClose(new Vector(Math.Abs(rbA.RadiusX), Math.Abs(rbA.RadiusY)) * rbA.Transform.Value, 
                                 new Vector(Math.Abs(rbB.RadiusX), Math.Abs(rbB.RadiusY)) * rbB.Transform.Value))
            {
                // 5. GradientStops have the same stop positions
                GradientStopCollection g = BlendGradientStops(rbA, _opacityOnly, rbB, brushB._opacityOnly); 

                if (g != null) 
                { 
                    BrushProxy bp = this.Clone();
 
                    RadialGradientBrush b = rbA.CloneCurrentValue();

                    b.GradientStops = g;
 
                    bp._brush = b;
 
                    return bp; 
                }
            } 

            return null;
        }
 
        #endregion
 
        #region Static Methods 

        static private BrushProxy _blackBrush = new BrushProxy(Brushes.Black); 
        static private BrushProxy _whiteBrush = new BrushProxy(Brushes.White);

        static public bool IsOpaqueWhite(Brush brush)
        { 
            SolidColorBrush sb = brush as SolidColorBrush;
 
            if ((sb != null) && Utility.IsOpaque(sb.Opacity)) 
            {
                Color c = sb.Color; 

                if ((c.A == 255) && (c.R == 255) && (c.G == 255) && (c.B == 255))
                {
                    return true; 
                }
            } 
 
            return false;
        } 

        static public bool IsOpaqueBlack(Brush brush)
        {
            SolidColorBrush sb = brush as SolidColorBrush; 

            if ((sb != null) && Utility.IsOpaque(sb.Opacity)) 
            { 
                Color c = sb.Color;
 
                if ((c.A == 255) && (c.R == 0) && (c.G == 0) && (c.B == 0))
                {
                    return true;
                } 
            }
 
            return false; 
        }
 
        /// 
        /// Performs core work in constructing BrushProxy wrapper.
        /// 
        ///  
        /// 
        ///  
        ///  
        /// Handles opaque white/black brushes, but otherwise does not attempt to simplify
        /// or check for empty brush. 
        /// 
        private static BrushProxy CreateBrushCore(Brush brush, Rect bounds)
        {
            Debug.Assert(brush != null, "null brush"); 

            // empty bound requires that brush be absolute. zero area means empty brush. 
            if (bounds.Width == 0 || bounds.Height == 0) 
            {
                return null; 
            }

            //
            // Handle simple/degenerate brushes. 
            //
            if (IsOpaqueWhite(brush)) 
            { 
                return _whiteBrush;
            } 

            if (IsOpaqueBlack(brush))
            {
                return _blackBrush; 
            }
 
            // 
            // Create brush proxy.
            // 
            BrushProxy brushProxy = new BrushProxy(brush);

            if (!bounds.IsEmpty)
            { 
                // make brush absolute relative to specified bounds
                if (!brushProxy.MakeBrushAbsolute(bounds)) 
                { 
                    // Fix bug 1463955: Brush has become empty; return empty brush.
                    return null; 
                }
            }

            // 
            // Verify created brush. Ensure that we have absolute brush.
            // 
            GradientBrush gb = brushProxy.Brush as GradientBrush; 

            if (gb != null) 
            {
                Debug.Assert(gb.MappingMode == BrushMappingMode.Absolute, "absolute brush");
            }
 
            TileBrush tb = brushProxy.Brush as TileBrush;
 
            if (tb != null) 
            {
                // Viewport must be absolute, but Viewbox can be relative 
                Debug.Assert(tb.ViewportUnits == BrushMappingMode.Absolute, "absolute brush required for BrushProxy");
            }

            return brushProxy; 
        }
 
        ///  
        /// Creates a BrushProxy wrapper around SolidColorBrush.
        ///  
        /// 
        /// SolidColorBrushes are the only types of brushes that can be specified without fill bounds due
        /// to the uniformity of the fill. Otherwise bounds are needed for proper rebuilding of brushes in BuildBrush.
        ///  
        public static BrushProxy CreateColorBrush(Color color)
        { 
            if (Utility.IsTransparent(color.ScA)) 
            {
                return null; 
            }
            else
            {
                return CreateBrushCore(new SolidColorBrush(color), Rect.Empty); 
            }
        } 
 
        /// 
        /// Creates a BrushProxy wrapper around Brush. 
        /// 
        /// 
        /// Bounds of region Brush will be filling; used to convert Brush to use absolute coordinates.
        /// May return null if empty brush. 
        public static BrushProxy CreateBrush(Brush brush, Rect bounds)
        { 
            if (IsEmpty(brush)) 
            {
                return null; 
            }
            else
            {
                return CreateBrushCore(brush, bounds); 
            }
        } 
 
        /// 
        /// Creates a BrushProxy opacity mask wrapper around Brush. 
        /// 
        /// 
        /// Bounds of region Brush will be filling; used to convert Brush to use absolute coordinates.
        /// May return null if empty brush. 
        public static BrushProxy CreateOpacityMaskBrush(Brush brush, Rect bounds)
        { 
            if (IsEmpty(brush)) 
            {
                return null; 
            }
            else
            {
                BrushProxy result = CreateBrushCore(brush, bounds); 

                if (result != null) 
                { 
                    result.OpacityOnly = true;
                } 

                return result;
            }
        } 

        ///  
        /// Creates a BrushProxy wrapper around Brush provided by user. 
        /// 
        ///  
        /// 
        /// Transformation hint to help determine rasterization bitmap size if needed
        /// May return null if empty brush.
        ///  
        /// Attempts to simplify brush via BrushProxy.ReduceBrush.
        ///  
        public static BrushProxy CreateUserBrush(Brush brush, Rect bounds, Matrix brushToWorldTransformHint) 
        {
            // simplify brushes so we don't have to handle as many corner cases. this also 
            // simplifies empty brushes to null.
            brush = ReduceBrush(brush, bounds, brushToWorldTransformHint, Size.Empty);

            if (brush == null) 
            {
                return null; 
            } 
            else
            { 
                return CreateBrushCore(brush, bounds);
            }
        }
 
        /// 
        /// Returns true if brush is equivalent to a transparent brush. 
        /// 

 



 

 
 

        public static bool IsEmpty(Brush brush) 
        {
            if (brush == null)
            {
                // see remarks for why null brush is not empty 
                return false;
            } 
 
            if (Utility.IsTransparent(brush.Opacity))
            { 
                return true;
            }

            if (brush.Transform != null && !Utility.IsValid(brush.Transform.Value)) 
            {
                // non-invertible transform, ignore object 
                return true; 
            }
 
            SolidColorBrush solidBrush = brush as SolidColorBrush;

            if (solidBrush != null)
            { 
                if (Utility.IsTransparent(solidBrush.Color.ScA))
                { 
                    // transparent solid color brush 
                    return true;
                } 

                return false;
            }
 
            GradientBrush gradientBrush = brush as GradientBrush;
 
            if (gradientBrush != null) 
            {
                GradientStopCollection stops = gradientBrush.GradientStops; 

                if (stops == null || stops.Count == 0)
                {
                    // gradient contains no stops, treat as empty brush 
                    return true;
                } 
 
                foreach (GradientStop stop in stops)
                { 
                    if (!Utility.IsValid(stop.Offset))
                    {
                        // invalid stop offset, treat as invisible fill
                        return true; 
                    }
                } 
 
                LinearGradientBrush linearBrush = brush as LinearGradientBrush;
 
                if (linearBrush != null)
                {
                    if (!Utility.IsRenderVisible(linearBrush.StartPoint) || !Utility.IsRenderVisible(linearBrush.EndPoint))
                    { 
                        // endpoints not visible
                        return true; 
                    } 

                    return false; 
                }

                RadialGradientBrush radialBrush = brush as RadialGradientBrush;
 
                if (radialBrush != null)
                { 
                    if (!Utility.IsRenderVisible(radialBrush.Center) || 
                        !Utility.IsRenderVisible(radialBrush.GradientOrigin) ||
                        !Utility.IsRenderVisible(radialBrush.RadiusX) || 
                        !Utility.IsRenderVisible(radialBrush.RadiusY))
                    {
                        // radial gradient not visible
                        return true; 
                    }
 
                    return false; 
                }
 
                Debug.Assert(false, "Unhandled GradientBrush type");
                return false;
            }
 
            TileBrush tileBrush = brush as TileBrush;
 
            if (tileBrush != null) 
            {
                if (! Utility.IsRenderVisible(tileBrush.Viewport) || 
                    ! Utility.IsValidViewbox(tileBrush.Viewbox, tileBrush.Stretch != Stretch.None)
                   )
                {
                    return true; 
                }
 
                Rect contentBounds = Utility.GetTileContentBounds(tileBrush); 

                if (!Utility.IsRenderVisible(contentBounds)) 
                {
                    return true;
                }
 
                return false;
            } 
 
            Debug.Assert(false, "Unandled Brush type");
 
            return false;
        }

        ///  
        /// Simplifies the brush.
        ///  
        ///  
        /// 
        ///  
        /// Fixed page dimension
        /// 
        public static Brush ReduceBrush(Brush brush, Rect bounds, Matrix brushToWorldTransformHint, Size pageSize)
        { 
            if (brush == null || IsEmpty(brush))
            { 
                return null; 
            }
 
            double opacity = Utility.NormalizeOpacity(brush.Opacity);

            GradientBrush gb = brush as GradientBrush;
 
            if (gb != null)
            { 
                // check for gradient brush where colors are similar enough to be a solid brush 
                GradientStopCollection gsc = gb.GradientStops;
 
                Debug.Assert(gsc != null && gsc.Count > 0, "BrushProxy.IsEmpty should return true upon GradientBrush with zero stops");

                bool allTrans = true;
                bool allSame = true; 

                Color c = gsc[0].Color; 
 
                foreach (GradientStop gs in gsc)
                { 
                    if (!Utility.IsTransparent(gs.Color.ScA))
                    {
                        allTrans = false;
                    } 

                    if (!Color.AreClose(c, gs.Color)) 
                    { 
                        allSame = false;
                    } 
                }

                if (allTrans)
                { 
                    return null;
                } 
 
                if (allSame)
                { 
                    Brush b = new SolidColorBrush(c);

                    b.Opacity = opacity;
 
                    return b;
                } 
 
                return brush;
            } 

            VisualBrush vb = brush as VisualBrush;

            if (vb != null) 
            {
                Debug.Assert(!bounds.IsEmpty, "Bounds must not be empty for VisualBrush"); 
 
                if (vb.Visual == null)
                { 
                    return null;
                }

                // 
                // Convert from VisualBrush to DrawingBrush to reduce the number of brush types
                // we need to handle. 
                // 
                // We convert with help from VisualTreeFlattener, to handle transformations, opacity,
                // etc. The building of the resulting Drawing takes place in DrawingFlattenDrawingContext. 
                //
                DrawingGroup drawing = new DrawingGroup();

                using (DrawingContext context = drawing.Open()) 
                {
                    // 
                    // Fix bug 1452451: Reduction of VisualBrush to DrawingBrush does preserve dimensions of 
                    // non-visible elements such as Canvas, causing resulting DrawingBrush content to have
                    // possibly smaller bounds. But VisualBrush Viewbox may be in relative units, which can 
                    // cause stretching if content bounds change.
                    //
                    // Fix is to draw transparent rectangle covering Visual descendant bounds to preserve
                    // bounds. Also apply Visual clip. 
                    //
                    // Fix bug 1514270: VisualTreeFlattener may rasterize parts of Visual (3D content, 
                    // bitmap effects), but the rasterization is done in bitmap-space, which may lead to 
                    // poor fidelity as the low-resolution bitmaps are stretched to fill a large region.
                    // We need to provide VisualTreeFlattener a hint as to the transformation from 
                    // VisualBrush.Visual to world-space.
                    //
                    DrawingFlattenDrawingContext metroContext = new DrawingFlattenDrawingContext(context);
 
                    Matrix visualToWorldTransformHint = Utility.CreateViewboxToViewportTransform(vb, bounds);
                    visualToWorldTransformHint.Append(brushToWorldTransformHint); 
 
                    VisualTreeFlattener flattener = new VisualTreeFlattener(metroContext, pageSize);
                    flattener.InheritedTransformHint = visualToWorldTransformHint; 
                    flattener.VisualWalk(vb.Visual);

                    // Get Visual descendant bounds with clipping taken into consideration.
                    Rect visualBounds = VisualTreeHelper.GetDescendantBounds(vb.Visual); 

                    Geometry visualClip = VisualTreeHelper.GetClip(vb.Visual); 
 
                    if (visualClip != null)
                    { 
                        visualBounds.Intersect(visualClip.Bounds);
                    }

                    // Get visual transform, and draw transformed rectangle covering descendant bounds 
                    // to ensure Drawing bounds matches Visual descendant bounds.
                    Transform visualTransform = Utility.GetVisualTransform(vb.Visual); 
                    context.PushTransform(visualTransform); 

                    context.DrawGeometry( 
                        Brushes.Transparent,
                        null,
                        new RectangleGeometry(visualBounds)
                        ); 

                    context.Pop(); 
                } 

                DrawingBrush drawingBrush = Utility.CreateNonInheritingDrawingBrush(drawing); 

                // copy TileBrush properties
                drawingBrush.AlignmentX = vb.AlignmentX;
                drawingBrush.AlignmentY = vb.AlignmentY; 
                drawingBrush.Stretch = vb.Stretch;
                drawingBrush.TileMode = vb.TileMode; 
                drawingBrush.Viewbox = vb.Viewbox; 
                drawingBrush.ViewboxUnits = vb.ViewboxUnits;
                drawingBrush.Viewport = vb.Viewport; 
                drawingBrush.ViewportUnits = vb.ViewportUnits;

                // copy Brush properties
                drawingBrush.Opacity = opacity; 
                drawingBrush.RelativeTransform = vb.RelativeTransform;
                drawingBrush.Transform = vb.Transform; 
 
                return drawingBrush;
            } 

            ImageBrush ib = brush as ImageBrush;

            if (ib != null) 
            {
                BitmapSource bitmapSource = ib.ImageSource as BitmapSource; 
 
                if (bitmapSource != null)
                { 
                    // we can handle bitmap images
                    return brush;
                }
 
                DrawingImage drawingImage = ib.ImageSource as DrawingImage;
 
                if (drawingImage != null) 
                {
                    // convert to DrawingBrush to reduce number of ImageBrush.ImageSource types we need to handle 
                    DrawingBrush db = Utility.CreateNonInheritingDrawingBrush(drawingImage.Drawing);

                    // copy TileBrush properties
                    db.AlignmentX = ib.AlignmentX; 
                    db.AlignmentY = ib.AlignmentY;
                    db.Stretch = ib.Stretch; 
                    db.TileMode = ib.TileMode; 
                    db.Viewbox = ib.Viewbox;
                    db.ViewboxUnits = ib.ViewboxUnits; 
                    db.Viewport = ib.Viewport;
                    db.ViewportUnits = ib.ViewportUnits;

                    // copy Brush properties 
                    db.Opacity = opacity;
                    db.RelativeTransform = ib.RelativeTransform; 
                    db.Transform = ib.Transform; 

                    return db; 
                }

                Debug.Assert(false, "Unhandled ImageBrush.ImageSource type");
            } 

            return brush; 
        } 

        public static BrushProxy BlendBrush(BrushProxy one, BrushProxy two) 
        {
            if (one == null)
            {
                return two; 
            }
 
            if (two == null) 
            {
                return one; 
            }

            return one.BlendBrush(two);
        } 

        public static BrushProxy BlendColorWithBrush(bool opacityOnly, Color colorA, BrushProxy brushB, bool reverse) 
        { 
            if (opacityOnly)
            { 
                if (Utility.IsOpaque(colorA.ScA))
                {
                    return brushB;
                } 

                BrushProxy b = brushB.Clone(); 
 
                b.PushOpacity(colorA.ScA, null);
 
                return b;
            }

            if (brushB._opacityMask != null) 
            {
                if (reverse) 
                { 
                    return brushB.BlendBrush(BrushProxy.CreateColorBrush(colorA));
                } 
                else
                {
                    return BrushProxy.CreateColorBrush(colorA).BlendBrush(brushB);
                } 
            }
 
            // SolidColorBrush * BrushList 
            if (brushB._brushList != null)
            { 
                return brushB.BlendBrushList(BrushProxy.CreateColorBrush(colorA), !reverse);
            }

            Debug.Assert(brushB.Brush != null, "null brush not expected"); 

            if (reverse) 
            { 
                if (Utility.IsOpaque(colorA.ScA))
                { 
                    return BrushProxy.CreateColorBrush(colorA);
                }
            }
            else 
            {
                if (brushB.IsOpaque()) 
                { 
                    if (brushB._opacityOnly)
                    { 
                        return BrushProxy.CreateColorBrush(colorA);
                    }
                    else
                    { 
                        return brushB;
                    } 
                } 
            }
 
            // SolidColorBrush * SolidColorBrush
            if (brushB.Brush is SolidColorBrush)
            {
                SolidColorBrush sB = brushB.Brush as SolidColorBrush; 

                if (brushB._opacityOnly) 
                { 
                    return BrushProxy.CreateColorBrush(
                        Utility.Scale( 
                            colorA,
                            Utility.NormalizeOpacity(sB.Color.ScA) * brushB._opacity
                            )
                        ); 
                }
                else 
                { 
                    return BrushProxy.CreateColorBrush(
                        Utility.BlendColor( 
                            colorA,
                            Utility.Scale(
                                sB.Color,
                                brushB._opacity 
                                )
                            ) 
                        ); 
                }
            } 

            //
            // SolidColorBrush * TileBrush where TileBrush does not completely fill
            // (example: TileBrush.Stretch == Stretch.None) 
            //
            // We need to fill region with color before/after filling with TileBrush, 
            // since TileBrush does not completely fill. An alternative is to clip 
            // TileBrush fill to its content, but that requires an internal Avalon fix.
            // 
            if (brushB.Brush is TileBrush && !brushB._opacityOnly)
            {
                TileBrush tileBrush = (TileBrush)brushB.Brush;
                if (!IsTileCompleteCover(tileBrush)) 
                {
                    return brushB.BlendTileBrush(colorA, reverse); 
                } 
            }
 
            // SolidColorBrush * GradientBrush
            if (brushB.Brush is GradientBrush)
            {
                GradientBrush gradientBrush = (GradientBrush)brushB.Brush; 
                return brushB.BlendGradient(colorA, reverse, gradientBrush.ColorInterpolationMode);
            } 
 
            // SolidColorBrush * ImageBrush
            if (brushB.Brush is ImageBrush) 
            {
                return brushB.BlendImage(colorA, reverse);
            }
 
            // SolidColorBrush * DrawingBrush
            if (brushB.Brush is DrawingBrush) 
            { 
                return brushB.BlendDrawingBrush(colorA, reverse);
            } 

            Debug.Assert(false, "Brush type not expected");

            return brushB; 
        }
 
        #endregion 

        #region Public Properties 

        public Brush Brush
        {
            get 
            {
                return _brush; 
            } 
        }
 
        public double Opacity
        {
            get
            { 
                return _opacity;
            } 
            set 
            {
                Debug.Assert(Utility.NormalizeOpacity(value) == value, "BrushProxy.Opacity must always be normalized"); 

                _opacity = value;
            }
        } 

        public BrushProxy OpacityMask 
        { 
            get
            { 
                return _opacityMask;
            }
            set
            { 
                _opacityMask = value;
            } 
        } 

        ///  
        /// Color fill prior to brush fill.
        /// 
        /// 
        /// Not affected by brush opacity. 
        /// 
        public Color BeforeFill 
        { 
            get
            { 
                return _beforeDrawing;
            }
        }
 
        /// 
        /// Color fill after brush fill. 
        ///  
        /// 
        /// Not affected by brush opacity. 
        /// 
        public Color AfterFill
        {
            get 
            {
                return _afterDrawing; 
            } 
        }
 
        public ArrayList BrushList
        {
            get
            { 
                return _brushList;
            } 
        } 

        public bool OpacityOnly 
        {
            get
            {
                return _opacityOnly; 
            }
            set 
            { 
                _opacityOnly = value;
            } 
        }

        [Flags]
        // Brush types are used both for classification and determine which brush to decompose first 
        // Brush with higher number is decomposed first
        public enum BrushTypes 
        { 
            None = 0,
 
            SolidColorBrush = 1,
            ImageBrush = 2,
            DrawingBrush = 4,
            BrushList = 8, 
            LinearGradientBrush = 16,  // Favour linear gradient brush in decomposition
            RadialGradientBrush = 32,  // Favour radial gradient brush more in decomposition 
 
            HasOpacityMask = 64,  // Decompose brushes with opacity mask first
            OpacityMaskOnly = 128 
        };

        public BrushTypes BrushType
        { 
            get
            { 
                BrushTypes result = BrushTypes.None; 

                if (_opacityOnly) 
                {
                    result |= BrushTypes.OpacityMaskOnly;
                }
 
                if (_opacityMask != null)
                { 
                    result |= BrushTypes.HasOpacityMask; 
                }
 
                if (_brushList != null)
                {
                    result |= BrushTypes.BrushList;
                } 
                else if (_brush != null)
                { 
                    if (_brush is SolidColorBrush) 
                    {
                        result |= BrushTypes.SolidColorBrush; 
                    }
                    else if (_brush is LinearGradientBrush)
                    {
                        result |= BrushTypes.LinearGradientBrush; 
                    }
                    else if (_brush is RadialGradientBrush) 
                    { 
                        result |= BrushTypes.RadialGradientBrush;
                    } 
                    else if (_brush is ImageBrush)
                    {
                        result |= BrushTypes.ImageBrush;
                    } 
                    else if (_brush is DrawingBrush)
                    { 
                        result |= BrushTypes.DrawingBrush; 
                    }
                    else 
                    {
                        Debug.Assert(false, "Unexpected brush type");
                    }
                } 

                return result; 
            } 
        }
 
        static public BrushProxy EmptyBrush
        {
            get
            { 
                if (s_EmptyBrush == null)
                { 
                    s_EmptyBrush = new BrushProxy(new SolidColorBrush(Color.FromArgb(0, 0, 0, 0))); 
                }
 
                return s_EmptyBrush;
            }
        }
 
        #endregion
 
        #region Private Fields 

        private Brush _brush; 
        private ImageProxy _image;       // Image proxy for ImageBrush
        private double _opacity;         // Combined brush opacity and element opacity
        private BrushProxy _opacityMask;
 
        private Rect _bounds = Rect.Empty;// bounds of region this brush is filling, from CreateBrush
 
        // 
        // _beforeDrawing and _afterDrawing are used to specify fill colors before and
        // after rendering TileBrush. It is needed since depending on brush content and 
        // TileBrush.Stretch, the brush may not completely fill target geometry, thus making
        // it impossible to somehow blend colors into the underlying Brush itself.
        //
        // When rendering, push/pop these colors before and after pushing/popping _opacity. 
        //
        private Color _beforeDrawing = Color.FromArgb(0, 0, 0, 0); 
        private Color _afterDrawing = Color.FromArgb(0, 0, 0, 0); 

        // 
        // If _brush is DrawingBrush, its content is converted to Primitive _drawing so
        // that we can push opacity into it, etc. Modifying _drawing desynchronizes it with
        // _brush, the latter of which is used when rasterizing with Avalon. We take notice
        // of desynchronization to force recomposition of DrawingBrush for rasterization. 
        //
        // It may be the case that (_brush is DrawingBrush && _drawing == null), which indicates 
        // _drawing needs to be rebuilt from _brush. 
        //
        private Primitive _drawing;    // Temp solution for Drawing within a DrawingBrush 
        private bool _drawingBrushChanged;

        private ArrayList _brushList;
        private bool _opacityOnly; 

        static private BrushProxy s_EmptyBrush; 
        #endregion 
    }
 
    /// 
    /// DrawingVisual for rasterizing a BrushProxy into a bitmap
    /// 
    internal class FillVisual : DrawingVisual 
    {
        public FillVisual(BrushProxy brush, Matrix mat, int width, int height) : base() 
        { 
            using (DrawingContext ctx = RenderOpen())
            { 
                if (brush.Brush != null)
                {
                    Brush b = brush.Brush.CloneCurrentValue();
 
                    Matrix bm = b.Transform.Value;
 
                    bm.Append(mat); 

                    b.Transform = new MatrixTransform(bm); 
                    b.Opacity = brush.Opacity;

                    Rect rect = new Rect(0, 0, width, height);
 
                    BrushProxy mask = brush.OpacityMask;
 
                    // Bug 1699894: OpacityMask is now supported by DrawingContext 
                    if (mask != null)
                    { 
                        Brush mb = mask.GetRealBrush().CloneCurrentValue();

                        Matrix mbm = mb.Transform.Value;
 
                        mbm.Append(mat);
 
                        mb.Transform = new MatrixTransform(mbm); 
                        mb.Opacity = mask.Opacity;
 
                        ctx.PushOpacityMask(mb);
                    }

                    if (brush.BeforeFill.A != 0) 
                    {
                        ctx.DrawRectangle(new SolidColorBrush(brush.BeforeFill), null, rect); 
                    } 

                    ctx.DrawRectangle(b, null, rect); 

                    if (brush.AfterFill.A != 0)
                    {
                        ctx.DrawRectangle(new SolidColorBrush(brush.AfterFill), null, rect); 
                    }
 
                    if (mask != null) 
                    {
                        ctx.Pop(); 
                    }
                }
                else
                { 
                    Debug.Assert(false, "Single brush expected");
                } 
            } 
        }
    } 

    /// 
    /// Represending color using 4 floating point numbers.
    /// We need to store temp out of [0..1] range color even in SRgb mode 
    /// 
    internal struct MyColor 
    { 
        #region Public Fields
 
        public float m_a;
        public float m_r;
        public float m_g;
        public float m_b; 

        #endregion 
 
        #region Constructors
 
        private MyColor(float a, float r, float g, float b)
        {
            Debug.Assert(
                Utility.IsValid(a) && Utility.IsValid(r) && Utility.IsValid(g) && Utility.IsValid(b), 
                "MyColor float constructor has invalid color values"
                ); 
 
            m_a = a;
            m_r = r; 
            m_g = g;
            m_b = b;
        }
 
        public MyColor(Color c, ColorInterpolationMode ciMode)
        { 
            if (ciMode == ColorInterpolationMode.ScRgbLinearInterpolation) 
            {
                c = Utility.NormalizeColor(c); 

                m_a = c.ScA;
                m_r = c.ScR;
                m_g = c.ScG; 
                m_b = c.ScB;
            } 
            else 
            {
                m_a = (float) (c.A / 255.0); 
                m_r = (float) (c.R / 255.0);
                m_g = (float) (c.G / 255.0);
                m_b = (float) (c.B / 255.0);
            } 
        }
 
        #endregion 

        #region Public Methods 

        public Color ToColor(ColorInterpolationMode ciMode)
        {
            if (ciMode == ColorInterpolationMode.ScRgbLinearInterpolation) 
            {
                return Color.FromScRgb(m_a, m_r, m_g, m_b); 
            } 
            else
            { 
                return Color.FromArgb(Utility.OpacityToByte(m_a), Utility.ColorToByte(m_r), Utility.ColorToByte(m_g), Utility.ColorToByte(m_b));
            }
        }
        #endregion 

        #region Public Static Methods 
 
        public static MyColor Interpolate(MyColor c0, float a, MyColor c1, float b)
        { 
            return new MyColor(c0.m_a * a + c1.m_a * b,
                               c0.m_r * a + c1.m_r * b,
                               c0.m_g * a + c1.m_g * b,
                               c0.m_b * a + c1.m_b * b); 
        }
 
        #endregion 
    }
 
    internal class GradientColor
    {
        #region Constructors
 
        public GradientColor(GradientStopCollection stops, double opacity, GradientSpreadMethod spread, ColorInterpolationMode ciMode)
        { 
            Debug.Assert(Utility.IsValid(opacity), "Opacity comes from BrushProxy, should be valid"); 

            double min = Double.MaxValue; 
            double max = Double.MinValue;

         // _count  = 0;
            _color  = new MyColor[stops.Count + 2]; 
            _offset = new double [stops.Count + 2];
            _ciMode = ciMode; 
 
            for (int i = 0; i < stops.Count; i++)
            { 
                double offset = stops[i].Offset;

                // Only need the largest negative offset
                if ((offset < 0) && (min < 0) && (offset < min)) 
                {
                    continue; 
                } 

                // Only need the smalest positive offset larger than 1 
                if ((offset > 1) && (max > 1) && (offset > max))
                {
                    continue;
                } 

                // Gradient color interpolation is now not premultiplied. 
                // 
                MyColor color = new MyColor(stops[i].Color, _ciMode);
                color.m_a = (float)(color.m_a * opacity); 

                if (AddStop(offset, color))
                {
                    min = Math.Min(min, offset); 
                    max = Math.Max(max, offset);
                } 
            } 

            if (_count >= 2) 
            {
                if (min > 0)
                {
                    AddStop(0, InterpolateColor(0, _offset[0], _color[0], _offset[1], _color[1])); 
                }
 
                if (max < 1) 
                {
                    AddStop(1, InterpolateColor(1, _offset[_count - 2], _color[_count - 2], _offset[_count - 1], _color[_count - 1])); 
                }
            }

            _spread = spread; 
        }
 
        #endregion 

        #region Public Methods 

        /// 
        /// Gets a color when gradient brush has invalid endpoints.
        ///  
        /// 
        ///  
        /// This currently retrieves the last color to match Avalon in some cases. 
        /// 
        public Color GetInvalidGradientColor() 
        {
            return _color[_count - 1].ToColor(_ciMode);
        }
 
        public Color GetColor(int i, int steps)
        { 
            if (_count == 0) 
            {
                Debug.Assert(false);    // Optimization is needed before reaching here 

                return Color.FromArgb(0, 255, 255, 255); // transparent white
            }
            else if (_count == 1) 
            {
                Debug.Assert(false);    // Optimization is needed before reaching here 
 
                return _color[0].ToColor(_ciMode);
            } 

            Debug.Assert(steps > 0);

            switch (_spread) 
            {
                case GradientSpreadMethod.Pad: 
                    if (i < 0) 
                    {
                        i = 0; 
                    }
                    else if (i >= steps)
                    {
                        i = steps - 1; 
                    }
                    break; 
 
                case GradientSpreadMethod.Reflect:
                    if (i < 0) 
                    {
                        i = -i;
                    }
 
                    i = i % (steps * 2);
 
                    if (i >= steps) 
                    {
                        i = steps * 2 - 1 - i; 
                    }
                    break;

                case GradientSpreadMethod.Repeat: 
                default:
                    while (i < 0) 
                    { 
                        i += steps;
                    } 

                    i = i % steps;
                    break;
            } 

            Debug.Assert((i >= 0) && (i < steps)); 
 
            float t = (float) (i) / (steps - 1);
 
            for (int c = 0; c < _count - 1; c ++)
            {
                if (t >= _offset[c] && (t <= _offset[c + 1]))
                { 
                    MyColor mc = InterpolateColor(t, _offset[c], _color[c], _offset[c + 1], _color[c + 1]);
 
                    return mc.ToColor(_ciMode); 
                }
            } 

            Debug.Assert(false);

            return Color.FromArgb(0, 255, 255, 255); 
        }
 
        ///  
        /// Estimate color distance.
        /// Same colors have distance of 0. 
        /// (1, 1, 1, 1) and (0, 0, 0, 0) have distance of 2.
        /// 
        private static double Distance(MyColor c0, MyColor c1)
        { 
            double sum = 0;
 
            double d; 

            d = c0.m_a - c1.m_a; sum += d * d; 
            d = c0.m_r - c1.m_r; sum += d * d;
            d = c0.m_g - c1.m_g; sum += d * d;
            d = c0.m_b - c1.m_b; sum += d * d;
 
            return Math.Sqrt(sum);
        } 
 
        /// 
        /// Estimate total color distance for gradient stops between [0..1] 
        /// 
        /// 
        public double ColorDistance()
        { 
            double distance = 0;
 
            for (int i = 1; i < _count; i++) 
            {
                if ((_offset[i - 1] >= 0) && (_offset[i] <= 1)) 
                {
                    distance += Distance(_color[i - 1], _color[i]);
                }
            } 

            return distance; 
        } 

        public int BandSteps(double distance) 
        {
            double step = distance / 96 * 20;                   // 20 steps per inch, for color distance 1 (#FF0000 & #000000)

            step *= ColorDistance();                            // Adjust by color distance 
            step *= Configuration.GradientDecompositionDensity; // Adjust by external supplied density
 
            return (int)Math.Ceiling(Math.Max(5, step));        // At least five. Radials look bad with less steps. 
        }
 
        #endregion

        #region Private Methods
 
        private bool AddStop(double offset, MyColor c)
        { 
            // Avoid colors at the same offset after the 2nd one 
            for (int k = 0; k < _count - 1; k++)
            { 
                if ((Utility.AreClose(offset, _offset[k])) &&
                     Utility.AreClose(offset, _offset[k + 1]))
                {
                    return false; 
                }
            } 
 
            // Insert in increasing offset order
            int j = _count - 1; 

            while (j >= 0)
            {
                if (offset >= _offset[j]) 
                {
                    break; 
                } 

                _offset[j + 1] = _offset[j]; 
                _color[j + 1] = _color[j];

                j--;
            } 

            _offset[j + 1] = offset; 
            _color[j + 1] = c; 

            _count++; 

            return true;
        }
 
        static private MyColor InterpolateColor(double offset, double i0, MyColor c0, double i1, MyColor c1)
        { 
            double di = i1 - i0; 

            Debug.Assert(di >= 0); 

            if (Math.Abs(di) < Double.Epsilon)
            {
                if (offset < i0) 
                {
                    return c0; 
                } 
                else
                { 
                    return c1;
                }
            }
 
            float a = (float)((i1 - offset) / di);
            float b = (float)((offset - i0) / di); 
 
            return MyColor.Interpolate(c0, a, c1, b);
        } 

        #endregion

        #region Private Fields 

        private GradientSpreadMethod _spread; 
 
        private MyColor[] _color;  // colors with premultiplied alpha
        private double[] _offset; 
        private int _count;
        private ColorInterpolationMode _ciMode;

        #endregion 
    }
 
    ///  
    /// Break linear gradient brush fill into slides with solid colors
    ///  
    internal class LinearGradientFlattener
    {
        #region Constructors
 
        public LinearGradientFlattener(LinearGradientBrush brush, Geometry geometry, double opacity)
        { 
            // 
            // The general idea is to divide the gradient into bands that are perpendicular to the
            // gradient vector. We transform the gradient so it lies along the x-axis. Thus, getting 
            // a band of the gradient involves creating a rectangular slice of the x-axis-aligned gradient,
            // and transforming it back to world-space.
            //
 
            _shape = geometry;
            _gradient = new GradientColor(brush.GradientStops, opacity, brush.SpreadMethod, brush.ColorInterpolationMode); 
 
            Matrix brushToWorldTransform = (brush.Transform == null) ? Matrix.Identity : brush.Transform.Value;
 
            if (!Utility.IsRenderVisible(brush.StartPoint) ||
                !Utility.IsRenderVisible(brush.EndPoint) ||
                !Utility.IsValid(brushToWorldTransform))
            { 
                // We have invalid/extreme brush points or transformation.
                return; 
            } 

            // In brush space, map its start point to origin and its end point to x-axis. 
            // We call this new space x-space. Also store the length of the gradient vector.
            Matrix brushToXTransform;
            if (!TransformGradientToXAxis(brush, out brushToXTransform, out _bandWidth))
            { 
                // invalid gradient brush
                return; 
            } 

            // Get bounding box of geometry in x-space. Slices of this will form 
            // band rectangles that'll be transformed back into world space via _bandTransform.
            Matrix worldToXTransform = brushToWorldTransform;
            worldToXTransform.Invert();
            worldToXTransform.Append(brushToXTransform); 

            Geometry xgeometry = Utility.TransformGeometry(geometry, worldToXTransform); 
            _bounds = xgeometry.Bounds; 

            // Compute x-space to world transform; we use this to transform each band to world space. 
            _bandTransform = worldToXTransform;
            _bandTransform.Invert();

            // 
            // Divide a single cycle of gradient into N slices.
            // 
            { 
                // need to scale band width to world space to increase fidelity for small brush fills
                // that get magnified 
                double xToWorldScale = Utility.GetScale(brushToWorldTransform);

                _bandSteps = _gradient.BandSteps(_bandWidth * xToWorldScale);
                _bandDelta = _bandWidth / _bandSteps;     // Width of each slices 

                double right = Math.Ceiling(_bounds.Right / _bandDelta); 
                double left = Math.Floor(_bounds.Left / _bandDelta); 

                _right = (int)(right); 
                _left = (int)(left);

                Debug.Assert(_left <= left);
                Debug.Assert(_right >= right); 
            }
 
            _valid = true; 
        }
 
        #endregion

        #region Public Methods
 
        public Geometry GetSlice(int i, out Color color)
        { 
            if (_valid) 
            {
                i += _left; 

                color = _gradient.GetColor(i, _bandSteps);

                // Create a slice of the bounding box, transform to original shape's coordinate space 
                return CreateRotatedRectangle(i * _bandDelta, _bounds.Top, _bandDelta, _bounds.Height, _bandTransform);
            } 
            else 
            {
                // invalid gradient, get the default invalid color 
                color = _gradient.GetInvalidGradientColor();

                return _shape;
            } 
        }
 
        #endregion 

        #region Public Properties 

        public int Steps
        {
            get 
            {
                if (_valid) 
                { 
                    return _right - _left;
                } 
                else
                {
                    return 1;
                } 
            }
        } 
 
        #endregion
 
        #region Private Methods

        static private Geometry CreateRotatedRectangle(double x, double y, double w, double h, Matrix mat)
        { 
            StreamGeometry geometry = new StreamGeometry();
 
            using (StreamGeometryContext context = geometry.Open()) 
            {
                context.BeginFigure(mat.Transform(new Point(x, y)), true, true); 
                context.LineTo(mat.Transform(new Point(x + w, y)), true, true);
                context.LineTo(mat.Transform(new Point(x + w, y + h)), true, true);
                context.LineTo(mat.Transform(new Point(x, y + h)), true, true);
            } 

            return geometry; 
        } 

        ///  
        /// Creates a transformation that places gradient vector (StartPoint -> EndPoint) onto the x-axis,
        /// with StartPoint at the origin.
        /// 
        ///  
        /// Output transformation of gradient vector onto x-axis.
        /// Length of the gradient vector. 
        /// Returns false if gradient doesn't have a direction, and therefore the brush should be treated as invalid. 
        private static bool TransformGradientToXAxis(
            LinearGradientBrush brush, 
            out Matrix transform,
            out double gradientVectorLength)
        {
            transform = Matrix.CreateTranslation(-brush.StartPoint.X, -brush.StartPoint.Y); 

            Vector gradientVector = brush.EndPoint - brush.StartPoint; 
            gradientVectorLength = gradientVector.Length; 

            if (Utility.IsZero(gradientVector.X) && Utility.IsZero(gradientVector.Y)) 
            {
                // gradient doesn't have a direction
                return false;
            } 
            else
            { 
                double rotateAngle = Math.Atan2(-gradientVector.Y, gradientVector.X) * 180.0 / Math.PI; 

                transform.Rotate(rotateAngle); 

                return true;
            }
        } 

        #endregion 
 
        #region Private Fields
 
        private bool _valid;
        private Geometry _shape;
        private GradientColor _gradient;
 
        private Rect _bounds;           // bounds of fill geometry transformed to brush space
        private Matrix _bandTransform;   // band transformation from brush- to world-space 
 
        private double _bandWidth;      // length of (StartPoint -> EndPoint) vector
        private int _bandSteps;         // number of steps to use when decomposing into bands 
        private double _bandDelta;      // distance between bands
        private int _left;
        private int _right;
 
        #endregion
    } 
 
    /// 
    /// Break radial gradient fill into rings of solid colors 
    /// 
    internal class RadialGradientFlattener
    {
        #region Constructors 

        public RadialGradientFlattener(RadialGradientBrush b, Geometry shape, double opacity) 
        { 
            Debug.Assert(Utility.IsValid(opacity), "Opacity comes from BrushProxy, should be valid");
 
            _trans = b.Transform.Value;

            _x0 = b.Center.X;
            _y0 = b.Center.Y; 
            _u0 = b.GradientOrigin.X;
            _v0 = b.GradientOrigin.Y; 
            _rx = Math.Abs(b.RadiusX); 
            _ry = Math.Abs(b.RadiusY);
 
            _shape = shape;

            _gradient = new GradientColor(b.GradientStops, opacity, b.SpreadMethod, b.ColorInterpolationMode);
 
            if (!Utility.IsRenderVisible(_x0) ||
                !Utility.IsRenderVisible(_y0) || 
                !Utility.IsRenderVisible(_u0) || 
                !Utility.IsRenderVisible(_v0) ||
                !Utility.IsRenderVisible(_rx) || 
                !Utility.IsRenderVisible(_ry))
            {
                return;
            } 

            // Calculate shape's bounds in brush space 
            // Transform oldtrans = shape.Transform; 

            Matrix mat = _trans; 

            mat.Invert();

            shape = Utility.TransformGeometry(shape, mat); 

            Rect bounds = shape.Bounds; 
 
            // shape.Transform = oldtrans;
 
            double mint = Double.MaxValue;
            double maxt = Double.MinValue;

            // If focus is within bounds, then _mint = 0 
            if ((_u0 >= bounds.Left) && (_u0 <= bounds.Right) &&
                (_v0 >= bounds.Top) && (_v0 <= bounds.Bottom)) 
            { 
                mint = 0;
            } 

            {
                Point p0 = new Point(0, 0); p0 = _trans.Transform(p0);
                Point p1 = new Point(_rx, _ry); p1 = _trans.Transform(p1); 

                Vector v = p0 - p1; 
 
                _bandSteps = _gradient.BandSteps(v.Length);  // Number of steps to decompose into
            } 

            // Find minimum/maximum t with four corners
            bool missing = false;
 
            PointIntersectWithRing(bounds.TopLeft, ref mint, ref maxt, ref missing);
            PointIntersectWithRing(bounds.TopRight, ref mint, ref maxt, ref missing); 
            PointIntersectWithRing(bounds.BottomRight, ref mint, ref maxt, ref missing); 
            PointIntersectWithRing(bounds.BottomLeft, ref mint, ref maxt, ref missing);
 
            // When distance(center, origin) > radius, gradient forms a triangle which may not touch
            // one or more corners of the bounding box.
            // Do not decompose in such cases, as it could generate endless rings.
            // Force rasterization instead by making _right a huge value. 
            if (missing)
            { 
                _left  = 0; 
                _right = Int32.MaxValue;
            } 
            else
            {
                // Find minimum t with four line segments
                if (mint > 0) 
                {
                    LineSegmentIntersectWithRing(bounds.TopLeft, bounds.TopRight, ref mint); 
                    LineSegmentIntersectWithRing(bounds.TopRight, bounds.BottomRight, ref mint); 
                    LineSegmentIntersectWithRing(bounds.BottomRight, bounds.BottomLeft, ref mint);
                    LineSegmentIntersectWithRing(bounds.BottomLeft, bounds.TopLeft, ref mint); 
                }

                if (mint < 0)
                { 
                    mint = 0;
                } 
 
                double right = Math.Ceiling(maxt * _bandSteps);
                double left  = Math.Floor(mint * _bandSteps); 

                _right = BoundedInt(right);
                _left  = BoundedInt(left);
 
                Debug.Assert(_left <= left);
                Debug.Assert(_right >= right); 
            } 

            _valid = true; 
        }

        #endregion
 
        #region Public Methods
 
        public Geometry GetSlice(int i, out Color color) 
        {
            if (_valid) 
            {
                i += _left;

                float t = (float)i / _bandSteps; 

                bool simple = Utility.IsScaleTranslate(_trans); 
 
                color = _gradient.GetColor(i - 1, _bandSteps);
 
                // Center for each ring gradually moving from Focus (u0, v0) to Center (x0, y0)
                Point center = new Point(_u0 * (1 - t) + _x0 * t,
                                         _v0 * (1 - t) + _y0 * t);
 
                Geometry geometry;
 
                if (simple) 
                {
                    center = _trans.Transform(center); 

                    geometry = new EllipseGeometry(center, _rx * t * _trans.M11, _ry * t * _trans.M22);
                }
                else 
                {
                    geometry = new EllipseGeometry(center, _rx * t, _ry * t); 
                    geometry.Transform = new MatrixTransform(_trans); 
                }
 
                return geometry;
            }
            else
            { 
                // Gradient isn't valid, return the default color.
                color = _gradient.GetInvalidGradientColor(); 
 
                return _shape;
            } 
        }

        #endregion
 
        #region Public Properties
 
        public int Steps 
        {
            get 
            {
                if (_valid)
                {
                    return _right - _left; 
                }
                else 
                { 
                    return 1;
                } 
            }
        }

        #endregion 

        #region Private Methods 
 
        // Radial gradient rings are parametric curves depending on parameter t
        // Find t for the ring which goes through p 
        private void PointIntersectWithRing(Point p, ref double mint, ref double maxt, ref bool missing)
        {
            // x  = _u0 * (1 - t) + _x0 * t = _u0 + (_x0 - _u0) * t
            // y  = _v0 * (1 - t) + _y0 * t = _v0 + (_y0 - _v0) * t 
            // rx = _rx * t
            // ry = _ry * t 
 
            // Solve x + rx cos(theta) = p.X
            //       y + ry sin(theta) = p.Y 

            //  Hypotenuse(p.X - x) / rx, (p.Y - y) / ry) = 1

            double a0 = (p.X - _u0) / _rx; 
            double a1 = (_u0 - _x0) / _rx;
 
            double b0 = (p.Y - _v0) / _ry; 
            double b1 = (_v0 - _y0) / _ry;
 
            // Hypotenuse(a0/t + a1, b0 / t + b1) = 1
            // Hypotenuse(a0 + a1 * t, b0 + b1 * t) = t * t

            // A * t * t + B t + C = 0 

            double A = a1 * a1 + b1 * b1 - 1; 
            double B = 2 * a0 * a1 + 2 * b0 * b1; 
            double C = a0 * a0 + b0 * b0;
 
            double B4AC = B * B - 4 * A * C;

            bool touch = false;
 
            if (B4AC >= 0)
            { 
                double root = Math.Sqrt(B4AC); 

                double one = (-B + root) / A / 2; 
                double two = (-B - root) / A / 2;

                // Ignore negative solutions
 
                if (one >= 0)
                { 
                    maxt = Math.Max(maxt, one); 
                    mint = Math.Min(mint, one);
 
                    touch = true;
                }

                if (two >= 0) 
                {
                    maxt = Math.Max(maxt, two); 
                    mint = Math.Min(mint, two); 

                    touch = true; 
                }
            }

            if (!touch) 
            {
                missing = true; 
            } 
        }
 
        // Radial gradient rings are parametric curves depending on parameter t
        // Find the minimum t for the ring which goes intesects with the line segment (p0, p1)
        private void LineSegmentIntersectWithRing(Point p0, Point p1, ref double mint)
        { 
            // Scale y coordinate to make radius X and Y the same.
 
            double ratio = _rx / _ry; 

            p0.Y *= ratio; 
            p1.Y *= ratio;

            double dx = p1.X - p0.X;
            double dy = p1.Y - p0.Y; 
            double len = Math.Sqrt(dx * dx + dy * dy);
 
            // Ignore if line P->Q is too short 
            if (len < Double.Epsilon)
            { 
                return;
            }

            // Point on ellipse: 
            // x = a + bt + rt cos(theta)
            // y = c + dt + rt sin(theta) 
            double a = _u0; 
            double b = _x0 - _u0;
 
            double c = _v0 * ratio;
            double d = (_y0 - _v0) * ratio;

            // Formula for the line (p0, p1): 
            // Ax + By + C = 0
 
            double A = dy; 
            double B = -dx;
            double C = p0.Y * dx - p0.X * dy; 

            // minimum t is reached with the distance from (x, y) to the line equals the radius
            // | (A(a+bt) + B(c+dt) + C | = sqrt(AA+BB) * radius t
            // | Et + F | = Gt 

            double E = A * b + B * d; 
            double F = A * a + B * c + C; 
            double G = len * _rx;
 
            for (int i = 0; i < 2; i++)
            {
                double t;
 
                if (i == 0)
                { 
                    t = F / (G - E); // Et + F = Gt   => (G-E)t = F 
                }
                else 
                {
                    t = -F / (G + E); // -Et - F = Gt => (G+E)t = -F
                }
 
                if ((t >= 0) && (t < mint))
                { 
                    // Center for the ellipse 
                    double cx = a + b * t;
                    double cy = c + d * t; 

                    // Intersection point with line P->Q
                    double sx = cx + _rx * t * dy / len;
                    double sy = cy - _rx * t * dx / len; 

                    // Relative location within [P..Q] 
                    double loc = ((sx - p0.X) * dx + (sy - p0.Y) * dy) / (len * len); 

                    // Ignore if the intersection is outside of [P..Q] 
                    if ((loc > 0) && (loc < 1))
                    {
                        mint = t;
                    } 
                }
            } 
        } 

        private static int BoundedInt(double v) 
        {
            if (v < System.Int32.MinValue)
            {
                return System.Int32.MinValue; 
            }
            else if (v > System.Int32.MaxValue) 
            { 
                return System.Int32.MaxValue;
            } 
            else
            {
                return (int)v;
            } 
        }
 
        #endregion 

        #region Private Fields 

        private bool _valid;

        private double _x0; // center 
        private double _y0;
        private double _u0; // focus 
        private double _v0; 
        private double _rx; // radius
        private double _ry; 

        private Geometry _shape; // fill region

        private GradientColor _gradient; 
        private Matrix        _trans;
 
        private int _bandSteps; 
        private int _left;
        private int _right; 

        #endregion
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK