Browse Source

Basic svg ellipse import workin workin

flabbet 8 months ago
parent
commit
e50e456a95

+ 8 - 7
src/PixiEditor.SVG/Elements/SvgPrimitive.cs

@@ -10,20 +10,21 @@ public abstract class SvgPrimitive(string tagName) : SvgElement(tagName), ITrans
     public SvgProperty<SvgColorUnit> Fill { get; } = new("fill");
     public SvgProperty<SvgColorUnit> Stroke { get; } = new("stroke");
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; } = new("stroke-width");
+
     public override void ParseData(XmlReader reader)
     {
         List<SvgProperty> properties = GetProperties().ToList();
-
-        if(!reader.MoveToFirstAttribute())
-        {
-            return;
-        }
         
+        properties.Add(Transform);
+        properties.Add(Fill);
+        properties.Add(Stroke);
+        properties.Add(StrokeWidth);
+
         do
         {
-            ParseAttributes(properties, reader); 
+            ParseAttributes(properties, reader);
         } while (reader.MoveToNextAttribute());
     }
-    
+
     protected abstract IEnumerable<SvgProperty> GetProperties();
 }

+ 1 - 1
src/PixiEditor.SVG/SvgProperty.cs

@@ -20,7 +20,7 @@ public class SvgProperty<T> : SvgProperty where T : struct, ISvgUnit
         get => (T?)base.Unit;
         set => base.Unit = value;
     }
-
+    
     public SvgProperty(string svgName) : base(svgName)
     {
     }

+ 1 - 1
src/PixiEditor.SVG/Units/SvgColorUnit.cs

@@ -13,7 +13,7 @@ public struct SvgColorUnit : ISvgUnit
         get => value;
         set
         {
-            value = this.value;
+            this.value = value;
             if(SvgColorUtility.TryConvertStringToColor(value, out Color color))
             {
                 Color = color;

+ 2 - 2
src/PixiEditor.SVG/Units/SvgTransformUnit.cs

@@ -43,13 +43,13 @@ public struct SvgTransformUnit : ISvgUnit
                     float.TryParse(values[4], NumberStyles.Any, CultureInfo.InvariantCulture, out float translateX) &&
                     float.TryParse(values[5], NumberStyles.Any, CultureInfo.InvariantCulture, out float translateY))
                 {
-                    MatrixValue = new Matrix3X3(scaleX, skewY, skewX, scaleY, translateX, translateY, 0, 0, 1);
+                    MatrixValue = new Matrix3X3(scaleX, skewX, translateX, skewY, scaleY, translateY, 0, 0, 1);
                 }
             }
         }
         else
         {
-            
+            // todo: parse other types of transformation syntax (rotate, translate, scale etc)
         }
     }
 }

+ 9 - 1
src/PixiEditor.SVG/Utils/SvgColorUtility.cs

@@ -53,7 +53,15 @@ public static class SvgColorUtility
                 {
                     if (float.TryParse(values[i], out float alpha))
                     {
-                        colorValues[i - 1] = (byte)(alpha * 255);
+                        if (alpha > 0 && alpha < 1)
+                        {
+                            colorValues[i - 1] = (byte)(alpha * 255);
+                        }
+                        else
+                        {
+                            colorValues[i - 1] = Math.Clamp(alpha, 0, 255);
+                        }
+
                         continue;
                     }
 

+ 11 - 1
src/PixiEditor/Helpers/DocumentViewModelBuilder.cs

@@ -10,6 +10,7 @@ using Drawie.Backend.Core;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.Parser;
 using PixiEditor.Parser.Graph;
 using PixiEditor.Parser.Skia;
@@ -124,7 +125,7 @@ internal class DocumentViewModelBuilder
         ImageEncoderUsed = encoder;
         return this;
     }
-    
+
     public DocumentViewModelBuilder WithLegacyColorBlending(bool usesLegacyColorBlending)
     {
         UsesLegacyColorBlending = usesLegacyColorBlending;
@@ -370,6 +371,15 @@ internal class NodeGraphBuilder
         return node;
     }
 
+    public NodeBuilder WithNodeOfType<T>(out int id) where T : IReadOnlyNode
+    {
+        NodeBuilder builder = this.WithNodeOfType(typeof(VectorLayerNode))
+            .WithId(AllNodes.Count);
+
+        id = AllNodes.Count;
+        return builder;
+    }
+
     internal class NodeBuilder
     {
         public int Id { get; set; }

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

@@ -19,6 +19,7 @@ using PixiEditor.Models.ExtensionServices;
 using PixiEditor.Models.Files;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers.Tools;
+using PixiEditor.Models.IO.CustomDocumentFormats;
 using PixiEditor.Models.IO.PaletteParsers;
 using PixiEditor.Models.IO.PaletteParsers.JascPalFile;
 using PixiEditor.Models.Localization;
@@ -110,6 +111,8 @@ internal static class ServiceCollectionHelpers
             .AddSingleton<IoFileType, SvgFileType>()
             // Serialization Factories
             .AddAssemblyTypes<SerializationFactory>()
+            // Custom document builders
+            .AddSingleton<IDocumentBuilder, SvgDocumentBuilder>()
             // Palette Parsers
             .AddSingleton<IPalettesProvider, PaletteProvider>()
             .AddSingleton<PaletteFileParser, JascFileParser>()

+ 9 - 0
src/PixiEditor/Models/IO/CustomDocumentFormats/IDocumentBuilder.cs

@@ -0,0 +1,9 @@
+using PixiEditor.Helpers;
+
+namespace PixiEditor.Models.IO.CustomDocumentFormats;
+
+internal interface IDocumentBuilder
+{
+    public void Build(DocumentViewModelBuilder builder, string path);
+    public IReadOnlyCollection<string> Extensions { get; }
+}

+ 110 - 0
src/PixiEditor/Models/IO/CustomDocumentFormats/SvgDocumentBuilder.cs

@@ -0,0 +1,110 @@
+using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
+using PixiEditor.Helpers;
+using PixiEditor.SVG;
+using PixiEditor.SVG.Elements;
+
+namespace PixiEditor.Models.IO.CustomDocumentFormats;
+
+internal class SvgDocumentBuilder : IDocumentBuilder
+{
+    public IReadOnlyCollection<string> Extensions { get; } = [".svg"];
+
+    public void Build(DocumentViewModelBuilder builder, string path)
+    {
+        string xml = File.ReadAllText(path);
+        SvgDocument document = SvgDocument.Parse(xml);
+
+        builder.WithSize((int)document.ViewBox.Width, (int)document.ViewBox.Height)
+            .WithGraph(graph =>
+            {
+                int? lastId = null;
+                foreach (SvgElement element in document.Children)
+                {
+                    if (element is SvgPrimitive primitive)
+                    {
+                        ShapeVectorData shapeData = null;
+                        if (element is SvgEllipse or SvgCircle)
+                        {
+                            shapeData = AddEllipse(element);
+                        }
+                        /*else if (element is SvgLine line)
+                        {
+                            AddLine(graph, line);
+                        }
+                        else if (element is SvgPath pathElement)
+                        {
+                            AddPath(graph, pathElement);
+                        }
+                        else if (element is SvgRectangle rect)
+                        {
+                            AddRect(graph, rect);
+                        }
+                        else if (element is SvgGroup group)
+                        {
+                            AddGroup(graph, group);
+                        }*/
+
+                        AddCommonShapeData(primitive, shapeData);
+
+                        graph.WithNodeOfType<VectorLayerNode>(out int id)
+                            .WithAdditionalData(new Dictionary<string, object>() { { "ShapeData", shapeData } });
+                        
+                        lastId = id;
+                    }
+                }
+                
+                graph.WithOutputNode(lastId, "Output");
+            });
+    }
+
+    private EllipseVectorData AddEllipse(SvgElement element)
+    {
+        if (element is SvgCircle circle)
+        {
+            return new EllipseVectorData(
+                new VecD(circle.Cx.Unit.Value.Value, circle.Cy.Unit.Value.Value),
+                new VecD(circle.R.Unit.Value.Value, circle.R.Unit.Value.Value));
+        }
+
+        if (element is SvgEllipse ellipse)
+        {
+            return new EllipseVectorData(
+                new VecD(ellipse.Cx.Unit.Value.Value, ellipse.Cy.Unit.Value.Value),
+                new VecD(ellipse.Rx.Unit.Value.Value, ellipse.Ry.Unit.Value.Value));
+        }
+
+        return null;
+    }
+
+    private void AddCommonShapeData(SvgPrimitive primitive, ShapeVectorData? shapeData)
+    {
+        if (shapeData == null)
+        {
+            return;
+        }
+
+        bool hasFill = primitive.Fill.Unit is { Color.A: > 0 };
+        bool hasStroke = primitive.Stroke.Unit is { Color.A: > 0 };
+        bool hasTransform = primitive.Transform.Unit is { MatrixValue.IsIdentity: false };
+
+        if (hasFill)
+        {
+            shapeData.Fill = true;
+            shapeData.FillColor = primitive.Fill.Unit.Value.Color;
+        }
+
+        if (hasStroke)
+        {
+            shapeData.StrokeColor = primitive.Stroke.Unit.Value.Color;
+            shapeData.StrokeWidth = (float)primitive.StrokeWidth.Unit.Value.Value;
+        }
+
+        if (hasTransform)
+        {
+            shapeData.TransformationMatrix = primitive.Transform.Unit.Value.MatrixValue;
+        }
+    }
+}

+ 32 - 13
src/PixiEditor/ViewModels/SubViewModels/FileViewModel.cs

@@ -1,21 +1,10 @@
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Threading.Tasks;
-using Avalonia;
+using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Input;
 using Avalonia.Platform.Storage;
 using Avalonia.Threading;
-using ChunkyImageLib;
-using Newtonsoft.Json.Linq;
-using PixiEditor.ChangeableDocument.Changeables.Graph;
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using Drawie.Backend.Core;
-using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using PixiEditor.Exceptions;
 using PixiEditor.Extensions.Common.Localization;
@@ -30,9 +19,10 @@ using PixiEditor.Models.Files;
 using PixiEditor.Models.IO;
 using PixiEditor.Models.UserData;
 using Drawie.Numerics;
+using Microsoft.Extensions.DependencyInjection;
+using PixiEditor.Models.IO.CustomDocumentFormats;
 using PixiEditor.OperatingSystem;
 using PixiEditor.Parser;
-using PixiEditor.Parser.Graph;
 using PixiEditor.UI.Common.Fonts;
 using PixiEditor.ViewModels.Document;
 using PixiEditor.Views;
@@ -57,6 +47,9 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
     }
 
     public RecentlyOpenedCollection RecentlyOpened { get; init; }
+    public IReadOnlyList<IDocumentBuilder> DocumentBuilders => documentBuilders;
+    
+    private List<IDocumentBuilder> documentBuilders;
 
     public FileViewModel(ViewModelMain owner)
         : base(owner)
@@ -70,6 +63,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
         }
 
         PixiEditorSettings.File.MaxOpenedRecently.ValueChanged += (_, value) => UpdateMaxRecentlyOpened(value);
+        documentBuilders = owner.Services.GetServices<IDocumentBuilder>().ToList();
     }
 
     public void AddRecentlyOpened(string path)
@@ -209,6 +203,10 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
             {
                 OpenDotPixi(path, associatePath);
             }
+            else if (IsCustomFormat(path))
+            {
+                OpenCustomFormat(path);
+            }
             else
             {
                 OpenRegularImage(path, associatePath);
@@ -223,6 +221,27 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
             NoticeDialog.Show("OLD_FILE_FORMAT_DESCRIPTION", "OLD_FILE_FORMAT");
         }
     }
+    
+    private bool IsCustomFormat(string path)
+    {
+        string extension = Path.GetExtension(path);
+        return documentBuilders.Any(x => x.Extensions.Contains(extension, StringComparer.OrdinalIgnoreCase));
+    }
+    
+    private void OpenCustomFormat(string path)
+    {
+        IDocumentBuilder builder = documentBuilders.First(x => x.Extensions.Contains(Path.GetExtension(path), StringComparer.OrdinalIgnoreCase));
+
+        if(!File.Exists(path))
+        {
+            NoticeDialog.Show("FILE_NOT_FOUND", "FAILED_TO_OPEN_FILE");
+            return;
+        }
+        
+        DocumentViewModel document = DocumentViewModel.Build(docBuilder => builder.Build(docBuilder, path));
+        AddDocumentViewModelToTheSystem(document);
+        AddRecentlyOpened(document.FullFilePath);
+    }
 
     /// <summary>
     /// Opens a .pixi file from path, creates a document from it, and adds it to the system