Laden Sie ein WPF-BitmapImage aus einer System.Drawing.Bitmap

223

Ich habe eine Instanz von a System.Drawing.Bitmapund möchte sie meiner WPF-App in Form von a zur Verfügung stellen System.Windows.Media.Imaging.BitmapImage.

Was wäre der beste Ansatz dafür?

Kevin
quelle

Antworten:

265

Wie wäre es mit Laden aus 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
quelle
11
Sie können diesen Code als Erweiterungsmethode auf System.Drawing.Bitmap hinzufügen, ähnlich wie ToBitmapImage ()
Luke Puplett
35
Die Verwendung von ImageFormat.Bmp ist um eine Größenordnung schneller.
RandomEngy
20
Falls andere Probleme mit diesem Code haben: Ich musste ms.Seek(0, SeekOrigin.Begin);vor dem Einstellen hinzufügen bi.StreamSource. Ich verwende .NET 4.0.
mlsteeves
6
@mls das würde für jede Version von .net gelten. Ich werde mich da reinschleichen und den Code reparieren. niemand sagt es Pawel.
7
Würde jemand in Betracht ziehen, diese Antwort so zu bearbeiten, dass alle (richtigen) Kommentare darin integriert sind? Im Moment ist es stark positiv bewertet, aber es ist überhaupt nicht klar, ob es die Antwort oder die Antwort + Kommentare sind, die 'richtig' sind ...
Benjol
81

Dank Hallgrim ist hier der Code, mit dem ich gelandet bin:

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

Am Ende habe ich mich auch an eine BitmapSource anstatt an ein BitmapImage gebunden, wie in meiner ursprünglichen Frage

Kevin
quelle
2
Toll! Warum wählen Sie nicht Ihre eigene Antwort als Antwort auf die Frage? Dein ist jetzt viel besser.
Hallgrim
1
Da Ihre Antwort bereits akzeptiert wurde, können Sie Ihre Antwort bearbeiten, um sie vollständiger zu gestalten.
Alan Jackson
39
Beachten Sie, dass dieser Code eine HBitmap verliert. Siehe stackoverflow.com/questions/1118496/… für eine Lösung
Lars Truijens
28
Warnung : Dadurch wird jedes Mal, wenn ein GDI-Handle verwendet wird, ein Leck verloren. Nach 10.000 Anrufen funktioniert es nicht mehr (65.000, wenn Sie Glück haben). Wie in dokumentiert GetHbitmap , Sie absolut Muss p / invoke DeleteObjectan diesem Griff.
Roman Starkov
1
Für den letzten Parameter habe ich verwendet BitmapSizeOptions.FromEmptyOptions(), und es funktioniert gut für meine Situation.
Tarik
53

Ich weiß, dass dies beantwortet wurde, aber hier sind einige Erweiterungsmethoden (für .NET 3.0+), die die Konvertierung durchführen. :) :)

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

und die NativeMethods-Klasse (um FxCop zu beschwichtigen)

    /// <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
quelle
1
Wenn Sie nicht verwaltete Handles (z. B. HBITMAP) verwenden, sollten Sie SafeHandles verwenden, siehe stackoverflow.com/questions/1546091/…
Jack Ukleja
22

Ich habe einige Zeit gebraucht, um die Konvertierung in beide Richtungen zum Laufen zu bringen. Hier sind die beiden Erweiterungsmethoden, die ich mir ausgedacht habe:

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
quelle
2
Ich benutze dies, aber benutze ImageFormat.Png. Ansonsten bekomme ich einen schwarzen Hintergrund auf dem Bild: stackoverflow.com/questions/4067448/…
Horst Walter
10

Am einfachsten ist es, wenn Sie die WPF-Bitmap direkt aus einer Datei erstellen können.

Andernfalls müssen Sie System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap verwenden.

Hallgrim
quelle
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
quelle
Was ist "DeleteObject ()"?
James Esh
6

Sie können die Pixeldaten einfach zwischen beiden Namespaces (Medien und Zeichnung) teilen, indem Sie eine benutzerdefinierte Bitmapsquelle schreiben. Die Konvertierung erfolgt sofort und es wird kein zusätzlicher Speicher zugewiesen. Wenn Sie keine explizite Kopie Ihrer Bitmap erstellen möchten, ist dies die gewünschte Methode.

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
quelle
Kannst du ein Beispiel posten?
schattigen
1
Genau das, wonach ich gesucht habe, ich hoffe, dass dies funktioniert, wenn ich es kompiliere = D
Greg
Wenn Sie also Properties.Resources.Image haben und es in eine Zeichenfläche zeichnen möchten, sind 133 Codezeilen erforderlich? WPF ist nicht in Ordnung.
Glenn Maynard
Es ist möglich, dies in einer Zeile zu tun. Aber wenn Sie es tun möchten, ohne eine tiefe Kopie der Bilddaten zu erstellen. Dies ist der richtige Weg.
Andreas
5

Ich arbeite bei einem Imaging-Anbieter und habe einen Adapter für WPF für unser Image-Format geschrieben, der einer System.Drawing.Bitmap ähnelt.

Ich habe diese KB geschrieben, um sie unseren Kunden zu erklären:

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

Und da ist Code, der das macht. Sie müssen AtalaImage durch Bitmap ersetzen und das Gleiche tun, was wir tun - es sollte ziemlich einfach sein.

Lou Franco
quelle
Danke Lou - konnte mit einer Codezeile machen, was ich brauchte
Kevin
4

Meine Einstellung dazu basiert auf einer Reihe von Ressourcen. 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
quelle
2

Ich bin auf diese Frage gekommen, weil ich versucht habe, dasselbe zu tun, aber in meinem Fall stammt die Bitmap aus einer Ressource / Datei. Ich fand die beste Lösung wie im folgenden Link beschrieben:

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
quelle