Table of Contents

Formats

Egami reads and writes PNG, APNG, JPEG, GIF, WebP, BMP, TGA, QOI and the Netpbm formats (PBM, PGM, PPM, PAM and the PFM float map), TIFF, Radiance .hdr, OpenEXR, JPEG XL, AVIF, and Windows icons and cursors, and reads JBIG2, raw CCITT fax data, JPEG 2000, Windows animated cursors (ANI) and Photoshop's PSD and PSB, layers included. HEIC phone photos load and save through the separate, opt-in Egami.Heic package; core Egami detects and probes them. SVG and Windows metafiles (WMF, EMF) load through the separate Egami.Svg and Egami.Metafiles packages, and PCX, SGI, Sun raster, XBM, XPM, WBMP Farbfeld, JPEG-LS, DDS and KTX textures, DPX and Cineon film frames, FITS, camera RAW (DNG and the camera makers' formats, with DNG written) and, as a preview, DICOM images and datasets through Egami.Formats.Extended. The Package column says which package holds each format. Every reader checks sizes against the safety limits before it allocates, and every reader has been fuzzed.

Each format has its own page with its options and what it keeps from the file; the table below links them. VP8 and AV1 video frames decode through Vp8StreamDecoder and Av1StreamDecoder, VP8 and AV1 frames encode through Vp8StreamEncoder and Av1StreamEncoder, and the boxes of MP4, QuickTime and HEIF files can be walked with IsoBoxReader. This page covers what all formats share: natural pixel types, detection, files with several images, and what writers drop.

At a glance

Format Reads Writes Natural pixel type Package
PNG Every color type and bit depth, interlaced, transparency, text, EXIF, XMP, ICC Every color type and bit depth, adaptive filtering, built palettes, Adam7 interlacing, compression on several cores The file's own: Gray8, Gray16, GrayAlpha8, Rgb8, Rgba8 or Rgba16 Egami
APNG Every PNG color type and depth, every disposal and blend Any raster with more than one frame, changed rectangles only Rgba8, or Rgba16 for 16-bit Egami
JPEG Baseline, extended and progressive, Huffman or arithmetic, 8 and 12-bit, lossless at 2 to 16 bits, any subsampling, gray, CMYK, YCCK, scaled decode, EXIF, XMP, ICC Baseline or progressive, 8 or 12-bit, quality 1 to 100, 4:4:4, 4:2:2, 4:2:0, gray, CMYK or YCCK, optimized Huffman tables or arithmetic coding; lossless at 2 to 16 bits Gray8 or Rgb8; Gray16, Rgb16 or Cmyk16 above 8 bits Egami
GIF 87a and 89a, every disposal, interlaced, transparency, loop count, comments, XMP, ICC 89a, exact or quantized palettes, global or per frame, dithering, frame optimization Rgba8 Egami
WebP Lossless (VP8L) with every transform; lossy (VP8) with alpha, as RGB or as YUV planes; ICC, EXIF, XMP Lossless at effort 0 to 6, exact transparent colors; ICC, EXIF with resolution, XMP Rgb8, or Rgba8 with alpha Egami
BMP Core, info, V2 to V5 and OS/2 2.x headers; 1 to 32 bits; RLE4, RLE8, RLE24, OS/2 Huffman 1D; bit fields; embedded PNG and JPEG; V5 ICC 24-bit, 32-bit with alpha, 1, 2, 4 or 8-bit palettes, RLE4 and RLE8; resolution and ICC Bgr8, or Bgra8 with alpha Egami
TGA Color-mapped, true-color and gray, raw and RLE, 8 to 32 bits, all origins, TGA 2.0 footer and extension area 8-bit gray, 24 and 32-bit, 8-bit palette, raw or RLE, TGA 2.0 footer Bgr8, Bgra8, Gray8 or GrayAlpha8 Egami
QOI The whole format, 3 or 4 channels, both colorspaces 3 or 4 channels, byte-identical to the reference encoder Rgb8 or Rgba8 Egami
TIFF Every page, strips and tiles, chunky and planar, classic and BigTIFF; None, LZW, Deflate, PackBits, CCITT G3 and G4, JPEG, old-style JPEG, ThunderScan, SGILog, WebP, JPEG XL, Zstd, LZMA, lossless JPEG at 2 to 16 bits; gray, palette, RGB, CMYK, YCbCr, Lab and LogLuv at 1 to 64 bits Every pixel type at its own depth, strips or tiles, classic or BigTIFF; None, LZW, Deflate, Zstd, LZMA, PackBits, CCITT G3 and G4, JPEG (lossless at Quality 100), WebP The page's own: Gray8, Gray16, Rgb8, Rgb16, Cmyk8, Cmyk16, float types and more Egami
CCITT Raw Group 3 and Group 4 data (T.4, T.6) with PDF's eight CCITTFaxDecode parameters, byte-aligned rows included No Gray8, black 0 and white 255 Egami
JBIG2 Every T.88 region and dictionary but the colour extension; standalone files (sequential and random-access, many pages) and the embedded form a PDF holds, with its globals No Gray8, black 0 and white 255 Egami
JPEG 2000 All of Part 1 (T.800): every progression order, code-block style, tiling and precinct size, reversible and irreversible; JP2 and JPX boxes, palettes, channel definitions, sYCC, CIELab, ICC; reduced and region decodes No Gray8, Rgb8, Rgba8, Cmyk8 or their 16-bit forms, by depth and color space Egami
ICO, CUR Every entry: PNG, and BMP at 1 to 32 bits with the AND mask; the best entry, or one by MinimumSize or index; cursor hotspots One entry a raster, or sets resized to favicon or Windows sizes; PNG at 256 and 32-bit BMP below; hotspots Rgba8 Egami
ICNS Every entry: PNG, JPEG 2000, RLE ARGB, RLE RGB with its 8-bit mask, and the old 1, 4 and 8-bit icons with their 1-bit mask; the best entry, or one by MinimumSize or index One PNG entry a raster, or sets resized to the macOS sizes 16 to 1024 Rgba8 Egami
ANI Animated cursors: every step as a frame with its delay, one size for all frames or every size with LoadAll; cursor, icon and raw-bitmap frames; hotspots, title and artist Yes: frames stored once as cursors, at their own size or a set of sizes, with hotspots, delays, title and artist Rgba8 Egami
PSD, PSB The flattened image in every color mode (bitmap, gray, indexed, RGB, CMYK, Lab, duotone, multichannel) at 1 to 32 bits, raw, PackBits and Deflate; every layer with its pixels and mask (PsdImage.ReadLayers); resources as metadata No By mode and depth: Gray8, Rgb8, Rgba8, Cmyk8, their 16-bit forms, float types Egami
Radiance RGBE and XYZE, every scan order, flat, old repeat-pixel and per-channel run-length scanlines RGBE, run-length encoded or flat, optional EXPOSURE line RgbF32, linear Egami
OpenEXR Scanline and tiled, single and multi-part, every OpenEXR 3 compression but HTJ2K, half, float and uint channels, luminance-chroma; any part, layer or channel One scanline part, R, G, B and A, ZIP, ZIPS, RLE, PIZ or none, in half or float RgbaF16 when the channels are half, RgbaF32 otherwise, GrayF32 for luminance alone; linear Egami
JPEG XL Every still, lossless Modular and lossy VarDCT or Modular, at 1 to 31 bits and float, with alpha, CMYK, extra channels, animations and layers, previews, PQ and HLG, gain maps, and recompressed JPEGs (given back byte for byte) Lossless in the raster's own precision, or lossy VarDCT in XYB, at cjxl's effort and quality scales; JPEGs recompressed losslessly By bit depth: Rgb8, Rgba8, Gray8, their 16-bit forms, RgbaF16 for 16-bit float, RgbF32, RgbaF32 or GrayF32 beyond Egami
AVIF Still images: AV1 intra frames with every coding tool, equal to dav1d's decode and converted to RGB as libavif does; alpha, grids, overlays, clean aperture, orientation, ICC, CICP, HDR, gain maps, every item, and animated sequences Yes: lossy and lossless still images and animations with alpha, 8, 10 and 12 bits, 4:2:0, 4:2:2, 4:4:4 and 4:0:0 By bit depth: Rgb8 or Rgba8 for 8-bit files, Rgb16 or Rgba16 for 10 and 12 bits, Gray8 or Gray16 (with alpha, GrayAlpha8 or GrayAlpha16) for monochrome Egami
HEIC With Egami.Heic: HEVC pictures up to 12 bits, grids, alpha, gain maps, depth maps, mattes and image sequences with inter frames; core Egami probes them With Egami.Heic: lossy and lossless stills, 8 to 12 bits, 4:0:0 to 4:4:4, alpha, grids, gain maps, thumbnails Rgb8 for 8-bit files, Rgb16 for 10 and 12 bits, with alpha when the file has it, gray for monochrome Egami.Heic
PNM P1 to P6 plain and binary, maxval 1 to 65,535, P7 PAM with alpha, PF and Pf float maps P1 to P7, plain or binary; PF and Pf Gray8, Gray16, GrayAlpha8, Rgb8, Rgba8 or Rgba16; RgbF32 or GrayF32 for PFM Egami
PCX 1 to 8-bit indexed, EGA planes, 24 and 32-bit, coded or not 8-bit gray or 24-bit, run-length coded Gray8, Rgb8 or Rgba8 Egami.Formats.Extended
SGI 1 to 4 channels of 8 or 16 bits, plain or run-length coded The raster's channels and depth, coded or not Gray8, GrayAlpha8, Rgb8, Rgba8 or their 16-bit forms Egami.Formats.Extended
Sun raster 1, 8, 24 and 32 bits, colormaps, byte-encoded 8-bit gray or 24-bit, plain or byte-encoded Gray8 or Rgb8 Egami.Formats.Extended
XBM X11 and X10 bitmaps X11 bitmaps Gray8, black 0 and white 255 Egami.Formats.Extended
XPM Version 3, any color keys, None, hex colors and X11 names Version 3 with every color of the raster Rgba8 Egami.Formats.Extended
WBMP Type 0 Type 0 Gray8, black 0 and white 255 Egami.Formats.Extended
Farbfeld The whole format 16-bit RGBA Rgba16 Egami.Formats.Extended
JPEG-LS T.87 lossless and near-lossless, 2 to 16 bits, every interleave mode, restart markers, mapping tables, subsampling and HP colour transforms Lossless and near-lossless, 2 to 16 bits, 1 to 4 components, HP colour transforms Gray8, GrayAlpha8, Rgb8, Rgba8 or their 16-bit forms Egami.Formats.Extended
DDS Legacy and DX10 headers, BC1 to BC7, every DXGI format DirectXTex reads; mips, cube maps, arrays and volumes Uncompressed formats and BC1 to BC7, with mips, arrays and cube maps The nearest to the stored format: Rgba8, Gray8, Rgb8, Rgba16, RgbaF16, RgbaF32 and others Egami.Formats.Extended
KTX KTX 1.1 and KTX 2.0, uncompressed or Zstandard and zlib supercompressed; BC1 to BC7 and the uncompressed Vulkan and OpenGL formats; mips, cube maps, arrays and volumes No As DDS: Rgba8, Gray8, Rgb8, Rgba16, RgbaF16, RgbaF32 and others Egami.Formats.Extended
DPX and Cineon DPX 1.0 to 3.0 and Cineon 4.5: 1 to 16-bit integer and 32 and 64-bit float samples, every packing and byte order, luma, RGB, RGBA and CbYCr, run-length coding; every image element; log to linear on request DPX: gray, RGB or RGBA at 8, 10, 12 or 16 bits; Cineon: 10-bit RGB The stored code values: Rgb16, Rgba16, Gray16 or their 8-bit forms; RgbF32 and others for float or linearized Egami.Formats.Extended
FITS Image HDUs and cube planes, BITPIX 8 to 64 and floats, BSCALE, BZERO and BLANK, tile-compressed images (Rice, gzip, PLIO); stretched for viewing or as physical values BITPIX 8, 16 or -32, gray or a three-plane colour cube, with the header cards GrayF32, or RgbF32 for a colour cube Egami.Formats.Extended
Camera RAW DNG 1.0 to 1.7, Canon CR2, Nikon NEF and NRW, Pentax PEF, Sony ARW and SR2, Olympus ORF, Panasonic RW2 and Fujifilm RAF, developed on load (white balance, demosaicing, noise reduction, opcodes, camera profiles, lens corrections), or their previews or sensor mosaic; CR3 previews and metadata DNG: any RAW file converted with its sensor data unchanged (RawFormat.ConvertToDng), or any raster Rgb16 sRGB when developed; Gray16 for the mosaic Egami.Formats.Extended
DICOM Preview: the dataset of PS3.10 files (DicomFile), every native transfer syntax and deflated, sequences, character sets, frames decoded as a viewer shows them (rescale, window, VOI LUT, palette, YBR) from native, RLE, JPEG, JPEG-LS, JPEG 2000, HTJ2K and JPEG XL pixel data Preview: Secondary Capture from rasters; files saved again in native, RLE, JPEG, JPEG-LS or JPEG XL — Egami.Formats.Extended

Raster.Load returns the natural type; Raster.Load<TPixel> decodes straight into the type you ask for, converting row by row.

using Raster image = Raster.Load("duck.png");
switch (image)
{
    case Raster<Rgb8> rgb:
        Console.WriteLine($"8-bit color, {rgb.Width} × {rgb.Height}");
        break;
    case Raster<Rgba8> rgba:
        Console.WriteLine($"8-bit color with alpha, {rgba.Width} × {rgba.Height}");
        break;
    default:
        Console.WriteLine($"{image.Layout.ChannelOrder}, {image.Layout.ChannelType}");
        break;
}

Detection

Loading never trusts the file extension: the format comes from the first bytes, in the order PNG, JPEG, GIF, WebP, BMP, QOI, PNM, TIFF, JBIG2, JPEG 2000, ICO, CUR, ANI, ICNS, PSD, Radiance, OpenEXR, JPEG XL (FF 0A or its signature box), AVIF (an ftyp box with the avif or avis brand), HEIC (an ftyp box with a HEVC brand such as heic or heix), then TGA, then the formats a plugin package registers, such as Egami.Formats.Extended's. TGA has no signature, so it is matched only when its whole 18-byte header is consistent. No file of another format in Egami's test corpora is taken for TGA, and neither is any of a million random headers. When you know the format, register only that one:

// TGA has no signature, so it is detected last. When you know a file is TGA, register only that format.
EgamiContext tgaOnly = EgamiContext.Default with { Formats = [TgaFormat.Info] };
using Raster image = Raster.Load("duck.tga", tgaOnly);

Data without a signature of its own, such as the JBIG2 or CCITT stream of a PDF image, loads with its format named, which skips detection. Load, LoadAll, Probe and RasterDecoder.Open all take the format this way; naming the wrong one throws CorruptDataException, as damaged data of that format would.

// Data without a signature of its own (a TGA here; a JBIG2 stream from a PDF) loads with its format named,
// which skips detection. Probe, LoadAll and RasterDecoder.Open take the format the same way.
using Raster image = Raster.Load(data, TgaFormat.Info);
RasterInfo info = Raster.Probe(data, TgaFormat.Info);

Saving to a path picks the writer from the extension; an animated raster saved as .png writes an APNG. Saving to a stream takes the format's Info.

Several images in one file

Frames are animation and share one size. A TIFF's pages are separate images, each with its own size and pixel type, so they come as a RasterList from Raster.LoadAll, which owns them; RasterInfo.ImageCount says how many there are. Every other format gives its one image, so LoadAll works on any file. Raster.SaveAll writes several rasters to a format that holds several, and one raster to any format.

// Every page at its own size and pixel type. The list owns the rasters.
using RasterList pages = Raster.LoadAll("scan.tif");
foreach (Raster page in pages)
{
    Console.WriteLine($"{page.Width} × {page.Height}");
}

// Keep one page after the list is gone.
using Raster first = pages.Detach(0);

What each writer drops

Writers never change the raster. When a format cannot hold something, it is left out:

  • Alpha: JPEG (unless JpegWriterOptions.Background blends it onto a color), BMP and TGA with Bgr24, PPM, QOI with IncludeAlpha = false.
  • Frames after the first: JPEG, BMP, TGA, QOI, PNM and HEIC.
  • Metadata: every writer keeps what its format can carry; StripMetadata = true on any writer options drops it all.

See the API reference for every option.

What the file said

Every reader fills RasterMetadata.FormatDetails with the header's own terms: PngDetails, JpegDetails, GifDetails, BmpDetails, TgaDetails, QoiDetails or PnmDetails. A writer of the same format uses them to keep what it can, and ignores another format's details.

// Each reader fills in what the file's header says, in the format's own terms.
using Raster png = Raster.Load("duck.png");
if (png.Metadata.FormatDetails is PngDetails pngDetails)
{
    Console.WriteLine($"PNG {pngDetails.ColorType}, {pngDetails.BitDepth}-bit, interlaced: {pngDetails.Interlaced}");
    foreach (PngTextSource source in pngDetails.TextSources)
    {
        Console.WriteLine($"Text from a {source.ChunkType} chunk, compressed: {source.Compressed}");
    }
}

using Raster bmp = Raster.Load("duck.bmp");
BmpDetails bmpDetails = (BmpDetails)bmp.Metadata.FormatDetails!;
Console.WriteLine($"BMP {bmpDetails.BitsPerPixel}-bit, {bmpDetails.Compression}, {bmpDetails.HeaderSize}-byte header");

using Raster tga = Raster.Load("duck.tga");
TgaDetails tgaDetails = (TgaDetails)tga.Metadata.FormatDetails!;
Console.WriteLine($"TGA type {tgaDetails.ImageType}, origin {tgaDetails.Origin}, alpha {tgaDetails.AlphaType}");

using Raster qoi = Raster.Load("duck.qoi");
QoiDetails qoiDetails = (QoiDetails)qoi.Metadata.FormatDetails!;
Console.WriteLine($"QOI {qoiDetails.Channels} channels, linear: {qoiDetails.Linear}");

using Raster pgm = Raster.Load("duck.pgm");
PnmDetails pnmDetails = (PnmDetails)pgm.Metadata.FormatDetails!;
Console.WriteLine($"PNM {pnmDetails.Kind}, maxval {pnmDetails.MaxValue}, plain text: {pnmDetails.PlainText}");

Each format is described by a FormatInfo: its name, extensions, MIME types, and how to recognize it from its first bytes.

// Every built-in format, with its name, extensions and MIME types; Raster.Save picks one by extension.
foreach (FormatInfo format in new[] { PngFormat.Info, JpegFormat.Info, GifFormat.Info, BmpFormat.Info, TgaFormat.Info, QoiFormat.Info, PnmFormat.Info })
{
    Console.WriteLine($"{format.Name}: {string.Join(", ", format.Extensions)} ({string.Join(", ", format.MimeTypes)})");
}

// A SignatureMatcher decides from the first SignatureLength bytes whether a file is in the format.
SignatureMatcher isPng = PngFormat.Info.Matches;
bool png = isPng(File.ReadAllBytes("duck.png").AsSpan(0, PngFormat.Info.SignatureLength));

Stored samples

A container that keeps its own pixel description, such as DICOM, needs the values a codestream stores rather than pixels. SampleImage.Load decodes JPEG (baseline, extended 12-bit and lossless at 2 to 16 bits), JPEG 2000 and JPEG XL (integer samples up to 16 bits) that way: one int a sample, interleaved, at each component's own precision and sign (SampleComponent), with no color conversion (SampleColor says what the components are), no widening and no offset. Other formats throw NotSupportedException, as do Region, MinimumSize and ConvertColorTo. Chroma at less than full resolution is brought to the image's size.

SampleImage, SampleComponent and SampleColor are a preview, like DICOM, which uses them: they carry .NET's [Experimental] attribute with the diagnostic EGAMIX001, so suppress that id to call them, and they may change in any minor release.

// The values the codestream stores: no color conversion, no widening to 8 or 16 bits, signed samples kept signed.
SampleImage image = SampleImage.Load("duck.jpg");
Console.WriteLine($"{image.Color}, {image.Components.Count} components of {image.Components[0].Precision} bits");

// A YCbCr JPEG stays YCbCr: luma, then the two chroma samples of each pixel, at full resolution.
Span<int> row = image.Row(0);
int luma = row[0], cb = row[1], cr = row[2];

A codestream taken out of a container is read as the format the container names, without detection:

// A codestream taken out of a container, such as a DICOM pixel data fragment, is read as the format the container names.
SampleImage image = SampleImage.Load(fragment, JpegFormat.Info);
SampleComponent gray = image.Components[0];
int first = image.Samples.Span[0];       // 0 to 4095 for 12 unsigned bits

Recipes

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

Batch convert a folder

// Convert a folder of images to QOI, a few at a time. Each image runs on one thread, so the files run side by side.
EgamiContext oneThread = EgamiContext.Default with { MaxParallelism = 1 };
Directory.CreateDirectory("out");
Parallel.ForEach(Directory.GetFiles("in"), new ParallelOptions { MaxDegreeOfParallelism = 4 }, path =>
{
    try
    {
        using Raster image = Raster.Load(path, oneThread);
        image.Save(Path.Combine("out", Path.GetFileNameWithoutExtension(path) + ".qoi"));
    }
    catch (EgamiException e)
    {
        Console.WriteLine($"{path}: {e.Message}");
    }
});

Read the size without decoding

RasterInfo info = Raster.Probe("sideways.jpg");
bool sideways = info.Orientation is >= Egami.Metadata.ImageOrientation.Transpose;
(int width, int height) = sideways ? (info.Height, info.Width) : (info.Width, info.Height);
Console.WriteLine($"{info.Format.Name}, shown as {width} × {height}, about {info.EstimatedBytes:N0} bytes to load");

Probe reads only the header, and reports the EXIF orientation, so you know how the image will be shown.

Run it

samples/Formats is a console app that does what this guide shows on the bundled images: dotnet run --project samples/Formats writes its results to ./output.