Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create an Icon file that contains Multiple Sizes / Images in C#

Tags:

c#

bitmap

icons

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.

like image 358
Geoff Avatar asked Jul 09 '10 15:07

Geoff


2 Answers

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);
}
like image 158
test Avatar answered Sep 23 '22 03:09

test


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.

like image 32
Wonko the Sane Avatar answered Sep 23 '22 03:09

Wonko the Sane