Free and open source Blazor online Ico conversion tool

Posted by Azala on Thu, 24 Feb 2022 17:21:46 +0100

1. Function effect demonstration

Warehouse address: IcoTool

Online presentation address: https://tool.dotnet9.com/ico

Demonstrate the file upload and conversion results:


Through this tool and code, we can understand:

  1. How to upload files to the server (Blazor Server) using Blazor.
  2. How to download files from the server.
  3. How to convert pictures such as png into Ico pictures.

The following is a brief description of the implementation code of the tool. If you are not clear, you can leave a message for communication.

2. Implementation description

Through this tool, we can understand:

  1. How to upload files to the server (Blazor Server) using Blazor.
  2. How to download files from the server.
  3. How to convert pictures such as png into Ico pictures.

The following is a brief description of the implementation code of the tool. If you are not clear, you can leave a message for communication.

2.1 upload of other pictures

Used MASA Blazor Upload component MFileInput , look at the following code to add the file saving operation when uploading to an upload component, code file: 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 bool _loading;
    private string _sourceFilePath = "";
    [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 async Task LoadFile(IBrowserFile? e)
    {
        if (e == null)
        {
            _destFilePath = _sourceFilePath = string.Empty;
            return;
        }
        _destFilePath = string.Empty;
        if (!string.IsNullOrWhiteSpace(_sourceFilePath) && File.Exists(_sourceFilePath)) File.Delete(_sourceFilePath);

        var saveImageDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", ImageDirName);
        if (!Directory.Exists(saveImageDir)) Directory.CreateDirectory(saveImageDir);

        _sourceFilePath = Path.Combine(saveImageDir, DateTime.UtcNow.ToString("yyyyMMddHHmmssfff"));
        await using var fs = new FileStream(_sourceFilePath, FileMode.Create);
        await e.OpenReadStream().CopyToAsync(fs);
    }
}

2.2 core code: other pictures transferred to Ico

Reference code: https://gist.github.com/darkfall/1656050

Because Bitmap is used, vs will prompt that it only supports windows platform. At present, the tool program is also deployed on Windows Server 2019 server. If there are other conversion codes, it supports cross platform. Welcome to the technical discussion. Here are the codes for converting other pictures I use to Ico. The code path is: 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
}

Simple unit tests are still necessary. See the code below: 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);
    }
}

The code of calling Ico conversion function on the page is as follows, providing a button to trigger the conversion and a method to execute the conversion. The code file is as follows: IcoTool.razor

@if (!string.IsNullOrWhiteSpace(_sourceFilePath) && File.Exists(_sourceFilePath))
{
    <MButton class="ma-2 white--text"
             Loading="_loading"
             Disabled="_loading"
             Depressed Color="primary"
             OnClick="@ConvertToIcon">
        <LoaderContent>
            <span>@T("IcoToolMButtonLoaderContent")</span>
        </LoaderContent>
        <ChildContent>
            <span>@T("IcoToolMButtonChildContent")</span>
        </ChildContent>
    </MButton>
}

@code {
    private async Task ConvertToIcon()
    {
        if (!string.IsNullOrWhiteSpace(_destFilePath) && File.Exists(_destFilePath))
        {
            await DownloadIco();
            return;
        }

        _loading = true;

        if (!string.IsNullOrWhiteSpace(_sourceFilePath) && File.Exists(_sourceFilePath))
        {
            _destFilePath = $"{_sourceFilePath}.ico";
            if (ImagingHelper.ConvertToIcon(_sourceFilePath, _destFilePath)) await DownloadIco();
        }

        _loading = false;
    }
}

2.3 download of converted Ico files

After the file conversion is successful, how can I download it?

Initially, I wanted to use a < a href = "/ files / xxx. ICO" target = "_blank" > XXX ICO < / a > tag provides browsing and downloading, but the dynamically generated pictures cannot be accessed. I don't know why, so I can only temporarily adopt a compromise way. If friends have good ideas, please leave a message.

At present, the button download method is adopted. The following is the encapsulated js download method, which comes from Microsoft documents: ASP.NET Core Blazor file downloads

I put the JS code _Layout.cshtml:

<script>
    // Omit some codes

    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>

When downloading the page, use the following code to use js interoperability (what is JS interoperability? You can refer to this article I reprint for details home page NETBlazorBlazor Server
(14 / 30) let's learn Blazor: Javascript interop
), code: IcoTool.razor

@inject IJSRuntime JS

// Omit n multiple codes

@code {

    private async Task DownloadIco()
    {
        await using var fileStream = new FileStream(_destFilePath, FileMode.Open);
        using var streamRef = new DotNetStreamReference(fileStream);

        await Js.InvokeVoidAsync("downloadFileFromStream", Path.GetFileName(_destFilePath), streamRef);
    }
}

3. Summary

  1. Used by Blazor component library MASA Blazor , very beautiful and generous Material Design style.
  2. Ico conversion, using system Drawing. Bitmap of common package NET 6 does not support cross platform, and the prompt only supports Windows platform.
  3. Use of this tool 7.0.100-preview.1 Develop, compile, launch and use .NET 6 Students, please rest assured that you can upgrade seamlessly.

Dotnet9 toolbox will continue to add new free, open source and online tools. Welcome star support. If there is any demand, I will consider adding it. Warehouse address: Dotnet9.Tools , but Submit issue,Website message WeChat official account (dotnet9) contact and so on.

Source code of this tool: IcoTool

Introduction article: Blazor online Ico conversion tool

Online presentation address: https://tool.dotnet9.com/ico

Topics: C# Blazor Open Source