oneimage

Convert an image to WebP in C#

Convert JPG, PNG or GIF to WebP in C# with one line of pure .NET code: quality and lossless settings, resizing, ASP.NET Core uploads, batches and animation.

To convert an image to WebP in C#, load it and save it with a .webp extension. With OneImage that is one line, and it converts JPG, PNG, GIF, TIFF, BMP or ICO to lossy WebP at quality 75. It runs in pure C#, so there is no native libwebp to install and the same code runs on Windows, Linux, macOS and in containers. The rest of this page covers quality, lossless output, resizing, ASP.NET Core uploads, whole folders and animated GIFs. Every sample was run against OneImage 1.5.0 on .NET 10.

TerminalNuGet
dotnet add package RecursiveAI.OneImage

Convert JPG or PNG to WebP

Program.cs.NET 10
using OneImage;

// JPEG, PNG, GIF, TIFF, BMP or ICO in; lossy WebP at quality 75 out.
Image.Load("photo.jpg").Save("photo.webp");

The output format comes from the file extension, and the input format is detected from the file’s content, so a PNG saved as photo.jpg still loads. On a 3000×3002 photograph (a NASA public-domain image, 6,511,067 bytes as JPEG) this wrote a 489,990-byte WebP at the same size.

A plain load and save keeps what the file carried: the ICC profile and the EXIF block travel into the WebP, and the orientation is applied to the pixels, with the tag reset to 1. That is right for archiving and wrong for publishing photos from phones, which can carry GPS positions. For web output, use Image.Thumbnail, shown below, which strips EXIF, XMP and IPTC.

Set WebP quality and compression effort

Encoder settings go in SaveOptions.EncoderOptions:

Program.cs.NET 10
using OneImage;

var options = new SaveOptions
{
    EncoderOptions = new WebPEncoderOptions { Quality = 80, Effort = 6 }
};

Image.Load("photo.jpg").Save("photo-q80.webp", options);
WebPEncoderOptionsDefaultWhat it does
Quality75Lossy quality from 0 (smallest) to 100 (best). 75 is also libwebp’s default. Ignored for lossless files.
Effort4Search effort from 0 (fastest) to 6 (smallest output). Higher values take longer to encode.
LosslessfalseWrite lossless (VP8L) data instead of lossy (VP8).
ExacttrueKeep the color values of fully transparent pixels. Set it to false to let the encoder replace them for better compression.

On the same photograph, quality 80 at effort 6 wrote 598,152 bytes, against 489,990 at the defaults. Quality costs bytes quickly. Compare a few settings on your own images and pick the lowest one that looks right at the size you display.

Lossless WebP for logos, screenshots and transparency

Lossy WebP suits photographs. For logos, UI screenshots and line art, where sharp edges and exact colors matter, write lossless WebP:

Program.cs.NET 10
using OneImage;

Image.Load("logo.png").Save("logo.webp", new SaveOptions
{
    EncoderOptions = new WebPEncoderOptions { Lossless = true }
});

Our 600×300 test logo with a transparent background went from a 2,346-byte PNG to a 720-byte WebP. Decoded again, every pixel matched the PNG, alpha channel included. Transparency survives lossy WebP too: lossy files store the alpha channel losslessly.

Resize and convert to WebP in one step

Most WebP conversions feed a web page, so the image should also be smaller, upright and free of private metadata. Image.Thumbnail does all of that:

Program.cs.NET 10
using OneImage;

// At most 1200 px wide, upright, sRGB, EXIF and GPS removed, saved as WebP.
Image.Thumbnail("photo.jpg", 1200).Save("photo-1200.webp");

In order, it applies the EXIF orientation, resizes (never enlarging unless you pass upscale: true), converts to sRGB with a compact embedded profile and removes EXIF, XMP and IPTC. The 6.5 MB photograph became a 1200×1201 WebP of 136,772 bytes with no EXIF block. For JPEG input it decodes at 1/2, 1/4 or 1/8 scale when that is enough, so a large photo is never held in memory at full size. Pass a height and a fit: (Fit.Cover, Fit.Pad and others) for fixed boxes. For widths, boxes and crops without WebP, see resize an image in C#.

Convert uploads to WebP in ASP.NET Core

The same calls work on streams. This minimal API endpoint takes an uploaded image and returns a WebP:

Program.csASP.NET Core · .NET 10
using OneImage;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/images", async (IFormFile file) =>
{
    try
    {
        await using Stream upload = file.OpenReadStream();
        Image image = Image.Thumbnail(upload, 1600); // upright, sRGB, no EXIF/GPS

        var webp = new MemoryStream();
        await image.SaveAsync(webp, WebPFormat.Instance);
        webp.Position = 0;
        return Results.File(webp, "image/webp");
    }
    catch (ImageException ex) // not an image, damaged, or over the size limits
    {
        return Results.BadRequest(ex.Message);
    }
}).DisableAntiforgery();

app.Run();

A stream has no extension, so SaveAsync names the format with WebPFormat.Instance, and you can pass SaveOptions as a third argument for quality. Thumbnail copies the compressed bytes when it reads a stream, so the upload stream isn’t needed afterwards. Posting our 3000×3002 JPEG returned 200, image/webp, 1600×1601, 204,576 bytes. Posting a text file returned 400 with “The data is not in a recognized image format.” Size limits are on by default and checked from the image headers before large buffers are allocated, which matters for files you didn’t create. The guide shows how to change them.

If you would rather resize on request than at upload time, the separate OneImage.Web package is middleware that serves resized images from URL commands, including the output format, with a disk cache.

Batch convert a folder to WebP

Program.cs.NET 10
using OneImage;

var options = new SaveOptions { EncoderOptions = new WebPEncoderOptions { Quality = 80 } };

foreach (string path in Directory.GetFiles("uploads"))
{
    if (Path.GetExtension(path).Equals(".webp", StringComparison.OrdinalIgnoreCase))
        continue;

    if (!Image.TryLoad(path, out Image? image))
    {
        Console.WriteLine($"skipped {Path.GetFileName(path)}: not a supported image");
        continue;
    }

    string target = Path.ChangeExtension(path, ".webp");
    image.Save(target, options);
    Console.WriteLine($"{Path.GetFileName(path)} -> {Path.GetFileName(target)}, {new FileInfo(target).Length / 1024} KiB");
}

TryLoad returns false for files that aren’t images or that exceed the size limits, so one bad file doesn’t stop the batch. Skipping existing .webp files keeps a second run from converting its own output. On a folder with our JPEG, a PNG, the logo and a text file it printed:

Output
skipped notes.txt: not a supported image
c.png -> c.webp, 3 KiB
b.png -> b.webp, 331 KiB
a.jpg -> a.webp, 604 KiB

Image objects are immutable and safe to share between threads, so Parallel.ForEach over the files works when you have many. Each Save already uses up to four threads by default.

Convert an animated GIF to animated WebP

Program.cs.NET 10
using OneImage;

Image gif = Image.Load("spinner.gif");
gif.Save("spinner.webp"); // every frame, with its delay and the loop count

Console.WriteLine($"{gif.Frames.Count} frames written");

WebP, PNG (as APNG), GIF and TIFF keep all frames, while JPEG and BMP write only the first. Our 12-frame test GIF came out as a 12-frame WebP with the same 80 ms delays and infinite loop. Don’t assume the file shrinks: this small flat-color animation grew from 4,642 bytes as GIF to 8,080 as lossy WebP. Results depend on the content, so compare lossy, lossless (Lossless = true) and the original GIF on your own files.

Check the result

Image.Identify reads only the headers, which makes it a cheap way to confirm a conversion in a test or a health check:

Program.cs.NET 10
using OneImage;

ImageInfo info = Image.Identify("photo.webp");
Console.WriteLine($"{info.Format.Name}, {info.Width}x{info.Height}");

For the first example it prints webp, 3000x3002.

How fast is WebP encoding in pure C#?

In OneImage’s published benchmarks, converting a PNG to lossy WebP at quality 75 took 79.1 to 95.8 ms, depending on the image content. ImageSharp 3.1.12 took 236.5 to 1,511.5 ms for the same task, and the fastest native wrapper (NetVips or Magick.NET) took 155.1 to 162.3 ms. Lossy WebP encoding of a 2000×1500 image took 101.6 ms against ImageSharp’s 590.5 ms. These are BenchmarkDotNet means on one shared workstation (AMD Ryzen 9 7950X3D, .NET 10), so treat them as local measurements rather than promises for your hardware. The performance page has the full table, memory use and method.

Other ways to write WebP in .NET

OneImage requires .NET 10. For what else it reads and writes, see the formats on the overview page.