Этап 2

This commit is contained in:
2026-03-29 15:34:33 +03:00
parent 09d52aa973
commit 08b9039b58
19 changed files with 872 additions and 3 deletions

View File

@@ -0,0 +1,35 @@
namespace Minint.Core.Models;
/// <summary>
/// Top-level container: holds dimensions shared by all documents/layers,
/// and a list of documents (frames).
/// </summary>
public sealed class MinintContainer
{
public int Width { get; set; }
public int Height { get; set; }
public List<MinintDocument> Documents { get; }
public int PixelCount => Width * Height;
public MinintContainer(int width, int height)
{
ArgumentOutOfRangeException.ThrowIfLessThan(width, 1);
ArgumentOutOfRangeException.ThrowIfLessThan(height, 1);
Width = width;
Height = height;
Documents = [];
}
/// <summary>
/// Creates a new document with a single transparent layer and adds it to the container.
/// </summary>
public MinintDocument AddNewDocument(string name)
{
var doc = new MinintDocument(name);
doc.Layers.Add(new MinintLayer("Layer 1", PixelCount));
Documents.Add(doc);
return doc;
}
}

View File

@@ -0,0 +1,53 @@
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; }
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
};
}

View File

@@ -0,0 +1,42 @@
namespace Minint.Core.Models;
/// <summary>
/// A single raster layer. Pixels are indices into the parent document's palette.
/// Array layout is row-major: Pixels[y * width + x].
/// </summary>
public sealed class MinintLayer
{
public string Name { get; set; }
public bool IsVisible { get; set; }
/// <summary>
/// Per-layer opacity (0 = fully transparent, 255 = fully opaque).
/// Used during compositing: effective alpha = paletteColor.A * Opacity / 255.
/// </summary>
public byte Opacity { get; set; }
/// <summary>
/// Palette indices, length must equal container Width * Height.
/// Index 0 = transparent by convention.
/// </summary>
public int[] Pixels { get; }
public MinintLayer(string name, int pixelCount)
{
Name = name;
IsVisible = true;
Opacity = 255;
Pixels = new int[pixelCount];
}
/// <summary>
/// Constructor for deserialization — accepts a pre-filled pixel buffer.
/// </summary>
public MinintLayer(string name, bool isVisible, byte opacity, int[] pixels)
{
Name = name;
IsVisible = isVisible;
Opacity = opacity;
Pixels = pixels;
}
}

View File

@@ -0,0 +1,37 @@
using System.Runtime.InteropServices;
namespace Minint.Core.Models;
/// <summary>
/// 4-byte RGBA color value. Equality is component-wise.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly record struct RgbaColor(byte R, byte G, byte B, byte A)
{
public static readonly RgbaColor Transparent = new(0, 0, 0, 0);
public static readonly RgbaColor Black = new(0, 0, 0, 255);
public static readonly RgbaColor White = new(255, 255, 255, 255);
/// <summary>
/// Packs color into a single uint as 0xAABBGGRR (little-endian RGBA).
/// Suitable for writing directly into BGRA bitmap buffers after byte-swap,
/// or for use as a dictionary key.
/// </summary>
public uint ToPackedRgba() =>
(uint)(R | (G << 8) | (B << 16) | (A << 24));
public static RgbaColor FromPackedRgba(uint packed) =>
new(
(byte)(packed & 0xFF),
(byte)((packed >> 8) & 0xFF),
(byte)((packed >> 16) & 0xFF),
(byte)((packed >> 24) & 0xFF));
/// <summary>
/// Packs as 0xAARRGGBB — used for Avalonia/SkiaSharp pixel buffers.
/// </summary>
public uint ToPackedArgb() =>
(uint)(B | (G << 8) | (R << 16) | (A << 24));
public override string ToString() => $"#{R:X2}{G:X2}{B:X2}{A:X2}";
}