3

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 to build each combinaison, and I save them like this:

Bitmap IconBitmap = new Bitmap(16, 16);
// Some processing, writing different parts of the source tileset
// ...
IconBitmap.Save(Path.Combine(TargetPath, "Icon" + Counter + ".ico"),
                ImageFormat.Icon);

But I found out that the file saved is actually a PNG. Neither Windows Explorer nor Visual Studio can display it correctly, but GIMP can, and if I open it in an Hex viewer, here is what i see:

00000000  89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52  ‰PNG........IHDR
00000010  00 00 00 10 00 00 00 10 08 06 00 00 00 1F F3 FF  ..............óÿ
00000020  61 00 00 00 01 73 52 47 42 00 AE CE 1C E9 00 00  a....sRGB.®Î.é..
00000030  00 04 67 41 4D 41 00 00 B1 8F 0B FC 61 05 00 00  ..gAMA..±..üa...
00000040  00 09 70 48 59 73 00 00 0E C3 00 00 0E C3 01 C7  ..pHYs...Ã...Ã.Ç
00000050  6F A8 64 00 00 00 15 49 44 41 54 38 4F 63 60 18  o¨d....IDAT8Oc`.
00000060  05 A3 21 30 1A 02 A3 21 00 09 01 00 04 10 00 01  .£!0..£!........
00000070  72 A5 13 76 00 00 00 00 49 45 4E 44 AE 42 60 82  r¥.v....IEND®B`‚

Also if I rename the .ico to .png Windows Explorer can display it properly.

I have this result even if I do NOTHING on the bitmap (I construct it with new and Save it directly, that gives me a black png).

What am I doing wrong?

I also tried this, which gave me awful 16 color icons, but I would prefer to avoid this solution anyway (using handles) :

Icon NewIcon = Icon.FromHandle(IconBitmap.GetHicon());
FileStream FS = new FileStream(Path.Combine(Target, "Icon" + Counter + ".ico"),
        FileMode.Create);
NewIcon.Save(FS);
Benlitz
  • 1,952
  • 1
  • 17
  • 30
  • 2
    Looking at this on [Microsoft Support](http://support.microsoft.com/default.aspx?scid=kb;en-us;q316563), it seems you are out of luck. – Steve Jul 11 '12 at 14:27
  • ICO is a container format that can accept either PNG or bitmap-encoded images, so the behavior you are seeing there is correct. It might be worth it for you to share your processing code so that we can see if you are doing anything there which would make your icon not display correctly. Also, I think the ability to display PNG ico files only started with Vista, so if you are on XP or need XP compatibility, you may be out of luck. – Chris Dworetzky Jul 11 '12 at 14:27
  • 2
    possible duplicate http://stackoverflow.com/questions/4042488/how-to-save-bitmap-as-icon – Antonio Bakula Jul 11 '12 at 14:30
  • Steve: thanks for the link, unfortunately this bug is *by design*... it's a shame it doesn't even throw an exception. FurDworetzky: no it's not correct, I have an actual PNG as result, and as you pointed out, ICO is a container. There's no container data in the resulting file. AntonioBakula: seems to be, but I didn't find it in my first searchs... – Benlitz Jul 12 '12 at 08:14

3 Answers3

3

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();
}
Benlitz
  • 1,952
  • 1
  • 17
  • 30
3

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));
    }
}
Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
0

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.

CodeCaster
  • 147,647
  • 23
  • 218
  • 272