在本文中,我想谈谈我在实现.NET Core应用程序中加载图像的机制,然后调整其大小并将其保存到文件系统中的经验。对于图像处理,我使用了Six Labors 的跨平台ImageSharp库。有许多用于处理图像的库,但是因为 我正在开发一个跨平台应用程序,我想找到一个跨平台库。在撰写本文时,它们仍处于候选发布阶段,但社区表示一切正常,可以安全使用。任务是从前端下载图像,将其裁剪为一定的宽高比并调整大小,以使保存的图像不会占用大量磁盘空间,因为云中的每兆字节都是金钱。在前端,实现了一个Angular组件,该组件在选择文件后立即将其发送到API方法进行保存。该API依次返回已保存图像的路径,然后将其保存在数据库中。因为 由于本文不是关于Angular的,因此我们省略了该组件的实现,而直接使用API方法。[HttpPost]
[Route("upload/box")]
public IActionResult UploadBoxImage(IFormFile file)
{
return UploadImage(file, ImageType.Box);
}
[HttpPost]
[Route("upload/logo")]
public IActionResult UploadLogoImage(IFormFile file)
{
return UploadImage(file, ImageType.Logo);
}
private IActionResult UploadImage(IFormFile file, ImageType type)
{
if (file.Length == 0)
return BadRequest(new ApiResponse(ErrorCodes.EmptyFile, Strings.EmptyFile));
try
{
var filePath = _imageService.SaveImage(file, type);
return Ok(new ApiResponse<string>(filePath));
}
catch (ImageProcessingException ex)
{
var response = new ApiResponse(ErrorCodes.ImageProcessing, ex.Message);
return BadRequest(response);
}
catch (Exception ex)
{
var response = new ApiResponse(ErrorCodes.Unknown, ex.Message);
return BadRequest(response);
}
}
并且更精确地讲两种方法。我的任务是为两个不同的目的上传图像,这些图像可以具有不同的大小,并且必须存储在不同的位置。图像类型在Enum中列出ImageType
。为了描述图像的类型,我IImageProfile
为每种图像类型创建了一个接口和两个实现,其中包含有关应如何处理图像的信息。public interface IImageProfile
{
ImageType ImageType { get; }
string Folder { get; }
int Width { get; }
int Height { get; }
int MaxSizeBytes { get; }
IEnumerable<string> AllowedExtensions { get; }
}
public class BoxImageProfile : IImageProfile
{
private const int mb = 1048576;
public BoxImageProfile()
{
AllowedExtensions = new List<string> { ".jpg", ".jpeg", ".png", ".gif" };
}
public ImageType ImageType => ImageType.Box;
public string Folder => "boxes";
public int Width => 500;
public int Height => 500;
public int MaxSizeBytes => 10 * mb;
public IEnumerable<string> AllowedExtensions { get; }
}
public class LogoImageProfile : IImageProfile
{
private const int mb = 1048576;
public LogoImageProfile()
{
AllowedExtensions = new List<string> { ".jpg", ".jpeg", ".png", ".gif" };
}
public ImageType ImageType => ImageType.Logo;
public string Folder => "logos";
public int Width => 300;
public int Height => 300;
public int MaxSizeBytes => 5 * mb;
public IEnumerable<string> AllowedExtensions { get; }
}
接下来,我将这些图像配置文件注入到服务方法中,为此您需要在DI容器中注册它们。...
services.AddTransient<IImageProfile, BoxImageProfile>();
services.AddTransient<IImageProfile, LogoImageProfile>();
...
之后,您可以将集合注入控制器或服务 IImageProfile
...
private readonly IEnumerable<IImageProfile> _imageProfiles;
public ImageService(IEnumerable<IImageProfile> imageProfiles)
{
...
_imageProfiles = imageProfiles;
}
现在让我们看看服务方法,我们将在其中使用所有这些方法。让我提醒您,对于图像处理,我使用了库ImageSharp
,可以在NuGet中找到该库。在下一个方法中,我将使用类型Image
及其方法来处理图像。详细文档可在此处阅读。public string SaveImage(IFormFile file, ImageType imageType)
{
var imageProfile = _imageProfiles.FirstOrDefault(profile =>
profile.ImageType == imageType);
if (imageProfile == null)
throw new ImageProcessingException("Image profile has not found");
ValidateExtension(file, imageProfile);
ValidateFileSize(file, imageProfile);
var image = Image.Load(file.OpenReadStream());
ValidateImageSize(image, imageProfile);
var folderPath = Path.Combine(_hostingEnvironment.WebRootPath, imageProfile.Folder);
if (!Directory.Exists(folderPath))
Directory.CreateDirectory(folderPath);
string filePath;
string fileName;
do
{
fileName = GenerateFileName(file);
filePath = Path.Combine(folderPath, fileName);
} while (File.Exists(filePath));
Resize(image, imageProfile);
Crop(image, imageProfile);
image.Save(filePath, new JpegEncoder { Quality = 75 });
return Path.Combine(imageProfile.Folder, fileName);
}
还有一些私人方法private void ValidateExtension(IFormFile file, IImageProfile imageProfile)
{
var fileExtension = Path.GetExtension(file.FileName);
if (imageProfile.AllowedExtensions.Any(ext => ext == fileExtension.ToLower()))
return;
throw new ImageProcessingException(Strings.WrongImageFormat);
}
private void ValidateFileSize(IFormFile file, IImageProfile imageProfile)
{
if (file.Length > imageProfile.MaxSizeBytes)
throw new ImageProcessingException(Strings.ImageTooLarge);
}
private void ValidateImageSize(Image image, IImageProfile imageProfile)
{
if (image.Width < imageProfile.Width || image.Height < imageProfile.Height)
throw new ImageProcessingException(Strings.ImageTooSmall);
}
private string GenerateFileName(IFormFile file)
{
var fileExtension = Path.GetExtension(file.FileName);
var fileName = Path.GetFileNameWithoutExtension(Path.GetRandomFileName());
return $"{fileName}{fileExtension}";
}
private void Resize(Image image, IImageProfile imageProfile)
{
var resizeOptions = new ResizeOptions
{
Mode = ResizeMode.Min,
Size = new Size(imageProfile.Width)
};
image.Mutate(action => action.Resize(resizeOptions));
}
private void Crop(Image image, IImageProfile imageProfile)
{
var rectangle = GetCropRectangle(image, imageProfile);
image.Mutate(action => action.Crop(rectangle));
}
private Rectangle GetCropRectangle(IImageInfo image, IImageProfile imageProfile)
{
var widthDifference = image.Width - imageProfile.Width;
var heightDifference = image.Height - imageProfile.Height;
var x = widthDifference / 2;
var y = heightDifference / 2;
return new Rectangle(x, y, imageProfile.Width, imageProfile.Height);
}
关于该方法的几句话Resize
。在ResizeOptions
我使用的中ResizeMode.Min
,这是调整图像大小直到达到图像较小边的所需长度时的模式。例如,如果原始图像是1000x2000,而我的目标是获得500x500,则在调整大小后它将变为500x1000,然后将其裁剪为500x500并保存。除了一瞬间,这几乎就是一切。 service方法将返回文件的路径,但它不是URL,而是PATH,看起来像这样:logos\\yourFile.jpg
。如果将此PATH提供给浏览器,它将成功地找出它并向您显示图像,但是如果将其提供给移动应用程序,则不会看到该图像。尽管如此,我还是决定将PATH存储在数据库中以便能够访问文件(如果需要),并存储在前排的DTO中,我通过将其转换为URL来映射此字段。为此,我需要以下扩展方法public static string PathToUrl(this string path)
{
return path?.Replace("\\", "/");
}
感谢您的关注,编写简洁的代码,请不要生病!