<概要>
VB.NETで、複数画像(Bitmap配列)を元にPNG形式のアイコンファイル(.ico)を作成するサンプルプログラムです。アイコンファイルのフォーマットの説明はあれども、簡単に実装できそうなサンプルはなかったので作成してみました(これまた需要は少なそうですが…💧)。フルカラーで複数サイズ(最大256x256)の画像を埋め込みできます。
なお、(画像さえ準備できれば)画像ファイルを読み込んでBitmap配列に落とし込む部分は簡単に実現できるかと思いますので、その部分はサンプルには含んでいません。
※フルカラーでなくて良いならVisualStudioのアイコンエディタを使って作成することもできます。
※単一の画像ファイルから小さいアイコンを作成するのであれば、Iconクラスだけで簡単に実装できるようです。(その場合はこちら↓のサイトが参考になると思います)
アイコンファイルを作成するに当たり、ファイルフォーマットに関しては次のサイトを参考にさせて頂きました。
こちらのサイトに記載されているフォーマットはビットマップ形式がベースとなっていましたが、ビットマップですと透過色をどのように設定するのか等々、勉強することが非常に多くとても大変そうでしたので、当記事ではPNG形式のアイコンとしました。
PNG形式の場合は
・ファイルヘッダー(6バイト)
・アイコンヘッダー(16バイト×アイコンの数)
・アイコンデータ (PNGヘッダ含め、全アイコン分を一括出力)
という構成になります。
<サンプルソース>
Bitmap配列 → Icoの変換関数
''' <summary>
''' Bitmapリストの内容をPNG形式のIconファイルとして保存
''' </summary>
Private Sub BitmapListToIcon(ByRef listBitmap As List(Of Bitmap), ByVal saveFileName As String)
Const C_FILE_HEAD_SIZE = 6
Const C_ICON_HEAD_SIZE = 16
Dim wFileHead(C_FILE_HEAD_SIZE - 1) As Byte 'ファイルヘッダ(6バイト)
'icoReserved 予約(常に0)
wFileHead(0) = &H0
wFileHead(1) = &H0
'icoResourceType リソースタイプ(1-アイコン、2-カーソル)
wFileHead(2) = &H1 '上位ビットと下位ビットは逆転している、、、
wFileHead(3) = &H0
'icoResourceCount 作成するアイコンファイルに含めるアイコンの数(=ビットマップ配列の数)
Dim countBytes As Byte() = BitConverter.GetBytes(CShort(listBitmap.Count))
wFileHead(4) = countBytes(0)
wFileHead(5) = countBytes(1)
'ファイルヘッダをファイルに出力(新規 または 上書き)
My.Computer.FileSystem.WriteAllBytes(saveFileName, wFileHead, False)
'--
'-- アイコンヘッダの作成
'--
'各アイコンデータのファイル内での位置を初期セット(ファイルヘッダー(6バイト)+アイコンヘッダー(16バイト×アイコン数))
Dim startAddr As Integer = C_FILE_HEAD_SIZE + (listBitmap.Count * C_ICON_HEAD_SIZE)
'アイコンデータを溜め込んで置く領域の宣言
Dim listData As New List(Of System.IO.MemoryStream)
'
For Each wBitmap As Bitmap In listBitmap
'ビットマップをPNG形式に変換してメモリストリームに保存
Dim m As New System.IO.MemoryStream
wBitmap.Save(m, System.Drawing.Imaging.ImageFormat.Png)
'メモリの内容は全ヘッダー出力後にファイルに書き込むため、保存しておく
listData.Add(m)
'--
'-- アイコンヘッダの設定
'--
Dim wIconHead(C_ICON_HEAD_SIZE - 1) As Byte
'幅
wIconHead(0) = IntToByte(wBitmap.Width)
'高さ
wIconHead(1) = IntToByte(wBitmap.Height)
'カラー数(※256色以上は0をセット。フルカラーのはずなのでここでは固定でゼロをセット)
wIconHead(2) = &H0
'予約
wIconHead(3) = &H0
'予約(ホットスポットx座標)※設定内容不明。参考にしたアイコンがこの値だった、、、
wIconHead(4) = &H1
wIconHead(5) = &H0
'予約(ホットスポットy座標)※設定内容不明。参考にしたアイコンがこの値だった、、、
wIconHead(6) = &H20
wIconHead(7) = &H0
'アイコンデータのサイズ(要素8~11の4バイトにセット)
IntSetToByteArr(m.Length, wIconHead, 8)
'アイコンデータのファイル内での開始位置(要素12~15の4バイトにセット)
IntSetToByteArr(startAddr, wIconHead, 12)
'
'アイコンヘッダをファイルに出力(追加で書き込む)
My.Computer.FileSystem.WriteAllBytes(saveFileName, wIconHead, True)
'次画像用にファイル内での開始位置をずらす
startAddr += m.Length
Next
'アイコンデータをファイルに出力
For Each m As System.IO.MemoryStream In listData
'アイコンデータをファイルに出力(追加で書き込む)
My.Computer.FileSystem.WriteAllBytes(saveFileName, m.ToArray, True)
Next
'メモリストリームの解放
For i As Integer = listData.Count - 1 To 0 Step -1
listData(i).Dispose()
listData.RemoveAt(i)
Next
End Sub
''' <summary>
''' Integerをバイトに変換して返却
''' </summary>
Private Function IntToByte(ByVal value As UInteger) As Byte
If value >= 256 Then
Return &H0 '1バイトなので256以降はゼロで保存
Else
Return Convert.ToByte(value)
End If
End Function
''' <summary>
''' Integer値を4バイトのバイナリ値に変換し、引数「バイト配列」の特定位置にセットする
''' </summary>
Private Sub IntSetToByteArr(ByVal value As Integer, ByVal byteArr As Byte(), ByVal startIdx As Integer)
Dim wBytes() As Byte = BitConverter.GetBytes(value)
wBytes.CopyTo(byteArr, startIdx)
End Sub
<実行イメージ>
<補足>
・プログラムを見て頂ければわかるように、基本的にはフォーマット通りに、ファイルヘッダーやアイコンヘッダー、アイコンデータをファイルの特定の位置に出力しているだけです。
データに関してもBitmapクラスの標準機能を利用して画像をPNG形式に変換しているだけですので、特に難しいことはしていません。
--
・アイコンヘッダーにはファイル内におけるデータの開始位置(ヘッダーに対応するデータの開始位置)やデータサイズを埋め込む必要があったため、BITMAPを先にPNG形式へと変換し溜め込んでから、ヘッダーを出力し、ヘッダー出力が全て終わった後に、溜め込んだものを全てファイル出力する構造となっています。
--
・注意点としては、数値型変数はメモリ上は上位ビットと下位ビットが逆転しているところでしょうか。
例えば10進数「256」は16進数では「0100」ですが、これを2バイトの変数に保存するとメモリ上は最初のアドレスに「00」が、次のアドレスに「01」が保存されメモリを上から順番に見ると「0001」であるかのように見えます。
サンプル中でリソースタイプの値が1なのに、2バイト目に「1」、3バイト目に「3」が入っていて、設定値があたかも「0100」=256であるかのように見えるかもしれませんが正しい値が設定されています。
BitConverter.GetBytesで必要なバイト配列に変換すれば特に意識する必要は無いとは思いますが、手動でバイト単位に値を設定する場合はご注意ください。
--
以上です!