23

Is there a way to get a DrawingContext (or something similar) for a WriteableBitmap? I.e. something to allow you to call simple DrawLine/DrawRectangle/etc kinds of methods, rather than manipulate the raw pixels directly.

Noam M
  • 3,156
  • 5
  • 26
  • 41
Emperor XLII
  • 13,014
  • 11
  • 65
  • 75

5 Answers5

22

I found sixlettervariables' solution the most workable one. However, there's a "drawingContext.Close()" missing. According to MSDN, "A DrawingContext must be closed before its content can be rendered". The result is the following utility function:

public static BitmapSource CreateBitmap(
    int width, int height, double dpi, Action<DrawingContext> render)
{
    DrawingVisual drawingVisual = new DrawingVisual();
    using (DrawingContext drawingContext = drawingVisual.RenderOpen())
    {
        render(drawingContext);
    }
    RenderTargetBitmap bitmap = new RenderTargetBitmap(
        width, height, dpi, dpi, PixelFormats.Default);
    bitmap.Render(drawingVisual);

    return bitmap;
}

This can then easily be used like this:

BitmapSource image = ImageTools.CreateBitmap(
    320, 240, 96,
    drawingContext =>
    {
        drawingContext.DrawRectangle(
            Brushes.Green, null, new Rect(50, 50, 200, 100));
        drawingContext.DrawLine(
            new Pen(Brushes.White, 2), new Point(0, 0), new Point(320, 240));
    });
Daniel Wolf
  • 12,855
  • 13
  • 54
  • 80
  • 5
    The `.Close()` is implicit in the `.Dispose()` - which is the whole point behind the `using` statement. If you leave *only* the `render(...)` command inside the using block, you'll be fine and don't need any `.Close()`. – Eamon Nerbonne Nov 16 '09 at 14:12
  • 4
    There's no `WriteableBitmap` in this answer, why is it the top one? :) It's completely unrelated to the question. – Roman Starkov Jan 03 '12 at 12:21
  • 6
    My guess is that it's upvoted because the original question may be akin to asking "How do I turn this screw with this hammer?" and the answer is "Use a screwdriver" instead of how to use the hammer to do it. – codekaizen Jul 03 '14 at 22:52
  • if you want to make WritableBitmap from it you can simply call: `WritaBleBitmap result = new WritableBitmap(image);` – vac Jul 04 '15 at 11:50
18

If you don't mind using System.Drawing you could do something like:

var wb = new WriteableBitmap( width, height, dpi, dpi, 
                              PixelFormats.Pbgra32, null );
wb.Lock();
var bmp = new System.Drawing.Bitmap( wb.PixelWidth, wb.PixelHeight,
                                     wb.BackBufferStride, 
                                     PixelFormat.Format32bppPArgb, 
                                     wb.BackBuffer );

Graphics g = System.Drawing.Graphics.FromImage( bmp ); // Good old Graphics

g.DrawLine( ... ); // etc...

// ...and finally:
g.Dispose(); 
bmp.Dispose();
wb.AddDirtyRect( ... );
wb.Unlock();                    
simo
  • 23,342
  • 38
  • 121
  • 218
Danko Durbić
  • 7,077
  • 5
  • 34
  • 39
  • I haven't had a chance to try it out just yet, but this seems like a very reasonable workaround in WPF. Thanks! – Emperor XLII May 05 '09 at 13:00
  • 4
    Just in case anyone is wondering, I tested the DrawingVisual WPF method mentioned in this topic and this System.Drawing method, and this System.Drawing method is MUCH FASTER. I'm very disappointed in WPF. – Verax Jan 10 '12 at 05:10
  • 1
    You can do the same thing with WPF, with similar (or better?) performance by using RenderTargetBitmap and DrawingVisual / DrawingContext – David Jeske Jun 07 '17 at 23:43
5

I'm wondering the same thing, as currently I do something like:

DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
   //
   // ... draw on the drawingContext
   //
   RenderTargetBitmap bmp = new RenderTargetBitmap(width, height, dpi, dpi, PixelFormats.Default);
   bmp.Render(drawingVisual);
   image.Source = bmp;
}

I'm trying to use the WriteableBitmap to allow multithreaded access to the pixel buffer, which is currently not allowed with neither a DrawingContext nor a RenderTargetBitmap. Maybe some sort of WritePixels routine based off of what you've retrieved from the RenderTargetBitmap would work?

user7116
  • 63,008
  • 17
  • 141
  • 172
4

It appears the word is no.


For future reference, we plan to use a port of the Writeable Bitmap Extensions for WPF.

For a solution using purely existing code, any of the other suggestions mentioned below will work.

Emperor XLII
  • 13,014
  • 11
  • 65
  • 75
  • 1
    That's no official word. That's just some guy saying something on a forum. – Roman Starkov Jan 03 '12 at 12:22
  • Point taken; updated text. I suppose you could say it meant "official" as in "posted to a forum monitored by officials, and not corrected by them", but that is a bit of a stretch :) – Emperor XLII Jan 04 '12 at 13:44
1

A different way to solve this problem is to use a RenderTargetBitmap as a backing store, just like in the WriteableBitmap example. Then you can create and issue WPF drawing commands to it whenever you want. For example:

// create the backing store in a constructor
var backingStore = 
      new RenderTargetBitmap(200,200,97,97,PixelFormats.Pbgra32);
myImage.Source = backingStore;

// whenever you want to update the bitmap, do:
var drawingVisual = new DrawingVisual();
var drawingContext = drawingVisual.RenderOpen();
{
    // your drawing commands go here
    drawingContext.DrawRectangle(
            Brushes.Red, new Pen(),
            new Rect(this.RenderSize));
}
Render(drawingContext);
drawingContext.Close();
backingStore.Render(drawingVisual);

If you want to redraw this RenderTargetBitmap every frame, you can catch the CompositionTarget.Rendering event, like this:

CompositionTarget.Rendering += MyRenderingHandler;
David Jeske
  • 2,306
  • 24
  • 29