File: System\Windows\Media\Imaging\BitmapDecoder.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.
 
#pragma warning disable 1634, 1691 // Allow suppression of certain presharp messages
 
using System.IO;
using System.IO.Packaging;
using System.Collections.ObjectModel;
using MS.Internal;
using MS.Win32.PresentationCore;
using Microsoft.Win32.SafeHandles;
using System.Windows.Threading;
using MS.Internal.PresentationCore;
using System.Net;
using System.Net.Cache;
using System.Text;
 
namespace System.Windows.Media.Imaging
{
    #region BitmapDecoder
 
    /// <summary>
    /// BitmapDecoder is a container for bitmap frames.  Each bitmap frame is an BitmapFrame.
    /// Any BitmapFrame it returns are frozen
    /// be immutable.
    /// </summary>
    public abstract class BitmapDecoder : DispatcherObject
    {
        #region Constructors
 
        static BitmapDecoder()
        {
        }
        /// <summary>
        /// Default constructor
        /// </summary>
        protected BitmapDecoder()
        {
        }
 
        /// <summary>
        /// Internal constructor
        /// </summary>
        internal BitmapDecoder(bool isBuiltIn)
        {
            _isBuiltInDecoder = isBuiltIn;
        }
 
        /// <summary>
        /// Constructor
        /// </summary>
        internal BitmapDecoder(
            Uri bitmapUri,
            BitmapCreateOptions createOptions,
            BitmapCacheOption cacheOption,
            Guid expectedClsId
            )
        {
            Guid clsId = Guid.Empty;
            bool isOriginalWritable = false;
 
            ArgumentNullException.ThrowIfNull(bitmapUri);
 
            if ((createOptions & BitmapCreateOptions.IgnoreImageCache) != 0)
            {
                ImagingCache.RemoveFromDecoderCache(bitmapUri);
            }
 
            BitmapDecoder decoder = CheckCache(bitmapUri, out clsId);
            if (decoder != null)
            {
                _decoderHandle = decoder.InternalDecoder;
            }
            else
            {
                _decoderHandle = SetupDecoderFromUriOrStream(
                    bitmapUri,
                    null,
                    cacheOption,
                    out clsId,
                    out isOriginalWritable,
                    out _uriStream,
                    out _unmanagedMemoryStream,
                    out _safeFilehandle
                    );
 
                if (_uriStream == null)
                {
                    GC.SuppressFinalize(this);
                }
            }
 
            if (clsId != expectedClsId)
            {
                throw new FileFormatException(bitmapUri, SR.Image_CantDealWithUri);
            }
 
            _uri = bitmapUri;
            _createOptions = createOptions;
            _cacheOption = cacheOption;
            _syncObject = _decoderHandle;
            _isOriginalWritable = isOriginalWritable;
            Initialize(decoder);
        }
 
        /// <summary>
        /// Constructor
        /// </summary>
        internal BitmapDecoder(
            Stream bitmapStream,
            BitmapCreateOptions createOptions,
            BitmapCacheOption cacheOption,
            Guid expectedClsId
            )
        {
            Guid clsId = Guid.Empty;
            bool isOriginalWritable = false;
 
            ArgumentNullException.ThrowIfNull(bitmapStream);
 
            _decoderHandle = SetupDecoderFromUriOrStream(
                null,
                bitmapStream,
                cacheOption,
                out clsId,
                out isOriginalWritable,
                out _uriStream,
                out _unmanagedMemoryStream,
                out _safeFilehandle
                );
 
            if (_uriStream == null)
            {
                GC.SuppressFinalize(this);
            }
 
            if (clsId != Guid.Empty && clsId != expectedClsId)
            {
                throw new FileFormatException(null, SR.Image_CantDealWithStream);
            }
 
            _stream = bitmapStream;
            _createOptions = createOptions;
            _cacheOption = cacheOption;
            _syncObject = _decoderHandle;
            _isOriginalWritable = isOriginalWritable;
            Initialize(null);
        }
 
        /// <summary>
        /// Constructor
        /// </summary>
        internal BitmapDecoder(
            SafeMILHandle decoderHandle,
            BitmapDecoder decoder,
            Uri baseUri,
            Uri uri,
            Stream stream,
            BitmapCreateOptions createOptions,
            BitmapCacheOption cacheOption,
            bool insertInDecoderCache,
            bool isOriginalWritable,
            Stream uriStream,
            UnmanagedMemoryStream unmanagedMemoryStream,
            SafeFileHandle safeFilehandle
            )
        {
            _decoderHandle = decoderHandle;
            _baseUri = baseUri;
            _uri = uri;
            _stream = stream;
            _createOptions = createOptions;
            _cacheOption = cacheOption;
            _syncObject = decoderHandle;
            _shouldCacheDecoder = insertInDecoderCache;
            _isOriginalWritable = isOriginalWritable;
            _uriStream = uriStream;
            _unmanagedMemoryStream = unmanagedMemoryStream;
            _safeFilehandle = safeFilehandle;
 
            if (_uriStream == null)
            {
                GC.SuppressFinalize(this);
            }
 
            Initialize(decoder);
        }
 
        /// <summary>
        /// Close the stream pointing to the file after this BitmapDecoder is no longer used.
        /// It might have been closed before in Initialize if the cache option was OnLoad or
        /// on decode if the cache option was OnDemand/Default. In those cases, the finalizer
        /// would have been suppressed.
        /// </summary>
        ~BitmapDecoder()
        {
            //
            // Normally _uriStream gets GCed because this BitmapDecoder is the only thing that
            // references it, and the stream's finalizer closes it. However, in some cases, other
            // objects still have references to the _uriStream, so it won't get GCed because this
            // decoder does. One such case is getting the stream from a pack uri that points to
            // a resource. In those cases, PackagePart created the streams and is also holding on
            // to them. It won't let go unless the stream is closed, but the stream won't close
            // unless it's been let go, so the stream is leaked. Closing the stream removes the
            // leak.
            //
            // _uriStream is always created within SetupDecoderFromUriOrStream. Either it's set
            // using an out parameter, or it's set in the internal constructor that takes a
            // uriStream parameter. That internal constructor is called by the sealed subclasses
            // of BitmapDecoder (such as JpegBitmapDecoder), those subclasses are constructed in
            // CreateFromUriOrStream, which also gets uriStream from SetupDecoderFromUriOrStream.
            //
            if (_uriStream != null)
            {
                _uriStream.Close();
            }
        }
 
        /// <summary>
        /// Create BitmapDecoder from the uri or stream. If both are specified, the uri
        /// is chosen.
        /// </summary>
        internal static BitmapDecoder CreateFromUriOrStream(
            Uri baseUri,
            Uri uri,
            Stream stream,
            BitmapCreateOptions createOptions,
            BitmapCacheOption cacheOption,
            RequestCachePolicy uriCachePolicy,
            bool insertInDecoderCache
            )
        {
            Guid clsId = Guid.Empty;
            bool isOriginalWritable = false;
            SafeMILHandle decoderHandle = null;
            BitmapDecoder cachedDecoder = null;
            Uri finalUri = null;
            Stream uriStream = null;
            UnmanagedMemoryStream unmanagedMemoryStream = null;
            SafeFileHandle safeFilehandle = null;
 
            if (uri != null)
            {
                finalUri = (baseUri != null) ?
                               System.Windows.Navigation.BaseUriHelper.GetResolvedUri(baseUri, uri) :
                               uri;
 
                if (insertInDecoderCache)
                {
                    if ((createOptions & BitmapCreateOptions.IgnoreImageCache) != 0)
                    {
                        ImagingCache.RemoveFromDecoderCache(finalUri);
                    }
 
                    cachedDecoder = CheckCache(
                        finalUri,
                        out clsId
                        );
                }
            }
 
            // try to retrieve the cached decoder
            if (cachedDecoder != null)
            {
                decoderHandle = cachedDecoder.InternalDecoder;
            }
            else if ((finalUri != null) && (finalUri.IsAbsoluteUri) && (stream == null) &&
                     ((finalUri.Scheme == Uri.UriSchemeHttp) ||
                      (finalUri.Scheme == Uri.UriSchemeHttps)))
            {
                return new LateBoundBitmapDecoder(baseUri, uri, stream, createOptions, cacheOption, uriCachePolicy);
            }
            else if ((stream != null) && (!stream.CanSeek))
            {
                return new LateBoundBitmapDecoder(baseUri, uri, stream, createOptions, cacheOption, uriCachePolicy);
            }
            else
            {
                // Create an unmanaged decoder
                decoderHandle = BitmapDecoder.SetupDecoderFromUriOrStream(
                    finalUri,
                    stream,
                    cacheOption,
                    out clsId,
                    out isOriginalWritable,
                    out uriStream,
                    out unmanagedMemoryStream,
                    out safeFilehandle
                    );
            }
 
            BitmapDecoder decoder = null;
 
            // Find out the decoder type and wrap it appropriately and return that
            if (MILGuidData.GUID_ContainerFormatBmp == clsId)
            {
                decoder = new BmpBitmapDecoder(
                    decoderHandle,
                    cachedDecoder,
                    baseUri,
                    uri,
                    stream,
                    createOptions,
                    cacheOption,
                    insertInDecoderCache,
                    isOriginalWritable,
                    uriStream,
                    unmanagedMemoryStream,
                    safeFilehandle
                    );
            }
            else if (MILGuidData.GUID_ContainerFormatGif == clsId)
            {
                decoder = new GifBitmapDecoder(
                    decoderHandle,
                    cachedDecoder,
                    baseUri,
                    uri,
                    stream,
                    createOptions,
                    cacheOption,
                    insertInDecoderCache,
                    isOriginalWritable,
                    uriStream,
                    unmanagedMemoryStream,
                    safeFilehandle
                    );
            }
            else if (MILGuidData.GUID_ContainerFormatIco == clsId)
            {
                decoder = new IconBitmapDecoder(
                    decoderHandle,
                    cachedDecoder,
                    baseUri,
                    uri,
                    stream,
                    createOptions,
                    cacheOption,
                    insertInDecoderCache,
                    isOriginalWritable,
                    uriStream,
                    unmanagedMemoryStream,
                    safeFilehandle
                    );
            }
            else if (MILGuidData.GUID_ContainerFormatJpeg == clsId)
            {
                decoder = new JpegBitmapDecoder(
                    decoderHandle,
                    cachedDecoder,
                    baseUri,
                    uri,
                    stream,
                    createOptions,
                    cacheOption,
                    insertInDecoderCache,
                    isOriginalWritable,
                    uriStream,
                    unmanagedMemoryStream,
                    safeFilehandle
                    );
            }
            else if (MILGuidData.GUID_ContainerFormatPng == clsId)
            {
                decoder = new PngBitmapDecoder(
                    decoderHandle,
                    cachedDecoder,
                    baseUri,
                    uri,
                    stream,
                    createOptions,
                    cacheOption,
                    insertInDecoderCache,
                    isOriginalWritable,
                    uriStream,
                    unmanagedMemoryStream,
                    safeFilehandle
                    );
            }
            else if (MILGuidData.GUID_ContainerFormatTiff == clsId)
            {
                decoder = new TiffBitmapDecoder(
                    decoderHandle,
                    cachedDecoder,
                    baseUri,
                    uri,
                    stream,
                    createOptions,
                    cacheOption,
                    insertInDecoderCache,
                    isOriginalWritable,
                    uriStream,
                    unmanagedMemoryStream,
                    safeFilehandle
                    );
            }
            else if (MILGuidData.GUID_ContainerFormatWmp == clsId)
            {
                decoder = new WmpBitmapDecoder(
                    decoderHandle,
                    cachedDecoder,
                    baseUri,
                    uri,
                    stream,
                    createOptions,
                    cacheOption,
                    insertInDecoderCache,
                    isOriginalWritable,
                    uriStream,
                    unmanagedMemoryStream,
                    safeFilehandle
                    );
            }
            else
            {
                decoder = new UnknownBitmapDecoder(
                    decoderHandle,
                    cachedDecoder,
                    baseUri,
                    uri,
                    stream,
                    createOptions,
                    cacheOption,
                    insertInDecoderCache,
                    isOriginalWritable,
                    uriStream,
                    unmanagedMemoryStream,
                    safeFilehandle
                    );
            }
 
            return decoder;
        }
 
        /// <summary>
        /// Create a BitmapDecoder from a Uri with the specified BitmapCreateOptions and
        /// BitmapCacheOption
        /// </summary>
        /// <param name="bitmapUri">Uri to decode</param>
        /// <param name="createOptions">Bitmap Create Options</param>
        /// <param name="cacheOption">Bitmap Caching Option</param>
        public static BitmapDecoder Create(
            Uri bitmapUri,
            BitmapCreateOptions createOptions,
            BitmapCacheOption cacheOption
            )
        {
            return Create(bitmapUri, createOptions, cacheOption, null);
        }
 
        /// <summary>
        /// Create a BitmapDecoder from a Uri with the specified BitmapCreateOptions and
        /// BitmapCacheOption
        /// </summary>
        /// <param name="bitmapUri">Uri to decode</param>
        /// <param name="createOptions">Bitmap Create Options</param>
        /// <param name="cacheOption">Bitmap Caching Option</param>
        /// <param name="uriCachePolicy">Optional web request cache policy</param>
        public static BitmapDecoder Create(
            Uri bitmapUri,
            BitmapCreateOptions createOptions,
            BitmapCacheOption cacheOption,
            RequestCachePolicy uriCachePolicy
            )
        {
            ArgumentNullException.ThrowIfNull(bitmapUri);
 
            return CreateFromUriOrStream(
                null,
                bitmapUri,
                null,
                createOptions,
                cacheOption,
                uriCachePolicy,
                true
                );
        }
 
        /// <summary>
        /// Create a BitmapDecoder from a Stream with the specified BitmapCreateOptions and
        /// BitmapCacheOption
        /// </summary>
        /// <param name="bitmapStream">Stream to decode</param>
        /// <param name="createOptions">Bitmap Create Options</param>
        /// <param name="cacheOption">Bitmap Caching Option</param>
        public static BitmapDecoder Create(
            Stream bitmapStream,
            BitmapCreateOptions createOptions,
            BitmapCacheOption cacheOption
            )
        {
            ArgumentNullException.ThrowIfNull(bitmapStream);
 
            return CreateFromUriOrStream(
                null,
                null,
                bitmapStream,
                createOptions,
                cacheOption,
                null,
                true
                );
        }
 
        #endregion
 
        #region Properties
 
        /// <summary>
        /// If there is an palette, return it.
        /// Otherwise, return null.
        /// </summary>
        public virtual BitmapPalette Palette
        {
            get
            {
                VerifyAccess();
                EnsureBuiltInDecoder();
 
                if (!_isPaletteCached)
                {
                    SafeMILHandle /* IWICBitmapPalette */ paletteHandle = BitmapPalette.CreateInternalPalette();
 
                    lock (_syncObject)
                    {
                        int hr = UnsafeNativeMethods.WICBitmapDecoder.CopyPalette(
                            _decoderHandle,
                            paletteHandle
                            );
                        if (hr != (int)WinCodecErrors.WINCODEC_ERR_PALETTEUNAVAILABLE)
                        {
                            HRESULT.Check(hr);
                            _palette = new BitmapPalette(paletteHandle);
                        }
                    }
 
                    _isPaletteCached = true;
                }
 
                return _palette;
            }
        }
 
        /// <summary>
        /// If there is an embedded color profile, return it.
        /// Otherwise, return null.
        /// </summary>
        public virtual ReadOnlyCollection<ColorContext> ColorContexts
        {
            get
            {
                VerifyAccess();
 
                return InternalColorContexts;
            }
        }
 
        /// <summary>
        /// If there is a global thumbnail, return it.
        /// Otherwise, return null. The returned source is frozen.
        /// </summary>
        public virtual BitmapSource Thumbnail
        {
            get
            {
                VerifyAccess();
                EnsureBuiltInDecoder();
 
                if (!_isThumbnailCached)
                {
                    IntPtr /* IWICBitmapSource */ thumbnail = IntPtr.Zero;
 
                    // Check if there is embedded thumbnail or not
                    lock (_syncObject)
                    {
                        int hr = UnsafeNativeMethods.WICBitmapDecoder.GetThumbnail(
                            _decoderHandle,
                            out thumbnail
                            );
                        if (hr != (int)WinCodecErrors.WINCODEC_ERR_CODECNOTHUMBNAIL)
                        {
                            HRESULT.Check(hr);
                        }
                    }
 
                    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(
                            BitmapSource.CreateCachedBitmap(
                                null,
                                thumbHandle,
                                BitmapCreateOptions.PreservePixelFormat,
                                _cacheOption,
                                palette
                                ));
                        _thumbnail.Freeze();
                    }
 
                    _isThumbnailCached = true;
                }
 
                return _thumbnail;
            }
        }
 
        /// <summary>
        /// If there is a global metadata, return it.
        /// Otherwise, return null. The returned source is frozen.
        /// </summary>
        public virtual BitmapMetadata Metadata
        {
            get
            {
                VerifyAccess();
                EnsureBuiltInDecoder();
 
                if (!_isMetadataCached)
                {
                    IntPtr /* IWICMetadataQueryReader */ metadata = IntPtr.Zero;
 
                    lock (_syncObject)
                    {
                        int hr = UnsafeNativeMethods.WICBitmapDecoder.GetMetadataQueryReader(
                            _decoderHandle,
                            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, IsMetadataFixedSize, _syncObject);
                        _metadata.Freeze();
                    }
 
                    _isMetadataCached = true;
                }
 
                return _metadata;
            }
        }
 
        /// <summary>
        /// The info that identifies this codec.
        /// </summary>
        public virtual BitmapCodecInfo CodecInfo
        {
            get
            {
                VerifyAccess();
                EnsureBuiltInDecoder();
 
                // There should always be a codec info.
                if (_codecInfo == null)
                {
                    SafeMILHandle /* IWICBitmapDecoderInfo */ codecInfoHandle =  new SafeMILHandle();
 
                    HRESULT.Check(UnsafeNativeMethods.WICBitmapDecoder.GetDecoderInfo(
                        _decoderHandle,
                        out codecInfoHandle
                        ));
 
                    _codecInfo = new BitmapCodecInfoInternal(codecInfoHandle);
                }
 
                return _codecInfo;
            }
        }
 
        /// <summary>
        /// Access to the individual frames. All BitmapFrames are frozen.
        /// </summary>
        public virtual ReadOnlyCollection<BitmapFrame> Frames
        {
            get
            {
                VerifyAccess();
                EnsureBuiltInDecoder();
 
                if (_frames == null)
                {
                    SetupFrames(null, null);
                }
 
                if (_readOnlyFrames == null)
                {
                    _readOnlyFrames = new ReadOnlyCollection<BitmapFrame>(_frames);
                }
 
                return _readOnlyFrames;
            }
        }
 
        /// <summary>
        /// If there is a global preview image, return it.
        /// Otherwise, return null. The returned source is frozen.
        /// </summary>
        public virtual BitmapSource Preview
        {
            get
            {
                VerifyAccess();
                EnsureBuiltInDecoder();
 
                if (!_isPreviewCached)
                {
                    IntPtr /* IWICBitmapSource */ preview = IntPtr.Zero;
 
                    lock (_syncObject)
                    {
                        // Check if there is embedded preview or not
                        int hr = UnsafeNativeMethods.WICBitmapDecoder.GetPreview(
                            _decoderHandle,
                            out preview
                            );
                        if (hr != (int)WinCodecErrors.WINCODEC_ERR_UNSUPPORTEDOPERATION)
                        {
                            HRESULT.Check(hr);
                        }
                    }
 
                    if (preview != IntPtr.Zero)
                    {
                        BitmapSourceSafeMILHandle previewHandle = new BitmapSourceSafeMILHandle(preview);
                        SafeMILHandle unmanagedPalette = BitmapPalette.CreateInternalPalette();
                        BitmapPalette palette = null;
                    
                        int hr = UnsafeNativeMethods.WICBitmapSource.CopyPalette(
                                    previewHandle,
                                    unmanagedPalette
                                    );
                        if (hr == HRESULT.S_OK)
                        {
                            palette = new BitmapPalette(unmanagedPalette);
                        }
                    
                        _preview = new UnmanagedBitmapWrapper(
                            BitmapSource.CreateCachedBitmap(
                                null,
                                previewHandle,
                                BitmapCreateOptions.PreservePixelFormat,
                                _cacheOption,
                                palette
                                ));
                        _preview.Freeze();
                    }
                    
                    _isPreviewCached = true;
                }
 
                return _preview;
            }
        }
 
        /// <summary>
        /// Returns true if the decoder is downloading content
        /// </summary>
        public virtual bool IsDownloading
        {
            get
            {
                VerifyAccess();
                EnsureBuiltInDecoder();
                return false;
            }
        }
 
        /// <summary>
        /// Raised when decoder has completed downloading content
        /// May not be raised for all content.
        /// </summary>
        public virtual event EventHandler DownloadCompleted
        {
            add
            {
                VerifyAccess();
                EnsureBuiltInDecoder();
                _downloadEvent.AddEvent(value);
            }
            remove
            {
                VerifyAccess();
                EnsureBuiltInDecoder();
                _downloadEvent.RemoveEvent(value);
            }
        }
 
        /// <summary>
        /// Raised when download has progressed
        /// May not be raised for all content.
        /// </summary>
        public virtual event EventHandler<DownloadProgressEventArgs> DownloadProgress
        {
            add
            {
                VerifyAccess();
                EnsureBuiltInDecoder();
                _progressEvent.AddEvent(value);
            }
            remove
            {
                VerifyAccess();
                EnsureBuiltInDecoder();
                _progressEvent.RemoveEvent(value);
            }
        }
 
        /// <summary>
        /// Raised when download has failed
        /// May not be raised for all content.
        /// </summary>
        public virtual event EventHandler<ExceptionEventArgs> DownloadFailed
        {
            add
            {
                VerifyAccess();
                EnsureBuiltInDecoder();
                _failedEvent.AddEvent(value);
            }
            remove
            {
                VerifyAccess();
                EnsureBuiltInDecoder();
                _failedEvent.RemoveEvent(value);
            }
        }
 
        #endregion
 
        #region Public Properties
 
        /// <summary>
        /// Create an in-place bitmap metadata writer.
        /// </summary>
        public virtual InPlaceBitmapMetadataWriter CreateInPlaceBitmapMetadataWriter()
        {
            VerifyAccess();
            EnsureBuiltInDecoder();
 
            CheckOriginalWritable();
 
            return InPlaceBitmapMetadataWriter.CreateFromDecoder(_decoderHandle, _syncObject);
        }
 
        #endregion
 
        #region ToString
 
        /// <summary>
        /// ToString
        /// </summary>
        public override string ToString()
        {
            VerifyAccess();
 
            if (!_isBuiltInDecoder)
            {
                return base.ToString();
            }
 
            if (_uri != null)
            {
                if (_baseUri != null)
                {
                    Uri uri = new Uri(_baseUri, _uri);
                    return BindUriHelper.UriToString(uri);
                }
                else
                {
                    return BindUriHelper.UriToString(_uri);
                }
            }
            else
            {
                return SafeSecurityHelper.IMAGE;
            }
        }
 
        #endregion
 
        #region Internal Properties
 
        /// <summary>
        /// Internal Decoder
        /// </summary>
        internal SafeMILHandle InternalDecoder
        {
            get
            {
                return _decoderHandle;
            }
        }
 
        /// <summary>
        /// Returns whether metadata is fixed size or not.
        /// </summary>
        internal virtual bool IsMetadataFixedSize
        {
            get
            {
                return false;
            }
        }
 
        /// <summary>
        /// Synchronization Object.  Any unmanaged PInvoke/call that requires synchronization
        /// must lock on the sync object.  This object must be internal or private so as not
        /// to be publicly lockable.
        /// </summary>
        internal object SyncObject
        {
            get
            {
                Debug.Assert(_syncObject != null);
                return _syncObject;
            }
        }
 
        /// <summary>
        /// Used as a delegate in InternalColorContexts to get the unmanaged IWICColorContexts
        /// </summary>
        private int GetColorContexts(ref uint numContexts, IntPtr[] colorContextPtrs)
        {
            Invariant.Assert(colorContextPtrs == null || numContexts <= colorContextPtrs.Length);
            
            return UnsafeNativeMethods.WICBitmapDecoder.GetColorContexts(_decoderHandle, numContexts, colorContextPtrs, out numContexts);
        }
 
        /// <summary>
        /// If there is an embedded color profile, return it.
        /// Otherwise, return null.
        /// </summary>
        internal ReadOnlyCollection<ColorContext> InternalColorContexts
        {
            get
            {
                EnsureBuiltInDecoder();
 
                if (!_isColorContextCached)
                {
                    lock (_syncObject)
                    {
                        IList<ColorContext> colorContextList = ColorContext.GetColorContextsHelper(GetColorContexts);
 
                        if (colorContextList != null)
                        {
                            _readOnlycolorContexts = new ReadOnlyCollection<ColorContext>(colorContextList);
                        }
 
                        _isColorContextCached = true;
                    }
                }
 
                return _readOnlycolorContexts;
            }
        }
 
 
        /// <summary>
        /// Checks whether the underlying source is writable (useful for in-place metadata editing)
        /// </summary>
        internal void CheckOriginalWritable()
        {
            if (!_isOriginalWritable)
            {
                throw new System.InvalidOperationException(SR.Image_OriginalStreamReadOnly);
            }
        }
 
        #endregion
 
        #region Internal/Private Methods
 
        internal static SafeMILHandle SetupDecoderFromUriOrStream(
            Uri uri,
            Stream stream,
            BitmapCacheOption cacheOption,
            out Guid clsId,
            out bool isOriginalWritable,
            out Stream uriStream,
            out UnmanagedMemoryStream unmanagedMemoryStream,
            out SafeFileHandle safeFilehandle
            )
        {
            SafeMILHandle decoderHandle;
            IntPtr decoder = IntPtr.Zero;
            System.IO.Stream bitmapStream = null;
            unmanagedMemoryStream = null;
            safeFilehandle = null;
            isOriginalWritable = false;
            uriStream = null;
 
            if ((uri != null) && (stream != null))
            {
                // In this case we expect the Uri to be http(s)
                Debug.Assert((uri.Scheme == Uri.UriSchemeHttp) || (uri.Scheme == Uri.UriSchemeHttps));
                Debug.Assert(stream.CanSeek);
            }
 
            // Uri
            if (uri != null)
            {
                if (uri.IsAbsoluteUri)
                {
                    // This code path executes only for pack web requests
                    if (string.Equals(uri.Scheme, PackUriHelper.UriSchemePack, StringComparison.OrdinalIgnoreCase))
                    {
                        WebResponse response = WpfWebRequestHelper.CreateRequestAndGetResponse(uri);
                        bitmapStream = response.GetResponseStream();
                        uriStream = bitmapStream;
                    }
                }
 
                if ((bitmapStream == null) || (bitmapStream == System.IO.Stream.Null))
                {
                    // We didn't get a stream from the pack web request, so we have
                    // to try to create one ourselves.
                    if (uri.IsAbsoluteUri)
                    {
                        // The Uri class can't tell if it is a file unless it
                        // has an absolute path.
                        int targetZone = SecurityHelper.MapUrlToZoneWrapper(uri);
                        if (targetZone == MS.Win32.NativeMethods.URLZONE_LOCAL_MACHINE)
                        {
                            if (uri.IsFile)
                            {
                                // FileStream does a demand for us, so no need to do a demand
                                bitmapStream = new System.IO.FileStream(uri.LocalPath, FileMode.Open, FileAccess.Read, FileShare.Read);
                            }
                        }
                        else // Any other zone
                        {
                            // UNC path for a file which is not http
                            if (uri.IsFile && uri.IsUnc) // for UNC
                            {
                                bitmapStream = ProcessUncFiles(uri);
                            }
                            else if (uri.Scheme == Uri.UriSchemeHttp) // for http
                            {
                                bitmapStream = ProcessHttpFiles(uri,stream);
                            }
                            else if (uri.Scheme == Uri.UriSchemeHttps) // for https
                            {
                                bitmapStream = ProcessHttpsFiles(uri,stream);
                            }
                            else
                            {
                                // The Uri is a custom Uri. Try to grab the stream from its WebResponse.
                                bitmapStream = WpfWebRequestHelper.CreateRequestAndGetResponseStream(uri);
                            }
                        }
                    }
                    else
                    {
                        #pragma warning disable 6518
                        // We don't have an absolute URI, so we don't necessarily know
                        // if it is a file, but we'll have to assume it is and try to
                        // create a stream from the original string.
                        bitmapStream = new System.IO.FileStream(uri.OriginalString, FileMode.Open, FileAccess.Read, FileShare.Read);
                        #pragma warning restore 6518
                    }
 
                    uriStream = bitmapStream;
                }
            }
 
            // We need to use the stream created from the Uri.
            if (bitmapStream != null)
            {
                stream = bitmapStream;
            }
            else
            {
                // Note whether the original stream is writable.
                isOriginalWritable = stream.CanSeek && stream.CanWrite;
            }
 
            // Make sure we always use a seekable stream to avoid problems with http Uris etc.
            stream = GetSeekableStream(stream);
 
            if (stream is UnmanagedMemoryStream)
            {
                unmanagedMemoryStream = stream as UnmanagedMemoryStream;
            }
 
            IntPtr comStream = IntPtr.Zero;
 
            if (stream is System.IO.FileStream)
            {
                System.IO.FileStream filestream = stream as System.IO.FileStream;
                try
                {
                    if (filestream.IsAsync is false)
                    {
                        safeFilehandle = filestream.SafeFileHandle;
                    }
                    else
                    {
                        // The IWICImagingFactory_CreateDecoderFromFileHandle_Proxy do not support async Filestream, so we revert to old code path.
                        safeFilehandle = null;
                    }
                }
                catch
                {
                    // If Filestream doesn't support SafeHandle then revert to old code path.
                    // See https://github.com/dotnet/wpf/issues/4355
                    safeFilehandle = null;
                }
            }
 
            try
            {
                Guid vendorMicrosoft = new Guid(MILGuidData.GUID_VendorMicrosoft);
                UInt32 metadataFlags = (uint)WICMetadataCacheOptions.WICMetadataCacheOnDemand;
 
                if (cacheOption == BitmapCacheOption.OnLoad)
                {
                    metadataFlags = (uint)WICMetadataCacheOptions.WICMetadataCacheOnLoad;
                }
 
                // We have a SafeHandle.
                if (safeFilehandle != null)
                {
                    using (FactoryMaker myFactory = new FactoryMaker())
                    {
                        HRESULT.Check(UnsafeNativeMethods.WICImagingFactory.CreateDecoderFromFileHandle(
                            myFactory.ImagingFactoryPtr,
                            safeFilehandle,
                            ref vendorMicrosoft,
                            metadataFlags,
                            out decoder
                            ));
                    }
                }
                else
                {
                    comStream = BitmapDecoder.GetIStreamFromStream(ref stream);
 
                    using (FactoryMaker myFactory = new FactoryMaker())
                    {
                        // This does an add-ref on the comStream
                        HRESULT.Check(UnsafeNativeMethods.WICImagingFactory.CreateDecoderFromStream(
                            myFactory.ImagingFactoryPtr,
                            comStream,
                            ref vendorMicrosoft,
                            metadataFlags,
                            out decoder
                            ));
                    }
                }
                Debug.Assert(decoder != IntPtr.Zero);
                decoderHandle = new SafeMILHandle(decoder);
            }
            catch
            {
                bitmapStream.Close();
                #pragma warning disable 6500
 
                decoderHandle = null;
                throw;
 
                #pragma warning restore 6500
            }
            finally
            {
                UnsafeNativeMethods.MILUnknown.ReleaseInterface(ref comStream);
            }
 
            string decoderMimeTypes;
            clsId = GetCLSIDFromDecoder(decoderHandle, out decoderMimeTypes);
 
            return decoderHandle;
        }
 
        private static Stream ProcessHttpsFiles(Uri uri, Stream stream)
        {
            Stream bitmapStream = stream;
            // This is the condition where the Bitmap has already been downloaded
            // or the stream is not seekable
            // using async dowload in that case simply return the original stream
            // else you download the stream
            if (bitmapStream == null || !bitmapStream.CanSeek)
            {
                WebRequest request = null;
 
                request = WpfWebRequestHelper.CreateRequest(uri);
 
                bitmapStream = WpfWebRequestHelper.GetResponseStream(request);
            }
            return bitmapStream;
        }
 
        private static Stream ProcessHttpFiles(Uri uri, Stream stream)
        {
            WebRequest request = null;
            Stream bitmapStream =  stream;
            // Download only if this content is not already downloaded or stream is not seekable
            if (bitmapStream == null || !bitmapStream.CanSeek)
            {
                request = WpfWebRequestHelper.CreateRequest(uri);
 
                // Download only if this content is not already downloaded or stream is not seekable
                bitmapStream = WpfWebRequestHelper.GetResponseStream(request);
            }
            return bitmapStream;
        }
 
        private static Stream ProcessUncFiles(Uri uri)
        {
 
 
            return new System.IO.FileStream(uri.LocalPath, FileMode.Open, FileAccess.Read, FileShare.Read);
        }
 
        /// Returns the decoder's CLSID
        private static Guid GetCLSIDFromDecoder(SafeMILHandle decoderHandle, out string decoderMimeTypes)
        {
            Guid clsId;
 
            // Get the decoder info
            SafeMILHandle decoderInfo = new SafeMILHandle();
            HRESULT.Check(UnsafeNativeMethods.WICBitmapDecoder.GetDecoderInfo(
                decoderHandle,
                out decoderInfo
                ));
 
            // Get CLSID for the decoder
            HRESULT.Check(UnsafeNativeMethods.WICBitmapCodecInfo.GetContainerFormat(decoderInfo, out clsId));
 
            StringBuilder mimeTypes = null;
            UInt32 length = 0;
 
            // Find the length of the string needed
            HRESULT.Check(UnsafeNativeMethods.WICBitmapCodecInfo.GetMimeTypes(
                decoderInfo,
                0,
                mimeTypes,
                out length
                ));
 
            // get the string back
            if (length > 0)
            {
                mimeTypes = new StringBuilder((int)length);
 
                HRESULT.Check(UnsafeNativeMethods.WICBitmapCodecInfo.GetMimeTypes(
                    decoderInfo,
                    length,
                    mimeTypes,
                    out length
                    ));
            }
 
            if (mimeTypes != null)
            {
                decoderMimeTypes = mimeTypes.ToString();
            }
            else
            {
                decoderMimeTypes = String.Empty;
            }
 
            return clsId;
        }
 
        /// Return a seekable stream if the current one is not seekable
        private static System.IO.Stream GetSeekableStream(System.IO.Stream bitmapStream)
        {
            // MIL codecs require the source stream to be seekable. But if
            // the source stream is an internet stream, it is not seekable.
            // The data is probably not in the stream yet.
 
            if (bitmapStream.CanSeek)
            {
                return bitmapStream;
            }
 
            // If the source is not seekable, we have to download the
            // stream into a memory stream before we can decode it.
            // ISSUE-2002/10/03--minliu: later on, if we can make MIL
            // support progressive JPEG etc., we should take out the
            // hack here and pass the network stream (CConectStream)
            // directly to the unmanaged code
 
            System.IO.MemoryStream memStream =
                new System.IO.MemoryStream();
 
            byte[] buffer = new byte[1024];
            int read;
 
            // Read all the bytes and write it to a memory stream
            // This could be unbounded Going forward,
            // we either need to eliminate this code and come up
            // with a cleaner way of doing this, or prevent unbounded
            // memory creation.
            do
            {
                read = bitmapStream.Read(buffer, 0, 1024);
                if (read <= 0)
                {
                    break;
                }
                memStream.Write(buffer, 0, read);
            } while (true);
 
            // Reset the memory stream pointer back to the begining
            memStream.Seek(0, System.IO.SeekOrigin.Begin);
 
            // Use the new stream
 
            return memStream;
        }
 
        /// Check the cache to see if decoder already exists
        private static BitmapDecoder CheckCache(
            Uri uri,
            out Guid clsId
            )
        {
            clsId = Guid.Empty;
            string mimeTypes;
 
            if (uri != null)
            {
                WeakReference weakRef = ImagingCache.CheckDecoderCache(uri) as WeakReference;
                if (weakRef != null)
                {
                    BitmapDecoder bitmapDecoder = weakRef.Target as BitmapDecoder;
                    if ((bitmapDecoder != null) && bitmapDecoder.CheckAccess())
                    {
                        lock (bitmapDecoder.SyncObject)
                        {
                            clsId = GetCLSIDFromDecoder(bitmapDecoder.InternalDecoder, out mimeTypes);
                            return bitmapDecoder;
                        }
                    }
                    // Remove from the cache if bitmapDecoder is already been collected
                    if (bitmapDecoder == null)
                    {
                        ImagingCache.RemoveFromDecoderCache(uri);
                    }
                }
            }
 
            return null;
        }
 
        /// <summary>
        /// Initialize the codec.
        /// </summary>
        private void Initialize(BitmapDecoder decoder)
        {
            _isBuiltInDecoder = true;
 
            if (decoder != null)
            {
                SetupFrames(decoder, null);
                //
                // We need to keep the strong reference to the cached decoder for a few reasons:
                //
                //    The application may release the original cached decoder and then keep a
                //    reference to this decoder only, in which case, the cache can be collected.
                //    This will cause a few undesirable results:
                //    1. The application may choose to decode the same URI again in which case
                //       we will not retrieve it from the cache even though we have a copy already
                //       decoded.
                //    2. The original cached decoder holds onto the file stream indirectly which if
                //       collected can cause bad behavior if the entire decoder is not loaded into
                //       memory.
                //
                _cachedDecoder = decoder;
            }
            else if ((_createOptions & BitmapCreateOptions.DelayCreation) == 0 && _cacheOption == BitmapCacheOption.OnLoad)
            {
                SetupFrames(null, null);
 
                // Its ok to close the stream since the frames are not delay created and caching is immediate
                CloseStream();
            }
 
 
            if ((_uri != null) && (decoder == null) && _shouldCacheDecoder)
            {
                // Add this decoder to the decoder cache
                ImagingCache.AddToDecoderCache(
                    (_baseUri == null) ? _uri : new Uri(_baseUri, _uri),
                    new WeakReference(this)
                    );
            }
        }
 
        /// <summary>
        /// Closes the stream if its non-null
        /// </summary>
        internal void CloseStream()
        {
            if (_uriStream != null)
            {
                _uriStream.Close();
                _uriStream = null;
 
                // The finalizer closes _uriStream, we already closed it so there's no need to run
                // the finalizer anymore.
                GC.SuppressFinalize(this);
            }
        }
 
        /// <summary>
        /// Sets up the frames collection.
        /// Also, ensures that the frame collection for this decoder
        /// will contain the same underlying frames as the collection
        /// passed in. This is called by the LateBoundBitmapDecoder
        /// </summary>
        internal void SetupFrames(BitmapDecoder decoder, ReadOnlyCollection<BitmapFrame> frames)
        {
            uint numFrames = 1;
 
            HRESULT.Check(UnsafeNativeMethods.WICBitmapDecoder.GetFrameCount(_decoderHandle, out numFrames));
 
            _frames = new List<BitmapFrame>((int)numFrames);
 
            // initialize the list of frames to null.
            // We'll fill it as it's used (pay for play).
            for (int i = 0; i < (int)numFrames; i++)
            {
                if (i > 0 && _cacheOption != BitmapCacheOption.OnLoad)
                {
                    _createOptions |= BitmapCreateOptions.DelayCreation;
                }
 
                BitmapFrameDecode bfd = null;
 
                if ((frames != null) && (frames.Count == (i + 1)))
                {
                    // If we already have a frames collection, get the BitmapFrame from it
                    bfd = frames[i] as BitmapFrameDecode;
                    bfd.UpdateDecoder(this);
                }
                else if (decoder == null)
                {
                    // All frames should be frozen.
                    bfd = new BitmapFrameDecode(
                        i,
                        _createOptions,
                        _cacheOption,
                        this
                        );
                    bfd.Freeze();
                }
                else
                {
                    // if we are creating from an existing cache, use the frames
                    // already stored in that cache
                    // All frames should be frozen.
                    bfd = new BitmapFrameDecode(
                        i,
                        _createOptions,
                        _cacheOption,
                        decoder.Frames[i] as BitmapFrameDecode
                        );
                    bfd.Freeze();
                }
 
                _frames.Add(bfd);
            }
        }
 
        /// <summary>
        /// Checks if the decoder is builtin. If not, throw exception
        /// </summary>
        private void EnsureBuiltInDecoder()
        {
            if (!_isBuiltInDecoder)
            {
                throw new NotImplementedException();
            }
        }
 
        /// <summary>
        /// Note, we must hold onto a reference to the managed stream
        /// as long as we are using the unmanaged stream in the decoder.
        /// This method may create a new managed stream.
        /// </summary>
        /// <param name="bitmapStream"></param>
        /// <returns></returns>
        private static IntPtr GetIStreamFromStream(ref System.IO.Stream bitmapStream)
        {
            IntPtr  comStream = IntPtr.Zero;
 
            // ensure the stream is seekable
            bool seekable = bitmapStream.CanSeek;
 
            if (bitmapStream is UnmanagedMemoryStream)
            {
                UnmanagedMemoryStream memoryStream = bitmapStream as UnmanagedMemoryStream;
                IntPtr bufferPtr = IntPtr.Zero;
                int length = 0;
 
                unsafe
                {
                   bufferPtr = (IntPtr) memoryStream.PositionPointer;
                   length = (int) memoryStream.Length;
                }
 
                if (bufferPtr != IntPtr.Zero)
                {
                    comStream = StreamAsIStream.IStreamFrom(bufferPtr, length);
                }
            }
            else
            {
                comStream = StreamAsIStream.IStreamFrom(bitmapStream);
 
                if (comStream == IntPtr.Zero)
                {
                    throw new System.InvalidOperationException(
                        SR.Image_CantDealWithStream);
                }
 
                // If the stream is not seekable, we must create a
                // seekable one for the decoder.
                if (!seekable || ((!bitmapStream.CanWrite) && (bitmapStream.Length <= 1048576)))
                {
                    IntPtr memoryStream = StreamAsIStream.IStreamMemoryFrom(comStream);
 
                    if (memoryStream != IntPtr.Zero)
                    {
                        // we don't need the original stream anymore
                        UnsafeNativeMethods.MILUnknown.ReleaseInterface(ref comStream);
                        bitmapStream = System.IO.Stream.Null;
                        return memoryStream;
                    }
                    else if (!seekable)
                    {
                        throw new System.InvalidOperationException(
                                SR.Image_CantDealWithStream);
                    }
                }
}
 
            if (comStream == IntPtr.Zero)
            {
                throw new System.InvalidOperationException(
                SR.Image_CantDealWithStream);
            }
 
            return comStream;
        }
 
 
        /// Returns whether decoder can be converted to a string
        internal bool CanConvertToString()
        {
            return (_uri != null);
        }
 
        #endregion
 
        #region Internal Abstract
 
        /// "Seals" the object
        internal abstract void SealObject();
 
        #endregion
 
        #region Data Members
 
        /// check to see if implementation is internal
        private bool _isBuiltInDecoder;
 
        /// Internal Decoder
        private SafeMILHandle _decoderHandle;
 
        /// flag to see if decoder should be inserted in the cache
        private bool _shouldCacheDecoder = true;
 
        /// flag to see if decoder should be inserted in the cache
        private bool _isOriginalWritable = false;
 
        /// If the palette is already cached
        private bool _isPaletteCached;
 
        /// Palette
        private BitmapPalette _palette = null;
 
        /// If the ColorContext is already cached
        private bool _isColorContextCached = false;
 
        /// ColorContexts collection
        internal ReadOnlyCollection<ColorContext> _readOnlycolorContexts;
 
        /// If the thumbnail is already cached
        private bool _isThumbnailCached;
 
        /// <summary>
        /// Metadata
        /// </summary>
        private BitmapMetadata _metadata;
 
        /// If the metadata is already cached
        private bool _isMetadataCached;
 
        /// Thumbnail
        private BitmapSource _thumbnail = null;
 
        /// CodecInfo
        private BitmapCodecInfo _codecInfo;
 
        /// If the preview is already cached
        private bool _isPreviewCached;
 
        /// Preview
        private BitmapSource _preview = null;
 
        /// Frames collection
        internal List<BitmapFrame> _frames;
 
        /// Frames collection
        internal ReadOnlyCollection<BitmapFrame> _readOnlyFrames;
 
        /// Stream
        internal Stream _stream;
 
        /// Uri
        internal Uri _uri;
 
        /// Base Uri, only stored internally. Not used for serialization
        internal Uri _baseUri;
 
        /// Uri Stream -- this is the stream that was created from the passed in Uri
        internal Stream _uriStream;
 
        /// CreateOptions
        internal BitmapCreateOptions _createOptions;
 
        /// CacheOption
        internal BitmapCacheOption _cacheOption;
 
        /// Event helper for download completed event
        internal UniqueEventHelper _downloadEvent = new UniqueEventHelper();
 
        /// Event helper for download progress event
        internal UniqueEventHelper<DownloadProgressEventArgs> _progressEvent = new UniqueEventHelper<DownloadProgressEventArgs>();
 
        /// Event helper for download failed event
        internal UniqueEventHelper<ExceptionEventArgs> _failedEvent = new UniqueEventHelper<ExceptionEventArgs>();
 
        /// SyncObject
        private readonly object _syncObject = new Object();
 
        // For UnmanagedMemoryStream we want to make sure that buffer
        // its pointing to is not getting release until decoder is alive
        private UnmanagedMemoryStream _unmanagedMemoryStream;
 
        private SafeFileHandle _safeFilehandle;
 
        private BitmapDecoder _cachedDecoder;
 
        #endregion
    }
 
    #endregion // BitmapDecoder
}