How to edit a WritableBitmap.BackBuffer in non UI thread?

Solution 1:

MSDN suggests writing to the backbuffer in a background thread. Only certain pre- and post update operations need to be carried out on the UI thread. So while the background thread is doing the actual updating, the UI thread is free to do other things:

        //Put this code in a method that is called from the background thread
        long pBackBuffer = 0, backBufferStride = 0;
        Application.Current.Dispatcher.Invoke(() =>
        {//lock bitmap in ui thread
            _bitmap.Lock();
            pBackBuffer = (long)_bitmap.BackBuffer;//Make pointer available to background thread
            backBufferStride = Bitmap.BackBufferStride;
        });
        //Back to the worker thread
        unsafe
        {
            //Carry out updates to the backbuffer here
            foreach (var update in updates)
            {
                long bufferWithOffset = pBackBuffer + GetBufferOffset(update.X, update.Y, backBufferStride);
                *((int*)bufferWithOffset) = update.Color;
            }
        }
        Application.Current.Dispatcher.Invoke(() =>
        {//UI thread does post update operations
            _bitmap.AddDirtyRect(new System.Windows.Int32Rect(0, 0, width, height));
            _bitmap.Unlock();
        });

Solution 2:

As Clemens said, this is impossible.

You have three choices:

1) Do your editing in a buffer and blit when finished as Clemens suggests.

2) Do the editing in very small chunks and schedule them at a nice priority on the GUI thread. If you keep your work chunks small enough, the GUI will remain responsive, but obviously this complicates the edit code.

3) Combine 1 & 2. Edit small chunks in another thread, then blit each chunk as it completes. This keeps GUI responsive without using memory for a full back buffer.

Solution 3:

In addition to what Klaus78 said, i would suggest the following approach:

  1. Perform asynchronous "bitmap editing" code on a separate buffer (e.g. byte[]) in a ThreadPool thread by means of QueueUserWorkItem. Do not create a new Thread every time you need to perform an asynchronous operation. That's what ThreadPool was made for.

  2. Copy the edited buffer by WritePixels in the WriteableBitmap's Dispatcher. No need for Lock/Unlock.

Example:

private byte[] buffer = new buffer[...];

private void UpdateBuffer()
{
    ThreadPool.QueueUserWorkItem(
        o =>
        {
            // write data to buffer...
            Dispatcher.BeginInvoke((Action)(() => writeableBitmap.WritePixels(..., buffer, ...)));
        });
}

Solution 4:

In WPF cross thread calls are done using the Dispatcher class.

In your case in the no-UI thread you need to get the instance of the Dispatcher of the thread where WritableBitmap is created.

On that dispatcher then call Invoke (or BeginInvoke if you want it asynchron)

Invoke then calls a delegate function where the BackBuffer is edited

Solution 5:

I implemented the following, based on this answer:

In the view model, there is a property like this, that is bound to the Image source in XAML:

private WriteableBitmap cameraImage;
private IntPtr cameraBitmapPtr;
public WriteableBitmap CameraImage
{
    get { return cameraImage; }
    set
    {
        cameraImage = value;
        cameraBitmapPtr = cameraImage.BackBuffer;
        NotifyPropertyChanged();
    }
}

Using a property means that if the WritableBitmap changes, e.g. because of resolution, it would be updated in the View and also a new IntPtr will be constructed.

The image is constructed when appropriate:

CameraImage = new WriteableBitmap(2448, 2048, 0, 0, PixelFormats.Bgr24, null);

In the update thread, a new image is copied in, e.g. using:

[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")]
public static extern void CopyMemory(IntPtr Destination, IntPtr Source, uint Length);

you would do

CopyMemory(cameraImagePtr, newImagePtr, 2448 * 2048 * 3);

There might be a better function for this...

In the same thread, after the copy:

parent.Dispatcher.Invoke(new Action(() =>
{
    cameraImage.Lock();
    cameraImage.AddDirtyRect(new Int32Rect(0, 0, cameraImage.PixelWidth, cameraImage.PixelHeight));
    cameraImage.Unlock();
}), DispatcherPriority.Render);

where parent is the Control / Window with the Image.