無料・オープンソースBlazorオンラインIco変換ツール

無料・オープンソースBlazorオンラインIco変換ツール

無料オープンソースのBlazorオンラインIco変換ツールです。元ファイルと変換後のファイルは保存されません。ダウンロード完了後に削除されますので、安心してご利用ください。

最終更新 2022/02/22 21:38
沙漠尽头的狼
読了目安 10 分
カテゴリ
Blazor
タグ
.NET C# Blazor オープンソース .NET 7

無料オープンソース Blazor オンライン Ico 変換ツール。元ファイルおよび変換後のファイルは保存されず、ダウンロード完了後に削除されますので、安心してお使いいただけます。

目次

    1. 機能デモ
    1. 実装の説明
    • 2.1 その他の画像アップロード
    • 2.2 コアコード:その他の画像をIcoに変換
    • 2.3 変換後のIcoファイルのダウンロード
    1. まとめ

1. 機能デモ

リポジトリ:IcoTool

オンラインデモ:https://tool.dotnet9.com/ico

ファイルアップロードと変換結果のデモ:

このツールとコードを通じて、以下のことがわかります。

  1. Blazor を使用してサーバー(Blazor Server)にファイルをアップロードする方法。
  2. サーバーからファイルをダウンロードする方法。
  3. png などの画像を Ico 画像に変換する方法。

以下に、このツールの実装コードについて簡単に説明します。不明な点があればコメントでお問い合わせください。

2. 実装の説明

2.1 その他の画像アップロード

使用しているのは MASA Blazor のアップロードコンポーネント MFileInput です。以下のコードをご覧ください。アップロードコンポーネントと、アップロード時のファイル保存処理のみです。コードファイル:IcoTool.razor

<MFileInput TValue="IBrowserFile"
            Placeholder="@T("IcoToolMFileInputPlaceholder")"
            Rules="_rules"
            ShowSize
            OnChange="@LoadFile"
            Accept="image/png, image/jpeg, image/jpg, image/bmp"
            Label="@T("IcoToolMFileInputLabel")">
</MFileInput>

@code {
    private readonly List<Func<IBrowserFile, StringBoolean>> _rules = new();

    private bool _loading;
    private IBrowserFile? _sourceBrowserFile;

    [Inject]
    public I18n I18N { get; set; } = default!;

    [Inject]
    public IJSRuntime Js { get; set; } = default!;

    protected override async Task OnInitializedAsync()
    {
        _rules.Add(value => value == null || value.Size < 2 * 1024 * 1024 ? true : T("IcoToolFileSizeLimitMessage"));
        await base.OnInitializedAsync();
    }

    private void LoadFile(IBrowserFile? e)
    {
        _sourceBrowserFile = e;
    }
}

上記のコードの LoadFile(IBrowserFile? e) メソッドは、ファイル選択時に IBrowserFile の参照を保存するだけで、選択後すぐに「今すぐ変換してIconをダウンロード」ボタンが表示されます。コードは以下の通りです。

@if (_sourceBrowserFile != null) {
<MButton
  class="ma-2 white--text"
  Loading="_loading"
  Disabled="_loading"
  Depressed
  Color="primary"
  OnClick="@ConvertAndDownloadIcon"
>
  <LoaderContent>
    <span>@T("IcoToolMButtonLoaderContent")</span>
  </LoaderContent>
  <ChildContent>
    <span>@T("IcoToolMButtonChildContent")</span>
  </ChildContent>
</MButton>
}

この時点では、元ファイルはまだアップロードされていません。ユーザーは再選択することも可能で、「今すぐ変換してIconをダウンロード」ボタンをクリックすると、アイコン変換とダウンロードが実行されます。これについては後述します。

2.2 コアコード:その他の画像をIcoに変換

参考コード:https://gist.github.com/darkfall/1656050

Bitmap を使用しているため、Visual Studio は Windows プラットフォームのみ対応と警告します(重要なのは System.Drawing.Common パッケージがクロスプラットフォームに対応していないだけで、.NET 6 がクロスプラットフォームに対応していないわけではない ということです)。現在、ツールプログラムは Windows Server 2019 サーバーにデプロイされています。クロスプラットフォームをサポートする他の変換コードがある場合は、技術的な議論を歓迎します。以下に、私が使用しているその他の画像をIcoに変換するコードを示します。コードのパスは ImagingHelper.cs です。

using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

namespace Dotnet9.Tools.Images;

/// <summary>
///     Adapted from this gist: https://gist.github.com/darkfall/1656050
///     Provides helper methods for imaging
/// </summary>
public static class ImagingHelper
{
    public const string FileheadBmp = "6677";
    public const string FileheadJpg = "255216";
    public const string FileheadPng = "13780";
    public const string FileheadGif = "7173";

    private static readonly Dictionary<ImageType, string> ImageTypeHead = new()
    {
        { ImageType.Bmp, FileheadBmp },
        { ImageType.Jpg, FileheadJpg },
        { ImageType.Png, FileheadPng },
        { ImageType.Gif, FileheadGif }
    };

    public static bool IsPicture(string filePath, out string fileHead)
    {
        fileHead = string.Empty;

        try
        {
            var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
            var reader = new BinaryReader(fs);
            var fileClass = $"{reader.ReadByte().ToString()}{reader.ReadByte().ToString()}";
            reader.Close();
            fs.Close();
            if (fileClass is not (FileheadBmp or FileheadJpg or FileheadPng or FileheadGif))
                return false;

            fileHead = fileClass;
            return true;
        }
        catch
        {
            return false;
        }
    }

    public static bool IsPictureType(string filePath, ImageType imageType)
    {
        var isPicture = IsPicture(filePath, out var fileHead);
        if (!isPicture) return false;

        return ImageTypeHead[imageType] == fileHead;
    }

    /// <summary>
    ///     Converts a PNG image to a icon (ico) with all the sizes windows likes
    /// </summary>
    /// <param name="inputBitmap">The input bitmap</param>
    /// <param name="output">The output stream</param>
    /// <returns>Wether or not the icon was succesfully generated</returns>
    public static bool ConvertToIcon(Bitmap inputBitmap, Stream output)
    {
        var sizes = new[] { 256, 48, 32, 16 };

        // Generate bitmaps for all the sizes and toss them in streams
        var imageStreams = new List<MemoryStream>();
        foreach (var size in sizes)
        {
            var newBitmap = ResizeImage(inputBitmap, size, size);

            var memoryStream = new MemoryStream();
            newBitmap.Save(memoryStream, ImageFormat.Png);
            imageStreams.Add(memoryStream);
        }

        var iconWriter = new BinaryWriter(output);

        var offset = 0;

        // 0-1 reserved, 0
        iconWriter.Write((byte)0);
        iconWriter.Write((byte)0);

        // 2-3 image type, 1 = icon, 2 = cursor
        iconWriter.Write((short)1);

        // 4-5 number of images
        iconWriter.Write((short)sizes.Length);

        offset += 6 + 16 * sizes.Length;

        for (var i = 0; i < sizes.Length; i++)
        {
            // image entry 1
            // 0 image width
            iconWriter.Write((byte)sizes[i]);
            // 1 image height
            iconWriter.Write((byte)sizes[i]);

            // 2 number of colors
            iconWriter.Write((byte)0);

            // 3 reserved
            iconWriter.Write((byte)0);

            // 4-5 color planes
            iconWriter.Write((short)0);

            // 6-7 bits per pixel
            iconWriter.Write((short)32);

            // 8-11 size of image data
            iconWriter.Write((int)imageStreams[i].Length);

            // 12-15 offset of image data
            iconWriter.Write(offset);

            offset += (int)imageStreams[i].Length;
        }

        for (var i = 0; i < sizes.Length; i++)
        {
            // write image data
            // png data must contain the whole png data file
            iconWriter.Write(imageStreams[i].ToArray());
            imageStreams[i].Close();
        }

        iconWriter.Flush();

        return true;
    }

    /// <summary>
    ///     Converts a PNG image to a icon (ico)
    /// </summary>
    /// <param name="input">The input stream</param>
    /// <param name="output">The output stream</param
    /// <returns>Wether or not the icon was succesfully generated</returns>
    public static bool ConvertToIcon(Stream input, Stream output)
    {
        var inputBitmap = (Bitmap)Image.FromStream(input);
        return ConvertToIcon(inputBitmap, output);
    }

    /// <summary>
    ///     Converts a PNG image to a icon (ico)
    /// </summary>
    /// <param name="inputPath">The input path</param>
    /// <param name="outputPath">The output path</param>
    /// <returns>Wether or not the icon was succesfully generated</returns>
    public static bool ConvertToIcon(string inputPath, string outputPath)
    {
        using var inputStream = new FileStream(inputPath, FileMode.Open);
        using var outputStream = new FileStream(outputPath, FileMode.OpenOrCreate);
        return ConvertToIcon(inputStream, outputStream);
    }


    /// <summary>
    ///     Converts an image to a icon (ico)
    /// </summary>
    /// <param name="inputImage">The input image</param>
    /// <param name="outputPath">The output path</param>
    /// <returns>Wether or not the icon was succesfully generated</returns>
    public static bool ConvertToIcon(Image inputImage, string outputPath)
    {
        using var outputStream = new FileStream(outputPath, FileMode.OpenOrCreate);
        return ConvertToIcon(new Bitmap(inputImage), outputStream);
    }


    /// <summary>
    ///     Resize the image to the specified width and height.
    ///     Found on stackoverflow: https://stackoverflow.com/questions/1922040/resize-an-image-c-sharp
    /// </summary>
    /// <param name="image">The image to resize.</param>
    /// <param name="width">The width to resize to.</param>
    /// <param name="height">The height to resize to.</param>
    /// <returns>The resized image.</returns>
    public static Bitmap ResizeImage(Image image, int width, int height)
    {
        var destRect = new Rectangle(0, 0, width, height);
        var destImage = new Bitmap(width, height);

        destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);

        using var graphics = Graphics.FromImage(destImage);
        graphics.CompositingMode = CompositingMode.SourceCopy;
        graphics.CompositingQuality = CompositingQuality.HighQuality;
        graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
        graphics.SmoothingMode = SmoothingMode.HighQuality;
        graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;

        using var wrapMode = new ImageAttributes();
        wrapMode.SetWrapMode(WrapMode.TileFlipXY);
        graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);

        return destImage;
    }
}

public enum ImageType
{
    Bmp,
    Jpg,
    Png,
    Gif
}

簡単な単体テストも用意しています。コードは ImageHelperTests.cs をご覧ください。

using Dotnet9.Tools.Images;

namespace Dotnet9.Tools.Tests.Images;

public class ImageHelperTests
{
    [Fact]
    public void IsPicture()
    {
        var testFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestFiles", "logo.png");
        Assert.True(File.Exists(testFilePath));

        var isPicture = ImagingHelper.IsPicture(testFilePath, out var typename);

        Assert.True(isPicture);
    }

    [Fact]
    public void IsNotPicture()
    {
        var testFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestFiles", "test.txt");
        Assert.True(File.Exists(testFilePath));

        var isPicture = ImagingHelper.IsPicture(testFilePath, out var typename);

        Assert.False(isPicture);
    }

    [Fact]
    public void IsPngFile()
    {
        var testFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestFiles", "logo.png");
        Assert.True(File.Exists(testFilePath));

        var isPng = ImagingHelper.IsPictureType(testFilePath, ImageType.Png);

        Assert.True(isPng);
    }

    [Fact]
    public void ShouldConvertPngToIcon()
    {
        var sourcePng = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestFiles", "logo.png");
        var destIco = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestFiles", "logo.ico");
        Assert.True(File.Exists(sourcePng));
        Assert.False(File.Exists(destIco));

        ImagingHelper.ConvertToIcon(sourcePng, destIco);

        Assert.True(File.Exists(destIco));
        File.Delete(destIco);
    }
}

ユーザーがファイルを選択したことを確認した後、前述の「今すぐ変換してIconをダウンロード」ボタンをクリックすると、変換メソッド ConvertAndDownloadIcon() が実行されます。コードファイルは IcoTool.razor です。

@code { private async Task ConvertAndDownloadIcon() { if (_sourceBrowserFile ==
null) return; _loading = true; var tempSourcePath = Path.GetTempFileName(); var
tempDestPath = Path.GetTempFileName(); try { var fileName =
$"{Path.GetFileNameWithoutExtension(_sourceBrowserFile.Name)}.ico"; await
UploadFile(tempSourcePath); ImagingHelper.ConvertToIcon(tempSourcePath,
tempDestPath); await DownloadFile(tempDestPath, fileName); } finally {
DeleteFile(tempSourcePath); DeleteFile(tempDestPath); _loading = false; } }
private async Task UploadFile(string saveFilePath) { await using var
sourceFileStream = new FileStream(saveFilePath, FileMode.Create); await
_sourceBrowserFile!.OpenReadStream().CopyToAsync(sourceFileStream); } private
async Task DownloadFile(string fromFilePath, string saveFileName) { await using
var destFileStream = new FileStream(fromFilePath, FileMode.Open); using var
streamRef = new DotNetStreamReference(destFileStream); await
Js.InvokeVoidAsync("downloadFileFromStream", saveFileName, streamRef); } private
void DeleteFile(string filePath) { try { if (File.Exists(filePath))
File.Delete(filePath); } catch { // ignored } } }

変換およびダウンロード中は、アップロードされた元ファイルと変換された Icon ファイルが一時的に保存されますが、ダウンロード完了後にこれらのファイルは即座に削除されますので、ご安心ください。

2.3 変換後の Ico ファイルのダウンロード

ファイルが正常に変換された後、どのようにダウンロードを提供するのでしょうか?

当初は <a href="/files/xxx.ico" target="_blank">xxx.ico</a> タグを使用してブラウジングとダウンロードを提供することを考えましたが、動的に生成された画像にはアクセスできませんでした(理由は不明です)。そのため、当面は妥協した方法を採用しています。良いアイデアがあれば、ぜひコメントを残してください。

現在は、ボタンを押してダウンロードする方法を採用しています。以下は、Microsoft のドキュメント ASP.NET Core Blazor file downloads から引用した、カプセル化された js ダウンロードメソッドです。

JS コードは _Layout.cshtml に配置しています。

<script>
    // 一部のコードは省略

    async function downloadFileFromStream(fileName, contentStreamReference) {
        const arrayBuffer = await contentStreamReference.arrayBuffer();
        const blob = new Blob([arrayBuffer]);

        const url = URL.createObjectURL(blob);

        triggerFileDownload(fileName, url);

        URL.revokeObjectURL(url);
    }

    function triggerFileDownload(fileName, url) {
        const anchorElement = document.createElement('a');
        anchorElement.href = url;

        if (fileName) {
            anchorElement.download = fileName;
        }

        anchorElement.click();
        anchorElement.remove();
    }
</script>

ページダウンロード時には、上記で紹介したメソッド DownloadFile(string fromFilePath, string saveFileName) を使用します。ここではコードを再掲しません。

ファイルのダウンロードには JS 相互運用 を使用しています(JS 相互運用 とは何か? 参考までに私が転載した記事 (14/30)みんなで Blazor を学ぼう:JavaScript interop(相互運用) をご覧ください)。

3. まとめ

  1. 使用している Blazor コンポーネントライブラリは MASA Blazor で、非常に美しく大規模な Material Design デザインスタイルです。
  2. Ico 変換には System.Drawing.Common パッケージの Bitmap を使用しています。.NET 6 ではクロスプラットフォームをサポートせず、Windows プラットフォームのみ対応と警告が出ます。重要なのは System.Drawing.Common パッケージがクロスプラットフォームに対応していないだけで、.NET 6 がクロスプラットフォームに対応していないわけではない ということです。
  3. 本ツールは 7.0.100-preview.1 で開発、コンパイル、公開しています。.NET 6 をお使いの方も安心してご利用いただけます。シームレスなアップグレードが可能です。

Dotnet9工具箱 は、無料・オープンソースのオンラインツールを随時追加していきます。スターでのご支援をお願いします。ご要望があれば検討いたします。リポジトリは Dotnet9.Tools です。Issue の提出サイトへのコメント、WeChat 公式アカウント(dotnet9)などでご連絡ください。

本ツールのソースコード:IcoTool

紹介記事:Blazor 在线 Ico 转换工具

オンラインデモ:https://tool.dotnet9.com/ico

さらに探索

関連読書

その他の記事
同じカテゴリ / 同じタグ 2024/02/29

Winformでもこんなデータ表示ができる

winform開発の過程で、データ表示機能が必要になることがよくあります。これまではgridcontrolコントロールを使用していましたが、今日は例を通して、winform blazor hybridでant design blazorのtableコンポーネントを使ってデータ表示を行う方法を紹介します。

続きを読む
同じカテゴリ / 同じタグ 2024/02/29

Winformの画面も綺麗にできる?

先日、winformでblazor hybridを使用することを紹介しました。また、blazorのUIを組み合わせることでwinformプログラムのデザインをより美しくできると言いました。今回はwinform blazor hybridで描画する例を挙げて説明します。参考になれば幸いです。

続きを読む