Этап 5

This commit is contained in:
2026-03-29 16:05:45 +03:00
parent 5fdaaaa2bf
commit 400af7982e
8 changed files with 474 additions and 34 deletions

View File

@@ -21,6 +21,13 @@ public sealed class MinintDocument
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;
@@ -50,4 +57,48 @@ public sealed class MinintDocument
<= 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;
}
}

View File

@@ -5,26 +5,10 @@ namespace Minint.Core.Services.Impl;
public sealed class PaletteService : IPaletteService
{
public int FindColor(MinintDocument document, RgbaColor color)
{
var palette = document.Palette;
for (int i = 0; i < palette.Count; i++)
{
if (palette[i] == color)
return i;
}
return -1;
}
=> document.FindColorCached(color);
public int EnsureColor(MinintDocument document, RgbaColor color)
{
int idx = FindColor(document, color);
if (idx >= 0)
return idx;
idx = document.Palette.Count;
document.Palette.Add(color);
return idx;
}
=> document.EnsureColorCached(color);
public void CompactPalette(MinintDocument document)
{
@@ -32,19 +16,16 @@ public sealed class PaletteService : IPaletteService
if (palette.Count <= 1)
return;
// 1. Collect indices actually used across all layers
var usedIndices = new HashSet<int> { 0 }; // always keep transparent
var usedIndices = new HashSet<int> { 0 };
foreach (var layer in document.Layers)
{
foreach (int idx in layer.Pixels)
usedIndices.Add(idx);
}
// 2. Build new palette and old→new mapping
var oldToNew = new int[palette.Count];
var newPalette = new List<RgbaColor>(usedIndices.Count);
// Index 0 (transparent) stays at 0
newPalette.Add(palette[0]);
oldToNew[0] = 0;
@@ -55,23 +36,21 @@ public sealed class PaletteService : IPaletteService
oldToNew[i] = newPalette.Count;
newPalette.Add(palette[i]);
}
// unused indices don't get a mapping — they'll never be looked up
}
// 3. If nothing was removed, skip the remap
if (newPalette.Count == palette.Count)
return;
// 4. Replace palette
palette.Clear();
palette.AddRange(newPalette);
// 5. Remap all pixel arrays
foreach (var layer in document.Layers)
{
var px = layer.Pixels;
for (int i = 0; i < px.Length; i++)
px[i] = oldToNew[px[i]];
}
document.InvalidatePaletteCache();
}
}