File: System\Windows\Media\Imaging\BitmapFrameDecode.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.Collections.ObjectModel;
using MS.Internal;
using MS.Win32.PresentationCore;
 
#pragma warning disable 1634, 1691 // Allow suppression of certain presharp messages
 
namespace System.Windows.Media.Imaging
{
    #region BitmapFrameDecode
 
    /// <summary>
    /// BitmapFrameDecode abstract class
    /// </summary>
    internal sealed class BitmapFrameDecode : BitmapFrame
    {
        #region Constructors
 
        /// <summary>
        /// Internal constructor -- Creates new frame using specified decoder
        /// </summary>
        internal BitmapFrameDecode(
            int frameNumber,
            BitmapCreateOptions createOptions,
            BitmapCacheOption cacheOption,
            BitmapDecoder decoder
            ) : base(true)
        {
            _bitmapInit.BeginInit();
            _frameNumber = frameNumber;
            _isThumbnailCached = false;
            _isMetadataCached = false;
            _frameSource = null;
 
            Debug.Assert(decoder != null);
            _decoder = decoder;
            _syncObject = decoder.SyncObject;
            _createOptions = createOptions;
            _cacheOption = cacheOption;
 
            _bitmapInit.EndInit();
 
            if ((createOptions & BitmapCreateOptions.DelayCreation) != 0)
            {
                DelayCreation = true;
            }
            else
            {
                FinalizeCreation();
            }
        }
 
        /// <summary>
        /// Internal constructor -- Creates frame from another frame
        /// </summary>
        internal BitmapFrameDecode(
            int frameNumber,
            BitmapCreateOptions createOptions,
            BitmapCacheOption cacheOption,
            BitmapFrameDecode frameDecode
            ) : base(true)
        {
            _bitmapInit.BeginInit();
            _frameNumber = frameNumber;
            WicSourceHandle = frameDecode.WicSourceHandle;
            IsSourceCached = frameDecode.IsSourceCached;
            CreationCompleted = frameDecode.CreationCompleted;
            _frameSource = frameDecode._frameSource;
            _decoder = frameDecode.Decoder;
            _syncObject = _decoder.SyncObject;
            _createOptions = createOptions;
            _cacheOption = cacheOption;
 
            _thumbnail = frameDecode._thumbnail;
            _isThumbnailCached = frameDecode._isThumbnailCached;
            _metadata = frameDecode._metadata;
            _isMetadataCached = frameDecode._isMetadataCached;
            _readOnlycolorContexts = frameDecode._readOnlycolorContexts;
            _isColorContextCached = frameDecode._isColorContextCached;
 
            _bitmapInit.EndInit();
 
            if ((createOptions & BitmapCreateOptions.DelayCreation) != 0)
            {
                DelayCreation = true;
            }
            else if (!CreationCompleted)
            {
                FinalizeCreation();
            }
            else
            {
                UpdateCachedSettings();
            }
        }
 
        /// <summary>
        /// Internal constructor -- Creates frame thats being downloaded
        /// </summary>
        internal BitmapFrameDecode(
            int frameNumber,
            BitmapCreateOptions createOptions,
            BitmapCacheOption cacheOption,
            LateBoundBitmapDecoder decoder
            ) : base(true)
        {
            _bitmapInit.BeginInit();
            _frameNumber = frameNumber;
 
            byte[] pixels = new byte[4];
 
            BitmapSource source = BitmapSource.Create(
                1,
                1,
                96,
                96,
                PixelFormats.Pbgra32,
                null,
                pixels,
                4
                );
 
            WicSourceHandle = source.WicSourceHandle;
 
            Debug.Assert(decoder != null);
            _decoder = decoder;
            _createOptions = createOptions;
            _cacheOption = cacheOption;
 
            //
            // Hook the decoders download events
            //
            _decoder.DownloadCompleted += OnDownloadCompleted;
            _decoder.DownloadProgress += OnDownloadProgress;
            _decoder.DownloadFailed += OnDownloadFailed;
 
            _bitmapInit.EndInit();
        }
 
        /// <summary>
        /// Do not allow construction
        /// </summary>
        private BitmapFrameDecode() : base(true)
        {
        }
 
        #endregion
 
        #region IUriContext
 
        /// <summary>
        /// Provides the base uri of the current context.
        /// </summary>
        public override Uri BaseUri
        {
            get
            {
                ReadPreamble();
                return _decoder._baseUri;
            }
            set
            {
                WritePreamble();
                // Nothing to do here.
            }
        }
 
        #endregion
 
        #region Public Properties
 
        /// <summary>
        /// Accesses the Thumbnail property for this BitmapFrameDecode
        /// </summary>
        public override BitmapSource Thumbnail
        {
            get
            {
                ReadPreamble();
                EnsureThumbnail();
                return _thumbnail;
            }
        }
 
        /// <summary>
        /// Accesses the Metadata property for this BitmapFrameDecode
        /// </summary>
        public override ImageMetadata Metadata
        {
            get
            {
                ReadPreamble();
                return InternalMetadata;
            }
        }
 
        /// <summary>
        /// Accesses the Decoder property for this BitmapFrameDecode
        /// </summary>
        public override BitmapDecoder Decoder
        {
            get
            {
                ReadPreamble();
                return _decoder;
            }
        }
 
        /// <summary>
        /// Used as a delegate in ColorContexts to get the unmanaged IWICColorContexts
        /// </summary>
        private int GetColorContexts(ref uint numContexts, IntPtr[] colorContextPtrs)
        {
            Invariant.Assert(colorContextPtrs == null || numContexts <= colorContextPtrs.Length);
            
            return UnsafeNativeMethods.WICBitmapFrameDecode.GetColorContexts(_frameSource, numContexts, colorContextPtrs, out numContexts);
        }
 
        /// <summary>
        /// If there is an embedded color profile, return it.
        /// Otherwise, return null.  This method does NOT create a
        /// color profile for bitmaps that don't already have one.
        /// </summary>
        /// <summary>
        /// Provides access to this encoders color profile
        /// </summary>
        ///
        public override ReadOnlyCollection<ColorContext> ColorContexts
        {
            get
            {
                ReadPreamble();
                if (!_isColorContextCached && !IsDownloading)
                {
                    EnsureSource();
 
                    lock (_syncObject)
                    {
                        IList<ColorContext> colorContextList = ColorContext.GetColorContextsHelper(GetColorContexts);
 
                        if (colorContextList != null)
                        {
                            _readOnlycolorContexts = new ReadOnlyCollection<ColorContext>(colorContextList);
                        }
 
                        _isColorContextCached = true;
                    }
                }
 
                return _readOnlycolorContexts;
}
        }
 
        /// <summary>
        /// Returns if the BitmapFrame is downloading content
        /// </summary>
        public override bool IsDownloading
        {
            get
            {
                ReadPreamble();
                return Decoder.IsDownloading;
            }
        }
 
        #endregion
 
        #region Public Methods
 
        /// <summary>
        /// Create an in-place bitmap metadata writer.
        /// </summary>
        public override InPlaceBitmapMetadataWriter CreateInPlaceBitmapMetadataWriter()
        {
            ReadPreamble();
 
            if (_decoder != null)
            {
                _decoder.CheckOriginalWritable();
            }
 
            // Demand Site Of Origin on the URI before usage of metadata.
            CheckIfSiteOfOrigin();
 
            EnsureSource();
 
            return InPlaceBitmapMetadataWriter.CreateFromFrameDecode(_frameSource, _syncObject);
        }
 
        #endregion
 
        #region ToString
 
        /// <summary>
        /// Can serialze "this" to a string
        /// </summary>
        internal override bool CanSerializeToString()
        {
            ReadPreamble();
 
            return _decoder.CanConvertToString();
        }
 
        /// <summary>
        /// Creates a string representation of this object based on the format string
        /// and IFormatProvider passed in.
        /// If the provider is null, the CurrentCulture is used.
        /// See the documentation for IFormattable for more information.
        /// </summary>
        /// <returns>
        /// A string representation of this object.
        /// </returns>
        internal override string ConvertToString(string format, IFormatProvider provider)
        {
            ReadPreamble();
 
            if (_decoder != null)
            {
                return _decoder.ToString();
            }
 
            return base.ConvertToString(format, provider);
        }
 
        #endregion
 
        #region Freezable
 
        /// <summary>
        /// Implementation of <see cref="System.Windows.Freezable.CreateInstanceCore">Freezable.CreateInstanceCore</see>.
        /// </summary>
        /// <returns>The new Freezable.</returns>
        protected override Freezable CreateInstanceCore()
        {
            return new BitmapFrameDecode();
        }
 
        /// <summary>
        /// Copy the fields not covered by DPs.  This is used by
        /// CloneCore(), CloneCurrentValueCore(), GetAsFrozenCore() and
        /// GetCurrentValueAsFrozenCore().
        /// </summary>
        private void CopyCommon(BitmapFrameDecode sourceBitmapFrameDecode)
        {
            _bitmapInit.BeginInit();
            _frameNumber = sourceBitmapFrameDecode._frameNumber;
            _isThumbnailCached = sourceBitmapFrameDecode._isThumbnailCached;
            _isMetadataCached = sourceBitmapFrameDecode._isMetadataCached;
            _isColorContextCached = sourceBitmapFrameDecode._isColorContextCached;
            _frameSource = sourceBitmapFrameDecode._frameSource;
            _thumbnail = sourceBitmapFrameDecode._thumbnail;
            _metadata = sourceBitmapFrameDecode.InternalMetadata;
            _readOnlycolorContexts = sourceBitmapFrameDecode._readOnlycolorContexts;
 
            _decoder = sourceBitmapFrameDecode._decoder;
            if (_decoder != null && _decoder.IsDownloading)
            {
                // UpdateDecoder must be called when download completes and the real decoder
                // is created. Normally _decoder will call UpdateDecoder, but in this case the
                // decoder will not know about the cloned BitmapFrameDecode and will only call
                // UpdateDecoder on the original. The clone will need to listen to the original
                // BitmapFrameDecode for DownloadCompleted, then call UpdateDecoder on itself.
                // The weak event sink hooks up handlers on DownloadCompleted, DownloadFailed,
                // and DownloadProgress
                _weakBitmapFrameDecodeEventSink =
                    new WeakBitmapFrameDecodeEventSink(this, sourceBitmapFrameDecode);
            }
 
            _syncObject = _decoder.SyncObject;
            _createOptions = sourceBitmapFrameDecode._createOptions;
            _cacheOption = sourceBitmapFrameDecode._cacheOption;
            _bitmapInit.EndInit();
        }
 
        /// <summary>
        /// Implementation of <see cref="System.Windows.Freezable.CloneCore(Freezable)">Freezable.CloneCore</see>.
        /// </summary>
        protected override void CloneCore(Freezable sourceFreezable)
        {
            BitmapFrameDecode sourceBitmapFrameDecode = (BitmapFrameDecode)sourceFreezable;
            base.CloneCore(sourceFreezable);
 
            CopyCommon(sourceBitmapFrameDecode);
        }
 
        /// <summary>
        /// Implementation of <see cref="System.Windows.Freezable.CloneCurrentValueCore(Freezable)">Freezable.CloneCurrentValueCore</see>.
        /// </summary>
        protected override void CloneCurrentValueCore(Freezable sourceFreezable)
        {
            BitmapFrameDecode sourceBitmapFrameDecode = (BitmapFrameDecode)sourceFreezable;
            base.CloneCurrentValueCore(sourceFreezable);
 
            CopyCommon(sourceBitmapFrameDecode);
        }
 
 
        /// <summary>
        /// Implementation of <see cref="System.Windows.Freezable.GetAsFrozenCore(Freezable)">Freezable.GetAsFrozenCore</see>.
        /// </summary>
        protected override void GetAsFrozenCore(Freezable sourceFreezable)
        {
            BitmapFrameDecode sourceBitmapFrameDecode = (BitmapFrameDecode)sourceFreezable;
            base.GetAsFrozenCore(sourceFreezable);
 
            CopyCommon(sourceBitmapFrameDecode);
        }
 
 
        /// <summary>
        /// Implementation of <see cref="System.Windows.Freezable.GetCurrentValueAsFrozenCore(Freezable)">Freezable.GetCurrentValueAsFrozenCore</see>.
        /// </summary>
        protected override void GetCurrentValueAsFrozenCore(Freezable sourceFreezable)
        {
            BitmapFrameDecode sourceBitmapFrameDecode = (BitmapFrameDecode)sourceFreezable;
            base.GetCurrentValueAsFrozenCore(sourceFreezable);
 
            CopyCommon(sourceBitmapFrameDecode);
        }
 
        #endregion
 
        #region Internal Properties / Methods
 
        /// <summary>
        /// Updates the internal decoder -- usually happens with a LateBoundBitmapDecoder
        /// </summary>
        internal void UpdateDecoder(BitmapDecoder decoder)
        {
            Debug.Assert(_decoder != null);
            _decoder = decoder;
            _syncObject = decoder.SyncObject;
            WicSourceHandle = null;
            _needsUpdate = true;
            FinalizeCreation();
 
            // Trigger a update of the UCE resource
            RegisterForAsyncUpdateResource();
        }
 
        /// <summary>
        /// Create the unmanaged resources
        /// </summary>
        internal override void FinalizeCreation()
        {
            EnsureSource();
 
            // Set the WicSourceHandle and Update the cached settings so that we
            // can query the source for information such as the palette which we need
            // to determine if we need to format convert or not.
            WicSourceHandle = _frameSource;
            UpdateCachedSettings();
 
            lock (_syncObject)
            {
                WicSourceHandle = CreateCachedBitmap(this, _frameSource, _createOptions, _cacheOption, Palette);
            }
 
            IsSourceCached = (_cacheOption != BitmapCacheOption.None);
            CreationCompleted = true;
            UpdateCachedSettings();
            EnsureThumbnail();
        }
 
        //
        // Workaround for a change caused by a bug fix, CopyCommon checks this property when
        // copying the delegates attached to events.
        // Default implementation in BitmapSource, see comments in BitmapSource.cs for details.
        //
        internal override bool ShouldCloneEventDelegates
        {
            get { return false; }
        }
 
        #endregion
 
        #region Private Methods
 
        /// Fired when the decoder download has completed
        private void OnDownloadCompleted(object sender, EventArgs e)
        {
            //
            // The sender should be the LateBoundDecoder that we hooked when it was _decoder.
            //
            LateBoundBitmapDecoder decoder = (LateBoundBitmapDecoder)sender;
 
            //
            // Unhook the decoders download events
            //
            decoder.DownloadCompleted -= OnDownloadCompleted;
            decoder.DownloadProgress -= OnDownloadProgress;
            decoder.DownloadFailed -= OnDownloadFailed;
            
            FireChanged();
            _downloadEvent.InvokeEvents(this, null);
        }
 
        /// Called when download progress is made
        private void OnDownloadProgress(object sender, DownloadProgressEventArgs e)
        {
            _progressEvent.InvokeEvents(this, e);
        }
 
        /// Called when download fails
        private void OnDownloadFailed(object sender, ExceptionEventArgs e)
        {
            //
            // The sender should be the LateBoundDecoder that we hooked when it was _decoder.
            //
            LateBoundBitmapDecoder decoder = (LateBoundBitmapDecoder)sender;
 
            //
            // Unhook the decoders download events
            //
            decoder.DownloadCompleted -= OnDownloadCompleted;
            decoder.DownloadProgress -= OnDownloadProgress;
            decoder.DownloadFailed -= OnDownloadFailed;
            
            _failedEvent.InvokeEvents(this, e);
        }
 
        /// Fired when the original's decoder's download has completed
        private void OnOriginalDownloadCompleted(BitmapFrameDecode original, EventArgs e)
        {
            CleanUpWeakEventSink();
 
            // Update the underlying decoder to match the original's
            // We already have a _decoder from the cloning, but it's referencing the
            // LateBoundBitmapDecoder. When download completes, LateBoundBitmapDecoder calls
            // EnsureDecoder to make the _realDecoder (something like JpegBitmapDecoder),
            // then calls SetupFrames on the _realDecoder, which calls BitmapFrameDecode's
            // UpdateDecoder to set _decoder to the JpegBitmapDecoder. So the original's _decoder
            // changes after download completes, and the clone should change as well.
            UpdateDecoder(original.Decoder);
 
            FireChanged();
            _downloadEvent.InvokeEvents(this, e);
        }
 
        /// Called when the original's decoder's download fails
        private void OnOriginalDownloadFailed(ExceptionEventArgs e)
        {
            CleanUpWeakEventSink();
 
            _failedEvent.InvokeEvents(this, e);
        }
 
        private void CleanUpWeakEventSink()
        {
            // Unhook the decoders download events
            _weakBitmapFrameDecodeEventSink.DetachSourceDownloadHandlers();
            _weakBitmapFrameDecodeEventSink = null;
        }
 
        /// <summary>
        /// Ensure that the thumbnail is created/cached
        /// </summary>
        private void EnsureThumbnail()
        {
            if (_isThumbnailCached || IsDownloading)
            {
                return;
            }
            else
            {
                EnsureSource();
 
                IntPtr /* IWICBitmapSource */ thumbnail = IntPtr.Zero;
 
                lock (_syncObject)
                {
                    // Check if there is embedded thumbnail or not
                    int hr = UnsafeNativeMethods.WICBitmapFrameDecode.GetThumbnail(
                        _frameSource,
                        out thumbnail
                        );
 
                    if (hr != (int)WinCodecErrors.WINCODEC_ERR_CODECNOTHUMBNAIL)
                    {
                        HRESULT.Check(hr);
                    }
                }
 
                _isThumbnailCached = true;
 
                if (thumbnail != IntPtr.Zero)
                {
                    BitmapSourceSafeMILHandle thumbHandle = new BitmapSourceSafeMILHandle(thumbnail);
                    SafeMILHandle unmanagedPalette = BitmapPalette.CreateInternalPalette();
                    BitmapPalette palette = null;
 
                    int hr = UnsafeNativeMethods.WICBitmapSource.CopyPalette(
                                thumbHandle,
                                unmanagedPalette
                                );
                    if (hr == HRESULT.S_OK)
                    {
                        palette = new BitmapPalette(unmanagedPalette);
                    }
 
                    _thumbnail = new UnmanagedBitmapWrapper(
                        CreateCachedBitmap(
                            null,
                            thumbHandle,
                            BitmapCreateOptions.PreservePixelFormat,
                            _cacheOption,
                            palette
                            ));
                    _thumbnail.Freeze();
                }
            }
        }
 
        /// <summary>
        /// Returns cached metadata and creates BitmapMetadata if it does not exist.
        /// This code will demand site of origin permissions.
        /// </summary>
        internal override BitmapMetadata InternalMetadata
        {
            get
            {
                // Demand Site Of Origin on the URI before usage of metadata.
                CheckIfSiteOfOrigin();
 
                if (!_isMetadataCached && !IsDownloading)
                {
                    EnsureSource();
 
                    IntPtr /* IWICMetadataQueryReader */ metadata = IntPtr.Zero;
 
                    lock (_syncObject)
                    {
                        // Check if there is embedded metadata or not
                        int hr = UnsafeNativeMethods.WICBitmapFrameDecode.GetMetadataQueryReader(
                            _frameSource,
                            out metadata
                            );
 
                        if (hr != (int)WinCodecErrors.WINCODEC_ERR_UNSUPPORTEDOPERATION)
                        {
                            HRESULT.Check(hr);
                        }
                    }
 
                    if (metadata != IntPtr.Zero)
                    {
                        SafeMILHandle metadataHandle = new SafeMILHandle(metadata);
 
                        _metadata = new BitmapMetadata(metadataHandle, true, _decoder != null ? _decoder.IsMetadataFixedSize : false, _syncObject);
                        _metadata.Freeze();
                    }
 
                    _isMetadataCached = true;
                }
 
                return _metadata;
            }
            set
            {
                throw new System.NotImplementedException();
            }
        }
 
        /// <summary>
        /// Ensure that a BitmapSource is created
        /// </summary>
        private void EnsureSource()
        {
            if (_frameSource == null)
            {
                if (_decoder == null)
                {
                    HRESULT.Check((int)WinCodecErrors.WINCODEC_ERR_NOTINITIALIZED);
                }
 
                //
                // Its possible that the frame was originally created with a network URI
                // and DelayCreation was enabled. In this case, the decoder may not yet
                // exist even though the download is complete. The code below creates a
                // decoder if one does not exist.
                //
                if (_decoder.InternalDecoder == null)
                {
                    Debug.Assert(_decoder is LateBoundBitmapDecoder);
                    Debug.Assert(IsDownloading == false);
 
                    _decoder = ((LateBoundBitmapDecoder)_decoder).Decoder;
                    _syncObject = _decoder.SyncObject;
 
                    Debug.Assert(_decoder.InternalDecoder != null);
                }
 
                IntPtr frameDecode = IntPtr.Zero;
 
                Debug.Assert(_syncObject != null);
                lock (_syncObject)
                {
                    HRESULT.Check(UnsafeNativeMethods.WICBitmapDecoder.GetFrame(
                        _decoder.InternalDecoder,
                        (uint)_frameNumber,
                        out frameDecode
                        ));
 
                    _frameSource = new BitmapSourceSafeMILHandle(frameDecode);
                    _frameSource.CalculateSize();
                }
            }
        }
 
        #endregion
 
        #region Data Members
 
        /// IWICBitmapFrameDecode source
        private BitmapSourceSafeMILHandle _frameSource = null;
 
        /// Frame number
        private int _frameNumber;
 
        /// Is the thumbnail cached
        private bool _isThumbnailCached;
 
        /// Is the metadata cached
        private bool _isMetadataCached;
 
        /// If the ColorContext is already cached
        private bool _isColorContextCached = false;
 
        /// CreateOptions for this Frame
        private BitmapCreateOptions _createOptions;
 
        /// CacheOption for this Frame
        private BitmapCacheOption _cacheOption;
 
        /// Decoder
        private BitmapDecoder _decoder;
 
        #endregion
 
        #region WeakBitmapSourceEvents
 
        // Used to propagate DownloadCompleted events for cloned BitmapFrameDecodes that are still downloading
        private WeakBitmapFrameDecodeEventSink _weakBitmapFrameDecodeEventSink;
 
        private class WeakBitmapFrameDecodeEventSink : WeakReference
        {
            public WeakBitmapFrameDecodeEventSink(BitmapFrameDecode cloned, BitmapFrameDecode original)
                : base(cloned)
            {
                _original = original;
 
                if (!_original.IsFrozen)
                {
                    _original.DownloadCompleted += OnSourceDownloadCompleted;
                    _original.DownloadFailed += OnSourceDownloadFailed;
                    _original.DownloadProgress += OnSourceDownloadProgress;
                }
            }
 
            public void OnSourceDownloadCompleted(object sender, EventArgs e)
            {
                BitmapFrameDecode clone = this.Target as BitmapFrameDecode;
                if (null != clone)
                {
                    clone.OnOriginalDownloadCompleted(_original, e);
                }
                else
                {
                    DetachSourceDownloadHandlers();
                }
            }
 
            public void OnSourceDownloadFailed(object sender, ExceptionEventArgs e)
            {
                BitmapFrameDecode clone = this.Target as BitmapFrameDecode;
                if (null != clone)
                {
                    clone.OnOriginalDownloadFailed(e);
                }
                else
                {
                    DetachSourceDownloadHandlers();
                }
            }
 
            public void OnSourceDownloadProgress(object sender, DownloadProgressEventArgs e)
            {
                BitmapFrameDecode clone = this.Target as BitmapFrameDecode;
                if (null != clone)
                {
                    clone.OnDownloadProgress(sender, e);
                }
                else
                {
                    DetachSourceDownloadHandlers();
                }
            }
 
            public void DetachSourceDownloadHandlers()
            {
                if (!_original.IsFrozen)
                {
                    _original.DownloadCompleted -= OnSourceDownloadCompleted;
                    _original.DownloadFailed -= OnSourceDownloadFailed;
                    _original.DownloadProgress -= OnSourceDownloadProgress;
                }
            }
 
            private BitmapFrameDecode _original;
        }
 
        #endregion WeakBitmapSourceEvents
    }
 
    #endregion // BitmapFrameDecode
}