โหลด WPF BitmapImage จาก System.Drawing.Bitmap


223

ผมมีตัวอย่างของหนึ่งSystem.Drawing.Bitmapและต้องการที่จะทำให้มันใช้ได้กับแอป WPF System.Windows.Media.Imaging.BitmapImageของฉันในรูปแบบของ

อะไรจะเป็นวิธีที่ดีที่สุดสำหรับสิ่งนี้?

คำตอบ:


265

แล้วโหลดจาก 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();
}

11
คุณสามารถเพิ่มรหัสนี้เป็นวิธีการขยายใน System.Drawing.Bitmap สิ่งที่ต้องการ ToBitmapImage ()
ลุค Puplett

35
การใช้ ImageFormat.Bmp เป็นลำดับความสำคัญเร็วขึ้น
RandomEngy

20
ในกรณีที่คนอื่นจะมีปัญหากับรหัสนี้ผมต้องเพิ่มก่อนการตั้งค่าms.Seek(0, SeekOrigin.Begin); bi.StreamSourceฉันใช้. NET 4.0
mlsteeves

6
@mls ซึ่งจะเป็นจริงของ. net ทุกเวอร์ชัน ฉันจะแอบเข้าไปที่นั่นและแก้ไขรหัสนั้น ไม่มีใครบอก Pawel

7
มีใครพิจารณาแก้ไขคำตอบนี้เพื่อให้ความเห็น (ถูกต้อง) รวมอยู่ในนั้น? ในขณะที่มัน upvoted อย่างหนัก แต่ไม่ได้ทุกอย่างชัดเจนไม่ว่าจะเป็นคำตอบหรือคำตอบ + ความคิดเห็นที่มี 'ขวา' ...
Benjol

81

ขอบคุณ Hallgrim นี่คือรหัสที่ฉันได้รับ:

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

ฉันยังผูกพันกับ BitmapSource แทน BitmapImage เช่นเดียวกับในคำถามเดิมของฉัน


2
ที่ดี! ทำไมคุณไม่เลือกคำตอบของคุณเองเป็นคำตอบของคำถาม? ของคุณดีขึ้นกว่านี้มาก
Hallgrim

1
เนื่องจากคุณเป็นคำตอบที่ยอมรับแล้วคุณสามารถแก้ไขคำตอบเพื่อทำให้สมบูรณ์ยิ่งขึ้น
Alan Jackson

39
โปรดทราบว่ารหัสนี้จะรั่วไหลใน HBitmap ดูstackoverflow.com/questions/1118496/…เพื่อรับการแก้ไข
Lars Truijens

29
คำเตือน : การรั่วไหลของ GDI นี้จัดการทุกครั้งที่มีการใช้งานดังนั้นหลังจาก 10k การโทรมันจะหยุดทำงาน (65k ถ้าคุณโชคดี) ตามที่ระบุไว้ในGetHbitmapคุณจะต้อง p / เรียกใช้DeleteObjectบนหมายเลขอ้างอิงนั้น
Roman Starkov

1
สำหรับพารามิเตอร์ตัวสุดท้ายฉันใช้BitmapSizeOptions.FromEmptyOptions()และมันใช้ได้กับสถานการณ์ของฉัน
Tarik

53

ฉันรู้ว่าสิ่งนี้ได้รับคำตอบแล้ว แต่ต่อไปนี้เป็นวิธีการขยายสองสามแบบ (สำหรับ. 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);
}

1
เมื่อใช้มือจับที่ไม่มีการจัดการ (เช่น HBITMAP) ให้พิจารณาใช้ SafeHandles ดูstackoverflow.com/questions/1546091/…
Jack Ukleja

22

ฉันใช้เวลาสักครู่เพื่อให้การแปลงทำงานได้ทั้งสองวิธีดังนั้นนี่คือวิธีการขยายสองวิธีที่ฉันใช้

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

2
ฉันใช้สิ่งนี้ แต่ใช้ ImageFormat.Png มิฉะนั้นฉันจะได้รับพื้นหลังสีดำในภาพ: stackoverflow.com/questions/4067448/ …
Horst Walter

10

สิ่งที่ง่ายที่สุดคือถ้าคุณสามารถสร้างบิตแมป WPF จากไฟล์โดยตรง

มิฉะนั้นคุณจะต้องใช้ System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap


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

"DeleteObject ()" คืออะไร
James Esh


6

คุณสามารถแบ่งปัน pixeldata ระหว่างเนมสเปซทั้งสอง (สื่อและรูปวาด) โดยการเขียนบิตแมปแหล่งข้อมูลที่กำหนดเอง การแปลงจะเกิดขึ้นทันทีและจะไม่มีการจัดสรรหน่วยความจำเพิ่มเติม หากคุณไม่ต้องการสร้างสำเนาบิตแมปของคุณอย่างชัดเจนนี่เป็นวิธีการที่คุณต้องการ

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
}

คุณสามารถโพสต์ตัวอย่างได้ไหม
ร่มรื่น

1
ว่าสิ่งที่ฉันกำลังมองหาฉันหวังว่างานนี้เมื่อฉันรวบรวมมัน = D
Greg

ดังนั้นถ้าคุณมี Properties.Resources.Image และคุณต้องการวาดมันลงในผืนผ้าใบมันต้องใช้โค้ด 133 บรรทัด? WPF ไม่เป็นไร
เกล็นเมย์นาร์ด

เป็นไปได้ที่จะทำในบรรทัดเดียว แต่ถ้าคุณต้องการที่จะทำมันโดยไม่ต้องทำสำเนาของ imagedata นี่คือวิธีที่จะไป
Andreas

5

ฉันทำงานที่ผู้ผลิตภาพและเขียนอะแดปเตอร์สำหรับ WPF ให้กับรูปแบบภาพของเราซึ่งคล้ายกับ System.Drawing.Bitmap

ฉันเขียน KB นี้เพื่ออธิบายให้ลูกค้าของเรา:

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

และมีรหัสนั่นที่ทำมัน คุณต้องแทนที่ AtalaImage ด้วยบิตแมปและทำสิ่งที่เทียบเท่ากับที่เรากำลังทำอยู่ - มันควรจะตรงไปตรงมา


ขอบคุณ Lou - สามารถทำสิ่งที่ฉันต้องการด้วยโค้ดหนึ่งบรรทัด
Kevin

4

ฉันใช้สิ่งนี้สร้างขึ้นจากทรัพยากรจำนวนมาก 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;
            }
        }
    }
}

2

ฉันมาที่คำถามนี้เพราะฉันพยายามทำเช่นเดียวกัน แต่ในกรณีของฉันบิตแมปมาจากทรัพยากร / ไฟล์ ฉันพบทางออกที่ดีที่สุดตามที่อธิบายไว้ในลิงค์ต่อไปนี้:

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;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.