File: AlphaFlattener\Optimizer.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\ReachFramework\ReachFramework.csproj (ReachFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
 
using System;
using System.Diagnostics;
using System.Collections;              // for ArrayList
using System.Windows;                  // for Rect                        WindowsBase.dll
using System.Windows.Media;            // for Geometry, Brush, ImageData. PresentationCore.dll
using System.Windows.Media.Imaging;
using System.Collections.Generic;
using System.Windows.Xps.Serialization;
using MS.Utility;
 
namespace Microsoft.Internal.AlphaFlattener
{
    internal class Cluster
    {
        #region Private Fields
 
        private List<int> m_primitives;
        private Rect m_bounds;
 
#if DEBUG
        private bool      m_debugRasterize;
#endif
 
        private int m_lowestPrimitive;     // lowest index in m_primitives
 
        #endregion
 
        #region Constructors
 
        public Cluster()
        {
            m_primitives = new List<int>();
            m_bounds     = Rect.Empty;
 
            m_lowestPrimitive = int.MaxValue;
        }
 
        #endregion
 
        #region Public Methods
 
        public void Render(List<PrimitiveInfo> commands, IProxyDrawingContext dc)
        {
            Toolbox.EmitEvent(EventTrace.Event.WClientDRXRasterStart);
 
            int width  = (int)Math.Round(m_bounds.Width * Configuration.RasterizationDPI / 96);
            int height = (int)Math.Round(m_bounds.Height * Configuration.RasterizationDPI / 96);
 
            if ((width >= 1) && (height >= 1)) // Skip shape which is too small
            {
                Matrix mat = Utility.CreateMappingTransform(m_bounds, width, height);
 
                RenderTargetBitmap brushImage = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
 
                DrawingVisual visual = new DrawingVisual();
 
                // clip image to the primitives it contains
                Geometry clip = null;
 
                using (DrawingContext ctx = visual.RenderOpen())
                {
                    ctx.PushTransform(new MatrixTransform(mat));
 
                    m_primitives.Sort();
 
                    foreach (int i in m_primitives)
                    {
                        Primitive primitive = commands[i].primitive;
 
                        // Fix bug 1396393: Can't use AddToCluster without fixing that bug.
                        // We need to clip cluster to primitives in case primitives have clipping.
                        Debug.Assert(GetPrimitiveIntersectAction() == PrimitiveIntersectAction.ClipToCluster);
 
                        bool empty;
                        Geometry geometry = Utility.Intersect(primitive.GetShapeGeometry(), primitive.Clip, Matrix.Identity, out empty);
 
                        if (!empty)
                        {
                            primitive.OnRender(ctx);
 
                            // clip cluster to this clipped primitive. we must have geometry information,
                            // otherwise the primitive will be clipped away.
                            Debug.Assert(geometry != null, "No geometry available for primitive");
 
                            if (clip == null)
                            {
                                clip = geometry;
                            }
                            else
                            {
                                CombinedGeometry cg = new CombinedGeometry();
 
                                // Opt-out of inheritance through the new Freezable.
                                cg.CanBeInheritanceContext = false;
                                cg.GeometryCombineMode     = GeometryCombineMode.Union;
                                cg.Geometry1               = clip;
                                cg.Geometry2               = geometry;
 
                                clip = cg;
                            }
                        }
 
                        commands[i] = null; // Command can be deleted after cluster is rendered
                    }
 
                    ctx.Pop();
                }
 
                brushImage.Render(visual);
 
                Toolbox.EmitEvent(EventTrace.Event.WClientDRXRasterEnd);
 
                dc.DrawImage(new ImageProxy(brushImage), m_bounds, clip, Matrix.Identity);
            }
        }
 
        #endregion
 
        #region Public Static Methods
 
        public static void CheckForRasterization(List<Cluster> clusters, List<PrimitiveInfo> commands)
        {
            foreach (Cluster c in clusters)
            {
                if (c.BetterRasterize(commands))
                {
#if DEBUG
                    c.m_debugRasterize = true;
#endif
 
                    foreach (int i in c.m_primitives)
                    {
                        commands[i].m_cluster = c;
                    }
                }
            }
        }
 
        public static List<Cluster> CalculateCluster(List<PrimitiveInfo> commands, int count, bool disjoint, List<int>[] oldUnderlay)
        {
            List<Cluster> transparentCluster = new List<Cluster>();
 
            // indicates which primitives have been added to any cluster
            bool[] addedPrimitives = new bool[commands.Count];
 
            // calculate clusters until cluster bounds stabilize
            while (true)
            {
                bool clusterBoundsChanged = CalculateClusterCore(
                    commands,
                    count,
                    disjoint,
                    oldUnderlay,
                    transparentCluster,
                    addedPrimitives
                    );
 
                if (!clusterBoundsChanged || GetPrimitiveIntersectAction() != PrimitiveIntersectAction.AddToCluster)
                    break;
 
                //
                // Cluster bounds have changed somewhere, need to examine all primitives that haven't
                // been added to a cluster and test for intersection with cluster. We add primitives
                // that intersect and rendered before the cluster.
                //
                // Note that here we check even opaque primitives, since they might get covered
                // by a transparent cluster, and thus need to be rasterized with a cluster if intersection
                // exists.
                //
                for (int primIndex = 0; primIndex < addedPrimitives.Length; primIndex++)
                {
                    if (!addedPrimitives[primIndex] && commands[primIndex] != null)
                    {
                        PrimitiveInfo primitive = commands[primIndex];
 
                        for (int clusterIndex = 0; clusterIndex < transparentCluster.Count; clusterIndex++)
                        {
                            Cluster cluster = transparentCluster[clusterIndex];
 
                            if (primitive.GetClippedBounds().IntersectsWith(cluster.m_bounds) &&
                                primIndex < cluster.m_lowestPrimitive)
                            {
                                // primitive intersects this cluster, add to cluster
                                cluster.Add(
                                    primIndex,
                                    oldUnderlay,
                                    commands,
                                    addedPrimitives
                                );
                            }
                        }
                    }
                }
            }
 
            return transparentCluster;
        }
 
        #endregion
 
        #region Private Methods
 
        /// <summary>
        /// What action to take to handle cases where primitives intersect a cluster's bounding box,
        /// but does not touch any of the cluster's primitives.
        /// </summary>
        private enum PrimitiveIntersectAction
        {
            /// <summary>
            /// Don't do anything, behavior corresponds to
            /// bug 1267200: MGC - ToolBarTray element does not print in some cases.
            /// </summary>
            None,
 
            /// <summary>
            /// Adds the primitive to the cluster for rasterization.
            /// </summary>
            AddToCluster,
 
            /// <summary>
            /// Clips the cluster rasterized image to the primitives it contains.
            /// </summary>
            ClipToCluster,
        }
 
        private static PrimitiveIntersectAction GetPrimitiveIntersectAction()
        {
            //
            // Fix bug 1396393: Printing: Background pictures are only partially printed for an xCal calendar
            //
            // The bug involves printing a calendar. What happens is the January ImagePrimitive is clipped
            // to the rectangle for January, but it's rasterized as part of a cluster with a color fill of
            // that same rectangle. Cluster rasterization loses the clipping on the picture, which effectively
            // makes the cluster grow to overlap April below January. This causes April to appear clipped.
            //
            // Fixing involves clipping clusters to its clipped primitives. As such, AddToCluster cannot
            // be used without further fixes.
            //
            return PrimitiveIntersectAction.ClipToCluster;
        }
 
        /// <summary>
        /// Adds primitive to this cluster.
        /// </summary>
        /// <param name="i">primitive index</param>
        /// <param name="underlay">underlay information</param>
        /// <param name="commands">list of primitives</param>
        /// <param name="addedPrimitives">indicates which primitives have been added to any cluster</param>
        private void Add(
            int i,
            List<int>[] underlay,
            List<PrimitiveInfo> commands,
            bool[] addedPrimitives
            )
        {
            // clip bounds to avoid generating excessively large cluster
            Rect bounds = commands[i].GetClippedBounds();
 
            m_bounds.Union(bounds);
            m_primitives.Add(i);
            addedPrimitives[i] = true;
 
            if (i < m_lowestPrimitive)
            {
                m_lowestPrimitive = i;
            }
 
            // Add all primitives covered by i recursively
            if ((underlay != null) && (underlay[i] != null))
            {
                foreach (int j in underlay[i])
                {
                    if ((commands[j] != null) && m_primitives.IndexOf(j) < 0)
                    {
                        Add(j, underlay, commands, addedPrimitives);
                    }
                }
            }
        }
 
        private void MergeWith(Cluster c)
        {
            m_bounds.Union(c.m_bounds);
 
            for (int i = 0; i < c.m_primitives.Count; i++)
            {
                if (m_primitives.IndexOf(c.m_primitives[i]) < 0)
                {
                    m_primitives.Add(c.m_primitives[i]);
                }
            }
        }
 
        /// <summary>
        /// Check if the whole cluster is complex enough such that rasterizing the whole thing is better than flattening it
        /// </summary>
        /// <param name="commands"></param>
        /// <returns></returns>
        private bool BetterRasterize(List<PrimitiveInfo> commands)
        {
            double clustersize = m_bounds.Width * m_bounds.Height;
 
            double diff = - Configuration.RasterizationCost(m_bounds.Width, m_bounds.Height);
 
            // Estimate cost of geometry operations (intersecting)
            double pathComplexity = 1;
 
            foreach (int i in m_primitives)
            {
                PrimitiveInfo pi = commands[i];
 
                Primitive p = pi.primitive;
 
                GeometryPrimitive gp = p as GeometryPrimitive;
 
                Rect bounds = pi.GetClippedBounds();
 
                bool rasterize = true;
 
                if (gp != null)
                {
                    double complexity = 1;
 
                    Geometry geo = gp.Geometry;
 
                    if (geo != null)
                    {
                        complexity = Utility.GetGeometryPointCount(geo);
 
                        // weight down the complexity of small region
                        complexity *= bounds.Width * bounds.Height / clustersize;
                    }
 
                    BrushProxy bp = gp.Brush;
 
                    if (bp == null)
                    {
                        bp = gp.Pen.StrokeBrush;
 
                        // Widen path would at least double the points
                        complexity *= 3;
                    }
 
                    if (complexity > 1)
                    {
                        pathComplexity *= complexity;
 
                        if (pathComplexity > 100000) // 333 x 333
                        {
                            return true;
                        }
                    }
 
                    if (bp != null)
                    {
                        Brush b = bp.Brush;
 
                        if ((b != null) && ((b is SolidColorBrush) || (b is LinearGradientBrush)))
                        {
                            // SolidColorBrush does not need full rasterization
                            // Vertical/Horizontal linear gradient brush does not need full rasterization
                            rasterize = false;
                        }
                    }
                }
 
                if (rasterize)
                {
                    diff += Configuration.RasterizationCost(bounds.Width, bounds.Height);
 
                    if (diff > 0)
                    {
                        break;
                    }
                }
            }
 
            return diff > 0;
        }
 
        /// <summary>
        /// Calculates list of transparent clusters, returning true if clusters added or bounding rectangles changed.
        /// </summary>
        private static bool CalculateClusterCore(
            List<PrimitiveInfo> commands,
            int count,
            bool disjoint,
            List<int> [] oldUnderlay,
            List<Cluster> transparentCluster,
            bool[] addedPrimitives      // primitives added to clusters
            )
        {
            bool clusterBoundsChanged = false;
 
            // Build clusters of transparent primitives
            for (int i = 0; i < count; i++)
            {
                PrimitiveInfo pi = commands[i];
 
                // When disjoint is true (flattening a subtree), add all primitives to a single cluster
                if ((pi != null) && (disjoint || !pi.primitive.IsOpaque) && !addedPrimitives[i])
                {
                    Rect bounds = pi.GetClippedBounds();
 
                    Cluster home = null;
 
                    for (int j = 0; j < transparentCluster.Count; j++)
                    {
                        Cluster c = transparentCluster[j];
 
                        if (disjoint || bounds.IntersectsWith(c.m_bounds))
                        {
                            home = c;
                            break;
                        }
                    }
 
                    if (home == null)
                    {
                        home = new Cluster();
                        transparentCluster.Add(home);
                    }
 
                    Rect oldClusterBounds = home.m_bounds;
 
                    home.Add(i, oldUnderlay, commands, addedPrimitives);
 
                    if (!clusterBoundsChanged && oldClusterBounds != home.m_bounds)
                    {
                        // cluster bounds have changed
                        clusterBoundsChanged = true;
                    }
                }
            }
 
            // Merges clusters which touch each other
            bool changed;
 
            do
            {
                changed = false;
 
                for (int i = 0; i < transparentCluster.Count; i++)
                    for (int j = i + 1; j < transparentCluster.Count; j++)
                    {
                        if (transparentCluster[i].m_bounds.IntersectsWith(transparentCluster[j].m_bounds))
                        {
                            transparentCluster[i].MergeWith(transparentCluster[j]);
 
                            // cluster bounds have changed since merging two clusters
                            clusterBoundsChanged = true;
 
                            transparentCluster.RemoveAt(j);
                            changed = true;
                            break;
                        }
                    }
            }
            while (changed);
 
            return clusterBoundsChanged;
        }
 
        #endregion
 
        #if DEBUG
 
        internal List<int> DebugPrimitives
        {
            get
            {
                return m_primitives;
            }
        }
 
        internal Rect DebugBounds
        {
            get
            {
                return m_bounds;
            }
        }
 
        internal bool DebugRasterize
        {
            get
            {
                return m_debugRasterize;
            }
        }
 
        #endif
    }
} // end of namespace