28

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) ?

Titouan56
  • 6,932
  • 11
  • 37
  • 61
  • What is displayed if there is no conversion? CPU consumption is about 6% without any bitmap displayed? – Clemens Jun 09 '15 at 09:02
  • yes, camera send event with new frame, but no conversion and nothing is displayed. – Titouan56 Jun 09 '15 at 09:04
  • 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? – Clemens Jun 09 '15 at 09:05
  • 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. – Titouan56 Jun 09 '15 at 09:10

2 Answers2

58

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;
}
Clemens
  • 123,504
  • 12
  • 155
  • 268
9

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%

Clemens
  • 123,504
  • 12
  • 155
  • 268
Titouan56
  • 6,932
  • 11
  • 37
  • 61
  • Reusing a WriteableBitmap is even better than creating a new one each time. You should probably also show the DllImport declaration of CopyMemory. – Clemens Jun 09 '15 at 11:11
  • 1
    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. – Clemens Jun 09 '15 at 11:15
  • 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 solution – Titouan56 Jun 09 '15 at 12:35
  • @Epitouille in CopyMemory line it showing error message "system.accessviolationexception attempted to read or write protected memory" – Giresh Jun 16 '17 at 09:48
  • 1
    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! – caesay Sep 04 '20 at 10:06
  • 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 difference – joshcomley Feb 01 '22 at 21:40