Browse Source

Added support for more elements

flabbet 8 months ago
parent
commit
473da48d14

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 0b3b4813ee0002ae7655788c2cc430ca1b58ebd4
+Subproject commit 1124f58f7ca5b4312aeb0ef8e22ceea7103a9bb2

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyShapeVectorData.cs

@@ -8,6 +8,7 @@ public interface IReadOnlyShapeVectorData
 {
     public Matrix3X3 TransformationMatrix { get; }
     public Color StrokeColor { get; }
+    public bool Fill { get; }
     public Color FillColor { get; }
     public float StrokeWidth { get; }
     public RectD GeometryAABB { get; }

+ 12 - 0
src/PixiEditor.SVG/Attributes/SvgValueAttribute.cs

@@ -0,0 +1,12 @@
+namespace PixiEditor.SVG.Attributes;
+
+[AttributeUsage(AttributeTargets.Field)]
+public class SvgValueAttribute : Attribute
+{
+    public string Value { get; }
+
+    public SvgValueAttribute(string value)
+    {
+        Value = value;
+    }
+}

+ 5 - 1
src/PixiEditor.SVG/Elements/SvgPath.cs

@@ -1,12 +1,16 @@
-using PixiEditor.SVG.Units;
+using PixiEditor.SVG.Enums;
+using PixiEditor.SVG.Units;
 
 namespace PixiEditor.SVG.Elements;
 
 public class SvgPath() : SvgPrimitive("path")
 {
     public SvgProperty<SvgStringUnit> PathData { get; } = new("d");
+    public SvgProperty<SvgEnumUnit<SvgFillRule>> FillRule { get; } = new("fill-rule");
+
     protected override IEnumerable<SvgProperty> GetProperties()
     {
         yield return PathData;
+        yield return FillRule;
     }
 }

+ 12 - 0
src/PixiEditor.SVG/Enums/SvgFillRule.cs

@@ -0,0 +1,12 @@
+using PixiEditor.SVG.Attributes;
+
+namespace PixiEditor.SVG.Enums;
+
+public enum SvgFillRule
+{
+    [SvgValue("nonzero")]
+    NonZero,
+    
+    [SvgValue("evenodd")]
+    EvenOdd
+}

+ 2 - 5
src/PixiEditor.SVG/SvgElement.cs

@@ -23,12 +23,9 @@ public class SvgElement(string tagName)
             if (property.PropertyType.IsAssignableTo(typeof(SvgProperty)))
             {
                 SvgProperty prop = (SvgProperty)property.GetValue(this);
-                if (prop != null)
+                if (prop?.Unit != null)
                 {
-                    if (prop.Unit != null)
-                    {
-                        builder.Append($" {prop.SvgName}=\"{prop.Unit.ToXml()}\"");
-                    }
+                    builder.Append($" {prop.SvgName}=\"{prop.Unit.ToXml()}\"");
                 }
             }
         }

+ 30 - 2
src/PixiEditor.SVG/Units/SvgEnumUnit.cs

@@ -1,4 +1,6 @@
-using PixiEditor.SVG.Helpers;
+using System.Reflection;
+using PixiEditor.SVG.Attributes;
+using PixiEditor.SVG.Helpers;
 
 namespace PixiEditor.SVG.Units;
 
@@ -13,14 +15,40 @@ public struct SvgEnumUnit<T> : ISvgUnit where T : struct, Enum
 
     public string ToXml()
     {
+        FieldInfo field = Value.GetType().GetField(Value.ToString());
+        SvgValueAttribute attribute = field.GetCustomAttribute<SvgValueAttribute>();
+        
+        if (attribute != null)
+        {
+            return attribute.Value;
+        }
+        
         return Value.ToString().ToKebabCase();
     }
 
     public void ValuesFromXml(string readerValue)
     {
-        if (Enum.TryParse(readerValue.FromKebabToTitleCase(), out T result))
+        bool matched = TryMatchEnum(readerValue);
+        if (!matched && Enum.TryParse(readerValue.FromKebabToTitleCase(), out T result))
         {
             Value = result;
         }
     }
+    
+    private bool TryMatchEnum(string value)
+    {
+        foreach (T enumValue in Enum.GetValues(typeof(T)))
+        {
+            FieldInfo field = enumValue.GetType().GetField(enumValue.ToString());
+            SvgValueAttribute attribute = field.GetCustomAttribute<SvgValueAttribute>();
+            
+            if (attribute != null && attribute.Value == value)
+            {
+                Value = enumValue;
+                return true;
+            }
+        }
+        
+        return false;
+    }
 }

+ 5 - 0
src/PixiEditor/Helpers/SupportedFilesHelper.cs

@@ -92,4 +92,9 @@ internal class SupportedFilesHelper
         var any = new FileTypeDialogDataSet(FileTypeDialogDataSet.SetKind.Any).GetFormattedTypes(true);
         return any.ToList();
     }
+
+    public static bool IsRasterFormat(string fileExtension)
+    {
+        return FileTypes.Any(i => i.Extensions.Contains(fileExtension) && i.SetKind == FileTypeDialogDataSet.SetKind.Image);
+    }
 }

+ 3 - 1
src/PixiEditor/Models/Files/SvgFileType.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Rendering;
+using Avalonia.Media;
+using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.IO;
 using Drawie.Numerics;
@@ -15,6 +16,7 @@ internal class SvgFileType : IoFileType
     public override string[] Extensions { get; } = new[] { ".svg" };
     public override string DisplayName { get; } = "Scalable Vector Graphics";
     public override FileTypeDialogDataSet.SetKind SetKind { get; } = FileTypeDialogDataSet.SetKind.Vector;
+    public override SolidColorBrush EditorColor { get; } = new SolidColorBrush(Color.FromRgb(0, 128, 0));
 
     public override async Task<SaveResult> TrySave(string pathWithExtension, DocumentViewModel document, ExportConfig config, ExportJob? job)
     {

+ 55 - 9
src/PixiEditor/Models/IO/CustomDocumentFormats/SvgDocumentBuilder.cs

@@ -1,10 +1,15 @@
-using Drawie.Numerics;
+using Drawie.Backend.Core.Vector;
+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.Extensions.Common.Localization;
 using PixiEditor.Helpers;
+using PixiEditor.Parser.Graph;
 using PixiEditor.SVG;
 using PixiEditor.SVG.Elements;
+using PixiEditor.SVG.Enums;
+using PixiEditor.ViewModels.Tools.Tools;
 
 namespace PixiEditor.Models.IO.CustomDocumentFormats;
 
@@ -23,22 +28,26 @@ internal class SvgDocumentBuilder : IDocumentBuilder
                 int? lastId = null;
                 foreach (SvgElement element in document.Children)
                 {
+                    LocalizedString name = "";
                     if (element is SvgPrimitive primitive)
                     {
                         ShapeVectorData shapeData = null;
                         if (element is SvgEllipse or SvgCircle)
                         {
                             shapeData = AddEllipse(element);
+                            name = VectorEllipseToolViewModel.NewLayerKey;
                         }
-                        /*else if (element is SvgLine line)
+                        else if (element is SvgLine line)
                         {
-                            AddLine(graph, line);
+                            shapeData = AddLine(line);
+                            name = VectorLineToolViewModel.NewLayerKey;
                         }
                         else if (element is SvgPath pathElement)
                         {
-                            AddPath(graph, pathElement);
+                            shapeData = AddPath(pathElement);
+                            name = VectorPathToolViewModel.NewLayerKey;
                         }
-                        else if (element is SvgRectangle rect)
+                        /*else if (element is SvgRectangle rect)
                         {
                             AddRect(graph, rect);
                         }
@@ -49,13 +58,26 @@ internal class SvgDocumentBuilder : IDocumentBuilder
 
                         AddCommonShapeData(primitive, shapeData);
 
-                        graph.WithNodeOfType<VectorLayerNode>(out int id)
+                        NodeGraphBuilder.NodeBuilder nBuilder = graph.WithNodeOfType<VectorLayerNode>(out int id)
+                            .WithName(name)
                             .WithAdditionalData(new Dictionary<string, object>() { { "ShapeData", shapeData } });
-                        
+
+                        if (lastId != null)
+                        {
+                            nBuilder.WithConnections([
+                                new PropertyConnection()
+                                {
+                                    InputPropertyName = "Background",
+                                    OutputPropertyName = "Output",
+                                    OutputNodeId = lastId.Value
+                                }
+                            ]);
+                        }
+
                         lastId = id;
                     }
                 }
-                
+
                 graph.WithOutputNode(lastId, "Output");
             });
     }
@@ -79,6 +101,30 @@ internal class SvgDocumentBuilder : IDocumentBuilder
         return null;
     }
 
+    private LineVectorData AddLine(SvgLine element)
+    {
+        return new LineVectorData(
+            new VecD(element.X1.Unit.Value.Value, element.Y1.Unit.Value.Value),
+            new VecD(element.X2.Unit.Value.Value, element.Y2.Unit.Value.Value));
+    }
+
+    private PathVectorData AddPath(SvgPath element)
+    {
+        VectorPath path = VectorPath.FromSvgPath(element.PathData.Unit.Value.Value);
+        
+        if(element.FillRule.Unit != null)
+        {
+            path.FillType = element.FillRule.Unit.Value.Value switch
+            {
+                SvgFillRule.EvenOdd => PathFillType.EvenOdd,
+                SvgFillRule.NonZero => PathFillType.Winding,
+                _ => PathFillType.Winding
+            };
+        }
+        
+        return new PathVectorData(path);
+    }
+
     private void AddCommonShapeData(SvgPrimitive primitive, ShapeVectorData? shapeData)
     {
         if (shapeData == null)
@@ -90,9 +136,9 @@ internal class SvgDocumentBuilder : IDocumentBuilder
         bool hasStroke = primitive.Stroke.Unit is { Color.A: > 0 };
         bool hasTransform = primitive.Transform.Unit is { MatrixValue.IsIdentity: false };
 
+        shapeData.Fill = hasFill;
         if (hasFill)
         {
-            shapeData.Fill = true;
             shapeData.FillColor = primitive.Fill.Unit.Value.Color;
         }
 

+ 26 - 27
src/PixiEditor/ViewModels/Document/DocumentViewModel.Serialization.cs

@@ -18,6 +18,7 @@ using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Backend.Core.Vector;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Handlers;
@@ -148,17 +149,22 @@ internal partial class DocumentViewModel
             elementToAdd = AddVectorPath(shapeData);
         }
 
-        if (vectorNode.ShapeData != null)
-        {
-            IReadOnlyShapeVectorData data = vectorNode.ShapeData;
-
-            if (data != null && elementToAdd is SvgPrimitive primitive)
-            {
-                Matrix3X3 transform = data.TransformationMatrix;
+        IReadOnlyShapeVectorData data = vectorNode.ShapeData;
 
-                transform = transform.PostConcat(Matrix3X3.CreateScale((float)resizeFactor.X, (float)resizeFactor.Y));
-                primitive.Transform.Unit = new SvgTransformUnit?(new SvgTransformUnit(transform));
-            }
+        if (data != null && elementToAdd is SvgPrimitive primitive)
+        {
+            Matrix3X3 transform = data.TransformationMatrix;
+
+            transform = transform.PostConcat(Matrix3X3.CreateScale((float)resizeFactor.X, (float)resizeFactor.Y));
+            primitive.Transform.Unit = new SvgTransformUnit?(new SvgTransformUnit(transform));
+
+            primitive.Fill.Unit = SvgColorUnit.FromRgba(data.FillColor.R, data.FillColor.G,
+                data.FillColor.B, data.Fill ? data.FillColor.A : 0);
+                
+            primitive.Stroke.Unit = SvgColorUnit.FromRgba(data.StrokeColor.R, data.StrokeColor.G,
+                data.StrokeColor.B, data.StrokeColor.A);
+                
+            primitive.StrokeWidth.Unit = SvgNumericUnit.FromUserUnits(data.StrokeWidth);
         }
 
         if (elementToAdd != null)
@@ -178,7 +184,7 @@ internal partial class DocumentViewModel
         line.Stroke.Unit = SvgColorUnit.FromRgba(lineData.StrokeColor.R, lineData.StrokeColor.G,
             lineData.StrokeColor.B, lineData.StrokeColor.A);
         line.StrokeWidth.Unit = SvgNumericUnit.FromUserUnits(lineData.StrokeWidth);
-        
+
         return line;
     }
 
@@ -189,11 +195,6 @@ internal partial class DocumentViewModel
         ellipse.Cy.Unit = SvgNumericUnit.FromUserUnits(ellipseData.Center.Y);
         ellipse.Rx.Unit = SvgNumericUnit.FromUserUnits(ellipseData.Radius.X);
         ellipse.Ry.Unit = SvgNumericUnit.FromUserUnits(ellipseData.Radius.Y);
-        ellipse.Fill.Unit = SvgColorUnit.FromRgba(ellipseData.FillColor.R, ellipseData.FillColor.G,
-            ellipseData.FillColor.B, ellipseData.FillColor.A);
-        ellipse.Stroke.Unit = SvgColorUnit.FromRgba(ellipseData.StrokeColor.R, ellipseData.StrokeColor.G,
-            ellipseData.StrokeColor.B, ellipseData.StrokeColor.A);
-        ellipse.StrokeWidth.Unit = SvgNumericUnit.FromUserUnits(ellipseData.StrokeWidth);
 
         return ellipse;
     }
@@ -212,11 +213,6 @@ internal partial class DocumentViewModel
 
         rect.Width.Unit = SvgNumericUnit.FromUserUnits(rectangleData.Size.X);
         rect.Height.Unit = SvgNumericUnit.FromUserUnits(rectangleData.Size.Y);
-        rect.Fill.Unit = SvgColorUnit.FromRgba(rectangleData.FillColor.R, rectangleData.FillColor.G,
-            rectangleData.FillColor.B, rectangleData.FillColor.A);
-        rect.Stroke.Unit = SvgColorUnit.FromRgba(rectangleData.StrokeColor.R, rectangleData.StrokeColor.G,
-            rectangleData.StrokeColor.B, rectangleData.StrokeColor.A);
-        rect.StrokeWidth.Unit = SvgNumericUnit.FromUserUnits(rectangleData.StrokeWidth);
 
         return rect;
     }
@@ -228,12 +224,15 @@ internal partial class DocumentViewModel
         {
             string pathData = data.Path.ToSvgPathData();
             path.PathData.Unit = new SvgStringUnit(pathData);
-
-            path.Fill.Unit =
-                SvgColorUnit.FromRgba(data.FillColor.R, data.FillColor.G, data.FillColor.B, data.FillColor.A);
-            path.Stroke.Unit = SvgColorUnit.FromRgba(data.StrokeColor.R, data.StrokeColor.G, data.StrokeColor.B,
-                data.StrokeColor.A);
-            path.StrokeWidth.Unit = SvgNumericUnit.FromUserUnits(data.StrokeWidth);
+            SvgFillRule fillRule = data.Path.FillType switch
+            {
+                PathFillType.EvenOdd => SvgFillRule.EvenOdd,
+                PathFillType.Winding => SvgFillRule.NonZero,
+                PathFillType.InverseWinding => SvgFillRule.NonZero,
+                PathFillType.InverseEvenOdd => SvgFillRule.EvenOdd,
+            };
+            
+            path.FillRule.Unit = new SvgEnumUnit<SvgFillRule>(fillRule);
         }
 
         return path;

+ 2 - 1
src/PixiEditor/ViewModels/Tools/Tools/VectorEllipseToolViewModel.cs

@@ -15,6 +15,7 @@ namespace PixiEditor.ViewModels.Tools.Tools;
 [Command.Tool(Key = Key.C)]
 internal class VectorEllipseToolViewModel : ShapeTool, IVectorEllipseToolHandler
 {
+    public const string NewLayerKey = "NEW_ELLIPSE_LAYER_NAME"; 
     private string defaultActionDisplay = "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT";
     public override string ToolNameLocalizationKey => "ELLIPSE_TOOL";
 
@@ -33,7 +34,7 @@ internal class VectorEllipseToolViewModel : ShapeTool, IVectorEllipseToolHandler
 
     public override Type LayerTypeToCreateOnEmptyUse { get; } = typeof(VectorLayerNode);
 
-    public string? DefaultNewLayerName { get; } = new LocalizedString("NEW_ELLIPSE_LAYER_NAME");
+    public string? DefaultNewLayerName { get; } = new LocalizedString(NewLayerKey);
 
     public override void UseTool(VecD pos)
     {

+ 2 - 1
src/PixiEditor/ViewModels/Tools/Tools/VectorLineToolViewModel.cs

@@ -19,6 +19,7 @@ namespace PixiEditor.ViewModels.Tools.Tools;
 [Command.Tool(Key = Key.L)]
 internal class VectorLineToolViewModel : ShapeTool, IVectorLineToolHandler
 {
+    public const string NewLayerKey = "NEW_LINE_LAYER_NAME";
     private string defaultActionDisplay = "LINE_TOOL_ACTION_DISPLAY_DEFAULT";
 
     public override bool IsErasable => false;
@@ -34,7 +35,7 @@ internal class VectorLineToolViewModel : ShapeTool, IVectorLineToolHandler
 
     public override string DefaultIcon => PixiPerfectIcons.Line;
     public override Type[]? SupportedLayerTypes { get; } = [];
-    public string? DefaultNewLayerName { get; } = new LocalizedString("NEW_LINE_LAYER_NAME");
+    public string? DefaultNewLayerName { get; } = new LocalizedString(NewLayerKey);
 
     [Settings.Inherited] 
     public double ToolSize => GetValue<double>();

+ 2 - 1
src/PixiEditor/ViewModels/Tools/Tools/VectorPathToolViewModel.cs

@@ -15,12 +15,13 @@ namespace PixiEditor.ViewModels.Tools.Tools;
 [Command.Tool(Key = Key.P)]
 internal class VectorPathToolViewModel : ShapeTool, IVectorPathToolHandler
 {
+    public const string NewLayerKey = "DEFAULT_PATH_LAYER_NAME";
     public override string ToolNameLocalizationKey => "PATH_TOOL";
     public override Type[]? SupportedLayerTypes { get; } = [typeof(IVectorLayerHandler)];
     public override Type LayerTypeToCreateOnEmptyUse { get; } = typeof(VectorLayerNode);
     public override LocalizedString Tooltip => new LocalizedString("PATH_TOOL_TOOLTIP", Shortcut);
 
-    public string? DefaultNewLayerName => new LocalizedString("DEFAULT_PATH_LAYER_NAME");
+    public string? DefaultNewLayerName => new LocalizedString(NewLayerKey);
 
     public override string DefaultIcon => PixiPerfectIcons.VectorPen;
     public override bool StopsLinkedToolOnUse => false;

+ 22 - 20
src/PixiEditor/Views/Visuals/PixiFilePreviewImage.cs

@@ -1,19 +1,15 @@
-using System.Drawing;
-using Avalonia;
+using Avalonia;
 using Avalonia.Threading;
-using FFMpegCore.Enums;
 using Drawie.Backend.Core;
 using PixiEditor.Extensions.Exceptions;
 using PixiEditor.Helpers;
 using PixiEditor.Models;
 using PixiEditor.Models.IO;
 using Drawie.Numerics;
-using PixiEditor.Parser;
-using Image = Avalonia.Controls.Image;
 
 namespace PixiEditor.Views.Visuals;
 
-internal class PixiFilePreviewImage : SurfaceControl
+internal class PixiFilePreviewImage : TextureControl
 {
     public static readonly StyledProperty<string> FilePathProperty =
         AvaloniaProperty.Register<PixiFilePreviewImage, string>(nameof(FilePath));
@@ -56,18 +52,18 @@ internal class PixiFilePreviewImage : SurfaceControl
 
     private void LoadImage(string path)
     {
-        var surface = LoadPreviewSurface(path);
+        var surface = LoadPreviewTexture(path);
 
         Dispatcher.UIThread.Post(() => SetImage(surface));
     }
 
-    private void SetImage(Surface? surface)
+    private void SetImage(Texture? texture)
     {
-        Surface = surface!;
+        Texture = texture!;
 
-        if (surface != null)
+        if (texture != null)
         {
-            ImageSize = surface.Size;
+            ImageSize = texture.Size;
         }
     }
 
@@ -75,14 +71,14 @@ internal class PixiFilePreviewImage : SurfaceControl
     {
         if (args.NewValue == null)
         {
-            previewImage.Surface = null;
+            previewImage.Texture = null;
             return;
         }
 
         previewImage.RunLoadImage();
     }
 
-    private Surface? LoadPreviewSurface(string filePath)
+    private Texture? LoadPreviewTexture(string filePath)
     {
         if (!File.Exists(filePath))
         {
@@ -96,7 +92,7 @@ internal class PixiFilePreviewImage : SurfaceControl
             return LoadPixiPreview(filePath);
         }
 
-        if (SupportedFilesHelper.IsExtensionSupported(fileExtension))
+        if (SupportedFilesHelper.IsExtensionSupported(fileExtension) && SupportedFilesHelper.IsRasterFormat(fileExtension))
         {
             return LoadNonPixiPreview(filePath);
         }
@@ -105,11 +101,11 @@ internal class PixiFilePreviewImage : SurfaceControl
 
     }
 
-    private Surface LoadPixiPreview(string filePath)
+    private Texture LoadPixiPreview(string filePath)
     {
         try
         {
-            var loaded = Importer.GetPreviewSurface(filePath);
+            var loaded = Importer.GetPreviewTexture(filePath);
 
             if (loaded.Size is { X: <= Constants.MaxPreviewWidth, Y: <= Constants.MaxPreviewHeight })
             {
@@ -127,13 +123,13 @@ internal class PixiFilePreviewImage : SurfaceControl
         }
     }
 
-    private Surface LoadNonPixiPreview(string filePath)
+    private Texture LoadNonPixiPreview(string filePath)
     {
-        Surface loaded = null;
+        Texture loaded = null;
 
         try
         {
-            loaded = Surface.Load(filePath);
+            loaded = Texture.Load(filePath);
         }
         catch (RecoverableException)
         {
@@ -154,7 +150,7 @@ internal class PixiFilePreviewImage : SurfaceControl
 
     }
 
-    private static Surface DownscaleSurface(Surface surface)
+    private static Texture DownscaleSurface(Texture surface)
     {
         double factor = Math.Min(
             Constants.MaxPreviewWidth / (double)surface.Size.X,
@@ -173,4 +169,10 @@ internal class PixiFilePreviewImage : SurfaceControl
     {
         Dispatcher.UIThread.Post(() => Corrupt = true);
     }
+
+    protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+    {
+        base.OnDetachedFromVisualTree(e);
+        Texture?.Dispose();
+    }
 }