MEDIA GUIDES / Image Effects

How to Use .NET to Compress Images for Media Optimization

Images are among the most widely used digital assets on the internet, and delivering them in high quality while ensuring fast load times is essential for creating engaging websites and applications. However, in media workflows and web development, improper image usage, such as unoptimized files or incorrect formats often leads to slow-loading websites, performance bottlenecks, and a poor user experience.

In web development, there are several techniques for optimizing images, such as image compression. In this guide, we’ll explore how to achieve image compression in .NET web applications. We’ll also cover built-in tools for compressing images in .NET and how to integrate with a cloud-based solution like Cloudinary for advanced media optimization.

In this article:

Image Compression in the Context of Media Workflows

Image compression is the process of reducing an image’s file size while maintaining acceptable visual quality. In media workflows, compression is essential for optimizing storage, reducing bandwidth usage, and speeding up content delivery. For web developers, this translates to faster page load times, better user experiences, and improved SEO.

Image compression can be applied at various stages of a media workflow, such as:

  • Pre-upload: Compressing images before storing them on a server or cloud platform.
  • On-the-fly: Dynamically compressing images as they are requested by users.
  • Post-processing: Compressing large volumes of images for efficient storage or delivery.

Core Concepts: Compression Techniques and Image Formats

Before we dive into coding, it’s important to understand the major types of image compression techniques commonly used in web development. It can take two distinct forms, depending on the type of image compression algorithm used. These are lossy and lossless compression.

  • Lossy Compression: Lossy compression reduces image file size by permanently removing less critical data, such as fine details that are barely noticeable to the human eye. It uses algorithms like Discrete Cosine Transform (DCT) and is commonly found in image formats like JPEG and WebP.
  • Lossless Compression: This method reduces file size without losing any data, allowing the image to be reconstructed exactly as the original. Image formats like PNG and GIF use lossless compression.

Your choice between the two compression methods can impact image performance in your web applications.

Generally, lossy compression is ideal for images where loading speed is prioritized over pixel-perfect accuracy. Lossless compression on the other hand, is suitable for images requiring transparency such as logos or precise details like icons.

Creating a .NET Web App

In this section, we’ll guide you through the process of building a .NET web application from scratch for image compression. You’ll learn how to set up a basic web app using ASP.NET Core and implement image compression using both built-in .NET tools and third-party libraries such as ImageSharp and Cloudinary.

Prerequisites

Before we start, you need to do the following if you haven’t done so already:

  • Download and install Visual Studio Code.
  • Install the C# Dev Kit extension by Microsoft for .NET development support in VS Code.
  • Install the .NET SDK for your OS and verify the installation by running dotnet --version in a terminal.

Create ASP.NET Core Web App

Next, create a folder where you’d like to have the project and run the following command in a terminal to create a new ASP.NET Core web app using the MVC template:

dotnet new mvc -o ImageCompressionApp

Next, change directory into the project and run the application:

cd ImageCompressionApp && dotnet run

Navigate to the URL displayed in the terminal and you should see the default ASP.NET Core MVC welcome page rendered as shown below:

Compress Images With Built-in .NET Tools

.NET provides built-in tools for image compression through the System.Drawing namespace, which is part of the .NET Framework and .NET Core. However, System.Drawing.Common is only fully supported on Windows.

To use it on OS like Linux and macOS, you need to install libraries like libgdiplus for it to work. If you need cross-platform compatibility, it’s best to use libraries like ImageSharp for consistent results.

Add System.Drawing to Dependencies

First, to use System.Drawing in ASP.NET Core, we need to add a reference to the System.Drawing.Common NuGet package. In the project directory, open the terminal and run the following command:

```bash
dotnet add package System.Drawing.Common
```

This will update the ImageOptimizerWebApp.csproj file accordingly and add System.Drawing to the package group.

Update the Controller to Handle Compression

Next, let’s create a controller to handle image compression when images are uploaded to the server. Open the Controllers/HomeController.cs file and replace its content with the following code:

using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using Microsoft.AspNetCore.Http; // Required for IFormFile
using System.IO; 
using Microsoft.AspNetCore.Hosting; // Required for IWebHostEnvironment
using ImageCompressionApp.Models;
using System.Drawing;
using System.Drawing.Imaging;

namespace ImageCompressionApp.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;
        private readonly IWebHostEnvironment _env; 

        public HomeController(ILogger<HomeController> logger, IWebHostEnvironment env)
        {
            _logger = logger;
            _env = env;
        }

        public IActionResult Index()
        {
            return View();
        }

        // POST: /Home (Handles the file upload and compression)
        [HttpPost]
        public async Task<IActionResult> Index(IFormFile file, [FromForm] int quality = 75)
        {
            if (file == null || file.Length == 0)
            {
                ViewBag.Message = "Please select a file to upload.";
                return View();
            }

            if (!file.ContentType.StartsWith("image/"))
            {
                ViewBag.Message = "Only image files are allowed.";
                return View();
            }

            if (quality < 0 || quality > 100)
            {
                quality = 75; 
            }

            // Define a temporary directory for uploaded files within wwwroot
            string uploadsFolder = Path.Combine(_env.WebRootPath, "uploads");
            Directory.CreateDirectory(uploadsFolder);

            string uniqueFileName = Guid.NewGuid().ToString() + "_" + Path.GetFileName(file.FileName);
            string filePath = Path.Combine(uploadsFolder, uniqueFileName);

            string compressedFileName = "compressed_" + uniqueFileName;
            string compressedFilePath = Path.Combine(uploadsFolder, compressedFileName);

            try
            {
                // Save the original file temporarily
                using (var stream = new FileStream(filePath, FileMode.Create))
                {
                    await file.CopyToAsync(stream);
                }

                using (var image = Image.FromFile(filePath))
                {
                    var jpegEncoder = ImageCodecInfo.GetImageDecoders().FirstOrDefault(c => c.FormatID == ImageFormat.Jpeg.Guid);
                    var encoderParams = new EncoderParameters(1);
                    encoderParams.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);

                    image.Save(compressedFilePath, jpegEncoder, encoderParams);
                }

                long originalSize = new FileInfo(filePath).Length;
                long compressedSize = new FileInfo(compressedFilePath).Length;

                ViewBag.Message = "Image uploaded and compressed successfully!";
                ViewBag.OriginalImage = "/uploads/" + uniqueFileName; 
                ViewBag.CompressedImage = "/uploads/" + compressedFileName;
                ViewBag.OriginalSize = (originalSize / 1024.0).ToString("0.00") + " KB";
                ViewBag.CompressedSize = (compressedSize / 1024.0).ToString("0.00") + " KB";
                ViewBag.Quality = quality;

                // Delete the original uploaded file
                System.IO.File.Delete(filePath);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error compressing image");
                ViewBag.Message = $"Error occurred during compression: {ex.Message}";
            }

            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}

Here’s what is going on in the above code.

  1. When an image is uploaded through the HTML form (we’ll build that in a moment), an HTTP POST request is sent to the Index action, which validates, saves the image temporarily, and compresses it to JPEG using the System.Drawing namespace.
  2. The ImageCodecInfo class retrieves the JPEG encoder by matching the ImageFormat.Jpeg.Guid to identify the codec for saving the image in JPEG format. The EncoderParameters class specifies compression settings, particularly the Quality parameter, which controls the JPEG compression level.
  3. After compression, the controller then calculates and displays the original and compressed file sizes (in KB) and provides paths to both images for display in the view.

Create HTML Form and Display Compressed Image

To create a view for the image upload form and display compression result, open Views/Home/Index.cshtml and replace its content with the following:

@{
    ViewData["Title"] = "Image Compression";
}

<h1>Image Compression</h1>

<form asp-action="Index" method="post" enctype="multipart/form-data">
    <div class="form-group">
        <label for="file">Select Image:</label>
        <input type="file" name="file" id="file" class="form-control" accept="image/*" required />
    </div>
    <div class="form-group mt-3">
        <label for="quality">Compression Quality (0-100):</label>
        <input type="number" name="quality" id="quality" class="form-control" value="75" min="0" max="100" />
    </div>
    <button type="submit" class="btn btn-primary mt-3">Upload and Compress</button>
</form>

<hr />

@if (!string.IsNullOrEmpty(ViewBag.Message))
{
    <div class="alert alert-info mt-4">
        @ViewBag.Message
    </div>

    @if (!string.IsNullOrEmpty(ViewBag.CompressedImage))
    {
        <div class="mt-3">
            <h4>Compression Results:</h4>
            <p>Original Size: @ViewBag.OriginalSize</p>
            <p>Compressed Size: @ViewBag.CompressedSize (Quality: @ViewBag.Quality)</p>
            <div class="row">
                <div class="col-md-6">
                    <h5>Compressed Image:</h5>
                    <img src="@ViewBag.CompressedImage" class="img-fluid" alt="Compressed Image" style="max-width: 100%; height: auto; border: 1px solid #ddd;" />
                </div>
            </div>
            <p class="mt-3">
                <a href="@ViewBag.CompressedImage" download class="btn btn-success">Download Locally Compressed Image</a>
            </p>
        </div>
    }
}

Now when you rebuild the app you should see the compression page and be able to compress image using System.Drawing, on Windows platforms:

Compress Images With ImageSharp

As mentioned earlier, System.Drawing is only natively supported on Windows, but not recommended for Linux or macOS in production. One cross-platform solution is ImageSharp, a 2D graphics library for image processing in .NET applications.

You can install ImageSharp with the following command:

dotnet add package SixLabors.ImageSharp

Open the Controllers/HomeController.cs file and update its content with the following code:

using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using Microsoft.AspNetCore.Http; // Required for IFormFile
using System.IO; 
using Microsoft.AspNetCore.Hosting; // Required for IWebHostEnvironment
using ImageCompressionApp.Models;

// Add ImageSharp usings
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;

namespace ImageCompressionApp.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;
        private readonly IWebHostEnvironment _env; // To get web root path

        public HomeController(ILogger<HomeController> logger, IWebHostEnvironment env)
        {
            _logger = logger;
            _env = env;
        }

        public IActionResult Index()
        {
            return View();
        }

        // POST: /Home (Handles the file upload and compression)
        [HttpPost]
        public async Task<IActionResult> Index(IFormFile file, [FromForm] int quality = 75)
        {
            if (file == null || file.Length == 0)
            {
                ViewBag.Message = "Please select a file to upload.";
                return View();
            }

            if (!file.ContentType.StartsWith("image/"))
            {
                ViewBag.Message = "Only image files are allowed.";
                return View();
            }

            if (quality < 0 || quality > 100)
            {
                quality = 75; 
            }

            // Define a temporary directory for uploaded files within wwwroot
            string uploadsFolder = Path.Combine(_env.WebRootPath, "uploads");
            Directory.CreateDirectory(uploadsFolder); // Ensure the directory exists

            string uniqueFileName = Guid.NewGuid().ToString() + "_" + Path.GetFileName(file.FileName);
            string filePath = Path.Combine(uploadsFolder, uniqueFileName);

            string compressedFileName = "compressed_" + uniqueFileName;
            string compressedFilePath = Path.Combine(uploadsFolder, compressedFileName);

            try
            {
                // Save the original file temporarily
                using (var stream = new FileStream(filePath, FileMode.Create))
                {
                    await file.CopyToAsync(stream);
                }

                // Perform compression using ImageSharp
                using (var originalStream = new FileStream(filePath, FileMode.Open))
                using (var image = SixLabors.ImageSharp.Image.Load(originalStream))
                {
                    var encoder = new JpegEncoder // Change to PngEncoder if you want to save as PNG
                    {
                        Quality = quality
                    };

                    // Save the compressed image to a file
                    using (var fileOutput = new FileStream(compressedFilePath, FileMode.Create))
                    {
                        image.Save(fileOutput, encoder);
                    }
                }

                long originalSize = new FileInfo(filePath).Length;
                long compressedSize = new FileInfo(compressedFilePath).Length;

                ViewBag.Message = "Image uploaded and compressed successfully!";
                ViewBag.OriginalImage = "/uploads/" + uniqueFileName; 
                ViewBag.CompressedImage = "/uploads/" + compressedFileName;
                ViewBag.OriginalSize = (originalSize / 1024.0).ToString("0.00") + " KB";
                ViewBag.CompressedSize = (compressedSize / 1024.0).ToString("0.00") + " KB";
                ViewBag.Quality = quality;

                // Clean up the original uploaded file
                System.IO.File.Delete(filePath);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error compressing image");
                ViewBag.Message = $"An error occurred during compression: {ex.Message}";
            }

            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}

Here’s an example:

By using a library like ImageSharp, you can ensure that your application behaves consistently across platforms and handles image compression reliably and efficiently without relying on platform-specific tools or dependencies.

Integrating Cloudinary for Cloud-Based Image Compression

While third party libraries are great for compression and other basic image processing tasks, they are often limited for advanced workflows, such as dynamic resizing, format conversion, or batch processing. Cloudinary, a cloud-based Image and Video API platform, offers more powerful tools for image optimization via its .NET SDK.

To compress images in Cloudinary, you can either allow Cloudinary to compress the image automatically by passing auto to the Quality method (q_auto in URLs) in the transformation object, or by specifying integer values between 1 – 100 (1 for the smallest file size possible and 100 for the best visual quality).

Configure Cloudinary

To use the Cloudinary SDK, you need to sign up and create a free account. After signing up, you’ll receive your Cloud Name, API Key, and API Secret from your Cloudinary dashboard.

Next, you’ll need to install the Cloudinary .NET SDK , CloudinaryDotNet, and dotenv.net (for managing Cloudinary credentials as environment variables).

Run the following command in the terminal to install the packages:

dotnet add package CloudinaryDotNet && dotnet add package dotenv.net

Next, create a .env in the project root and add your Cloudinary credentials as follows:

CLOUDINARY_CLOUD_NAME='YOUR_CLOUDINARY_CLOUD_NAME'
CLOUDINARY_API_KEY='YOUR_CLOUDINARY_API_KEY'
CLOUDINARY_API_SECRET='YOUR_CLOUDINARY_API_SECRET'

To prevent exposing your credentials unintentionally, create a .gitignore file in the project root and add the following to it:

[Dd]ebug/
[Rr]elease/
[Bb]in/
[Oo]bj/
*.tmp
.secrets/
appsettings.Development.json
.env
wwwroot/uploads/

Update the Controller

Next, update Controllers/HomeController.cs with the following code:

using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using Microsoft.AspNetCore.Http;
using System.IO;
using Microsoft.AspNetCore.Hosting;
using ImageCompressionApp.Models;
using CloudinaryDotNet;
using CloudinaryDotNet.Actions;
using dotenv.net;

namespace ImageCompressionApp.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;
        private readonly IWebHostEnvironment _env;
        private readonly Cloudinary _cloudinary;

        public HomeController(ILogger<HomeController> logger, IWebHostEnvironment env)
        {
            _logger = logger;
            _env = env;



            DotEnv.Load();

            var account = new Account(
                Environment.GetEnvironmentVariable("CLOUDINARY_CLOUD_NAME")?.Trim('\''),
                Environment.GetEnvironmentVariable("CLOUDINARY_API_KEY")?.Trim('\''),
                Environment.GetEnvironmentVariable("CLOUDINARY_API_SECRET")?.Trim('\'')
            );
            _cloudinary = new Cloudinary(account);
        }

        public IActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Index(IFormFile file, [FromForm] int quality = 75)
        {
            if (file == null || file.Length == 0)
            {
                ViewBag.Message = "Please select a file to upload.";
                return View();
            }

            if (!file.ContentType.StartsWith("image/"))
            {
                ViewBag.Message = "Only image files are allowed.";
                return View();
            }

            if (quality < 0 || quality > 100)
            {
                quality = 75;
            }

            try
            {
                using (var stream = file.OpenReadStream())
                {
                    var uploadParams = new ImageUploadParams()
                    {
                        File = new FileDescription(file.FileName, stream),
                        Transformation = new Transformation().Quality(quality)
                    };

                    var uploadResult = await _cloudinary.UploadAsync(uploadParams);

                    if (uploadResult.StatusCode == System.Net.HttpStatusCode.OK)
                    {
                        ViewBag.Message = "Image uploaded and compressed successfully to Cloudinary!";
                        ViewBag.CompressedImage = uploadResult.SecureUrl.ToString();
                        ViewBag.Quality = quality;
                        ViewBag.OriginalSize = (file.Length / 1024.0).ToString("0.00") + " KB";
                        ViewBag.CompressedSize = "N/A";
                    }
                    else
                    {
                        ViewBag.Message = "Cloudinary upload failed: " + uploadResult.Error?.Message;
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error uploading image to Cloudinary");
                ViewBag.Message = $"Error occurred during Cloudinary upload: {ex.Message}";
            }

            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}

Here’s a breakdown of what’s going on in the above code.

  1. The two using statements, using CloudinaryDotNet and using CloudinaryDotNet.Actions import the main classes and functionality for interacting with Cloudinary’s API and performing actions, such as uploading, deleting, or transforming media.
  2. new Cloudinary(account) initializes a Cloudinary instance by loading your credentials (cloud name, API key, and secret) from the .env file using dotenv.net.
  3. The Transformation object is used by Cloudinary to define a set of image manipulations or optimizations to apply during the upload process. In our case, we want to compress the image so we specify the  .Quality(quality) method to set the compression quality for the image.
  4. After the image has been uploaded, the compressed image URL is returned.

Resizing and Compressing in One Workflow

There are instances where you might need to combine multiple image transformations such as resizing and compressing at the same time. Luckily, the Cloudinary SDK supports this by default.

For example, to resize an image to 300×300 pixels and compress it, simply modify the Transformation object as follows:

// ...
{
    File = new FileDescription(image.FileName, stream),
    Transformation = new Transformation().Width(300).Height(300).Crop("fill").Quality("auto")
};

By allowing you to chain multiple image transformations at once, this capability makes Cloudinary a great tool in the media workflow for every developer.

Best Practices for .NET Image Compression

Striking the perfect balance between image compression and visual quality can be challenging. To achieve the best results when compressing images in .NET, consider the following best practices:

  • Choose the Right Format: Use JPEG for photographs, PNG for graphics with transparency, and WebP for modern web applications when browser support allows.
  • Balance Quality and Size: For System.Drawing, experiment with quality settings (e.g., 50-80) to find the sweet spot. For Cloudinary, use q_auto for automatic optimization.
  • Test Across Devices: Verify how compressed images appear on different screens (e.g., high-DPI displays, mobile devices) to ensure consistent quality.
  • Use Responsive Images: Resize images to match display dimensions using Cloudinary’s dynamic resizing or CSS to prevent serving oversized images.
  • Leverage CDNs: Use Cloudinary’s CDN or another content delivery network to cache and deliver images from servers close to users, reducing latency.
  • Monitor Performance: Regularly check your site’s load times using tools like Google PageSpeed Insights to ensure compression is effective.
  • Automate Workflows: Integrate Cloudinary into your CI/CD pipeline to automate compression during deployment, reducing manual errors.
  • Optimize for SEO: Add descriptive alt text to images using Cloudinary’s auto_tagging feature to improve accessibility and search engine rankings.

Speed Up Page Speeds with Image Compression

Image compression is a cornerstone of modern web development, and .NET provides accessible tools for developers to optimize media effectively. Proper image compression not only improves page load times but also boosts conversion rates and enhances user experience, factors that are essential for both customer satisfaction and SEO performance.

By following the steps outlined in this article, you can build a .NET web app that delivers fast-loading, high-quality images tailored to your users’ needs.

To take your image optimization workflow to the next level, sign up for a free Cloudinary account today and start exploring its powerful features for automating your media workflows.

QUICK TIPS
Colby Fayock
Cloudinary Logo Colby Fayock

In my experience, here are tips that can help you better optimize image compression in .NET beyond the techniques covered in your article:

  1. Integrate perceptual image hashing
    Before recompressing, use perceptual hashing (like pHash or dHash) to detect if visually identical images already exist in your system. This saves processing resources in media-heavy apps.
  2. Apply chroma subsampling where applicable
    When using lossy formats like JPEG, consider applying chroma subsampling (4:2:0) to further reduce file size without significantly degrading perceived quality, especially for photographic content.
  3. Use adaptive image quality scoring
    Implement a feedback loop with client-side A/B testing to adjust quality parameters (e.g., ImageSharp’s Quality) based on actual bounce and engagement metrics, not just file size.
  4. Cache transform pipelines with hashes
    If applying multiple transformations (resize, format convert, compress), generate a hash key for each transformation pipeline to avoid recomputing results on repeated requests.
  5. Detect and flatten transparent backgrounds when not needed
    For PNGs or WebPs with transparency where transparency isn’t functionally needed, convert to opaque with a white/blurred background and save as JPEG — this can cut size drastically.
  6. Prefer progressive JPEG encoding
    When saving JPEGs, enable progressive encoding. It provides a better user-perceived loading experience, especially on slower connections, with no extra cost in file size.
  7. Use image entropy analysis for smarter compression
    Calculate image entropy to automatically adjust compression aggressiveness — low entropy images (like icons or solid backgrounds) can often tolerate more compression.
  8. Minify embedded metadata
    Many uploaded images include bloated EXIF/XMP metadata. Strip unnecessary metadata during compression to reduce file size, especially for photos from cameras or smartphones.
  9. Deploy format fallback with content negotiation
    Add logic to serve WebP/AVIF to supported browsers while gracefully falling back to JPEG/PNG. In .NET, use middleware or response headers to negotiate content type.
  10. Throttle uploads and transformations
    In multi-user apps, protect processing capacity by adding throttling or rate-limiting on concurrent image uploads and transformations. Combine this with job queuing (e.g., Hangfire or Azure Queues).
Last updated: Jun 12, 2025