Browse Source

Added deserialization to svg images

Krzysztof Krysiński 4 months ago
parent
commit
9900abe497

+ 5 - 0
src/PixiEditor.SVG/StyleContext.cs

@@ -20,6 +20,7 @@ public struct StyleContext
     public SvgProperty<SvgNumericUnit> Opacity { get; }
     public SvgProperty<SvgNumericUnit> Opacity { get; }
     public SvgProperty<SvgStyleUnit> InlineStyle { get; set; }
     public SvgProperty<SvgStyleUnit> InlineStyle { get; set; }
     public VecD ViewboxOrigin { get; set; }
     public VecD ViewboxOrigin { get; set; }
+    public VecD ViewboxSize { get; set; }
     public SvgDefs Defs { get; set; }
     public SvgDefs Defs { get; set; }
 
 
     public StyleContext()
     public StyleContext()
@@ -50,6 +51,9 @@ public struct StyleContext
         ViewboxOrigin = new VecD(
         ViewboxOrigin = new VecD(
             document.ViewBox.Unit.HasValue ? -document.ViewBox.Unit.Value.Value.X : 0,
             document.ViewBox.Unit.HasValue ? -document.ViewBox.Unit.Value.Value.X : 0,
             document.ViewBox.Unit.HasValue ? -document.ViewBox.Unit.Value.Value.Y : 0);
             document.ViewBox.Unit.HasValue ? -document.ViewBox.Unit.Value.Value.Y : 0);
+        ViewboxSize = new VecD(
+            document.ViewBox.Unit.HasValue ? document.ViewBox.Unit.Value.Value.Width : 0,
+            document.ViewBox.Unit.HasValue ? document.ViewBox.Unit.Value.Value.Height : 0);
         InlineStyle = document.Style;
         InlineStyle = document.Style;
         Defs = document.Defs;
         Defs = document.Defs;
     }
     }
@@ -159,6 +163,7 @@ public struct StyleContext
         }
         }
 
 
         styleContext.ViewboxOrigin = ViewboxOrigin;
         styleContext.ViewboxOrigin = ViewboxOrigin;
+        styleContext.ViewboxSize = ViewboxSize;
 
 
         if (InlineStyle.Unit != null)
         if (InlineStyle.Unit != null)
         {
         {

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

@@ -103,7 +103,7 @@ public class SvgElement(string tagName)
         do
         do
         {
         {
             SvgProperty matchingProperty = properties.FirstOrDefault(x =>
             SvgProperty matchingProperty = properties.FirstOrDefault(x =>
-                string.Equals(x.SvgName, reader.Name, StringComparison.OrdinalIgnoreCase));
+                string.Equals(x.SvgFullName, reader.Name, StringComparison.OrdinalIgnoreCase));
             if (matchingProperty != null)
             if (matchingProperty != null)
             {
             {
                 ParseAttribute(matchingProperty, reader, defs);
                 ParseAttribute(matchingProperty, reader, defs);

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

@@ -18,6 +18,7 @@ public abstract class SvgProperty
     public string? NamespaceName { get; set; }
     public string? NamespaceName { get; set; }
     public string SvgName { get; set; }
     public string SvgName { get; set; }
     public ISvgUnit? Unit { get; set; }
     public ISvgUnit? Unit { get; set; }
+    public string? SvgFullName => NamespaceName == null ? SvgName : $"{NamespaceName}:{SvgName}";
 
 
     public ISvgUnit? CreateDefaultUnit()
     public ISvgUnit? CreateDefaultUnit()
     {
     {

+ 102 - 5
src/PixiEditor/Models/IO/CustomDocumentFormats/SvgDocumentBuilder.cs

@@ -1,6 +1,8 @@
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
+using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Text;
 using Drawie.Backend.Core.Text;
 using Drawie.Backend.Core.Vector;
 using Drawie.Backend.Core.Vector;
@@ -29,7 +31,7 @@ internal class SvgDocumentBuilder : IDocumentBuilder
         string xml = File.ReadAllText(path);
         string xml = File.ReadAllText(path);
         SvgDocument document = SvgDocument.Parse(xml);
         SvgDocument document = SvgDocument.Parse(xml);
 
 
-        if(document == null)
+        if (document == null)
         {
         {
             throw new SvgParsingException("Failed to parse SVG document");
             throw new SvgParsingException("Failed to parse SVG document");
         }
         }
@@ -58,6 +60,10 @@ internal class SvgDocumentBuilder : IDocumentBuilder
                     {
                     {
                         lastId = AddGroup(group, graph, style, lastId);
                         lastId = AddGroup(group, graph, style, lastId);
                     }
                     }
+                    else if (element is SvgImage svgImage)
+                    {
+                        lastId = AddImage(svgImage, style, graph, lastId);
+                    }
                 }
                 }
 
 
                 graph.WithOutputNode(lastId, "Output");
                 graph.WithOutputNode(lastId, "Output");
@@ -103,7 +109,10 @@ internal class SvgDocumentBuilder : IDocumentBuilder
 
 
         NodeGraphBuilder.NodeBuilder nBuilder = graph.WithNodeOfType<VectorLayerNode>(out int id)
         NodeGraphBuilder.NodeBuilder nBuilder = graph.WithNodeOfType<VectorLayerNode>(out int id)
             .WithName(name)
             .WithName(name)
-            .WithInputValues(new Dictionary<string, object>() { { StructureNode.OpacityPropertyName, (float)(styleContext.Opacity.Unit?.Value ?? 1f) } })
+            .WithInputValues(new Dictionary<string, object>()
+            {
+                { StructureNode.OpacityPropertyName, (float)(styleContext.Opacity.Unit?.Value ?? 1f) }
+            })
             .WithAdditionalData(new Dictionary<string, object>() { { "ShapeData", shapeData } });
             .WithAdditionalData(new Dictionary<string, object>() { { "ShapeData", shapeData } });
 
 
         if (lastId != null)
         if (lastId != null)
@@ -137,6 +146,10 @@ internal class SvgDocumentBuilder : IDocumentBuilder
             {
             {
                 childId = AddGroup(childGroup, graph, childStyle, childId, connectTo);
                 childId = AddGroup(childGroup, graph, childStyle, childId, connectTo);
             }
             }
+            else if (child is SvgImage image)
+            {
+                childId = AddImage(image, childStyle, graph, childId);
+            }
         }
         }
 
 
         NodeGraphBuilder.NodeBuilder nBuilder = graph.WithNodeOfType<FolderNode>(out int id)
         NodeGraphBuilder.NodeBuilder nBuilder = graph.WithNodeOfType<FolderNode>(out int id)
@@ -173,6 +186,87 @@ internal class SvgDocumentBuilder : IDocumentBuilder
         return lastId;
         return lastId;
     }
     }
 
 
+    private int? AddImage(SvgImage image, StyleContext style, NodeGraphBuilder graph, int? lastId)
+    {
+        byte[] bytes = TryReadImage(image.Href.Unit?.Value ?? "");
+
+        Surface? imgSurface = bytes is { Length: > 0 } ? Surface.Load(bytes) : null;
+        Surface? finalSurface = null;
+
+        if (style.ViewboxSize.ShortestAxis > 0)
+        {
+            finalSurface = new Surface((VecI)style.ViewboxSize);
+            double x = image.X.Unit?.PixelsValue ?? 0;
+            double y = image.Y.Unit?.PixelsValue ?? 0;
+            finalSurface.DrawingSurface.Canvas.DrawSurface(imgSurface.DrawingSurface, (int)x, (int)y);
+            imgSurface.Dispose();
+
+            if (finalSurface.Size.X != (int)image.Width.Unit?.PixelsValue || finalSurface.Size.Y != (int)image.Height.Unit?.PixelsValue)
+            {
+                var resized = finalSurface.ResizeNearestNeighbor((VecI)style.ViewboxSize);
+                finalSurface.Dispose();
+                finalSurface = resized;
+            }
+
+        }
+
+        var graphBuilder = graph.WithImageLayerNode(
+            image.Id.Unit?.Value ?? new LocalizedString("NEW_LAYER").Value,
+            finalSurface, ColorSpace.CreateSrgb(), out int id);
+
+        if (lastId != null)
+        {
+            var nodeBuilder = graphBuilder.AllNodes[^1];
+
+            Dictionary<string, object> inputValues = new()
+            {
+                { StructureNode.OpacityPropertyName, (float)(style.Opacity.Unit?.Value ?? 1f) }
+            };
+
+            nodeBuilder.WithInputValues(inputValues);
+            nodeBuilder.WithConnections([
+                new PropertyConnection()
+                {
+                    InputPropertyName = "Background", OutputPropertyName = "Output", OutputNodeId = lastId.Value
+                }
+            ]);
+        }
+
+        lastId = id;
+
+        return lastId;
+    }
+
+    private byte[] TryReadImage(string svgHref)
+    {
+        if (string.IsNullOrEmpty(svgHref))
+        {
+            return [];
+        }
+
+        if (svgHref.StartsWith("data:image/png;base64,"))
+        {
+            return Convert.FromBase64String(svgHref.Replace("data:image/png;base64,", ""));
+        }
+
+        // TODO: Implement downloading images from the internet
+        /*if (Uri.TryCreate(svgHref, UriKind.Absolute, out Uri? uri))
+        {
+            try
+            {
+                using WebClient client = new();
+                return client.DownloadData(uri);
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine(e);
+                return [];
+            }
+        }*/
+
+        return [];
+    }
+
     private EllipseVectorData AddEllipse(SvgElement element)
     private EllipseVectorData AddEllipse(SvgElement element)
     {
     {
         if (element is SvgCircle circle)
         if (element is SvgCircle circle)
@@ -242,9 +336,11 @@ internal class SvgDocumentBuilder : IDocumentBuilder
 
 
     private TextVectorData AddText(SvgText element)
     private TextVectorData AddText(SvgText element)
     {
     {
-        Font font = element.FontFamily.Unit.HasValue ? Font.FromFamilyName(element.FontFamily.Unit.Value.Value) : Font.CreateDefault();
+        Font font = element.FontFamily.Unit.HasValue
+            ? Font.FromFamilyName(element.FontFamily.Unit.Value.Value)
+            : Font.CreateDefault();
         FontFamilyName? missingFont = null;
         FontFamilyName? missingFont = null;
-        if(font == null)
+        if (font == null)
         {
         {
             font = Font.CreateDefault();
             font = Font.CreateDefault();
             missingFont = new FontFamilyName(element.FontFamily.Unit.Value.Value);
             missingFont = new FontFamilyName(element.FontFamily.Unit.Value.Value);
@@ -273,7 +369,8 @@ internal class SvgDocumentBuilder : IDocumentBuilder
         }
         }
 
 
         bool hasFill = styleContext.Fill.Unit?.Paintable is { AnythingVisible: true };
         bool hasFill = styleContext.Fill.Unit?.Paintable is { AnythingVisible: true };
-        bool hasStroke = styleContext.Stroke.Unit?.Paintable is { AnythingVisible: true } || styleContext.StrokeWidth.Unit is { PixelsValue: > 0 };
+        bool hasStroke = styleContext.Stroke.Unit?.Paintable is { AnythingVisible: true } ||
+                         styleContext.StrokeWidth.Unit is { PixelsValue: > 0 };
         bool hasTransform = styleContext.Transform.Unit is { MatrixValue.IsIdentity: false };
         bool hasTransform = styleContext.Transform.Unit is { MatrixValue.IsIdentity: false };
 
 
         shapeData.Fill = hasFill;
         shapeData.Fill = hasFill;

+ 1 - 0
src/PixiEditor/ViewModels/Document/DocumentViewModel.Serialization.cs

@@ -282,6 +282,7 @@ internal partial class DocumentViewModel
         });
         });
 
 
         var image = CreateImageElement(resizeFactor, tightBounds.Value, toSave, useNearestNeighborForImageUpscaling);
         var image = CreateImageElement(resizeFactor, tightBounds.Value, toSave, useNearestNeighborForImageUpscaling);
+        image.Id.Unit = new SvgStringUnit(member.NodeNameBindable);
 
 
         elementContainer.Children.Add(image);
         elementContainer.Children.Add(image);
     }
     }