9

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?

Vasyl Boroviak
  • 5,959
  • 5
  • 51
  • 70

5 Answers5

12

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();
        });
LOAS
  • 7,161
  • 2
  • 28
  • 25
  • I don't think this is the right approach. The two Invoke calls will block longer than the time it would take to write the buffer. – Brannon Feb 13 '14 at 21:28
  • I completely agree for scenarios where the update of the pixel buffer is a speedy process. If the buffer update involves some expensive computation (ie costing more than the two invoke calls) then the above should still be a perfectly viable approach. – LOAS Feb 17 '14 at 14:53
4

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.

GazTheDestroyer
  • 20,722
  • 9
  • 70
  • 103
  • Yeah. The second approach difficult to implement, whereas first one can be done easily using http://writeablebitmapex.codeplex.com/ There is a fast and nice Blit function. And the third approach is the best one of course, however I got no time to enhance our app that much. – Vasyl Boroviak Mar 26 '12 at 09:39
  • Suggestion #1 didn't worked out because `Dispatcher.BeginInvoke(...writeableBitmap.WritePixels(..., buffer, ...));` takes too much time. As the result applcation glitches a lot. Moreover, doing CPU-heavy stuff in UI thread looks even more responsive. Now I'll try to reimplement WritableBitmap using two regular Bitmaps. – Vasyl Boroviak Mar 26 '12 at 16:19
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, ...)));
        });
}
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • I've posted `new Thread()` in my question just to implicilty state that the operation should be done in some other (random) thread. – Vasyl Boroviak Mar 26 '12 at 09:23
  • Also in my question I stated that I do not want to create any buffers, but I forgot to mention that my bitmap is huuuuge. That is why your approach a little bit not the one I need. :) – Vasyl Boroviak Mar 26 '12 at 09:24
  • Yes, but i'm afraid that otherwise the answer to your question simply is: you can't. – Clemens Mar 26 '12 at 09:29
  • 1
    I thought that the BackBuffer exists there specifically for a background edit operations, however Microsoft decided differently. Thank you anyway. – Vasyl Boroviak Mar 26 '12 at 09:36
  • 1
    I wouldn't say "simply can't" since you actually can :) See my reply for code. – LOAS Sep 25 '12 at 16:55
  • WritePixels calls internally Lock() and Unlock() so there isnt really an advantage when using this method (source: https://referencesource.microsoft.com/#PresentationCore/Core/CSharp/System/Windows/Media/Imaging/WriteableBitmap.cs,1101) – Snicker Dec 25 '16 at 23:42
1

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.

geometrikal
  • 3,195
  • 2
  • 29
  • 40
1

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

Klaus78
  • 11,648
  • 5
  • 32
  • 28