26

I have a project that converts an image format file into an icon file. However, after converting the image, the color of the image changes.

Here is my code

Bitmap theBitmap = new Bitmap(theImage, new Size(width, height));
IntPtr Hicon = theBitmap.GetHicon();// Get an Hicon for myBitmap.
Icon newIcon = Icon.FromHandle(Hicon);// Create a new icon from the handle.
FileStream fs = new FileStream(@"c:\Icon\" + filename + ".ico", FileMode.OpenOrCreate);//Write Icon to File Stream

enter image description here

Anybody know how to solve this?

Jim Simson
  • 2,774
  • 3
  • 22
  • 30
r.vengadesh
  • 1,721
  • 3
  • 20
  • 36
  • You're going to have to run that through some better processing. `GetHicon()` is not a lossless conversion. – DonBoitnott Jun 20 '13 at 11:43
  • There are a couple of links in [this answer](http://stackoverflow.com/a/11655019/62576) to a very similar question that might help. {This one](http://stackoverflow.com/a/17208258/62576) might help also. – Ken White Jun 20 '13 at 12:49
  • [stackoverflow.com/a/17208258/62576] this question is asked by myself @KenWhite – r.vengadesh Jun 20 '13 at 13:00
  • @r.vengadesh: Then why are you duplicating it here? – Ken White Jun 20 '13 at 13:02
  • You can also use the example on MSDN: [Image to Icon Generator](http://code.msdn.microsoft.com/Image-to-Icon-Generator-0dbffe44) – Jeremy Thompson Jan 07 '14 at 23:47
  • You also may want to destroy that icon handle. Please read the manual (RTM) or you will leak GDI+ resources. https://msdn.microsoft.com/en-us/library/system.drawing.bitmap.gethicon(v=vs.110).aspx https://msdn.microsoft.com/en-us/library/system.drawing.icon.fromhandle(v=vs.110).aspx Note in the remarks section, you are responsible for manually deleting the handle. – Jeremy Apr 12 '16 at 20:10
  • Super-late comment but: his code doesn't actually save the icon to disk. It just creates an empty icon file. To actually save the icon you need this at the end of his block: newIcon.Save(fs); – AS7K May 07 '17 at 17:37

8 Answers8

17

Bitmap.GetHicon() is very good at creating icons that work well on any Windows version that can run .NET code. Including the old ones, Windows 98 and Windows 2000. Operating systems that did not yet support fancy icons.

So what you get is an icon with only 16 colors, using a pre-cooked palette with basic colors. This tends to generate disappointing results, to put it mildly.

The Bitmap or Icon classes do not have an option to get a better result. In general you'll need to use an icon editor to create good icons. Which should include multiple images in different sizes and color depths so they'll work well with any video adapter setting and any operating system version. Particularly color reduction from 16 million to 256 or 16 colors is a non-trivial operation with multiple ways to do it, none of them perfect. A good icon editor has the tools you need to make that work well enough.


UPDATE: getting to be a very dated problem, XP is yesteryear. Today you can generate a very good looking icon with this code.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Yes, Bitmap.GetHicon() is good, supported, low size, fast pixel manipulation (as uses only 16 colors). For better results you can try some histgram manipulators like picasa have. You can choose your own color, style. – user9371102 Jun 20 '13 at 14:14
9

This is my method, this can convert png to icon, including the transparence:

public void ConvertToIco(Image img, string file, int size)
{
    Icon icon;
    using (var msImg = new MemoryStream())
    using (var msIco = new MemoryStream())
    {
        img.Save(msImg, ImageFormat.Png);
        using (var bw = new BinaryWriter(msIco))
        {
            bw.Write((short)0);           //0-1 reserved
            bw.Write((short)1);           //2-3 image type, 1 = icon, 2 = cursor
            bw.Write((short)1);           //4-5 number of images
            bw.Write((byte)size);         //6 image width
            bw.Write((byte)size);         //7 image height
            bw.Write((byte)0);            //8 number of colors
            bw.Write((byte)0);            //9 reserved
            bw.Write((short)0);           //10-11 color planes
            bw.Write((short)32);          //12-13 bits per pixel
            bw.Write((int)msImg.Length);  //14-17 size of image data
            bw.Write(22);                 //18-21 offset of image data
            bw.Write(msImg.ToArray());    // write image data
            bw.Flush();
            bw.Seek(0, SeekOrigin.Begin);
            icon = new Icon(msIco);
        }
    }
    using (var fs = new FileStream(file, FileMode.Create, FileAccess.Write))
    {
        icon.Save(fs);
    }
}
Xiaohuan ZHOU
  • 478
  • 5
  • 6
  • This does not work. Which format must the Image have (color depth 8, 24, 32) ? – Elmue Jul 22 '21 at 04:55
  • Worked for me when I spliced this code into some code I already had reading a byte array from a file (and then reverted). msImg can simply be a byte array of a 32x32 pixel PNG file, and then this works fine. Creating an Icon from msIco is unnecessary: you can just run `File.WriteAllBytes(file, bytes)`, where *bytes* is an array of byte. – Tim Feb 23 '22 at 23:49
8

If you only need 32-bit icons, you can use FreeImage http://freeimage.sourceforge.net

string icoFile = "C:\path\to\file.ico";
FreeImageBitmap fiBitmap = new FreeImageBitmap(theBitmap);
fiBitmap.Rescale(48, 48, FREE_IMAGE_FILTER.FILTER_BICUBIC);
fiBitmap.Save(icoFile);
fiBitmap.Rescale(32, 32, FREE_IMAGE_FILTER.FILTER_BICUBIC);
fiBitmap.SaveAdd(icoFile);
fiBitmap.Rescale(16, 16, FREE_IMAGE_FILTER.FILTER_BICUBIC);
fiBitmap.SaveAdd(icoFile);

If you want full support for 32, 8, 4, and 1-bit icons, you will have to create your own ico format writer. I ran into this problem while developing my own C# based png to ico converter http://iconverticons.com

It actually isn't too difficult; the ico file format specifications you will need are here: http://msdn.microsoft.com/en-us/library/ms997538.aspx

You will also need the Bitmap header specification from here, as ico is a subset of bitmap: http://msdn.microsoft.com/en-us/library/dd183376.aspx

Mathew Eis
  • 280
  • 1
  • 2
  • 7
2

I have created this simple function that take a string of image to convert and the path where to save. The .ico works on chrome and other browsers.

public void ConvertToIco(string pathImageToConvert, string pathToSave)
{
    Bitmap bmp = new Bitmap(pathImageToConvert);
    bmp.Save(pathToSave, System.Drawing.Imaging.ImageFormat.Icon);
}

Here is a exemple of path

pathImageToConvert = "yourpath/" + image.extension
pathToSave  = "yourpath/" + image.ico  
J.C
  • 632
  • 1
  • 10
  • 41
1

You can try this:

Bitmap theBitmap = new Bitmap(theImage, new Size(width, height));

theBitmap.Save(@"C:\Icon\" + filename + ".ico", System.Drawing.Imaging.ImageFormat.Icon);
Andy
  • 3,997
  • 2
  • 19
  • 39
0

It seems that .Net Framework doesn't have any Icon Encoder; so you can NOT save any real Icon file. The saved file only is a PNG file.

Gholamalih
  • 60
  • 2
0

Solved for Vista and higher

If you have to dynamically generate icons (my app has a changing number), you'd be out of luck if you had to use an editor anyway.

Ruiwei Bu (darkfall) has a github gist showing how. Since of course we don't support XP and lower this did it for us.

Sample using a class I put that code in:

Dim ico As New Icons With {.state = Iconstates.OK, .UpgradeNum = "123"}
Dim tempfile = "C:\file5.ico"
ico.GetIcon(tempfile)
Dim newicon As New Icon(tempfile)

The class with darkfall's code:

Imports System.Drawing.Imaging
Imports System.IO

Public Class Icons
  Property UpgradeNum As String
  Property state As Iconstates

  Function GetIcon(Optional OptionalSave As String = "") As Icon
    Dim bmp As New Bitmap(16, 16)

    Using g = Graphics.FromImage(bmp)
      g.Clear(Color.Transparent)
      g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
      Select Case state
        Case Iconstates.OK
          g.FillEllipse(Brushes.Green, 1, 1, 14, 14)
      End Select
      g.DrawString(UpgradeNum, New Font("Small Fonts", 6), Brushes.Aquamarine, 0, 0)
    End Using

    bmp.Save(OptionalSave & ".png")

    Dim outputStream As New MemoryStream()
    Dim size As Integer = bmp.Size.Width
    If Not ConvertToIcon(bmp, outputStream, size) Then
      Return Nothing
    End If
    If OptionalSave > "" Then

      Using file = New FileStream(OptionalSave, FileMode.Create, System.IO.FileAccess.Write)
        outputStream.WriteTo(file)
        file.Close()
      End Using
    End If

    outputStream.Seek(0, SeekOrigin.Begin)

    Return New Icon(outputStream)
  End Function




  ''' <summary>
  ''' Converts a PNG image to an icon (ico)
  ''' </summary>
  ''' <param name="inputBitmap">The input stream</param>
  ''' <param name="output">The output stream</param>
  ''' <param name="size">Needs to be a factor of 2 (16x16 px by default)</param>
  ''' <param name="preserveAspectRatio">Preserve the aspect ratio</param>
  ''' <returns>Wether or not the icon was succesfully generated</returns>
  Public Shared Function ConvertToIcon(inputBitmap As Bitmap, output As Stream, Optional size As Integer = 16, Optional preserveAspectRatio As Boolean = False) As Boolean

    Dim width As Single = size, height As Single = size


    Dim newBitmap = New Bitmap(inputBitmap, New Size(CInt(width), CInt(height)))
    If newBitmap Is Nothing Then
      Return False
    End If

    ' save the resized png into a memory stream for future use
    Using memoryStream As New MemoryStream()
      newBitmap.Save(memoryStream, ImageFormat.Png)

      Dim iconWriter = New BinaryWriter(output)
      If output Is Nothing OrElse iconWriter Is Nothing Then
        Return False
      End If

      ' 0-1 reserved, 0
      iconWriter.Write(CByte(0))
      iconWriter.Write(CByte(0))

      ' 2-3 image type, 1 = icon, 2 = cursor
      iconWriter.Write(CShort(1))

      ' 4-5 number of images
      iconWriter.Write(CShort(1))

      ' image entry 1
      ' 0 image width
      iconWriter.Write(CByte(width))
      ' 1 image height
      iconWriter.Write(CByte(height))

      ' 2 number of colors
      iconWriter.Write(CByte(0))

      ' 3 reserved
      iconWriter.Write(CByte(0))

      ' 4-5 color planes
      iconWriter.Write(CShort(0))

      ' 6-7 bits per pixel
      iconWriter.Write(CShort(32))

      ' 8-11 size of image data
      iconWriter.Write(CInt(memoryStream.Length))

      ' 12-15 offset of image data
      iconWriter.Write(CInt(6 + 16))

      ' write image data
      ' png data must contain the whole png data file
      iconWriter.Write(memoryStream.ToArray())

      iconWriter.Flush()
    End Using

    Return True
  End Function


End Class
' https://gist.github.com/darkfall/1656050
'=======================================================
'Service provided by Telerik (www.telerik.com)
'Conversion powered by NRefactory.
'Twitter: @telerik
'Facebook: facebook.com/telerik
'=======================================================
Community
  • 1
  • 1
FastAl
  • 6,194
  • 2
  • 36
  • 60
0

It is easy with BMP. Below code works fine if you have BMP image direct in your project.

  <code>
           try
            {
                Bitmap b = new Bitmap(".\\ConnApp.bmp");
                IntPtr pIcon = b.GetHicon();
                Icon i = Icon.FromHandle(pIcon);
                this.Icon = i;
                logInfo("Icon loaded ?" + i);

                // save ico file. This you can directly assign Icon to your Form (Properties -> Icon -> choose)
                // the exe Icon you can assign from Application -> Properties -> Icon and Manifest -> select icon. 
                FileStream SourceStream = File.Create("\\pos2.ico");
                i.Save(SourceStream);
                SourceStream.Close();
                i.Dispose();
            } catch (Exception ex1) {
                logInfo("Error on iconload " + ex1);
            }
  </code>