1

UPDATED: Memory leak eliminated

GDI System.Drawing is a way faster than WPF DrawingContext in many cases, so I extended FrameworkElement with the following overload of OnRender:

protected abstract void OnRender(Graphics graphics);

It uses GDI bitmap buffer:

public abstract class GraphicsElement : FrameworkElement
{
    protected abstract void OnRender(Graphics graphics);

    protected override void OnRender(DrawingContext drawingContext)
    {
        if (TooSmall)
            return;

        using (var buffer = new Bitmap(BufferWidth, BufferHeight))
        {
            using (var g = Graphics.FromImage(buffer))
            {
                g.SmoothingMode = SmoothingMode.HighQuality;
                g.ScaleTransform(XScale, YScale);
                OnRender(g);
            }

            var source = buffer.ToBitmapSource();
            source.Freeze();
            drawingContext.DrawImage(
                source,
                new Rect(0, 0, ActualWidth, ActualHeight));
        }
    }

    bool TooSmall => BufferWidth <= 0 || BufferHeight <= 0;
    int BufferWidth => (int)(ActualWidth * XScale);
    int BufferHeight => (int)(ActualHeight * YScale);
    float XScale => (float)(Transformation?.M11 ?? 1.0);
    float YScale => (float)(Transformation?.M22 ?? 1.0);
    System.Windows.Media.Matrix? Transformation =>
        PresentationSource.FromVisual(this)
            ?.CompositionTarget
            ?.TransformToDevice;
}

Where helpers are:

public static class BitmapInterop
{
    public static BitmapSource ToBitmapSource(this Bitmap bitmap)
    {
        using (var handle = new SafeBitmapHandle(bitmap.GetHbitmap(), true))
            return Imaging.CreateBitmapSourceFromHBitmap(
                handle.DangerousGetHandle(),
                IntPtr.Zero,
                Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());
    }
}

public class SafeBitmapHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    [SecurityCritical]
    public SafeBitmapHandle(IntPtr preexistingHandle, bool ownsHandle)
        : base(ownsHandle)
    {
        SetHandle(preexistingHandle);
    }

    protected override bool ReleaseHandle() =>            
        DeleteObject(handle);

    [DllImport("gdi32.dll")]
    static extern bool DeleteObject(IntPtr hObject);
}

Demo code:

public class Plot : GraphicsElement
{
    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);
        drawingContext.DrawRectangle(
            null, 
            new System.Windows.Media.Pen(System.Windows.Media.Brushes.Gray, 1),
            new Rect(0, 0, ActualWidth, ActualHeight));
    }

    protected override void OnRender(Graphics graphics)
    {
        graphics.DrawLine(System.Drawing.Pens.Gray, 
            0, 0, (float)ActualWidth, (float)ActualHeight-1);
        graphics.DrawLine(System.Drawing.Pens.Gray, 
            0, (float)ActualHeight - 1, (float)ActualWidth, 0);
    }
}

It works, but I got some pixilation problem when upscaling Layout or Rendering Transform is in effect. I do pay attention to the system screen DPI settings properly. How to calculate an effective DPI factor?

Dmitry Nogin
  • 3,670
  • 1
  • 19
  • 35
  • Be aware of a potential memory leak when using `GetHbitmap()`: http://stackoverflow.com/q/1546091/1136211 – Clemens Dec 17 '16 at 08:15

0 Answers0