File: System\Windows\Media\Effects\Effect.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// 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.
 
//
 
namespace System.Windows.Media.Effects
{
    /// <summary>
    /// Effect
    /// </summary>
    public abstract partial class Effect
    {
        static Effect()
        {
            // Let the ImplicitInput be a very obscure brush, and treat it as
            // the implicit input if the exact color matches.  Note that this
            // color will be matched in native code that recognized
            // ImplicitInput, so don't change.
            
            // This specific brush is used as a placeholder for the implicit input 
            // texture. This is a little hacky, but *way* easier than adding a new 
            // Brush subclass for the implicit input and having it work properly 
            // as a real brush.
            
            ImplicitInput = new ImplicitInputBrush();
            ImplicitInput.Freeze();
        }
 
        /// Represents the Sampler-destined shader input that comes from
        /// context.  Like the representation of the UIElement when a
        /// UIElement.Effect is applied.  Intended to be set into an input of a
        /// ShaderEffect.
        [System.ComponentModel.BrowsableAttribute(false)]
        public static Brush ImplicitInput
        {
            get;
            private set;
        }
 
 
        /// <summary>
        /// Default constructor for an Effect.
        /// </summary>
        protected Effect()
        {
        }
 
 
        /// <summary>
        /// Takes in content bounds, and returns the bounds of the rendered
        /// output of that content after the Effect is applied.
        /// </summary>
        internal abstract Rect GetRenderBounds(Rect contentBounds);
 
 
        /// <summary>
        /// Input will get transformed through the inverse of this transform.
        /// TransformToAncestor/Descendant will also go through this.
        /// Override to modify from the identity transform.  Should expect
        /// incoming coordinates to be in the [0-1] range, and outgoing points
        /// should map to [0-1] as well.  The Inverse property should return a
        /// GeneralTransform that does the inverse mapping.  
        /// The inverse maps a point from after the effect was applied to the
        /// point that it came from before the effect.  The non-inverse maps
        /// where a point before the effect is applied goes after the effect is
        /// applied. 
        /// </summary>
        internal protected virtual GeneralTransform EffectMapping
        {
            get
            {
                return Transform.Identity;
            }
        }
 
 
        // The GeneralTransform returned by Effect is in unit space.  The
        // invocation of GeneralTransform is in world space.  This method
        // returns a GeneralTransform that maps between the two.
        internal GeneralTransform CoerceToUnitSpaceGeneralTransform(GeneralTransform gt,
                                                                    Rect worldBounds)
        {
            GeneralTransform result;
            
            // First, if the gt is identity, just return it straight away.
            if (gt == Transform.Identity)
            {
                result = Transform.Identity;
            }
            else 
            {
                // Maintain an MRU cache of GeneralTransforms with exactly one in
                // it.  May want to extend in the future.  Note that this will
                // thrash if the effect is used on multiple elements with different
                // sizes, but that's not our sweetspot.
                if (_mruWorldBounds != worldBounds || _mruInnerGeneralTransform != gt)
                {
                    _mruWorldBounds = worldBounds;
                    _mruInnerGeneralTransform = gt;
                    _mruWorldSpaceGeneralTransform = new UnitSpaceCoercingGeneralTransform(worldBounds, gt);
                }
 
                result = _mruWorldSpaceGeneralTransform;
            }
 
            return result;
        }
 
        // If worldBounds is Rect.Empty, will return (NaN, NaN) which tends to propagate silently and eventually show up as corruption much later
        private static Point UnitToWorldUnsafe(Point unitPoint, Rect worldBounds)
        {
            return new Point(
                worldBounds.Left + unitPoint.X * worldBounds.Width,
                worldBounds.Top  + unitPoint.Y * worldBounds.Height);
        }
        
        internal static Point? UnitToWorld(Point unitPoint, Rect worldBounds)
        {
            return worldBounds.IsEmpty ? null : new Nullable<Point>(UnitToWorldUnsafe(unitPoint, worldBounds));
        }
 
        internal static Point? WorldToUnit(Point worldPoint, Rect worldBounds)
        {
            if (worldBounds.Width == 0 || worldBounds.Height == 0)
            {
                return null;
            }
 
            return new Point(
                (worldPoint.X - worldBounds.Left) / worldBounds.Width,
                (worldPoint.Y - worldBounds.Top)  / worldBounds.Height);
        }
 
        internal static Rect UnitToWorld(Rect unitRect, Rect worldBounds)
        {
            return worldBounds.IsEmpty 
                ? Rect.Empty
                : new Rect(UnitToWorldUnsafe(unitRect.TopLeft, worldBounds),
                                UnitToWorldUnsafe(unitRect.BottomRight, worldBounds));
        }
 
        internal static Rect? WorldToUnit(Rect worldRect, Rect worldBounds)
        {
            Point? tl = WorldToUnit(worldRect.TopLeft, worldBounds);
            Point? br = WorldToUnit(worldRect.BottomRight, worldBounds);
            
            if (tl == null || br == null)
            {
                return null;
            }
 
            return new Rect(tl.Value, br.Value);
        }
 
        
        // Private GeneralTransform subclass that's all about transforming from
        // and to the unit square.
        private class UnitSpaceCoercingGeneralTransform : GeneralTransform
        {
            public UnitSpaceCoercingGeneralTransform(Rect worldBounds, GeneralTransform innerTransform)
            {
                _worldBounds = worldBounds;
                _innerTransform = innerTransform;
                _isInverse = false;
            }
 
            public override GeneralTransform Inverse
            {
                get 
                {
                    if (_inverseTransform == null)
                    {
                        // We can cache the clone because the _worldBounds and
                        // inner transform won't change.
                        _inverseTransform = (UnitSpaceCoercingGeneralTransform)this.Clone();
                        _inverseTransform._isInverse = !_isInverse;
                    }
                    return _inverseTransform;
                }
            }
 
            public override Rect TransformBounds(Rect rect)
            {
                // Since this doesn't rotate or skew, we can just pass each
                // point to the point transformer.
                Point topLeftResult = new Point();
                Point bottomRightResult = new Point();
                
                bool ok = TryTransform(rect.TopLeft, out topLeftResult)
                       && TryTransform(rect.BottomRight, out bottomRightResult);
                
                if (!ok)
                {
                    return Rect.Empty;
                }
 
                return new Rect(topLeftResult, bottomRightResult);
            }
 
            public override bool TryTransform(Point inPoint, out Point result)
            {
                bool ok = false;
                result = new Point();
 
                // Both the normal and the inverse require the point to first be
                // translated from world space to unit space.
                Point? unitSpace = Effect.WorldToUnit(inPoint, _worldBounds);
                if (unitSpace != null)
                {
                    // Now just run through the normal or the inverse version of the
                    // inner effect.
                    GeneralTransform innerToUse = GetCorrectInnerTransform();
 
                    Point unitResult;
                    if (innerToUse.TryTransform(unitSpace.Value, out unitResult))
                    {
                        // Both the normal and the inverse require the unit-space result
                        // to be converted back to world space
                        Point? worldSpace = Effect.UnitToWorld(unitResult, _worldBounds);
                        if (worldSpace != null)
                        {
                            result = worldSpace.Value;
                            ok = true;
                        }
                    }
                }
 
                return ok;
            }
 
            protected override Freezable CreateInstanceCore()
            {
                return new UnitSpaceCoercingGeneralTransform(_worldBounds, _innerTransform) { _isInverse = _isInverse };
            }
 
            private GeneralTransform GetCorrectInnerTransform()
            {
                GeneralTransform result;
                if (_isInverse)
                {
                    if (_innerTransformInverse == null)
                    {
                        // Cache the inverse so it doesn't get new'd up all the
                        // time. 
                        _innerTransformInverse = _innerTransform.Inverse;
                    }
                    result = _innerTransformInverse;
                }
                else
                {
                    result = _innerTransform;
                }
                
                return result;
            }
            
            private readonly Rect _worldBounds;
            private readonly GeneralTransform _innerTransform;
            private GeneralTransform _innerTransformInverse = null;
            private bool _isInverse;
            private UnitSpaceCoercingGeneralTransform _inverseTransform = null;
        }
        
 
        // Stores the "cache" of bounds x inner transform -> world space transform.
        private Rect _mruWorldBounds = Rect.Empty;
        private GeneralTransform _mruInnerGeneralTransform = null;
        private GeneralTransform _mruWorldSpaceGeneralTransform;
    }
}