C#でアプリケーションを作っていて、アイコンを設定したかったが、アイコンファイルを作成するのに少し手間取った。例えばWindowsに付属するペイントや、あるいは普段使用しているpaint.netではICOファイルを保存できない。
C#ではImage.SaveにImageFormatオプションでIconを指定できるが、この場合はPNGで保存されてしまう。
ICOファイルは結構簡単なフォーマットらしいので、C#でICOファイルを保存してみた。
流れとしては、外部のソフトウェア(適当なペイントソフトやドローソフト)で作成したアイコンの画像をC#で読み込み、それをICOのフォーマットに従って保存している。
static void ImagesToIcon(Image[] images, Stream dst)
{
if (dst.Length != 0 ||
images.Length < 1 ||
short.MaxValue < images.Length ||
images.Any(a => a.PixelFormat != System.Drawing.Imaging.PixelFormat.Format32bppArgb))
{
throw new InvalidOperationException();
}
; void WriteInt(int x)
=> dst.Write(MemoryMarshal.Cast<int, byte>(new(ref x)));
WriteInt(0x0001_0000);
dst.Write(BitConverter.GetBytes((ushort)images.Length));
dst.Write(new byte[images.Length * 16]);
foreach (var (i, image) in images.Select((a, i) => (i, a)))
{
var offset = dst.Position;
image.Save(dst, System.Drawing.Imaging.ImageFormat.Png);
var size = dst.Position - offset;
dst.Position = 6 + i * 16 + 8;
WriteInt((int)size);
WriteInt((int)offset);
dst.Position = dst.Length;
}
}
一部のパラメーター(解像度とかビット深度とか)は0を設定している。一応動作していることを確認しているが、すべての環境で動作するとは限らない(今回は自分用に作ったツールを識別する目的なので、自分のPCで動けばOKとした)。
ICOファイルにはいくつかのフォーマットで画像を埋め込むことができるらしいが、今回はPNGを埋め込んでいる。24bitPNGはだめらしく、32bitPNGが必要。読み込んだ画像が24bitだった場合はusing var bmp = tmp.Clone(new() { Size = tmp.Size }, PixelFormat.Format32bppArgb)
のような感じでフォーマットを変換しておく。もちろん透過を含む画像も扱える。