Files
Minint/Minint.Core/Models/MinintDocument.cs
2026-03-29 16:05:45 +03:00

105 lines
3.3 KiB
C#

namespace Minint.Core.Models;
/// <summary>
/// A single document (frame) within a container.
/// Has its own palette shared by all layers, plus a list of layers.
/// </summary>
public sealed class MinintDocument
{
public string Name { get; set; }
/// <summary>
/// Delay before showing the next frame during animation playback (ms).
/// </summary>
public uint FrameDelayMs { get; set; }
/// <summary>
/// Document palette. Index 0 is always <see cref="RgbaColor.Transparent"/>.
/// All layers reference colors by index into this list.
/// </summary>
public List<RgbaColor> Palette { get; }
public List<MinintLayer> Layers { get; }
/// <summary>
/// Reverse lookup cache: RgbaColor → palette index. Built lazily, invalidated
/// on structural palette changes (compact, clear). Call <see cref="InvalidatePaletteCache"/>
/// after bulk palette modifications.
/// </summary>
private Dictionary<RgbaColor, int>? _paletteCache;
public MinintDocument(string name)
{
Name = name;
FrameDelayMs = 100;
Palette = [RgbaColor.Transparent];
Layers = [];
}
/// <summary>
/// Constructor for deserialization — accepts pre-built palette and layers.
/// </summary>
public MinintDocument(string name, uint frameDelayMs, List<RgbaColor> palette, List<MinintLayer> layers)
{
Name = name;
FrameDelayMs = frameDelayMs;
Palette = palette;
Layers = layers;
}
/// <summary>
/// Returns the number of bytes needed to store a single palette index on disk.
/// </summary>
public int IndexByteWidth => Palette.Count switch
{
<= 255 => 1,
<= 65_535 => 2,
<= 16_777_215 => 3,
_ => 4
};
/// <summary>
/// O(1) lookup of a color in the palette. Returns the index, or -1 if not found.
/// Lazily builds an internal dictionary on first call.
/// </summary>
public int FindColorCached(RgbaColor color)
{
var cache = EnsurePaletteCache();
return cache.TryGetValue(color, out int idx) ? idx : -1;
}
/// <summary>
/// Returns the index of <paramref name="color"/>. If absent, appends it to the palette
/// and updates the cache. O(1) amortized.
/// </summary>
public int EnsureColorCached(RgbaColor color)
{
var cache = EnsurePaletteCache();
if (cache.TryGetValue(color, out int idx))
return idx;
idx = Palette.Count;
Palette.Add(color);
cache[color] = idx;
return idx;
}
/// <summary>
/// Drops the reverse lookup cache. Must be called after any operation that
/// reorders, removes, or bulk-replaces palette entries (e.g. compact, grayscale).
/// </summary>
public void InvalidatePaletteCache() => _paletteCache = null;
private Dictionary<RgbaColor, int> EnsurePaletteCache()
{
if (_paletteCache is not null)
return _paletteCache;
var cache = new Dictionary<RgbaColor, int>(Palette.Count);
for (int i = 0; i < Palette.Count; i++)
cache.TryAdd(Palette[i], i); // first occurrence wins (for dupes)
_paletteCache = cache;
return cache;
}
}