0

What I want to do:

Given a simple Bitmap (System.Drawing.Bitmap) I am willing to output it to my window in WPF application. I am also willing to do it frequently, creating a frame stream.

What I've used:

Firstly, I've been converting Bitmap to a BitmapImage and then assigning it to Source field of an Image control.

The problem with this method is the conversion itself. It's very slow. I haven't found a way that works fast enough, the best one took around 20 ms for a 640x480 Bitmap, which is slow. I am hoping to find a way that would do this for less than 5 ms for any common resolution or a different approach to the whole problem. Perhaps, there is a different control other than Image that would work with pure Bitmap and I wouldn't need to convert. I am also not tied to using WPF, does UWP or the new WinUI 3 have this problem? I have checked UWP uses WriteableBitmap, which would also require conversion, but maybe there is a different way as well?

I have found various conversions some of which are slow and some of which just produce a white image as a result for some reason. I am providing a list below (I've tried more but I don't remember what exactly):

  1. Using the method below. This conversion works but it takes around 20 ms to convert 640x480 ms Bitmap.

Method (source):

public BitmapImage ToBitmapImage(Bitmap bitmap)
{
    using (MemoryStream memory = new MemoryStream())
    {
        bitmap.Save(memory, ImageFormat.Png);

        memory.Position = 0;

        BitmapImage bitmapImage = new BitmapImage();

        bitmapImage.BeginInit();

        bitmapImage.StreamSource = memory;

        bitmapImage.CacheOption = BitmapCacheOption.OnLoad;

        bitmapImage.EndInit();

        bitmapImage.Freeze();

        return bitmapImage;
    }
}
  1. Using Asmak9.EssentialToolKit library (source), but that conversion took around 27 ms, so that was not an option.

  2. Using the method below. This one does not work for me for some strange reason. It runs with no problem, but the result of the conversion is a blank (white) image and not something that was fed into it.

Method (source):

private BitmapSource Convert(Bitmap bmp)
{
  
  var bitmapData = bmp.LockBits(
    new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height),
    System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);  
    
  var bitmapSource = BitmapSource.Create(
    bitmapData.Width, bitmapData.Height,
    bitmap.HorizontalResolution, bitmap.VerticalResolution,
    PixelFormats.Bgr24, null,
    bitmapData.Scan0, bitmapData.Stride * bitmapData.Height, bitmapData.Stride);
     
  bmp.UnlockBits(bitmapData);
  return bitmapSource;
}
  1. Using the method below.This produces the same result as the previous conversion - blank BitmapImage. I am also not sure what could possibly go wrong here.

Method (source):

[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);

private BitmapSource Bitmap2BitmapImage(Bitmap bitmap)
{
    IntPtr hBitmap = bitmap.GetHbitmap();
    BitmapSource retval;

    try
    {
        retval = Imaging.CreateBitmapSourceFromHBitmap(
                        hBitmap,
                        IntPtr.Zero,
                        Int32Rect.Empty,
                        BitmapSizeOptions.FromEmptyOptions());
    }
    finally
    {
        DeleteObject(hBitmap);
    }

    return retval;
}

Perhaps, the last 2 conversions are better, but they do not convert properly, or I am doing something wrong? Or is there a better way in general to discard the conversion step and display the Bitmap straight?

Bobbie
  • 163
  • 1
  • 11
  • Method 3 should be fast. You have to make sure that the pixel formats are matching. – Clemens Apr 21 '21 at 07:14
  • @Clemens thank you for your reply! I just checked, I am using PixelFormats.Rgb24 in the Create function, as my bitmap.PixelFormat returns "Format24bppRgb", but it still gives me white image... What could possibly go wrong here? Also, I checked, BitmapSource.Create returns CachedBitmap and people say you can't convert it to ImageSource, so maybe that is the reason of a blank image? Also, everyone in the internet, it seems, have method 4 working, but it also gives blank image. I am really lost at this point. Do you know what I could have done wrong? – Bobbie Apr 23 '21 at 13:34
  • No, sorry. However, what "people say" is wrong. All WPF bitmap types are derived from ImageSource, and can hence be converted. If not, you would have got a runtime exception. – Clemens Apr 23 '21 at 14:17
  • Also checked my answer here: https://stackoverflow.com/a/30729291/1136211. Your code seems identical. – Clemens Apr 23 '21 at 14:21
  • @Clemens Thanks a lot, I really appreciate it. I figured it out. The problem was that I was calling your Convert method from a different thread and it messed up (possibly because it deals with unmanaged stuff?). Nevertheless, it works amazingly well. Thank you very much! – Bobbie Apr 24 '21 at 12:05
  • Where does the "simple Bitmap" come from, though? Isn't there any way to receive that as a different class from the start? Are those bitmaps files read from disc, or does the data come from something else? In most cases it should not be necessary to use `System.Drawing.Bitmap` in a wpf project. I sincerely hope you're not confusing the Bitmap file format with `System.Drawing.Bitmap`. – Nyerguds May 26 '21 at 09:52
  • @Nyerguds Basically any popular video framework that allows you to view video stream from camera in form of individual frames would give you the frames in form of a System.Drawing.Bitmap, which is quite uncomfortable to use in WPF. If you take AForge for example, it will give you a callback with the Bitmap frame. – Bobbie May 26 '21 at 15:15
  • @Bobbie I see. Annoying. I thought most such frameworks would move away from `System.Drawing` these days, to be more universally usable... – Nyerguds May 26 '21 at 19:58
  • I agree, that would be great. – Bobbie May 27 '21 at 10:40

1 Answers1

0

As Clement pointed out above, Method 3 is the fastest, but it requires 2 things:

  1. Correct pixel format set.
  2. The Convert method should be run in the main thread.
Bobbie
  • 163
  • 1
  • 11