やれること
こんな感じのWindowsアイコンが作れる。
UIを作りこむのが大変なので、下記のソースコード(EasyDrawIcon.cs
)を直接修正して使う方向で。
⇒ ツールにしました。 https://qiita.com/kob58im/items/7129810d3237e65b424d
注意事項
作ったアイコンについては著作権に注意してください。特にフォントには著作権があります。
どのフォントがセーフなのかは自分はわかりません。
ソースコード
以前のコードをちょっとリファクタリングして、アイコンデータ作成部分をIconUtility.cs
として分離した。
ソースコード - IconUtility.cs
IconUtility.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
namespace IconUtility
{
public class Icons
{
class IconEntry
{
[StructLayout(LayoutKind.Sequential)]
public struct IconDir
{
public short icoReserved; // must be 0
public short icoResourceType; //must be 1 for icon
public short icoResourceCount;
public IconDir(int n)
{
icoReserved = 0;
icoResourceType = 1;
icoResourceCount = (short)n;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct IconDirEntry
{
byte _Width;
byte _Height;
public byte ColorCount;
public byte reserved1;
public short reserved2;
public short reserved3;
public int icoDIBSize;
public int icoDIBOffset;
public int Width{get{return (_Width>0)?_Width:256;}}
public int Height{get{return (_Height>0)?_Height:256;}}
public IconDirEntry(int w, int h)
{
if ( w<0 || w>256 || h<0 || h>256 ) {
throw new Exception("Size parameter error");
}
_Width = (byte)w;
_Height = (byte)h;
ColorCount=0;
reserved1=0;
reserved2=0;
reserved3=0;
icoDIBSize=0;
icoDIBOffset=0;
}
}
[StructLayout(LayoutKind.Sequential)]
struct BitmapInfoHeader
{
public int biSize; // must be 40
public int biWidth;
public int biHeight;
public short biPlanes; // must be 1
public short biBitCount; // color
public int biCompression; // 0:not compress
public int biSizeImage;
public int biXPixPerMeter;
public int biYPixPerMeter;
public int biClrUsed;
public int biClrImportant;
public BitmapInfoHeader(int w, int h)
{
biSize = 40;
biWidth = w;
biHeight = h*2; // 本体とmaskを含むため2倍とする決まりらしい
biPlanes = 1;
biBitCount = 32;
biCompression=0;
biSizeImage=0;
biXPixPerMeter=0;
biYPixPerMeter=0;
biClrUsed=0;
biClrImportant=0;
}
}
IconDirEntry iconDirEntry;
BitmapInfoHeader bitmapInfoHeader;
byte[] bitmapBody;
byte[] bitmapMask;
public System.Drawing.Size Size{get{return new System.Drawing.Size(iconDirEntry.Width, iconDirEntry.Height);}}
public int Width{get{return iconDirEntry.Width;}}
public int Height{get{return iconDirEntry.Height;}}
int BitPerPixel{get{return bitmapInfoHeader.biBitCount;}}
int CalcDIBSize()
{
return Marshal.SizeOf(typeof(BitmapInfoHeader)) + bitmapBody.Length + bitmapMask.Length;
}
public int UpdateIconDirEntry(int icoDIBOffset)
{
iconDirEntry.icoDIBOffset = icoDIBOffset;
iconDirEntry.icoDIBSize = CalcDIBSize();
return iconDirEntry.icoDIBSize;
}
public void WriteIconDirEntryTo(BinaryWriter writer)
{
Icons.CopyDataToByteArray<IconDirEntry>(writer, iconDirEntry);
}
public void WriteDataTo(BinaryWriter writer)
{
Icons.CopyDataToByteArray<BitmapInfoHeader>(writer, bitmapInfoHeader);
writer.Write(bitmapBody);
writer.Write(bitmapMask);
}
IconEntry(IconDirEntry _iconDirEntry, BitmapInfoHeader _bitmapInfoHeader, byte[] _bitmapBody, byte[] _bitmapMask)
{
iconDirEntry = _iconDirEntry;
bitmapInfoHeader = _bitmapInfoHeader;
bitmapBody = _bitmapBody;
bitmapMask = _bitmapMask;
}
// 本体
public IconEntry(Bitmap bmp, Color? alphaColor)
{
int w = bmp.Width;
int h = bmp.Height;
if ( w>256 || h>256 ) {
throw new Exception("size parameter error");
}
iconDirEntry = new IconDirEntry(w, h);
bitmapInfoHeader = new BitmapInfoHeader(w, h);
bitmapBody = new byte[Icons.GetBitmapBodySize(w, h, bitmapInfoHeader.biBitCount)];
bitmapMask = new byte[Icons.GetBitmapMaskSize(w, h)];
Draw32bppBitmapToData(bmp, alphaColor);
}
void Draw32bppBitmapToData(Bitmap bmp, Color? alphaColor)
{
Array.Clear(bitmapMask, 0, bitmapMask.Length);
Array.Clear(bitmapBody, 0, bitmapBody.Length);
BitmapData bd = bmp.LockBits(new Rectangle(0,0,bmp.Width,bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
try {
IntPtr ptr = bd.Scan0;
byte[] pixels = new byte[bd.Stride * bmp.Height];
Marshal.Copy(ptr, pixels, 0, pixels.Length);
int maskStride = (((Width+7)/8+3)/4)*4;
int icoStride = Width*4;
for (int y = 0; y < bd.Height; y++) {
for (int x = 0; x < bd.Width; x++) {
int posIco = y * icoStride + 4*x;
int bytePosMask = y * maskStride + x/8;
int bitPosMask = 7-(x%8);
int pos = (bd.Height-1-y) * bd.Stride + x * 4;
bitmapBody[posIco ] = pixels[pos]; //blue;
bitmapBody[posIco+1] = pixels[pos+1]; //green;
bitmapBody[posIco+2] = pixels[pos+2]; //red;
bitmapBody[posIco+3] = pixels[pos+3]; //alpha
if ( pixels[pos+3] == 0 ||
(alphaColor != null &&
pixels[pos] ==alphaColor.Value.B &&
pixels[pos+1]==alphaColor.Value.G &&
pixels[pos+2]==alphaColor.Value.R )) {
//bitmapMask[bytePosMask] |= (byte)(1<<bitPosMask);
// 32bit色のiconだとmaskではなくalpha channelが使用されるっぽい
bitmapBody[posIco+3] = 0x00;
}
}
}
}
finally {
bmp.UnlockBits(bd);
}
}
}
int UpdateIconDirEntries()
{
iconDir.icoResourceCount = (short)iconEntries.Count;
int offset = Marshal.SizeOf(typeof(IconEntry.IconDir)) + iconEntries.Count * Marshal.SizeOf(typeof(IconEntry.IconDirEntry));
for (int i=0;i<iconEntries.Count;i++) {
offset += iconEntries[i].UpdateIconDirEntry(offset);
}
return offset;
}
static int GetBitmapBodySize(int w, int h, int bitCount)
{
return ((((w*bitCount + 7)/8)+3)/4)*4 * h;
}
static int GetBitmapMaskSize(int w, int h)
{
return ((((w+7)/8)+3)/4)*4 * h;
}
static TStruct CopyDataToStruct<TStruct> (BinaryReader reader) where TStruct : struct
{
var size = Marshal.SizeOf(typeof(TStruct));
var ptr = IntPtr.Zero;
try {
ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(reader.ReadBytes(size), 0, ptr, size);
return (TStruct)Marshal.PtrToStructure(ptr, typeof(TStruct));
}
finally {
if (ptr != IntPtr.Zero) {
Marshal.FreeHGlobal(ptr);
}
}
}
static void CopyDataToByteArray<TStruct>(BinaryWriter writer, TStruct s) where TStruct : struct
{
var size = Marshal.SizeOf(typeof(TStruct));
var buffer = new byte[size];
var ptr = IntPtr.Zero;
try {
ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(s, ptr, false);
Marshal.Copy(ptr, buffer, 0, size);
}
finally {
if (ptr != IntPtr.Zero) {
Marshal.FreeHGlobal(ptr);
}
}
writer.Write(buffer);
}
IconEntry.IconDir iconDir;
List<IconEntry> iconEntries;
// ------------------------------------------------------------------------------
// public members
public int Count {get{return iconEntries.Count;}}
public Icons()
{
iconDir = new IconEntry.IconDir(0);
iconEntries = new List<IconEntry>();
}
public void AddIcon(Bitmap bmp, Color alphaColor)
{
iconEntries.Add(new IconEntry(bmp, alphaColor));
UpdateIconDirEntries();
}
public void AddIcon(Bitmap bmp)
{
iconEntries.Add(new IconEntry(bmp, null));
UpdateIconDirEntries();
}
public bool SaveToFile(string path)
{
//int size =
UpdateIconDirEntries();
using ( var fs = new FileStream(path, FileMode.Create) ) {
using ( var writer = new BinaryWriter(fs) ) {
CopyDataToByteArray<IconEntry.IconDir>(writer, iconDir);
foreach(var t in iconEntries) {
t.WriteIconDirEntryTo(writer);
}
foreach(var t in iconEntries) {
t.WriteDataTo(writer);
}
}
}
return true;
}
}
}
ソースコード - EasyDrawIcon.cs
EasyDrawIcon.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using IconUtility;
class EasyDraw:Form
{
PictureBox pct;
const int MAX_SIZE = 64;
const int CANVAS_SIZE = 512;
// const int ZOOM = 2;
Font curFont;
int curFontStyle;
Pen curPen;
Brush curBrush;
PointF curPoint;
GraphicsPath curPath;
Graphics curG;
const string DefaultFontName = "メイリオ"; // フォントを変える場合はここを変更
EasyDraw()
{
curPen = Pens.Black;
curBrush = Brushes.White;
curPath = new GraphicsPath();
curFont = new Font(DefaultFontName, 30.0f); // フォントサイズを変える場合はここを変更
if (curFont.Name != DefaultFontName) {
MessageBox.Show("フォント \""+DefaultFontName+"\"の取得に失敗しました。\r\n"
+ "代わりにフォント\"" + curFont.Name + "\"が使用されます。\r\n");
}
MessageBox.Show("アイコンに文字を使う場合は、フォントの著作権に注意してください。");
curFontStyle = (int)FontStyle.Regular; // Bold , Italic , Strikeout , Underline の bitOR を設定可能
pct = new PictureBox();
pct.Image = new Bitmap(CANVAS_SIZE, CANVAS_SIZE);
pct.Dock = DockStyle.Fill;
Controls.Add(pct);
ClientSize = new Size(CANVAS_SIZE, CANVAS_SIZE);
Load+=(sender,e)=>{Draw();};
}
void moveto(float x, float y)
{
curPoint.X = x;
curPoint.Y = y;
}
void rmoveto(float rx, float ry)
{
curPoint.X += rx;
curPoint.Y += ry;
}
void lineto(float x, float y)
{
curPath.AddLine(curPoint.X, curPoint.Y, x, y);
curPoint.X = x;
curPoint.Y = y;
}
void rlineto(float rx, float ry)
{
curPath.AddLine(curPoint.X, curPoint.Y, curPoint.X+rx, curPoint.Y+ry);
curPoint.X += rx;
curPoint.Y += ry;
}
void charpath(string s)
{
var sf = new StringFormat();
sf.Alignment = StringAlignment.Center; // 横方向の中央
sf.LineAlignment = StringAlignment.Center; // 縦方向の中央
FontFamily ff = curFont.FontFamily;
curPath.AddString(s, ff, curFontStyle, curFont.Size, curPoint, sf);
}
void newpath()
{
curPath.Reset();
curPath.StartFigure();
}
void closepath()
{
curPath.CloseFigure();
}
void stroke()
{
if ( curG != null ) {
curG.DrawPath(curPen, curPath);
}
}
void fill()
{
if ( curG != null ) {
curPath.FillMode = FillMode.Winding;
curG.FillPath(curBrush, curPath);
}
}
void eofill()
{
if ( curG != null ) {
curPath.FillMode = FillMode.Alternate;
curG.FillPath(curBrush, curPath);
}
}
float cos(float degree)
{
return (float)Math.Cos((degree/180.0)*Math.PI);
}
float sin(float degree)
{
return (float)Math.Sin((degree/180.0)*Math.PI);
}
void Draw()
{
Bitmap bmp = CreateTransparentBitmap(MAX_SIZE, MAX_SIZE);
curG = Graphics.FromImage(bmp);
curG.SmoothingMode = SmoothingMode.AntiAlias;
try {
// ---- ここの中を変更すればよい
curBrush = new LinearGradientBrush(new Point(0, 0), new Point(64, 64), Color.Red, Color.Yellow);
newpath();
for ( int deg=0 ; deg<360 ; deg+=60 ) {
float x = 32+31*cos(deg);
float y = 32+31*sin(deg);
if ( deg == 0 ) {
moveto(x,y);
}
else {
lineto(x,y);
}
}
closepath();
fill();
curBrush = new LinearGradientBrush(new Point(0, 0), new Point(64, 64), Color.Blue, Color.Black);
newpath();
moveto(32,36); // (32,32)が中央だが、文字列描画位置がずれるので微調整
charpath("C#");
closepath();
fill();
// ----
}
finally {
curG.Dispose();
curG = null;
}
int zoom = 2;
Graphics g = Graphics.FromImage(pct.Image);
g.FillRectangle(Brushes.White, 0, 0, pct.Image.Width, pct.Image.Height);
g.DrawImage(bmp, 0, 0, bmp.Width*zoom, bmp.Height*zoom);
g.Dispose();
Icons icons;
icons = new Icons();
icons.AddIcon(bmp);
icons.SaveToFile("Output.ico");
}
// 透過色で初期化
Bitmap CreateTransparentBitmap(int width, int height)
{
Bitmap bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
BitmapData bd = bmp.LockBits(new Rectangle(0,0,width,height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
try {
unsafe {
// 書き込み
byte* ptr = (byte*)bd.Scan0;
for ( int y=0 ; y<height ; y++ ) {
for ( int x=0 ; x<width ; x++ ) {
ptr[y*bd.Stride + 4*x ] = 0;// B
ptr[y*bd.Stride + 4*x + 1] = 0;// G
ptr[y*bd.Stride + 4*x + 2] = 0;// R
ptr[y*bd.Stride + 4*x + 3] = 0;// alpha = 0 (透過)
}
}
}
}
finally {
bmp.UnlockBits(bd);
}
return bmp;
}
[STAThread]
static void Main()
{
Application.Run(new EasyDraw());
}
}
メソッド名について
PostScript言語をまねてみた。
コンパイル方法
csc /unsafe EasyDrawIcon.cs IconUtility.cs
アイコンのキャッシュを更新する方法
同じファイル名のアイコンをつくると、アイコンの表示が更新されない。
コマンドプロンプトなどで
Windows 10では「ie4uinit.exe -show
」、
それ以外のバージョンでは「ie4uinit.exe -ClearIconCache
」
を実行すればよい。
参考サイト: https://news.mynavi.jp/article/20180131-windows_icon/