从 System.Drawing.Bitmap 加载 WPF BitmapImage

Posted

技术标签:

【中文标题】从 System.Drawing.Bitmap 加载 WPF BitmapImage【英文标题】:Load a WPF BitmapImage from a System.Drawing.Bitmap 【发布时间】:2010-09-10 19:15:33 【问题描述】:

我有一个 System.Drawing.Bitmap 的实例,并希望以 System.Windows.Media.Imaging.BitmapImage 的形式将其提供给我的 WPF 应用程序。

最好的方法是什么?

【问题讨论】:

【参考方案1】:

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

【讨论】:

您可以将此代码添加为 System.Drawing.Bitmap 的扩展方法,类似于 ToBitmapImage() 使用 ImageFormat.Bmp 快一个数量级。 如果其他人对此代码有问题:我必须在设置 bi.StreamSource 之前添加 ms.Seek(0, SeekOrigin.Begin);。我正在使用 .NET 4.0。 @mls 对任何版本的 .net 都是如此。我要潜入那里修复代码;没有人告诉帕维尔。 有人会考虑编辑这个答案,以便将所有(正确的)cmets 集成到其中吗?目前它的投票率很高,但根本不清楚答案或答案+cmets是“正确的”......【参考方案2】:

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

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

我也最终绑定到了 BitmapSource 而不是 BitmapImage ,就像我原来的问题一样

【讨论】:

太棒了!为什么不选择自己的答案作为问题的答案?你的现在好多了。 由于您的答案已经被接受,您可以编辑您的答案以使其更完整。 请注意此代码会泄漏 HBitmap。请参阅***.com/questions/1118496/… 进行修复 警告:This leaks a GDI handle 每次使用它,所以在 10k 调用后它会停止工作(如果你幸运的话,它会停止工作 65k)。如 GetHbitmap 中所述,您绝对必须在该句柄上 p/invoke DeleteObject 对于最后一个参数,我使用了BitmapSizeOptions.FromEmptyOptions(),它适用于我的情况。【参考方案3】:

我知道这已得到解答,但这里有几个扩展方法(用于 .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);

【讨论】:

使用非托管句柄(例如 HBITMAP)时,请考虑使用 SafeHandles,请参阅 ***.com/questions/1546091/…【参考方案4】:

我花了一些时间才让转换双向工作,所以这是我想出的两种扩展方法:

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;
        
    

【讨论】:

我正在使用这个,但是使用 ImageFormat.Png。否则我会在图像上看到黑色背景:***.com/questions/4067448/… 不错的答案和最好的事情:没有互操作。 @DanielWolf:但霍斯特是对的:BMP 格式不支持透明度。应更正此答案以改用 PNG。 如果你想要透明度,BmpBitmapEncoder 应该是 PngBitmapEncoder。否则你会变黑。 @HorstWalter, david.pfx:感谢您的 cmets,这是有道理的。我已经有几年没有使用 .NET,所以我无法快速尝试这些更改。你们谁能编辑我的代码,确保它仍然有效?【参考方案5】:

最简单的事情是如果您可以直接从文件制作 WPF 位图。

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

【讨论】:

【参考方案6】:
// at class level;
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);    // https://***.com/a/1546121/194717


/// <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()”? 见***.com/questions/1546091/…【参考方案7】:

您可以通过编写自定义位图源在两个命名空间(媒体和绘图)之间共享像素数据。转换将立即发生,不会分配额外的内存。如果您不想显式创建位图的副本,这就是您想要的方法。

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

【讨论】:

你能举个例子吗? 正是我要找的东西,我希望在我编译它时这能工作=D 那么如果你有Properties.Resources.Image,你想把它画到画布上,需要133行代码? WPF 不行。 一行可以搞定。但是,如果您想在不制作图像数据的深层副本的情况下执行此操作。这是要走的路。【参考方案8】:

我在一家图像供应商工作,并为我们的图像格式编写了一个 WPF 适配器,类似于 System.Drawing.Bitmap。

我写这个知识库是为了向我们的客户解释它:

http://www.atalasoft.com/kb/article.aspx?id=10156

那里有代码可以做到这一点。您需要将 AtalaImage 替换为 Bitmap 并执行我们正在执行的等效操作 - 它应该非常简单。

【讨论】:

感谢 Lou - 能够用一行代码完成我需要的工作 答案中的链接已失效 "404: Page Not Found". 如果有人出于某种原因仍在寻找这个特定的答案,可以在 archive.org 上找到它:web.archive.org/web/20160622213213/http://www.atalasoft.com/KB/…【参考方案9】:

我对此的看法基于大量资源。 https://***.com/a/7035036https://***.com/a/1470182/360211

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;
            
        
    

【讨论】:

【参考方案10】:

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

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

// 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;

【讨论】:

以上是关于从 System.Drawing.Bitmap 加载 WPF BitmapImage的主要内容,如果未能解决你的问题,请参考以下文章

是否可以从 System.Drawing.Bitmap 创建 Avalonia.Media.Imaging.Bitmap?

从 Android.Graphics.Bitmap.GetPixels 获得的 RGB 值与 System.Drawing.Bitmap.GetPixel 略有不同

将 HBITMAP 句柄从非托管代码传递到托管代码以创建 System.Drawing.Bitmap 的安全性

从 VB 转换为 C#:bitmap.save() 参数错误

图片类处理

System.Drawing.Bitmap.GetHicon() 上的 GDI 对象泄漏