Skip to content

Improve WPF Performance with BitmapSource #522

@swharden

Description

@swharden

Schmuck Ákos suggested in an email:

One small thing I've noticed is that there is a memory cost in ScottPlot.WPF, as a WinForms Bitmap is copied into a new BitmapImage each time WpfPlot.Render is invoked. There is a little known trick, where you can avoid this by implementing BitmapSource. See: https://stackoverflow.com/a/32841840

I have attached an implementation based on that, which seems to work well enough on my computer. I'm not sure if this code is production ready though.

Modification to WpfPlot.Render()

// current method
imagePlot.Source = BmpImageFromBmp(plt.GetBitmap(/*...*/));

// improved method
imagePlot.Source = new WinFormsBitmapWrapper(plt.GetBitmap(/*...*/));

WinFormsBitmapWrapper.cs

using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace ScottPlotTest
{
    //// NOTE: Wraps a WinForms Bitmap to be consumed in WPF.
    ////       Does NOT dispose of the Bitmap, it must be released manually.

    internal class WinFormsBitmapWrapper : System.Windows.Media.Imaging.BitmapSource
    {
        //// based on:
        //// https://stackoverflow.com/a/32841840

        private readonly System.Drawing.Bitmap winFormsBitmap;

        public WinFormsBitmapWrapper( System.Drawing.Bitmap bitmap )
        {
            this.winFormsBitmap = bitmap ?? throw new ArgumentNullException(nameof(bitmap));
        }

        private static System.Windows.Media.PixelFormat ConvertPixelFormat( System.Drawing.Imaging.PixelFormat sourceFormat )
        {
            switch( sourceFormat )
            {
            case System.Drawing.Imaging.PixelFormat.Format24bppRgb:
                return PixelFormats.Bgr24;

            case System.Drawing.Imaging.PixelFormat.Format32bppRgb:
                return PixelFormats.Bgr32;

            case System.Drawing.Imaging.PixelFormat.Format32bppArgb:
                return PixelFormats.Bgra32;

            case System.Drawing.Imaging.PixelFormat.Format32bppPArgb:
                return PixelFormats.Pbgra32;

            default:
                throw new ArgumentException($"Pixel format conversion not implemented for {sourceFormat}!");
            }
        }

        #region BitmapSource

        public override double DpiX => this.winFormsBitmap.HorizontalResolution;

        public override double DpiY => this.winFormsBitmap.VerticalResolution;

        public override int PixelWidth => this.winFormsBitmap.Width;

        public override int PixelHeight => this.winFormsBitmap.Height;

        public override System.Windows.Media.PixelFormat Format => ConvertPixelFormat(this.winFormsBitmap.PixelFormat);

        public override BitmapPalette Palette => null;

        public override void CopyPixels( Int32Rect sourceRect, Array pixels, int stride, int offset )
        {
            var sourceData = this.winFormsBitmap.LockBits(
                new System.Drawing.Rectangle(sourceRect.X, sourceRect.Y, sourceRect.Width, sourceRect.Height),
                System.Drawing.Imaging.ImageLockMode.ReadOnly,
                this.winFormsBitmap.PixelFormat);

            try
            {
                var length = sourceData.Stride * sourceData.Height;
                if( pixels is byte[] )
                {
                    var bytes = pixels as byte[];
                    System.Runtime.InteropServices.Marshal.Copy(sourceData.Scan0, bytes, 0, length);
                }
            }
            finally
            {
                this.winFormsBitmap.UnlockBits(sourceData);
            }
        }

        protected override Freezable CreateInstanceCore()
        {
            return (Freezable)new WinFormsBitmapWrapper(new System.Drawing.Bitmap(10, 10));
        }

#pragma warning disable 0067 // disable CS0067: The event is never used.
        public override event EventHandler<ExceptionEventArgs> DecodeFailed; // exception if not overridden
#pragma warning restore 0067

        #endregion
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions