105 lines
3.3 KiB
C#
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;
|
|
}
|
|
}
|