0

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?

David Sykes
  • 48,469
  • 17
  • 71
  • 80

4 Answers4

2

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();

        }

    }
CCondron
  • 1,926
  • 17
  • 27
1

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

TowmeyKaw
  • 89
  • 1
  • 4
1

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();
    });
}
Artfunkel
  • 1,832
  • 17
  • 23
  • which option was throwing the access exception? In production we generally keep the pointers alive by pinning the blank image once and avoiding looping calls to either fixed or GCHandle.Alloc. Nice simplification btw. – CCondron May 18 '20 at 20:48
  • IIRC exceptions would start to appear at an unpredictable point elsewhere in the application, presumably after a GC run shuffled memory about. – Artfunkel Jul 19 '20 at 10:57
  • I went back and reviewed the production code, it looks like the difference is using 'Invoke' rather than "InvokeAsync' and it's inside the fixed scope. I'll update the example. – CCondron Jul 21 '20 at 16:59
1

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.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Patrick Stalph
  • 792
  • 1
  • 9
  • 19