File: System\Windows\Media\Imaging\BitmapImage.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.IO;
using System.ComponentModel;
using MS.Internal;
using MS.Internal.PresentationCore;
using System.Windows.Markup;
using System.Net.Cache;
 
namespace System.Windows.Media.Imaging
{
    #region BitmapImage
 
    /// <summary>
    /// BitmapImage provides caching functionality for a BitmapSource.
    /// </summary>
    public sealed partial class BitmapImage : Imaging.BitmapSource, ISupportInitialize, IUriContext
    {
        /// <summary>
        /// BitmapImage constructor
        /// </summary>
        public BitmapImage()
            : base(true) // Use base class virtuals
        {
        }
 
        /// <summary>
        /// Construct a BitmapImage with the given Uri
        /// </summary>
        /// <param name="uriSource">Uri of the source Bitmap</param>
        public BitmapImage(Uri uriSource)
            : this(uriSource, null)
        {
        }
 
        /// <summary>
        /// Construct a BitmapImage with the given Uri and RequestCachePolicy
        /// </summary>
        /// <param name="uriSource">Uri of the source Bitmap</param>
        /// <param name="uriCachePolicy">Optional web request cache policy</param>
        public BitmapImage(Uri uriSource, RequestCachePolicy uriCachePolicy)
            : base(true) // Use base class virtuals
        {
            ArgumentNullException.ThrowIfNull(uriSource);
 
            BeginInit();
            UriSource = uriSource;
            UriCachePolicy = uriCachePolicy;
            EndInit();
        }
        
        #region ISupportInitialize
 
        /// <summary>
        /// Prepare the bitmap to accept initialize paramters.
        /// </summary>
        public void BeginInit()
        {
            WritePreamble();
            _bitmapInit.BeginInit();
        }
 
        /// <summary>
        /// Prevent the bitmap from accepting any further initialize paramters.
        /// </summary>
        public void EndInit()
        {
            WritePreamble();
            _bitmapInit.EndInit();
 
            if (UriSource == null && StreamSource == null)
            {
                throw new InvalidOperationException(SR.Format(SR.Image_NeitherArgument, "UriSource", "StreamSource"));
            }
 
            // If the Uri is relative, use delay creation as the BaseUri could be set at a later point
            if (UriSource != null && !UriSource.IsAbsoluteUri && CacheOption != BitmapCacheOption.OnLoad)
            {
                DelayCreation = true;
            }
 
            if (!DelayCreation && !CreationCompleted)
                FinalizeCreation();
        }
 
        #endregion
 
        #region IUriContext
 
        /// <summary>
        /// Provides the base uri of the current context.
        /// </summary>
        public Uri BaseUri
        {
            get
            {
                ReadPreamble();
                return _baseUri;
            }
            set
            {
                WritePreamble();
                if (!CreationCompleted && _baseUri != value)
                {
                    _baseUri = value;
                    WritePostscript();
                }
            }
        }
 
        #endregion
 
        #region Properties
 
        /// <summary>
        /// Returns if the BitmapFrame is downloading content
        /// </summary>
        public override bool IsDownloading
        {
            get
            {
                ReadPreamble();
                return _isDownloading;
            }
        }
 
        /// <summary>
        /// Get the Metadata of the bitmap
        /// </summary>
        public override ImageMetadata Metadata
        {
            get
            {
                throw new System.NotSupportedException(SR.Image_MetadataNotSupported);
            }
        }
 
 
        #endregion
 
        #region ToString
 
        /// <summary>
        /// Can serialze "this" to a string
        /// </summary>
        internal override bool CanSerializeToString()
        {
            ReadPreamble();
 
            return (
                // UriSource not null
                UriSource != null &&
 
                // But rest are defaults
                StreamSource == null &&
                SourceRect.IsEmpty &&
                DecodePixelWidth == 0 &&
                DecodePixelHeight == 0 &&
                Rotation == Rotation.Rotate0 &&
                CreateOptions == BitmapCreateOptions.None &&
                CacheOption == BitmapCacheOption.Default &&
                UriCachePolicy == null
               );
        }
 
        /// <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 (UriSource != null)
            {
                if (_baseUri != null)
                {
                    return BindUriHelper.UriToString(new Uri(_baseUri, UriSource));
                }
                else
                {
                    return BindUriHelper.UriToString(UriSource);
                }
            }
 
            return base.ConvertToString(format, provider);
        }
 
        #endregion
 
        private void ClonePrequel(BitmapImage otherBitmapImage)
        {
            BeginInit();
            _isDownloading = otherBitmapImage._isDownloading;
            _decoder = otherBitmapImage._decoder;
            _baseUri = otherBitmapImage._baseUri;
        }
 
        private void ClonePostscript(BitmapImage otherBitmapImage)
        {
            //
            // If the previous BitmapImage is downloading, we need to listen for those
            // events as our state will change based on when the download is done.
            //
            // NOTE -- This cannot happen in ClonePrequel as CopyCommon on BitmapSource
            //         is called after ClonePrequel which clones the event handlers,
            //         which will cause a reference onto itself and cause a Stack Overflow.
            //
            if (_isDownloading)
            {
                Debug.Assert(_decoder != null);
                _decoder.DownloadProgress += OnDownloadProgress;
                _decoder.DownloadCompleted += OnDownloadCompleted;
                _decoder.DownloadFailed += OnDownloadFailed;
            }
 
            EndInit();
        }
 
        /// Check the cache for an existing BitmapImage
        private BitmapImage CheckCache(Uri uri)
        {
            if (uri != null)
            {
                WeakReference bitmapWeakReference = ImagingCache.CheckImageCache(uri) as WeakReference;
 
                if (bitmapWeakReference != null)
                {
                    BitmapImage bitmapImage = (bitmapWeakReference.Target as BitmapImage);
 
                    // See if this bitmap was already in the image cache.
                    if (bitmapImage != null)
                    {
                        return bitmapImage;
                    }
                    else
                    {
                        ImagingCache.RemoveFromImageCache(uri);
                    }
                }
            }
 
            return null;
        }
 
        /// Insert BitmapImage in cache
        private void InsertInCache(Uri uri)
        {
            if (uri != null)
            {
                //
                // Keeping the image bits alive in the cache can bloat working set by 40-50mb in
                // common scenarios.  So, keep weak refereances in the cache so that we don't keep
                // bits around indefinitely.
                //
                // We currently get cache benefits if:
                //
                //    1. We have multiple references to the same bitmap on the same page
                //    2. A bitmap is referenced before a GC and the object is still alive
                //    3. The user explicit holds onto the bitmap object and keeps the cache alive
                //
                // Note that this cache is the in-memory cache for bitmap loads from disk.  If we miss,
                // we will likely hit the disk cache, so cache misses can be reasonable.  Downloads off the
                // network are cached to disk at another level and are unaffected by the weak references.
                //
 
                ImagingCache.AddToImageCache(uri, new WeakReference(this));
            }
        }
 
        ///
        /// Create the unmanaged resources
        ///
        internal override void FinalizeCreation()
        {
            _bitmapInit.EnsureInitializedComplete();
            Uri uri = UriSource;
            if (_baseUri != null)
                uri = new Uri(_baseUri, UriSource);
 
            if ((CreateOptions & BitmapCreateOptions.IgnoreImageCache) != 0)
            {
                ImagingCache.RemoveFromImageCache(uri);
            }
 
            BitmapImage bitmapImage = CheckCache(uri);
 
            if (bitmapImage != null &&
                bitmapImage.CheckAccess() &&
                bitmapImage.SourceRect.Equals(SourceRect) &&
                bitmapImage.DecodePixelWidth == DecodePixelWidth &&
                bitmapImage.DecodePixelHeight == DecodePixelHeight &&
                bitmapImage.Rotation == Rotation &&
                (bitmapImage.CreateOptions & BitmapCreateOptions.IgnoreColorProfile) ==
                (CreateOptions & BitmapCreateOptions.IgnoreColorProfile)
               )
            {
                _syncObject = bitmapImage.SyncObject;
                lock (_syncObject)
                {
                    WicSourceHandle = bitmapImage.WicSourceHandle;
                    IsSourceCached = bitmapImage.IsSourceCached;
                    _convertedDUCEPtr = bitmapImage._convertedDUCEPtr;
 
                    //
                    // We nee d to keep the strong reference to the cached image for a few reasons:
                    //
                    //    The application may release the original cached image and then keep a
                    //    reference to this image 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 image holds onto the file stream indirectly which if
                    //       collected can cause bad behavior if the entire image is not loaded into
                    //       memory.
                    //
                    _cachedBitmapImage = bitmapImage;
                }
                UpdateCachedSettings();
                return;
            }
 
            BitmapDecoder decoder = null;
            if (_decoder == null)
            {
                // Note: We do not want to insert in the cache if there is a chance that
                //       the decode pixel width/height may cause the decoder LOD to change
                decoder = BitmapDecoder.CreateFromUriOrStream(
                    _baseUri,
                    UriSource,
                    StreamSource,
                    CreateOptions & ~BitmapCreateOptions.DelayCreation,
                    BitmapCacheOption.None, // do not cache the frames since we will do that here
                    _uriCachePolicy,
                    false
                    );
 
                if (decoder.IsDownloading)
                {
                    _isDownloading = true;
                    _decoder = decoder;
                    decoder.DownloadProgress += OnDownloadProgress;
                    decoder.DownloadCompleted += OnDownloadCompleted;
                    decoder.DownloadFailed += OnDownloadFailed;
                }
                else
                {
                    Debug.Assert(decoder.SyncObject != null);
                }
            }
            else
            {
                // We already had a decoder, meaning we were downloading
                Debug.Assert(!_decoder.IsDownloading);
                decoder = _decoder;
                _decoder = null;
            }
 
            if (decoder.Frames.Count == 0)
            {
                throw new System.ArgumentException(SR.Image_NoDecodeFrames);
            }
 
            BitmapFrame frame = decoder.Frames[0];
            BitmapSource source = frame;
 
            Int32Rect sourceRect = SourceRect;
 
            if (sourceRect.X == 0 && sourceRect.Y == 0 &&
                sourceRect.Width == source.PixelWidth &&
                sourceRect.Height == source.PixelHeight)
            {
                sourceRect = Int32Rect.Empty;
            }
 
            if (!sourceRect.IsEmpty)
            {
                CroppedBitmap croppedSource = new CroppedBitmap();
                croppedSource.BeginInit();
                croppedSource.Source = source;
                croppedSource.SourceRect = sourceRect;
                croppedSource.EndInit();
 
                source = croppedSource;
                if (_isDownloading)
                {
                    // Unregister the download events because this is a dummy image. See comment below.
                    source.UnregisterDownloadEventSource();
                }
            }
 
            int finalWidth = DecodePixelWidth;
            int finalHeight = DecodePixelHeight;
 
            if (finalWidth == 0 && finalHeight == 0)
            {
                finalWidth = source.PixelWidth;
                finalHeight = source.PixelHeight;
            }
            else if (finalWidth == 0)
            {
                finalWidth = (source.PixelWidth * finalHeight) / source.PixelHeight;
            }
            else if (finalHeight == 0)
            {
                finalHeight = (source.PixelHeight * finalWidth) / source.PixelWidth;
            }
 
            if (finalWidth != source.PixelWidth || finalHeight != source.PixelHeight ||
                Rotation != Rotation.Rotate0)
            {
                TransformedBitmap transformedSource = new TransformedBitmap();
                transformedSource.BeginInit();
                transformedSource.Source = source;
 
                TransformGroup transformGroup = new TransformGroup();
 
                if (finalWidth != source.PixelWidth || finalHeight != source.PixelHeight)
                {
                    int oldWidth = source.PixelWidth;
                    int oldHeight = source.PixelHeight;
 
                    Debug.Assert(oldWidth > 0 && oldHeight > 0);
 
                    transformGroup.Children.Add(
                        new ScaleTransform(
                            (1.0*finalWidth)/ oldWidth,
                            (1.0*finalHeight)/oldHeight));
                }
 
                if (Rotation != Rotation.Rotate0)
                {
                    double rotation = 0.0;
 
                    switch (Rotation)
                    {
                        case Rotation.Rotate0:
                            rotation = 0.0;
                            break;
                        case Rotation.Rotate90:
                            rotation = 90.0;
                            break;
                        case Rotation.Rotate180:
                            rotation = 180.0;
                            break;
                        case Rotation.Rotate270:
                            rotation = 270.0;
                            break;
                        default:
                            Debug.Assert(false);
                            break;
                    }
 
                    transformGroup.Children.Add(new RotateTransform(rotation));
                }
 
                transformedSource.Transform = transformGroup;
 
                transformedSource.EndInit();
 
                source = transformedSource;
                if (_isDownloading)
                {
                    //
                    // If we're currently downloading, then the BitmapFrameDecode isn't actually
                    // the image, it's just a 1x1 placeholder. The chain we're currently building
                    // will be replaced with another chain once download completes, so there's no
                    // need to have this chain handle DownloadCompleted.
                    //
                    // Having this chain handle DownloadCompleted is actually a bad thing. Because
                    // the dummy is just 1x1, the TransformedBitmap we're building here will have
                    // a large scaling factor (to scale the image from 1x1 up to whatever
                    // DecodePixelWidth/Height specifies). When the TransformedBitmap receives
                    // DownloadCompleted from the BFD, it will call into WIC to create a new
                    // bitmap scaler using the same large scaling factor, which can produce a huge
                    // bitmap (since the BFD is now no longer 1x1). This problem is made worse if
                    // this BitmapImage has BitmapCacheOption.OnLoad, since that will put a
                    // CachedBitmap after the TransformedBitmap. When DownloadCompleted propagates
                    // from the TransformedBitmap down to the CachedBitmap, the CachedBitmap will
                    // call CreateBitmapFromSource using the TransformedBitmap, which calls
                    // CopyPixels on the huge TransformedBitmap. We want to avoid chewing up the
                    // CPU and the memory, so we unregister the download event handlers here.
                    //
                    source.UnregisterDownloadEventSource();
                }
            }
 
            //
            // If the original image has a color profile and IgnoreColorProfile is not one of the create options,
            // apply the profile so bits are color-corrected.
            //
            if ((CreateOptions & BitmapCreateOptions.IgnoreColorProfile) == 0 &&
                frame.ColorContexts != null &&
                frame.ColorContexts[0] != null &&
                frame.ColorContexts[0].IsValid && 
                source.Format.Format != PixelFormatEnum.Extended
                )
            {
                // NOTE: Never do this for a non-MIL pixel format, because the format converter has
                // special knowledge to deal with the profile
 
                PixelFormat duceFormat = BitmapSource.GetClosestDUCEFormat(source.Format, source.Palette);
                bool changeFormat = (source.Format != duceFormat);
                ColorContext destinationColorContext;
 
                // We need to make sure, we can actually create the ColorContext for the destination duceFormat
                // If the duceFormat is gray or scRGB, the following is not supported, so we cannot
                // create the ColorConvertedBitmap
                try
                {
                    destinationColorContext= new ColorContext(duceFormat);
                }
                catch (NotSupportedException)
                {
                    destinationColorContext = null;
                }
 
                if (destinationColorContext != null)
                {
                    bool conversionSuccess = false;
                    bool badColorContext = false;
 
                    // First try if the color converter can handle the source format directly
                    // Its possible that the color converter does not support certain pixelformats, so put a try/catch here.
                    try
                    {
                        ColorConvertedBitmap colorConvertedBitmap = new ColorConvertedBitmap(
                            source,
                            frame.ColorContexts[0],
                            destinationColorContext,
                            duceFormat
                            );
 
                        source = colorConvertedBitmap;
                        if (_isDownloading)
                        {
                            // Unregister the download events because this is a dummy image. See comment above.
                            source.UnregisterDownloadEventSource();
                        }
                        conversionSuccess = true;
                    }
                    catch (NotSupportedException)
                    {
                    }
                    catch (FileFormatException)
                    {
                        // If the file contains a bad color context, we catch the exception here
                        // and don't bother trying the color conversion below, since color transform isn't possible
                        // with the given color context.
                        badColorContext = true;
                    }
 
                    if (!conversionSuccess && !badColorContext && changeFormat)
                    {   // If the conversion failed, we first use
                        // a FormatConvertedBitmap, and then Color Convert that one...
                        FormatConvertedBitmap formatConvertedBitmap = new FormatConvertedBitmap(source, duceFormat, source.Palette, 0.0);
 
                        ColorConvertedBitmap colorConvertedBitmap = new ColorConvertedBitmap(
                            formatConvertedBitmap,
                            frame.ColorContexts[0],
                            destinationColorContext,
                            duceFormat
                            );
 
                        source = colorConvertedBitmap;
                        if (_isDownloading)
                        {
                            // Unregister the download events because this is a dummy image. See comment above.
                            source.UnregisterDownloadEventSource();
                        }
                    }
                }
            }
 
            if (CacheOption != BitmapCacheOption.None)
            {
                try
                {
                    // The bitmaps bits could be corrupt, and this will cause an exception if the CachedBitmap forces a decode.
                    CachedBitmap cachedSource = new CachedBitmap(source, CreateOptions & ~BitmapCreateOptions.DelayCreation, CacheOption);
                    source = cachedSource;
                    if (_isDownloading)
                    {
                        // Unregister the download events because this is a dummy image. See comment above.
                        source.UnregisterDownloadEventSource();
                    }
                }
                catch (Exception e)
                {
                    RecoverFromDecodeFailure(e);
                    CreationCompleted = true; // we're bailing out because the decode failed
                    return;
                }
            }
 
            // If CacheOption == OnLoad, no need to keep the stream around
            if (decoder != null && CacheOption == BitmapCacheOption.OnLoad)
            {
                decoder.CloseStream();
            }
            else if (CacheOption != BitmapCacheOption.OnLoad)
            {
                //ensure that we don't GC the source
                _finalSource = source;
            }
 
            WicSourceHandle = source.WicSourceHandle;
            IsSourceCached = source.IsSourceCached;
 
            CreationCompleted = true;
            UpdateCachedSettings();
 
            // Only insert in the imaging cache if download is complete
            if (!IsDownloading)
            {
                InsertInCache(uri);
            }
        }
 
        private void UriCachePolicyPropertyChangedHook(DependencyPropertyChangedEventArgs e)
        {
            if (!e.IsASubPropertyChange)
            {
                _uriCachePolicy = e.NewValue as RequestCachePolicy;
            }
        }
 
        private void UriSourcePropertyChangedHook(DependencyPropertyChangedEventArgs e)
        {
            if (!e.IsASubPropertyChange)
            {
                _uriSource = e.NewValue as Uri;
            }
        }
 
        private void StreamSourcePropertyChangedHook(DependencyPropertyChangedEventArgs e)
        {
            if (!e.IsASubPropertyChange)
            {
                _streamSource = e.NewValue as Stream;
            }
        }
 
        private void DecodePixelWidthPropertyChangedHook(DependencyPropertyChangedEventArgs e)
        {
            if (!e.IsASubPropertyChange)
            {
                _decodePixelWidth = (Int32)e.NewValue;
            }
        }
 
        private void DecodePixelHeightPropertyChangedHook(DependencyPropertyChangedEventArgs e)
        {
            if (!e.IsASubPropertyChange)
            {
                _decodePixelHeight = (Int32)e.NewValue;
            }
        }
 
        private void RotationPropertyChangedHook(DependencyPropertyChangedEventArgs e)
        {
            if (!e.IsASubPropertyChange)
            {
                _rotation = (Rotation)e.NewValue;
            }
        }
 
        private void SourceRectPropertyChangedHook(DependencyPropertyChangedEventArgs e)
        {
            if (!e.IsASubPropertyChange)
            {
                _sourceRect = (Int32Rect)e.NewValue;
            }
        }
 
        private void CreateOptionsPropertyChangedHook(DependencyPropertyChangedEventArgs e)
        {
            BitmapCreateOptions options = (BitmapCreateOptions)e.NewValue;
            _createOptions = options;
            DelayCreation = ((options & BitmapCreateOptions.DelayCreation) != 0);
        }
 
        private void CacheOptionPropertyChangedHook(DependencyPropertyChangedEventArgs e)
        {
            if (!e.IsASubPropertyChange)
            {
                _cacheOption = (BitmapCacheOption)e.NewValue;
            }
        }
 
        /// <summary>
        ///     Coerce UriCachePolicy
        /// </summary>
        private static object CoerceUriCachePolicy(DependencyObject d, object value)
        {
            BitmapImage image = (BitmapImage)d;
            if (!image._bitmapInit.IsInInit)
            {
                return image._uriCachePolicy;
            }
            else
            {
                return value;
            }
        }
 
        /// <summary>
        ///     Coerce UriSource
        /// </summary>
        private static object CoerceUriSource(DependencyObject d, object value)
        {
            BitmapImage image = (BitmapImage)d;
            if (!image._bitmapInit.IsInInit)
            {
                return image._uriSource;
            }
            else
            {
                return value;
            }
        }
 
        /// <summary>
        ///     Coerce StreamSource
        /// </summary>
        private static object CoerceStreamSource(DependencyObject d, object value)
        {
            BitmapImage image = (BitmapImage)d;
            if (!image._bitmapInit.IsInInit)
            {
                return image._streamSource;
            }
            else
            {
                return value;
            }
        }
 
        /// <summary>
        ///     Coerce DecodePixelWidth
        /// </summary>
        private static object CoerceDecodePixelWidth(DependencyObject d, object value)
        {
            BitmapImage image = (BitmapImage)d;
            if (!image._bitmapInit.IsInInit)
            {
                return image._decodePixelWidth;
            }
            else
            {
                return value;
            }
        }
 
        /// <summary>
        ///     Coerce DecodePixelHeight
        /// </summary>
        private static object CoerceDecodePixelHeight(DependencyObject d, object value)
        {
            BitmapImage image = (BitmapImage)d;
            if (!image._bitmapInit.IsInInit)
            {
                return image._decodePixelHeight;
            }
            else
            {
                return value;
            }
        }
 
        /// <summary>
        ///     Coerce Rotation
        /// </summary>
        private static object CoerceRotation(DependencyObject d, object value)
        {
            BitmapImage image = (BitmapImage)d;
            if (!image._bitmapInit.IsInInit)
            {
                return image._rotation;
            }
            else
            {
                return value;
            }
        }
 
        /// <summary>
        ///     Coerce SourceRect
        /// </summary>
        private static object CoerceSourceRect(DependencyObject d, object value)
        {
            BitmapImage image = (BitmapImage)d;
            if (!image._bitmapInit.IsInInit)
            {
                return image._sourceRect;
            }
            else
            {
                return value;
            }
        }
 
        /// <summary>
        ///     Coerce CreateOptions
        /// </summary>
        private static object CoerceCreateOptions(DependencyObject d, object value)
        {
            BitmapImage image = (BitmapImage)d;
            if (!image._bitmapInit.IsInInit)
            {
                return image._createOptions;
            }
            else
            {
                return value;
            }
        }
 
        /// <summary>
        ///     Coerce CacheOption
        /// </summary>
        private static object CoerceCacheOption(DependencyObject d, object value)
        {
            BitmapImage image = (BitmapImage)d;
            if (!image._bitmapInit.IsInInit)
            {
                return image._cacheOption;
            }
            else
            {
                return value;
            }
        }
 
        /// Called when decoder finishes download
        private void OnDownloadCompleted(object sender, EventArgs e)
        {
            _isDownloading = false;
 
            //
            // Unhook the decoders download events
            //
            _decoder.DownloadProgress -= OnDownloadProgress;
            _decoder.DownloadCompleted -= OnDownloadCompleted;
            _decoder.DownloadFailed -= OnDownloadFailed;
            
            if ((CreateOptions & BitmapCreateOptions.DelayCreation) != 0)
            {
                DelayCreation = true;
            }
            else
            {
                FinalizeCreation();
 
                // Trigger a update of the UCE resource
                _needsUpdate = true;
                RegisterForAsyncUpdateResource();
                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)
        {
            _isDownloading = false;
 
            //
            // Unhook the decoders download events
            //
            _decoder.DownloadProgress -= OnDownloadProgress;
            _decoder.DownloadCompleted -= OnDownloadCompleted;
            _decoder.DownloadFailed -= OnDownloadFailed;
            
            _failedEvent.InvokeEvents(this, e);
        }
 
        #region Data Members
 
        // Base Uri from IUriContext
        private Uri _baseUri;
 
        /// Is downloading content
        private bool _isDownloading;
 
        /// Bitmap Decoder
        private BitmapDecoder _decoder;
 
        private RequestCachePolicy _uriCachePolicy;
 
        private Uri _uriSource;
 
        private Stream _streamSource;
 
        private Int32 _decodePixelWidth;
 
        private Int32 _decodePixelHeight;
 
        private Rotation _rotation;
 
        private Int32Rect _sourceRect;
 
        private BitmapCreateOptions _createOptions;
 
        private BitmapCacheOption _cacheOption;
 
        // used to ensure the source isn't GC'd
        private BitmapSource _finalSource;
 
        private BitmapImage _cachedBitmapImage;
 
        #endregion
    }
 
    #endregion // BitmapImage
}