Charger une image bitmap WPF à partir d'un System.Drawing.Bitmap

223

J'ai une instance de a System.Drawing.Bitmapet j'aimerais la mettre à la disposition de mon application WPF sous la forme d'un System.Windows.Media.Imaging.BitmapImage.

Quelle serait la meilleure approche pour cela?

Kevin
la source

Réponses:

265

Que diriez-vous de le charger à partir de 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
la source
11
Vous pouvez ajouter ce code comme méthode d'extension sur System.Drawing.Bitmap, quelque chose comme ToBitmapImage ()
Luke Puplett
35
L'utilisation d'ImageFormat.Bmp est un ordre de grandeur plus rapide.
RandomEngy
20
Dans le cas où d'autres ont des problèmes avec ce code: j'ai dû ajouter ms.Seek(0, SeekOrigin.Begin);avant de régler bi.StreamSource. J'utilise .NET 4.0.
mlsteeves
6
@mls qui serait vrai pour n'importe quelle version de .net. Je vais me faufiler là-dedans et corriger ce code; personne ne le dit à Pawel.
7
Quelqu'un pourrait-il envisager de modifier cette réponse afin que tous les (bons) commentaires y soient intégrés? En ce moment, il est fortement voté, mais il n'est pas du tout clair si c'est la réponse ou la réponse + les commentaires qui sont ``
corrects
81

Merci à Hallgrim, voici le code avec lequel je me suis retrouvé:

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

J'ai également fini par me lier à une BitmapSource au lieu d'une BitmapImage comme dans ma question d'origine

Kevin
la source
2
Génial! Pourquoi ne choisissez-vous pas votre propre réponse comme réponse à la question? Le vôtre va beaucoup mieux maintenant.
Hallgrim
1
Puisque la vôtre est déjà la réponse acceptée, vous pouvez modifier votre réponse pour la rendre plus complète.
Alan Jackson
39
Gardez à l'esprit que ce code laisse échapper un HBitmap. Voir stackoverflow.com/questions/1118496/… pour un correctif
Lars Truijens
28
Avertissement : Cela fuit un handle GDI à chaque fois qu'il est utilisé, donc après 10 000 appels, il cessera de fonctionner (65 000 si vous êtes chanceux). Comme indiqué dans GetHbitmap , vous devez absolument p / invoquer DeleteObjectsur ce handle.
Roman Starkov
1
Pour le dernier paramètre, j'ai utilisé BitmapSizeOptions.FromEmptyOptions(), et cela fonctionne très bien avec ma situation.
Tarik
53

Je sais que cela a été répondu, mais voici quelques méthodes d'extension (pour .NET 3.0+) qui effectuent la conversion. :)

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

et la classe NativeMethods (pour apaiser 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
la source
1
Lorsque vous utilisez des poignées non gérées (par exemple HBITMAP), envisagez d'utiliser SafeHandles, voir stackoverflow.com/questions/1546091/…
Jack Ukleja
22

Il m'a fallu un certain temps pour que la conversion fonctionne dans les deux sens, voici donc les deux méthodes d'extension que j'ai trouvées:

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
la source
2
J'utilise ceci, mais utilisez ImageFormat.Png. Sinon, je reçois un fond noir sur l'image: stackoverflow.com/questions/4067448/…
Horst Walter
10

Le plus simple est de pouvoir créer directement le bitmap WPF à partir d'un fichier.

Sinon, vous devrez utiliser System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap.

Hallgrim
la source
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
la source
Qu'est-ce que "DeleteObject ()"?
James Esh
6

Vous pouvez simplement partager les pixeldata entre les deux espaces de noms (Media et Drawing) en écrivant une bitmapsource personnalisée. La conversion se fera immédiatement et aucune mémoire supplémentaire ne sera allouée. Si vous ne souhaitez pas créer explicitement une copie de votre Bitmap, c'est la méthode que vous souhaitez.

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
la source
pouvez-vous poster un exemple?
shady
1
Exactement ce que je cherchais, j'espère que cela fonctionnera quand je le compilerai = D
Greg
Donc, si vous avez Properties.Resources.Image et que vous souhaitez le dessiner dans un canevas, cela prend 133 lignes de code? WPF ne va pas.
Glenn Maynard
Il est possible de le faire en une seule ligne. Mais si vous voulez le faire sans faire une copie complète des images imagées. C'est la voie à suivre.
Andreas
5

Je travaille chez un fournisseur d'imagerie et j'ai écrit un adaptateur pour WPF à notre format d'image qui est similaire à un System.Drawing.Bitmap.

J'ai écrit ce KB pour l'expliquer à nos clients:

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

Et il y a du code qui le fait. Vous devez remplacer AtalaImage par Bitmap et faire la même chose que nous faisons - cela devrait être assez simple.

Lou Franco
la source
Merci Lou - a pu faire ce dont j'avais besoin avec une seule ligne de code
Kevin
4

Mon point de vue sur cette construction à partir d'un certain nombre de ressources. 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
la source
2

Je suis venu à cette question parce que j'essayais de faire la même chose, mais dans mon cas, le bitmap provient d'une ressource / d'un fichier. J'ai trouvé que la meilleure solution est celle décrite dans le lien suivant:

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
la source