1. 功能效果演示
演示下文件上传、转换结果:
通过该工具及代码,能了解到:
使用Blazor怎么上传文件到服务器(Blazor Server)。怎么从服务器下载文件。如何将png等图片转换为Ico图片。
下面对该工具的实现代码做个简单说明,不太清楚的可以留言交流。
2. 实现说明
通过该工具,能了解到:
使用Blazor怎么上传文件到服务器(Blazor Server)。怎么从服务器下载文件。如何将png等图片转换为Ico图片。
下面对该工具的实现代码做个简单说明,不太清楚的可以留言交流。
2.1 其他图片上传
使用的MASA Blazor[3]上传组件MFileInput[4],看下面的代码,就一个上传组件加上传时文件保存操作,代码文件:IcoTool.razor[5]
<MFileInputTValue=”IBrowserFile”Placeholder=”@T(“IcoToolMFileInputPlaceholder”)”Rules=”_rules”ShowSizeOnChange=”@LoadFile”Accept=”image/png,image/jpeg,image/jpg,image/bmp”Label=”@T(“IcoToolMFileInputLabel”)”></MFileInput>@code{privatebool_loading;privatestring_sourceFilePath=””;[Inject]publicI18nI18N{get;set;}=default!;[Inject]publicIJSRuntimeJs{get;set;}=default!;protectedoverrideasyncTaskOnInitializedAsync(){_rules.Add(value=>(value==null||value.Size<2*1024*1024)?true:T(“IcoToolFileSizeLimitMessage”));awaitbase.OnInitializedAsync();}privateasyncTaskLoadFile(IBrowserFile?e){if(e==null){_destFilePath=_sourceFilePath=string.Empty;return;}_destFilePath=string.Empty;if(!string.IsNullOrWhiteSpace(_sourceFilePath)&&File.Exists(_sourceFilePath))File.Delete(_sourceFilePath);varsaveImageDir=Path.Combine(AppDomain.CurrentDomain.BaseDirectory,”wwwroot”,ImageDirName);if(!Directory.Exists(saveImageDir))Directory.CreateDirectory(saveImageDir);_sourceFilePath=Path.Combine(saveImageDir,DateTime.UtcNow.ToString(“yyyyMMddHHmmssfff”));awaitusingvarfs=newFileStream(_sourceFilePath,FileMode.Create);awaite.OpenReadStream().CopyToAsync(fs);}}2.2 核心代码:其他图片转Ico
参考代码:https://gist.github.com/darkfall/1656050[6]
因为使用到Bitmap,vs会提示只支持Windows平台,目前工具程序也部署在Windows Server 2019服务器上,如果有其他转换代码,支持跨平台欢迎技术讨论,下面给出我使用的其他图片转Ico的代码,代码路径在:ImagingHelper.cs[7]
usingSystem.Drawing;usingSystem.Drawing.Drawing2D;usingSystem.Drawing.Imaging;namespaceDotnet9.Tools.Images;///<summary>///Adaptedfromthisgist:https://gist.github.com/darkfall/1656050///Provideshelpermethodsforimaging///</summary>publicstaticclassImagingHelper{publicconststringFileheadBmp=”6677″;publicconststringFileheadJpg=”255216″;publicconststringFileheadPng=”13780″;publicconststringFileheadGif=”7173″;privatestaticreadonlyDictionary<ImageType,string>ImageTypeHead=new(){{ImageType.Bmp,FileheadBmp},{ImageType.Jpg,FileheadJpg},{ImageType.Png,FileheadPng},{ImageType.Gif,FileheadGif}};publicstaticboolIsPicture(stringfilePath,outstringfileHead){fileHead=string.Empty;try{varfs=newFileStream(filePath,FileMode.Open,FileAccess.Read);varreader=newBinaryReader(fs);varfileClass=$”{reader.ReadByte().ToString()}{reader.ReadByte().ToString()}”;reader.Close();fs.Close();if(fileClassisnot(FileheadBmporFileheadJpgorFileheadPngorFileheadGif))returnfalse;fileHead=fileClass;returntrue;}catch{returnfalse;}}publicstaticboolIsPictureType(stringfilePath,ImageTypeimageType){varisPicture=IsPicture(filePath,outvarfileHead);if(!isPicture)returnfalse;returnImageTypeHead[imageType]==fileHead;}///<summary>///ConvertsaPNGimagetoaicon(ico)withallthesizeswindowslikes///</summary>///<paramname=”inputBitmap”>Theinputbitmap</param>///<paramname=”output”>Theoutputstream</param>///<returns>Wetherornottheiconwassuccesfullygenerated</returns>publicstaticboolConvertToIcon(BitmapinputBitmap,Streamoutput){varsizes=new[]{256,48,32,16};//GeneratebitmapsforallthesizesandtosstheminstreamsvarimageStreams=newList<MemoryStream>();foreach(varsizeinsizes){varnewBitmap=ResizeImage(inputBitmap,size,size);varmemoryStream=newMemoryStream();newBitmap.Save(memoryStream,ImageFormat.Png);imageStreams.Add(memoryStream);}variconWriter=newBinaryWriter(output);varoffset=0;//0-1reserved,0iconWriter.Write((byte)0);iconWriter.Write((byte)0);//2-3imagetype,1=icon,2=cursoriconWriter.Write((short)1);//4-5numberofimagesiconWriter.Write((short)sizes.Length);offset =6 16*sizes.Length;for(vari=0;i<sizes.Length;i ){//imageentry1//0imagewidthiconWriter.Write((byte)sizes[i]);//1imageheighticonWriter.Write((byte)sizes[i]);//2numberofcolorsiconWriter.Write((byte)0);//3reservediconWriter.Write((byte)0);//4-5colorplanesiconWriter.Write((short)0);//6-7bitsperpixeliconWriter.Write((short)32);//8-11sizeofimagedataiconWriter.Write((int)imageStreams[i].Length);//12-15offsetofimagedataiconWriter.Write(offset);offset =(int)imageStreams[i].Length;}for(vari=0;i<sizes.Length;i ){//writeimagedata//pngdatamustcontainthewholepngdatafileiconWriter.Write(imageStreams[i].ToArray());imageStreams[i].Close();}iconWriter.Flush();returntrue;}///<summary>///ConvertsaPNGimagetoaicon(ico)///</summary>///<paramname=”input”>Theinputstream</param>///<paramname=”output”>Theoutputstream</param///<returns>Wetherornottheiconwassuccesfullygenerated</returns>publicstaticboolConvertToIcon(Streaminput,Streamoutput){varinputBitmap=(Bitmap)Image.FromStream(input);returnConvertToIcon(inputBitmap,output);}///<summary>///ConvertsaPNGimagetoaicon(ico)///</summary>///<paramname=”inputPath”>Theinputpath</param>///<paramname=”outputPath”>Theoutputpath</param>///<returns>Wetherornottheiconwassuccesfullygenerated</returns>publicstaticboolConvertToIcon(stringinputPath,stringoutputPath){usingvarinputStream=newFileStream(inputPath,FileMode.Open);usingvaroutputStream=newFileStream(outputPath,FileMode.OpenOrCreate);returnConvertToIcon(inputStream,outputStream);}///<summary>///Convertsanimagetoaicon(ico)///</summary>///<paramname=”inputImage”>Theinputimage</param>///<paramname=”outputPath”>Theoutputpath</param>///<returns>Wetherornottheiconwassuccesfullygenerated</returns>publicstaticboolConvertToIcon(ImageinputImage,stringoutputPath){usingvaroutputStream=newFileStream(outputPath,FileMode.OpenOrCreate);returnConvertToIcon(newBitmap(inputImage),outputStream);}///<summary>///Resizetheimagetothespecifiedwidthandheight.///Foundonstackoverflow:https://stackoverflow.com/questions/1922040/resize-an-image-c-sharp///</summary>///<paramname=”image”>Theimagetoresize.</param>///<paramname=”width”>Thewidthtoresizeto.</param>///<paramname=”height”>Theheighttoresizeto.</param>///<returns>Theresizedimage.</returns>publicstaticBitmapResizeImage(Imageimage,intwidth,intheight){vardestRect=newRectangle(0,0,width,height);vardestImage=newBitmap(width,height);destImage.SetResolution(image.HorizontalResolution,image.VerticalResolution);usingvargraphics=Graphics.FromImage(destImage);graphics.CompositingMode=CompositingMode.SourceCopy;graphics.CompositingQuality=CompositingQuality.HighQuality;graphics.InterpolationMode=InterpolationMode.HighQualityBicubic;graphics.SmoothingMode=SmoothingMode.HighQuality;graphics.PixelOffsetMode=PixelOffsetMode.HighQuality;usingvarwrapMode=newImageAttributes();wrapMode.SetWrapMode(WrapMode.TileFlipXY);graphics.DrawImage(image,destRect,0,0,image.Width,image.Height,GraphicsUnit.Pixel,wrapMode);returndestImage;}}publicenumImageType{Bmp,Jpg,Png,Gif}
简单的单元测试还是要有的,代码见:ImageHelperTests.cs[8]
usingDotnet9.Tools.Images;namespaceDotnet9.Tools.Tests.Images;publicclassImageHelperTests{[Fact]publicvoidIsPicture(){vartestFilePath=Path.Combine(AppDomain.CurrentDomain.BaseDirectory,”TestFiles”,”logo.png”);Assert.True(File.Exists(testFilePath));varisPicture=ImagingHelper.IsPicture(testFilePath,outvartypename);Assert.True(isPicture);}[Fact]publicvoidIsNotPicture(){vartestFilePath=Path.Combine(AppDomain.CurrentDomain.BaseDirectory,”TestFiles”,”test.txt”);Assert.True(File.Exists(testFilePath));varisPicture=ImagingHelper.IsPicture(testFilePath,outvartypename);Assert.False(isPicture);}[Fact]publicvoidIsPngFile(){vartestFilePath=Path.Combine(AppDomain.CurrentDomain.BaseDirectory,”TestFiles”,”logo.png”);Assert.True(File.Exists(testFilePath));varisPng=ImagingHelper.IsPictureType(testFilePath,ImageType.Png);Assert.True(isPng);}[Fact]publicvoidShouldConvertPngToIcon(){varsourcePng=Path.Combine(AppDomain.CurrentDomain.BaseDirectory,”TestFiles”,”logo.png”);vardestIco=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[9]
@if(!string.IsNullOrWhiteSpace(_sourceFilePath)&&File.Exists(_sourceFilePath)){<MButtonclass=”ma-2white–text”Loading=”_loading”Disabled=”_loading”DepressedColor=”primary”OnClick=”@ConvertToIcon”><LoaderContent><span>@T(“IcoToolMButtonLoaderContent”)</span></LoaderContent><ChildContent><span>@T(“IcoToolMButtonChildContent”)</span></ChildContent></MButton>}@code{privateasyncTaskConvertToIcon(){if(!string.IsNullOrWhiteSpace(_destFilePath)&&File.Exists(_destFilePath)){awaitDownloadIco();return;}_loading=true;if(!string.IsNullOrWhiteSpace(_sourceFilePath)&&File.Exists(_sourceFilePath)){_destFilePath=$”{_sourceFilePath}.ico”;if(ImagingHelper.ConvertToIcon(_sourceFilePath,_destFilePath))awaitDownloadIco();}_loading=false;}}2.3 转换后的Ico文件下载
文件转换成功后,怎么提供下载呢?
起初想使用一个<a href=”/files/xxx.ico” target=”_blank”>xxx.ico</a>标签提供浏览下载的,但动态生成的图片无法访问,不知道什么原因,只能暂时采用一个折衷的方式,有朋友有好的想法欢迎留言。
目前采用的是提供按钮下载,下面是封装的js下载方法,来自微软的文档:ASP.NET Core Blazor file downloads[10]
我把JS代码放_Layout.cshtml[11]:
<script>//省略部分代码asyncfunctiondownloadFileFromStream(fileName,contentStreamReference){constarrayBuffer=awaitcontentStreamReference.arrayBuffer();constblob=newBlob([arrayBuffer]);consturl=URL.createObjectURL(blob);triggerFileDownload(fileName,url);URL.revokeObjectURL(url);}functiontriggerFileDownload(fileName,url){constanchorElement=document.createElement(‘a’);anchorElement.href=url;if(fileName){anchorElement.download=fileName;}anchorElement.click();anchorElement.remove();}</script>
@injectIJSRuntimeJS//省略n多代码@code{privateasyncTaskDownloadIco(){awaitusingvarfileStream=newFileStream(_destFilePath,FileMode.Open);usingvarstreamRef=newDotNetStreamReference(fileStream);awaitJs.InvokeVoidAsync(“downloadFileFromStream”,Path.GetFileName(_destFilePath),streamRef);}}3. 总结 Blazor组件库使用的MASA Blazor[13],很美观大方的Material Design设计风格。Ico转换,使用到了System.Drawing.Common包的Bitmap,.NET 6开始不支持跨平台,提示只支持Windows平台。本工具使用7.0.100-preview.1[14]开发、编译、上线,使用.NET 6[15]的同学,请放心使用,可以无缝升级。
本工具源码:IcoTool[19]
介绍文章:Blazor在线Ico转换工具[20]
参考资料[1]
IcoTool: https://github.com/dotnet9/dotnet9.com/blob/develop/src/Dotnet9.Tools.Web/Pages/Public/ImageTools/IcoTool.razor
[2]
https://tool.dotnet9.com/ico: https://tool.dotnet9.com/ico
[3]
MASA Blazor: https://masa-blazor-docs-dev.lonsid.cn/
[4]
MFileInput: https://masa-blazor-docs-dev.lonsid.cn/components/file-inputs
[5]
IcoTool.razor: https://github.com/dotnet9/dotnet9.com/blob/develop/src/Dotnet9.Tools.Web/Pages/Public/ImageTools/IcoTool.razor
[6]
https://gist.github.com/darkfall/1656050: https://gist.github.com/darkfall/1656050
[7]
ImagingHelper.cs: https://github.com/dotnet9/dotnet9.com/blob/develop/src/Dotnet9.Tools/Images/ImagingHelper.cs
[8]
ImageHelperTests.cs: https://github.com/dotnet9/dotnet9.com/blob/develop/src/test/Dotnet9.Tools.Tests/Images/ImageHelperTests.cs
[9]
IcoTool.razor: https://github.com/dotnet9/dotnet9.com/blob/develop/src/Dotnet9.Tools.Web/Pages/Public/ImageTools/IcoTool.razor
[10]
ASP.NET Core Blazor file downloads: https://docs.microsoft.com/en-us/aspnet/core/blazor/file-downloads?view=aspnetcore-6.0
[11]
_Layout.cshtml: https://github.com/dotnet9/dotnet9.com/blob/develop/src/Dotnet9.Tools.Web/Pages/_Layout.cshtml
[12]
IcoTool.razor: https://github.com/dotnet9/dotnet9.com/blob/develop/src/Dotnet9.Tools.Web/Pages/Public/ImageTools/IcoTool.razor
[13]
MASA Blazor: https://masa-blazor-docs-dev.lonsid.cn/
[14]
7.0.100-preview.1: https://dotnet.microsoft.com/en-us/download/dotnet/7.0
[15]
.NET 6: https://dotnet.microsoft.com/en-us/download/dotnet/6.0
[16]
Dotnet9.Tools: https://github.com/dotnet9/dotnet9.com
[17]
提交issue: https://github.com/dotnet9/dotnet9.com/issues/new
[18]
网站留言: https://dotnet9.com
[19]
IcoTool: https://github.com/dotnet9/dotnet9.com/blob/develop/src/Dotnet9.Tools.Web/Pages/Public/ImageTools/IcoTool.razor
[20]
Blazor在线Ico转换工具: https://dotnet9.com/?p=1715(在新窗口中打开)
[21]
https://tool.dotnet9.com/ico: https://tool.dotnet9.com/ico