소스 검색

Merge branch 'refs/heads/node-points' into avalonia-rewrite

CPKreuz 1 년 전
부모
커밋
8bee35ceed

+ 27 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Evaluator/ColorEvaluatorLeftNode.cs

@@ -0,0 +1,27 @@
+using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.DrawingApi.Core;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Evaluator;
+
+[NodeInfo("ColorEvaluatorLeft")]
+[PairNode(typeof(ColorEvaluatorRightNode), "ColorEvaluatorZone", true)]
+public class ColorEvaluatorLeftNode : Node
+{
+    public override string DisplayName { get; set; } = "BEGIN_COLOR_EVALUATOR";
+
+    public FuncOutputProperty<VecD> Position { get; }
+
+    public ColorEvaluatorLeftNode()
+    {
+        Position = CreateFuncOutput("Position", "UV", c => c.Position);
+    }
+    
+    protected override Surface? OnExecute(RenderingContext context)
+    {
+        return null;
+    }
+
+    public override Node CreateCopy() => new ColorEvaluatorLeftNode();
+}

+ 29 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Evaluator/ColorEvaluatorRightNode.cs

@@ -0,0 +1,29 @@
+using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.DrawingApi.Core;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Evaluator;
+
+[NodeInfo("ColorEvaluatorRight")]
+[PairNode(typeof(ColorEvaluatorLeftNode), "ColorEvaluatorZone")]
+public class ColorEvaluatorRightNode : Node
+{
+    public override string DisplayName { get; set; } = "FINISH_COLOR_EVALUATOR";
+    
+    public FuncOutputProperty<Color> Output { get; }
+
+    public FuncInputProperty<Color> Input { get; }
+
+    public ColorEvaluatorRightNode()
+    {
+        Output = CreateFuncOutput("Output", "COLOR", c => Input.Value(c));
+        Input = CreateFuncInput("Input", "COLOR", Colors.Black);
+    }
+    
+    protected override Surface? OnExecute(RenderingContext context)
+    {
+        return null;
+    }
+
+    public override Node CreateCopy() => new ColorEvaluatorRightNode();
+}

+ 18 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Filters.cs

@@ -5,18 +5,36 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 public static class Filters
 {
+    /// <summary>
+    /// Maps red to the red, green and blue channels. Sets alpha to 1
+    /// </summary>
     public static readonly ColorFilter RedGrayscaleFilter =
         ColorFilter.CreateColorMatrix(
             ColorMatrix.UseRed + ColorMatrix.MapRedToGreenBlue + ColorMatrix.OpaqueAlphaOffset);
 
+    /// <summary>
+    /// Maps green to the red, green and blue channels. Sets alpha to 1
+    /// </summary>
     public static readonly ColorFilter GreenGrayscaleFilter =
         ColorFilter.CreateColorMatrix(ColorMatrix.UseGreen + ColorMatrix.MapGreenToRedBlue +
                                       ColorMatrix.OpaqueAlphaOffset);
 
+    /// <summary>
+    /// Maps blue to the red, green and blue channels. Sets alpha to 1
+    /// </summary>
     public static readonly ColorFilter BlueGrayscaleFilter =
         ColorFilter.CreateColorMatrix(ColorMatrix.UseBlue + ColorMatrix.MapBlueToRedGreen +
                                       ColorMatrix.OpaqueAlphaOffset);
 
+    /// <summary>
+    /// Maps alpha to the red, green and blue channels. Sets alpha to 1
+    /// </summary>
     public static readonly ColorFilter AlphaGrayscaleFilter =
         ColorFilter.CreateColorMatrix(ColorMatrix.MapAlphaToRedGreenBlue + ColorMatrix.OpaqueAlphaOffset);
+    
+    /// <summary>
+    /// The rgb values become averaged into a grayscale image. Sets alpha to 1 <br/>
+    /// </summary>
+    public static readonly ColorFilter AverageGrayscaleFilter =
+        ColorFilter.CreateColorMatrix(ColorMatrix.AverageGrayscale + ColorMatrix.OpaqueAlphaOffset);
 }

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

@@ -0,0 +1,164 @@
+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<int> Seed { get; }
+
+    public DistributePointsNode()
+    {
+        Points = CreateOutput(nameof(Points), "POINTS", PointList.Empty);
+
+        Probability = CreateInput<Surface>("Probability", "PROBABILITY", null);
+        MaxPointCount = CreateInput("MaxPointCount", "MAX_POINTS", 10);
+        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);
+}

+ 55 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Points/RasterizePointsNode.cs

@@ -0,0 +1,55 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Context;
+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 FuncInputProperty<Color> Color { get; }
+
+    public RasterizePointsNode()
+    {
+        Image = CreateOutput<Surface>("Image", "IMAGE", null);
+        Points = CreateInput("Points", "POINTS", PointList.Empty);
+        Color = CreateFuncInput("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);
+
+        var colorFunc = Color.Value;
+        var funcContext = new FuncContext();
+        foreach (var point in points)
+        {
+            funcContext.UpdateContext(point, context.DocumentSize);
+            _paint.Color = colorFunc(funcContext);
+            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();
+}

+ 23 - 7
src/PixiEditor.DrawingApi.Core/Surface.cs

@@ -21,28 +21,44 @@ public class Surface : IDisposable, ICloneable, IPixelsMap
     
     public bool IsDisposed => disposed;
 
+    private static ImageInfo DefaultImageInfo => new(0, 0, ColorType.RgbaF16, AlphaType.Premul, ColorSpace.CreateSrgb());
+
     public event SurfaceChangedEventHandler? Changed;
 
     private Paint drawingPaint = new Paint() { BlendMode = BlendMode.Src };
     private Paint nearestNeighborReplacingPaint = new() { BlendMode = BlendMode.Src, FilterQuality = FilterQuality.None };
 
-    public Surface(VecI size)
+    private Surface(ImageInfo info)
     {
+        var size = info.Size;
+        
         if (size.X < 1 || size.Y < 1)
             throw new ArgumentException("Width and height must be >=1");
 
         Size = size;
 
-        BytesPerPixel = 8;
+        BytesPerPixel = info.BytesPerPixel;
         PixelBuffer = CreateBuffer(size.X, size.Y, BytesPerPixel);
-        DrawingSurface = CreateDrawingSurface();
+        DrawingSurface = CreateDrawingSurface(info);
     }
 
-    public Surface(Surface original) : this((VecI)original.Size)
+    public Surface(VecI size) : this(DefaultImageInfo.WithSize(size))
+    {
+    }
+
+    public Surface(Surface original) : this(original.Size)
     {
         DrawingSurface.Canvas.DrawSurface(original.DrawingSurface, 0, 0);
     }
-    
+
+    public static Surface UsingColorType(VecI size, ColorType type = ColorType.RgbaF16)
+    {
+        if (type == ColorType.Unknown)
+            throw new ArgumentException("Can't use unknown color type for surface", nameof(type));
+
+        return new Surface(DefaultImageInfo.WithSize(size).WithColorType(type));
+    }
+
     public static Surface Combine(int width, int height, List<(Image img, VecI offset)> images)
     {
         Surface surface = new Surface(new VecI(width, height));
@@ -187,9 +203,9 @@ public class Surface : IDisposable, ICloneable, IPixelsMap
     }
 #endif
 
-    private DrawingSurface CreateDrawingSurface()
+    private DrawingSurface CreateDrawingSurface(ImageInfo info)
     {
-        var surface = DrawingSurface.Create(new ImageInfo(Size.X, Size.Y, ColorType.RgbaF16, AlphaType.Premul, ColorSpace.CreateSrgb()), PixelBuffer);
+        var surface = DrawingSurface.Create(info, PixelBuffer);
         surface.Changed += DrawingSurfaceChanged;
         if (surface is null)
             throw new InvalidOperationException($"Could not create surface (Size:{Size})");

+ 12 - 1
src/PixiEditor.Numerics/ColorMatrix.cs

@@ -121,7 +121,7 @@ public record struct ColorMatrix
         (0, 0, 1, 0, 0),
         (0, 0, 0, 0, 0)
     );
-    
+
     /// <summary>
     /// The alpha value will stay the alpha value <br/>
     /// (_, _, _, w) => (0, 0, 0, w)
@@ -139,6 +139,17 @@ public record struct ColorMatrix
     /// </summary>
     public static ColorMatrix OpaqueAlphaOffset => Offset(0, 0, 0, 1);
     
+    /// <summary>
+    /// The rgb values become averaged into a grayscale image. Alpha becomes zero <br/>
+    /// (r, g, b, _) => (r, g, b, 0) / 3
+    /// </summary>
+    public static ColorMatrix AverageGrayscale => new(
+        (1 / 3f, 1 / 3f, 1 / 3f, 0, 0),
+        (1 / 3f, 1 / 3f, 1 / 3f, 0, 0),
+        (1 / 3f, 1 / 3f, 1 / 3f, 0, 0),
+        (0, 0, 0, 0, 0)
+    );
+
     public static ColorMatrix operator +(ColorMatrix left, ColorMatrix right) => new(left.M11 + right.M11,
         left.M12 + right.M12, left.M13 + right.M13, left.M14 + right.M14, left.M15 + right.M15, left.M21 + right.M21,
         left.M22 + right.M22, left.M23 + right.M23, left.M24 + right.M24, left.M25 + right.M25, left.M31 + right.M31,

+ 10 - 0
src/PixiEditor.UI.Common/Accents/Base.axaml

@@ -48,10 +48,14 @@
             <Color x:Key="VecDSocketColor">#c984ca</Color>
             <Color x:Key="VecISocketColor">#c9b4ca</Color>
             <Color x:Key="IntSocketColor">#4C64B1</Color>
+            <Color x:Key="PointListSocketColor">#7f5280</Color>
             
             <Color x:Key="PixiEditorModifyImageBorderColor">#68abdf</Color>
             <Color x:Key="PixiEditorModifyImageNodeBackgroundColor">#4068abdf</Color>
 
+            <Color x:Key="PixiEditorColorEvaluatorBorderColor">#99e4aa</Color>
+            <Color x:Key="PixiEditorColorEvaluatorNodeBackgroundColor">#4099e4aa</Color>
+
             <Color x:Key="NodeFrameBorderColor">#101010</Color>
             <Color x:Key="NodeFrameBackgroundColor">#40101010</Color>
 
@@ -98,12 +102,18 @@
             <SolidColorBrush x:Key="VecDSocketBrush" Color="{StaticResource VecDSocketColor}"/>
             <SolidColorBrush x:Key="VecISocketBrush" Color="{StaticResource VecISocketColor}"/>
             <SolidColorBrush x:Key="Int32SocketBrush" Color="{StaticResource IntSocketColor}"/>
+            <SolidColorBrush x:Key="PointListSocketBrush" Color="{StaticResource PointListSocketColor}"/>
             
             <SolidColorBrush x:Key="PixiEditorModifyImageLeftBorderBrush" Color="{StaticResource PixiEditorModifyImageBorderColor}"/>
             <SolidColorBrush x:Key="PixiEditorModifyImageRightBorderBrush" Color="{StaticResource PixiEditorModifyImageBorderColor}"/>
             <SolidColorBrush x:Key="PixiEditorModifyImageZoneBorderBrush" Color="{StaticResource PixiEditorModifyImageBorderColor}"/>
             <SolidColorBrush x:Key="PixiEditorModifyImageZoneBackgroundBrush" Color="{StaticResource PixiEditorModifyImageNodeBackgroundColor}"/>
             
+            <SolidColorBrush x:Key="PixiEditorColorEvaluatorLeftBorderBrush" Color="{StaticResource PixiEditorColorEvaluatorBorderColor}"/>
+            <SolidColorBrush x:Key="PixiEditorColorEvaluatorRightBorderBrush" Color="{StaticResource PixiEditorColorEvaluatorBorderColor}"/>
+            <SolidColorBrush x:Key="PixiEditorColorEvaluatorZoneBorderBrush" Color="{StaticResource PixiEditorColorEvaluatorBorderColor}"/>
+            <SolidColorBrush x:Key="PixiEditorColorEvaluatorZoneBackgroundBrush" Color="{StaticResource PixiEditorColorEvaluatorNodeBackgroundColor}"/>
+
             <SolidColorBrush x:Key="NodeFrameBorderBrush" Color="{StaticResource NodeFrameBorderColor}"/>
             <SolidColorBrush x:Key="NodeFrameBackgroundBrush" Color="{StaticResource NodeFrameBackgroundColor}"/>
 

BIN
src/PixiEditor/Data/BetaExampleFiles/Stars.pixi


+ 11 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -694,5 +694,15 @@
   "FINISHED": "Finished",
   "GENERATING_SPRITE_SHEET": "Generating Sprite Sheet",
   "RENDERING_IMAGE": "Rendering Image",
-  "PROGRESS_POPUP_TITLE": "Progress"
+  "PROGRESS_POPUP_TITLE": "Progress",
+  "POINTS": "Points",
+  "MIN_DISTANCE": "Min. Distance",
+  "MAX_POINTS": "Max. Points",
+  "PROBABILITY": "Probability",
+  "DISTRIBUTE_POINTS": "Distribute points",
+  "REMOVE_CLOSE_POINTS": "Remove close points",
+  "RASTERIZE_POINTS": "Rasterize Points",
+  "BEGIN_COLOR_EVALUATOR": "Begin evaluating color",
+  "FINISH_COLOR_EVALUATOR": "Finish evaluating color",
+  "STARS_EXAMPLE": "Stars"
 }

+ 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;
+    }
+}

+ 2 - 0
src/PixiEditor/Views/Windows/HelloTherePopup.axaml

@@ -84,6 +84,7 @@
                         <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
                             <windows:BetaExampleButton FileName="Pond.pixi" DisplayName="POND_EXAMPLE" CloseCommand="{Binding CloseCommand, RelativeSource={RelativeSource AncestorType=windows:HelloTherePopup}}" />
                             <windows:BetaExampleButton FileName="Tree.pixi" DisplayName="TREE_EXAMPLE" CloseCommand="{Binding CloseCommand, RelativeSource={RelativeSource AncestorType=windows:HelloTherePopup}}" />
+                            <windows:BetaExampleButton FileName="Stars.pixi" DisplayName="STARS_EXAMPLE" CloseCommand="{Binding CloseCommand, RelativeSource={RelativeSource AncestorType=windows:HelloTherePopup}}" />
                             
                             <StackPanel>
                                 <Button Margin="0,10,0,0" HorizontalAlignment="Center"
@@ -339,6 +340,7 @@
                         <StackPanel Orientation="Horizontal">
                             <windows:BetaExampleButton FileName="Pond.pixi" DisplayName="POND_EXAMPLE" CloseCommand="{Binding CloseCommand, RelativeSource={RelativeSource AncestorType=windows:HelloTherePopup}}" />
                             <windows:BetaExampleButton FileName="Tree.pixi" DisplayName="TREE_EXAMPLE" CloseCommand="{Binding CloseCommand, RelativeSource={RelativeSource AncestorType=windows:HelloTherePopup}}" />
+                            <windows:BetaExampleButton FileName="Stars.pixi" DisplayName="STARS_EXAMPLE" CloseCommand="{Binding CloseCommand, RelativeSource={RelativeSource AncestorType=windows:HelloTherePopup}}" />
                             <windows:BetaExampleButton FileName="Outline.pixi" DisplayName="OUTLINE_EXAMPLE" CloseCommand="{Binding CloseCommand, RelativeSource={RelativeSource AncestorType=windows:HelloTherePopup}}" />
                         </StackPanel>
                     </ScrollViewer>