从System.Drawing.Bitmap加载WPF BitmapImage

237

我有一个 System.Drawing.Bitmap 实例,希望以 System.Windows.Media.Imaging.BitmapImage 的形式在我的 WPF 应用程序中使用它。

针对此问题,最好的方法是什么?

10个回答

277

从MemoryStream加载如何?

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();
}

11
你可以将这段代码作为扩展方法添加到System.Drawing.Bitmap上,类似于ToBitmapImage()。 - Luke Puplett
36
使用ImageFormat.Bmp会快上一个数量级。 - RandomEngy
21
如果其他人在使用这段代码时遇到问题:在设置 bi.StreamSource 之前,我不得不添加 ms.Seek(0, SeekOrigin.Begin);。我正在使用 .NET 4.0。 - mlsteeves
8
有人能否考虑编辑这个回答,将所有(正确的)评论整合到其中?目前它得到了很多赞,但是不清楚是回答还是回答+评论是“正确”的... - Benjol
7
还需要补充的是,虽然ImageFormat.Bmp更快,但会放弃原始Bitmap所具有的透明度信息。 - Pyritie
显示剩余5条评论

90

感谢Hallgrim,这是我最终得到的代码:

ScreenCapture = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
   bmp.GetHbitmap(), 
   IntPtr.Zero, 
   System.Windows.Int32Rect.Empty, 
   BitmapSizeOptions.FromWidthAndHeight(width, height));

我最终绑定到了一个BitmapSource而不是原来问题中的BitmapImage。


2
太好了!为什么不将自己的答案选为问题的答案呢?你的现在更好了。 - Hallgrim
1
既然你的回答已经被接受了,你可以编辑你的回答使其更完整。 - Alan Jackson
41
请注意,这段代码会泄露HBitmap。请查看https://dev59.com/xHNA5IYBdhLWcg3wBpHs#1118557获取修复方法。 - Lars Truijens
30
警告:每次使用此方法都会泄露一个GDI句柄(https://dev59.com/7nI_5IYBdhLWcg3wDOnW),因此在调用10k次后它将停止工作(如果你幸运的话可能是65k)。正如[GetHbitmap](http://msdn.microsoft.com/en-us/library/system.drawing.bitmap.gethbitmap.aspx)中所述,你必须对该句柄进行p/invoke DeleteObject操作。 - Roman Starkov
1
对于最后一个参数,我使用了BitmapSizeOptions.FromEmptyOptions(),在我的情况下它完全可以正常工作。 - Tarik
显示剩余3条评论

55

我知道这个问题已经有了答案,但是这里有几个扩展方法(适用于 .NET 3.0+),可以进行转换。 :)

        /// <summary>
    /// Converts a <see cref="System.Drawing.Image"/> into a WPF <see cref="BitmapSource"/>.
    /// </summary>
    /// <param name="source">The source image.</param>
    /// <returns>A BitmapSource</returns>
    public static BitmapSource ToBitmapSource(this System.Drawing.Image source)
    {
        System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(source);

        var bitSrc = bitmap.ToBitmapSource();

        bitmap.Dispose();
        bitmap = null;

        return bitSrc;
    }

    /// <summary>
    /// Converts a <see cref="System.Drawing.Bitmap"/> into a WPF <see cref="BitmapSource"/>.
    /// </summary>
    /// <remarks>Uses GDI to do the conversion. Hence the call to the marshalled DeleteObject.
    /// </remarks>
    /// <param name="source">The source bitmap.</param>
    /// <returns>A BitmapSource</returns>
    public static BitmapSource ToBitmapSource(this System.Drawing.Bitmap source)
    {
        BitmapSource bitSrc = null;

        var hBitmap = source.GetHbitmap();

        try
        {
            bitSrc = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
                hBitmap,
                IntPtr.Zero,
                Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());
        }
        catch (Win32Exception)
        {
            bitSrc = null;
        }
        finally
        {
            NativeMethods.DeleteObject(hBitmap);
        }

        return bitSrc;
    }

还有 NativeMethods 类(为了满足 FxCop 的要求)

    /// <summary>
/// FxCop requires all Marshalled functions to be in a class called NativeMethods.
/// </summary>
internal static class NativeMethods
{
    [DllImport("gdi32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern bool DeleteObject(IntPtr hObject);
}

2
在使用不受托管的句柄(例如 HBITMAP)时,请考虑使用 SafeHandles,请参见 https://dev59.com/7nI_5IYBdhLWcg3wDOnW#7035036。 - Jack Ukleja

30

我花了一些时间才使得双向转换工作起来,所以这里是我想出的两个扩展方法:

using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Media.Imaging;

public static class BitmapConversion {

    public static Bitmap ToWinFormsBitmap(this BitmapSource bitmapsource) {
        using (MemoryStream stream = new MemoryStream()) {
            BitmapEncoder enc = new BmpBitmapEncoder();
            enc.Frames.Add(BitmapFrame.Create(bitmapsource));
            enc.Save(stream);

            using (var tempBitmap = new Bitmap(stream)) {
                // According to MSDN, one "must keep the stream open for the lifetime of the Bitmap."
                // So we return a copy of the new bitmap, allowing us to dispose both the bitmap and the stream.
                return new Bitmap(tempBitmap);
            }
        }
    }

    public static BitmapSource ToWpfBitmap(this Bitmap bitmap) {
        using (MemoryStream stream = new MemoryStream()) {
            bitmap.Save(stream, ImageFormat.Bmp);

            stream.Position = 0;
            BitmapImage result = new BitmapImage();
            result.BeginInit();
            // According to MSDN, "The default OnDemand cache option retains access to the stream until the image is needed."
            // Force the bitmap to load right now so we can dispose the stream.
            result.CacheOption = BitmapCacheOption.OnLoad;
            result.StreamSource = stream;
            result.EndInit();
            result.Freeze();
            return result;
        }
    }
}

3
我在使用这个代码,但是要使用ImageFormat.Png。否则图片会出现黑色背景:https://dev59.com/hG855IYBdhLWcg3w75Eu - Horst Walter
很好的答案,最棒的是:没有Interop。 - david.pfx
1
@DanielWolf:但是Horst是正确的:BMP格式不支持透明度。这个答案应该更正为使用PNG。 - david.pfx
1
如果你想要透明度,那么BmpBitmapEncoder应该改为PngBitmapEncoder。否则你会得到黑色。 - david.pfx
@HorstWalter,david.pfx:感谢您的评论,这很有道理。我已经好几年没有使用.NET了,所以我不能快速尝试这些更改。你们两个能否编辑我的代码,确保它仍然可以工作? - Daniel Wolf
在ToWinFormsBitmap方法中使用PngBitmapEncoder而不是BmpBitmapEncoder(),以保持透明度和良好的质量。 - fsbflavio

10
// at class level;
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);    // https://dev59.com/7nI_5IYBdhLWcg3wDOnW#1546121


/// <summary> 
/// Converts a <see cref="System.Drawing.Bitmap"/> into a WPF <see cref="BitmapSource"/>. 
/// </summary> 
/// <remarks>Uses GDI to do the conversion. Hence the call to the marshalled DeleteObject. 
/// </remarks> 
/// <param name="source">The source bitmap.</param> 
/// <returns>A BitmapSource</returns> 
public static System.Windows.Media.Imaging.BitmapSource ToBitmapSource(this System.Drawing.Bitmap source)
{
    var hBitmap = source.GetHbitmap();
    var result = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, System.Windows.Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());

    DeleteObject(hBitmap);

    return result;
}

什么是 "DeleteObject()"? - James Esh
请参见https://dev59.com/7nI_5IYBdhLWcg3wDOnW。 - tofutim

10

如果能直接从文件中创建WPF位图,则最简单的方法就是这样。

否则,您将不得不使用System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap。


6

您可以通过编写自定义的BitmapSource,直接在Media和Drawing两个命名空间之间共享像素数据。转换将立即进行,不会分配额外的内存。如果您不想显式地创建位图的副本,则使用此方法。

class SharedBitmapSource : BitmapSource, IDisposable
{
    #region Public Properties

    /// <summary>
    /// I made it public so u can reuse it and get the best our of both namespaces
    /// </summary>
    public Bitmap Bitmap { get; private set; }

    public override double DpiX { get { return Bitmap.HorizontalResolution; } }

    public override double DpiY { get { return Bitmap.VerticalResolution; } }

    public override int PixelHeight { get { return Bitmap.Height; } }

    public override int PixelWidth { get { return Bitmap.Width; } }

    public override System.Windows.Media.PixelFormat Format { get { return ConvertPixelFormat(Bitmap.PixelFormat); } }

    public override BitmapPalette Palette { get { return null; } }

    #endregion

    #region Constructor/Destructor

    public SharedBitmapSource(int width, int height,System.Drawing.Imaging.PixelFormat sourceFormat)
        :this(new Bitmap(width,height, sourceFormat) ) { }

    public SharedBitmapSource(Bitmap bitmap)
    {
        Bitmap = bitmap;
    }

    // Use C# destructor syntax for finalization code.
    ~SharedBitmapSource()
    {
        // Simply call Dispose(false).
        Dispose(false);
    }

    #endregion

    #region Overrides

    public override void CopyPixels(Int32Rect sourceRect, Array pixels, int stride, int offset)
    {
        BitmapData sourceData = Bitmap.LockBits(
        new Rectangle(sourceRect.X, sourceRect.Y, sourceRect.Width, sourceRect.Height),
        ImageLockMode.ReadOnly,
        Bitmap.PixelFormat);

        var length = sourceData.Stride * sourceData.Height;

        if (pixels is byte[])
        {
            var bytes = pixels as byte[];
            Marshal.Copy(sourceData.Scan0, bytes, 0, length);
        }

        Bitmap.UnlockBits(sourceData);
    }

    protected override Freezable CreateInstanceCore()
    {
        return (Freezable)Activator.CreateInstance(GetType());
    }

    #endregion

    #region Public Methods

    public BitmapSource Resize(int newWidth, int newHeight)
    {
        Image newImage = new Bitmap(newWidth, newHeight);
        using (Graphics graphicsHandle = Graphics.FromImage(newImage))
        {
            graphicsHandle.InterpolationMode = InterpolationMode.HighQualityBicubic;
            graphicsHandle.DrawImage(Bitmap, 0, 0, newWidth, newHeight);
        }
        return new SharedBitmapSource(newImage as Bitmap);
    }

    public new BitmapSource Clone()
    {
        return new SharedBitmapSource(new Bitmap(Bitmap));
    }

    //Implement IDisposable.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    #endregion

    #region Protected/Private Methods

    private static System.Windows.Media.PixelFormat ConvertPixelFormat(System.Drawing.Imaging.PixelFormat sourceFormat)
    {
        switch (sourceFormat)
        {
            case System.Drawing.Imaging.PixelFormat.Format24bppRgb:
                return PixelFormats.Bgr24;

            case System.Drawing.Imaging.PixelFormat.Format32bppArgb:
                return PixelFormats.Pbgra32;

            case System.Drawing.Imaging.PixelFormat.Format32bppRgb:
                return PixelFormats.Bgr32;

        }
        return new System.Windows.Media.PixelFormat();
    }

    private bool _disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                // Free other state (managed objects).
            }
            // Free your own state (unmanaged objects).
            // Set large fields to null.
            _disposed = true;
        }
    }

    #endregion
}

1
你能发一个例子吗? - shady
1
正是我所需要的,希望在编译时能够正常工作 =D - Greg
所以,如果你有Properties.Resources.Image并且想要将其绘制到画布中,需要133行代码吗?WPF不行。 - Glenn Maynard
可以用一行代码完成。但是如果你不想创建imagedata的深度拷贝,这就是正确的方法。 - Andreas
我在这里没有看到bitmapImage.. 这怎么回答问题?你有如何使用它的例子吗? - john k
我尝试了这个,但无法让它工作。当我尝试执行 "imgTop.Source = ss;" 时,出现了一个错误 "在 PresentationCore.dll 中发生了一个 'System.NotImplementedException' 类型的异常,但在用户代码中没有处理。该方法或操作未实现。" - undefined

5
我在一家图像供应商工作,为WPF编写了一个适配器,用于将我们的图像格式转换成类似于System.Drawing.Bitmap的格式。
我撰写了这篇文章来向我们的客户解释它:http://www.atalasoft.com/kb/article.aspx?id=10156。其中包含代码示例,您只需要将AtalaImage替换为Bitmap,并执行与我们所做的相同的操作即可,这应该很简单。

谢谢Lou - 我用一行代码就可以做到我需要的。 - Kevin
链接失效,显示“404:页面未找到”。 - Pang
2
如果有人因某种原因仍在寻找这个特定的答案,可以在archive.org上找到它:https://web.archive.org/web/20160622213213/http://www.atalasoft.com/KB/Article.aspx?id=10156 - JaykeBird

4

以下是我根据多个资源整理的内容。 https://dev59.com/7nI_5IYBdhLWcg3wDOnW#7035036 https://dev59.com/1XVD5IYBdhLWcg3wE3bz#1470182

这篇文章是关于IT技术方面的内容。
using System;
using System.Drawing;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using Microsoft.Win32.SafeHandles;

namespace WpfHelpers
{
    public static class BitmapToBitmapSource
    {
        public static BitmapSource ToBitmapSource(this Bitmap source)
        {
            using (var handle = new SafeHBitmapHandle(source))
            {
                return Imaging.CreateBitmapSourceFromHBitmap(handle.DangerousGetHandle(),
                    IntPtr.Zero, Int32Rect.Empty,
                    BitmapSizeOptions.FromEmptyOptions());
            }
        }

        [DllImport("gdi32")]
        private static extern int DeleteObject(IntPtr o);

        private sealed class SafeHBitmapHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            [SecurityCritical]
            public SafeHBitmapHandle(Bitmap bitmap)
                : base(true)
            {
                SetHandle(bitmap.GetHbitmap());
            }

            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
            protected override bool ReleaseHandle()
            {
                return DeleteObject(handle) > 0;
            }
        }
    }
}

2

我来到这个问题是因为我也在尝试做同样的事情,但在我的情况下,位图来自资源/文件。我发现最好的解决方案如下链接所述:

http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.bitmapimage.aspx

(请注意保留HTML标签)
// Create the image element.
Image simpleImage = new Image();    
simpleImage.Width = 200;
simpleImage.Margin = new Thickness(5);

// Create source.
BitmapImage bi = new BitmapImage();
// BitmapImage.UriSource must be in a BeginInit/EndInit block.
bi.BeginInit();
bi.UriSource = new Uri(@"/sampleImages/cherries_larger.jpg",UriKind.RelativeOrAbsolute);
bi.EndInit();
// Set the image source.
simpleImage.Source = bi;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接