Этап 2
This commit is contained in:
9
Minint.Core/Minint.Core.csproj
Normal file
9
Minint.Core/Minint.Core.csproj
Normal file
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
35
Minint.Core/Models/MinintContainer.cs
Normal file
35
Minint.Core/Models/MinintContainer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
53
Minint.Core/Models/MinintDocument.cs
Normal file
53
Minint.Core/Models/MinintDocument.cs
Normal 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
|
||||
};
|
||||
}
|
||||
42
Minint.Core/Models/MinintLayer.cs
Normal file
42
Minint.Core/Models/MinintLayer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
37
Minint.Core/Models/RgbaColor.cs
Normal file
37
Minint.Core/Models/RgbaColor.cs
Normal 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}";
|
||||
}
|
||||
13
Minint.Core/Services/IBmpExporter.cs
Normal file
13
Minint.Core/Services/IBmpExporter.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Minint.Core.Services;
|
||||
|
||||
public interface IBmpExporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Exports a composited ARGB pixel buffer as a 32-bit BMP file.
|
||||
/// </summary>
|
||||
/// <param name="stream">Output stream.</param>
|
||||
/// <param name="pixels">Pixel data packed as 0xAARRGGBB, row-major.</param>
|
||||
/// <param name="width">Image width.</param>
|
||||
/// <param name="height">Image height.</param>
|
||||
void Export(Stream stream, uint[] pixels, int width, int height);
|
||||
}
|
||||
13
Minint.Core/Services/ICompositor.cs
Normal file
13
Minint.Core/Services/ICompositor.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Minint.Core.Models;
|
||||
|
||||
namespace Minint.Core.Services;
|
||||
|
||||
public interface ICompositor
|
||||
{
|
||||
/// <summary>
|
||||
/// Composites all visible layers of <paramref name="document"/> into a flat RGBA buffer.
|
||||
/// Result is packed as ARGB (0xAARRGGBB) per pixel, row-major, length = width * height.
|
||||
/// Layers are blended bottom-to-top with alpha compositing.
|
||||
/// </summary>
|
||||
uint[] Composite(MinintDocument document, int width, int height);
|
||||
}
|
||||
25
Minint.Core/Services/IDrawingService.cs
Normal file
25
Minint.Core/Services/IDrawingService.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Minint.Core.Models;
|
||||
|
||||
namespace Minint.Core.Services;
|
||||
|
||||
public interface IDrawingService
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies a circular brush stroke at (<paramref name="cx"/>, <paramref name="cy"/>)
|
||||
/// with the given <paramref name="radius"/>. Sets affected pixels to <paramref name="colorIndex"/>.
|
||||
/// </summary>
|
||||
void ApplyBrush(MinintLayer layer, int cx, int cy, int radius, int colorIndex, int width, int height);
|
||||
|
||||
/// <summary>
|
||||
/// Applies a circular eraser at (<paramref name="cx"/>, <paramref name="cy"/>)
|
||||
/// with the given <paramref name="radius"/>. Sets affected pixels to index 0 (transparent).
|
||||
/// </summary>
|
||||
void ApplyEraser(MinintLayer layer, int cx, int cy, int radius, int width, int height);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the set of pixel coordinates affected by a circular brush/eraser
|
||||
/// centered at (<paramref name="cx"/>, <paramref name="cy"/>) with given <paramref name="radius"/>.
|
||||
/// Used for tool preview overlay.
|
||||
/// </summary>
|
||||
List<(int X, int Y)> GetBrushMask(int cx, int cy, int radius, int width, int height);
|
||||
}
|
||||
12
Minint.Core/Services/IFloodFillService.cs
Normal file
12
Minint.Core/Services/IFloodFillService.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Minint.Core.Models;
|
||||
|
||||
namespace Minint.Core.Services;
|
||||
|
||||
public interface IFloodFillService
|
||||
{
|
||||
/// <summary>
|
||||
/// Flood-fills a contiguous region of identical color starting at (<paramref name="x"/>, <paramref name="y"/>)
|
||||
/// with <paramref name="newColorIndex"/>. Uses 4-connectivity (up/down/left/right).
|
||||
/// </summary>
|
||||
void Fill(MinintLayer layer, int x, int y, int newColorIndex, int width, int height);
|
||||
}
|
||||
29
Minint.Core/Services/IFragmentService.cs
Normal file
29
Minint.Core/Services/IFragmentService.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Minint.Core.Models;
|
||||
|
||||
namespace Minint.Core.Services;
|
||||
|
||||
public interface IFragmentService
|
||||
{
|
||||
/// <summary>
|
||||
/// Copies a rectangular region from one document/layer to another.
|
||||
/// Palette colors are merged: missing colors are added to the destination palette.
|
||||
/// </summary>
|
||||
/// <param name="srcDoc">Source document.</param>
|
||||
/// <param name="srcLayerIndex">Index of the source layer.</param>
|
||||
/// <param name="srcX">Source rectangle X origin.</param>
|
||||
/// <param name="srcY">Source rectangle Y origin.</param>
|
||||
/// <param name="regionWidth">Width of the region to copy.</param>
|
||||
/// <param name="regionHeight">Height of the region to copy.</param>
|
||||
/// <param name="dstDoc">Destination document.</param>
|
||||
/// <param name="dstLayerIndex">Index of the destination layer.</param>
|
||||
/// <param name="dstX">Destination X origin.</param>
|
||||
/// <param name="dstY">Destination Y origin.</param>
|
||||
/// <param name="containerWidth">Container width (shared by both docs).</param>
|
||||
/// <param name="containerHeight">Container height (shared by both docs).</param>
|
||||
void CopyFragment(
|
||||
MinintDocument srcDoc, int srcLayerIndex,
|
||||
int srcX, int srcY, int regionWidth, int regionHeight,
|
||||
MinintDocument dstDoc, int dstLayerIndex,
|
||||
int dstX, int dstY,
|
||||
int containerWidth, int containerHeight);
|
||||
}
|
||||
13
Minint.Core/Services/IGifExporter.cs
Normal file
13
Minint.Core/Services/IGifExporter.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Minint.Core.Services;
|
||||
|
||||
public interface IGifExporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Exports multiple frames as an animated GIF.
|
||||
/// </summary>
|
||||
/// <param name="stream">Output stream.</param>
|
||||
/// <param name="frames">Sequence of (ARGB pixels, delay in ms) per frame.</param>
|
||||
/// <param name="width">Frame width.</param>
|
||||
/// <param name="height">Frame height.</param>
|
||||
void Export(Stream stream, IReadOnlyList<(uint[] Pixels, uint DelayMs)> frames, int width, int height);
|
||||
}
|
||||
20
Minint.Core/Services/IImageEffectService.cs
Normal file
20
Minint.Core/Services/IImageEffectService.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Minint.Core.Models;
|
||||
|
||||
namespace Minint.Core.Services;
|
||||
|
||||
public interface IImageEffectService
|
||||
{
|
||||
/// <summary>
|
||||
/// Adjusts contrast of the document by modifying palette colors.
|
||||
/// <paramref name="factor"/> > 1 increases contrast, < 1 decreases.
|
||||
/// Index 0 (transparent) is not modified.
|
||||
/// </summary>
|
||||
void AdjustContrast(MinintDocument document, float factor);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the document to grayscale by modifying palette colors.
|
||||
/// Uses ITU-R BT.601 luminance: gray = 0.299R + 0.587G + 0.114B.
|
||||
/// Index 0 (transparent) is not modified.
|
||||
/// </summary>
|
||||
void ToGrayscale(MinintDocument document);
|
||||
}
|
||||
17
Minint.Core/Services/IMinintSerializer.cs
Normal file
17
Minint.Core/Services/IMinintSerializer.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Minint.Core.Models;
|
||||
|
||||
namespace Minint.Core.Services;
|
||||
|
||||
public interface IMinintSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Serializes the container to a binary .minint stream.
|
||||
/// </summary>
|
||||
void Write(Stream stream, MinintContainer container);
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes a .minint stream into a container.
|
||||
/// Throws <see cref="InvalidDataException"/> on format/validation errors.
|
||||
/// </summary>
|
||||
MinintContainer Read(Stream stream);
|
||||
}
|
||||
23
Minint.Core/Services/IPaletteService.cs
Normal file
23
Minint.Core/Services/IPaletteService.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Minint.Core.Models;
|
||||
|
||||
namespace Minint.Core.Services;
|
||||
|
||||
public interface IPaletteService
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the index of <paramref name="color"/> in the document palette.
|
||||
/// If the color is not present, appends it and returns the new index.
|
||||
/// </summary>
|
||||
int EnsureColor(MinintDocument document, RgbaColor color);
|
||||
|
||||
/// <summary>
|
||||
/// Finds index of an exact color match, or returns -1 if not found.
|
||||
/// </summary>
|
||||
int FindColor(MinintDocument document, RgbaColor color);
|
||||
|
||||
/// <summary>
|
||||
/// Removes unused colors from the palette and remaps all layer pixel indices.
|
||||
/// Index 0 (transparent) is always preserved.
|
||||
/// </summary>
|
||||
void CompactPalette(MinintDocument document);
|
||||
}
|
||||
28
Minint.Core/Services/IPatternGenerator.cs
Normal file
28
Minint.Core/Services/IPatternGenerator.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Minint.Core.Models;
|
||||
|
||||
namespace Minint.Core.Services;
|
||||
|
||||
public enum PatternType
|
||||
{
|
||||
Checkerboard,
|
||||
HorizontalGradient,
|
||||
VerticalGradient,
|
||||
HorizontalStripes,
|
||||
VerticalStripes,
|
||||
ConcentricCircles,
|
||||
Tile
|
||||
}
|
||||
|
||||
public interface IPatternGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a new document with a single layer filled with the specified pattern.
|
||||
/// </summary>
|
||||
/// <param name="type">Pattern type.</param>
|
||||
/// <param name="width">Image width in pixels.</param>
|
||||
/// <param name="height">Image height in pixels.</param>
|
||||
/// <param name="colors">Colors to use (interpretation depends on pattern type).</param>
|
||||
/// <param name="param1">Primary parameter: cell/stripe size, ring width, etc.</param>
|
||||
/// <param name="param2">Secondary parameter (optional, pattern-dependent).</param>
|
||||
MinintDocument Generate(PatternType type, int width, int height, RgbaColor[] colors, int param1, int param2 = 0);
|
||||
}
|
||||
Reference in New Issue
Block a user