Click here to Skip to main content
15,891,375 members
Articles / Desktop Programming / WPF

Bitmap to BitmapSource

Rate me:
Please Sign up or sign in to vote.
4.67/5 (20 votes)
7 Sep 2010CPOL2 min read 182.3K   33   18
How to convert System.Drawing.Bitmap to System.Windows.Media.Imaging.BitmapSource

Introduction

When I started to convert my application from WinForms to WPF, I quickly reached the point where I needed to use my System.Drawing.Bitmap resources in WPF controls. But WPF uses System.Windows.Media.Imaging.BitmapSource.

The .NET Framework provides some interoperability methods to make this conversion but be careful when using them! This article will point some interesting things to know when using these methods and how you can avoid them.

Using the Code

My first attempt looked like this:

C#
public static class Imaging
{
    public static BitmapSource CreateBitmapSourceFromBitmap(Bitmap bitmap)
    {
        if (bitmap == null)
            throw new ArgumentNullException("bitmap");

        return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
            bitmap.GetHbitmap(),
            IntPtr.Zero,
            Int32Rect.Empty,
            BitmapSizeOptions.FromEmptyOptions());
    }
}

The CreateBitmapSourceFromHBitmap method does all the job: it returns a managed BitmapSource, based on the provided pointer to an unmanaged bitmap and palette information.

The problem with this piece of code is the call to GetHbitmap. It will leave a dangling GDI handle unless you P/Invoke to DeleteObject():

C#
public static class Imaging
{
    [DllImport("gdi32.dll")]
    private static extern bool DeleteObject(IntPtr hObject);

    public static BitmapSource CreateBitmapSourceFromBitmap(Bitmap bitmap)
    {
        if (bitmap == null)
            throw new ArgumentNullException("bitmap");

        IntPtr hBitmap = bitmap.GetHbitmap();

        try
        {
            return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
                hBitmap,
                IntPtr.Zero,
                Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());
        }
        finally
        {
            DeleteObject(hBitmap);
        }
    }
}

Calling DeleteObject will release the GDI handle. This method will work perfectly for most cases. However, if you have to work in a multi-threaded environment, be aware that it is not allowed to call GetHbitmap on the same bitmap in two different threads at the same time. To avoid this, use the lock keyword to create a critical section:

C#
public static class Imaging
{
    [DllImport("gdi32.dll")]
    private static extern bool DeleteObject(IntPtr hObject);

    public static BitmapSource CreateBitmapSourceFromBitmap(Bitmap bitmap)
    {
        if (bitmap == null)
            throw new ArgumentNullException("bitmap");

        lock (bitmap)
        {
            IntPtr hBitmap = bitmap.GetHbitmap();

            try
            {
                return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
                    hBitmap,
                    IntPtr.Zero,
                    Int32Rect.Empty,
                    BitmapSizeOptions.FromEmptyOptions());
            }
            finally
            {
                DeleteObject(hBitmap);
            }
        }
    }
}

In my opinion, using DllImport is not elegant and I try to avoid it when possible. To avoid using it, you should get rid of the interoperability method and use Bitmap Decoders:

C#
public static class Imaging
{
    public static BitmapSource CreateBitmapSourceFromBitmap(Bitmap bitmap)
    {
        if (bitmap == null)
            throw new ArgumentNullException("bitmap");

        using (MemoryStream memoryStream = new MemoryStream())
        {
            try
            {
                // You need to specify the image format to fill the stream. 
                // I'm assuming it is PNG
                bitmap.Save(memoryStream, ImageFormat.Png);
                memoryStream.Seek(0, SeekOrigin.Begin);

                BitmapDecoder bitmapDecoder = BitmapDecoder.Create(
                    memoryStream,
                    BitmapCreateOptions.PreservePixelFormat,
                    BitmapCacheOption.OnLoad);
                
                // This will disconnect the stream from the image completely...
                WriteableBitmap writable = 
		new WriteableBitmap(bitmapDecoder.Frames.Single());
                writable.Freeze();

                return writable;
            }
            catch (Exception)
            {
                return null;
            }
        }
    }
}

There is still a problem with this way of doing it: this method needs to be called from the UI thread otherwise it might throw exceptions later depending on how you are using the bitmap.

C#
public static class Imaging
{
    public static BitmapSource CreateBitmapSourceFromBitmap(Bitmap bitmap)
    {
        if (bitmap == null)
            throw new ArgumentNullException("bitmap");
                
        if (Application.Current.Dispatcher == null)
            return null; // Is it possible?
                
        try
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
                // You need to specify the image format to fill the stream. 
                // I'm assuming it is PNG
                bitmap.Save(memoryStream, ImageFormat.Png);
                memoryStream.Seek(0, SeekOrigin.Begin);
                
                // Make sure to create the bitmap in the UI thread
                if (InvokeRequired)
                    return (BitmapSource)Application.Current.Dispatcher.Invoke(
                        new Func<Stream, BitmapSource>(CreateBitmapSourceFromBitmap),
                        DispatcherPriority.Normal,
                        memoryStream);
                
                return CreateBitmapSourceFromBitmap(memoryStream);
            }
        }
        catch (Exception)
        {
            return null;
        }
    }
                
    private static bool InvokeRequired
    {
        get { return Dispatcher.CurrentDispatcher != Application.Current.Dispatcher; }
    }
                
    private static BitmapSource CreateBitmapSourceFromBitmap(Stream stream)
    {
        BitmapDecoder bitmapDecoder = BitmapDecoder.Create(
            stream,
            BitmapCreateOptions.PreservePixelFormat,
            BitmapCacheOption.OnLoad);
                
        // This will disconnect the stream from the image completely...
        WriteableBitmap writable = new WriteableBitmap(bitmapDecoder.Frames.Single());
        writable.Freeze();
                
        return writable;
    }
} 

When Do You Really Need to do Conversions Like These?

As I pointed in the introduction of this article, I needed to make conversions from System.Drawing.Bitmap to System.Windows.Media.Imaging.BitmapSource because my application was sharing some resources between WinForms and WPF. In fact, I can't really think of any other situation where it would be really required to do so (if you have any, let me know).

For sure, you should not need to do these conversions when starting a WPF application from scratch. You should take a look at this article (or Google it to find tons of articles about this subject) to learn how to manage images in a WPF application.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionNice, but this does not support higher pixel depths ... Pin
yvdh27-Feb-12 7:28
yvdh27-Feb-12 7:28 
Hi,

great job, but one of the reasons for converting Wpf to GDI+ images (and vice-versa) is that using WPF it is possible to add support for higher pixel depths to an GDI+ application. GDI+ has support for 48 and 64 bpp images, but it cannot load (decode) or save (encode) them . This means that your code will convert any such image to an 24 or 32 bpp image. Using the System.Windows.Interop.Imageing.CreateBitmapSourceFromHBitmap() does not seem to work either, as it again squashes data to 32 bpp or less. On top of that it loses the metadata (EXIF, IPTC, ...). I am working on a solution, but it's not quite there yet: I can load and save 48 bpp images, but the data is messed up because GDI+, believe it or not only uses 39 bpp instead of 48 bpp internally! Ah well ....

public static void Save(Bitmap img, ImageFormat format, System.IO.Stream stream, int quality = BestJpegQuality)
    {
    if (img.PixelFormat == PixelFormat.Format48bppRgb)
      {
      // Convert GDI+ Bitmap to WPF BitmapSource.
      //IntPtr handleToBitmap = img.GetHbitmap();
      //BitmapSource wpfBitmap = null;
      //The rollowing code is limited to 32 bpp!
      //try
      //  {
      //  wpfBitmap = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(handleToBitmap, IntPtr.Zero, System.Windows.Int32Rect.Empty,
      //      System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions() );
      //  }
      //finally
      //  {
      //  Util.DeleteObject(handleToBitmap);
      //  }

      WriteableBitmap wpfBitmap = new WriteableBitmap(img.Width, img.Height, img.HorizontalResolution, img.VerticalResolution,
        System.Windows.Media.PixelFormats.Rgb48, null);
      BitmapData data = BitmapUtil.LockData(img); // Simple wrapper around Lock method
      wpfBitmap.WritePixels(new System.Windows.Int32Rect(0,0, img.Width , img.Height), data.Scan0, data.Stride * img.Height,  data.Stride, 0, 0);
      BitmapUtil.UnlockData(img, data); // Simple wrapper around Unlock method

      BitmapEncoder encoder = null;
      if (format.Equals(ImageFormat.Jpeg))
        encoder = new JpegBitmapEncoder() { QualityLevel = (int)quality };
      else if (format.Equals(ImageFormat.Tiff))
        encoder = new TiffBitmapEncoder();
      else if (format.Equals(ImageFormat.Png))
        encoder = new PngBitmapEncoder();
      else if (format.Equals(ImageFormat.Bmp))
        encoder = new BmpBitmapEncoder();

      encoder.Frames.Add(BitmapFrame.Create(wpfBitmap));
      encoder.Save(stream);
      stream.Close();

      wpfBitmap = null;
      }
    else
      {
      //Do normal GDI+ save
      if (format.Equals(ImageFormat.Jpeg))
        {
        ImageCodecInfo jpgCodecInfo = GetEncoderInfo("image/jpeg");
        EncoderParameters codecParams = new EncoderParameters();
        codecParams.Param[0] = new EncoderParameter(Encoder.Quality, quality);

        img.Save(stream, jpgCodecInfo, codecParams);
        }
      else
        img.Save(stream, format);
      }
    }

 public static Bitmap Load(string filename)
    {
    ImageFormat format = GetImageFormat(filename); // Extract format from extension

    BitmapDecoder decoder = null;
    if (format.Equals(ImageFormat.Jpeg))
      decoder = new JpegBitmapDecoder(new Uri(filename), BitmapCreateOptions.None, BitmapCacheOption.None);
    else if (format.Equals(ImageFormat.Tiff))
      decoder = new TiffBitmapDecoder(new Uri(filename), BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.None);
    else if (format.Equals(ImageFormat.Png))
      decoder = new PngBitmapDecoder(new Uri(filename), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None);
    else if (format.Equals(ImageFormat.Bmp))
      decoder = new BmpBitmapDecoder(new Uri(filename), BitmapCreateOptions.None, BitmapCacheOption.None);

    BitmapSource bitmapSource = decoder.Frames[0];

    // Convert WPF BitmapSource to GDI+ Bitmap
    System.Drawing.Bitmap bitmap;
    using (MemoryStream outStream = new MemoryStream())
      {
      // from System.Media.BitmapImage to System.Drawing.Bitmap
      BitmapEncoder enc = new PngBitmapEncoder();
      enc.Frames.Add(BitmapFrame.Create(bitmapSource));
      enc.Save(outStream);
      bitmap = new System.Drawing.Bitmap(outStream);
      }
    return (bitmap);
    }


The least we can say is that Microsoft has really made a mess of all this!

Btw. The load method as pointed out is indeed really slow, and it's the conversion which takes up WPF to GDI 95% of the time ...
Cheers

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.