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 im
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);
}
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.
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.
Use IcoFX: http://icofx.ro/
It can create Windows icons and store multiple sizes and colors in 1 ico file
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.