I need to write a program that will generate 108 combinaisons of icons (standard windows .ico files) based on a tileset image.
I use the class System.Drawing.Bitmap
It's true that the ImageFormat.Icon
does not work for writing as you'd suppose, .NET simply does not support writing .ico files and simply dumps the PNG data.
There are a few projects on CodeProject (and this one) (and another one) and that let you write an .ico
file, it's actually not that hard. The file format is pretty straight-forward, and supports BMP and PNG data.
I made a quick-and-dirty workaround myself, I post it here for the record (it might help someone that need a quick solution, like me).
I won't accept this as the correct answer, it's not an actual icon writer. It just write a 32bits ARGB bitmap into an ico file, using PNG format (works on Vista or later)
It is based on the ICO file format article from Wikipedia, and some fails and retry.
void SaveAsIcon(Bitmap SourceBitmap, string FilePath)
{
FileStream FS = new FileStream(FilePath, FileMode.Create);
// ICO header
FS.WriteByte(0); FS.WriteByte(0);
FS.WriteByte(1); FS.WriteByte(0);
FS.WriteByte(1); FS.WriteByte(0);
// Image size
FS.WriteByte((byte)SourceBitmap.Width);
FS.WriteByte((byte)SourceBitmap.Height);
// Palette
FS.WriteByte(0);
// Reserved
FS.WriteByte(0);
// Number of color planes
FS.WriteByte(0); FS.WriteByte(0);
// Bits per pixel
FS.WriteByte(32); FS.WriteByte(0);
// Data size, will be written after the data
FS.WriteByte(0);
FS.WriteByte(0);
FS.WriteByte(0);
FS.WriteByte(0);
// Offset to image data, fixed at 22
FS.WriteByte(22);
FS.WriteByte(0);
FS.WriteByte(0);
FS.WriteByte(0);
// Writing actual data
SourceBitmap.Save(FS, ImageFormat.Png);
// Getting data length (file length minus header)
long Len = FS.Length - 22;
// Write it in the correct place
FS.Seek(14, SeekOrigin.Begin);
FS.WriteByte((byte)Len);
FS.WriteByte((byte)(Len >> 8));
FS.Close();
}
Here's a simple ICO file writer I wrote today that outputs multiple System.Drawing.Image
images to a file.
// https://en.wikipedia.org/wiki/ICO_(file_format)
public static class IconWriter
{
public static void Write(Stream stream, IReadOnlyList<Image> images)
{
if (images.Any(image => image.Width > 256 || image.Height > 256))
throw new ArgumentException("Image cannot have height or width greater than 256px.", "images");
//
// ICONDIR structure
//
WriteInt16(stream, 0); // reserved
WriteInt16(stream, 1); // image type (icon)
WriteInt16(stream, (short) images.Count); // number of images
var encodedImages = images.Select(image => new
{
image.Width,
image.Height,
Bytes = EncodeImagePng(image)
}).ToList();
//
// ICONDIRENTRY structure
//
const int iconDirSize = 6;
const int iconDirEntrySize = 16;
var offset = iconDirSize + (images.Count*iconDirEntrySize);
foreach (var image in encodedImages)
{
stream.WriteByte((byte) image.Width);
stream.WriteByte((byte) image.Height);
stream.WriteByte(0); // no pallete
stream.WriteByte(0); // reserved
WriteInt16(stream, 0); // no color planes
WriteInt16(stream, 32); // 32 bpp
// image data length
WriteInt32(stream, image.Bytes.Length);
// image data offset
WriteInt32(stream, offset);
offset += image.Bytes.Length;
}
//
// Image data
//
foreach (var image in encodedImages)
stream.Write(image.Bytes, 0, image.Bytes.Length);
}
private static byte[] EncodeImagePng(Image image)
{
var stream = new MemoryStream();
image.Save(stream, ImageFormat.Png);
return stream.ToArray();
}
private static void WriteInt16(Stream stream, short s)
{
stream.WriteByte((byte) s);
stream.WriteByte((byte) (s >> 8));
}
private static void WriteInt32(Stream stream, int i)
{
stream.WriteByte((byte) i);
stream.WriteByte((byte) (i >> 8));
stream.WriteByte((byte) (i >> 16));
stream.WriteByte((byte) (i >> 24));
}
}