File: System\Windows\Media\Imaging\BitmapEncoder.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections.ObjectModel;
using MS.Internal;
using System.Windows.Threading;
using System.Runtime.InteropServices;
using MS.Win32.PresentationCore;
 
#pragma warning disable 1634, 1691  // suppressing PreSharp warnings
 
namespace System.Windows.Media.Imaging
{
    #region PROPBAG2
 
    [StructLayout(LayoutKind.Sequential, Pack=1)]
    internal struct PROPBAG2
    {
        internal UInt32 dwType;
        internal ushort vt;
        internal ushort cfType;
        internal IntPtr dwHint;
 
        internal IntPtr pstrName; //this is string array
 
        internal Guid clsid;
 
        internal void Init(String name)
        {
            pstrName = Marshal.StringToCoTaskMemUni(name);
        }
 
        internal void Clear()
        {
            Marshal.FreeCoTaskMem(pstrName);
            pstrName = IntPtr.Zero;
        }
    }
 
    #endregion
 
    #region BitmapEncoder
 
    /// <summary>
    /// BitmapEncoder collects a set of frames (BitmapSource's) with their associated
    /// thumbnails and saves them to a specified stream.  In addition
    /// to frame-specific thumbnails, there can also be an bitmap-wide
    /// (global) thumbnail, if the codec supports it.
    /// </summary>
    public abstract class BitmapEncoder : DispatcherObject
    {
        #region Constructors
 
        /// <summary>
        /// Constructor.
        /// </summary>
        protected BitmapEncoder()
        {
        }
 
        /// <summary>
        /// Internal Constructor.
        /// </summary>
        internal BitmapEncoder(bool isBuiltIn)
        {
            _isBuiltIn = isBuiltIn;
        }
 
        /// <summary>
        /// Creates a BitmapEncoder from a container format Guid
        /// </summary>
        /// <param name="containerFormat">Container format for the codec</param>
        public static BitmapEncoder Create(Guid containerFormat)
        {
            if (containerFormat == Guid.Empty)
            {
                throw new ArgumentException(
                    SR.Format(SR.Image_GuidEmpty, "containerFormat"),
                    "containerFormat"
                    );
            }
            else if (containerFormat == MILGuidData.GUID_ContainerFormatBmp)
            {
                return new BmpBitmapEncoder();
            }
            else if (containerFormat == MILGuidData.GUID_ContainerFormatGif)
            {
                return new GifBitmapEncoder();
            }
            else if (containerFormat == MILGuidData.GUID_ContainerFormatJpeg)
            {
                return new JpegBitmapEncoder();
            }
            else if (containerFormat == MILGuidData.GUID_ContainerFormatPng)
            {
                return new PngBitmapEncoder();
            }
            else if (containerFormat == MILGuidData.GUID_ContainerFormatTiff)
            {
                return new TiffBitmapEncoder();
            }
            else if (containerFormat == MILGuidData.GUID_ContainerFormatWmp)
            {
                return new WmpBitmapEncoder();
            }
            else
            {
                return new UnknownBitmapEncoder(containerFormat);
            }
        }
 
        #endregion
 
        #region Public Properties
 
        /// <summary>
        /// Set or get the bitmap's color profile.
        /// </summary>
        public virtual ReadOnlyCollection<ColorContext> ColorContexts
        {
            get
            {
                VerifyAccess();
                EnsureBuiltIn();
                return _readOnlycolorContexts;
            }
            set
            {
                VerifyAccess();
                EnsureBuiltIn();
 
                ArgumentNullException.ThrowIfNull(value);
 
                if (!_supportsColorContext)
                {
                    throw new InvalidOperationException(SR.Image_EncoderNoColorContext);
                }
 
                _readOnlycolorContexts = value;
            }
        }
 
        /// <summary>
        /// Set or get the bitmap's global embedded thumbnail.
        /// </summary>
        public virtual BitmapSource Thumbnail
        {
            get
            {
                VerifyAccess();
                EnsureBuiltIn();
                return _thumbnail;
            }
            set
            {
                VerifyAccess();
                EnsureBuiltIn();
 
                ArgumentNullException.ThrowIfNull(value);
 
                if (!_supportsGlobalThumbnail)
                {
                    throw new InvalidOperationException(SR.Image_EncoderNoGlobalThumbnail);
                }
 
                _thumbnail = value;
            }
        }
 
        /// <summary>
        /// Set or get the bitmap's global embedded metadata.
        /// </summary>
        public virtual BitmapMetadata Metadata
        {
            get
            {
                VerifyAccess();
                EnsureBuiltIn();
                EnsureMetadata(true);
 
                return _metadata;
            }
            set
            {
                VerifyAccess();
                EnsureBuiltIn();
 
                ArgumentNullException.ThrowIfNull(value);
 
                if (value.GuidFormat != ContainerFormat)
                {
                    throw new InvalidOperationException(SR.Image_MetadataNotCompatible);
                }
 
                if (!_supportsGlobalMetadata)
                {
                    throw new InvalidOperationException(SR.Image_EncoderNoGlobalMetadata);
                }
 
                _metadata = value;
            }
        }
 
        /// <summary>
        /// Set or get the bitmap's global preview
        /// </summary>
        public virtual BitmapSource Preview
        {
            get
            {
                VerifyAccess();
                EnsureBuiltIn();
                return _preview;
            }
            set
            {
                VerifyAccess();
                EnsureBuiltIn();
 
                ArgumentNullException.ThrowIfNull(value);
 
                if (!_supportsPreview)
                {
                    throw new InvalidOperationException(SR.Image_EncoderNoPreview);
                }
 
                _preview = value;
            }
        }
 
        /// <summary>
        /// The info that identifies this codec.
        /// </summary>
        public virtual BitmapCodecInfo CodecInfo
        {
            get
            {
                VerifyAccess();
                EnsureBuiltIn();
                EnsureUnmanagedEncoder();
 
                // There should always be a codec info.
                if (_codecInfo == null)
                {
                    SafeMILHandle /* IWICBitmapEncoderInfo */ codecInfoHandle =  new SafeMILHandle();
 
                    HRESULT.Check(UnsafeNativeMethods.WICBitmapEncoder.GetEncoderInfo(
                        _encoderHandle,
                        out codecInfoHandle
                        ));
 
                    _codecInfo = new BitmapCodecInfoInternal(codecInfoHandle);
                }
 
                return _codecInfo;
            }
        }
 
        /// <summary>
        /// Provides access to this bitmap's palette
        /// </summary>
        public virtual BitmapPalette Palette
        {
            get
            {
                VerifyAccess();
                EnsureBuiltIn();
                return _palette;
            }
            set
            {
                VerifyAccess();
                EnsureBuiltIn();
 
                ArgumentNullException.ThrowIfNull(value);
 
                _palette = value;
            }
        }
 
        /// <summary>
        /// Access to the individual frames.
        /// </summary>
        public virtual IList<BitmapFrame> Frames
        {
            get
            {
                VerifyAccess();
                EnsureBuiltIn();
                if (_frames == null)
                {
                    _frames = new List<BitmapFrame>(0);
                }
 
                return _frames;
            }
            set
            {
                VerifyAccess();
                EnsureBuiltIn();
 
                ArgumentNullException.ThrowIfNull(value);
 
                _frames = value;
            }
        }
 
        #endregion
 
        #region Public Methods
 
        /// <summary>
        /// Save (encode) the bitmap to the specified stream.
        /// </summary>
        /// <param name="stream">Stream to save into</param>
        public virtual void Save(System.IO.Stream stream)
        {
            VerifyAccess();
            EnsureBuiltIn();
            EnsureUnmanagedEncoder();
 
            // No-op to get rid of build error
            if (_encodeState == EncodeState.None)
            {
            }
 
            if (_hasSaved)
            {
                throw new InvalidOperationException(SR.Image_OnlyOneSave);
            }
 
            if (_frames == null)
            {
                throw new System.NotSupportedException(SR.Format(SR.Image_NoFrames, null));
            }
 
            int count = _frames.Count;
            if (count <= 0)
            {
                throw new System.NotSupportedException(SR.Format(SR.Image_NoFrames, null));
            }
 
            IntPtr comStream = IntPtr.Zero;
            SafeMILHandle encoderHandle = _encoderHandle;
 
            try
            {
                comStream = StreamAsIStream.IStreamFrom(stream);
 
                // does this addref the stream?
                HRESULT.Check(UnsafeNativeMethods.WICBitmapEncoder.Initialize(
                    encoderHandle,
                    comStream,
                    WICBitmapEncodeCacheOption.WICBitmapEncodeNoCache
                    ));
 
                // Helpful for debugging stress and remote dumps
                _encodeState = EncodeState.EncoderInitialized;
 
                // Save global thumbnail if any.
                if (_thumbnail != null)
                {
                    Debug.Assert(_supportsGlobalThumbnail);
                    SafeMILHandle thumbnailBitmapSource = _thumbnail.WicSourceHandle;
 
                    lock (_thumbnail.SyncObject)
                    {
                        HRESULT.Check(UnsafeNativeMethods.WICBitmapEncoder.SetThumbnail(
                            encoderHandle,
                            thumbnailBitmapSource
                            ));
 
                        // Helpful for debugging stress and remote dumps
                        _encodeState = EncodeState.EncoderThumbnailSet;
                    }
                }
 
                // Save global palette if any.
                if (_palette != null && _palette.Colors.Count > 0)
                {
                    SafeMILHandle paletteHandle = _palette.InternalPalette;
 
                    HRESULT.Check(UnsafeNativeMethods.WICBitmapEncoder.SetPalette(
                        encoderHandle,
                        paletteHandle
                        ));
 
                    // Helpful for debugging stress and remote dumps
                    _encodeState = EncodeState.EncoderPaletteSet;
                }
 
                // Save global metadata if any.
                if (_metadata != null && _metadata.GuidFormat == ContainerFormat)
                {
                    Debug.Assert(_supportsGlobalMetadata);
 
                    EnsureMetadata(false);
 
                    if (_metadata.InternalMetadataHandle != _metadataHandle)
                    {
                        PROPVARIANT propVar = new PROPVARIANT();
 
                        try
                        {
                            propVar.Init(_metadata);
 
                            lock (_metadata.SyncObject)
                            {
                                HRESULT.Check(UnsafeNativeMethods.WICMetadataQueryWriter.SetMetadataByName(
                                    _metadataHandle,
                                    "/",
                                    ref propVar
                                    ));
                            }
                        }
                        finally
                        {
                            propVar.Clear();
                        }
                    }
                }
 
                for (int i = 0; i < count; i++)
                {
                    SafeMILHandle frameEncodeHandle = new SafeMILHandle();
                    SafeMILHandle encoderOptions = new SafeMILHandle();
                    HRESULT.Check(UnsafeNativeMethods.WICBitmapEncoder.CreateNewFrame(
                        encoderHandle,
                        out frameEncodeHandle,
                        out encoderOptions
                        ));
 
                    // Helpful for debugging stress and remote dumps
                    _encodeState = EncodeState.EncoderCreatedNewFrame;
                    _frameHandles.Add(frameEncodeHandle);
 
                    SaveFrame(frameEncodeHandle, encoderOptions, _frames[i]);
 
                    // If multiple frames are not supported, break out
                    if (!_supportsMultipleFrames)
                    {
                        break;
                    }
                }
 
                // Now let the encoder know we are done encoding the file.
                HRESULT.Check(UnsafeNativeMethods.WICBitmapEncoder.Commit(encoderHandle));
 
                // Helpful for debugging stress and remote dumps
                _encodeState = EncodeState.EncoderCommitted;
            }
            finally
            {
                UnsafeNativeMethods.MILUnknown.ReleaseInterface(ref comStream);
            }
 
            _hasSaved = true;
        }
 
        #endregion
 
        #region Internal Properties / Methods
 
        /// <summary>
        /// Returns the container format for this encoder
        /// </summary>
        internal virtual Guid ContainerFormat
        {
            get
            {
                return Guid.Empty;
            }
        }
 
        /// <summary>
        /// Returns whether metadata is fixed size or not.
        /// </summary>
        internal virtual bool IsMetadataFixedSize
        {
            get
            {
                return false;
            }
        }
 
        /// <summary>
        /// Setups the encoder and other properties before encoding each frame
        /// </summary>
        internal virtual void SetupFrame(SafeMILHandle frameEncodeHandle, SafeMILHandle encoderOptions)
        {
            throw new NotImplementedException();
        }
 
        #endregion
 
        #region Private Methods
 
        /// <summary>
        /// Checks to see if encoder is built in.
        /// </summary>
        private void EnsureBuiltIn()
        {
            if (!_isBuiltIn)
            {
                throw new NotImplementedException();
            }
        }
 
        /// <summary>
        /// Checks to see if encoder has built-in metadata.
        /// </summary>
        private void EnsureMetadata(bool createBitmapMetadata)
        {
            if (!_supportsGlobalMetadata)
            {
                return;
            }
 
            if (_metadataHandle == null)
            {
                SafeMILHandle /* IWICMetadataQueryWriter */ metadataHandle = new SafeMILHandle();
 
                int hr = UnsafeNativeMethods.WICBitmapEncoder.GetMetadataQueryWriter(
                    _encoderHandle,
                    out metadataHandle
                    );
 
                if (hr == (int)WinCodecErrors.WINCODEC_ERR_UNSUPPORTEDOPERATION)
                {
                    _supportsGlobalMetadata = false;
                    return;
                }
                HRESULT.Check(hr);
 
                _metadataHandle = metadataHandle;
            }
 
            if (createBitmapMetadata &&
                _metadata == null &&
                _metadataHandle != null)
            {
                _metadata = new BitmapMetadata(_metadataHandle, false, IsMetadataFixedSize, _metadataHandle);
            }
        }
 
        /// <summary>
        /// Creates the unmanaged encoder object
        /// </summary>
        private void EnsureUnmanagedEncoder()
        {
            if (_encoderHandle == null)
            {
                using (FactoryMaker myFactory = new FactoryMaker())
                {
                    SafeMILHandle encoderHandle = null;
 
                    Guid vendorMicrosoft = new Guid(MILGuidData.GUID_VendorMicrosoft);
                    Guid containerFormat = ContainerFormat;
 
                    HRESULT.Check(UnsafeNativeMethods.WICImagingFactory.CreateEncoder(
                                myFactory.ImagingFactoryPtr,
                                ref containerFormat,
                                ref vendorMicrosoft,
                                out encoderHandle
                                ));
 
                    _encoderHandle = encoderHandle;
                }
            }
        }
 
        /// <summary>
        /// Save the frame
        /// </summary>
        private void SaveFrame(SafeMILHandle frameEncodeHandle, SafeMILHandle encoderOptions, BitmapFrame frame)
        {
            SetupFrame(frameEncodeHandle, encoderOptions);
 
            // Helpful for debugging stress and remote dumps
            _encodeState = EncodeState.FrameEncodeInitialized;
 
            // Set the size
            HRESULT.Check(UnsafeNativeMethods.WICBitmapFrameEncode.SetSize(
                frameEncodeHandle,
                frame.PixelWidth,
                frame.PixelHeight
                ));
 
            // Helpful for debugging stress and remote dumps
            _encodeState = EncodeState.FrameEncodeSizeSet;
 
            // Set the resolution
            double dpiX = frame.DpiX;
            double dpiY = frame.DpiY;
 
            if (dpiX <= 0)
            {
                dpiX = 96;
            }
            if (dpiY <= 0)
            {
                dpiY = 96;
            }
 
            HRESULT.Check(UnsafeNativeMethods.WICBitmapFrameEncode.SetResolution(
                frameEncodeHandle,
                dpiX,
                dpiY
                ));
 
            // Helpful for debugging stress and remote dumps
            _encodeState = EncodeState.FrameEncodeResolutionSet;
 
            if (_supportsFrameThumbnails)
            {
                // Set the thumbnail.
                BitmapSource thumbnail = frame.Thumbnail;
 
                if (thumbnail != null)
                {
                    SafeMILHandle thumbnailHandle = thumbnail.WicSourceHandle;
 
                    lock (thumbnail.SyncObject)
                    {
                        HRESULT.Check(UnsafeNativeMethods.WICBitmapFrameEncode.SetThumbnail(
                            frameEncodeHandle,
                            thumbnailHandle
                            ));
 
                        // Helpful for debugging stress and remote dumps
                        _encodeState = EncodeState.FrameEncodeThumbnailSet;
                    }
                }
            }
 
            // if the source has been color corrected, we want to use a corresponding color profile
            if (frame._isColorCorrected)
            {
                ColorContext colorContext = new ColorContext(frame.Format);
                IntPtr[] colorContextPtrs = new IntPtr[1] { colorContext.ColorContextHandle.DangerousGetHandle() };
 
                int hr = UnsafeNativeMethods.WICBitmapFrameEncode.SetColorContexts(
                    frameEncodeHandle,
                    1,
                    colorContextPtrs
                    );
 
                // It's possible that some encoders may not support color contexts so don't check hr
                if (hr == HRESULT.S_OK)
                {
                    // Helpful for debugging stress and remote dumps
                    _encodeState = EncodeState.FrameEncodeColorContextsSet;
                }
            }
            // if the caller has explicitly provided color contexts, add them to the encoder
            else
            {
                IList<ColorContext> colorContexts = frame.ColorContexts;
                if (colorContexts != null && colorContexts.Count > 0)
                {             
                    int count = colorContexts.Count;
 
                    // Marshal can't convert SafeMILHandle[] so we must
                    {
                        IntPtr[] colorContextPtrs = new IntPtr[count];
                        for (int i = 0; i < count; ++i)
                        {
                            colorContextPtrs[i] = colorContexts[i].ColorContextHandle.DangerousGetHandle();
                        }
 
                        int hr = UnsafeNativeMethods.WICBitmapFrameEncode.SetColorContexts(
                            frameEncodeHandle,
                            (uint)count,
                            colorContextPtrs
                            );
 
                        // It's possible that some encoders may not support color contexts so don't check hr
                        if (hr == HRESULT.S_OK)
                        {
                            // Helpful for debugging stress and remote dumps
                            _encodeState = EncodeState.FrameEncodeColorContextsSet;
                        }
                    }
                }
            }
 
            // Set the pixel format and palette
 
            lock (frame.SyncObject)
            {
                SafeMILHandle outSourceHandle = new SafeMILHandle();
                SafeMILHandle bitmapSourceHandle = frame.WicSourceHandle;
                SafeMILHandle paletteHandle = new SafeMILHandle();
 
                // Set the pixel format and palette of the bitmap.
                // This could (but hopefully won't) introduce a format converter.
                HRESULT.Check(UnsafeNativeMethods.WICCodec.WICSetEncoderFormat(
                    bitmapSourceHandle,
                    paletteHandle,
                    frameEncodeHandle,
                    out outSourceHandle
                    ));
 
                // Helpful for debugging stress and remote dumps
                _encodeState = EncodeState.FrameEncodeFormatSet;
                _writeSourceHandles.Add(outSourceHandle);
 
                // Set the metadata
                if (_supportsFrameMetadata)
                {
                    BitmapMetadata metadata = frame.Metadata as BitmapMetadata;
 
                    // If the frame has metadata associated with a different container format, then we ignore it.
                    if (metadata != null && metadata.GuidFormat == ContainerFormat)
                    {
                        SafeMILHandle /* IWICMetadataQueryWriter */ metadataHandle = new SafeMILHandle();
 
                        HRESULT.Check(UnsafeNativeMethods.WICBitmapFrameEncode.GetMetadataQueryWriter(
                            frameEncodeHandle,
                            out metadataHandle
                            ));
 
                        PROPVARIANT propVar = new PROPVARIANT();
 
                        try
                        {
                            propVar.Init(metadata);
 
                            lock (metadata.SyncObject)
                            {
                                HRESULT.Check(UnsafeNativeMethods.WICMetadataQueryWriter.SetMetadataByName(
                                    metadataHandle,
                                    "/",
                                    ref propVar
                                    ));
 
                                // Helpful for debugging stress and remote dumps
                                _encodeState = EncodeState.FrameEncodeMetadataSet;
                            }
                        }
                        finally
                        {
                            propVar.Clear();
                        }
                    }
                }
 
                Int32Rect r = new Int32Rect();
                HRESULT.Check(UnsafeNativeMethods.WICBitmapFrameEncode.WriteSource(
                    frameEncodeHandle,
                    outSourceHandle,
                    ref r
                    ));
 
                // Helpful for debugging stress and remote dumps
                _encodeState = EncodeState.FrameEncodeSourceWritten;
 
                HRESULT.Check(UnsafeNativeMethods.WICBitmapFrameEncode.Commit(
                    frameEncodeHandle
                    ));
 
                // Helpful for debugging stress and remote dumps
                _encodeState = EncodeState.FrameEncodeCommitted;
            }
        }
 
        #endregion
 
        #region Internal Abstract
 
        /// "Seals" the object
        internal abstract void SealObject();
 
        #endregion
 
        #region Data Members
 
        /// does encoder support a preview?
        internal bool _supportsPreview = true;
 
        /// does encoder support a global thumbnail?
        internal bool _supportsGlobalThumbnail = true;
 
        /// does encoder support a global metadata?
        internal bool _supportsGlobalMetadata = true;
 
        /// does encoder support per frame thumbnails?
        internal bool _supportsFrameThumbnails = true;
 
        /// does encoder support per frame thumbnails?
        internal bool _supportsFrameMetadata = true;
 
        /// does encoder support multiple frames?
        internal bool _supportsMultipleFrames = false;
 
        /// does encoder support color context?
        internal bool _supportsColorContext = false;
 
        /// is it a built in encoder
        private bool _isBuiltIn;
 
        /// Internal WIC encoder handle
        private SafeMILHandle _encoderHandle;
 
        /// metadata
        private BitmapMetadata _metadata;
 
        /// Internal WIC metadata handle
        private SafeMILHandle _metadataHandle;
 
        /// colorcontext
        private ReadOnlyCollection<ColorContext> _readOnlycolorContexts;
 
        /// codecinfo
        private BitmapCodecInfoInternal _codecInfo;
 
        /// thumbnail
        private BitmapSource _thumbnail;
 
        /// preview
        private BitmapSource _preview;
 
        /// palette
        private BitmapPalette _palette;
 
        /// frames
        private IList<BitmapFrame> _frames;
 
        /// true if Save has been called.
        private bool _hasSaved;
 
        /// The below data members have been added for stress or remote dump debugging purposes
        /// By the time we get an exception in managed code, we loose all context of what was
        /// on the stack and our locals are gone. The below will cache some critcal locals and state
        /// so they can be retrieved during debugging.
        private IList<SafeMILHandle> _frameHandles = new List<SafeMILHandle>(0);
        private IList<SafeMILHandle> _writeSourceHandles = new List<SafeMILHandle>(0);
        private enum EncodeState
        {
            None = 0,
            EncoderInitialized = 1,
            EncoderThumbnailSet = 2,
            EncoderPaletteSet = 3,
            EncoderCreatedNewFrame = 4,
            FrameEncodeInitialized = 5,
            FrameEncodeSizeSet = 6,
            FrameEncodeResolutionSet = 7,
            FrameEncodeThumbnailSet = 8,
            FrameEncodeMetadataSet = 9,
            FrameEncodeFormatSet = 10,
            FrameEncodeSourceWritten = 11,
            FrameEncodeCommitted = 12,
            EncoderCommitted = 13,
            FrameEncodeColorContextsSet = 14,
        };
        private EncodeState _encodeState;
 
        #endregion
    }
 
    #endregion // BitmapEncoder
}