Table of Contents

Interface IMultiImageWriter

Namespace
Egami.Formats
Assembly
Egami.dll

An optional interface for a format's writer whose files hold several images (TIFF pages, icon entries, DDS texture arrays and cube maps), for SaveAll(IEnumerable<Raster>, Stream, FormatInfo, WriterOptions?). Without it, SaveAll takes one raster and writes it as Save would.

public interface IMultiImageWriter

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

WriteAll(IReadOnlyList<Raster>, Stream, WriterOptions)

Writes every raster, in order, to one file.

void WriteAll(IReadOnlyList<Raster> rasters, Stream output, WriterOptions options)

Parameters

rasters IReadOnlyList<Raster>

The images, at least one.

output Stream

Where to write. The writer leaves it open.

options WriterOptions

How to write.

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