Bitmap 到 BitmapSource
如何将 System.Drawing.Bitmap 转换为 System.Windows.Media.Imaging.BitmapSource
引言
当我开始将我的应用程序从 WinForms 转换为 WPF 时,我很快就遇到了需要在 WPF 控件中使用我的 System.Drawing.Bitmap
资源的情况。但是 WPF 使用 System.Windows.Media.Imaging.BitmapSource
。
.NET Framework 提供了一些互操作方法来实现这种转换,但在使用它们时要小心!本文将指出在使用这些方法时需要了解的一些有趣的事情,以及如何避免它们。
Using the Code
我最初的尝试如下所示
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());
}
}
CreateBitmapSourceFromHBitmap
方法完成了所有工作:它返回一个托管的 BitmapSource
,基于提供的指向非托管位图和调色板信息的指针。
这段代码的问题在于对 GetHbitmap
的调用。除非你 P/Invoke 到 DeleteObject()
,否则它会留下一个悬空的 GDI 句柄。
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);
}
}
}
调用 DeleteObject
将释放 GDI 句柄。这种方法在大多数情况下都能完美地工作。但是,如果你需要在多线程环境中工作,请注意不允许在两个不同的线程中同时对同一个位图调用 GetHbitmap
。为了避免这种情况,请使用 lock
关键字创建一个临界区。
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);
}
}
}
}
我认为使用 DllImport
并不优雅,并且我尽量避免在可能的情况下使用它。为了避免使用它,你应该摆脱互操作方法并使用位图解码器。
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;
}
}
}
}
这种方法仍然存在一个问题:该方法需要从 UI 线程调用,否则根据你如何使用位图,可能会稍后抛出异常。
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;
}
}
你真的需要在什么时候进行像这样的转换?
正如我在本文的介绍中所指出的,我需要从 System.Drawing.Bitmap
到 System.Windows.Media.Imaging.BitmapSource
进行转换,因为我的应用程序在 WinForms 和 WPF 之间共享一些资源。事实上,我实在想不出任何其他真正需要这样做的情况(如果你有,请告诉我)。
当然,当你从头开始一个 WPF 应用程序时,你不需要进行这些转换。你应该查看这篇文章 文章(或者在 Google 上搜索它以找到大量关于这个主题的文章),以了解如何在 WPF 应用程序中管理图像。