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
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
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
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
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
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
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
dataReadOnlySpan<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
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
cancellationTokenCancellationTokenCancels reading.
Returns
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
streamStreamThe encoded image.
contextEgamiContextoptionsReaderOptions
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
streamis 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
streamStreamThe encoded image, read from its current position and left open.
formatFormatInfoThe format to read with. It need not be registered in the context.
contextEgamiContextoptionsReaderOptions
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
formathas 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
streamorformatis 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
dataReadOnlyMemory<byte>The encoded image.
contextEgamiContextoptionsReaderOptions
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
dataReadOnlyMemory<byte>The encoded image.
formatFormatInfoThe format to read with. It need not be registered in the context.
contextEgamiContextoptionsReaderOptions
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
formathas 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
formatis null.
Open(string, EgamiContext?, ReaderOptions?)
Opens an image file. See Open(Stream, EgamiContext?, ReaderOptions?).
public static RasterDecoder Open(string path, EgamiContext? context = null, ReaderOptions? options = null)
Parameters
pathstringThe file path.
contextEgamiContextoptionsReaderOptions
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
pathis 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
streamStreamThe encoded image, read from its current position.
contextEgamiContextoptionsReaderOptionscancellationTokenCancellationTokenCancels 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
streamis null.- OperationCanceledException
cancellationTokenwas 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
contextEgamiContextoptionsReaderOptions
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
targetRaster<TPixel>Where to write: Info's size.
cancellationTokenCancellationTokenCancels reading.
Returns
- ValueTask<FrameMetadata>
The frame's metadata, or null after the last frame.
Type Parameters
TPixelThe 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
TPixelThe 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
Returns
- FrameMetadata
The frame's metadata, or null after the last frame.
Type Parameters
TPixelThe 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
targetis 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
bandRaster<TPixel>Where to write: Info's width, any height.
cancellationTokenCancellationTokenCancels reading.
Returns
- ValueTask<int>
The rows written, at least one unless the frame has ended; 0 at the end of the frame.
Type Parameters
TPixelThe 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
Returns
- int
The rows written; 0 at the end of the frame. Call NextFrame() to go on to the next frame.
Type Parameters
TPixelThe pixel type to produce; it may differ from call to call.
Exceptions
- ArgumentException
bandis 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
Returns
- bool
Whether a preview was drawn; false before the header or for files that hand out finished rows instead.
Type Parameters
TPixelThe 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
targetis 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
countintThe rows to skip.
Returns
- int
The rows skipped, fewer than
countat the end of the frame.
Exceptions
- ArgumentOutOfRangeException
countis negative.- ObjectDisposedException
The decoder has been disposed.