How to edit a WritableBitmap.BackBuffer in non UI

2020-02-05 10:19发布

My application runs CPU-heavy algorythms to edit an Image placed at a WPF window. I need the edition to be done in a background thread. However trying to edit the BackBuffer of WritableBitmap in non UI thread throws InvalidOperationException.

    private WriteableBitmap writeableBitmap;

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        // Create WritableBitmap in UI thread.
        this.writeableBitmap = new WriteableBitmap(10, 10, 96, 96, PixelFormats.Bgr24, null);
        this.image1.Source = this.writeableBitmap;

        // Run code in non UI thread.
        new Thread(
            () =>
            {
                // 'Edit' bitmap in non UI thread.
                this.writeableBitmap.Lock(); // Exception: The calling thread cannot access this object because a different thread owns it.

                // ... At this place the CPU is highly loaded, we edit this.writeableBitmap.BackBuffer.

                this.writeableBitmap.Unlock();
            }).Start();
    }

I have read dozens of manuals, all of them tells me to do the BackBuffer edition in UI thread (i.e MSDN).

How to edit the WritableBitmap.BackBuffer in a non UI thread without any useless buffer copying/cloning?

标签: c# .net wpf
5条回答
冷血范
2楼-- · 2020-02-05 10:59

You simply can't write to BackBuffer from a non UI thread.

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, ...)));
        });
}
查看更多
闹够了就滚
3楼-- · 2020-02-05 11:05

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();
        });
查看更多
疯言疯语
4楼-- · 2020-02-05 11:13

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.

查看更多
冷血范
5楼-- · 2020-02-05 11:17

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

查看更多
\"骚年 ilove
6楼-- · 2020-02-05 11:24

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.

查看更多
登录 后发表回答