Home .NET Loading and Processing Images in .NET Core

Loading and Processing Images in .NET Core

by admin

In this article, I want to talk about my experience in implementing a mechanism for loading images into a .NET Core application and then resizing and storing them in the file system. For image processing I used the cross-platform library ImageSharp From Six Labors. There are many different libraries for working with images, but since I’m developing a cross-platform application, I wanted to find a cross-platform library as well. At the time of this writing they are still in release candidate stage, but the community assures me that everything works fine and can be used.
The task was to load the image from the front, crop it to a certain aspect ratio and resize it so that the saved image doesn’t eat up a lot of disk space, because every megabyte in the cloud is money.
On the front end, an Angular component was implemented that immediately after selecting a file, sends it to the API method to save it. The API in turn returns the path to the already saved image, which is then saved in the database. Since this article isn’t about Angular, we’ll skip the component and go straight to the API method.

[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, ImageTypetype){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);}}

Or to be more specific, to two methods. My task was to load images for two different assignments, which can be of different sizes and must be stored in different locations. The types of images are specified in Enum ImageType And to describe the image type I have created an interface IImageProfile and its two implementations for each image type, which contains information about how the image should be processed.

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; }}

Next, I will inject these image profiles into the service method and register them in the DI-container.

..services.AddTransient<IImageProfile, BoxImageProfile> ();services.AddTransient<IImageProfile, LogoImageProfile> ();...

After that you can inject the collection into a controller or service IImageProfile

..private readonly IEnumerable<IImageProfile> _imageProfiles;public ImageService(IEnumerable<IImageProfile> imageProfiles){..._imageProfiles = imageProfiles;}

Now let’s look at the service method where we’ll use all this. Let me remind you that for the image processing I used the library ImageSharp which can be found in NuGet. In the following method, I will use the type Image and its methods on how to work with the image. You can read the detailed documentation here

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);}

And several private methods that serve it

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);}

A few words about the method Resize In ResizeOptions I used ResizeMode.Min , this is the mode where the image is resized until it reaches the desired length of the smaller side of the image. For example, if the original image is 1000×2000 and my goal is 500×500, then after resizing it becomes 500×1000, then it is cropped to 500×500 and saved.
That’s pretty much it, except for one thing. The service method will return the path to the file, but it will not be a URL but a PATH and it will look like this : logos\\yourFile.jpg If you feed this PATH to a browser it will sort it out and show you the image, but if you give it to a mobile app for example, you won’t see the image. Regardless, I made the decision to store exactly the PATH in the database to be able to access the file if needed, and in the DTO that flies to the front I map that field by converting it to a URL. To do this, I need the following extension method

public static string PathToUrl(this string path){return path?.Replace("\\", "/");}

Thanks for your attention, write clean code and don’t get sick!

You may also like