Interface IRowFormatReader
An optional interface for a format's reader: an open image that hands out rows as they are asked for, so RasterDecoder streams the format, with Region and MinimumSize applied on the way out. A reader without it still works everywhere: the decoder reads the whole image with Read(EgamiInput, ReaderOptions) and serves rows from it. Load, Probe and an incremental decode keep using IFormatReader.
public interface IRowFormatReader
Examples
/// <summary>The same RAW8 format, streaming: rows go in and out one at a time, so RasterDecoder, RasterEncoder and Transcode stream it.</summary>
public static class StreamingRaw8Format
{
public static FormatInfo Info { get; } = Raw8Format.Info with { Reader = new Reader(), Writer = new Writer() };
private sealed class Reader : IFormatReader, IRowFormatReader
{
private static readonly IFormatReader Whole = Raw8Format.Info.Reader!;
public RasterInfo ReadInfo(EgamiInput input, ReaderOptions options) => Whole.ReadInfo(input, options) with { Access = RasterAccess.Streamed };
public Raster<TPixel> Read<TPixel>(EgamiInput input, ReaderOptions options)
where TPixel : unmanaged, IPixelType<TPixel> => Whole.Read<TPixel>(input, options);
public Raster Read(EgamiInput input, ReaderOptions options) => Whole.Read(input, options);
// The open image reads each row when it is asked for; the decoder crops, reduces and converts after it.
public RowReader Open(EgamiInput input, ReaderOptions options)
{
input.Skip(4);
int width = (int)input.ReadUInt32BigEndian();
int height = (int)input.ReadUInt32BigEndian();
// No raster checks the size here, so check it before the row buffer is allocated.
input.CheckFrameSize(width, height);
return new Rows(input, width, height);
}
}
private sealed class Rows(EgamiInput input, int width, int height) : RowReader<Rgba8>
{
private readonly Rgba8[] _row = new Rgba8[width];
private int _next;
private bool _started;
public override int Width => width;
public override int Height => height;
public override Metadata.RasterMetadata Metadata { get; } = new();
public override Metadata.FrameMetadata? NextFrame()
{
if (_started)
{
return null;
}
_started = true;
return new Metadata.FrameMetadata();
}
public override void ReadRow<TPixel>(int y, int x, Span<TPixel> destination)
{
for (; _next <= y; _next++)
{
input.ReadExactly(MemoryMarshal.AsBytes(_row.AsSpan()));
}
PixelConvert.Convert<Rgba8, TPixel>(_row.AsSpan(x, destination.Length), destination);
}
}
private sealed class Writer : IFormatWriter, IRowFormatWriter
{
public void Write<TPixel>(Raster<TPixel> raster, Stream output, WriterOptions options)
where TPixel : unmanaged, IPixelType<TPixel> => Raw8Format.Info.Writer!.Write(raster, output, options);
// Save, SaveAsync, RasterEncoder and Transcode all write through the encoder.
public RowEncoder<TPixel> Start<TPixel>(Stream output, EncodeStart<TPixel> start)
where TPixel : unmanaged, IPixelType<TPixel>
{
// RAW8 keeps 8-bit RGBA and nothing else: deeper samples or metadata are reported as lost.
start.ReportLosses("RAW8", SaveKeeps.Alpha);
return new Encoder<TPixel>(output, start.Width, start.Height);
}
}
private sealed class Encoder<TPixel> : RowEncoder<TPixel>
where TPixel : unmanaged, IPixelType<TPixel>
{
private readonly Stream _output;
private readonly Rgba8[] _row;
public Encoder(Stream output, int width, int height)
{
_output = output;
_row = new Rgba8[width];
Span<byte> header = stackalloc byte[12];
"RAW8"u8.CopyTo(header);
BinaryPrimitives.WriteUInt32BigEndian(header[4..], (uint)width);
BinaryPrimitives.WriteUInt32BigEndian(header[8..], (uint)height);
output.Write(header);
}
public override void WriteRow(ReadOnlySpan<TPixel> row)
{
PixelConvert.Convert<TPixel, Rgba8>(row, _row);
_output.Write(MemoryMarshal.AsBytes(_row.AsSpan()));
}
public override void Complete()
{
}
}
}
Methods
Open(EgamiInput, ReaderOptions)
Reads the header and returns the open image, which reads its rows from input; the decoder keeps the input open until it disposes the image.
RowReader Open(EgamiInput input, ReaderOptions options)
Parameters
inputEgamiInputThe data, positioned at the start of the image.
optionsReaderOptionsHow to read. Region and MinimumSize are applied by the decoder; a reader may ignore them.
Returns
- RowReader
The open image.
Examples
/// <summary>The same RAW8 format, streaming: rows go in and out one at a time, so RasterDecoder, RasterEncoder and Transcode stream it.</summary>
public static class StreamingRaw8Format
{
public static FormatInfo Info { get; } = Raw8Format.Info with { Reader = new Reader(), Writer = new Writer() };
private sealed class Reader : IFormatReader, IRowFormatReader
{
private static readonly IFormatReader Whole = Raw8Format.Info.Reader!;
public RasterInfo ReadInfo(EgamiInput input, ReaderOptions options) => Whole.ReadInfo(input, options) with { Access = RasterAccess.Streamed };
public Raster<TPixel> Read<TPixel>(EgamiInput input, ReaderOptions options)
where TPixel : unmanaged, IPixelType<TPixel> => Whole.Read<TPixel>(input, options);
public Raster Read(EgamiInput input, ReaderOptions options) => Whole.Read(input, options);
// The open image reads each row when it is asked for; the decoder crops, reduces and converts after it.
public RowReader Open(EgamiInput input, ReaderOptions options)
{
input.Skip(4);
int width = (int)input.ReadUInt32BigEndian();
int height = (int)input.ReadUInt32BigEndian();
// No raster checks the size here, so check it before the row buffer is allocated.
input.CheckFrameSize(width, height);
return new Rows(input, width, height);
}
}
private sealed class Rows(EgamiInput input, int width, int height) : RowReader<Rgba8>
{
private readonly Rgba8[] _row = new Rgba8[width];
private int _next;
private bool _started;
public override int Width => width;
public override int Height => height;
public override Metadata.RasterMetadata Metadata { get; } = new();
public override Metadata.FrameMetadata? NextFrame()
{
if (_started)
{
return null;
}
_started = true;
return new Metadata.FrameMetadata();
}
public override void ReadRow<TPixel>(int y, int x, Span<TPixel> destination)
{
for (; _next <= y; _next++)
{
input.ReadExactly(MemoryMarshal.AsBytes(_row.AsSpan()));
}
PixelConvert.Convert<Rgba8, TPixel>(_row.AsSpan(x, destination.Length), destination);
}
}
private sealed class Writer : IFormatWriter, IRowFormatWriter
{
public void Write<TPixel>(Raster<TPixel> raster, Stream output, WriterOptions options)
where TPixel : unmanaged, IPixelType<TPixel> => Raw8Format.Info.Writer!.Write(raster, output, options);
// Save, SaveAsync, RasterEncoder and Transcode all write through the encoder.
public RowEncoder<TPixel> Start<TPixel>(Stream output, EncodeStart<TPixel> start)
where TPixel : unmanaged, IPixelType<TPixel>
{
// RAW8 keeps 8-bit RGBA and nothing else: deeper samples or metadata are reported as lost.
start.ReportLosses("RAW8", SaveKeeps.Alpha);
return new Encoder<TPixel>(output, start.Width, start.Height);
}
}
private sealed class Encoder<TPixel> : RowEncoder<TPixel>
where TPixel : unmanaged, IPixelType<TPixel>
{
private readonly Stream _output;
private readonly Rgba8[] _row;
public Encoder(Stream output, int width, int height)
{
_output = output;
_row = new Rgba8[width];
Span<byte> header = stackalloc byte[12];
"RAW8"u8.CopyTo(header);
BinaryPrimitives.WriteUInt32BigEndian(header[4..], (uint)width);
BinaryPrimitives.WriteUInt32BigEndian(header[8..], (uint)height);
output.Write(header);
}
public override void WriteRow(ReadOnlySpan<TPixel> row)
{
PixelConvert.Convert<TPixel, Rgba8>(row, _row);
_output.Write(MemoryMarshal.AsBytes(_row.AsSpan()));
}
public override void Complete()
{
}
}
}