File: System\Windows\Media\Imaging\TransformedBitmap.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.
 
using System.ComponentModel;
using MS.Internal;
using MS.Win32.PresentationCore;
 
namespace System.Windows.Media.Imaging
{
    #region TransformedBitmap
    /// <summary>
    /// TransformedBitmap provides caching functionality for a BitmapSource.
    /// </summary>
    public sealed partial class TransformedBitmap : Imaging.BitmapSource, ISupportInitialize
    {
        /// <summary>
        /// TransformedBitmap construtor
        /// </summary>
        public TransformedBitmap()
            : base(true) // Use base class virtuals
        {
        }
 
        /// <summary>
        /// Construct a TransformedBitmap with the given newTransform
        /// </summary>
        /// <param name="source">BitmapSource to apply to the newTransform to</param>
        /// <param name="newTransform">Transform to apply to the bitmap</param>
        public TransformedBitmap(BitmapSource source, Transform newTransform)
            : base(true) // Use base class virtuals
        {
            ArgumentNullException.ThrowIfNull(source);
 
            if (newTransform == null)
            {
                throw new InvalidOperationException(SR.Format(SR.Image_NoArgument, "Transform"));
            }
 
            if (!CheckTransform(newTransform))
            {
                throw new InvalidOperationException(SR.Image_OnlyOrthogonal);
            }
 
            _bitmapInit.BeginInit();
 
            Source = source;
            Transform = newTransform;
 
            _bitmapInit.EndInit();
            FinalizeCreation();
        }
 
        // ISupportInitialize
 
        /// <summary>
        /// Prepare the bitmap to accept initialize paramters.
        /// </summary>
        public void BeginInit()
        {
            WritePreamble();
            _bitmapInit.BeginInit();
        }
 
        /// <summary>
        /// Prepare the bitmap to accept initialize paramters.
        /// </summary>
        public void EndInit()
        {
            WritePreamble();
            _bitmapInit.EndInit();
 
            IsValidForFinalizeCreation(/* throwIfInvalid = */ true);
            FinalizeCreation();
        }
 
        private void ClonePrequel(TransformedBitmap otherTransformedBitmap)
        {
            BeginInit();
        }
 
        private void ClonePostscript(TransformedBitmap otherTransformedBitmap)
        {
            EndInit();
        }
 
        /// <summary>
        /// Check the transformation to see if it's a simple scale and/or rotation and/or flip.
        /// </summary>
        internal bool CheckTransform(Transform newTransform)
        {
            Matrix m = newTransform.Value;
            bool canHandle = false;
 
            if ( (DoubleUtil.IsZero(m.M11) && DoubleUtil.IsZero(m.M22)) ||
                 (DoubleUtil.IsZero(m.M12) && DoubleUtil.IsZero(m.M21)) )
            {
                canHandle = true;
            }
 
            return canHandle;
        }
 
        /// <summary>
        /// Check the transformation to see if it's a simple scale and/or rotation and/or flip.
        /// </summary>
        internal void GetParamsFromTransform(
            Transform newTransform,
            out double scaleX,
            out double scaleY,
            out WICBitmapTransformOptions options)
        {
            Matrix m = newTransform.Value;
 
            if (DoubleUtil.IsZero(m.M12) && DoubleUtil.IsZero(m.M21))
            {
                scaleX = Math.Abs(m.M11);
                scaleY = Math.Abs(m.M22);
 
                options = WICBitmapTransformOptions.WICBitmapTransformRotate0;
 
                if (m.M11 < 0)
                {
                    options |= WICBitmapTransformOptions.WICBitmapTransformFlipHorizontal;
                }
 
                if (m.M22 < 0)
                {
                    options |= WICBitmapTransformOptions.WICBitmapTransformFlipVertical;
                }
            }
            else
            {
                Debug.Assert(DoubleUtil.IsZero(m.M11) && DoubleUtil.IsZero(m.M22));
 
                scaleX = Math.Abs(m.M12);
                scaleY = Math.Abs(m.M21);
 
                options = WICBitmapTransformOptions.WICBitmapTransformRotate90;
 
                if (m.M12 < 0)
                {
                    options |= WICBitmapTransformOptions.WICBitmapTransformFlipHorizontal;
                }
 
                if (m.M21 >= 0)
                {
                    options |= WICBitmapTransformOptions.WICBitmapTransformFlipVertical;
                }
            }
        }
 
        ///
        /// Create the unmanaged resources
        ///
        internal override void FinalizeCreation()
        {
            _bitmapInit.EnsureInitializedComplete();
            BitmapSourceSafeMILHandle wicTransformer = null;
 
            double scaleX, scaleY;
            WICBitmapTransformOptions options;
 
            GetParamsFromTransform(Transform, out scaleX, out scaleY, out options);
 
            using (FactoryMaker factoryMaker = new FactoryMaker())
            {
                try
                {
                    IntPtr wicFactory = factoryMaker.ImagingFactoryPtr;
 
                    wicTransformer = _source.WicSourceHandle;
 
                    if (!DoubleUtil.IsOne(scaleX) || !DoubleUtil.IsOne(scaleY))
                    {
                        uint width = Math.Max(1, (uint)(scaleX * _source.PixelWidth + 0.5));
                        uint height = Math.Max(1, (uint)(scaleY * _source.PixelHeight + 0.5));
 
                        HRESULT.Check(UnsafeNativeMethods.WICImagingFactory.CreateBitmapScaler(
                                wicFactory,
                                out wicTransformer));
 
                        lock (_syncObject)
                        {
                            HRESULT.Check(UnsafeNativeMethods.WICBitmapScaler.Initialize(
                                    wicTransformer,
                                    _source.WicSourceHandle,
                                    width,
                                    height,
                                    WICInterpolationMode.Fant));
                        }
                    }
 
                    if (options != WICBitmapTransformOptions.WICBitmapTransformRotate0)
                    {
                        // Rotations are extremely slow if we're pulling from a decoder because we end
                        // up decoding multiple times.  Caching the source lets us rotate faster at the cost
                        // of increased memory usage.
                        wicTransformer = CreateCachedBitmap(
                            null,
                            wicTransformer,
                            BitmapCreateOptions.PreservePixelFormat,
                            BitmapCacheOption.Default,
                            _source.Palette);
                        // BitmapSource.CreateCachedBitmap already calculates memory pressure for
                        // the new bitmap, so there's no need to do it before setting it to
                        // WicSourceHandle.
 
                        BitmapSourceSafeMILHandle rotator = null;
 
                        HRESULT.Check(UnsafeNativeMethods.WICImagingFactory.CreateBitmapFlipRotator(
                                wicFactory,
                                out rotator));
 
                        lock (_syncObject)
                        {
                            HRESULT.Check(UnsafeNativeMethods.WICBitmapFlipRotator.Initialize(
                                    rotator,
                                    wicTransformer,
                                    options));
                        }
 
                        wicTransformer = rotator;
                    }
 
                    // If we haven't introduced either a scaler or rotator, add a null rotator
                    // so that our WicSourceHandle isn't the same as our Source's.
                    if (options == WICBitmapTransformOptions.WICBitmapTransformRotate0 &&
                        DoubleUtil.IsOne(scaleX) && DoubleUtil.IsOne(scaleY))
                    {
                        HRESULT.Check(UnsafeNativeMethods.WICImagingFactory.CreateBitmapFlipRotator(
                                wicFactory,
                                out wicTransformer));
 
                        lock (_syncObject)
                        {
                            HRESULT.Check(UnsafeNativeMethods.WICBitmapFlipRotator.Initialize(
                                    wicTransformer,
                                    _source.WicSourceHandle,
                                    WICBitmapTransformOptions.WICBitmapTransformRotate0));
                        }
                    }
 
                    WicSourceHandle = wicTransformer;
                    _isSourceCached = _source.IsSourceCached;
                }
                catch
                {
                    _bitmapInit.Reset();
                    throw;
                }
            }
 
            CreationCompleted = true;
            UpdateCachedSettings();
        }
 
        /// <summary>
        ///     Notification on source changing.
        /// </summary>
        private void SourcePropertyChangedHook(DependencyPropertyChangedEventArgs e)
        {
            if (!e.IsASubPropertyChange)
            {
                BitmapSource newSource = e.NewValue as BitmapSource;
                _source = newSource;
                RegisterDownloadEventSource(_source);
                _syncObject = (newSource != null) ? newSource.SyncObject : _bitmapInit;
            }
        }
 
        internal override bool IsValidForFinalizeCreation(bool throwIfInvalid)
        {
            if (Source == null)
            {
                if (throwIfInvalid)
                {
                    throw new InvalidOperationException(SR.Format(SR.Image_NoArgument, "Source"));
                }
                return false;
            }
 
            Transform transform = Transform;
            if (transform == null)
            {
                if (throwIfInvalid)
                {
                    throw new InvalidOperationException(SR.Format(SR.Image_NoArgument, "Transform"));
                }
                return false;
            }
 
            if (!CheckTransform(transform))
            {
                if (throwIfInvalid)
                {
                    throw new InvalidOperationException(SR.Image_OnlyOrthogonal);
                }
                return false;
            }
 
            return true;
        }
 
        /// <summary>
        ///     Notification on transform changing.
        /// </summary>
        private void TransformPropertyChangedHook(DependencyPropertyChangedEventArgs e)
        {
            if (!e.IsASubPropertyChange)
            {
                _transform = e.NewValue as Transform;
            }
        }
 
        /// <summary>
        ///     Coerce Source
        /// </summary>
        private static object CoerceSource(DependencyObject d, object value)
        {
            TransformedBitmap bitmap = (TransformedBitmap)d;
            if (!bitmap._bitmapInit.IsInInit)
            {
                return bitmap._source;
            }
            else
            {
                return value;
            }
        }
 
        /// <summary>
        ///     Coerce Transform
        /// </summary>
        private static object CoerceTransform(DependencyObject d, object value)
        {
            TransformedBitmap bitmap = (TransformedBitmap)d;
            if (!bitmap._bitmapInit.IsInInit)
            {
                return bitmap._transform;
            }
            else
            {
                return value;
            }
        }
 
        #region Data Members
 
        private BitmapSource _source;
 
        private Transform _transform;
 
        #endregion
    }
 
    #endregion // TransformedBitmap
}