---
title: "Compress an image in C#"
url: https://recursiveai.org/oneimage/cookbook/compress-image/
description: "Compress an image in C# to reduce its file size: JPEG quality, resizing first, WebP, lossless PNG and WebP, and compressing a JPEG to a target size in KB. Runnable code with OneImage."
version: "1.10.4"
---

# Compress an image in C#

Resize the image to the size it is shown at, then save it as JPEG or WebP with a quality setting. Resizing saves the most; quality and format do the rest. The program below measures each step on NASA’s “Blue Marble” photograph (4579×4579, 1.9 MB, public domain).

```shell
dotnet new console -o CompressImage && cd CompressImage
dotnet add package RecursiveAI.OneImage --version 1.10.4
curl -L -o photo.jpg \
  https://images-assets.nasa.gov/image/as17-148-22727/as17-148-22727~orig.jpg
```

OneImage needs the .NET 10 SDK. Put this in `Program.cs` and run `dotnet run`:

```csharp
using OneImage;

static SaveOptions Jpeg(int quality) =>
    new() { EncoderOptions = new JpegEncoderOptions { Quality = quality } };

Image photo = Image.Load("photo.jpg");

// 1. Lower the JPEG quality (1 to 100, default 75).
photo.Save("q80.jpg", Jpeg(80));
photo.Save("q60.jpg", Jpeg(60));

// 2. Resize to the displayed size first. Thumbnail also strips EXIF and GPS.
Image web = Image.Thumbnail("photo.jpg", 1600);
web.Save("1600-q80.jpg", Jpeg(80));

// 3. Write WebP instead of JPEG.
web.Save("1600-q80.webp", new SaveOptions
{
    EncoderOptions = new WebPEncoderOptions { Quality = 80 },
});

foreach (string file in new[] { "photo.jpg", "q80.jpg", "q60.jpg", "1600-q80.jpg",
                                "1600-q80.webp" })
{
    Console.WriteLine($"{file}: {new FileInfo(file).Length / 1024:N0} KB");
}
```

```text
photo.jpg: 1,870 KB
q80.jpg: 1,402 KB
q60.jpg: 1,111 KB
1600-q80.jpg: 278 KB
1600-q80.webp: 208 KB
```

Resizing to the 1600 px it is shown at saved more than any quality setting, and WebP took a quarter off that. Pick the quality by looking at the result at the size it is shown: below about 60, blocks and smeared color appear around edges. The encoder already uses optimized Huffman tables and 4:2:0 chroma subsampling; `Progressive = true` in `JpegEncoderOptions` usually saves a little more and lets browsers show a rough version early.

## Compress a PNG without losing quality

PNG has no quality setting. A higher deflate level, lossless WebP or a 256-color palette are the choices, from no pixel change to fewer colors. This uses Wikimedia’s transparent dice image:

```shell
wiki=https://commons.wikimedia.org/wiki/Special:FilePath
curl -L -o dice.png $wiki/PNG_transparency_demonstration_1.png
```

```csharp
using OneImage;

Image png = Image.Load("dice.png");

// Lossless: the same pixels, compressed harder.
png.Save("level9.png", new SaveOptions
{
    EncoderOptions = new PngEncoderOptions { CompressionLevel = 9 },
});
png.Save("lossless.webp", new SaveOptions
{
    EncoderOptions = new WebPEncoderOptions { Lossless = true },
});

// Lossy: a 256-color palette, as pngquant does.
png.Quantize(colors: 256, dither: Dither.FloydSteinberg).Save("256.png");

foreach (string file in new[] { "dice.png", "level9.png", "lossless.webp", "256.png" })
{
    Console.WriteLine($"{file}: {new FileInfo(file).Length / 1024:N0} KB");
}
```

```text
dice.png: 219 KB
level9.png: 233 KB
lossless.webp: 172 KB
256.png: 87 KB
```

This PNG was already squeezed by an optimizer, so level 9 came out larger than the original; on PNGs written by ordinary software it usually saves a few percent. Lossless WebP kept every pixel in a smaller file, and the 256-color palette, which changes pixels, cut the file to 40%. Re-saving a JPEG at any quality decodes and encodes it again, which always changes some pixels.

## Compress to a target size in KB

Upload forms often cap files at 100 or 200 KB. File size doesn’t follow from quality by a formula, so search for the highest quality that fits; seven encodes cover 10 to 95:

```csharp
using OneImage;

static byte[] CompressToSize(Image image, long maxBytes)
{
    byte[]? best = null;
    int low = 10, high = 95;
    while (low <= high)
    {
        int quality = (low + high) / 2;
        using var buffer = new MemoryStream();
        image.Save(buffer, JpegFormat.Instance, new SaveOptions
        {
            EncoderOptions = new JpegEncoderOptions { Quality = quality },
        });
        if (buffer.Length <= maxBytes) { best = buffer.ToArray(); low = quality + 1; }
        else { high = quality - 1; }
    }
    return best ?? throw new InvalidOperationException("Too large at quality 10.");
}

byte[] jpeg = CompressToSize(Image.Thumbnail("photo.jpg", 2000), 200 * 1024);
File.WriteAllBytes("photo-200kb.jpg", jpeg);
Console.WriteLine($"photo-200kb.jpg: {jpeg.Length / 1024} KB");
```

```text
photo-200kb.jpg: 197 KB
```

If even quality 10 is too big, make the image smaller rather than going lower. The same function works for WebP with `WebPFormat.Instance` and `WebPEncoderOptions`.

## Limits

- **Sizes depend on content.** Your savings will differ; measure on your own images.
- **JPEG has no transparency.** Put transparent images on a background with `Flatten` first, or use WebP or PNG.
- **No AVIF or JPEG XL output**, which can be smaller than WebP.

For WebP options, see [Convert an image to WebP](https://recursiveai.org/oneimage/cookbook/convert-to-webp/); for grayscale, which also shrinks files, [Convert to grayscale](https://recursiveai.org/oneimage/cookbook/convert-to-grayscale/). Every encoder setting is in the [guide](https://recursiveai.org/oneimage/docs/guide/#saving).
