Ver código fonte

Added points list, distribute points node, remove close points node and rasterize points node

CPKreuz 1 ano atrás
pai
commit
fade66d7fc

+ 167 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Points/DistributePointsNode.cs

@@ -0,0 +1,167 @@
+using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.DrawingApi.Core;
+using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
+using PixiEditor.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Points;
+
+[NodeInfo("DistributePoints")]
+public class DistributePointsNode : Node
+{
+    private readonly Paint averageGrayscalePaint = new()
+    {
+        ColorFilter = Filters.AverageGrayscaleFilter
+    };
+    
+    public override string DisplayName { get; set; } = "DISTRIBUTE_POINTS";
+
+    public OutputProperty<PointList> Points { get; }
+
+    public InputProperty<Surface> Probability { get; }
+
+    public InputProperty<int> MaxPointCount { get; }
+
+    public InputProperty<bool> IntegerOnly { get; }
+
+    public InputProperty<int> Seed { get; }
+
+    public DistributePointsNode()
+    {
+        Points = CreateOutput(nameof(Points), "POINTS", PointList.Empty);
+
+        Probability = CreateInput<Surface>("Probability", "PROBABILITY", null);
+        MaxPointCount = CreateInput("MaxPointCount", "MAX_POINTS", 10);
+        IntegerOnly = CreateInput("IntegerOnly", "INTEGER_ONLY", false);
+        Seed = CreateInput("Seed", "SEED", 0);
+    }
+
+    protected override Surface? OnExecute(RenderingContext context)
+    {
+        Points.Value = Probability.Value switch
+        {
+            { } prop => GetPointsByProbability(prop),
+            _ => GetPointsRandomly()
+        };
+        
+        return null;
+    }
+
+    private PointList GetPointsRandomly()
+    {
+        var random = new Random(Seed.Value);
+        var pointCount = MaxPointCount.Value;
+        var finalPoints = new PointList(pointCount);
+
+        for (int i = 0; i < pointCount; i++)
+        {
+            finalPoints.Add(new VecD(random.NextDouble(), random.NextDouble()));
+        }
+        
+        return finalPoints;
+    }
+
+    private PointList GetPointsByProbability(Surface probability)
+    {
+        var size = probability.Size;
+        using var probabilityImage = Surface.UsingColorType(size, ColorType.Gray8);
+        probabilityImage.DrawingSurface.Canvas.DrawSurface(probability.DrawingSurface, 0, 0, averageGrayscalePaint);
+
+        using var pixmap = probabilityImage.PeekPixels();
+
+        var random = new Random(Seed.Value);
+        var pixels = pixmap.GetPixelSpan<byte>();
+        
+        var rowSumCache = new int[size.Y];
+        var rowColCache = new int[size.X];
+        Array.Fill(rowSumCache, -1);
+        
+        var pointCount = MaxPointCount.Value;
+        var finalPoints = new PointList(pointCount);
+        
+        for (int i = 0; i < pointCount; i++)
+        {
+            var xColRandom = random.Next(size.Y);
+            var columnSum = GetColumnSum(xColRandom, size, pixels, rowColCache);
+            
+            var yRowRandom = random.Next(size.Y);
+            var row = pixels.Slice(yRowRandom * size.X, size.X);
+            var rowSum = GetRowSum(yRowRandom, row, rowSumCache);
+
+            var xRandom = random.Next(rowSum);
+            var yRandom = random.Next(columnSum);
+            
+            int counted = 0;
+            int finalX = GetFinalPosition(row.Length, xRandom, row, (s, j) => s[j]);
+            int finalY = GetFinalPosition(size.Y, yRandom, pixels, (s, j) => s[j * size.X + xColRandom]);
+
+            if (finalX == -1 || finalY == -1)
+            {
+                continue;
+            }
+            
+            finalPoints.Add(new VecD((double)finalX / size.X, (double)finalY / size.Y));
+        }
+
+        return finalPoints;
+
+        static int GetFinalPosition(int size, int random, Span<byte> pixels, SpanAccessor accessor)
+        {
+            int counted = 0;
+            int final;
+            for (final = 0; final < size; final++)
+            {
+                counted += accessor(pixels, final);
+
+                if (counted > random)
+                {
+                    //finalPoints.Add(new VecD((double)j / size.X, (double)yRowRandom / size.Y));
+                    return final;
+                }
+            }
+
+            return -1;
+        }
+        
+    }
+    
+    delegate byte SpanAccessor(Span<byte> span, int index);
+
+    private static int GetColumnSum(int x, VecI size, Span<byte> pixels, int[] sumCache)
+    {
+        int sum = sumCache[x];
+
+        if (sum == -1)
+        {
+            sum = 0;
+            for (int y = 0; y < size.Y; y++)
+            {
+                sum += pixels[size.X * y];
+            }
+
+            sumCache[x] = sum;
+        }
+
+        return sum;
+    }
+
+    private static int GetRowSum(int y, ReadOnlySpan<byte> row, int[] sumCache)
+    {
+        var sum = sumCache[y];
+
+        if (sum == -1)
+        {
+            sum = 0;
+            foreach (var value in row)
+            {
+                sum += value;
+            }
+
+            sumCache[y] = sum;
+        }
+
+        return sum;
+    }
+    
+    public override Node CreateCopy() => new DistributePointsNode();
+}

+ 20 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Points/PointList.cs

@@ -0,0 +1,20 @@
+using PixiEditor.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Points;
+
+public class PointList : List<VecD>
+{
+    public PointList()
+    {
+    }
+
+    public PointList(IEnumerable<VecD> collection) : base(collection)
+    {
+    }
+
+    public PointList(int capacity) : base(capacity)
+    {
+    }
+
+    public static PointList Empty { get; } = new(0);
+}

+ 51 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Points/RasterizePoints.cs

@@ -0,0 +1,51 @@
+using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.DrawingApi.Core;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
+using PixiEditor.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Points;
+
+[NodeInfo("RasterizePoints")]
+public class RasterizePointsNode : Node
+{
+    private Paint _paint = new();
+
+    public override string DisplayName { get; set; } = "RASTERIZE_POINTS";
+    
+    public OutputProperty<Surface> Image { get; }
+
+    public InputProperty<PointList> Points { get; }
+
+    public InputProperty<Color> Color { get; }
+
+    public RasterizePointsNode()
+    {
+        Image = CreateOutput<Surface>("Image", "IMAGE", null);
+        Points = CreateInput("Points", "POINTS", PointList.Empty);
+        Color = CreateInput("Color", "COLOR", Colors.Black);
+    }
+
+    protected override Surface? OnExecute(RenderingContext context)
+    {
+        var points = Points.Value;
+
+        if (points.Count == 0)
+            return null;
+
+        var size = context.DocumentSize;
+        var image = new Surface(size);
+
+        _paint.Color = Color.Value;
+        foreach (var point in points)
+        {
+            image.DrawingSurface.Canvas.DrawPixel((VecI)point.Multiply(size), _paint);
+        }
+
+        Image.Value = image;
+        
+        return image;
+    }
+
+    public override Node CreateCopy() => new RasterizePointsNode();
+}

+ 74 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Points/RemoveClosePointsNode.cs

@@ -0,0 +1,74 @@
+using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.DrawingApi.Core;
+using PixiEditor.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Points;
+
+[NodeInfo("RemoveClosePoints")]
+public class RemoveClosePointsNode : Node
+{
+    public override string DisplayName { get; set; } = "REMOVE_CLOSE_POINTS";
+    
+    public OutputProperty<PointList> Output { get; }
+    
+    public InputProperty<PointList> Input { get; }
+    
+    public InputProperty<double> MinDistance { get; }
+
+    public InputProperty<int> Seed { get; }
+
+    public RemoveClosePointsNode()
+    {
+        Output = CreateOutput("Output", "POINTS", PointList.Empty);
+        Input = CreateInput("Input", "POINTS", PointList.Empty);
+        MinDistance = CreateInput("MinDistance", "MIN_DISTANCE", 0d);
+        Seed = CreateInput("Seed", "SEED", 0);
+    }
+    
+    protected override Surface? OnExecute(RenderingContext context)
+    {
+        var distance = MinDistance.Value;
+
+        if (distance == 0)
+        {
+            Output.Value = Input.Value;
+            return null;
+        }
+
+        var availablePoints = new PointList(Input.Value).Distinct().ToList();
+        var newPoints = new PointList(availablePoints.Count);
+
+        var minDistance = MinDistance.Value;
+        var documentSize = context.DocumentSize;
+
+        var random = new Random(Seed.Value);
+        while (availablePoints.Count > 1)
+        {
+            var index = random.Next(availablePoints.Count);
+            var point = availablePoints[index];
+
+            newPoints.Add(point);
+            availablePoints.RemoveAt(index);
+
+            foreach (var remove in availablePoints.Where(InRange).ToList())
+            {
+                availablePoints.Remove(remove);
+            }
+
+            continue;
+            bool InRange(VecD other) => (other.Multiply(documentSize) - point.Multiply(documentSize)).Length <= minDistance;
+        }
+
+        if (availablePoints.Count == 1)
+        {
+            newPoints.Add(availablePoints[0]);
+        }
+        
+        Output.Value = newPoints;
+        
+        return null;
+
+    }
+
+    public override Node CreateCopy() => new RemoveClosePointsNode();
+}

+ 1 - 0
src/PixiEditor/Helpers/ServiceCollectionHelpers.cs

@@ -123,6 +123,7 @@ internal static class ServiceCollectionHelpers
             .AddSingleton<SerializationFactory, VecISerializationFactory>()
             .AddSingleton<SerializationFactory, ColorSerializationFactory>()
             .AddSingleton<SerializationFactory, ColorMatrixSerializationFactory>()
+            .AddSingleton<SerializationFactory, PointListSerializationFactory>()
             // Palette Parsers
             .AddSingleton<IPalettesProvider, PaletteProvider>()
             .AddSingleton<PaletteFileParser, JascFileParser>()

+ 27 - 0
src/PixiEditor/Models/Serialization/Factories/PointListSerializationFactory.cs

@@ -0,0 +1,27 @@
+using MessagePack;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Points;
+
+namespace PixiEditor.Models.Serialization.Factories;
+
+public class PointListSerializationFactory : SerializationFactory<byte[], PointList>
+{
+    public override string DeserializationId { get; } = "PixiEditor.PointList";
+
+    public override byte[] Serialize(PointList original)
+    {
+        return MessagePackSerializer.Serialize(original);
+    }
+
+    public override bool TryDeserialize(object serialized, out PointList? original)
+    {
+        if (serialized is not byte[] buffer)
+        {
+            original = null;
+            return false;
+        }
+        
+        original = MessagePackSerializer.Deserialize<PointList>(buffer);
+
+        return true;
+    }
+}