File: Serialization\ImageSourceTypeConverter.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\ReachFramework\ReachFramework.csproj (ReachFramework)
// 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.
 
/*++
 
    Abstract:
        This file implements the ImageSourceTypeConverter
        used by the Xps Serialization APIs for serializing
        images to a Xps package.
 
--*/
using System;
using System.Net;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.IO.Packaging;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Xps.Packaging;
using System.Security;
using MS.Internal.ReachFramework;
using System.Windows.Markup;
using MS.Internal.IO.Packaging;
using MS.Utility;
 
namespace System.Windows.Xps.Serialization
{
    /// <summary>
    /// This class implements a type converter for converting
    /// images to Uris.  It handles the writing of the image
    /// to a Xps package and returns a package URI to the
    /// caller.  It also handles reading an image from a
    /// Xps package given a Uri.
    /// </summary>
    public class ImageSourceTypeConverter : ExpandableObjectConverter
    {
        #region Public overrides for ExpandableObjectConverted
 
        /// <summary>
        /// Returns whether this converter can convert an object
        /// of the given type to the type of this converter.
        /// </summary>
        /// <param name="context">
        /// An ITypeDescriptorContext that provides a format context.
        /// </param>
        /// <param name="sourceType">
        /// A Type that represents the type you want to convert from.
        /// </param>
        /// <returns>
        /// true if this converter can perform the conversion;
        /// otherwise, false.
        /// </returns>
        public
        override
        bool
        CanConvertFrom(
            ITypeDescriptorContext      context,
            Type                        sourceType
            )
        {
            return IsSupportedType(sourceType);
        }
 
        /// <summary>
        /// Returns whether this converter can convert the object
        /// to the specified type.
        /// </summary>
        /// <param name="context">
        /// An ITypeDescriptorContext that provides a format context.
        /// </param>
        /// <param name="destinationType">
        /// A Type that represents the type you want to convert to.
        /// </param>
        /// <returns>
        /// true if this converter can perform the conversion;
        /// otherwise, false.
        /// </returns>
        public
        override
        bool
        CanConvertTo(
            ITypeDescriptorContext      context,
            Type                        destinationType
            )
        {
            return IsSupportedType(destinationType);
        }
 
        /// <summary>
        /// Converts the given value to the type of this converter.
        /// </summary>
        /// <param name="context">
        /// An ITypeDescriptorContext that provides a format context.
        /// </param>
        /// <param name="culture">
        /// The CultureInfo to use as the current culture.
        /// </param>
        /// <param name="value">
        /// The Object to convert.
        /// </param>
        /// <returns>
        /// An Object that represents the converted value.
        /// </returns>
        public
        override
        object
        ConvertFrom(
            ITypeDescriptorContext              context,
            System.Globalization.CultureInfo    culture,
            object                              value
            )
        {
            ArgumentNullException.ThrowIfNull(value);
 
            if (!IsSupportedType(value.GetType()))
            {
                throw new NotSupportedException(SR.Converter_ConvertFromNotSupported);
            }
 
            throw new NotImplementedException();
        }
 
        /// <summary>
        /// Converts the given value object to the specified type,
        /// using the arguments.
        /// </summary>
        /// <param name="context">
        /// An ITypeDescriptorContext that provides a format context.
        /// </param>
        /// <param name="culture">
        /// A CultureInfo object. If null is passed, the current
        /// culture is assumed.
        /// </param>
        /// <param name="value">
        /// The Object to convert.
        /// </param>
        /// <param name="destinationType">
        /// The Type to convert the value parameter to.
        /// </param>
        /// <returns>
        /// The Type to convert the value parameter to.
        /// </returns>
        public
        override
        object
        ConvertTo(
            ITypeDescriptorContext              context,
            System.Globalization.CultureInfo    culture,
            object                              value,
            Type                                destinationType
            )
        {
            Toolbox.EmitEvent(EventTrace.Event.WClientDRXConvertImageBegin);
 
            ArgumentNullException.ThrowIfNull(context);
            if (!IsSupportedType(destinationType))
            {
                throw new NotSupportedException(SR.Converter_ConvertToNotSupported);
            }
 
            //
            // Check that we have a valid BitmapSource instance.
            //
            BitmapSource bitmapSource = (BitmapSource)value;
            if (bitmapSource == null)
            {
                throw new ArgumentException(SR.Format(SR.MustBeOfType, "value", "BitmapSource"));
            }
 
            //
            // Get the current serialization manager.
            //
            PackageSerializationManager manager = (PackageSerializationManager)context.GetService(typeof(XpsSerializationManager));
 
            //Get the image Uri if it has already been serialized
            Uri imageUri = GetBitmapSourceFromImageTable(manager, bitmapSource);
 
            //
            // Get the current page image cache
            //
            Dictionary<int, Uri> currentPageImageTable = manager.ResourcePolicy.CurrentPageImageTable;
            if (imageUri != null)
            {
                int uriHashCode = imageUri.GetHashCode();
                if(!currentPageImageTable.ContainsKey(uriHashCode))
                {
                   //
                   // Also, add a relationship for the current page to this image
                   // resource.  This is needed to conform with Xps specification.
                   //
                   manager.AddRelationshipToCurrentPage(imageUri, XpsS0Markup.ResourceRelationshipName);
                   currentPageImageTable.Add(uriHashCode, imageUri);
                }
            }
            else
            {
                //
                // This image as never serialized before so we will do it now.
                // Retrieve the image serialization service from the resource policy
                //
                IServiceProvider resourceServiceProvider = manager.ResourcePolicy;
 
                XpsImageSerializationService imageService = (XpsImageSerializationService)resourceServiceProvider.GetService(typeof(XpsImageSerializationService));
                if (imageService == null)
                {
                    throw new XpsSerializationException(SR.ReachSerialization_NoImageService);
                }
 
                //
                // Obtain a valid encoder for the image.
                //
                BitmapEncoder encoder = imageService.GetEncoder(bitmapSource);
                string imageMimeType = GetImageMimeType(encoder);
 
                bool isSupportedMimeType = imageService.IsSupportedMimeType(bitmapSource);
 
                //
                // Acquire a writable stream from the serialization manager and encode
                // and serialize the image into the stream.
                //
                XpsResourceStream resourceStream = manager.AcquireResourceStream(typeof(BitmapSource), imageMimeType);
                bool bCopiedStream = false;
 
                BitmapFrame bitmapFrame = bitmapSource as BitmapFrame;
 
                if (isSupportedMimeType &&
                    bitmapFrame != null &&
                    bitmapFrame.Decoder != null
                    )
                {
                    BitmapDecoder decoder = bitmapFrame.Decoder;
                    try
                    {
                        Uri sourceUri = new Uri(decoder.ToString());
                        using (Stream srcStream = MS.Internal.WpfWebRequestHelper.CreateRequestAndGetResponseStream(sourceUri))
                        {
                            CopyImageStream(srcStream, resourceStream.Stream);
                        }
                        bCopiedStream = true;
                    }
                    catch (UriFormatException)
                    {
                        //the uri was not valid we will re-encode the image below
                    }
                    catch (WebException)
                    {
                        //Web Request failed we will re-encode the image below
                    }
                }
 
                if (!bCopiedStream)
                {
                    Stream stream = new MemoryStream();
                    ReEncodeImage(bitmapSource, encoder, stream);
                    stream.Position = 0;
                    CopyImageStream(stream, resourceStream.Stream);
                }
 
                //
                // Make sure to commit the resource stream by releasing it.
                //
                imageUri = resourceStream.Uri;
                manager.ReleaseResourceStream(typeof(BitmapSource));
 
                AddBitmapSourceToImageTables(manager, imageUri);
            }
 
            Toolbox.EmitEvent(EventTrace.Event.WClientDRXConvertImageEnd);
 
            return imageUri;
        }
 
        /// <summary>
        /// Gets a collection of properties for the type of object
        /// specified by the value parameter.
        /// </summary>
        /// <param name="context">
        /// An ITypeDescriptorContext that provides a format context.
        /// </param>
        /// <param name="value">
        /// An Object that specifies the type of object to get the
        /// properties for.
        /// </param>
        /// <param name="attributes">
        /// An array of type Attribute that will be used as a filter.
        /// </param>
        /// <returns>
        /// A PropertyDescriptorCollection with the properties that are
        /// exposed for the component, or null if there are no properties.
        /// </returns>
        public
        override
        PropertyDescriptorCollection
        GetProperties(
            ITypeDescriptorContext      context,
            object                      value,
            Attribute[]                 attributes
            )
        {
            throw new NotImplementedException();
        }
 
        #endregion Public overrides for ExpandableObjectConverted
 
        #region Private static helper methods
 
        /// <summary>
        /// Looks up the type in a table to determine
        /// whether this type is supported by this
        /// class.
        /// </summary>
        /// <param name="type">
        /// Type to lookup in table.
        /// </param>
        /// <returns>
        /// True is supported; otherwise false.
        /// </returns>
        private
        static
        bool
        IsSupportedType(
            Type            type
            )
        {
            return typeof(Uri).Equals(type);
        }
 
        private
        static
        void
        CopyImageStream( Stream sourceStream, Stream destinationStream )
        {
            byte[] buffer= new byte[_readBlockSize];
            int bytesRead = PackagingUtilities.ReliableRead( sourceStream, buffer, 0, _readBlockSize);
            while( bytesRead > 0 )
            {
                destinationStream.Write(buffer, 0, bytesRead);
                bytesRead = PackagingUtilities.ReliableRead( sourceStream, buffer, 0, _readBlockSize);
            }
        }
 
        private
        static
        void
        ReEncodeImage(BitmapSource bitmapSource, BitmapEncoder encoder, Stream stream )
        {
            BitmapFrame bitmapFrame = null;
            //
            // The uri the BitmapFrame.Create will use is null since it is accessing  metadata at
            // construction and its uri is still null
            //
 
            // If bitmapSource is indexed, has a color palette and transparency (e.g. transparent GIF)
            // PNG conversion may lose color or transparency or both information
            // To avoid this we convert all paletted bitmapSources to the 32 bit per pixel bgra format
            if (bitmapSource != null
                && bitmapSource.Palette != null
                && bitmapSource.Palette.Colors != null
                && bitmapSource.Palette.Colors.Count > 0)
            {
                bitmapSource = new FormatConvertedBitmap(bitmapSource, PixelFormats.Bgra32, null, 0.0);
            }
 
            bitmapFrame = BitmapFrame.Create(bitmapSource);
 
            encoder.Frames.Add(bitmapFrame);
 
            encoder.Save(stream);
}
 
        /// <summary>
        /// Calculates a Crc32 value for a given BitmapSource.
        /// </summary>
        /// <param name="bitmapSource">
        /// BitmapSource containing image data to calculate
        /// the Crc32 value for.
        /// </param>
        /// <returns>
        /// A 32-bit unsigned integer Crc32 value.
        /// </returns>
        private
        static
        UInt32
        CalculateImageCrc32(
            BitmapSource    bitmapSource
            )
        {
            Crc32 crc32 = new Crc32();
 
            int width = bitmapSource.PixelWidth;
            int height = bitmapSource.PixelHeight;
            int stride = (width * bitmapSource.Format.BitsPerPixel + 7) / 8;
            Int32Rect rect = new Int32Rect(0, 0, width, 1);
            byte[] pixels = new byte[stride];
 
 
            for (int i = 0; i < height; i += 1)
            {
                bitmapSource.CriticalCopyPixels(rect, pixels, stride, 0);
                rect.Y++;
 
                crc32.AddData(pixels);
            }
 
            return crc32.Crc32Value;
        }
 
 
        private
        static
        string
        GetImageMimeType(
            BitmapEncoder encoder
        )
        {
            string mimetypes;
            string imageMimeType = "image/unknown";
            //
            // To determine the mime-type of the image we just grab
            // the first one supported by this encoder and use that.
            //
            mimetypes = encoder.CodecInfo.MimeTypes;
            int comma = mimetypes.IndexOf(',');
            if (comma != -1)
            {
                imageMimeType = mimetypes.Substring(0, comma);
            }
            else
            {
                imageMimeType = mimetypes;
            }
            return imageMimeType;
        }
 
 
        private
        Uri
        GetBitmapSourceFromImageTable(PackageSerializationManager manager, BitmapSource bitmapSource)
        {
            Uri imageUri = null;
 
            //Initialize cache values
            _uriHashValue = 0;
            _crc32HashValue = 0;
 
            BitmapFrame bitmapFrame = bitmapSource as BitmapFrame;
 
            //Use the Uri hash table if we have a bitmap with a Uri
            if (bitmapFrame != null &&
                bitmapFrame.Decoder != null)
            {
                String sourceUri  = bitmapFrame.Decoder.ToString();
                if (sourceUri != null)
                {
                    _uriHashValue = sourceUri.GetHashCode();
 
                    Dictionary<int, Uri> imageUriHashTable = manager.ResourcePolicy.ImageUriHashTable;
                    if (imageUriHashTable.ContainsKey(_uriHashValue))
                    {
                        imageUri = imageUriHashTable[_uriHashValue];
                    }
                }
            }
 
            //Checking the UriHash for zero tells us if the uri of the bitmap was checked and if it returned a valid Uri
            if (_uriHashValue == 0)
            {
                //
                // Calculate the image Crc32 value.  This is used as a key
                // into the image cache.
                //
                _crc32HashValue = CalculateImageCrc32(bitmapSource);
 
                //
                // Get the current image cache
                //
                Dictionary<UInt32, Uri> imageCrcTable = manager.ResourcePolicy.ImageCrcTable;
 
                //
                // The image has already been cached (and therefore serialized).
                // No need to serialize it again so we just return the Uri in the
                // package where the original was serialized. For that Uri returned
                // a relationship is only created if this has not been included on
                // the current page before.
                //
                if (imageCrcTable.ContainsKey(_crc32HashValue))
                {
                    imageUri = imageCrcTable[_crc32HashValue];
                }
            }
 
            return imageUri;
        }
 
        private
        void
        AddBitmapSourceToImageTables(PackageSerializationManager manager, Uri imageUri)
        {
            if (_uriHashValue != 0)
            {
                manager.ResourcePolicy.ImageUriHashTable.Add(_uriHashValue,imageUri);
                _uriHashValue = 0;
            }
            else
            {
                manager.ResourcePolicy.ImageCrcTable.Add(_crc32HashValue, imageUri);
                _crc32HashValue = 0;
            }
 
            manager.ResourcePolicy.CurrentPageImageTable.Add(imageUri.GetHashCode(), imageUri);
        }
 
        #endregion Private static helper methods
 
        #region Private static data
        
        private static readonly int _readBlockSize = 1048576; //1MB
 
        /// <summary>
        /// Cached hash values for image tables
        /// </summary>
        private int _uriHashValue;
        private UInt32 _crc32HashValue;
        #endregion Private static data
    }
}