Practical development of image-to-Icon tools-from requirements analysis to code implementation

Practical development of image-to-Icon tools-from requirements analysis to code implementation

This article describes how to use C#and Avalonia to develop a picture-to-Icon tool, including requirements analysis, core code implementation, UI design and application of MVVM patterns.

最后更新 3/10/2025 6:14 AM
沙漠尽头的狼
预计阅读 11 分钟
分类
.NET
标签
.NET C# Avalonia UI MVVM UI design

1. Demand analysis and plan design

During development work, we often need to convert pictures into Icon files of different size. Whether you're making favicon.ico for websites or designing icons for applications, this is a common requirement. Although there are many tools on the market that convert pictures to Icon, they usually have problems such as single functions, multiple advertisements or complex operations.

This article describes how to use C#and Avalonia to develop a simple and efficient image-to-icon tool that does the following:

  1. Support converting common image formats (such as PNG, JPG, etc.) to ICO format
  2. Supports generation of icons in multiple size (16x16, 32x32, 48x48, 64x64, 128x128, 256x256)
  3. Two conversion modes are available:
    • Merge mode: Merge icons of multiple size into one ICO file
    • Separation mode: Generate separate ICO files for each size
  4. Support drag-and-drop operations to improve user experience

2. Core conversion code

首先,我们来看核心的图片转 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);
        }
    }
}

The above code uses the NuGet package Magick.NET-Q16-AnyCPU. Magick.NET is ImageMagick's. NET encapsulation library, which provides powerful image processing functions. Q16 means that 16-bit quantization is used for image processing, and AnyCPU means that multiple processor architectures are supported. Through this library, we can easily adjust the size and save the image in ICO format.

The core code provides two main methods:

  • MergeGenerateIcon:将一张源图片转换为包含多个尺寸的单个 ICO 文件
  • SeparateGenerateIcon:将一张源图片转换为多个不同尺寸的 ICO 文件

3. User interface design

1. Basic interface layout

使用 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="&lt;link rel=&quot;shortcut icon&quot; href=&quot;/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;" />
        </Border>
    </StackPanel>
</UserControl>

For a brief description of the above code, our interface mainly includes the following parts:

  1. Source image selection area (supports text input and file selection)
  2. Target icon size selection area (select through check box)
  3. Two operation buttons (merge generation and separate generation)
  4. Remarks information area (provides instructions and examples of HTML quotes)

The achieved results are as follows:

界面截图

2. Implementation of drag-and-drop function

In order to improve the user experience, we support two ways to select source images:

  1. Click "... "button to select from the file selector
  2. Drag and drop the image file directly to the input box

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;
    }
}

Through the above code, the function of automatically obtaining the file path when dragging a file to a text box is realized:

拖拽功能演示

4. Implementation of view model

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
}

The view model follows the MVVM design pattern and is mainly responsible for:

  1. Manage UI data and status
  2. Handle user actions (selecting files, performing conversions, etc.)
  3. Verify input data
  4. Call core business logic
  5. handle irregularities

The effects of the two conversion modes are as follows:

多尺寸合并转换

转换成多个尺寸

5. Data model design

To manage icon size options, we defined the following data model:

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;
}

6. Online Icon conversion function

In addition to the desktop application version, I have also developed an online Icon conversion tool based on Blazor, which allows users to convert pictures to Icon without installing software.

1. Online converter features

Online access address: https://www.example.com

Compared with the desktop version, the online version has the following characteristics:

      • No installation required **: Access and use directly through browser
      • Cross-platform compatibility **: Supports any modern browser, including mobile devices
      • Temporary file storage **: The converted files will be temporarily saved on the server and users need to download them in time
      • Simplify the interface **: Optimize for the web experience and make the operation simpler

在线转换界面

2. transformation process

The online conversion tool's workflow is simple and intuitive:

  1. Select a picture file (PNG, JPG, JPEG, WEBP formats are supported)
  2. Select the icon size to convert
  3. Select the conversion mode (combined generation or separate generation)
  4. After clicking the button, the system uploads the picture to the server for conversion
  5. After the conversion is complete, click the "Download" button to get the generated file

The online version also uses Magick.NET for image processing. The core conversion logic is the same as the desktop version, but adds functions such as file upload processing, temporary storage and cleaning. Readers who are interested in in-depth understanding of the specific implementation can directly view the source code:

7. Summary and application scenarios

Through this article, we have implemented two image-to-Icon tools, desktop version and online version, which meet the needs of different users. They have the following characteristics:

      • Concise user interface **: Intuitive operation and support drag and drop operations
      • Rich conversion options **: Supports multiple size to meet the needs of different application scenarios
      • Flexible conversion mode **: You can generate a single multi-size ICO file or multiple single-size ICO files
      • Good code structure **: adopts MVVM design pattern, the code is clear, easy to maintain and extend

This tool can be applied in the following scenarios:

  • Generating favicon.ico during website development
  • Generating application icons during application development
  • Designers quickly generate icon files of different size

In addition, this project also demonstrates how to use the powerful image processing library Magick.NET in C#applications and how to use Avalonia to build cross-platform desktop applications. These knowledge points can be applied to other similar development projects.

I hope this article will be helpful to you. If you have any questions, please leave a comment area to discuss it!

Source code reference

Keep Exploring

延伸阅读

更多文章
同分类 / 同标签 3/10/2025

Practical development of QR code generation tool for moving cars

This article describes how to develop a mobile QR code generation tool, including desktop versions implemented by C#and Avalonia, and online versions implemented by Blazor front-end and. NET Web API, covering requirements analysis, core code implementation, UI design and MVVM pattern applications.

继续阅读
同分类 / 同标签 2/25/2025

NET 10 Preview 1 released

Today. NET 10 Preview 1 was released. I downloaded it as soon as possible and upgraded the Avalonia UI project and blog site. The former's functional testing and AOT release were normal, the latter's debugging was normal, and Docker was not successful for the time being.

继续阅读