David Jeske
David Jeske

Reputation: 2466

howto efficiently have a WritableBitmap which matches a WPF control pixels 1:1?

I'm using WritableBitmap to draw fast 2d graphics in WPF.

The problem is, my WritableBitmap does not match the pixel size of the Image control 1:1. When I try to make a new WritableBitmap based on size-change events of the Image control, the performance is terrible.

How can I render a WPF control's pixels 1:1 with a WritableBitmap?

You can see both WPF and Windows.Forms versions of my code on github.

The application is a tiny mini-app called SoundLevelMonitor, which monitors audio levels of applications and paints a continuously moving graph with a legend. Below is a screenshot of the Windows.Forms version. The WPF version is basically the same, except the Image is a fixed size and stretched to fit the window.

enter image description here

Before I started using WritableBitmap, I tried using UIElement, but the drawing performance was horrible. One problem is that triggering repaint requires InvalidateVisual() which causes a slow re-layout. The other problem is that drawing text with DrawingContext is extremely slow.

So I found this suggestion to write to WriteableBitmap using GDI, and it actually works really well, and is SUPER fast. (the WPF version feels more responsive than the windows forms version!)

However, the WriteableBitmap is a fixed-sized backingstore which WPF is scaling up to fit in the Image control. I want the WriteableBitmap to be a 1:1 pixel backing store for the control, even when the control is resized.

I tried to use both OnRender and LayoutUpdated to trigger creating a new size-matched WriteableBitmap, but both of them were incredibly slow. Probably because setting Image.Source is triggering a relayout.

Any ideas?

Upvotes: 2

Views: 984

Answers (1)

David Jeske
David Jeske

Reputation: 2466

My biggest discovery is that I don't need to use a bitmap to get the results I want. I can create a DrawingGroup as a backingstore, draw this to the drawing context during OnRender, and then still update it later whenever I want.

DrawingGroup backingStore = new DrawingGroup();

protected override void OnRender(DrawingContext drawingContext) {      
    base.OnRender(drawingContext);            

    Render(); // put content into our backingStore
    drawingContext.DrawDrawing(backingStore);
}

// I can call this anytime, and it'll update my visual drawing
// without ever triggering layout or OnRender()
private void Render() {            
    var drawingContext = backingStore.Open();
    Render(drawingContext);
    drawingContext.Close();            
}

I also learned that StreamGeometry with Freeze() is notably faster than other drawing methods, as it optimizes for no animation.


Related to my bitmap attempts -- the correct way to be notified when the Image Control size changes is to listen to SizeChanged, which fires only when the control changes size. In this event handler, I had to make a new WriteableBitmap of size Image.RenderSize and assign it to the Image.Source.

Also, in the Image Control instantiation, I had to set it to Stretch="Fill" to cause it to fill it's containing box completely, instead of maintaining the image's aspect ratio.

LayoutUpdated is fired every time the control content updates, which is what was causing my original performance problem.

I also found that I can use RenderTargetBitmap instead of WriteableBitmap, and then I can issue WPF drawing commands to it, instead of using a GDI Graphics context.

..and I can catch the CompositionTarget.Rendering event to cause it to be redrawn every frame.

Upvotes: 3

Related Questions