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.