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:
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()
:
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:
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:
public static class Imaging
{
public static BitmapSource CreateBitmapSourceFromBitmap(Bitmap bitmap)
{
if (bitmap == null)
throw new ArgumentNullException("bitmap");
using (MemoryStream memoryStream = new MemoryStream())
{
try
{
bitmap.Save(memoryStream, ImageFormat.Png);
memoryStream.Seek(0, SeekOrigin.Begin);
BitmapDecoder bitmapDecoder = BitmapDecoder.Create(
memoryStream,
BitmapCreateOptions.PreservePixelFormat,
BitmapCacheOption.OnLoad);
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.
public static class Imaging
{
public static BitmapSource CreateBitmapSourceFromBitmap(Bitmap bitmap)
{
if (bitmap == null)
throw new ArgumentNullException("bitmap");
if (Application.Current.Dispatcher == null)
return null;
try
{
using (MemoryStream memoryStream = new MemoryStream())
{
bitmap.Save(memoryStream, ImageFormat.Png);
memoryStream.Seek(0, SeekOrigin.Begin);
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);
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.