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?