Memuat dan memproses gambar dalam .NET Core

Pada artikel ini saya ingin berbicara tentang pengalaman saya dalam menerapkan mekanisme untuk memuat gambar dalam aplikasi .NET Core, diikuti oleh mereka mengubah ukuran dan menyimpan ke sistem file. Untuk pemrosesan gambar, saya menggunakan pustaka ImageSharp lintas platform dari Six Labors. Ada banyak perpustakaan berbeda untuk bekerja dengan gambar, tetapi karena Saya sedang mengembangkan aplikasi lintas platform, saya ingin menemukan perpustakaan lintas platform. Pada saat penulisan, mereka masih dalam tahap kandidat pelepasliaran, tetapi masyarakat mengatakan bahwa semuanya berfungsi dengan baik dan dapat digunakan dengan aman.
Tugasnya adalah mengunduh gambar dari depan, memotongnya ke rasio aspek tertentu dan mengubah ukuran sehingga gambar yang disimpan tidak memakan banyak ruang disk, karena setiap megabyte di cloud adalah uang.

Di bagian depan, komponen Angular diimplementasikan, yang segera setelah memilih file mengirimkannya ke metode API untuk disimpan. API, pada gilirannya, mengembalikan jalur ke gambar yang sudah disimpan, yang kemudian disimpan dalam database. Karena Karena artikel ini bukan tentang Angular, kami mengabaikan implementasi komponen dan langsung ke metode 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);
    }
}

Dan lebih tepatnya, lalu ke dua metode. Tugas saya adalah mengunggah gambar untuk dua tujuan yang berbeda, yang bisa berukuran berbeda dan harus disimpan di tempat yang berbeda. Jenis gambar tercantum dalam Enum ImageType. Dan untuk menggambarkan jenis gambar, saya membuat antarmuka IImageProfiledan dua implementasi untuk setiap jenis gambar, yang berisi informasi tentang bagaimana gambar harus diproses.

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

Selanjutnya, saya akan menyuntikkan profil gambar ini ke dalam metode layanan, untuk ini Anda harus mendaftarkannya dalam wadah DI.

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

Setelah itu, Anda bisa menyuntikkan koleksi ke controller atau layanan IImageProfile

...
private readonly IEnumerable<IImageProfile> _imageProfiles;

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

Sekarang mari kita lihat metode layanan, di mana kita akan menggunakan semua ini. Biarkan saya mengingatkan Anda bahwa untuk pemrosesan gambar, saya menggunakan perpustakaan ImageSharp, yang dapat ditemukan di NuGet. Pada metode selanjutnya, saya akan menggunakan tipe Imagedan metodenya untuk bekerja dengan gambar. Dokumentasi terperinci dapat dibaca di sini.

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

Dan beberapa metode pribadi yang melayaninya

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

Beberapa kata tentang metode ini Resize. Dalam ResizeOptionsI digunakan ResizeMode.Min, ini adalah mode ketika gambar diubah ukurannya sampai panjang yang diinginkan dari sisi yang lebih kecil dari gambar tercapai. Misalnya, jika gambar aslinya adalah 1000x2000, dan tujuan saya adalah untuk mendapatkan 500x500, kemudian setelah mengubah ukurannya akan menjadi 500x1000, kemudian dipangkas menjadi 500x500 dan disimpan.

Ini hampir semuanya, kecuali satu saat. Metode layanan akan mengembalikan path ke file, tetapi itu bukan URL, tapi PATH dan akan terlihat seperti ini:logos\\yourFile.jpg. Jika Anda memberi makan PATH ini ke peramban, maka ia akan berhasil mengetahuinya dan menunjukkan gambar itu kepada Anda, tetapi jika Anda memberikannya, misalnya, ke aplikasi seluler, Anda tidak akan melihat gambar itu. Meskipun demikian, saya memutuskan untuk menyimpan PATH di database sehingga saya dapat mengakses file jika perlu, dan di DTO yang terbang ke depan, saya memetakan bidang ini dengan mengonversinya ke URL. Untuk melakukan ini, saya perlu metode ekstensi berikut

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

Terima kasih atas perhatian Anda, tulis kode bersih dan jangan sakit!

All Articles