Convert System.Drawing.Icon to System.Media.ImageSource
Asked Answered
M

7

56

I've got an IntPtr marshaled across an unmanaged/managed boundary that corresponds to an Icon Handle. Converting it to an Icon is trivial via the FromHandle() method, and this was satisfactory until recently.

Basically, I've got enough thread weirdness going on now that the MTA/STA dance I've been playing to keep a hosted WinForm from breaking the primary (WPF-tastic) UI of the application is too brittle to stick with. So the WinForm has got to go.

So, how can I get an ImageSource version of an Icon?

Note, I've tried ImageSourceConverter to no avail.

As an aside, I can get the underlying resource for some but not all of the icons involved and they generally exist outside of my application's assembly (in fact, they often exist in unmanaged dll's).

Menstrual answered 14/7, 2009 at 20:3 Comment(0)
C
60

Try this:

Icon img;

Bitmap bitmap = img.ToBitmap();
IntPtr hBitmap = bitmap.GetHbitmap();

ImageSource wpfBitmap =
     Imaging.CreateBitmapSourceFromHBitmap(
          hBitmap, IntPtr.Zero, Int32Rect.Empty, 
          BitmapSizeOptions.FromEmptyOptions());

UPDATE: Incorporating Alex's suggestion and making it an extension method:

internal static class IconUtilities
{
    [DllImport("gdi32.dll", SetLastError = true)]
    private static extern bool DeleteObject(IntPtr hObject);

    public static ImageSource ToImageSource(this Icon icon)
    {            
        Bitmap bitmap = icon.ToBitmap();
        IntPtr hBitmap = bitmap.GetHbitmap();

        ImageSource wpfBitmap = Imaging.CreateBitmapSourceFromHBitmap(
            hBitmap,
            IntPtr.Zero,
            Int32Rect.Empty,
            BitmapSizeOptions.FromEmptyOptions());

        if (!DeleteObject(hBitmap))
        {
            throw new Win32Exception();
        }

        return wpfBitmap;
    }
}

Then you can do:

ImageSource wpfBitmap = img.ToImageSource();
Cavy answered 14/7, 2009 at 20:23 Comment(4)
After doing this conversion, you should use the DeleteObject( IntPtr hObject ) call in gdi32.dll on the hBitmap to avoid a memory leak.Wedurn
Even the updated solution might be causing the "Parameter is not valid" issue blog.lavablast.com/post/2007/11/… in some certain situations, I'd need to investigate more though. It might be safer to use Darren's solution insteadForehanded
I looked around the internet for quite sometime in efforts to get an answer to this solution. Most recommend using "Resource" rather than "Embedded Resource" to their project. The problem with that is if you do not wish to distribute & conceal the resourced info within the project, you will need to embed them. What was interesting to note was the <Type> of the resource being returned. In WPF for example, to set an Icon on the window it requires an <IconSource> type. Accessing the "Properties.Resource.<Icon>" has a native GDI type and will need to be converted to Windows.Media.Anatase
If you use an icon for the main application, it puts it local to the project anyway. Such is the case, if you wish to share this with your MainWindow icon, just use Icon="pack://application:,,,/MyApp;component/image.ico"> in the XAML which will embed it into the assembly just the same.Anatase
S
98

Simple conversion method without creating any extra objects:

    public static ImageSource ToImageSource(this Icon icon)
    {
        ImageSource imageSource = Imaging.CreateBitmapSourceFromHIcon(
            icon.Handle,
            Int32Rect.Empty,
            BitmapSizeOptions.FromEmptyOptions());

        return imageSource;
    }
Somnolent answered 5/7, 2011 at 9:54 Comment(6)
This solution seems more elegant than the one using Imaging.CreateBitmapSourceFromHBitmap. There's no need to create an unmanaged bitmap (and then have to remember to dispose of it) when Imaging can create the BitmapSource from the Icon handle directly.Tichonn
You're dragging in the obsolete System.Drawing to WPF... and if you are a WPF purist, this is a no-go.Illusionist
CreateBitmapSourceFromHIcon lives in the System.Windows.Interop namespace so be sure to add your Using or Import statement.Endomorph
@Illusionist I don't see how that's relevant given that OP is already using System.Drawing.IconSolecism
@radj -- read his question. Specifically I've got an IntPtr marshaled across an unmanaged/managed boundary that corresponds to an Icon Handle. He doesn't bring in System.Drawing.Icon. So there is no reason to use System.Drawing at all. Look at @Justin Davis' answer. That's the correct way.Illusionist
@Illusionist In the second half of that paragraph OP mentions the FromHandle method, which I understood to be System.Drawing.Icon.FromHandle(IntPtr); although I do agree that the answer you mentioned is a better approach, and I apologize if I came off as rude in my original comment.Solecism
C
60

Try this:

Icon img;

Bitmap bitmap = img.ToBitmap();
IntPtr hBitmap = bitmap.GetHbitmap();

ImageSource wpfBitmap =
     Imaging.CreateBitmapSourceFromHBitmap(
          hBitmap, IntPtr.Zero, Int32Rect.Empty, 
          BitmapSizeOptions.FromEmptyOptions());

UPDATE: Incorporating Alex's suggestion and making it an extension method:

internal static class IconUtilities
{
    [DllImport("gdi32.dll", SetLastError = true)]
    private static extern bool DeleteObject(IntPtr hObject);

    public static ImageSource ToImageSource(this Icon icon)
    {            
        Bitmap bitmap = icon.ToBitmap();
        IntPtr hBitmap = bitmap.GetHbitmap();

        ImageSource wpfBitmap = Imaging.CreateBitmapSourceFromHBitmap(
            hBitmap,
            IntPtr.Zero,
            Int32Rect.Empty,
            BitmapSizeOptions.FromEmptyOptions());

        if (!DeleteObject(hBitmap))
        {
            throw new Win32Exception();
        }

        return wpfBitmap;
    }
}

Then you can do:

ImageSource wpfBitmap = img.ToImageSource();
Cavy answered 14/7, 2009 at 20:23 Comment(4)
After doing this conversion, you should use the DeleteObject( IntPtr hObject ) call in gdi32.dll on the hBitmap to avoid a memory leak.Wedurn
Even the updated solution might be causing the "Parameter is not valid" issue blog.lavablast.com/post/2007/11/… in some certain situations, I'd need to investigate more though. It might be safer to use Darren's solution insteadForehanded
I looked around the internet for quite sometime in efforts to get an answer to this solution. Most recommend using "Resource" rather than "Embedded Resource" to their project. The problem with that is if you do not wish to distribute & conceal the resourced info within the project, you will need to embed them. What was interesting to note was the <Type> of the resource being returned. In WPF for example, to set an Icon on the window it requires an <IconSource> type. Accessing the "Properties.Resource.<Icon>" has a native GDI type and will need to be converted to Windows.Media.Anatase
If you use an icon for the main application, it puts it local to the project anyway. Such is the case, if you wish to share this with your MainWindow icon, just use Icon="pack://application:,,,/MyApp;component/image.ico"> in the XAML which will embed it into the assembly just the same.Anatase
B
10
MemoryStream iconStream = new MemoryStream();
myForm.Icon.Save(iconStream);
iconStream.Seek(0, SeekOrigin.Begin);
_wpfForm.Icon = System.Windows.Media.Imaging.BitmapFrame.Create(iconStream);
Bobcat answered 28/8, 2009 at 14:35 Comment(4)
Do you need to dispose of the MemoryStream?Chalcedony
should use Dispose on stream and for some reason this gives me very low quality, black and white images. anyone else has this problem?Eba
@Patrick, I also had issues using a MemoryStream and getting very low quality output. I used this solution by Byte (from below).Responsiveness
You don't need to dispose a MemoryStream, it doesn't do anything except marking it as disposed: referencesource.microsoft.com/#mscorlib/system/io/…. However it will improve performance of the GC slightly since Stream.Dispose will remove it from the finalization queue.Southdown
M
10

When using disposable streams it is almost always recommended to use 'using' blocks to force correct releasing of resources.

using (MemoryStream iconStream = new MemoryStream())
{
   icon.Save(iconStream);
   iconStream.Seek(0, SeekOrigin.Begin);

   this.TargetWindow.Icon = System.Windows.Media.Imaging.BitmapFrame.Create(iconStream);
}

Where icon is the source System.Drawing.Icon, and this.TargetWindow is the target System.Windows.Window.

Martinic answered 27/10, 2010 at 8:30 Comment(4)
getting really low quality image with this methodHindquarter
MemoryStream.Dispose doesn't free anything though, referencesource.microsoft.com/#mscorlib/system/io/…Southdown
This causes my WPF application to crash with disposing exception. So, Windows needs that memory stream later on when displaying the icon. You need to keep that memory stream alive throughout your window's life. and dispose of it when the window has closed.Illusionist
I fixed it... use this format and you can dispose of the memory stream as in the above sample: BitmapFrame.Create(iconStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad)Illusionist
P
2

Taking from some above this has created the highest quality of icons for my self. Loading the icons from a byte array. I use cache onload because if you don't you will get a disposed exception when you dispose the memory stream.

   internal static ImageSource ToImageSource(this byte[] iconBytes)
    {
        if (iconBytes == null)
            throw new ArgumentNullException(nameof(iconBytes));
        using (var ms = new MemoryStream(iconBytes))
        {
            return BitmapFrame.Create(ms, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
        }
    }
Poliomyelitis answered 4/7, 2016 at 18:52 Comment(0)
E
0

Somehow similar example, only tuned from developer's use cases...

    [DllImport("shell32.dll")]
    public static extern IntPtr ExtractIcon(IntPtr hInst, string file, int nIconIndex);

    [DllImport("user32.dll", SetLastError = true)]
    static extern bool DestroyIcon(IntPtr hIcon);

    /// <summary>
    /// Gets application icon from main .exe.
    /// </summary>
    /// <param name="setToObject">object to which to set up icon</param>
    /// <param name="bAsImageSource">true if get it as "ImageSource" (xaml technology), false if get it as "Icon" (winforms technology)</param>
    /// <returns>true if successful.</returns>
    public bool GetIcon(object setToObject, bool bAsImageSource)
    {
        String path = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
        path = Path.Combine(path, "yourmainexecutableName.exe");
        int iIconIndex = 0;

        // If your application contains multiple icons, then
        // you could change iIconIndex here.

        object o2set = null;
        IntPtr hIcon = ExtractIcon(IntPtr.Zero, path, iIconIndex);
        if (hIcon == IntPtr.Zero)
            return false;

        Icon icon = (Icon)Icon.FromHandle(hIcon);
        if (bAsImageSource)
        {
            o2set = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
                icon.ToBitmap().GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, 
                System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
        } else {
            icon = (Icon)icon.Clone();
        }

        DestroyIcon(hIcon);
        setToObject.GetType().GetProperty("Icon").SetValue(setToObject, o2set);
        return true;
    } //GetIcon
Empressement answered 9/5, 2016 at 12:39 Comment(0)
N
-1

There is a really simple solution to this problem.

Steps:

(1) add image to resources in solution explorer -> resources.resx (2) edit image properties inside "Resources" directory in solution explorer and change "Build action" to "Resource"

In xaml, add the following...

Icon="resources/name of image" (where "name of image" is the name of the image you added to resources - see point (1).

Numerator answered 22/9, 2015 at 9:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.