Memuat BitmapImage WPF dari System.Drawing.Bitmap

223

Saya memiliki instance dari System.Drawing.Bitmapdan ingin membuatnya tersedia untuk aplikasi WPF saya dalam bentuk a System.Windows.Media.Imaging.BitmapImage.

Apa yang akan menjadi pendekatan terbaik untuk ini?

Kevin
sumber

Jawaban:

265

Bagaimana dengan memuatnya dari 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
sumber
11
Anda bisa menambahkan kode ini sebagai metode ekstensi pada System.Drawing.Bitmap, sesuatu seperti ToBitmapImage ()
Luke Puplett
35
Menggunakan ImageFormat.Bmp adalah urutan besarnya lebih cepat.
RandomEngy
20
Jika ada orang lain yang mengalami masalah dengan kode ini: Saya harus menambahkan ms.Seek(0, SeekOrigin.Begin);sebelum pengaturan bi.StreamSource. Saya menggunakan .NET 4.0.
mlsteeves
6
@mls yang akan berlaku untuk semua versi .net. Saya akan menyelinap di sana dan memperbaiki kode itu; tidak ada yang memberi tahu Pawel.
7
Apakah seseorang akan mempertimbangkan untuk mengedit jawaban ini sehingga semua komentar (benar) terintegrasi ke dalamnya? Saat ini sangat terangkat, tetapi sama sekali tidak jelas apakah itu jawaban atau jawaban + komentar yang 'benar' ...
Benjol
81

Berkat Hallgrim, ini kode yang saya dapatkan:

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

Saya juga akhirnya mengikat ke BitmapSource alih-alih BitmapImage seperti pada pertanyaan awal saya

Kevin
sumber
2
Bagus! Mengapa Anda tidak memilih jawaban Anda sendiri sebagai jawaban untuk pertanyaan itu? Milikmu jauh lebih baik sekarang.
Hallgrim
1
Karena jawaban Anda sudah diterima, Anda dapat mengedit jawaban agar lebih lengkap.
Alan Jackson
39
Harap diingat bahwa kode ini membocorkan HBitmap. Lihat stackoverflow.com/questions/1118496/… untuk perbaikan
Lars Truijens
28
Peringatan : Ini bocor pegangan GDI setiap kali digunakan, jadi setelah panggilan 10r itu akan berhenti berfungsi (65r jika Anda beruntung). Seperti yang didokumentasikan dalam GetHbitmap , Anda benar - benar harus memohon DeleteObjectpada pegangan itu.
Roman Starkov
1
Untuk parameter terakhir, saya telah menggunakan BitmapSizeOptions.FromEmptyOptions(), dan berfungsi dengan baik untuk situasi saya.
Tarik
53

Saya tahu ini telah dijawab, tetapi berikut adalah beberapa metode ekstensi (untuk .NET 3.0+) yang melakukan konversi. :)

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

dan kelas NativeMethods (untuk menenangkan 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
sumber
1
Saat menggunakan gagang yang tidak dikelola (mis. HBITMAP) pertimbangkan untuk menggunakan SafeHandles, lihat stackoverflow.com/questions/1546091/…
Jack Ukleja
22

Butuh beberapa waktu untuk membuat konversi berfungsi dengan baik, jadi inilah dua metode ekstensi yang saya buat:

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
sumber
2
Saya menggunakan ini, tetapi menggunakan ImageFormat.Png. Kalau tidak, saya mendapatkan latar belakang hitam pada gambar: stackoverflow.com/questions/4067448/…
Horst Walter
10

Yang paling mudah adalah jika Anda bisa membuat bitmap WPF dari file secara langsung.

Kalau tidak, Anda harus menggunakan System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap.

Hallgrim
sumber
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
sumber
Whats "DeleteObject ()"?
James Esh
6

Anda bisa membagikan pixeldata antara ruang nama kedua (Media dan Gambar) dengan menulis sumber bitmap kustom. Konversi akan terjadi segera dan tidak ada memori tambahan yang akan dialokasikan. Jika Anda tidak ingin secara eksplisit membuat salinan Bitmap Anda, ini adalah metode yang Anda inginkan.

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
sumber
dapatkah Anda memposting contoh?
teduh
1
Persis apa yang saya cari, saya harap ini berfungsi ketika saya kompilasi = D
Greg
Jadi jika Anda memiliki Properties.Resources.Image dan Anda ingin menariknya ke kanvas, dibutuhkan 133 baris kode? WPF tidak apa-apa.
Glenn Maynard
Dimungkinkan untuk melakukannya dalam satu baris. Tetapi jika Anda ingin melakukannya tanpa membuat salinan imagedata yang mendalam. Ini cara untuk pergi.
Andreas
5

Saya bekerja di vendor pencitraan dan menulis adaptor untuk WPF ke format gambar kami yang mirip dengan System.Drawing.Bitmap.

Saya menulis KB ini untuk menjelaskannya kepada pelanggan kami:

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

Dan ada kode di sana yang melakukannya. Anda perlu mengganti AtalaImage dengan Bitmap dan melakukan hal yang setara yang kami lakukan - itu seharusnya cukup mudah.

Lou Franco
sumber
Terima kasih Lou - mampu melakukan apa yang saya butuhkan dengan satu baris kode
Kevin
4

Pendapat saya tentang ini dibangun dari sejumlah sumber daya. 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
sumber
2

Saya sampai pada pertanyaan ini karena saya mencoba melakukan hal yang sama, tetapi dalam kasus saya Bitmap berasal dari sumber / file. Saya menemukan solusi terbaik seperti yang dijelaskan dalam tautan berikut:

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
sumber