7

How do I create an icon file that contains multiple sizes?

I know that I create a icon from a bitmap using Icon.FromHandle() but how do I add another image / size to that icon?

Edit: I need to do this in my application, so I cannot execute an external application to do the combining.

Zach Johnson
  • 23,678
  • 6
  • 69
  • 86
Geoff
  • 4,676
  • 3
  • 26
  • 38
  • Since I don't think it answers your question, I will post this as a comment. I use a program called IcoFX to create icons, and it is very handy for creating multiple sizes of icon all at once, by reasampling the original 256x256 icon at the various other sizes (i.e. 64x64, 32x32...) You may or may not find it and related info useful. Web site: http://icofx.ro/ – JYelton Jul 09 '10 at 15:20
  • funny so many people immediately give you external tool even though you have specified that you cannot use them. Wonder if they read the question fully! – iamserious Jul 09 '10 at 16:25
  • @iamserious: The specification was added after those comments, so the users who commented did most likely read the question. I've edited the question to help clarify that. :) – Zach Johnson Jul 09 '10 at 20:31
  • to be fair, the original title *did* say "in C#" – NickAldwin Jul 09 '10 at 20:43

5 Answers5

10

I was looking for a way to combine .png files, nothing fancy, into an icon. I created the below code after not being able to find something simple and with this question being the top search result.


The following code can create an icon with multiple sizes if, for each of the images, the Image.RawFormat is ImageFormat.Png, the Image.PixelFormat is PixelFormat.Format32bppArgb and the dimensions are less than or equal to 256x256:

/// <summary>
/// Provides methods for creating icons.
/// </summary>
public class IconFactory
{

    #region constants

    /// <summary>
    /// Represents the max allowed width of an icon.
    /// </summary>
    public const int MaxIconWidth = 256;

    /// <summary>
    /// Represents the max allowed height of an icon.
    /// </summary>
    public const int MaxIconHeight = 256;

    private const ushort HeaderReserved = 0;
    private const ushort HeaderIconType = 1;
    private const byte HeaderLength = 6;

    private const byte EntryReserved = 0;
    private const byte EntryLength = 16;

    private const byte PngColorsInPalette = 0;
    private const ushort PngColorPlanes = 1;

    #endregion

    #region methods

    /// <summary>
    /// Saves the specified <see cref="Bitmap"/> objects as a single 
    /// icon into the output stream.
    /// </summary>
    /// <param name="images">The bitmaps to save as an icon.</param>
    /// <param name="stream">The output stream.</param>
    /// <remarks>
    /// The expected input for the <paramref name="images"/> parameter are 
    /// portable network graphic files that have a <see cref="Image.PixelFormat"/> 
    /// of <see cref="PixelFormat.Format32bppArgb"/> and where the
    /// width is less than or equal to <see cref="IconFactory.MaxIconWidth"/> and the 
    /// height is less than or equal to <see cref="MaxIconHeight"/>.
    /// </remarks>
    /// <exception cref="InvalidOperationException">
    /// Occurs if any of the input images do 
    /// not follow the required image format. See remarks for details.
    /// </exception>
    /// <exception cref="ArgumentNullException">
    /// Occurs if any of the arguments are null.
    /// </exception>
    public static void SavePngsAsIcon(IEnumerable<Bitmap> images, Stream stream)
    {
        if (images == null)
            throw new ArgumentNullException("images");
        if (stream == null)
            throw new ArgumentNullException("stream");

        // validates the pngs
        IconFactory.ThrowForInvalidPngs(images);

        Bitmap[] orderedImages = images.OrderBy(i => i.Width)
                                       .ThenBy(i => i.Height)
                                       .ToArray();

        using (var writer = new BinaryWriter(stream))
        {

            // write the header
            writer.Write(IconFactory.HeaderReserved);
            writer.Write(IconFactory.HeaderIconType);
            writer.Write((ushort)orderedImages.Length);

            // save the image buffers and offsets
            Dictionary<uint, byte[]> buffers = new Dictionary<uint, byte[]>();

            // tracks the length of the buffers as the iterations occur
            // and adds that to the offset of the entries
            uint lengthSum = 0;
            uint baseOffset = (uint)(IconFactory.HeaderLength +
                                     IconFactory.EntryLength * orderedImages.Length);

            for (int i = 0; i < orderedImages.Length; i++)
            {
                Bitmap image = orderedImages[i];

                // creates a byte array from an image
                byte[] buffer = IconFactory.CreateImageBuffer(image);

                // calculates what the offset of this image will be
                // in the stream
                uint offset = (baseOffset + lengthSum);

                // writes the image entry
                writer.Write(IconFactory.GetIconWidth(image));
                writer.Write(IconFactory.GetIconHeight(image));
                writer.Write(IconFactory.PngColorsInPalette);
                writer.Write(IconFactory.EntryReserved);
                writer.Write(IconFactory.PngColorPlanes);
                writer.Write((ushort)Image.GetPixelFormatSize(image.PixelFormat));
                writer.Write((uint)buffer.Length);
                writer.Write(offset);

                lengthSum += (uint)buffer.Length;

                // adds the buffer to be written at the offset
                buffers.Add(offset, buffer);
            }

            // writes the buffers for each image
            foreach (var kvp in buffers)
            {

                // seeks to the specified offset required for the image buffer
                writer.BaseStream.Seek(kvp.Key, SeekOrigin.Begin);

                // writes the buffer
                writer.Write(kvp.Value);
            }
        }

    }

    private static void ThrowForInvalidPngs(IEnumerable<Bitmap> images)
    {
        foreach (var image in images)
        {
            if (image.PixelFormat != PixelFormat.Format32bppArgb)
            {
                throw new InvalidOperationException
                    (string.Format("Required pixel format is PixelFormat.{0}.",
                                   PixelFormat.Format32bppArgb.ToString()));
            }

            if (image.RawFormat.Guid != ImageFormat.Png.Guid)
            {
                throw new InvalidOperationException
                    ("Required image format is a portable network graphic (png).");
            }

            if (image.Width > IconFactory.MaxIconWidth ||
                image.Height > IconFactory.MaxIconHeight)
            {
                throw new InvalidOperationException
                    (string.Format("Dimensions must be less than or equal to {0}x{1}",
                                   IconFactory.MaxIconWidth, 
                                   IconFactory.MaxIconHeight));
            }
        }
    }

    private static byte GetIconHeight(Bitmap image)
    {
        if (image.Height == IconFactory.MaxIconHeight)
            return 0;

        return (byte)image.Height;
    }

    private static byte GetIconWidth(Bitmap image)
    {
        if (image.Width == IconFactory.MaxIconWidth)
            return 0;

        return (byte)image.Width;
    }

    private static byte[] CreateImageBuffer(Bitmap image)
    {
        using (var stream = new MemoryStream())
        {
            image.Save(stream, image.RawFormat);

            return stream.ToArray();
        }
    }

    #endregion

}

Usage:

using (var png16 = (Bitmap)Bitmap.FromFile(@"C:\Test\3dGlasses16.png"))
using (var png32 = (Bitmap)Bitmap.FromFile(@"C:\Test\3dGlasses32.png"))
using (var stream = new FileStream(@"C:\Test\Combined.ico", FileMode.Create))
{
    IconFactory.SavePngsAsIcon(new[] { png16, png32 }, stream);
}
test
  • 2,589
  • 2
  • 24
  • 52
3

Quick CYA: I just did a Google search, and have not tested the method below. YMMV.

I found this article, which mentions a class that does this (albeit in VB.Net, but easy enough to translate), and tells how he used it. While the page that the thread points to no longer appears to have the source code mentioned, I did find a version of it here.

Wonko the Sane
  • 10,623
  • 8
  • 67
  • 92
2

This can be done with IconLib. You can get the source from the CodeProject article or you can get a compiled dll from my GitHub mirror.

public void Convert(string pngPath, string icoPath)
{
    MultiIcon mIcon = new MultiIcon();
    mIcon.Add("Untitled").CreateFrom(pngPath, IconOutputFormat.FromWin95);
    mIcon.SelectedIndex = 0;
    mIcon.Save(icoPath, MultiIconFormat.ICO);
}

CreateFrom can take either the path to a 256x256 png or a System.Drawing.Bitmap object.

James EJ
  • 1,955
  • 1
  • 18
  • 16
1

You can't create an icon using the System.Drawing APIs. They were built for accessing specific icons from within an icon file, but not for writing back multiple icons to an .ico file.

If you are just wanting to make icons, you could use GIMP or another image processing program to create your .ico files. Otherwise if you really need to make the .ico files programatically, you could use png2ico (invoking using System.Diagnostics.Process.Start) or something similar.

Zach Johnson
  • 23,678
  • 6
  • 69
  • 86
0

Use IcoFX: http://icofx.ro/

It can create Windows icons and store multiple sizes and colors in 1 ico file

Icemanind
  • 47,519
  • 50
  • 171
  • 296