I.要件分析とプログラム設計
開発作業では、画像をさまざまなサイズのアイコンファイルに変換する必要がよくあります。ウェブサイトのfavicon.icoを作成する場合でも、アプリのアイコンをデザインする場合でも、これは一般的なニーズです。市場には多くの画像からアイコンへのツールがありますが、それらは通常、単一機能、広告、または複雑な操作の問題があります。
この記事では、C#とAvaloniaを使用して以下の機能を実現するシンプルで効率的な画像変換ツールを開発する方法を説明します。
- 一般的な画像形式(PNG、JPGなど)をICO形式に変換できます。
- 多様なサイズのアイコンの生成をサポート(16x16、32x32、48x48、64x64、128x128、256x256)
- 次の2つの変換モードがあります。
- マージモード:複数のサイズのアイコンを1つのICOファイルにマージする
- 分離モード:寸法ごとに個別のICOファイルを生成
- ドラッグ&ドロップ操作をサポートし、ユーザーエクスペリエンスを向上
2.コアトランスファーコード
首先,我们来看核心的图片转 Icon 转换逻辑。这部分代码封装在ImageHelper类中:
using ImageMagick;
using System.IO;
using System.Threading.Tasks;
// ReSharper disable once CheckNamespace
namespace CodeWF.Tools;
public static class ImageHelper
{
public static async Task MergeGenerateIcon(string sourceImagePath, string destIconPath, uint[] sizes)
{
var baseImage = new MagickImage(sourceImagePath);
var collection = new MagickImageCollection();
foreach (var size in sizes)
{
var resizedImage = baseImage.Clone();
resizedImage.Resize(size, size);
collection.Add(resizedImage);
}
await collection.WriteAsync(destIconPath);
}
public static async Task SeparateGenerateIcon(string sourceImagePath, string destIconFolder, uint[] sizes)
{
var fileName = Path.GetFileNameWithoutExtension(sourceImagePath);
var baseImage = new MagickImage(sourceImagePath);
foreach (var size in sizes)
{
var resizedImage = baseImage.Clone();
resizedImage.Resize(size, size);
var savePath = Path.Combine(destIconFolder, $"{fileName}-{size}x{size}.ico");
await resizedImage.WriteAsync(savePath);
}
}
}
上記のコードはNuGetパッケージMagick.NET-Q 16-AnyCPUを使用します。Magick.NETはImageMagickの. NETラッパーで、強力な画像処理機能を提供します。Q16は画像処理に16ビット量子化を用いることを示し,AnyCPUは多様なプロセッサアーキテクチャをサポートすることを示している.このライブラリを使用すると、画像のサイズを簡単に変更してICO形式で保存できます。
コアコードには主に2つの方法があります。
MergeGenerateIcon:将一张源图片转换为包含多个尺寸的单个 ICO 文件SeparateGenerateIcon:将一张源图片转换为多个不同尺寸的 ICO 文件
3.ユーザーインターフェイス設計
1. 基本インターフェースレイアウト
使用 Avalonia 框架设计用户界面,界面定义在ImageToIconView.axaml文件中:
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
xmlns:u="https://irihi.tech/ursa"
xmlns:i18n="https://codewf.com"
xmlns:vm="clr-namespace:CodeWF.Modules.Converter.ViewModels"
xmlns:language="clr-namespace:Localization"
xmlns:local="clr-namespace:CodeWF.Modules.Converter.Models"
prism:ViewModelLocator.AutoWireViewModel="True"
x:DataType="vm:ImageToIconViewModel"
x:CompileBindings="True"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="CodeWF.Modules.Converter.ImageToIconView">
<StackPanel>
<TextBlock Text="{i18n:I18n {x:Static language:ImageToIconView.ChoiceSourceImageDescription}}" />
<StackPanel Orientation="Horizontal" Margin="0 10">
<TextBox VerticalAlignment="Center" Margin="10 0" Width="400" Classes="Small"
Text="{Binding NeedConvertImagePath}"
DragDrop.AllowDrop="True" DragDrop.Drop="RaiseDropSourceImagePath"/>
<Button Content="..." Classes="Small" Command="{Binding RaiseChoiceNeedConvertImageHandler}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{i18n:I18n {x:Static language:ImageToIconView.DestImageSize}}" />
<ItemsControl ItemsSource="{Binding IconSizes}" Margin="0 10">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected}" Content="{Binding Content}"
VerticalAlignment="Center" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<Button Margin="10" Classes="Small"
Content="{i18n:I18n {x:Static language:ImageToIconView.MergeGenerateButtonContent}}"
Command="{Binding RaiseMergeGenerateIconHandler}" />
<Button Classes="Small"
Content="{i18n:I18n {x:Static language:ImageToIconView.SeparateGenerateButtonContent}}"
Command="{Binding RaiseSeparateGenerateIconHandler}" />
</StackPanel>
<TextBlock Margin="0 40 0 0" Classes="H4" Theme="{StaticResource TitleTextBlock}"
Text="{i18n:I18n {x:Static language:ImageToIconView.MemoTitle}}" />
<TextBlock Margin="0 5 0 3" Text="{i18n:I18n {x:Static language:ImageToIconView.MemoContent1}}" />
<TextBlock Text="{i18n:I18n {x:Static language:ImageToIconView.MemoContent2}}" />
<Border Margin="0,16" Classes="CodeBlock">
<SelectableTextBlock FontFamily="Consolas"
Text="<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />" />
</Border>
</StackPanel>
</UserControl>
上記のコードを簡単に説明すると、インターフェイスは主に以下の部分で構成されています。
- ソース画像选択(テキスト入力とファイル选択をサポート)
- ターゲットアイコンのサイズ選択領域チェックボックスで選択
- 2つのアクション·ボタンマージ生成と分離生成
- 注釈(使用およびHTML参照の例を示します)
効果は以下の通り。

2. ドラッグ機能の実装
ユーザーエクスペリエンスを向上させるために、ソース画像を選択する2つの方法をサポートします。
- クリック“..。“”ボタンファイルセレクタから选択
- 画像ファイルを入力ボックスに直接ドラッグ
在ImageToIconView.axaml.cs中实现拖拽处理:
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Platform.Storage;
using CodeWF.Modules.Converter.ViewModels;
namespace CodeWF.Modules.Converter;
public partial class ImageToIconView : UserControl
{
public ImageToIconView()
{
InitializeComponent();
}
public void RaiseDropSourceImagePath(object? sender, DragEventArgs e)
{
if (this.DataContext is not ImageToIconViewModel vm)
{
return;
}
var files = e.Data.GetFiles();
var file = files?.FirstOrDefault();
if (file == null)
{
return;
}
vm.NeedConvertImagePath = file.TryGetLocalPath();
e.Handled = true;
}
}
以上のコードにより、ファイルをテキストボックスにドラッグするときに自動的にファイルパスを取得する機能が実現されています。

IV.ビューモデルの実現
在ImageToIconViewModel.cs中实现业务逻辑:
using Avalonia.Platform.Storage;
using AvaloniaXmlTranslator;
using CodeWF.Core.IServices;
using CodeWF.Modules.Converter.Models;
using CodeWF.Tools;
using CodeWF.Tools.FileExtensions;
using ReactiveUI;
using System.Collections.ObjectModel;
using Ursa.Controls;
namespace CodeWF.Modules.Converter.ViewModels;
public class ImageToIconViewModel : ReactiveObject
{
private readonly IFileChooserService _fileChooserService;
private readonly INotificationService _notificationService;
private readonly FilePickerFileType _icoFilePickerFileType =
new("Icon file") { Patterns = ["*.ico"] };
public ImageToIconViewModel(IFileChooserService fileChooserService, INotificationService notificationService)
{
_fileChooserService = fileChooserService;
_notificationService = notificationService;
IconSizes.AddRange(Enum.GetValues<IconSize>()
.Select(size => new IconSizeItem(size)));
}
#region Properties
public ObservableCollection<IconSizeItem> IconSizes { get; } = new();
private string? _needConvertImagePath;
public string? NeedConvertImagePath
{
get => _needConvertImagePath;
set => this.RaiseAndSetIfChanged(ref _needConvertImagePath, value);
}
#endregion
#region Command's handler
public async Task RaiseChoiceNeedConvertImageHandler()
{
var files = await _fileChooserService.OpenFileAsync(
I18nManager.Instance.GetResource(Localization.ImageToIconView.ChoiceSourceImageDescription)!,
true,
[FilePickerFileTypes.All]);
if (!(files?.Count > 0))
{
return;
}
NeedConvertImagePath = files[0];
}
public async Task RaiseMergeGenerateIconHandler()
{
(bool isSuccess, uint[]? sizes) = await GetGenerateInfo();
if (!isSuccess)
{
return;
}
var folder = Path.GetDirectoryName(NeedConvertImagePath);
var fileName = Path.GetFileNameWithoutExtension(NeedConvertImagePath);
var saveIconPath = Path.Combine(folder, $"{fileName}.ico");
try
{
await ImageHelper.MergeGenerateIcon(NeedConvertImagePath, saveIconPath, sizes);
}
catch (Exception ex)
{
await MessageBox.ShowOverlayAsync(ex.Message);
}
FileHelper.OpenFolderAndSelectFile(saveIconPath);
}
public async Task RaiseSeparateGenerateIconHandler()
{
(bool isSuccess, uint[]? sizes) = await GetGenerateInfo();
if (!isSuccess)
{
return;
}
var saveIconFolder = Path.GetDirectoryName(NeedConvertImagePath);
try
{
await ImageHelper.SeparateGenerateIcon(NeedConvertImagePath, saveIconFolder, sizes);
}
catch (Exception ex)
{
await MessageBox.ShowOverlayAsync(ex.Message);
}
FileHelper.OpenFolder(saveIconFolder);
}
private async Task<(bool IsSuccess, uint[]? Sizes)> GetGenerateInfo()
{
if (string.IsNullOrWhiteSpace(NeedConvertImagePath)
|| !File.Exists(NeedConvertImagePath))
{
await MessageBox.ShowOverlayAsync(
I18nManager.Instance.GetResource(Localization.ImageToIconView.ChoiceSourceImageDialogTitle)!);
return (false, null);
}
var selectedSize = IconSizes.Where(item => item.IsSelected).ToList();
if (selectedSize.Count <= 0)
{
await MessageBox.ShowOverlayAsync(
I18nManager.Instance.GetResource(Localization.ImageToIconView.DestImageSize)!);
return (false, null);
}
var destSizes = selectedSize.Select(size => (uint)(size.Size)).ToArray();
return (true, destSizes);
}
#endregion
}
ビューモデルはMVVMデザインパターンに従い、主に以下を担当します。
- UIデータとステータスの管理
- ユーザーアクションのファイルの选択、変换の実行など
- 入力データの検证
- コアビジネスロジックの呼び出し
- 例外の処理
2つの変換モードの効果は次のとおりです。


5.データモデル設計
アイコンサイズオプションを管理するために、次のデータモデルをしました。
using CodeWF.Tools.Extensions;
using ReactiveUI;
using System.ComponentModel;
namespace CodeWF.Modules.Converter.Models;
public enum IconSize
{
[Description("16x16")] Size16 = 16,
[Description("24x24")] Size24 = 24,
[Description("32x32")] Size32 = 32,
[Description("48x48")] Size48 = 48,
[Description("64x64")] Size64 = 64,
[Description("128x128")] Size128 = 128,
[Description("256x256")] Size256 = 256
}
public class IconSizeItem(IconSize size) : ReactiveObject
{
private bool _isSelected = true;
public bool IsSelected
{
get => _isSelected;
set => this.RaiseAndSetIfChanged(ref _isSelected, value);
}
public string Content { get; set; } = size.GetDescription();
public IconSize Size { get; set; } = size;
}
オンラインアイコン変換機能。
デスクトップ版に加えて、Blazorベースのオンラインアイコン変換ツールも開発しました。ソフトウェアをインストールせずに画像をアイコンに変換できるようにしました。
1. Online Converterの特徴
オンライン·アクセスhttps//dotnet9.com/tool/ico
デスクトップ版と比較して、オンライン版は以下の特徴があります。
- ** インストール不要 **:ブラウザから直接アクセス
- ** クロスプラットフォーム互換性 **:モバイルデバイスを含む最新のブラウザをサポートします。
- ** 一時ファイルストレージ **:変換されたファイルはサーバーに一時的に保存され、ユーザーは時間内にダウンロードする必要があります。
- ** シンプルなインターフェイス **:ウェブエクスペリエンスに最適化され、シンプルな操作

2. 変换プロセス
オンライン変換ツールのワークフローはシンプルで直感的です。
- 画像ファイルを选択(PNG、JPG、JPEG、WEBP形式をサポート)
- 変換するアイコンサイズの選択
- 変換モードの選択結合または個別に生成
- ボタンをクリックすると、システムは画像をサーバーにアップロードして変換する。
- 変換が完了したら、“ダウンロード”ボタンをクリックして生成ファイルを取得します
オンライン版も画像処理にMagick.NETを使用しており、コア変換ロジックはデスクトップ版と同じですが、ファイルアップロード処理、一時保存、クリーンアップなどの機能が追加されています。実装の詳細に興味のある読者は、ソースコードを直接見ることができます:
VII.概要と適用シナリオ
この記事では、異なるユーザーのニーズを満たすために、デスクトップ版とオンライン版の2つの画像変換ツールを実装しました。以下のような特徴がある。
- ** シンプルなユーザーインターフェイス **:直感的な操作、ドラッグアンドドロップ操作のサポート
- ** 豊富な変換オプション **:さまざまなアプリケーションシナリオのニーズを満たすために複数のサイズをサポート
- ** 柔軟な変換モード **:単一のマルチサイズICOファイルまたは複数のシングルサイズICOファイルを生成できます。
- ** 優れたコード構造 **:MVVMデザインパターンを採用したクリーンなコード、保守と拡張が容易
このツールは以下のシナリオで使用できます。
- Webサイト開発におけるfavicon.icoの生成
- アプリケーション開発でのアプリケーションアイコンの生成
- デザイナーはさまざまなサイズのアイコンファイルをすばやく生成
さらに、このプロジェクトでは、強力な画像処理ライブラリMagick.NETをC#アプリケーションで使用する方法や、Avaloniaを使用してクロスプラットフォームデスクトップアプリケーションを構築する方法を示します。これらの知識は、他の同様の開発プロジェクトに適用できます。
この記事が役に立つことを願っています。質問があれば、コメント欄にコメントを残してください!
ソースコード参照