Load a WPF BitmapImage from a System.Drawing.Bitmap
Asked Answered
H

10

239

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?

Hurried answered 18/9, 2008 at 16:58 Comment(0)
H
91

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

Hurried answered 18/9, 2008 at 20:19 Comment(8)
Great! Why don't you select your own answer as the answer to the question? Your's is much better now.Bartko
Since yours is the accepted answer already, you could edit your answer to make it more complete.Endostosis
Do mind that this code leaks a HBitmap. See #1118996 for a fixOrganize
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 for more details.Branle
Warning: This leaks a GDI handle every single time it's used, so after 10k calls it will stop working (65k if you're lucky). As documented in GetHbitmap, you absolutely must p/invoke DeleteObject on that handle.Proclus
For the last parameter, I have used BitmapSizeOptions.FromEmptyOptions(), and it works just fine to my situation.Roundshouldered
your question is for BitmapImage from a System.Drawing.Bitmap. but your code return a BitmapSource not BitmapImage.Fissi
Using Interop adds risk: security implications, bugs, whatever. In my view a bad choice except as a last resort.Pember
F
279

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();
}
Fluorescein answered 1/7, 2009 at 14:39 Comment(10)
You could add this code as an extension method on System.Drawing.Bitmap, something like ToBitmapImage()Abduce
Using ImageFormat.Bmp is an order of magnitude faster.Document
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.Clientage
@mls that would be true of any version of .net. I'm gonna sneak in there and fix that code; nobody tell Pawel.Deedee
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: #5347227Cingulum
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!Stores
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'...Thiamine
Should also add that while ImageFormat.Bmp is faster, it throws away any transparency that the original Bitmap hadBragg
if using as an extension like previously mentioned, be sure to Freeze the bitmapImage before returning it.Lovash
219 people upvoted compressing and decompressing a PNG? Seriously?Sandman
H
91

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

Hurried answered 18/9, 2008 at 20:19 Comment(8)
Great! Why don't you select your own answer as the answer to the question? Your's is much better now.Bartko
Since yours is the accepted answer already, you could edit your answer to make it more complete.Endostosis
Do mind that this code leaks a HBitmap. See #1118996 for a fixOrganize
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 for more details.Branle
Warning: This leaks a GDI handle every single time it's used, so after 10k calls it will stop working (65k if you're lucky). As documented in GetHbitmap, you absolutely must p/invoke DeleteObject on that handle.Proclus
For the last parameter, I have used BitmapSizeOptions.FromEmptyOptions(), and it works just fine to my situation.Roundshouldered
your question is for BitmapImage from a System.Drawing.Bitmap. but your code return a BitmapSource not BitmapImage.Fissi
Using Interop adds risk: security implications, bugs, whatever. In my view a bad choice except as a last resort.Pember
A
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);
}
Ardeha answered 24/9, 2009 at 7:11 Comment(1)
When using unmanaged handles (e.g. HBITMAP) consider using SafeHandles, see #1546591Dees
H
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;
        }
    }
}
Humiliating answered 21/7, 2011 at 11:12 Comment(6)
I am using this, but use ImageFormat.Png. Otherwise I do get a black background on the image: #4067948Rumal
Nice answer and best thing: no Interop.Pember
@DanielWolf: but Horst is right: BMP format does not support transparency. This answer should be corrected to use PNG instead.Pember
And BmpBitmapEncoder should be PngBitmapEncoder if you want transparency. Otherwise you get black.Pember
@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?Humiliating
Use PngBitmapEncoder instead of BmpBitmapEncoder() in ToWinFormsBitmap method, to keep transparency and good quality too.Simony
B
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.

Bartko answered 18/9, 2008 at 17:1 Comment(0)
G
10
// at class level;
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);    // https://mcmap.net/q/119380/-wpf-createbitmapsourcefromhbitmap-memory-leak


/// <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;
}
Guncotton answered 12/9, 2011 at 15:35 Comment(2)
Whats "DeleteObject()"?Tenrec
See #1546591Roubaix
V
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
}
Vesuvian answered 29/9, 2015 at 10:42 Comment(6)
can you post an example?Fara
Exactly what i was looking for, i hope this works when i compile it =DHelenehelenka
So if you have Properties.Resources.Image and you want to draw it into a canvas, it takes 133 lines of code? WPF isn't okay.Sandman
It is possible to do it in one line. But if you want to do it without making a deep copy of the imagedata. This is the way to go.Vesuvian
I don't see bitmapImage anywhere here.. how does this answer the question? Do you have an example how to use this?Hicks
I tried this but can't get it to work. when I try to "imgTop.Source = ss;" I get an error "An exception of type 'System.NotImplementedException' occurred in PresentationCore.dll but was not handled in user code The method or operation is not implemented."Chaps
L
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.

Livelihood answered 18/9, 2008 at 17:4 Comment(3)
Thanks Lou - was able to do what I needed with one line of codeHurried
Link in answer is dead "404: Page Not Found".Assent
If one is still looking for this particular answer for some reason, it is available on archive.org: web.archive.org/web/20160622213213/http://www.atalasoft.com/KB/…Headway
P
4

My take on this built from a number of resources. https://stackoverflow.com/a/7035036 https://mcmap.net/q/116728/-load-a-wpf-bitmapimage-from-a-system-drawing-bitmap

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;
            }
        }
    }
}
Pot answered 28/4, 2015 at 11:17 Comment(0)
J
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;
Jelsma answered 11/9, 2011 at 0:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.