0
votes

I have a WriteableBitmap which I would like to repeatedly clear and redraw.

I have seen references to writing to the Pixels array directly, but the Pixels array is not accessible in WriteableBitmap. I have also tried the suggestion of recreating the WriteableBitmap each time, but my app runs out of memory almost immediately.

Is there a simple way to clear a WriteableBitmap?

4

4 Answers

2
votes

We block copy a blank byte array to the back buffer of the writeable bitmap to do this. We can also copy raw frames at over 30 frames per second for a 1k x 1k bitmap.

Two examples fast or safe(and still pretty fast). I prefer the unsafe version.

    #region External
    [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")]
    public static extern void CopyMemory(IntPtr destination, IntPtr source, uint length);
    #endregion
    private const int PixelHeight = 1024;
    private const int PixelWidth = 1024;
    private const int DpiHeight = 96;
    private const int DpiWidth = 96;
    private const int RgbBytesPerPixel = 3;
    WriteableBitmap _myBitmap = new WriteableBitmap(PixelHeight, PixelWidth, DpiHeight, DpiWidth, PixelFormats.Rgb24, null);
    private readonly byte[] _blankImage = new byte[PixelHeight * PixelWidth * RgbBytesPerPixel];


    private unsafe void FastClear()
    {
        fixed (byte* b = _blankImage)
        {
            CopyMemory(_myBitmap.BackBuffer, (IntPtr)b, (uint)_blankImage.Length);            
        Application.Current.Dispatcher.Invoke(() =>
        {
            _myBitmap.Lock();
            _myBitmap.AddDirtyRect(new Int32Rect(0, 0, _myBitmap.PixelWidth, _myBitmap.PixelHeight));
            _myBitmap.Unlock();
        });
       }
    }
    private void SafeClear()
    {
        GCHandle pinnedArray = new GCHandle();
        IntPtr pointer = IntPtr.Zero;
        try
        {
            //n.b. If pinnedArray is used often wrap it in a class with IDisopsable and keep it around
            pinnedArray = GCHandle.Alloc(_blankImage, GCHandleType.Pinned);
            pointer = pinnedArray.AddrOfPinnedObject();

            CopyMemory(_myBitmap.BackBuffer, pointer, (uint)_blankImage.Length);

            Application.Current.Dispatcher.InvokeAsync(() =>
            {
                _myBitmap.Lock();
                _myBitmap.AddDirtyRect(new Int32Rect(0, 0, _myBitmap.PixelWidth, _myBitmap.PixelHeight));
                _myBitmap.Unlock();
            });
        }

        finally
        {
            pointer = IntPtr.Zero;
            pinnedArray.Free();

        }

    }
1
votes

To use the writeablebitmap.clear() you need the WriteableBitmapEx library. http://writeablebitmapex.codeplex.com

1
votes

The answer given by CCondron will eventually start throwing AccessViolationException. Thankfully we can solve this problem in a simpler, faster, and stable way:

[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern void RtlZeroMemory(IntPtr dst, int length);

protected void ClearWriteableBitmap(WriteableBitmap bmp)
{
    RtlZeroMemory(bmp.BackBuffer, bmp.PixelWidth * bmp.PixelHeight * (bmp.Format.BitsPerPixel / 8));

    bmp.Dispatcher.Invoke(() =>
    {
        bmp.Lock();
        bmp.AddDirtyRect(new Int32Rect(0, 0, bmp.PixelWidth, bmp.PixelHeight));
        bmp.Unlock();
    });
}
0
votes

Using net472 you can clear a WriteableBitmap very fast (1ms on average in my full screen testing) without your own Kernel32.dll import (which is called under the hood anyways):

WriteableBitmap bitmap = ...;
Int32Rect rect = new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
int bytesPerPixel = bitmap.Format.BitsPerPixel / 8; // typically 4 (BGR32)
byte[] empty = new byte[rect.Width * rect.Height * bytesPerPixel]; // cache this one
int emptyStride = rect.Width * bytesPerPixel;
bitmap.WritePixels(rect, empty, emptyStride, 0);

You can easily tweak this code for repetitive calls (store the byte array somewhere for quick retrieval). The tricky thing is the 'stride', which is the size in bytes of one row of pixels. For Bgr32 representation (WriteableBitmap's favorite representation) this should be 4*bitmap.PixelWidth.