65.9K
CodeProject 正在变化。 阅读更多。
Home

Bitmap 到 BitmapSource

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (19投票s)

Aug 26, 2010

CPOL

2分钟阅读

viewsIcon

187522

如何将 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 应用程序中管理图像。

© . All rights reserved.