Этап 4
This commit is contained in:
73
Minint.Core/Services/Impl/Compositor.cs
Normal file
73
Minint.Core/Services/Impl/Compositor.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using Minint.Core.Models;
|
||||
|
||||
namespace Minint.Core.Services.Impl;
|
||||
|
||||
public sealed class Compositor : ICompositor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public uint[] Composite(MinintDocument document, int width, int height)
|
||||
{
|
||||
int pixelCount = width * height;
|
||||
var result = new uint[pixelCount]; // starts as 0x00000000 (transparent black)
|
||||
|
||||
var palette = document.Palette;
|
||||
|
||||
foreach (var layer in document.Layers)
|
||||
{
|
||||
if (!layer.IsVisible)
|
||||
continue;
|
||||
|
||||
byte layerOpacity = layer.Opacity;
|
||||
if (layerOpacity == 0)
|
||||
continue;
|
||||
|
||||
var pixels = layer.Pixels;
|
||||
for (int i = 0; i < pixelCount; i++)
|
||||
{
|
||||
int idx = pixels[i];
|
||||
if (idx == 0)
|
||||
continue; // transparent — skip
|
||||
|
||||
var src = palette[idx];
|
||||
|
||||
// Effective source alpha = palette alpha * layer opacity / 255
|
||||
int srcA = src.A * layerOpacity / 255;
|
||||
if (srcA == 0)
|
||||
continue;
|
||||
|
||||
if (srcA == 255)
|
||||
{
|
||||
// Fully opaque — fast path, no blending needed
|
||||
result[i] = PackArgb(src.R, src.G, src.B, 255);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Standard "over" alpha compositing
|
||||
uint dst = result[i];
|
||||
int dstA = (int)(dst >> 24);
|
||||
int dstR = (int)((dst >> 16) & 0xFF);
|
||||
int dstG = (int)((dst >> 8) & 0xFF);
|
||||
int dstB = (int)(dst & 0xFF);
|
||||
|
||||
int outA = srcA + dstA * (255 - srcA) / 255;
|
||||
if (outA == 0)
|
||||
continue;
|
||||
|
||||
int outR = (src.R * srcA + dstR * dstA * (255 - srcA) / 255) / outA;
|
||||
int outG = (src.G * srcA + dstG * dstA * (255 - srcA) / 255) / outA;
|
||||
int outB = (src.B * srcA + dstB * dstA * (255 - srcA) / 255) / outA;
|
||||
|
||||
result[i] = PackArgb(
|
||||
(byte)Math.Min(outR, 255),
|
||||
(byte)Math.Min(outG, 255),
|
||||
(byte)Math.Min(outB, 255),
|
||||
(byte)Math.Min(outA, 255));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static uint PackArgb(byte r, byte g, byte b, byte a) =>
|
||||
(uint)(b | (g << 8) | (r << 16) | (a << 24));
|
||||
}
|
||||
77
Minint.Core/Services/Impl/PaletteService.cs
Normal file
77
Minint.Core/Services/Impl/PaletteService.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using Minint.Core.Models;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public void CompactPalette(MinintDocument document)
|
||||
{
|
||||
var palette = document.Palette;
|
||||
if (palette.Count <= 1)
|
||||
return;
|
||||
|
||||
// 1. Collect indices actually used across all layers
|
||||
var usedIndices = new HashSet<int> { 0 }; // always keep transparent
|
||||
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;
|
||||
|
||||
for (int i = 1; i < palette.Count; i++)
|
||||
{
|
||||
if (usedIndices.Contains(i))
|
||||
{
|
||||
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]];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user