瀏覽代碼

Generic data serialization works

flabbet 1 年之前
父節點
當前提交
1d29c756e1

+ 7 - 12
src/PixiEditor.AvaloniaUI/Helpers/DocumentViewModelBuilder.cs

@@ -28,6 +28,7 @@ internal class DocumentViewModelBuilder
     public List<KeyFrameBuilder> AnimationData { get; set; } = new List<KeyFrameBuilder>();
 
     public NodeGraphBuilder Graph { get; set; }
+    public string ImageEncoderUsed { get; set; } = "QOI";
 
     public DocumentViewModelBuilder WithSize(int width, int height)
     {
@@ -92,11 +93,11 @@ internal class DocumentViewModelBuilder
         return this;
     }
 
-    public DocumentViewModelBuilder WithGraph(NodeGraph graph, ImageEncoder encoder, Action<NodeGraph, NodeGraphBuilder, ImageEncoder> builder)
+    public DocumentViewModelBuilder WithGraph(NodeGraph graph, Action<NodeGraph, NodeGraphBuilder> builder)
     {
         if (graph != null)
         {
-            WithGraph(x => builder(graph, x, encoder));
+            WithGraph(x => builder(graph, x));
         }
 
         return this;
@@ -109,17 +110,11 @@ internal class DocumentViewModelBuilder
         Graph = graph;
         return this;
     }
-
-    private static Dictionary<string, object> ToDictionary(NodePropertyValue[] values)
+    
+    public DocumentViewModelBuilder WithImageEncoder(string encoder)
     {
-        Dictionary<string, object> dictionary = new();
-
-        foreach (var value in values)
-        {
-            dictionary.Add(value.PropertyName, value.Value);
-        }
-
-        return dictionary;
+        ImageEncoderUsed = encoder;
+        return this;
     }
 
     private static void BuildKeyFrames(List<IKeyFrame> root, List<KeyFrameBuilder> data)

+ 5 - 86
src/PixiEditor.AvaloniaUI/Helpers/Extensions/PixiParserDocumentEx.cs

@@ -34,14 +34,15 @@ internal static class PixiParserDocumentEx
 
         return DocumentViewModel.Build(b => b
             .WithSize(document.Width, document.Height)
+            .WithImageEncoder(document.ImageEncoderUsed)
             .WithPalette(document.Palette, color => new PaletteColor(color.R, color.G, color.B))
             .WithSwatches(document.Swatches, x => new(x.R, x.G, x.B))
             .WithReferenceLayer(document.ReferenceLayer, BuildReferenceLayer, encoder)
-            .WithGraph(document.Graph, encoder, BuildGraph)
+            .WithGraph(document.Graph, BuildGraph)
             .WithAnimationData(document.AnimationData));
     }
 
-    private static void BuildGraph(NodeGraph graph, NodeGraphBuilder graphBuilder, ImageEncoder encoder)
+    private static void BuildGraph(NodeGraph graph, NodeGraphBuilder graphBuilder)
     {
         if (graph.AllNodes != null)
         {
@@ -52,84 +53,13 @@ internal static class PixiParserDocumentEx
                     .WithPosition(node.Position)
                     .WithName(node.Name)
                     .WithUniqueNodeName(node.UniqueNodeName)
-                    .WithInputValues(ParseArbitraryData(ToDictionary(node.InputPropertyValues), encoder))
-                    .WithAdditionalData(ParseArbitraryData(node.AdditionalData, encoder))
+                    .WithInputValues(ToDictionary(node.InputPropertyValues))
+                    .WithAdditionalData(node.AdditionalData)
                     .WithConnections(node.InputConnections));
             }
         }
     }
 
-    private static Dictionary<string, object> ParseArbitraryData(Dictionary<string, object> data, ImageEncoder encoder)
-    {
-        Dictionary<string, object> parsedData = new();
-
-        foreach (var item in data)
-        {
-            if (item.Value is IEnumerable enumerable)
-            {
-                List<object> parsedList = new();
-                foreach (var listElement in enumerable)
-                {
-                    if (TryParseSurface(listElement, encoder, out object parsed))
-                    {
-                        parsedList.Add(parsed);
-                    }
-                }
-
-                if (parsedList.Count > 0)
-                {
-                    // if all children are the same type
-                    if (parsedList.All(x => x is Surface))
-                    {
-                        parsedData.Add(item.Key, parsedList.Cast<Surface>());
-                    }
-                    else
-                    {
-                        parsedData.Add(item.Key, parsedList);
-                    }
-                }
-                else
-                {
-                    parsedData.Add(item.Key, item.Value);
-                }
-            }
-            else if (TryParseSurface(item, encoder, out object parsed))
-            {
-                parsedData.Add(item.Key, parsed);
-            }
-            else
-            {
-                parsedData.Add(item.Key, item.Value);
-            }
-        }
-
-        return parsedData;
-    }
-
-    private static bool TryParseSurface(object item, ImageEncoder encoder, out object parsed)
-    {
-        parsed = null;
-        if (item is IEnumerable<object> objEnumerable)
-        {
-            var array = objEnumerable.ToArray();
-            if (array.Count() == 3 && array.First() is IEnumerable<byte> bytes)
-            {
-                try
-                {
-                    parsed = DecodeSurface(bytes.ToArray(), encoder);
-                    return true;
-                }
-                catch
-                {
-                    parsed = item;
-                    return false;
-                }
-            }
-        }
-
-        return false;
-    }
-
     private static Dictionary<string, object> ToDictionary(IEnumerable<NodePropertyValue> properties)
     {
         Dictionary<string, object> dict = new();
@@ -165,15 +95,4 @@ internal static class PixiParserDocumentEx
 
         return surface;
     }
-    
-    private static Surface DecodeSurface(byte[] imgBytes, ImageEncoder encoder)
-    {
-        byte[] decoded =
-            encoder.Decode(imgBytes, out SKImageInfo info);
-        using Image img = Image.FromPixels(info.ToImageInfo(), decoded);
-        Surface surface = new Surface(img.Size);
-        surface.DrawingSurface.Canvas.DrawImage(img, 0, 0);
-
-        return surface;
-    }
 }

+ 81 - 9
src/PixiEditor.AvaloniaUI/Helpers/SerializationUtil.cs

@@ -1,16 +1,13 @@
-using ChunkyImageLib;
+using System.Collections;
 using PixiEditor.AvaloniaUI.Models.Serialization;
 using PixiEditor.AvaloniaUI.Models.Serialization.Factories;
-using PixiEditor.DrawingApi.Core;
-using PixiEditor.Numerics;
-using PixiEditor.Parser;
-using PixiEditor.Parser.Skia;
 
 namespace PixiEditor.AvaloniaUI.Helpers;
 
 public static class SerializationUtil
 {
-    public static object SerializeObject(object? value, SerializationConfig config, IReadOnlyList<SerializationFactory> allFactories)
+    public static object SerializeObject(object? value, SerializationConfig config,
+        IReadOnlyList<SerializationFactory> allFactories)
     {
         if (value is null)
         {
@@ -18,17 +15,92 @@ public static class SerializationUtil
         }
 
         var factory = allFactories.FirstOrDefault(x => x.OriginalType == value.GetType());
-        
+
         if (factory != null)
         {
+            factory.Config = config;
             return factory.Serialize(value);
         }
-        
+
         if (value.GetType().IsValueType || value is string)
         {
             return value;
         }
 
-        throw new ArgumentException($"Type {value.GetType()} is not serializable and appropriate serialization factory was not found.");
+        throw new ArgumentException(
+            $"Type {value.GetType()} is not serializable and appropriate serialization factory was not found.");
+    }
+
+    public static object Deserialize(object value, SerializationConfig config,
+        IReadOnlyList<SerializationFactory> allFactories)
+    {
+        if (IsComplexObject(value))
+        {
+            object[] arr = ((IEnumerable<object>)value).ToArray();
+            string id = (string)arr.First();
+            object data = arr.Last();
+            var factory = allFactories.FirstOrDefault(x => x.DeserializationId == id);
+
+            if (factory != null)
+            {
+                factory.Config = config;
+                return factory.Deserialize(data is Dictionary<object, object> processableDict
+                    ? ToDictionary(processableDict)
+                    : data);
+            }
+        }
+
+        return value;
+    }
+
+
+    public static Dictionary<string, object> DeserializeDict(Dictionary<string, object> data,
+        SerializationConfig config, List<SerializationFactory> allFactories)
+    {
+        var dict = new Dictionary<string, object>();
+
+        foreach (var (key, value) in data)
+        {
+            if (value is object[] objArr && objArr.Length > 0)
+            {
+                var deserialized = Deserialize(objArr[0], config, allFactories);
+                var targetArr = Array.CreateInstance(deserialized.GetType(), objArr.Length);
+                targetArr.SetValue(deserialized, 0);
+                
+                for (int i = 1; i < objArr.Length; i++)
+                {
+                    targetArr.SetValue(Deserialize(objArr[i], config, allFactories), i);
+                }
+                
+                dict[key] = targetArr;
+            }
+            else
+            {
+                dict[key] = Deserialize(value, config, allFactories);
+            }
+        }
+
+        return dict;
+    }
+
+    private static bool IsComplexObject(object value)
+    {
+        // SerializedObject signature
+        return value is IEnumerable<object> enumerable && enumerable.Count() == 2;
+    }
+
+    private static Dictionary<string, object> ToDictionary(Dictionary<object, object> data)
+    {
+        // input data is probably Dictionary<object, object> with KeyValuePair keys (where key as type is object, but actually is string)
+        // note that this depends if serialized type uses string keys or int
+
+        var dict = new Dictionary<string, object>();
+
+        foreach (KeyValuePair<object, object> item in data)
+        {
+            dict.Add((string)item.Key, item.Value);
+        }
+
+        return dict;
     }
 }

+ 20 - 7
src/PixiEditor.AvaloniaUI/Models/Serialization/Factories/KernelSerializationFactory.cs

@@ -4,10 +4,6 @@ namespace PixiEditor.AvaloniaUI.Models.Serialization.Factories;
 
 public class KernelSerializationFactory : SerializationFactory<SerializableKernel, Kernel>
 {
-    public KernelSerializationFactory(SerializationConfig config) : base(config)
-    {
-    }
-
     public override SerializableKernel Serialize(Kernel original)
     {
         return new SerializableKernel
@@ -18,9 +14,26 @@ public class KernelSerializationFactory : SerializationFactory<SerializableKerne
         };    
     }
 
-    public override Kernel Deserialize(SerializableKernel serialized)
+    public override bool TryDeserialize(object raw, out Kernel original)
     {
-        Kernel kernel = new(serialized.Width, serialized.Height, serialized.Values);
-        return kernel;
+        if (raw is not Dictionary<string, object> serialized)
+        {
+            original = null;
+            return false;
+        }
+        
+        if (serialized.ContainsKey("Width") && serialized.ContainsKey("Height") && serialized.ContainsKey("Values"))
+        {
+            int width = ExtractInt(serialized["Width"]);
+            int height = ExtractInt(serialized["Height"]);
+            float[] values = ExtractArray<float>(serialized["Values"]);
+            original = new Kernel(width, height, values);
+            return true;
+        }
+
+        original = null;
+        return false; 
     }
+
+    public override string DeserializationId { get; } = "PixiEditor.Kernel";
 }

+ 48 - 16
src/PixiEditor.AvaloniaUI/Models/Serialization/Factories/SerializationFactory.cs

@@ -1,36 +1,68 @@
-namespace PixiEditor.AvaloniaUI.Models.Serialization.Factories;
+using MessagePack;
+
+namespace PixiEditor.AvaloniaUI.Models.Serialization.Factories;
 
 public abstract class SerializationFactory
 {
     public abstract Type OriginalType { get; }
-    public abstract Type SerializedType { get; }
+    public abstract string DeserializationId { get; } 
+    public SerializationConfig Config { get; set; }
+
     public abstract object Serialize(object original);
-    public abstract object Deserialize(object serialized);
-}
+    public abstract object Deserialize(object rawData);
+    
+    protected int ExtractInt(object value)
+    {
+        return value switch
+        {
+            byte b => b,
+            int i => i,
+            long l => (int)l,
+            _ => throw new InvalidOperationException("Value is not an integer.")
+        };
+    }
 
-public abstract class SerializationFactory<TSerializable, TOriginal> : SerializationFactory
-{
-    protected SerializationFactory(SerializationConfig config)
+    protected T[] ExtractArray<T>(object value)
     {
-        Config = config;
+        return value switch
+        {
+            T[] arr => arr,
+            object[] objArr => objArr.Cast<T>().ToArray(),
+            _ => throw new InvalidOperationException("Value is not an array.")
+        };
     }
+}
 
-    public SerializationConfig Config { get; }
-    
-    
+public abstract class SerializationFactory<TSerializable, TOriginal> : SerializationFactory
+{
     public abstract TSerializable Serialize(TOriginal original);
-    public abstract TOriginal Deserialize(TSerializable serialized);
+    public abstract bool TryDeserialize(object serialized, out TOriginal original);
     
     public override object Serialize(object original)
     {
-        return Serialize((TOriginal)original);
+        SerializedObject serialized = new SerializedObject
+        {
+            SerializationId = DeserializationId,
+            Data = Serialize((TOriginal)original)
+        };
+
+        return serialized;
     }
     
-    public override object Deserialize(object serialized)
+    public override object Deserialize(object rawData)
     {
-        return Deserialize((TSerializable)serialized);
+        return TryDeserialize(rawData, out TOriginal original) ? original : default;
     }
     
     public override Type OriginalType => typeof(TOriginal);
-    public override Type SerializedType => typeof(TSerializable);
+}
+
+[MessagePackObject]
+class SerializedObject
+{
+    [Key(0)]
+    public string SerializationId { get; set; }
+    
+    [Key(1)]
+    public object Data { get; set; }
 }

+ 29 - 10
src/PixiEditor.AvaloniaUI/Models/Serialization/Factories/SurfaceSerializationFactory.cs

@@ -1,27 +1,46 @@
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.DrawingApi.Core;
+using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
+using PixiEditor.DrawingApi.Skia;
 using PixiEditor.Parser;
+using PixiEditor.Parser.Skia;
 
 namespace PixiEditor.AvaloniaUI.Models.Serialization.Factories;
 
-public class SurfaceSerializationFactory : SerializationFactory<ImageContainer, Surface>
+public class SurfaceSerializationFactory : SerializationFactory<byte[], Surface>
 {
-    public SurfaceSerializationFactory(SerializationConfig config) : base(config)
+    public override byte[] Serialize(Surface original)
     {
+        var encoder = Config.Encoder;
+        byte[] result = encoder.Encode(original.ToByteArray(), original.Size.X, original.Size.Y);
+
+        return result;
     }
 
-    public override ImageContainer Serialize(Surface original)
+    public override bool TryDeserialize(object serialized, out Surface original)
     {
-        var encoder = Config.Encoder;
-        byte[] result = encoder.Encode(original.ToByteArray(), original.Size.X, original.Size.Y);
-        ImageContainer container =
-            new ImageContainer { ImageBytes = result, ResourceOffset = 0, ResourceSize = result.Length };
+        if (serialized is byte[] imgBytes)
+        {
+            original = DecodeSurface(imgBytes, Config.Encoder);
+            return true;
+        }
         
-        return container;
+        original = null;
+        return false;
     }
 
-    public override Surface Deserialize(ImageContainer serialized)
+
+    public static Surface DecodeSurface(byte[] imgBytes, ImageEncoder encoder)
     {
-        throw new NotImplementedException();
+        byte[] decoded =
+            encoder.Decode(imgBytes, out SKImageInfo info);
+        using Image img = Image.FromPixels(info.ToImageInfo(), decoded);
+        Surface surface = new Surface(img.Size);
+        surface.DrawingSurface.Canvas.DrawImage(img, 0, 0);
+
+        return surface;
     }
+
+
+    public override string DeserializationId { get; } = "PixiEditor.Surface";
 }

+ 3 - 3
src/PixiEditor.AvaloniaUI/Models/Serialization/SerializableKernel.cs

@@ -5,12 +5,12 @@ namespace PixiEditor.AvaloniaUI.Models.Serialization;
 [MessagePackObject]
 public class SerializableKernel
 {
-    [Key(0)]
+    [Key("Width")]
     public int Width { get; set; }
     
-    [Key(1)]
+    [Key("Height")]
     public int Height { get; set; }
     
-    [Key(2)]
+    [Key("Values")]
     public float[] Values { get; set; }
 }

+ 11 - 2
src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs

@@ -10,6 +10,7 @@ using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
 using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.Extensions.DependencyInjection;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Helpers.Collections;
 using PixiEditor.AvaloniaUI.Helpers.Extensions;
@@ -19,6 +20,8 @@ using PixiEditor.AvaloniaUI.Models.DocumentModels.Public;
 using PixiEditor.AvaloniaUI.Models.DocumentPassthroughActions;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.Models.Position;
+using PixiEditor.AvaloniaUI.Models.Serialization;
+using PixiEditor.AvaloniaUI.Models.Serialization.Factories;
 using PixiEditor.AvaloniaUI.Models.Structures;
 using PixiEditor.AvaloniaUI.Models.Tools;
 using PixiEditor.AvaloniaUI.ViewModels.Document.TransformOverlays;
@@ -32,6 +35,7 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.ChangeInfos;
+using PixiEditor.ChangeableDocument.Changes.NodeGraph;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.DrawingApi.Core;
@@ -277,6 +281,10 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         viewModel.Swatches = new ObservableCollection<PaletteColor>(builderInstance.Swatches);
         viewModel.Palette = new ObservableRangeCollection<PaletteColor>(builderInstance.Palette);
 
+        SerializationConfig config = new SerializationConfig(BuiltInEncoders.Encoders[builderInstance.ImageEncoderUsed]);
+        
+        List<SerializationFactory> allFactories = ViewModelMain.Current.Services.GetServices<SerializationFactory>().ToList();
+        
         AddNodes(builderInstance.Graph);
 
         if (builderInstance.Graph.AllNodes.Count == 0)
@@ -327,13 +335,14 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             {
                 foreach (var propertyValue in serializedNode.InputValues)
                 {
-                    acc.AddActions(new UpdatePropertyValue_Action(guid, propertyValue.Key, propertyValue.Value));
+                    object value = SerializationUtil.Deserialize(propertyValue.Value, config, allFactories);
+                    acc.AddActions(new UpdatePropertyValue_Action(guid, propertyValue.Key, value));
                 }
             }
 
             if (serializedNode.AdditionalData != null && serializedNode.AdditionalData.Count > 0)
             {
-                acc.AddActions(new DeserializeNodeAdditionalData_Action(guid, serializedNode.AdditionalData));
+                acc.AddActions(new DeserializeNodeAdditionalData_Action(guid, SerializationUtil.DeserializeDict(serializedNode.AdditionalData, config, allFactories)));
             }
 
             if (!string.IsNullOrEmpty(serializedNode.Name))

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs

@@ -342,6 +342,6 @@ public abstract class Node : IReadOnlyNode, IDisposable
 
     internal virtual void DeserializeData(IReadOnlyDictionary<string, object> data)
     {
-      
+        
     }
 }

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changes/DeserializeNodeAdditionalData_Change.cs

@@ -22,6 +22,7 @@ internal class DeserializeNodeAdditionalData_Change : Change
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
         Node node = target.FindNode<Node>(nodeId);
+        
         node.DeserializeData(data);
         ignoreInUndo = false;
         return new None();

+ 4 - 0
src/PixiEditor.ChangeableDocument/PixiEditor.ChangeableDocument.csproj

@@ -66,4 +66,8 @@
     <ProjectReference Include="..\PixiEditor.ChangeableDocument.Gen\PixiEditor.ChangeableDocument.Gen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
   </ItemGroup>
 
+  <ItemGroup>
+    <Folder Include="Utils\" />
+  </ItemGroup>
+
 </Project>