Table of Contents

WebP

Egami reads and writes WebP: lossless (VP8L), lossy (VP8) with or without alpha, and animated, written from RFC 9649 and RFC 6386 with no native code. WebpFormat.Info is registered in EgamiContext.Default, so Raster.Load recognizes WebP from its first bytes and Save("x.webp") picks the writer.

Writing

// Lossy, the default for rasters not read from WebP: quality 0 to 100 and effort 0 to 6 on cwebp's scales.
photo.Save("small.webp", new WebpWriterOptions { Quality = 80 });

// Lossless: every pixel exact, colors under full transparency included. Effort 0 (fastest) to 6 (smallest).
photo.Save("duck.webp", new WebpWriterOptions { Lossless = true, Effort = 6 });

// A raster read from a WebP file keeps its kind when saved with the defaults.
using Raster again = Raster.Load("duck.webp");
again.Save("copy.webp");

WebpWriterOptions follows libwebp's own knobs, so the same numbers give about the same files as cwebp:

Option Default Does
Lossless null true writes VP8L, false VP8; null keeps the kind of the WebP file the raster came from (WebpDetails.Lossless) and writes lossy otherwise
Quality 75 Lossy quality, 0 to 100, cwebp's -q scale
Effort 4 0 (fastest) to 6 (smallest), cwebp's -m, for both kinds
AlphaQuality 100 Lossy files with transparency: 100 keeps alpha exact
KeepTransparentColors true Colors under fully transparent pixels are kept (exactly in lossless files); false lets the encoder change them for smaller files
SharpYuv false Lossy: a slower color conversion that keeps sharp colored edges
NearLossless 100 Lossless: below 100 pixel values may move a little before coding
MixedFrames false Animations: each frame is encoded both ways and the smaller kept
FirstFrameOnly false Writes only the first frame, as a still

Lossy files with transparency carry the alpha in an ALPH chunk: compressed losslessly with the filter that gives the smallest result (all four tried at effort 5 and 6), and with fewer levels when AlphaQuality is below 100, fully transparent and fully opaque pixels always kept exact.

Lossless WebP holds sides up to 16,384 pixels and lossy up to 16,383; larger rasters throw before any work. Every pixel type can be saved: it is converted to 8-bit RGBA first.

Reading

Raster.Load gives Rgba8 when the file says it has alpha (the VP8X flag, the VP8L hint or an ALPH chunk) and Rgb8 otherwise. Lossless files decode to exactly libwebp's pixels. Lossy files decode to libwebp's YUV planes sample for sample, and to RGB within one level of libwebp's: chroma is brought to full resolution exactly as libwebp's default upsampler does, then converted with the BT.601 kernel of the planar YUV guide. YuvRaster.Load returns a lossy file's planes untouched (4:2:0, BT.601, limited range, chroma in the centre), with the alpha as a fourth plane. Every kind streams through RasterDecoder, with regions, reductions and incremental decode. The same VP8 decoder reads video frames too, inter frames included: see VP8 video frames. WebpReaderOptions adds two choices on top of the general reader options:

using Raster image = Raster.Load("duck.webp", options: new WebpReaderOptions
{
    UseBackgroundColor = true, // animations: start from the ANIM background color instead of transparent
    FastUpsampling = true,     // lossy files: repeat chroma samples instead of interpolating
});

Format details and metadata

ICC profiles, EXIF and XMP are read into RasterMetadata and written back unchanged. WebP has no resolution field, so the resolution is read from the EXIF block and written into it, in a minimal EXIF block when the raster has none. IPTC has no place in WebP and is not written. WebpDetails says what kind of file it was and keeps chunks Egami does not read, each a WebpChunk with its WebpChunkPosition; animation frames carry WebpFrameDetails.

using Raster<Rgba8> image = Raster.Load<Rgba8>("duck.webp");
WebpDetails webp = (WebpDetails)image.Metadata.FormatDetails!;
Console.WriteLine($"{(webp.Lossless ? "lossless" : "lossy")}, extended: {webp.Extended}, alpha: {webp.HasAlpha}");

// Chunks Egami does not read are kept and written back where they were.
foreach (WebpChunk chunk in webp.OtherChunks)
{
    Console.WriteLine($"{chunk.Type}: {chunk.Data.Length} bytes, {chunk.Position}");
}

// An animation's frames say whether each was stored lossless.
foreach (Frame<Rgba8> frame in image.Frames)
{
    Console.WriteLine(frame.Metadata.FormatDetails is WebpFrameDetails { Lossless: true } ? "lossless frame" : "lossy frame");
}

Animation

Animated WebP reads and writes through the same frames as GIF and APNG. Reading composes every frame on a canvas exactly as libwebp's animation decoder does (its blending and key frames included), starting transparent unless WebpReaderOptions.UseBackgroundColor asks for the ANIM background color; ReaderOptions.Frames and streaming through RasterDecoder work as for GIF, holding one canvas whatever the frame count. Writing stores each frame's changed rectangle at even offsets, blended over the canvas when its new pixels are opaque and replacing them otherwise, with delays rounded to whole milliseconds and capped at 16,777,215.

// A GIF (or APNG) becomes an animated WebP in one Load and one Save: frames, delays and loop count carry over. Each frame
// stores only what changed; lossless frames are exact, MixedFrames keeps whichever kind is smaller per frame.
using Raster gif = Raster.Load("pan.gif");
gif.Save("pan.webp", new WebpWriterOptions { Lossless = false, MixedFrames = true });

// Read back, every frame is the composed canvas, as with GIF and APNG.
using Raster<Rgba8> webp = Raster.Load<Rgba8>("pan.webp");
foreach (Frame<Rgba8> frame in webp.Frames)
{
    Console.WriteLine($"{frame.Metadata.Delay.TotalMilliseconds} ms, changed {frame.Metadata.Bounds}, blend {frame.Metadata.Blend}");
}

Recipes

Short answers to common tasks in this area. Each one compiles and runs in Egami's test run.

Convert images to WebP for the web

// Photos as lossy WebP, graphics with few colors as lossless: Probe reads only the header to tell them apart.
foreach (string path in new[] { "duck.png", "egret.png" })
{
    using Raster image = Raster.Load(path);
    bool photo = image.Width * image.Height > 100_000;
    image.Save(Path.ChangeExtension(path, ".webp"), photo
        ? new WebpWriterOptions { Quality = 80, Effort = 4 }
        : new WebpWriterOptions { Lossless = true, Effort = 6 });
}

Quality and Effort mean what cwebp's -q and -m mean; lossless keeps every pixel.