File: System\Windows\Media\Imaging\WriteableBitmap.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.ComponentModel;
using MS.Internal;
using MS.Win32.PresentationCore;
using System.Runtime.InteropServices;
using System.Windows.Media.Composition;
using System.Threading;
 
namespace System.Windows.Media.Imaging
{
    /// <summary>
    ///   WriteableBitmap provides an efficient, tear-free mechanism for updating
    ///   a system-memory bitmap.
    /// </summary>
    public sealed class WriteableBitmap : BitmapSource
    {
        #region Constructors
 
        /// <summary>
        ///     Internal constructor
        /// </summary>
        internal WriteableBitmap()
        {
        }
        
        /// <summary>
        ///     Creates a new WriteableBitmap instance initialized with the
        ///     contents of the specified BitmapSource.
        /// </summary>
        /// <param name="source">
        ///     The BitmapSource to copy.
        /// </param>
        public WriteableBitmap(
            BitmapSource source
            )
            : base(true) // Use base class virtuals
        {
            InitFromBitmapSource(source);
        }
        
        /// <summary>
        ///   Initializes a new instance of the WriteableBitmap class with
        ///   the specified parameters.
        /// </summary>
        /// <param name="pixelWidth">The desired width of the bitmap.</param>
        /// <param name="pixelHeight">The desired height of the bitmap.</param>
        /// <param name="dpiX">The horizontal dots per inch (dpi) of the bitmap.</param>
        /// <param name="dpiY">The vertical dots per inch (dpi) of the bitmap.</param>
        /// <param name="pixelFormat">The PixelFormat of the bitmap.</param>
        /// <param name="palette">The BitmapPalette of the bitmap.</param>
        public WriteableBitmap(
            int pixelWidth,
            int pixelHeight,
            double dpiX,
            double dpiY,
            PixelFormat pixelFormat,
            BitmapPalette palette
            )
            : base(true) // Use base class virtuals
        {
            BeginInit();
 
            //
            // Sanitize inputs
            //
 
            if (pixelFormat.Palettized && palette == null)
            {
                throw new InvalidOperationException(SR.Image_IndexedPixelFormatRequiresPalette);
            }
 
            if (pixelFormat.Format == PixelFormatEnum.Extended)
            {
                // We don't support third-party pixel formats yet.
                throw new ArgumentException(SR.Effect_PixelFormat, "pixelFormat");
            }
 
            if (pixelWidth < 0)
            {
                // Backwards Compat
                HRESULT.Check((int)WinCodecErrors.WINCODEC_ERR_VALUEOVERFLOW);
            }
 
            if (pixelWidth == 0)
            {
                // Backwards Compat
                HRESULT.Check(MS.Win32.NativeMethods.E_INVALIDARG);
            }
 
            if (pixelHeight < 0)
            {
                // Backwards Compat
                HRESULT.Check((int)WinCodecErrors.WINCODEC_ERR_VALUEOVERFLOW);
            }
 
            if (pixelHeight == 0)
            {
                // Backwards Compat
                HRESULT.Check(MS.Win32.NativeMethods.E_INVALIDARG);
            }
 
            //
            // Create and initialize a new unmanaged double buffered bitmap.
            //
            Guid formatGuid = pixelFormat.Guid;
 
            // This SafeMILHandle gets ignored if the pixel format is not palettized.
            SafeMILHandle internalPalette = new SafeMILHandle();
            if (pixelFormat.Palettized)
            {
                internalPalette = palette.InternalPalette;
            }
 
            HRESULT.Check(MILSwDoubleBufferedBitmap.Create(
                (uint) pixelWidth, // safe cast
                (uint) pixelHeight, // safe cast
                dpiX,
                dpiY,
                ref formatGuid,
                internalPalette,
                out _pDoubleBufferedBitmap
                ));
 
            _pDoubleBufferedBitmap.UpdateEstimatedSize(
                GetEstimatedSize(pixelWidth, pixelHeight, pixelFormat));
 
            // Momentarily lock to populate the BackBuffer/BackBufferStride properties.
            Lock();
            Unlock();
 
            EndInit();
        }
 
        #endregion // Constructors
 
        #region Public Methods
 
        /// <summary>
        ///   Adds a dirty region to the WriteableBitmap's back buffer.
        /// </summary>
        /// <param name="dirtyRect">
        ///   An Int32Rect structure specifying the dirty region.
        /// </param>
        /// <remarks>
        ///   This method can be called multiple times, and the areas are accumulated
        ///   in a sufficient, but not necessarily minimal, representation.  For efficiency,
        ///   only the areas that are marked as dirty are guaranteed to be copied over to
        ///   the rendering system.
        ///   AddDirtyRect can only be called while the bitmap is locked, otherwise an
        ///   InvalidOperationException will be thrown.
        /// </remarks>
        public void AddDirtyRect(Int32Rect dirtyRect)
        {
            WritePreamble();
 
            if (_lockCount == 0)
            {
                throw new InvalidOperationException(SR.Image_MustBeLocked);
            }
 
            //
            // Sanitize the dirty rect.
            //
            dirtyRect.ValidateForDirtyRect("dirtyRect", _pixelWidth, _pixelHeight);
            if (dirtyRect.HasArea)
            {
                MILSwDoubleBufferedBitmap.AddDirtyRect(
                    _pDoubleBufferedBitmap,
                    ref dirtyRect);
 
                _hasDirtyRects = true;
            }
 
            // Note: we do not call WritePostscript because we do not want to
            // raise change notifications until the writeable bitmap is unlocked.
        }
 
        /// <summary>
        ///   Shadows inherited Copy() with a strongly typed version for convenience.
        /// </summary>
        public new WriteableBitmap Clone()
        {
            return (WriteableBitmap)base.Clone();
        }
 
        /// <summary>
        ///   Shadows inherited CloneCurrentValue() with a strongly typed version for convenience.
        /// </summary>
        public new WriteableBitmap CloneCurrentValue()
        {
            return (WriteableBitmap)base.CloneCurrentValue();
        }
 
        /// <summary>
        ///     This method locks the WriteableBitmap and increments the lock count.
        /// </summary>
        /// <remarks>
        ///     By "locking" the WriteableBitmap, updates will not be sent to the rendering system until
        ///     the WriteableBitmap is fully unlocked.  This can be used to support multi-threaded scenarios.
        ///     This method blocks until the rendering system is finished processing the last frame's update.
        ///     To provide a timeout see WriteableBitmap.TryLock.
        ///     Locking the WriteableBitmap gives the caller write permission to the back buffer whose address
        ///     can be obtained via the WriteableBitmap.BackBuffer property.
        /// </remarks>
        public void Lock()
        {
            bool locked = TryLock(Duration.Forever);
            Debug.Assert(locked);
        }
 
        /// <summary>
        ///     This method tries to lock the WriteableBitmap for the specified
        ///     timeout and increments the lock count if successful.
        /// </summary>
        /// <param name="timeout">
        ///     The amount of time to wait while trying to acquire the lock.
        ///     To block indefinitely pass Duration.Forever.
        ///     Duration.Automatic is an invalid value.
        /// </param>
        /// <returns>Returns true if the lock is now held, false otherwise.</returns>
        public bool TryLock(Duration timeout)
        {
            WritePreamble();
 
            TimeSpan timeoutSpan;
            if (timeout == Duration.Automatic)
            {
                throw new ArgumentOutOfRangeException("timeout");
            }
            else if (timeout == Duration.Forever)
            {
                timeoutSpan = TimeSpan.FromMilliseconds(-1);
            }
            else
            {
                timeoutSpan = timeout.TimeSpan;
            }
 
            if (_lockCount == UInt32.MaxValue)
            {
                throw new InvalidOperationException(SR.Image_LockCountLimit);
            }
 
            if (_lockCount == 0)
            {
                // Try to acquire the back buffer by the supplied timeout, if the acquire call times out, return false.
                if (!AcquireBackBuffer(timeoutSpan, true))
                {
                    return false;
                }
 
                Int32Rect rect = new Int32Rect(0, 0, _pixelWidth, _pixelHeight);
 
                HRESULT.Check(UnsafeNativeMethods.WICBitmap.Lock(
                    WicSourceHandle,
                    ref rect,
                    LockFlags.MIL_LOCK_WRITE,
                    out _pBackBufferLock
                    ));
 
                // If this is the first lock operation, cache the BackBuffer and
                // BackBufferStride.  These two values will never change, so we
                // don't fetch them on every lock.
                if (_backBuffer == IntPtr.Zero)
                {
                    IntPtr tempBackBufferPointer = IntPtr.Zero;
                    uint lockBufferSize = 0;
                    HRESULT.Check(UnsafeNativeMethods.WICBitmapLock.GetDataPointer(
                        _pBackBufferLock,
                        ref lockBufferSize,
                        ref tempBackBufferPointer
                        ));
                    BackBuffer = tempBackBufferPointer;
 
                    uint lockBufferStride = 0;
                    HRESULT.Check(UnsafeNativeMethods.WICBitmapLock.GetStride(
                        _pBackBufferLock,
                        ref lockBufferStride
                        ));
                    Invariant.Assert(lockBufferStride <= Int32.MaxValue);
                    _backBufferStride = (int)lockBufferStride;
                }
 
                // If we were subscribed to the CommittingBatch event, unsubscribe
                // since we should not be part of the batch now that we are
                // locked.  When we unlock, we will subscribe to the
                // CommittingBatch again.
                UnsubscribeFromCommittingBatch();
            }
 
            _lockCount++;
            return true;
        }
 
        /// <summary>
        ///   This method decrements the lock count, and if it reaches zero will release the
        ///   on the back buffer and request a render pass.
        /// </summary>
        public void Unlock()
        {
            WritePreamble();
 
            if (_lockCount == 0)
            {
                throw new InvalidOperationException(SR.Image_MustBeLocked);
            }
            Invariant.Assert(_lockCount > 0, "Lock count should never be negative!");
 
            _lockCount--;
            if (_lockCount == 0)
            {
                // This makes the back buffer read-only.
                _pBackBufferLock.Dispose();
                _pBackBufferLock = null;
 
                if (_hasDirtyRects)
                {
                    SubscribeToCommittingBatch();
 
                    //
                    // Notify listeners that we have changed.
                    //
                    WritePostscript();
                }
            }
        }
 
        /// <summary>
        ///   Updates the pixels in the specified region of the bitmap.
        /// </summary>
        /// <param name="sourceRect">The rect to copy from the input buffer.</param>
        /// <param name="sourceBuffer">The input buffer used to update the bitmap.</param>
        /// <param name="sourceBufferSize">The size of the input buffer in bytes.</param>
        /// <param name="sourceBufferStride">
        ///     The stride of the input buffer in bytes.
        ///     It indicates where the next row starts in the input buffer.
        /// </param>
        /// <param name="destinationX">The destination x-coordinate of the left-most pixel to copy.</param>
        /// <param name="destinationY">The destination y-coordinate of the top-most pixel to copy.</param>
        public void WritePixels(
            Int32Rect sourceRect,
            IntPtr    sourceBuffer,
            int       sourceBufferSize,
            int       sourceBufferStride,
            int       destinationX,
            int       destinationY
            )
        {
            WritePreamble();
 
            WritePixelsImpl(sourceRect,
                            sourceBuffer,
                            sourceBufferSize,
                            sourceBufferStride, 
                            destinationX,
                            destinationY,
                            /*backwardsCompat*/ false);
        }
        
        /// <summary>
        ///   Updates the pixels in the specified region of the bitmap.
        /// </summary>
        /// <param name="sourceRect">The rect to copy from the input buffer.</param>
        /// <param name="sourceBuffer">The input buffer used to update the bitmap.</param>
        /// <param name="sourceBufferStride">
        ///     The stride of the input buffer in bytes.
        ///     It indicates where the next row starts in the input buffer.
        /// </param>
        /// <param name="destinationX">The destination x-coordinate of the left-most pixel to copy.</param>
        /// <param name="destinationY">The destination y-coordinate of the top-most pixel to copy.</param>
        public void WritePixels(
            Int32Rect sourceRect,
            Array     sourceBuffer,
            int       sourceBufferStride,
            int       destinationX,
            int       destinationY
            )
        {
            WritePreamble();
 
            int elementSize;
            int sourceBufferSize;
            Type elementType;
            ValidateArrayAndGetInfo(sourceBuffer,
                                    /*backwardsCompat*/ false,
                                    out elementSize,
                                    out sourceBufferSize,
                                    out elementType);
 
            // We accept arrays of arbitrary value types - but not reference types.
            if (elementType == null || !elementType.IsValueType)
            {
                throw new ArgumentException(SR.Image_InvalidArrayForPixel);
            }
 
            // Get the address of the data in the array by pinning it.
            GCHandle arrayHandle = GCHandle.Alloc(sourceBuffer, GCHandleType.Pinned);
            try
            {
                unsafe
                {
                    IntPtr buffer = arrayHandle.AddrOfPinnedObject();
                    WritePixelsImpl(sourceRect,
                                    buffer,
                                    sourceBufferSize,
                                    sourceBufferStride,
                                    destinationX,
                                    destinationY,
                                    /*backwardsCompat*/ false);
                }
            }
            finally
            {
                arrayHandle.Free();
            }
        }
 
        /// <summary>
        /// Update the pixels of this Bitmap
        /// </summary>
        /// <param name="sourceRect">Area to update</param>
        /// <param name="buffer">Input buffer</param>
        /// <param name="bufferSize">Size of the buffer</param>
        /// <param name="stride">Stride of the input buffer</param>
        public unsafe void WritePixels(
            Int32Rect sourceRect,
            IntPtr buffer,
            int bufferSize,
            int stride
            )
        {
            WritePreamble();
 
            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bufferSize);
            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(stride);
 
            if (sourceRect.IsEmpty || sourceRect.Width <= 0 || sourceRect.Height <= 0)
            {
                return;
            }
 
            // Backwards-Compat:
            //
            // The "sourceRect" is actually a "destinationRect", as in it
            // refers to the location where the contents are written.
            //
            // This method presumes that the pixels are copied from the
            // the specified offset (element count) in the source buffer, and
            // that no sub-byte pixel formats are used.
            int destinationX = sourceRect.X;
            int destinationY = sourceRect.Y;
            sourceRect.X = 0;
            sourceRect.Y = 0;
 
            WritePixelsImpl(sourceRect, 
                            buffer,
                            bufferSize,
                            stride,
                            destinationX,
                            destinationY,
                            /*backwardsCompat*/ true);
        }
 
        /// <summary>
        /// Update the pixels of this Bitmap
        /// </summary>
        /// <param name="sourceRect">Area to update</param>
        /// <param name="pixels">Input buffer</param>
        /// <param name="stride">Stride of the input buffer</param>
        /// <param name="offset">Input buffer offset</param>
        public void WritePixels(
            Int32Rect sourceRect,
            Array pixels,
            int stride,
            int offset
            )
        {
            WritePreamble();
 
            if (sourceRect.IsEmpty || sourceRect.Width <= 0 || sourceRect.Height <= 0)
            {
                return;
            }
 
            int elementSize;
            int sourceBufferSize;
            Type elementType;
            ValidateArrayAndGetInfo(pixels,
                                    /*backwardsCompat*/ true,
                                    out elementSize,
                                    out sourceBufferSize, 
                                    out elementType);
 
            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(stride);
            ArgumentOutOfRangeException.ThrowIfNegative(offset);
 
            // We accept arrays of arbitrary value types - but not reference types.
            if (elementType == null || !elementType.IsValueType)
            {
                throw new ArgumentException(SR.Image_InvalidArrayForPixel);
            }
            
            checked
            {
                int offsetInBytes = checked(offset * elementSize);
                if (offsetInBytes >= sourceBufferSize)
                {
                    // Backwards compat:
                    //
                    // The original code would throw an exception deeper in
                    // the code when it indexed off the end of the array.  We
                    // now check earlier (compat break) but throw the same
                    // exception.
                    throw new IndexOutOfRangeException();
                }
 
                // Backwards-Compat:
                //
                // The "sourceRect" is actually a "destinationRect", as in it
                // refers to the location where the contents are written.
                //
                // This method presumes that the pixels are copied from the
                // the specified offset (element count) in the source buffer, and
                // that no sub-byte pixel formats are used.  We handle the offset
                // later.
                int destinationX = sourceRect.X;
                int destinationY = sourceRect.Y;
                sourceRect.X = 0;
                sourceRect.Y = 0;
 
                // Get the address of the data in the array by pinning it.
                GCHandle arrayHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned);
                try
                {
                    IntPtr buffer = arrayHandle.AddrOfPinnedObject();
 
                    checked
                    {
                        buffer = new IntPtr(((long) buffer) + (long) offsetInBytes);
                        sourceBufferSize -= offsetInBytes;
                    }
 
                    WritePixelsImpl(sourceRect,
                                    buffer,
                                    sourceBufferSize,
                                    stride,
                                    destinationX,
                                    destinationY,
                                    /*backwardsCompat*/ true);
                }
                finally
                {
                    arrayHandle.Free();
                }
            }
        }
 
        #endregion // Public Methods
 
        #region Protected Methods
 
        /// <summary>
        ///   Implementation of Freezable.CreateInstanceCore.
        /// </summary>
        /// <returns>The new Freezable.</returns>
        protected override Freezable CreateInstanceCore()
        {
            return new WriteableBitmap();
        }
 
        /// <summary>
        ///   Implementation of Freezable.CloneCore.
        /// </summary>
        protected override void CloneCore(Freezable sourceFreezable)
        {
            WriteableBitmap sourceBitmap = (WriteableBitmap) sourceFreezable;
 
            base.CloneCore(sourceFreezable);
 
            CopyCommon(sourceBitmap);
        }
 
        /// <summary>
        ///   Implementation of Freezable.FreezeCore.
        /// </summary>
        protected override bool FreezeCore(bool isChecking)
        {
            bool canFreeze = (_lockCount == 0) && base.FreezeCore(isChecking);
 
            if (canFreeze && !isChecking)
            {
                Debug.Assert(_pBackBufferLock == null);
 
                //
                // By entering 'frozen' mode, we convert from being a 
                // DoubleBufferedBitmap to a regular BitmapSource.
                //
 
                // Protect the back buffer for writing
                HRESULT.Check(MILSwDoubleBufferedBitmap.ProtectBackBuffer(_pDoubleBufferedBitmap));
 
                // Get the back buffer to be used as our WicSourceHandle
                AcquireBackBuffer(TimeSpan.Zero, false);
                _needsUpdate = true;
                _hasDirtyRects = false;
 
                // Transfer the memory pressure over to WicSourceHandle.
                WicSourceHandle.CopyMemoryPressure(_pDoubleBufferedBitmap);
 
                // From here on out we're going to effectively be an ordinary
                // BitmapSource.
                _actLikeSimpleBitmap = true;
 
                // Pull this resource off all the channels and put it back on.
                int channelCount = _duceResource.GetChannelCount();
                for (int i = 0; i < channelCount; i++)
                {
                    DUCE.IResource resource = this as DUCE.IResource;
                    DUCE.Channel channel = _duceResource.GetChannel(i);
 
                    //
                    // It could have been added multiple times, so release until
                    // it's no longer on a channel.
                    //
                    uint refCount = _duceResource.GetRefCountOnChannel(channel);
                    for (uint j = 0; j < refCount; j++)
                    {
                        resource.ReleaseOnChannel(channel);
                    }
 
                    // Put it back on the Channel, only this time it wont
                    // be a SwDoubleBufferedBitmap.
                    for (uint j = 0; j < refCount; j++)
                    {
                        resource.AddRefOnChannel(channel);
                    }
                }
                
                Debug.Assert(!_isWaitingForCommit);
 
                // We no longer need the SwDoubleBufferedBitmap
                _pDoubleBufferedBitmap.Dispose();
                _pDoubleBufferedBitmap = null;
 
                // We will no longer need to wait for this event.
                _copyCompletedEvent.Close();
                _copyCompletedEvent = null;
 
                // Clear out unused variables
                _committingBatchHandler = null;
                _pBackBuffer = null;
            }
 
            return canFreeze;
        }
 
        /// <summary>
        ///   Implementation of Freezable.CloneCurrentValueCore.
        /// </summary>
        protected override void CloneCurrentValueCore(Freezable sourceFreezable)
        {
            WriteableBitmap sourceBitmap = (WriteableBitmap) sourceFreezable;
 
            base.CloneCurrentValueCore(sourceFreezable);
 
            CopyCommon(sourceBitmap);
        }
 
        /// <summary>
        ///   Implementation of Freezable.GetAsFrozenCore.
        /// </summary>
        protected override void GetAsFrozenCore(Freezable sourceFreezable)
        {
            WriteableBitmap sourceBitmap = (WriteableBitmap)sourceFreezable;
 
            base.GetAsFrozenCore(sourceFreezable);
 
            CopyCommon(sourceBitmap);
        }
 
        /// <summary>
        ///   Implementation of Freezable.GetCurrentValueAsFrozenCore.
        /// </summary>
        protected override void GetCurrentValueAsFrozenCore(Freezable sourceFreezable)
        {
            WriteableBitmap sourceBitmap = (WriteableBitmap)sourceFreezable;
 
            base.GetCurrentValueAsFrozenCore(sourceFreezable);
 
            CopyCommon(sourceBitmap);
        }
 
        #endregion // Protected Methods
 
        #region Private/Internal Methods
 
        /// <summary>
        ///     Gets the estimated memory pressure in bytes
        /// </summary>
        private long GetEstimatedSize(int pixelWidth, int pixelHeight, PixelFormat pixelFormat)
        {
            // Dimensions of the bitmap * bytes per pixel, then multiply by 2 because
            // WriteableBitmap uses 2 buffers.
            return pixelWidth * pixelHeight * pixelFormat.InternalBitsPerPixel / 8 * 2;
        }
 
        /// <summary>
        ///     Initializes this WriteableBitmap with the
        ///     contents of the specified BitmapSource.
        /// </summary>
        /// <param name="source">
        ///     The BitmapSource to copy.
        /// </param>
        private void InitFromBitmapSource(
            BitmapSource source
            )
        {
            ArgumentNullException.ThrowIfNull(source);
 
            if (source.PixelWidth < 0)
            {
                // Backwards Compat
                HRESULT.Check((int)WinCodecErrors.WINCODEC_ERR_VALUEOVERFLOW);
            }
 
            if (source.PixelHeight < 0)
            {
                // Backwards Compat
                HRESULT.Check((int)WinCodecErrors.WINCODEC_ERR_VALUEOVERFLOW);
            }
 
            BeginInit();
 
            _syncObject = source.SyncObject;
            lock (_syncObject)
            {
                Guid formatGuid = source.Format.Guid;
 
                SafeMILHandle internalPalette = new SafeMILHandle();
                if (source.Format.Palettized)
                {
                    internalPalette = source.Palette.InternalPalette;
                }
                
                HRESULT.Check(MILSwDoubleBufferedBitmap.Create(
                    (uint)source.PixelWidth, // safe cast
                    (uint)source.PixelHeight, // safe cast
                    source.DpiX,
                    source.DpiY,
                    ref formatGuid,
                    internalPalette,
                    out _pDoubleBufferedBitmap
                    ));
 
                _pDoubleBufferedBitmap.UpdateEstimatedSize(
                    GetEstimatedSize(source.PixelWidth, source.PixelHeight, source.Format));
 
                Lock();
 
                try
                {
                    Int32Rect rcFull = new Int32Rect(0, 0, _pixelWidth, _pixelHeight);
                    int bufferSize = checked(_backBufferStride * source.PixelHeight);
                    source.CriticalCopyPixels(rcFull, _backBuffer, bufferSize, _backBufferStride);
                    AddDirtyRect(rcFull);
                }
                finally
                {
                    Unlock();
                }
            }
 
            EndInit();
        }
 
        /// <summary>
        ///     Updates the pixels in the specified region of the bitmap.
        /// </summary>
        /// <param name="sourceRect">
        ///     The rect to copy from the input buffer.
        /// </param>
        /// <param name="sourceBuffer">
        ///     The input buffer used to update the bitmap.
        /// </param>
        /// <param name="sourceBufferSize">
        ///     The size of the input buffer in bytes.
        /// </param>
        /// <param name="sourceBufferStride">
        ///     The stride of the input buffer in bytes.
        ///     It indicates where the next row starts in the input buffer.
        /// </param>
        /// <param name="destX">
        ///     The destination x-coordinate of the left-most pixel to copy.
        /// </param>
        /// <param name="destY">
        ///     The destination y-coordinate of the top-most pixel to copy.
        /// </param>
        /// <param name="backwardsCompat">
        ///     Whether or not to preserve the old WritePixels behavior.
        /// </param>
        private void WritePixelsImpl(
            Int32Rect sourceRect,
            IntPtr    sourceBuffer,
            int       sourceBufferSize,
            int       sourceBufferStride,
            int       destinationX,
            int       destinationY,
            bool      backwardsCompat
            )
        {
            //
            // Sanitize the source rect and assure it will fit within the back buffer.
            //
            Debug.Assert(!(backwardsCompat && (sourceRect.X < 0 || sourceRect.Y < 0 || sourceRect.Width < 0 || sourceRect.Height < 0)));
            ArgumentOutOfRangeException.ThrowIfNegative(sourceRect.X, nameof(sourceRect));
            ArgumentOutOfRangeException.ThrowIfNegative(sourceRect.Y, nameof(sourceRect));
            ArgumentOutOfRangeException.ThrowIfNegative(sourceRect.Width, nameof(sourceRect));
            ArgumentOutOfRangeException.ThrowIfNegative(sourceRect.Height, nameof(sourceRect));
 
            if (!backwardsCompat)
            {
                ArgumentOutOfRangeException.ThrowIfGreaterThan(sourceRect.Width, _pixelWidth, nameof(sourceRect));
                ArgumentOutOfRangeException.ThrowIfGreaterThan(sourceRect.Height, _pixelHeight, nameof(sourceRect));
                ArgumentOutOfRangeException.ThrowIfNegative(destinationX);
                ArgumentOutOfRangeException.ThrowIfNegative(destinationY);
                ArgumentOutOfRangeException.ThrowIfGreaterThan(destinationX, _pixelWidth - sourceRect.Width);
                ArgumentOutOfRangeException.ThrowIfGreaterThan(destinationY, _pixelHeight - sourceRect.Height);
            }
            else if(sourceRect.Width > _pixelWidth || sourceRect.Height > _pixelHeight || destinationX > _pixelWidth - sourceRect.Width || destinationY > _pixelHeight - sourceRect.Height)
            {
                HRESULT.Check(MS.Win32.NativeMethods.E_INVALIDARG);
            }
            else if (destinationX < 0 || destinationY < 0)
            {
                HRESULT.Check((int)WinCodecErrors.WINCODEC_ERR_VALUEOVERFLOW);
            }
 
            //
            // Sanitize the other parameters.
            //
            if (sourceBuffer == IntPtr.Zero)
            {
                // Backwards Compat:
                //
                // The original code would null-ref when it was passed a null
                // buffer (IntPtr.Zero).  We choose to throw a better
                // exception.
                throw new ArgumentNullException(backwardsCompat ? "buffer" : "sourceBuffer");
            }
 
            Debug.Assert(!(backwardsCompat && sourceBufferStride < 1));
            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(sourceBufferStride);
 
            if (sourceRect.Width == 0 || sourceRect.Height == 0)
            {
                Debug.Assert(!backwardsCompat);
 
                // Nothing to do.
                return;
            }
 
            checked
            {
                uint finalRowWidthInBits = (uint)((sourceRect.X + sourceRect.Width) * _format.InternalBitsPerPixel);
                uint finalRowWidthInBytes = ((finalRowWidthInBits + 7) / 8);
                uint requiredBufferSize = (uint)((sourceRect.Y + sourceRect.Height - 1) * sourceBufferStride) + finalRowWidthInBytes;
                if (sourceBufferSize < requiredBufferSize)
                {
                    if (backwardsCompat)
                    {
                        HRESULT.Check((int)WinCodecErrors.WINCODEC_ERR_INSUFFICIENTBUFFER);
                    }
                    else
                    {
                        throw new ArgumentException(SR.Image_InsufficientBufferSize, "sourceBufferSize");
                    }
                }
 
                uint copyWidthInBits = (uint)(sourceRect.Width * _format.InternalBitsPerPixel);
 
                // Calculate some offsets that we'll need in a moment.
                uint sourceXbyteOffset = (uint)((sourceRect.X * _format.InternalBitsPerPixel) / 8);
                uint sourceBufferBitOffset = (uint)((sourceRect.X * _format.InternalBitsPerPixel) % 8);
                uint firstPixelByteOffet = (uint)((sourceRect.Y * sourceBufferStride) + sourceXbyteOffset);
                uint destXbyteOffset = (uint)((destinationX * _format.InternalBitsPerPixel) / 8);
                uint destBufferBitOffset = (uint)((destinationX * _format.InternalBitsPerPixel) % 8);
 
                Int32Rect destinationRect = sourceRect;
                destinationRect.X = destinationX;
                destinationRect.Y = destinationY;
 
                //
                // Copy pixel information from the user supplied buffer to the back buffer.
                //
                unsafe
                {
                    uint destOffset = (uint)(destinationY * _backBufferStride) + destXbyteOffset;
                    byte* pDest = (byte*)_backBuffer.ToPointer();
                    pDest += destOffset;
                    uint outputBufferSize = _backBufferSize - destOffset;
 
                    byte* pSource = (byte*)sourceBuffer.ToPointer();
                    pSource += firstPixelByteOffet;
                    uint inputBufferSize = (uint)sourceBufferSize - firstPixelByteOffet;
 
                    Lock();
 
                    try
                    {
                        MILUtilities.MILCopyPixelBuffer(
                            pDest,
                            outputBufferSize,
                            (uint) _backBufferStride,
                            destBufferBitOffset,
                            pSource,
                            inputBufferSize,
                            (uint) sourceBufferStride,
                            sourceBufferBitOffset,
                            (uint) sourceRect.Height,
                            copyWidthInBits);
                        AddDirtyRect(destinationRect);
                    }
                    finally
                    {
                        // MILUtilities.MILCopyPixelBuffer may throw ArgumentException (e.g. for invalid stride)
                        // See https://github.com/dotnet/wpf/issues/8134
                        Unlock();
                    }
                }
            }
 
            // Note: we do not call WritePostscript because we do not want to
            // raise change notifications until the writeable bitmap is unlocked.
            //
            // Change notifications may have already been raised in the Unlock
            // call in this method.
        }
 
        /// <summary>
        ///   Try to acquire the back buffer of our unmanaged double buffered bitmap in the specified timeout.
        /// </summary>
        /// <param name="timeout">
        ///   The time to wait while trying to acquire the lock.
        /// </param>
        /// <param name="waitForCopy">
        ///   Should we try to wait for the copy completed event?
        /// </param>        
        /// <returns>Returns true if the back buffer was acquired before the timeout expired.</returns>
        private bool AcquireBackBuffer(TimeSpan timeout, bool waitForCopy)
        {
            bool backBufferAcquired = false;
 
            //
            // Only get the back buffer from the unmanaged double buffered bitmap if this is our
            // first time being called since the last successful call to OnCommittingBatch.
            // OnCommittingBatch sets _pBackBuffer to null.
            //
            if (_pBackBuffer == null)
            {
                bool shouldGetBackBuffer = true;
                
                if (waitForCopy)
                {
                    // If we have committed a copy-forward command, we need to wait
                    // for the render thread to finish the copy before we can use
                    // the back buffer.
                    shouldGetBackBuffer = _copyCompletedEvent.WaitOne(timeout, false);
                }
                
                if (shouldGetBackBuffer)
                {
                    MILSwDoubleBufferedBitmap.GetBackBuffer(
                        _pDoubleBufferedBitmap,
                        out _pBackBuffer,
                        out _backBufferSize);
 
                    _syncObject = WicSourceHandle = _pBackBuffer;
                    backBufferAcquired = true;
                }
            }
            else
            {
                backBufferAcquired = true;
            }
 
            return backBufferAcquired;
        }
 
        /// <summary>
        ///   Common implementation for CloneCore(), CloneCurrentValueCore(),
        ///   GetAsFrozenCore(), and GetCurrentValueAsFrozenCore().
        /// </summary>
        /// <param name="sourceBitmap">The WriteableBitmap to copy from.</param>
        private void CopyCommon(WriteableBitmap sourceBitmap)
        {
            // Avoid Animatable requesting resource updates for invalidations
            // that occur during construction.
            Animatable_IsResourceInvalidationNecessary = false;
            _actLikeSimpleBitmap = false;
 
            // Create a SwDoubleBufferedBitmap and copy the sourceBitmap into it.
            InitFromBitmapSource(sourceBitmap);
 
            // The next invalidation will cause Animatable to register an
            // UpdateResource callback.
            Animatable_IsResourceInvalidationNecessary = true;
        }
 
 
        // ISupportInitialize
 
        /// <summary>
        ///   Prepare the bitmap to accept initialize paramters.
        /// </summary>
        private void BeginInit()
        {
            _bitmapInit.BeginInit();
        }
 
        /// <summary>
        ///   Prepare the bitmap to accept initialize paramters.
        /// </summary>
        private void EndInit()
        {
            _bitmapInit.EndInit();
 
            FinalizeCreation();
        }
 
        /// <summary>
        ///   Create the unmanaged resources.
        /// </summary>
        internal override void FinalizeCreation()
        {
            IsSourceCached = true;
            CreationCompleted = true;
            UpdateCachedSettings();
        }
 
        /// <summary>
        ///     Get the size of the specified array and of the elements in it.
        /// </summary>
        /// <param name="sourceBuffer">
        ///     The array to get info about.
        /// </param>
        /// <param name="elementSize">
        ///     On output, will contain the size of the elements in the array.
        /// </param>
        /// <param name="sourceBufferSize">
        ///     On output, will contain the size of the array.
        /// </param>
        private void ValidateArrayAndGetInfo(Array sourceBuffer,
                                                       bool backwardsCompat,
                                                       out int elementSize,
                                                       out int sourceBufferSize,
                                                       out Type elementType)
        {
            //
            // Assure that a valid pixels Array was provided.
            //
            if (sourceBuffer == null)
            {
                throw new ArgumentNullException(backwardsCompat ? "pixels" : "sourceBuffer");
            }
 
            if (sourceBuffer.Rank == 1)
            {
                int firstDimLength = sourceBuffer.GetLength(0);
                if (firstDimLength == 0)
                {
                    if (backwardsCompat)
                    {
                        elementSize = 1;
                        sourceBufferSize = 0;
                        elementType = null;
                    }
                    else
                    {
                        throw new ArgumentException(SR.Image_InsufficientBuffer, nameof(sourceBuffer));
                    }
                }
                else
                {
                    checked
                    {
                        object exemplar = sourceBuffer.GetValue(0);
                        elementSize = Marshal.SizeOf(exemplar);
                        sourceBufferSize = firstDimLength * elementSize;
                        elementType = exemplar.GetType();
                    }
                }
}
            else if (sourceBuffer.Rank == 2)
            {
                int firstDimLength = sourceBuffer.GetLength(0);
                int secondDimLength = sourceBuffer.GetLength(1);
                if (firstDimLength == 0 || secondDimLength == 0)
                {
                    if (backwardsCompat)
                    {
                        elementSize = 1;
                        sourceBufferSize = 0;
                        elementType = null;
                    }
                    else
                    {
                        throw new ArgumentException(SR.Image_InsufficientBuffer, nameof(sourceBuffer));
                    }
                }
                else
                {
                    checked
                    {
                        object exemplar = sourceBuffer.GetValue(0, 0);
                        elementSize = Marshal.SizeOf(exemplar);
                        sourceBufferSize = (firstDimLength * secondDimLength) * elementSize;
                        elementType = exemplar.GetType();
                    }
                }
            }
            else
            {
                throw new ArgumentException(SR.Collection_BadRank, backwardsCompat ? "pixels" : "sourceBuffer");
            }
        }
 
        /// <summary>
        ///     Adds a reference to our DUCE resource on <paramref name="channel"/>.
        /// </summary>
        /// <param name="channel">
        ///     The channel we want to AddRef on.
        /// </param>
        /// <returns>
        ///     The handle to our DoubleBufferedBitmap or BitmapSource handle.
        /// </returns>
        /// <remarks>
        ///     We override this method because we use a different resource
        ///     type than our base class does.  This probably suggests that the
        ///     base class should not presume the resource type, but it
        ///     currently does.  The base class uses TYPE_BITMAPSOURCE
        ///     resources, and we use TYPE_DOUBLEBUFFEREDBITMAP resources.
        /// </remarks>
        internal override DUCE.ResourceHandle AddRefOnChannelCore(DUCE.Channel channel)
        {
            //
            // If we're in BitmapSource mode, then just defer to the BitmapSource
            // implementation.
            //
            if (_actLikeSimpleBitmap)
            {
                return base.AddRefOnChannelCore(channel);
            }
 
            if (_duceResource.CreateOrAddRefOnChannel(this, channel, DUCE.ResourceType.TYPE_DOUBLEBUFFEREDBITMAP))
            {
                // This is the first AddRef on this channel...
 
                // If we are being put onto the asynchronous compositor channel in
                // a dirty state, we need to subscribe to the CommittingBatch event.
                if (!channel.IsSynchronous && _hasDirtyRects)
                {
                    SubscribeToCommittingBatch();
                }
 
                AddRefOnChannelAnimations(channel);
 
                // The first time our resource is created on a channel, we need
                // to update it.  We can skip "on channel" check since we
                // already know that the resource is on channel.
                UpdateResource(channel, true);
            }
 
            return _duceResource.GetHandle(channel);
        }
 
        internal override void ReleaseOnChannelCore(DUCE.Channel channel)
        {
            Debug.Assert(_duceResource.IsOnChannel(channel));
 
            if (_duceResource.ReleaseOnChannel(channel))
            {
                // This is the last release from this channel...
 
                // If we are being pulled off the asynchronous compositor channel
                // then unsubscribe from the CommittingBatch event.
                if (!channel.IsSynchronous)
                {
                    UnsubscribeFromCommittingBatch();
                }
 
                ReleaseOnChannelAnimations(channel);
            }
        }
 
        /// <summary>
        ///   Updates the double-buffered bitmap DUCE resource with a pointer to our acutal object.
        /// </summary>
        /// <param name="channel">The channel to update the resource on.</param>
        /// <param name="skipOnChannelCheck">
        ///   If this is true, we know we are on channel and don't need to explicitly check.
        /// </param>
        internal override void UpdateBitmapSourceResource(DUCE.Channel channel, bool skipOnChannelCheck)
        {
            //
            // If we're in BitmapSource mode, then just defer to the BitmapSource
            // implementation.
            //
            if (_actLikeSimpleBitmap)
            {
                base.UpdateBitmapSourceResource(channel, skipOnChannelCheck);
                return;
            }
            
            // We override this method because we use a different resource type
            // than our base class does.  This probably suggests that the base
            // class should not presume the resource type, but it currently
            // does.  The base class uses TYPE_BITMAPSOURCE resources, and we
            // use TYPE_DOUBLEBUFFEREDBITMAP resources.
 
            // If we're told we can skip the channel check, then we must be on channel
            Debug.Assert(!skipOnChannelCheck || _duceResource.IsOnChannel(channel));
 
            if (skipOnChannelCheck || _duceResource.IsOnChannel(channel))
            {
                DUCE.MILCMD_DOUBLEBUFFEREDBITMAP command;
                command.Type = MILCMD.MilCmdDoubleBufferedBitmap;
                command.Handle = _duceResource.GetHandle(channel);
                unsafe
                {
                    command.SwDoubleBufferedBitmap = (UInt64) _pDoubleBufferedBitmap.DangerousGetHandle().ToPointer();
                }
                command.UseBackBuffer = channel.IsSynchronous ? 1u : 0u;
 
                //
                // We need to ensure that this object stays alive while traveling over the channel
                // so we'll AddRef it here, and simply take over the reference on the other side.
                //
                UnsafeNativeMethods.MILUnknown.AddRef(_pDoubleBufferedBitmap);
                
                unsafe
                {
                    channel.SendCommand(
                        (byte*)&command,
                        sizeof(DUCE.MILCMD_DOUBLEBUFFEREDBITMAP),
                        false /* sendInSeparateBatch */
                        );
                }
            }
        }
 
        private void SubscribeToCommittingBatch()
        {
            // Only subscribe the the CommittingBatch event if we are on-channel.
            if (!_isWaitingForCommit)
            {
                MediaContext mediaContext = MediaContext.From(Dispatcher);
                if (_duceResource.IsOnChannel(mediaContext.Channel))
                {
                    mediaContext.CommittingBatch += CommittingBatchHandler;
                    _isWaitingForCommit = true;
                }
            }
        }
 
        private void UnsubscribeFromCommittingBatch()
        {
            if (_isWaitingForCommit)
            {
                MediaContext mediaContext = MediaContext.From(Dispatcher);
                mediaContext.CommittingBatch -= CommittingBatchHandler;
                _isWaitingForCommit = false;
            }
        }
 
        /// <summary>
        ///   Send a packet on the DUCE.Channel telling our double-buffered bitmap resource
        ///   to copy forward dirty regions from the back buffer to the front buffer.
        /// </summary>
        /// <remarks>
        ///   For the packet to be sent, the user must have added a dirty region to the
        ///   WriteableBitmap and there must be no outstanding locks.
        /// </remarks>
        private void OnCommittingBatch(object sender, EventArgs args)
        {
            Debug.Assert(_isWaitingForCommit);  // How else are we here?
            UnsubscribeFromCommittingBatch();
 
            Debug.Assert(_lockCount == 0);  // How else are we here?
            Debug.Assert(_hasDirtyRects);  // How else are we here?
 
            // Before using the back buffer again, we need to know when
            // the rendering thread has completed the copy. By setting
            // our back buffer pointer to null, we'll have to re-acquire
            // it the next time, which will wait for the copy to complete.
            _copyCompletedEvent.Reset();
            _pBackBuffer = null;
 
            DUCE.Channel channel = sender as DUCE.Channel;
            Debug.Assert(_duceResource.IsOnChannel(channel));  // How else are we here?
 
            // We are going to pass an event in the command packet we send to
            // the composition thread.  We need to make sure the event stays 
            // alive in case we get collected before the composition thread
            // processes the packet.  We do this by duplicating the event
            // handle, and the composition thread will close the handle after
            // signalling it.
            IntPtr hDuplicate;
            IntPtr hCurrentProc = MS.Win32.UnsafeNativeMethods.GetCurrentProcess();
            if (!MS.Win32.UnsafeNativeMethods.DuplicateHandle(
                    hCurrentProc,
                    _copyCompletedEvent.SafeWaitHandle,
                    hCurrentProc,
                    out hDuplicate,
                    0,
                    false,
                    MS.Win32.UnsafeNativeMethods.DUPLICATE_SAME_ACCESS
                    ))
            {
                throw new Win32Exception();
            }
 
            DUCE.MILCMD_DOUBLEBUFFEREDBITMAP_COPYFORWARD command;
            command.Type = MILCMD.MilCmdDoubleBufferedBitmapCopyForward;
            command.Handle = _duceResource.GetHandle(channel);
            command.CopyCompletedEvent = (UInt64) hDuplicate.ToInt64();
 
            // Note that the batch is closed after the sendcommand because this method is called under the 
            // context of the MediaContext.CommitChannel and the command needs to make it into the current set of changes which are 
            // being commited to the compositor.  If the batch is not closed, it would go into the 
            // "future" batch which would not get submitted this time around. This leads to a dead-lock situation which occurs when 
            // the app calls Lock on the WriteableBitmap because Lock waits on _copyCompletedEvent which the compositor sets when it sees the 
            // Present command. However, since the compositor does not get the Present command, it will not set the event and the 
            // UI thread will wait forever on the compositor which will cause the application to stop responding.
            // Another option is to send the command in its own batch (instead of closing the batch). This doesn't work in all cases 
            // because the command for creating the resource handle (AddRefOnChannelCore) or the command for initializing the resource (UpdateBitmapSourceResource)
            // could be in the "future" batch thus crashing the CopyForward operation in this batch.
 
 
            unsafe
            {
                channel.SendCommand(
                    (byte*)&command,
                    sizeof(DUCE.MILCMD_DOUBLEBUFFEREDBITMAP_COPYFORWARD));
                channel.CloseBatch();
            }
 
            // We are committing the batch to the asynchronous compositor,
            // which will copy the rects forward.  The copy will complete
            // before we can access the back buffer again.  So, we consider
            // ourselves clean.
            _hasDirtyRects = false;
        }
 
        #endregion
 
        #region Properties
 
        /// <summary>
        ///   Read-only data pointer to the back buffer.
        /// </summary>
        public IntPtr BackBuffer
        {
            get
            {
                ReadPreamble();
 
                return _backBuffer;
            }
 
            private set
            {
                _backBuffer = value;
            }
        }
 
        private IntPtr _backBuffer;
 
        private uint _backBufferSize;
 
        /// <summary>
        ///   Read-only stride of the back buffer.
        /// </summary>
        public int BackBufferStride
        {
            get
            {
                ReadPreamble();
 
                return _backBufferStride;
            }
        }
 
        private int _backBufferStride;
 
        #endregion // Properties
 
        #region Fields
 
        private SafeMILHandle _pDoubleBufferedBitmap;   // CSwDoubleBufferedBitmap
 
        private SafeMILHandle _pBackBufferLock;         // IWICBitmapLock
 
        private BitmapSourceSafeMILHandle _pBackBuffer; // IWICBitmap
 
        private uint _lockCount = 0;
 
        // Flags whether the user has added a dirty rect since the last CopyForward packet was sent.
        private bool _hasDirtyRects = true;
 
        // Flags whether a MediaContext.CommittingBatch handler has already been added.
        private bool _isWaitingForCommit = false;   
 
        private ManualResetEvent _copyCompletedEvent = new ManualResetEvent(true);
 
        private EventHandler CommittingBatchHandler
        {
            get
            {
                if (_committingBatchHandler == null)
                {
                    _committingBatchHandler = OnCommittingBatch;
                }
 
                return _committingBatchHandler;
            }
        }
        private EventHandler _committingBatchHandler; // = OnCommittingBatch (CS0236)
 
        private bool _actLikeSimpleBitmap = false;
 
        #endregion // Fields
    }
}