Table of Contents

Class RasterDecoder

Namespace
Egami
Assembly
Egami.dll

One open image, decoded a band of rows or a frame at a time. Opening reads the header and the metadata that comes before the image data; ReadRows<TPixel>(Raster<TPixel>) and ReadFrame<TPixel>(Raster<TPixel>) then hand out the image in any pixel type, with Region and MinimumSize applied as in Load(Stream, EgamiContext?, ReaderOptions?).

public sealed class RasterDecoder : IDisposable
Inheritance
RasterDecoder
Implements
Inherited Members

Examples

// A band of rows at a time: memory is the band plus the format's working set, whatever the image size.
using RasterDecoder decoder = RasterDecoder.Open("egret.png");
Console.WriteLine($"{decoder.Info.Width}×{decoder.Info.Height}, {decoder.Info.Access}, holding {decoder.Info.WorkingBytes:N0} bytes");
using Raster<Gray8> band = Raster.Create<Gray8>(decoder.Info.Width, 64);
int rows;
while ((rows = decoder.ReadRows(band)) > 0)
{
    for (int y = 0; y < rows; y++)
    {
        foreach (Gray8 pixel in band.Row(y))
        {
            sum += pixel.L;
        }
    }
}

Remarks

A decoder is one caller's object and is not thread-safe. Access says whether the format streams its rows or is decoded into a buffer first, and WorkingBytes how much that holds.

Properties

CurrentFrame

Gets the metadata of the frame being read, or null after the last frame (or while an incremental decode waits for it).

public FrameMetadata? CurrentFrame { get; }

Property Value

FrameMetadata

HeaderRead

Gets whether the header has been read, so Info and Metadata can be used. Always true unless the decode is incremental.

public bool HeaderRead { get; }

Property Value

bool

Info

Gets what the decoder produces: the size after Region and MinimumSize, the natural pixel layout, the frame count when known, and how rows are produced.

public RasterInfo Info { get; }

Property Value

RasterInfo

Exceptions

InvalidOperationException

An incremental decode has not received the header yet; see HeaderRead.

IsMetadataComplete

Gets whether Metadata holds everything the file has. PNG and GIF may put text, EXIF and comments after the image data, which arrive once the last frame has been read.

public bool IsMetadataComplete { get; }

Property Value

bool

Metadata

Gets the metadata read so far; see IsMetadataComplete. When the region or reduction changes the size, the EXIF dimensions follow it and the EXIF thumbnail is dropped, as a crop or resize would.

public RasterMetadata Metadata { get; }

Property Value

RasterMetadata

Exceptions

InvalidOperationException

An incremental decode has not received the header yet; see HeaderRead.

Status

Gets where the decode stands: rows can be read, it needs more bytes (incremental decodes only), the current frame has been read, or every frame has.

public DecodeStatus Status { get; }

Property Value

DecodeStatus

Methods

Append(ReadOnlySpan<byte>)

Gives an incremental decode the next bytes of the file, and reads the header once it has all arrived.

public void Append(ReadOnlySpan<byte> data)

Parameters

data ReadOnlySpan<byte>

The bytes, in order after those appended before. They are copied.

Exceptions

InvalidOperationException

The decoder is not incremental, or EndOfInput() was called.

UnsupportedFormatException

No registered format matches the data.

CorruptDataException

The header breaks the format's rules.

LimitExceededException

The header breaks a safety limit.

ObjectDisposedException

The decoder has been disposed.

Dispose()

Returns the source's buffers and closes the file when the decoder opened it.

public void Dispose()

EndOfInput()

Says the last byte has been appended: from now on, missing bytes are the end of the file, as in a normal read.

public void EndOfInput()

Exceptions

InvalidOperationException

The decoder is not incremental.

UnsupportedFormatException

No registered format matches the data.

CorruptDataException

The header breaks the format's rules or is cut off.

ObjectDisposedException

The decoder has been disposed.

NextFrame()

Moves to the next frame, leaving any rows of the current one unread.

public bool NextFrame()

Returns

bool

true if there is another frame; false after the last.

Exceptions

ObjectDisposedException

The decoder has been disposed.

NextFrameAsync(CancellationToken)

Moves to the next frame, reading the stream asynchronously as far as it needs. See NextFrame().

public ValueTask<bool> NextFrameAsync(CancellationToken cancellationToken = default)

Parameters

cancellationToken CancellationToken

Cancels reading.

Returns

ValueTask<bool>

true if there is another frame; false after the last.

Examples

// On a server: read rows as the upload arrives, without holding the whole file.
await using Stream upload = File.OpenRead("egret.png");
using RasterDecoder decoder = await RasterDecoder.OpenAsync(upload, cancellationToken: TestContext.Current.CancellationToken);
using Raster<Gray8> band = Raster.Create<Gray8>(decoder.Info.Width, 32);
int rows;
while ((rows = await decoder.ReadRowsAsync(band, TestContext.Current.CancellationToken)) > 0)
{
    for (int y = 0; y < rows; y++)
    {
        sum += band.Row(y)[0].L;
    }
}

Exceptions

InvalidOperationException

The decoder was not opened with OpenAsync(Stream, EgamiContext?, ReaderOptions?, CancellationToken).

Open(Stream, EgamiContext?, ReaderOptions?)

Opens an image from a stream: detects the format and reads the header. The stream is read from its current position and left open; the decoder does not dispose it.

public static RasterDecoder Open(Stream stream, EgamiContext? context = null, ReaderOptions? options = null)

Parameters

stream Stream

The encoded image.

context EgamiContext

The context, or null for Default.

options ReaderOptions

How to read, or null for Default.

Returns

RasterDecoder

The decoder. The caller must dispose it.

Examples

// A band of rows at a time: memory is the band plus the format's working set, whatever the image size.
using RasterDecoder decoder = RasterDecoder.Open("egret.png");
Console.WriteLine($"{decoder.Info.Width}×{decoder.Info.Height}, {decoder.Info.Access}, holding {decoder.Info.WorkingBytes:N0} bytes");
using Raster<Gray8> band = Raster.Create<Gray8>(decoder.Info.Width, 64);
int rows;
while ((rows = decoder.ReadRows(band)) > 0)
{
    for (int y = 0; y < rows; y++)
    {
        foreach (Gray8 pixel in band.Row(y))
        {
            sum += pixel.L;
        }
    }
}

Exceptions

UnsupportedFormatException

No registered format matches the data.

CorruptDataException

The data breaks the format's rules.

LimitExceededException

The image breaks a safety limit.

ArgumentOutOfRangeException

Region has no area or misses the image.

ArgumentNullException

stream is null.

Open(Stream, FormatInfo, EgamiContext?, ReaderOptions?)

Opens an image of a known format from a stream without detecting it, for data that has no signature of its own. See Open(Stream, EgamiContext?, ReaderOptions?).

public static RasterDecoder Open(Stream stream, FormatInfo format, EgamiContext? context = null, ReaderOptions? options = null)

Parameters

stream Stream

The encoded image, read from its current position and left open.

format FormatInfo

The format to read with. It need not be registered in the context.

context EgamiContext

The context, or null for Default.

options ReaderOptions

How to read, or null for Default.

Returns

RasterDecoder

The decoder. The caller must dispose it.

Examples

// 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);

Exceptions

UnsupportedFormatException

format has no reader.

CorruptDataException

The data breaks the format's rules, including data of another format.

LimitExceededException

The image breaks a safety limit.

ArgumentOutOfRangeException

Region has no area or misses the image.

ArgumentNullException

stream or format is null.

Open(ReadOnlyMemory<byte>, EgamiContext?, ReaderOptions?)

Opens an image from bytes in memory. See Open(Stream, EgamiContext?, ReaderOptions?).

public static RasterDecoder Open(ReadOnlyMemory<byte> data, EgamiContext? context = null, ReaderOptions? options = null)

Parameters

data ReadOnlyMemory<byte>

The encoded image.

context EgamiContext

The context, or null for Default.

options ReaderOptions

How to read, or null for Default.

Returns

RasterDecoder

The decoder. The caller must dispose it.

Examples

// A band of rows at a time: memory is the band plus the format's working set, whatever the image size.
using RasterDecoder decoder = RasterDecoder.Open("egret.png");
Console.WriteLine($"{decoder.Info.Width}×{decoder.Info.Height}, {decoder.Info.Access}, holding {decoder.Info.WorkingBytes:N0} bytes");
using Raster<Gray8> band = Raster.Create<Gray8>(decoder.Info.Width, 64);
int rows;
while ((rows = decoder.ReadRows(band)) > 0)
{
    for (int y = 0; y < rows; y++)
    {
        foreach (Gray8 pixel in band.Row(y))
        {
            sum += pixel.L;
        }
    }
}

Exceptions

UnsupportedFormatException

No registered format matches the data.

CorruptDataException

The data breaks the format's rules.

LimitExceededException

The image breaks a safety limit.

ArgumentOutOfRangeException

Region has no area or misses the image.

Open(ReadOnlyMemory<byte>, FormatInfo, EgamiContext?, ReaderOptions?)

Opens an image of a known format from bytes in memory. See Open(Stream, FormatInfo, EgamiContext?, ReaderOptions?).

public static RasterDecoder Open(ReadOnlyMemory<byte> data, FormatInfo format, EgamiContext? context = null, ReaderOptions? options = null)

Parameters

data ReadOnlyMemory<byte>

The encoded image.

format FormatInfo

The format to read with. It need not be registered in the context.

context EgamiContext

The context, or null for Default.

options ReaderOptions

How to read, or null for Default.

Returns

RasterDecoder

The decoder. The caller must dispose it.

Examples

// 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);

Exceptions

UnsupportedFormatException

format has no reader.

CorruptDataException

The data breaks the format's rules, including data of another format.

LimitExceededException

The image breaks a safety limit.

ArgumentOutOfRangeException

Region has no area or misses the image.

ArgumentNullException

format is null.

Open(string, EgamiContext?, ReaderOptions?)

public static RasterDecoder Open(string path, EgamiContext? context = null, ReaderOptions? options = null)

Parameters

path string

The file path.

context EgamiContext

The context, or null for Default.

options ReaderOptions

How to read, or null for Default.

Returns

RasterDecoder

The decoder. The caller must dispose it, which closes the file.

Examples

// A band of rows at a time: memory is the band plus the format's working set, whatever the image size.
using RasterDecoder decoder = RasterDecoder.Open("egret.png");
Console.WriteLine($"{decoder.Info.Width}×{decoder.Info.Height}, {decoder.Info.Access}, holding {decoder.Info.WorkingBytes:N0} bytes");
using Raster<Gray8> band = Raster.Create<Gray8>(decoder.Info.Width, 64);
int rows;
while ((rows = decoder.ReadRows(band)) > 0)
{
    for (int y = 0; y < rows; y++)
    {
        foreach (Gray8 pixel in band.Row(y))
        {
            sum += pixel.L;
        }
    }
}

Exceptions

UnsupportedFormatException

No registered format matches the data.

CorruptDataException

The data breaks the format's rules.

LimitExceededException

The image breaks a safety limit.

ArgumentOutOfRangeException

Region has no area or misses the image.

ArgumentNullException

path is null.

FileNotFoundException

The file does not exist.

IOException

The file cannot be read.

OpenAsync(Stream, EgamiContext?, ReaderOptions?, CancellationToken)

Opens an image from a stream read asynchronously: the header is read before this returns, and ReadRowsAsync<TPixel>(Raster<TPixel>, CancellationToken), ReadFrameAsync<TPixel>(Raster<TPixel>, CancellationToken) and NextFrameAsync(CancellationToken) read on as they need. Only the bytes since the decode's last step are held, not the whole file; formats from plugins, and files decoded into a buffer first, gather the file and are read at the end. The stream is left open; the decoder does not dispose it.

public static Task<RasterDecoder> OpenAsync(Stream stream, EgamiContext? context = null, ReaderOptions? options = null, CancellationToken cancellationToken = default)

Parameters

stream Stream

The encoded image, read from its current position.

context EgamiContext

The context, or null for Default.

options ReaderOptions

How to read, or null for Default.

cancellationToken CancellationToken

Cancels reading and, between rows, decoding.

Returns

Task<RasterDecoder>

The decoder, its header read. The caller must dispose it.

Examples

// On a server: read rows as the upload arrives, without holding the whole file.
await using Stream upload = File.OpenRead("egret.png");
using RasterDecoder decoder = await RasterDecoder.OpenAsync(upload, cancellationToken: TestContext.Current.CancellationToken);
using Raster<Gray8> band = Raster.Create<Gray8>(decoder.Info.Width, 32);
int rows;
while ((rows = await decoder.ReadRowsAsync(band, TestContext.Current.CancellationToken)) > 0)
{
    for (int y = 0; y < rows; y++)
    {
        sum += band.Row(y)[0].L;
    }
}

Exceptions

UnsupportedFormatException

No registered format matches the data.

CorruptDataException

The header breaks the format's rules.

LimitExceededException

The image breaks a safety limit.

ArgumentNullException

stream is null.

OperationCanceledException

cancellationToken was canceled.

OpenIncremental(EgamiContext?, ReaderOptions?)

Starts an incremental decode: bytes are given with Append(ReadOnlySpan<byte>) as they arrive, and rows, frames and previews come out as far as the bytes reach. Status says when more bytes are needed and HeaderRead when Info is known; EndOfInput() says the last byte has come.

public static RasterDecoder OpenIncremental(EgamiContext? context = null, ReaderOptions? options = null)

Parameters

context EgamiContext

The context, or null for Default.

options ReaderOptions

How to read, or null for Default.

Returns

RasterDecoder

The decoder. The caller must dispose it.

Examples

// Decode while the bytes arrive, a chunk at a time, as from a download.
using RasterDecoder decoder = RasterDecoder.OpenIncremental();
Raster<Rgb8>? band = null;
foreach (ReadOnlyMemory<byte> chunk in file.Chunk(4096).Select(c => new ReadOnlyMemory<byte>(c)))
{
    decoder.Append(chunk.Span);
    if (decoder.HeaderRead)
    {
        band ??= Raster.Create<Rgb8>(decoder.Info.Width, 16);
        int rows;
        while ((rows = decoder.ReadRows(band)) > 0)
        {
            rowsSoFar += rows; // the rows finished so far; an interlaced file shows RenderPreview instead
        }
    }
}

decoder.EndOfInput();
while (decoder.ReadRows(band!) is int rest and > 0)
{
    rowsSoFar += rest;
}

band?.Dispose();

Remarks

Every 1.x format decodes as its bytes arrive, keeping only the bytes since the step it is in (an MCU row, a PNG chunk or scanline, a GIF block, a row). Files that are decoded into a buffer first (PFM, plain-text PNM, bottom-up BMP and TGA, a 32-bit BMP whose alpha may not count, TGA alpha set by a footer) and formats from plugins wait for EndOfInput() and are then read whole.

ReadFrameAsync<TPixel>(Raster<TPixel>, CancellationToken)

Decodes the rest of the current frame into target, reading the stream asynchronously as far as it needs, and moves to the next frame. See ReadFrame<TPixel>(Raster<TPixel>).

public ValueTask<FrameMetadata?> ReadFrameAsync<TPixel>(Raster<TPixel> target, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixelType<TPixel>

Parameters

target Raster<TPixel>

Where to write: Info's size.

cancellationToken CancellationToken

Cancels reading.

Returns

ValueTask<FrameMetadata>

The frame's metadata, or null after the last frame.

Type Parameters

TPixel

The pixel type to produce.

Examples

// On a server: read rows as the upload arrives, without holding the whole file.
await using Stream upload = File.OpenRead("egret.png");
using RasterDecoder decoder = await RasterDecoder.OpenAsync(upload, cancellationToken: TestContext.Current.CancellationToken);
using Raster<Gray8> band = Raster.Create<Gray8>(decoder.Info.Width, 32);
int rows;
while ((rows = await decoder.ReadRowsAsync(band, TestContext.Current.CancellationToken)) > 0)
{
    for (int y = 0; y < rows; y++)
    {
        sum += band.Row(y)[0].L;
    }
}

Exceptions

InvalidOperationException

The decoder was not opened with OpenAsync(Stream, EgamiContext?, ReaderOptions?, CancellationToken).

ReadFrame<TPixel>()

Decodes the rest of the current frame into a new raster and moves to the next frame. See ReadFrame<TPixel>(Raster<TPixel>).

public Raster<TPixel>? ReadFrame<TPixel>() where TPixel : unmanaged, IPixelType<TPixel>

Returns

Raster<TPixel>

The frame, with a copy of Metadata, or null after the last frame. The caller must dispose it.

Type Parameters

TPixel

The pixel type to produce.

Exceptions

LimitExceededException

The raster would break MaxTotalBytes.

ObjectDisposedException

The decoder has been disposed.

ReadFrame<TPixel>(Raster<TPixel>)

Decodes the rest of the current frame into target, each row at its own place, and moves to the next frame. Animations come fully composed, the same pixels Load(Stream, EgamiContext?, ReaderOptions?) gives.

public FrameMetadata? ReadFrame<TPixel>(Raster<TPixel> target) where TPixel : unmanaged, IPixelType<TPixel>

Parameters

target Raster<TPixel>

Where to write: Info's size. Only its first frame is written.

Returns

FrameMetadata

The frame's metadata, or null after the last frame.

Type Parameters

TPixel

The pixel type to produce.

Examples

// An animation one composed frame at a time, into one reused canvas.
using RasterDecoder gif = RasterDecoder.Open("pan.gif");
using Raster<Rgba8> canvas = Raster.Create<Rgba8>(gif.Info.Width, gif.Info.Height);
while (gif.ReadFrame(canvas) is Metadata.FrameMetadata frame)
{
    Console.WriteLine($"frame {count++}, shown for {frame.Delay.TotalMilliseconds} ms");
}

Exceptions

ArgumentException

target is not Info's size.

ObjectDisposedException

The decoder has been disposed.

ReadRowsAsync<TPixel>(Raster<TPixel>, CancellationToken)

Fills band with the next rows of the current frame, reading the stream asynchronously as far as they need. See ReadRows<TPixel>(Raster<TPixel>).

public ValueTask<int> ReadRowsAsync<TPixel>(Raster<TPixel> band, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixelType<TPixel>

Parameters

band Raster<TPixel>

Where to write: Info's width, any height.

cancellationToken CancellationToken

Cancels reading.

Returns

ValueTask<int>

The rows written, at least one unless the frame has ended; 0 at the end of the frame.

Type Parameters

TPixel

The pixel type to produce.

Examples

// On a server: read rows as the upload arrives, without holding the whole file.
await using Stream upload = File.OpenRead("egret.png");
using RasterDecoder decoder = await RasterDecoder.OpenAsync(upload, cancellationToken: TestContext.Current.CancellationToken);
using Raster<Gray8> band = Raster.Create<Gray8>(decoder.Info.Width, 32);
int rows;
while ((rows = await decoder.ReadRowsAsync(band, TestContext.Current.CancellationToken)) > 0)
{
    for (int y = 0; y < rows; y++)
    {
        sum += band.Row(y)[0].L;
    }
}

Exceptions

InvalidOperationException

The decoder was not opened with OpenAsync(Stream, EgamiContext?, ReaderOptions?, CancellationToken).

ReadRows<TPixel>(Raster<TPixel>)

Fills band from its top row with the next rows of the current frame, converting while decoding.

public int ReadRows<TPixel>(Raster<TPixel> band) where TPixel : unmanaged, IPixelType<TPixel>

Parameters

band Raster<TPixel>

Where to write: Info's width, any height. Only its first frame is written.

Returns

int

The rows written; 0 at the end of the frame. Call NextFrame() to go on to the next frame.

Type Parameters

TPixel

The pixel type to produce; it may differ from call to call.

Exceptions

ArgumentException

band is not Info's width.

ObjectDisposedException

The decoder has been disposed.

RenderPreview<TPixel>(Raster<TPixel>)

Draws what is known of the frame in progress: an interlaced PNG from the passes received so far, a progressive JPEG from its scans so far, a progressive (layered) AVIF from its best layer received so far at full size, a JPEG XL still as libjxl's progressive flush draws it (its LF image upsampled, then each pass and group as it arrives), a GIF frame from the rows received so far (interlaced ones repeated over the rows they stand for), each later pass sharpening it. Region and reduction apply as to rows. The decode does not move on.

public bool RenderPreview<TPixel>(Raster<TPixel> target) where TPixel : unmanaged, IPixelType<TPixel>

Parameters

target Raster<TPixel>

Where to draw: Info's size. Only its first frame is written.

Returns

bool

Whether a preview was drawn; false before the header or for files that hand out finished rows instead.

Type Parameters

TPixel

The pixel type to draw in.

Examples

// Decode while the bytes arrive, a chunk at a time, as from a download.
using RasterDecoder decoder = RasterDecoder.OpenIncremental();
Raster<Rgb8>? band = null;
foreach (ReadOnlyMemory<byte> chunk in file.Chunk(4096).Select(c => new ReadOnlyMemory<byte>(c)))
{
    decoder.Append(chunk.Span);
    if (decoder.HeaderRead)
    {
        band ??= Raster.Create<Rgb8>(decoder.Info.Width, 16);
        int rows;
        while ((rows = decoder.ReadRows(band)) > 0)
        {
            rowsSoFar += rows; // the rows finished so far; an interlaced file shows RenderPreview instead
        }
    }
}

decoder.EndOfInput();
while (decoder.ReadRows(band!) is int rest and > 0)
{
    rowsSoFar += rest;
}

band?.Dispose();

Exceptions

ArgumentException

target is not Info's size.

ObjectDisposedException

The decoder has been disposed.

SkipRows(int)

Moves past rows of the current frame, decoding them only where the format requires it.

public int SkipRows(int count)

Parameters

count int

The rows to skip.

Returns

int

The rows skipped, fewer than count at the end of the frame.

Exceptions

ArgumentOutOfRangeException

count is negative.

ObjectDisposedException

The decoder has been disposed.