免费开源Blazor在线Ico转换工具

339 阅读3分钟

行文目录

    1. 功能效果演示

    1. 实现说明

    • 2.1 其他图片上传

    • 2.2 核心代码:其他图片转Ico

    • 2.3 转换后的Ico文件下载

    1. 总结

1. 功能效果演示

仓库地址:IcoTool

在线演示地址:tool.dotnet9.com/ico

演示下文件上传、转换结果:

通过该工具及代码,能了解到:

  1. 使用Blazor怎么上传文件到服务器(Blazor Server)。

  2. 怎么从服务器下载文件。

  3. 如何将png等图片转换为Ico图片。

下面对该工具的实现代码做个简单说明,不太清楚的可以留言交流。

2. 实现说明

通过该工具,能了解到:

  1. 使用Blazor怎么上传文件到服务器(Blazor Server)。

  2. 怎么从服务器下载文件。

  3. 如何将png等图片转换为Ico图片。

下面对该工具的实现代码做个简单说明,不太清楚的可以留言交流。

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 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 核心代码:其他图片转Ico

参考代码:gist.github.com/darkfall/16…

因为使用到Bitmap,vs会提示只支持Windows平台,目前工具程序也部署在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);    }}

页面调用Ico转换功能代码如下,提供一个触发转换的按钮和执行转换的方法,代码文件: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 转换后的Ico文件下载

文件转换成功后,怎么提供下载呢?

起初想使用一个<a href="/files/xxx.ico" target="_blank">xxx.ico</a>标签提供浏览下载的,但动态生成的图片无法访问,不知道什么原因,只能暂时采用一个折衷的方式,有朋友有好的想法欢迎留言。

目前采用的是提供按钮下载,下面是封装的js下载方法,来自微软的文档:ASP.NET Core Blazor file downloads

我把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>

页面下载时使用以下代码,使用到JS互操作(什么是JS互操作?可以参考我转载的这篇文章了解首页.NETBlazorBlazor Server (14/30)大家一起学Blazor:JavaScript interop(互操作)),代码放:IcoTool.razor

@inject IJSRuntime JS// 省略n多代码@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. 总结

  1. Blazor组件库使用的MASA Blazor,很美观大方的Material Design设计风格。

  2. Ico转换,使用到了System.Drawing.Common包的Bitmap,.NET 6开始不支持跨平台,提示只支持Windows平台。

  3. 本工具使用7.0.100-preview.1开发、编译、上线,使用.NET 6的同学,请放心使用,可以无缝升级。

Dotnet9工具箱会不断添加新的免费、开源、在线工具,欢迎star支持,有什么需求我会考虑加上,仓库地址:Dotnet9.Tools,可提交issue网站留言、微信公众号(dotnet9)联系等等。

本工具源码:IcoTool

介绍文章:Blazor在线Ico转换工具

在线演示地址:tool.dotnet9.com/ico