Cargue una imagen de mapa de bits WPF desde un System.Drawing.Bitmap

223

Tengo una instancia de a System.Drawing.Bitmapy me gustaría ponerla a disposición de mi aplicación WPF en forma de a System.Windows.Media.Imaging.BitmapImage.

¿Cuál sería el mejor enfoque para esto?

Kevin
fuente

Respuestas:

265

¿Qué hay de cargarlo desde 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();
}
Pawel Lesnikowski
fuente
11
Puede agregar este código como método de extensión en System.Drawing.Bitmap, algo así como ToBitmapImage ()
Luke Puplett
35
Usar ImageFormat.Bmp es un orden de magnitud más rápido.
RandomEngy
20
En caso de que otros tengan problemas con este código: tuve que agregar ms.Seek(0, SeekOrigin.Begin);antes de configurar bi.StreamSource. Estoy usando .NET 4.0.
mlsteeves
66
@mls eso sería cierto para cualquier versión de .net. Voy a colarse allí y arreglar ese código; nadie le dice a Pawel.
77
¿Alguien consideraría editar esta respuesta para que todos los comentarios (correctos) se integren en ella? Por el momento está muy votado, pero no está del todo claro si es la respuesta o la respuesta + comentarios que están 'bien' ...
Benjol
81

Gracias a Hallgrim, aquí está el código con el que terminé:

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

También terminé vinculando a un BitmapSource en lugar de una BitmapImage como en mi pregunta original

Kevin
fuente
2
¡Excelente! ¿Por qué no seleccionas tu propia respuesta como la respuesta a la pregunta? El tuyo es mucho mejor ahora.
Hallgrim
1
Como la suya ya es la respuesta aceptada, puede editar su respuesta para que sea más completa.
Alan Jackson
39
Tenga en cuenta que este código filtra un HBitmap. Vea stackoverflow.com/questions/1118496/… para una solución
Lars Truijens el
28
Advertencia : esto filtra un identificador GDI cada vez que se usa, por lo que después de 10k llamadas dejará de funcionar (65k si tienes suerte). Como se documenta en GetHbitmap , absolutamente debe p / invocar DeleteObjecten ese identificador.
Roman Starkov
1
Para el último parámetro, lo he usado BitmapSizeOptions.FromEmptyOptions(), y funciona bien para mi situación.
Tarik
53

Sé que esto ha sido respondido, pero aquí hay un par de métodos de extensión (para .NET 3.0+) que hacen la conversión. :)

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

y la clase NativeMethods (para apaciguar a 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);
}
Alastair Pitts
fuente
1
Cuando use manijas no administradas (por ejemplo, HBITMAP) considere usar SafeHandles, consulte stackoverflow.com/questions/1546091/…
Jack Ukleja
22

Me llevó algo de tiempo hacer que la conversión funcionara en ambos sentidos, así que aquí están los dos métodos de extensión que se me ocurrieron:

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;
        }
    }
}
Daniel Wolf
fuente
2
Estoy usando esto, pero uso ImageFormat.Png. De lo contrario, aparece un fondo negro en la imagen: stackoverflow.com/questions/4067448/…
Horst Walter
10

Lo más fácil es si puede hacer el mapa de bits WPF desde un archivo directamente.

De lo contrario, deberá usar System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap.

Hallgrim
fuente
9
// at class level;
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);    // https://stackoverflow.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;
}
Tony
fuente
¿Qué es "DeleteObject ()"?
James Esh
6

Puede compartir los datos de píxeles entre ambos espacios de nombres (Medios y Dibujo) escribiendo una fuente de mapa de bits personalizada. La conversión se realizará de inmediato y no se asignará memoria adicional. Si no desea crear explícitamente una copia de su mapa de bits, este es el método que desea.

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
}
Andreas
fuente
¿Puede publicar un ejemplo?
sombrío
1
Exactamente lo que estaba buscando, espero que esto funcione cuando lo compile = D
Greg
Entonces, si tiene Properties.Resources.Image y desea dibujarlo en un lienzo, ¿se necesitan 133 líneas de código? WPF no está bien.
Glenn Maynard
Es posible hacerlo en una línea. Pero si quieres hacerlo sin hacer una copia profunda de los datos de imagen. Este es el camino a seguir.
Andreas
5

Trabajo en un proveedor de imágenes y escribí un adaptador para WPF a nuestro formato de imagen que es similar a un System.Drawing.Bitmap.

Escribí este KB para explicárselo a nuestros clientes:

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

Y hay un código allí que lo hace. Debe reemplazar AtalaImage con Bitmap y hacer lo mismo que estamos haciendo: debería ser bastante sencillo.

Lou Franco
fuente
Gracias Lou - pude hacer lo que necesitaba con una línea de código
Kevin
4

Mi opinión sobre esto se basa en una serie de recursos. https://stackoverflow.com/a/7035036 https://stackoverflow.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;
            }
        }
    }
}
Weston
fuente
2

Llegué a esta pregunta porque estaba tratando de hacer lo mismo, pero en mi caso el Bitmap es de un recurso / archivo. Encontré que la mejor solución es como se describe en el siguiente enlace:

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;
Roland
fuente