Interface IMultiImageReader
An optional interface for a format's reader whose files hold several images (TIFF pages, icon entries, DDS surfaces). LoadAll(Stream, EgamiContext?, ReaderOptions?) asks it for one options value per image and loads each as Load would, so every Load feature applies to each image. The options a reader returns name the image in a way its own IFormatReader understands, usually a ReaderOptions subclass of the format's own.
public interface IMultiImageReader
Examples
/// <summary>A format holding several RAW8 images: "RAW8P", the image count as one byte, then each image as RAW8 stores it.</summary>
public static class Raw8PagesFormat
{
public static FormatInfo Info { get; } = new()
{
Name = "RAW8P",
Extensions = ["raw8p"],
SignatureLength = 5,
Matches = header => header.StartsWith("RAW8P"u8),
Reader = new Reader(),
};
/// <summary>Names the image a Load reads; LoadAll sets it for each.</summary>
public sealed record PageOptions : ReaderOptions
{
public int Page { get; init; }
}
private sealed class Reader : IFormatReader, IMultiImageReader
{
public IReadOnlyList<ReaderOptions> Images(EgamiInput input, ReaderOptions options)
{
input.Skip(5);
int count = input.ReadByte();
return [.. Enumerable.Range(0, count).Select(page => new PageOptions { Recovery = options.Recovery, Page = page })];
}
public RasterInfo ReadInfo(EgamiInput input, ReaderOptions options)
{
(int width, int height, int count) = Seek(input, options);
return new RasterInfo { Format = Info, Width = width, Height = height, Layout = Rgba8.Layout, FrameCount = 1, ImageCount = count, EstimatedBytes = 4L * width * height };
}
public Raster<TPixel> Read<TPixel>(EgamiInput input, ReaderOptions options)
where TPixel : unmanaged, IPixelType<TPixel>
{
(int width, int height, _) = Seek(input, options);
Raster<TPixel> raster = input.CreateRaster<TPixel>(width, height);
byte[] row = new byte[width * 4];
for (int y = 0; y < height; y++)
{
input.ReadExactly(row);
PixelConvert.Convert<Rgba8, TPixel>(MemoryMarshal.Cast<byte, Rgba8>(row.AsSpan()), raster.Row(y));
}
return raster;
}
public Raster Read(EgamiInput input, ReaderOptions options) => Read<Rgba8>(input, options);
/// <summary>Skips to the page the options name and reads its size.</summary>
private static (int Width, int Height, int Count) Seek(EgamiInput input, ReaderOptions options)
{
input.Skip(5);
int count = input.ReadByte();
int page = (options as PageOptions)?.Page ?? 0;
for (int i = 0; ; i++)
{
input.Skip(4);
int width = (int)input.ReadUInt32BigEndian();
int height = (int)input.ReadUInt32BigEndian();
if (i == page)
{
return (width, height, count);
}
input.Skip(4L * width * height);
}
}
}
}
Methods
Images(EgamiInput, ReaderOptions)
Returns, from the file's structure alone, the options that load each image, in the file's order.
IReadOnlyList<ReaderOptions> Images(EgamiInput input, ReaderOptions options)
Parameters
inputEgamiInputThe data, positioned at the start of the file.
optionsReaderOptionsThe caller's options.
Returns
- IReadOnlyList<ReaderOptions>
One options value per image; Load with each gives that image.
Examples
/// <summary>A format holding several RAW8 images: "RAW8P", the image count as one byte, then each image as RAW8 stores it.</summary>
public static class Raw8PagesFormat
{
public static FormatInfo Info { get; } = new()
{
Name = "RAW8P",
Extensions = ["raw8p"],
SignatureLength = 5,
Matches = header => header.StartsWith("RAW8P"u8),
Reader = new Reader(),
};
/// <summary>Names the image a Load reads; LoadAll sets it for each.</summary>
public sealed record PageOptions : ReaderOptions
{
public int Page { get; init; }
}
private sealed class Reader : IFormatReader, IMultiImageReader
{
public IReadOnlyList<ReaderOptions> Images(EgamiInput input, ReaderOptions options)
{
input.Skip(5);
int count = input.ReadByte();
return [.. Enumerable.Range(0, count).Select(page => new PageOptions { Recovery = options.Recovery, Page = page })];
}
public RasterInfo ReadInfo(EgamiInput input, ReaderOptions options)
{
(int width, int height, int count) = Seek(input, options);
return new RasterInfo { Format = Info, Width = width, Height = height, Layout = Rgba8.Layout, FrameCount = 1, ImageCount = count, EstimatedBytes = 4L * width * height };
}
public Raster<TPixel> Read<TPixel>(EgamiInput input, ReaderOptions options)
where TPixel : unmanaged, IPixelType<TPixel>
{
(int width, int height, _) = Seek(input, options);
Raster<TPixel> raster = input.CreateRaster<TPixel>(width, height);
byte[] row = new byte[width * 4];
for (int y = 0; y < height; y++)
{
input.ReadExactly(row);
PixelConvert.Convert<Rgba8, TPixel>(MemoryMarshal.Cast<byte, Rgba8>(row.AsSpan()), raster.Row(y));
}
return raster;
}
public Raster Read(EgamiInput input, ReaderOptions options) => Read<Rgba8>(input, options);
/// <summary>Skips to the page the options name and reads its size.</summary>
private static (int Width, int Height, int Count) Seek(EgamiInput input, ReaderOptions options)
{
input.Skip(5);
int count = input.ReadByte();
int page = (options as PageOptions)?.Page ?? 0;
for (int i = 0; ; i++)
{
input.Skip(4);
int width = (int)input.ReadUInt32BigEndian();
int height = (int)input.ReadUInt32BigEndian();
if (i == page)
{
return (width, height, count);
}
input.Skip(4L * width * height);
}
}
}
}