Table of Contents

Getting started

Egami loads, edits and saves images in .NET 10, in fully managed code. This page takes you from install to a saved file.

Install

dotnet add package Egami

One package holds everything: every format, operation and pixel type. It has no dependencies and works with trimming and Native AOT.

Load, edit, save

// Load in the pixel type that best fits the file (here Rgba8: this PNG stores alpha).
using Raster image = Raster.Load("duck.png");
Console.WriteLine($"{image.Width} × {image.Height}, {image.Layout.ChannelOrder}");

// Make a thumbnail that fits 200 × 200, keeping the aspect ratio, and save it as JPEG.
image.Apply(ops => ops.Fit(200, 200));
image.Save("duck-small.jpg");

Raster.Load detects the format from the file's first bytes and returns a Raster in the pixel type that holds the file without loss. Apply runs a chain of operations in place. Save picks the writer from the file extension.

Pixel types

A raster is a grid of one pixel type, such as Rgba8 (8-bit red, green, blue and alpha) or Gray16. Ask for the type you want to work in, and rows come back as spans of it.

// Decode straight into the type you want to work in.
using Raster<Rgba8> rgba = Raster.Load<Rgba8>("duck.png");

// Pixels are a row at a time: each row is a Span<Rgba8>.
for (int y = 0; y < rgba.Height; y++)
{
    Span<Rgba8> row = rgba.Row(y);
    for (int x = 0; x < row.Length; x++)
    {
        row[x].A = 128;
    }
}

// Convert a whole raster; the result is a new raster you dispose too.
using Raster<Gray8> gray = rgba.As<Gray8>();
gray.Save("duck-gray.png");
Type Channels Typical use
Rgba8, Bgra8 8-bit color with alpha The default working type; interop with Windows and GPU buffers
Rgb8, Bgr8 8-bit color Photos without alpha
Argb8, Abgr8 8-bit color with alpha first Interop with alpha-first buffers
Alpha8 Alpha only Masks and glyph coverage
Gray8, Gray16, GrayAlpha8, GrayAlpha16 Gray Grayscale images and masks
Rgba16, Rgb16 16-bit color, with or without alpha 16-bit PNG and PPM
RgbaF32, RgbF32, GrayF32 32-bit float color or gray High-precision processing, PFM, depth maps
RgbaF16 16-bit half-float color with alpha HDR and GPU textures
Bgr565, Bgra4444, Rgb10A2 Packed 16 and 32-bit color Embedded displays, legacy textures, 10-bit displays
Bgr555, Bgra5551 Packed 5-5-5 color, with a 1-bit alpha 16-bit BMP and TGA, GDI+ 16-bit bitmaps
Cmyk8, Cmyk16 Cyan, magenta, yellow and black ink CMYK JPEG, TIFF and PSD
PremulRgba8, PremulBgra8, PremulRgbaF32 Color premultiplied by alpha Compositing; Windows and GPU surfaces

Values are sRGB-encoded with straight (not premultiplied) alpha, except in the Premul types, which store color multiplied by alpha; their conversions still take and return straight colors, and resize, blur, rotation and DrawImage run on them without multiplying first. raster.As<TPixel>() converts a whole raster; PixelConvert.Convert converts a span.

Disposal

// Every raster owns its pixel memory: dispose it when done, best with a using declaration.
using Raster photo = Raster.Load("duck.png");

// Copy, As and Derive return new rasters, which you dispose as well. Everything else works in place.
using Raster copy = photo.Derive(ops => ops.Resize(100, 0));
photo.Apply(ops => ops.Grayscale());

Pixel memory comes from a pool, not the garbage-collected heap, so a raster you forget to dispose holds its memory until the next collection. Methods that return a new raster say so in their name: Copy, As and Derive.

Sync and async

// Every Load, Probe and Save has an async twin. Async reads the stream into a pooled buffer first,
// then decodes (decoding is CPU work, so it is synchronous either way).
await using FileStream stream = File.OpenRead("duck.png");
using Raster image = await Raster.LoadAsync(stream, cancellationToken: cancellationToken);
await image.SaveAsync("duck-copy.png", cancellationToken: cancellationToken);

Save, SaveAsync, SaveAll and Transcode also take an IProgress<SaveProgress> and a CancellationToken, in the same shape as Egami.Video's MediaProgress: a report as the save starts, at most one every 250 ms, then a final one with IsFinal set and a Fraction of 1. Row-by-row formats count the rows they have written; AVIF, lossy JPEG XL and lossy WebP count their own work; the rest report only as they start and end.

// Slow encoders (AVIF, JPEG XL, lossy WebP) report how far they have come: once as they start, at most every 250 ms,
// then once more with IsFinal. A canceled token stops the encode and deletes the half-written file.
Progress<SaveProgress> progress = new(p => Console.WriteLine($"{p.Fraction:P0} after {p.Elapsed.TotalSeconds:F1} s"));
image.Save("duck.avif", options: null, progress, cancellationToken);

Choosing a format

// Save(path) picks the writer from the file extension...
image.Save("duck-copy.qoi");

// ...or name the format, with its options.
using FileStream output = File.Create("duck-q90.jpg");
image.Save(output, Egami.Formats.Jpeg.JpegFormat.Info, new Egami.Formats.Jpeg.JpegWriterOptions { Quality = 90 });

// Loading never looks at the extension: the format is detected from the first bytes.
RasterInfo info = Raster.Probe("duck.png");
Console.WriteLine(info.Format.Name);   // PNG
Use Format
Photos for the web JPEG (or PNG when you need alpha)
Screenshots, diagrams, anything with sharp edges or alpha PNG
Short animations GIF for reach, APNG for full color and alpha
Fast lossless storage QOI
Interchange with older tools BMP, TGA, PNM

Next: the guides explain each area in order, starting with pixel types and formats, many ending with recipes for common tasks.