fast converting Bitmap to BitmapSource wpf
Asked Answered
B

2

29

I need to draw an image on the Image component at 30Hz. I use this code :

public MainWindow()
    {
        InitializeComponent();

        Messenger.Default.Register<Bitmap>(this, (bmp) =>
        {
            ImageTarget.Dispatcher.BeginInvoke((Action)(() =>
            {
                var hBitmap = bmp.GetHbitmap();
                var drawable = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
                  hBitmap,
                  IntPtr.Zero,
                  Int32Rect.Empty,
                  BitmapSizeOptions.FromEmptyOptions());
                DeleteObject(hBitmap);
                ImageTarget.Source = drawable;
            }));
        });
    }

The problem is, with this code, My CPU usage is about 80%, and, without the convertion it's about 6%.

So why converting bitmap is so long ?
Are there faster method (with unsafe code) ?

Blues answered 9/6, 2015 at 8:55 Comment(4)
What is displayed if there is no conversion? CPU consumption is about 6% without any bitmap displayed?Pooka
yes, camera send event with new frame, but no conversion and nothing is displayed.Blues
So how do you know that not all of the 80% CPU consumption is just used for displaying 30 BitmapSources per second, and conversion takes no time at all?Pooka
your probably right, I tested whitout displaying (but keep the conversion) and it's about 40% usage. (it's kind of normal, but a little high). After, I added a GC.Collect(); after ImageTarget.Source = drawable; and my CPU is about 45% usage. So 80% is probably memory leak.Blues
P
59

Here is a method that (to my experience) is at least four times faster than CreateBitmapSourceFromHBitmap.

It requires that you set the correct PixelFormat of the resulting BitmapSource.

public static BitmapSource Convert(System.Drawing.Bitmap bitmap)
{
    var bitmapData = bitmap.LockBits(
        new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
        System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat);

    var bitmapSource = BitmapSource.Create(
        bitmapData.Width, bitmapData.Height,
        bitmap.HorizontalResolution, bitmap.VerticalResolution,
        PixelFormats.Bgr24, null,
        bitmapData.Scan0, bitmapData.Stride * bitmapData.Height, bitmapData.Stride);

    bitmap.UnlockBits(bitmapData);

    return bitmapSource;
}
Pooka answered 9/6, 2015 at 10:21 Comment(0)
B
10

I answered myself before Clemens answer with :

[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);

WriteableBitmap writeableBitmap = new WriteableBitmap(1280, 1024, 96.0, 96.0, PixelFormats.Bgr24, null);

public MainWindow()
{
    InitializeComponent();

    ImageTarget.Source = writeableBitmap;

    Messenger.Default.Register<Bitmap>(this, (bmp) =>
    {
        ImageTarget.Dispatcher.BeginInvoke((Action)(() =>
        {
            BitmapData data = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
            writeableBitmap.Lock();
            CopyMemory(writeableBitmap.BackBuffer, data.Scan0,
                       (writeableBitmap.BackBufferStride * bmp.Height));
            writeableBitmap.AddDirtyRect(new Int32Rect(0, 0, bmp.Width, bmp.Height));
            writeableBitmap.Unlock();
            bmp.UnlockBits(data);
        }));
    });
}

Now my CPU usage is about 15%

Blues answered 9/6, 2015 at 10:36 Comment(6)
Reusing a WriteableBitmap is even better than creating a new one each time. You should probably also show the DllImport declaration of CopyMemory.Pooka
And did you try if the Lock/AddDirtyRect/Unlock sequence is really faster than WritePixels? To my experience it's more or less the same, and the latter would save you some code and a DllImport.Pooka
I've tested with the WritePixel and CopyMemory, it's look like the same, but WritePixel is more readable. I'm going to switch to this solutionBlues
@Epitouille in CopyMemory line it showing error message "system.accessviolationexception attempted to read or write protected memory"Sharonsharona
Copying directly to BackBuffer is slightly faster than WritePixels. WritePixels essentially just does a Lock, MemCpy, AddDirtyRect, and Unlock for you in one step.. but it also performs a bunch of sanity checks on your input buffer which can slow things down. It might not matter for your usecase, but if you are trying to squeeze out every last ms of performance, copy direct to BackBuffer!Antediluvian
How can I make this draw the image not at the top left? Change X and Y of the dirty rect seems to yield no differenceFurriery

© 2022 - 2024 — McMap. All rights reserved.