前のページ
最近一直在完善我今年的两款桌面软件:视频剪辑工具 Clipify 和 AI 文章创作工具 StarBlogPublisher
インターフェイスは基本的に完璧ですが、アイコンはデフォルトで、非常にプロフェッショナルではありません。
私はこの2つのソフトウェアをきれいにするつもりです。
VB 6.0では、オープンソースのICOアイコン作成ツールを使用していましたが、今は見つかりません。
オンラインで検索されるICOツールの多くはファビコン用です。
他のものは重すぎないか有料であるので、オープンソースツールに目を向けました。
依存関係のないnodejsベースの実装を見つけました(ほとんどがC++実装の画像ライブラリMagickに依存しています)が、残念ながらこのツールは私のコンピュータでは動作しません。
ターゲットは
C#で画像フォーマット変換ツールを開発し、. Net 8のAOT機能を使用して、Go言語のようにクロスプラットフォームの単一実行ファイルを開発できることを思い出しました。
そこで、C#を使ってアイコン生成ツールを開発することにしました。
- 純粋なC#実装、外部依存なし、クロスプラットフォーム、単一実行ファイル、AOT
- png画像を異なるサイズの複数の小さな画像(1辺16、32、48、64、128、256、512)に分割し、ICOアイコンを合成して、さまざまなサイズの画面で優れた視覚体験を実現します。
- ICOアイコンの読み取りと分析を可能にするInspect機能のサポート
- dotnet tool、scoop、brewなどのツールをワンクリックでインストールできる便利な配布方法
NugetやScoopにも公開されましたBrewにも公開されました
プロジェクトホームページhttps://github.com/star-plan/sharp-ico
実現すること
在 SharpIco 中,.ico 文件的生成完全不依赖 ImageMagick 或任何图像处理外部工具,而是通过纯 C# 代码手工拼接符合规范的 ICO 二进制结构。
这部分的核心类是 IcoGenerator,具体代码我就不贴了,在项目里有,挑几个要点介绍吧~
マルチサイズ画像の作成
ImageSharpを使用したマルチサイズ画像の生成
var clone = original.Clone(ctx => ctx.Resize(size, size));
clone.SaveAsPng(ms);
- ** ImageSharpのResizeおよびClone** 機能を使用して、生の高解像度PNGを複数のターゲットサイズ(16 x 16、32 x 32、256 x 256など)に生成します。
- 以 PNG 格式保存到内存流中,用于后续写入
.ico
💡ICOファイルはPNG画像の埋め込みをサポートしており(Vista以降)、体積を小さく保ち、透明度を向上させます。
ICOヘッダーを手動で作成
ICOファイル形式からICONDIRとICONDIRENTRYを手動でビルドする
ICOファイルのヘッダーは3つの部分から構成されます:
ICONDIR(6 字节):固定结构ICONDIRENTRY × N(每个 16 字节):描述每一张嵌入图像的尺寸、偏移Image Data × N:实际图像二进制数据
writer.Write((ushort)0); // Reserved
writer.Write((ushort)1); // Type = icon
writer.Write((ushort)images.Count); // Image count
- ICOヘッダを最初に書き込むICONDIR
- 次に、各画像の説明情報(幅、高さ、ビット深度、データオフセットなど)をループします。
writer.Write((byte)(img.Width == 256 ? 0 : img.Width)); // 256 用 0 表示
writer.Write((ushort)32); // bits per pixel
writer.Write(image.Length);
writer.Write(offset);
ICOファイルのwidth/heightフィールドが0の場合、256 を意味します。これはICO形式の特別な規定です。
ICOファイル形式には、画像サイズを表現する際の制限があります。幅と高さのフィールドはそれぞれ1バイトで、値の範囲は0-255です。これらのフィールドが0の、256ピクセルを仕様に表します。256より大きいサイズ(512×512や1024×1024など)の場合、ファイルヘッダには0(256)と表示されますが、実際の画像データにはより大きなサイズの画像を含めることができます。
画像をつなぎ合わせる
すべてのPNG画像データを接続する
foreach (var image in images) {
writer.Write(image);
}
- すべての記述情報が書かれた後、画像データ本体に書き込まれます。
- オフセットを事前に計算することで、各画像データを正しく認識して読み取ることができます。
拡張子
カスタム寸法のサポート
public static void GenerateIcon(string sourcePng, string outputIco, int[] sizes)
- 16~512の一般サイズをサポートします。
- パラメータを介して柔軟にサイズの組み合わせを指定できます(32/256のみパッケージ)。
Inspectの機能
除了图标生成,SharpIco 还内置了一个图标内容分析工具 IcoInspector,可以帮助开发者深入理解 .ico 文件内部结构,并验证实际包含的图层图像尺寸与位深信息,解决市面上不少图标工具生成不规范 .ico 文件的问题。
アイデアを簡単に紹介します。
ICOヘッダーの読み込み
ICONDIR + ICONDIRENTRY構造体の手動読み込み
ICOファイルのヘッダ構造は3つの部分から構成されます。
ICONDIR(6 字节):标记为图标、记录图像数量ICONDIRENTRY × N(每个 16 字节):记录每张图片的元信息(宽、高、位深、数据偏移等)- 画像データブロック:実際のPNGまたはBMP画像
ushort reserved = reader.ReadUInt16(); // 必须为0
ushort type = reader.ReadUInt16(); // 1表示图标
ushort count = reader.ReadUInt16(); // 图像数量
その後、画像エントリを1つずつ読み込み、その後の処理のためにメモリに保存します。
byte width = reader.ReadByte();
byte height = reader.ReadByte();
ushort bitCount = reader.ReadUInt16();
int sizeInBytes = reader.ReadInt32();
int imageOffset = reader.ReadInt32();
对了,前面介绍过,ICO 头部的空间有限,只能存 8 位,所以如果 width/height 字段为 0,根据规范表示 256,或者是超过 256
画像データの抽出と解析
画像データの抽出と解像度検証
ICO 文件中记录的宽高未必真实,尤其是嵌入 PNG 格式的情况。因此,使用 ImageSharp 对每张图像进行真正解析。
fs.Seek(entry.ImageOffset, SeekOrigin.Begin);
fs.Read(imageData, 0, dataSize);
Image.Load(imageData) → 获取真实 Width 和 Height
- 通过
GetImageDimensions()方法判断是否为 PNG,并用ImageSharp加载 - フォーマットが間違っている場合、または読み取りに失敗した場合、ヘッダーで宣言された寸法にロールバック
これは、特定の“偽ICO”の問題(サイズと画像の内容の不一致など)を検出するために使用できます。
分析結果の補完と出力
すべてのエントリが読み込まれて解析されると、コンテンツを構造化情報として出力します。
正在检查ICO文件: logo.ico
图标数量: 7
- 第1张图像: 16x16, 32bpp, 大小: 840字节, 偏移: 118
- 第2张图像: 32x32, 32bpp, 大小: 1939字节, 偏移: 958
- 第3张图像: 48x48, 32bpp, 大小: 3375字节, 偏移: 2897
- 第4张图像: 64x64, 32bpp, 大小: 4951字节, 偏移: 6272
- 第5张图像: 128x128, 32bpp, 大小: 13782字节, 偏移: 11223
- 第6张图像: 256x256, 32bpp, 大小: 37823字节, 偏移: 25005
- 第7张图像: 512x512, 32bpp, 大小: 114655字节, 偏移: 62828
注意: 文件头中指定的尺寸为256x256,但实际图像尺寸为512x512
迅速に確認できます:
- 一个
.ico文件中包含多少图层 - 各レイヤーの実際のサイズと深さ
- フォーマットの問題があるかどうかサイズの一致が合わない、サイズの異常など
コマンドラインインタフェースの設計
SharpIcoは単なるコードベースではありません。完全なコマンドラインツールを提供しており、手動で使用してもビルドスクリプトに統合しても、あらゆる開発シナリオで素早く呼び出すことができます。
这一部分使用了 .NET 的现代 CLI 构建库 System.CommandLine,实现了两个主命令:
generate:将 PNG 图像转换为 ICO 图标inspect:检查 ICO 文件的结构与图层信息
コマンドラインの使い方:
sharpico generate -i logo.png -o icon.ico --sizes 16 32 48 256
sharpico inspect icon.ico
🛠️ 生成命令 generate
このコマンドは、入力、出力パス、および生成アイコンのサイズをパラメータで制御できます。
sharpico generate --input logo.png --output app.ico --sizes 16 32 64 256
パラメーターの説明:
| パラメータ | 短縮形の短縮形 | 説明書の作成 |
|---|---|---|
--input |
-i |
ソースPNGイメージパス |
--output |
-o |
ICOファイルのパスを出力する必須 |
--sizes |
-s |
生成するアイコンサイズのリスト、サポート(既定では一般的な7つのサイズ) |
支持多个尺寸同时生成,内部调用
IcoGenerator.GenerateIcon()实现多图层.ico文件创建。
一例として:
sharpico generate -i logo.png -o icon.ico -s 32 64 256
出力には、3つの解像度レイヤーが含まれます。
🔍 检查命令 inspect
可以对任意 .ico 文件进行结构检查与验证:
sharpico inspect icon.ico
これにより、アイコン内の各レイヤーが出力されます。
- 幅と高さ(宣言と実際の寸法)
- ビット深さbpp
- データオフセットとサイズ
- 頭部が実際の画像サイズと一致しないという潜在的な問題がないか
エクスポート例:
正在检查ICO文件: logo.ico
图标数量: 7
- 第1张图像: 16x16, 32bpp, 大小: 840字节, 偏移: 118
- 第2张图像: 32x32, 32bpp, 大小: 1939字节, 偏移: 958
- 第3张图像: 48x48, 32bpp, 大小: 3375字节, 偏移: 2897
- 第4张图像: 64x64, 32bpp, 大小: 4951字节, 偏移: 6272
- 第5张图像: 128x128, 32bpp, 大小: 13782字节, 偏移: 11223
- 第6张图像: 256x256, 32bpp, 大小: 37823字节, 偏移: 25005
- 第7张图像: 512x512, 32bpp, 大小: 114655字节, 偏移: 62828
注意: 文件头中指定的尺寸为256x256,但实际图像尺寸为512x512
出版物を
SharpIco 不只是一个库,也不是一个只能源码使用的小工具,而是一个可以 通过 dotnet tool 全平台安装运行、支持 AOT 编译优化性能和体积 的专业图标工具。
今回はAOTと従来のリリースの両方をサポートする必要があるため、プロジェクトファイルの設定を行いました。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- AOT编译设置移至条件属性组 -->
<InvariantGlobalization>true</InvariantGlobalization>
<!-- .NET Tool 配置 -->
<PackAsTool>true</PackAsTool>
<ToolCommandName>sharpico</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>
<!-- 包信息 -->
<PackageId>SharpIco</PackageId>
<Version>1.0.0</Version>
<Authors>StarPlan</Authors>
<Description>SharpIco是一个纯 C# AOT 实现的轻量级图标生成工具,用于生成和检查ICO图标文件。可将一张高分辨率 PNG 图片一键生成标准的 Windows .ico 图标文件,内含多种尺寸(16x16 到 512x512),还可以自定义尺寸。除了图标生成,SharpIco 还内置图标结构分析功能,助你轻松验证 .ico 文件中包含的图层与尺寸。</Description>
<PackageTags>icon;ico;png;converter,DealiAxy,cli,tool,dotnet-tool,imagesharp</PackageTags>
<PackageProjectUrl>https://github.com/star-plan/sharp-ico</PackageProjectUrl>
<RepositoryUrl>https://github.com/star-plan/sharp-ico</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<!-- AOT发布专用设置 -->
<PropertyGroup Condition="'$(PublishAot)' == 'true'">
<PublishAot>true</PublishAot>
<TrimMode>full</TrimMode>
<InvariantGlobalization>true</InvariantGlobalization>
<IlcGenerateStackTraceData>false</IlcGenerateStackTraceData>
<IlcOptimizationPreference>Size</IlcOptimizationPreference>
<IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.8" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
</ItemGroup>
</Project>
いくつかのポイント:
CLIツール(dotnet tool)として定義
<PackAsTool>true</PackAsTool>
<ToolCommandName>sharpico</ToolCommandName>
这使得 SharpIco 可以像任何其他 .NET CLI 工具 一样被全局安装:
dotnet tool install -g SharpIco --add-source ./nupkg
インストール後は、以下を実行します。
sharpico generate -i logo.png -o icon.ico
AOTコンパイルリリースのサポート(. NET 9ネイティブサポート)
<PublishAot>true</PublishAot>
<IlcOptimizationPreference>Size</IlcOptimizationPreference>
<TrimMode>full</TrimMode>
PublishAot=trueを設定することで、SharpIcoはネイティブ実行ファイルへのコンパイルをサポート
NETランタイムサポートなし、起動速度が非常に高速で、ビルドツールチェーンや統合環境に適しています。
AOTビルドコマンドの例
dotnet publish -c Release -r win-x64 /p:PublishAot=true
生成的 sharpico.exe 是一个纯原生的 Windows 可执行文件,无需安装 .NET!
除 Windows 外,也可发布为
linux-x64,osx-arm64等跨平台目标。
nugetの自動公開
SharpIcoは完全なGitHub Actions CI/CDパイプラインを採用しており、一度にタグ付けしてプラットフォーム全体のビルドを自動化しています。
只需推送一个符合语义化格式的标签(如 v1.0.0),系统将自动:
- NuGet Toolkitのビルドと公開
- Windows / Linux / macOS用のネイティブAOT実行ファイルのコンパイル
- すべての製品をGitHubリリースページに自動アップロード
私は以前、Nugetの公開方法について書きました。
- 开发现代化的.NET Core控制台程序 (3) 将nuget包发布到GitHubPackages
- 开发现代化的.NET Core控制台程序 (4) 使用GitHubAction自动构建以及发布nuget包
この部分は問題が少ない
しかし、今回は少し異なります。以前にリリースされたライブラリとプロジェクトテンプレートは、今回はDotnetツールです。
これはnpxスクリプトやpipツールなどの概念に似ています
dotnet toolコマンドを使用してインストールおよび起動できます。
この方法ではAOTは使用できず、フレームワーク依存の方法でのみ公開できます。
Nugetはdotnetコマンドを使用して簡単に配布できます。
dotnet pack -c Release
重要なのは、GitHub Actionを使ってビルドとリリースを自動化することです。
name: 发布SharpIco
run-name: ${{ github.actor }} 正在发布SharpIco 🚀
on:
push:
tags:
- "v*.*.*" # 更明确的版本格式匹配
# 为整个工作流设置权限
permissions:
contents: write
id-token: write
issues: write
jobs:
# 第一步:发布NuGet包
publish-nuget:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # 获取所有历史记录用于版本号计算
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 9.0.x
- name: 缓存NuGet包
uses: actions/cache@v3
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: 提取版本号
id: get_version
shell: bash
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: 恢复依赖
run: dotnet restore ./SharpIco/SharpIco.csproj
- name: 运行测试
run: dotnet test --no-restore
- name: 构建项目
run: dotnet build --no-restore -c Release --nologo ./SharpIco/SharpIco.csproj -p:Version=${{ steps.get_version.outputs.VERSION }}
- name: 创建NuGet包
run: dotnet pack -c Release ./SharpIco/SharpIco.csproj -p:PackageVersion=${{ steps.get_version.outputs.VERSION }} --no-build --output ./nupkg
- name: 发布到NuGet Gallery
run: dotnet nuget push ./nupkg/*.nupkg --api-key ${{ secrets.NUGET_GALLERY_TOKEN }} --source https://api.nuget.org/v3/index.json --skip-duplicate
GitHubへの自動公開
これは最悪で、私は成功するまでに10回以上テストしました。
GitHub Actionのデバッグはフレンドリーではないと思います。
私の最後の成功を投稿してください。
name: 发布SharpIco
run-name: ${{ github.actor }} 正在发布SharpIco 🚀
on:
push:
tags:
- "v*.*.*" # 更明确的版本格式匹配
# 为整个工作流设置权限
permissions:
contents: write
id-token: write
issues: write
jobs:
# 第二步:编译各平台可执行文件
build-executables:
needs: publish-nuget # 确保在NuGet包发布后运行
strategy:
fail-fast: false
matrix:
kind: ['windows', 'linux', 'macOS']
include:
- kind: windows
os: windows-latest
target: win-x64
extension: '.zip'
- kind: linux
os: ubuntu-latest
target: linux-x64
extension: '.tar.gz'
- kind: macOS
os: macos-latest
target: osx-x64
extension: '.tar.gz'
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # 获取所有历史记录用于版本号计算
- name: 提取版本号
id: get_version
shell: bash
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 9.0.x
- name: 缓存NuGet包
uses: actions/cache@v3
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: 安装Linux依赖
if: matrix.kind == 'linux'
run: |
sudo apt-get update
sudo apt-get install -y clang zlib1g-dev libkrb5-dev
- name: 设置Windows环境
if: matrix.kind == 'windows'
shell: pwsh
run: |
Write-Host "设置Windows编译环境..."
# 确保有最新的开发者工具
choco install visualstudio2022buildtools -y --no-progress
- name: 恢复依赖
run: dotnet restore ./SharpIco/SharpIco.csproj
- name: AOT编译
run: |
echo "正在为 ${{ matrix.kind }} 平台进行AOT编译..."
dotnet publish ./SharpIco/SharpIco.csproj -c Release -r ${{ matrix.target }} --self-contained true -p:PublishAot=true -p:Version=${{ steps.get_version.outputs.VERSION }} -o ./publish/${{ matrix.kind }}
- name: 打包Windows可执行文件
if: matrix.kind == 'windows'
run: |
cd ./publish/${{ matrix.kind }}
7z a -tzip ../../SharpIco-${{ matrix.kind }}-${{ steps.get_version.outputs.VERSION }}${{ matrix.extension }} *
- name: 打包Linux/macOS可执行文件
if: matrix.kind != 'windows'
run: |
cd ./publish/${{ matrix.kind }}
tar -czvf ../../SharpIco-${{ matrix.kind }}-${{ steps.get_version.outputs.VERSION }}${{ matrix.extension }} *
# 上传构建产物作为工作流构件(artifacts)
- name: 上传构建产物
uses: actions/upload-artifact@v4
with:
name: SharpIco-${{ matrix.kind }}-${{ steps.get_version.outputs.VERSION }}
path: ./SharpIco-${{ matrix.kind }}-${{ steps.get_version.outputs.VERSION }}${{ matrix.extension }}
retention-days: 1
# 第三步:统一上传所有平台可执行文件到GitHub Release
upload-to-release:
needs: build-executables
runs-on: ubuntu-latest
steps:
- name: 提取版本号
id: get_version
shell: bash
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
# 下载所有平台构建产物
- name: 下载Windows构建产物
uses: actions/download-artifact@v4
with:
name: SharpIco-windows-${{ steps.get_version.outputs.VERSION }}
path: ./artifacts
- name: 下载Linux构建产物
uses: actions/download-artifact@v4
with:
name: SharpIco-linux-${{ steps.get_version.outputs.VERSION }}
path: ./artifacts
- name: 下载macOS构建产物
uses: actions/download-artifact@v4
with:
name: SharpIco-macOS-${{ steps.get_version.outputs.VERSION }}
path: ./artifacts
# 列出下载的文件以确认
- name: 列出下载的文件
run: ls -la ./artifacts
# 统一上传到GitHub Release
- name: 上传所有文件到GitHub Release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: ./artifacts/*
tag_name: ${{ github.ref }}
fail_on_unmatched_files: false
draft: false
name: SharpIco 版本 ${{ steps.get_version.outputs.VERSION }}
generate_release_notes: true
ワークフローの概要
ワークフローは3つの主要なジョブで構成されます。
| ステージは | 説明する。 |
|---|---|
publish-nuget |
编译项目并发布 .nupkg 包到 NuGet.org |
build-executables |
3つのプラットフォーム用のAOTネイティブ実行ファイルのコンパイル |
upload-to-release |
すべての製品を現在のタグに対応するGitHubリリースにアップロードする |
nuget 部分の公開は簡単なので、ここでは繰り返さない
3プラットフォームAOT実行可能ファイルのコンパイル(build-executables)
GitHub Matrixビルド戦略を使用して、以下の目的を達成します。
win-x64(Windows 可执行文件,ZIP 打包)linux-x64(Linux ELF,可执行,tar.gz 打包)osx-x64(macOS 可执行,tar.gz 打包)
dotnet publish -c Release -r ${{ matrix.target }} --self-contained true -p:PublishAot=true
每个平台生成对应压缩包,打包后使用 upload-artifact 上传中转。
GitHubリリースへのアップロード(upload-to-release)
このフェーズでは、以前にビルドされたものが自動的にダウンロードされ、現在のリリースページにパッケージされます。
uses: softprops/action-gh-release@v1
发布页面自动生成更新说明(generate_release_notes: true),方便用户查看版本变更。
最終的な効果は次のとおりです。
🔖 v1.0.0
├── SharpIco-windows-v1.0.0.zip
├── SharpIco-linux-v1.0.0.tar.gz
└── SharpIco-macOS-v1.0.0.tar.gz
ピット·ピット
这里最大的坑就是原本使用 actions/upload-artifact@v3 一直报错,后面查了 issue 发现是已经 deprecate 了
V 4へのアップグレード問題解決
GitHubリリースは常に競合し、常に存在すると言います。
appendが設定されていてもできません
その後、最初に転送をアップロードし、一度リリースしてようやく成功しました。
概要まとめまとめ
SharpIco 是我基于 .NET 9 和 AOT 编译能力打造的一款纯 C# 图标工具,目标是替代繁琐依赖、简化图标生成与验证流程。
从 ImageSharp 图像处理,到 BinaryWriter 手工拼装 ICO 格式;从 System.CommandLine 打造命令行体验,到 GitHub Actions 全流程自动化发布;SharpIco 不仅是一个实用工具,也是一场小而美的工程探索。
軽量、純粋、統合が容易、クロスプラットフォーム、すぐに使えるというツールの理想を追求しています。
- Python、Node、ImageMagickのインストールは不要です。
- AOTコンパイルをサポートし、ネイティブ実行ファイルを生成
- アイコン生成と構造解析の機能を搭載
- 1回のリリースで、プラットフォーム全体の構築結果を自動出力
Star / Fork / Issue / PRへようこそ。ビルドシステムに統合してください。
📦GitHubプロジェクトアドレス 👉 https://github.com/star-plan/sharp-ico