Reputation: 2466
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.
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
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