237

I have an instance of a System.Drawing.Bitmap and would like to make it available to my WPF app in the form of a System.Windows.Media.Imaging.BitmapImage.

What would be the best approach for this?

Thomas Freudenberg
  • 5,048
  • 1
  • 35
  • 44
Kevin
  • 9,309
  • 12
  • 44
  • 51

10 Answers10

276

How about loading it from 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();
}
weston
  • 54,145
  • 21
  • 145
  • 203
Pawel Lesnikowski
  • 6,264
  • 4
  • 38
  • 42
  • 11
    You could add this code as an extension method on System.Drawing.Bitmap, something like ToBitmapImage() – Luke Puplett Feb 15 '10 at 13:46
  • 36
    Using ImageFormat.Bmp is an order of magnitude faster. – RandomEngy Mar 07 '10 at 20:35
  • 21
    In case others are having problems with this code: I had to add `ms.Seek(0, SeekOrigin.Begin);` before setting `bi.StreamSource`. I'm using .NET 4.0. – mlsteeves May 07 '10 at 13:57
  • 7
    @mls that would be true of any version of .net. I'm gonna sneak in there and fix that code; nobody tell Pawel. –  Sep 27 '10 at 18:00
  • 1
    Important addition to this code: add `bitmap.CacheOption = BitmapCacheOption.OnLoad;` between `BeginInit()` and `EndInit()`. Otherwise the stream may be closed (especially if enclosed in `using`) by the time the `BitmapImage` tries to read it. See answer here: http://stackoverflow.com/questions/5346727/wpf-convert-memory-stream-to-bitmapimage – Pieter Müller Jan 06 '12 at 09:51
  • 1
    For me it didn't work at first, but then I changed the line `bi.StreamSource = ms;` to `bi.StreamSource = new MemoryStream(ms.ToArray());` and this change was all that was needed! – Muhammad Umer Asif Jun 14 '12 at 10:55
  • 8
    Would someone consider editing this answer so that all the (correct) comments are integrated into it? At the moment it's heavily upvoted, but not at all clear whether it's the answer or answer+comments that are 'right'... – Benjol Apr 25 '13 at 07:59
  • 7
    Should also add that while ImageFormat.Bmp is faster, it throws away any transparency that the original Bitmap had – Pyritie Feb 07 '14 at 14:23
  • 1
    if using as an extension like previously mentioned, be sure to `Freeze` the `bitmapImage` before returning it. – Wobbles Apr 03 '15 at 13:46
  • 4
    219 people upvoted compressing and decompressing a PNG? Seriously? – Glenn Maynard Feb 23 '17 at 00:01
89

Thanks to Hallgrim, here is the code I ended up with:

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

I also ended up binding to a BitmapSource instead of a BitmapImage as in my original question

devdigital
  • 34,151
  • 9
  • 98
  • 120
Kevin
  • 9,309
  • 12
  • 44
  • 51
  • 2
    Great! Why don't you select your own answer as the answer to the question? Your's is much better now. – Hallgrim Sep 18 '08 at 21:28
  • 1
    Since yours is the accepted answer already, you could edit your answer to make it more complete. – Alan Jackson May 28 '09 at 05:35
  • 41
    Do mind that this code leaks a HBitmap. See http://stackoverflow.com/questions/1118496/using-image-control-in-wpf-to-display-system-drawing-bitmap/1118557#1118557 for a fix – Lars Truijens Jul 13 '09 at 11:54
  • Please also note that this code will produce aמ InteropBitmap. I had a problem with this when using XamlWriter and then XamlReader - InteropBitmap is not supported. See [this link](http://social.msdn.microsoft.com/Forums/en/wpf/thread/16dd28e7-6412-4e54-a4bf-20a9f42ce7e2) for more details. – AVIDeveloper Sep 15 '11 at 10:11
  • 30
    **Warning**: [This leaks a GDI handle](http://stackoverflow.com/questions/1546091/wpf-createbitmapsourcefromhbitmap-memory-leak) every single time it's used, so after 10k calls it will stop working (65k if you're lucky). As documented in [GetHbitmap](http://msdn.microsoft.com/en-us/library/system.drawing.bitmap.gethbitmap.aspx), you absolutely **must** p/invoke `DeleteObject` on that handle. – Roman Starkov Dec 28 '11 at 00:24
  • 1
    For the last parameter, I have used `BitmapSizeOptions.FromEmptyOptions()`, and it works just fine to my situation. – Tarik Aug 22 '16 at 18:09
  • your question is for BitmapImage from a System.Drawing.Bitmap. but your code return a BitmapSource not BitmapImage. – naseer mohammad Sep 05 '18 at 14:40
  • Using Interop adds risk: security implications, bugs, whatever. In my view a bad choice except as a last resort. – david.pfx Oct 07 '20 at 05:34
55

I know this has been answered, but here are a couple of extension methods (for .NET 3.0+) that do the 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;
    }

and the NativeMethods class (to appease 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);
}
Michael Eakins
  • 4,149
  • 3
  • 35
  • 54
Alastair Pitts
  • 19,423
  • 9
  • 68
  • 97
  • 2
    When using unmanaged handles (e.g. HBITMAP) consider using SafeHandles, see http://stackoverflow.com/questions/1546091/wpf-createbitmapsourcefromhbitmap-memory-leak/7035036#7035036 – Jack Ukleja Aug 12 '11 at 02:38
30

It took me some time to get the conversion working both ways, so here are the two extension methods I came up with:

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
  • 12,855
  • 13
  • 54
  • 80
  • 3
    I am using this, but use ImageFormat.Png. Otherwise I do get a black background on the image: http://stackoverflow.com/questions/4067448/converting-image-to-bitmap-turns-background-black – Horst Walter Nov 30 '12 at 13:44
  • Nice answer and best thing: no Interop. – david.pfx Oct 07 '20 at 05:35
  • 1
    @DanielWolf: but Horst is right: BMP format does not support transparency. This answer should be corrected to use PNG instead. – david.pfx Oct 07 '20 at 10:13
  • 1
    And `BmpBitmapEncoder` should be `PngBitmapEncoder` if you want transparency. Otherwise you get black. – david.pfx Oct 08 '20 at 00:17
  • @HorstWalter, david.pfx: Thanks for your comments, that makes sense. I haven't used .NET for some years, so I can't quickly try these changes. Could one of you edit my code, making sure it still works? – Daniel Wolf Oct 08 '20 at 09:41
  • Use PngBitmapEncoder instead of BmpBitmapEncoder() in ToWinFormsBitmap method, to keep transparency and good quality too. – fsbflavio Jan 07 '21 at 17:15
10
// 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
  • 16,527
  • 15
  • 80
  • 134
10

The easiest thing is if you can make the WPF bitmap from a file directly.

Otherwise you will have to use System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap.

Hallgrim
  • 15,143
  • 10
  • 46
  • 54
6

You can just share the pixeldata between a both namespaces ( Media and Drawing) by writing a custom bitmapsource. The conversion will happen immediately and no additional memory will be allocated. If you do not want to explicitly create a copy of your Bitmap this is the method you want.

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
  • 3,843
  • 3
  • 40
  • 53
5

I work at an imaging vendor and wrote an adapter for WPF to our image format which is similar to a System.Drawing.Bitmap.

I wrote this KB to explain it to our customers:

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

And there is code there that does it. You need to replace AtalaImage with Bitmap and do the equivalent thing that we are doing -- it should be pretty straightforward.

Lou Franco
  • 87,846
  • 14
  • 132
  • 192
  • Thanks Lou - was able to do what I needed with one line of code – Kevin Sep 18 '08 at 20:21
  • Link in answer is dead *"404: Page Not Found"*. – Pang Oct 22 '20 at 06:31
  • 2
    If one is still looking for this particular answer for some reason, it is available on archive.org: https://web.archive.org/web/20160622213213/http://www.atalasoft.com/KB/Article.aspx?id=10156 – JaykeBird May 06 '21 at 02:34
4

My take on this built from a number of resources. 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;
            }
        }
    }
}
Community
  • 1
  • 1
weston
  • 54,145
  • 21
  • 145
  • 203
2

I came to this question because I was trying to do the same, but in my case the Bitmap is from a resource/file. I found the best solution is as described in the following link:

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
  • 7,525
  • 13
  • 61
  • 124