Why does my WriteableBitmap not display on my Image?

Viewed 270

Note: this was fixed by updating the drivers, as noted in the duplicate.

In my real WPF (.NET 6) application, I have an Image that I want to use to display a camera feed. I receive 30fps and I want to render those frames as efficiently as possible.

I had expected to be able to create a single WriteableBitmap and a single Image, set the bitmap as the source for the image, then call WriteableBitmap.WritePixels for each frame. I'm already dispatching to the UI thread, so there should be no threading issues there.

In reality, this creates a very jerky image - at least on my development laptops. (Weirdly, it doesn't appear to be a problem on the desktop I use for "production" with this application. The desktop is significantly more powerful than the laptops, if that's relevant.) If I create a new WriteableBitmap on each frame, the problem goes away - but that's very inefficient. (If I'm going to create a new source on each frame, I can just use BitmapSource.Create.)

I've reproduced the problem in the sample code below. The intention is that every time you click the "Generate" button, it replaces the content of the image with random bytes (white noise with colours, effectively). In reality, with the code as shown, the "noise" is never generated on one laptop. On the other laptop (where the real app is still jerky) the demo app works fine. I'm baffled.

Code:

using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Demo;

public partial class MainWindow : Window
{
    private WriteableBitmap bitmap;

    public MainWindow()
    {
        InitializeComponent();
        // If this code is moved into DrawImage, it's fine
        bitmap = new WriteableBitmap(128, 128, 96.0, 96.0, PixelFormats.Pbgra32, null);
        image.Source = bitmap;
    }

    private void DrawImage(object sender, RoutedEventArgs e)
    {
        var data = new byte[128 * 128 * 4];
        var random = new Random();
        random.NextBytes(data);
        bitmap.WritePixels(new Int32Rect(0, 0, 128, 128), data, 128 * 4, 0);
    }
}

XAML:

<Window x:Class="Demo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Height="200" Width="150">
    <StackPanel>
        <Image Width="128" Height="128" x:Name="image" />
        <Button Margin="5" Click="DrawImage" HorizontalAlignment="Center">Generate</Button>
    </StackPanel>
</Window>

As noted in the comment, if you move the lines in the constructor to the DrawImage method (creating a new WriteableBitmap for each button click) it works fine.

Some variations on this:

  • If you move just image.Source into DrawImage, then the first image is generated correctly, but after that it doesn't work
  • If you make the method asynchronous add a Task.Delay call, then if the Task.Delay is before the place where image.Source is set, it's fine, but if the Task.Delay comes between the image.Source assignment and the WritePixels call, the image isn't shown.

I've also tried using explicit Lock and Unlock calls (with AddDirtyRect) - it doesn't help.

The example code in the WriteableBitmap documentation behaves similarly: the first pixel to be drawn "works", but that's the only one.

I've tried this in .NET 4.8 as well, but that fails too.

This looks to be pretty simple code - what am I missing? (I'm sure I'll kick myself...)

0 Answers
Related