Windows multi icon

Creating a Windows icon containing multiple bitmaps is not supported by .NET. Fortunately, on the net (the other one) I found a (version 1.1) .NET implementation to read a multi icon.

The re-usability wasn’t that great, merging and writing was not supported, and as .NET 1.1 did not support Typed lists yet, I had to rewrite it all:

[code=c#]using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;

namespace HelloWorld.Drawing
{
///

Represents a Windows multi icon, which is a collection of small bitmap images
/// used to represent an object. Icons can be thought of as transparent
/// bitmaps, although their size is determined by the system.
///

public class MultiIcon : List
{
///

Initializes a new instance of the Tjip.Drawing.MultiIcon class.

public MultiIcon() { }

///

Represents the Widows multi icon as System.String.

public override string ToString()
{
var str = string.Format(“{0} Items: {1}”,
GetType().FullName,
this.Count);
if (this.Count == 1)
{
str += string.Format(“, Size: {0} px”,
this.First().Width);
}
if (this.Count > 1)
{
str += string.Format(“, Smallest: {0} px, Largest: {1} px”,
this.GetSmallest().Width,
this.GetLargest().Width);
}
return str;
}

///

Loads a Windows multi icon based on the specfied path.

/// The filepath to load from. public static MultiIcon Load(string filepath)
{
using (var stream = new FileStream(filepath, FileMode.Open, FileAccess.Read))
{
return Load(stream);
}
}
///

Loads a Windows multi icon from a stream.

/// The stream to load from. public static MultiIcon Load(Stream stream)
{
// Read the stream.
using (var reader = new BinaryReader(stream))
{
var icon = new MultiIcon();
var entries = new List();

// Read the header.
var header = reader.ReadMultiIconHeader();

// Read the icon entries.
for (int i = 0; i < header.Count; i++)
{
var entry = reader.ReadMultiIconEntry();
entries.Add(entry);
}
// Read the icons based on the entries.
foreach (var entry in entries)
{
var ico = reader.ReadIcon(header, entry);
icon.Add(ico);
}
return icon;
}
}
}

///

Methods for Windows multi icon, that should be available
/// for all collections of Widows icons.

public static class MultiIconExtensions
{
///

Saves the Windows icons to a single file.

/// The list of icons. /// The filepath to save to. public static void Save(this IList icons, string filepath)
{
using (var stream = new FileStream(filepath, FileMode.CreateNew, FileAccess.Write))
{
icons.Save(stream);
}
}
///

Saves the Windows icons to a single stream.

/// The list of icons. /// The stream to save to. public static void Save(this IList icons, Stream stream)
{
using (var writer = new BinaryWriter(stream))
{
long startposition = stream.Position;

var entries = new List();
var buffers = new List();
// create header.
var header = new MultiIconHeader()
{
Count = (short)icons.Count,
};
// create entries.
for (int i = 0; i < icons.Count; i++)
{
using (var icon_stream = new MemoryStream())
{
var entry = new MultiIconEntry();
var item = icons[i];
item.Save(icon_stream);
icon_stream.Position = 0;

using (var reader = new BinaryReader(icon_stream))
{
// icoHeader.Reserved[2],
// icoHeader.Type[2],
// none.Pos[2],
// Width[1],
// Height[1],
// ColorCount[1],
// Reserved[1]
// Planes[2]
// BitCount[2]
// BytesInRes[4]
// none.OffSet[4]
var header_reserved = reader.ReadInt16();
var header_type = reader.ReadInt16();
var none_startpos = reader.ReadInt16();
entry.Width = reader.ReadByte();
entry.Height = reader.ReadByte();
entry.ColorCount = reader.ReadByte();
entry.Reserved = reader.ReadByte();
entry.Planes = reader.ReadInt16();
entry.BitCount = reader.ReadInt16();
entry.BytesInRes = reader.ReadInt32();
entry.ImageOffset = MultiIconHeader.ByteSize + MultiIconEntry.ByteSize * icons.Count + buffers.Sum(buf => buf.Length);
var none_offset = reader.ReadInt32();

var buffer = new byte[icon_stream.Length - icon_stream.Position];
icon_stream.Read(buffer, 0, buffer.Length);
entries.Add(entry);
buffers.Add(buffer);
}
}
}
// Writer header.
writer.Write(header);

// Write entries.
foreach (var entry in entries)
{
writer.Write(entry);
}
// Write images.
foreach (var buffer in buffers)
{
writer.Write(buffer);
}
// Clear buffer and save.
writer.Flush();
}
}

///

Gets the smallest icon.

/// The list of icons. /// The smallest icon.
public static Icon GetSmallest(this IEnumerable icons)
{
var icon =
(
from
item in icons
orderby
item.Width ascending
select
item
)
.FirstOrDefault();

return icon;
}
///

Gets the largest icon.

/// The list of icons. /// The largest icon.
public static Icon GetLargest(this IEnumerable icons)
{
var icon =
(
from
item in icons
orderby
item.Width descending
select
item
)
.FirstOrDefault();

return icon;
}
}
///

Represents the header of a Windows (multi) icon.

internal class MultiIconHeader
{
///

The byte size of a single icon header.

public const int ByteSize = 2 + 2 + 2;

///

Initializes a new instance of the Tjip.Drawing.MultiIconHeader class.

public MultiIconHeader()
{
this.Type = 1;
}

public short Reserved { get; set; }
public short Type { get; set; }
public short Count { get; set; }
}

///

Extension methods for MultiIconHeader.

internal static class MultiIconHeaderExtensions
{
///

Reads a Windows (multi) icon header from the current stream
/// and advances the current position of the stream by six bytes.
///

/// The reader. public static MultiIconHeader ReadMultiIconHeader(this BinaryReader reader)
{
var header = new MultiIconHeader()
{
Reserved = reader.ReadInt16(),
Type = reader.ReadInt16(),
Count = reader.ReadInt16(),
};
return header;
}

///

Writes a Windows (multi) icon header to the current stream
/// and advances the current position of the stream by six bytes.
///

/// The writer. /// The Windows multi icon header to write. public static void Write(this BinaryWriter writer, MultiIconHeader header)
{
writer.Write(header.Reserved);
writer.Write(header.Type);
writer.Write(header.Count);
}
}
///

Represents the entry of a Windows (multi) icon.

internal class MultiIconEntry
{
///

The byte size of a single icon entry.

public const int ByteSize = 1 + 1 + 1 + 1 + 2 + 2 + 4 + 4;

///

Initializes a new instance of the Tjip.Drawing.MultiIconEntry class.

public MultiIconEntry() { }

public byte Width { get; set; }
public byte Height { get; set; }
public byte ColorCount { get; set; }
public byte Reserved { get; set; }
public short Planes { get; set; }
public short BitCount { get; set; }
public int BytesInRes { get; set; }
public int ImageOffset { get; set; }
}

///

Extension methods for MultiIconEntry.

internal static class MultiIconEntryrExtensions
{
///

Reads a Windows (multi) icon entry from the current stream
/// and advances the current position of the stream by sixteen bytes.
///

/// The reader. public static MultiIconEntry ReadMultiIconEntry(this BinaryReader reader)
{
var entry = new MultiIconEntry()
{
Width = reader.ReadByte(),
Height = reader.ReadByte(),
ColorCount = reader.ReadByte(),
Reserved = reader.ReadByte(),
Planes = reader.ReadInt16(),
BitCount = reader.ReadInt16(),
BytesInRes = reader.ReadInt32(),
ImageOffset = reader.ReadInt32(),
};
return entry;
}

///

Reads a Windows icon from the current stream
/// and advances the current position of the stream to the end of the
/// Windows icon in the stream.
///

/// The reader. public static Icon ReadIcon(this BinaryReader reader, MultiIconHeader header, MultiIconEntry entry)
{
const short ICON_STREAM_START = 1;
const int ICON_STREAM_OFFSET = 22;

using (var newIcon = new MemoryStream())
{
using (var writer = new BinaryWriter(newIcon))
{
// Write it
writer.Write(header.Reserved);
writer.Write(header.Type);
writer.Write(ICON_STREAM_START);
writer.Write(entry.Width);
writer.Write(entry.Height);
writer.Write(entry.ColorCount);
writer.Write(entry.Reserved);
writer.Write(entry.Planes);
writer.Write(entry.BitCount);
writer.Write(entry.BytesInRes);
writer.Write(ICON_STREAM_OFFSET);

// Grab the icon
byte[] tmpBuffer = new byte[entry.BytesInRes];
reader.BaseStream.Position = entry.ImageOffset;
reader.Read(tmpBuffer, 0, entry.BytesInRes);
writer.Write(tmpBuffer);

// Finish up
writer.Flush();
newIcon.Position = 0;
return new Icon(newIcon);
}
}
}

///

Writes a Windows (multi) icon entry to the current stream
/// and advances the current position of the stream by sixteen bytes.
///

/// The writer. /// The Windows multi icon entry to write. public static void Write(this BinaryWriter writer, MultiIconEntry entry)
{
writer.Write(entry.Width);
writer.Write(entry.Height);
writer.Write(entry.ColorCount);
writer.Write(entry.Reserved);
writer.Write(entry.Planes);
writer.Write(entry.BitCount);
writer.Write(entry.BytesInRes);
writer.Write(entry.ImageOffset);
}
}
}[/code]

It works perfectly. The only thing I still need to solve is the conversion of PNG24 to ICO:
[code=c#]var icon = Icon.FromHandle(new Bitmap("some_pgn24.png").GetHicon());[/code]
This results in an icon that does not have an alpha-channel. When I solved this, I will inform you.

Leave a Reply

You must be logged in to post a comment.