Răsfoiți Sursa

Initial pattern node working

Krzysztof Krysiński 3 săptămâni în urmă
părinte
comite
771352c829

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit b62a0dca915a92c020e9e9145fb287fa69da4e64
+Subproject commit 83b43909a86caf85d2c326f99254b1e592206c30

+ 13 - 6
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs

@@ -54,9 +54,16 @@ public class CreateImageNode : Node, IPreviewRenderable
         RenderOutput.ChainToPainterValue();
     }
 
-    private Texture Render(RenderContext context)
+    private Texture? Render(RenderContext context)
     {
-        var surface = textureCache.RequestTexture(0, (VecI)(Size.Value * context.ChunkResolution.Multiplier()), context.ProcessingColorSpace, false);
+        var size = (VecI)(Size.Value * context.ChunkResolution.Multiplier());
+        if (size.X <= 0 || size.Y <= 0)
+        {
+            return null;
+        }
+
+        var surface = textureCache.RequestTexture(0, (VecI)(Size.Value * context.ChunkResolution.Multiplier()),
+            context.ProcessingColorSpace, false);
         surface.DrawingSurface.Canvas.SetMatrix(Matrix3X3.Identity);
 
         if (Fill.Value is ColorPaintable colorPaintable)
@@ -90,7 +97,7 @@ public class CreateImageNode : Node, IPreviewRenderable
 
     private void OnPaint(RenderContext context, DrawingSurface surface)
     {
-        if(Output.Value == null || Output.Value.IsDisposed) return;
+        if (Output.Value == null || Output.Value.IsDisposed) return;
 
         int saved = surface.Canvas.Save();
         surface.Canvas.Scale((float)context.ChunkResolution.InvertedMultiplier());
@@ -130,14 +137,14 @@ public class CreateImageNode : Node, IPreviewRenderable
         }
 
         var surface = Render(context);
-        
+
         if (surface == null || surface.IsDisposed)
         {
             return false;
         }
-        
+
         renderOn.Canvas.DrawSurface(surface.DrawingSurface, 0, 0);
-        
+
         return true;
     }
 }

+ 157 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Image/PatternNode.cs

@@ -0,0 +1,157 @@
+using Drawie.Backend.Core;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Backend.Core.Mesh;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Backend.Core.Vector;
+using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
+using PixiEditor.ChangeableDocument.Rendering;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Image;
+
+[NodeInfo("Pattern")]
+public class PatternNode : RenderNode
+{
+    public InputProperty<Texture> Fill { get; }
+    public InputProperty<double> Spacing { get; }
+    public InputProperty<ShapeVectorData> Path { get; }
+    public InputProperty<PatternAlignment> Alignment { get; }
+    public InputProperty<PatternStretching> Stretching { get; }
+
+    public PatternNode()
+    {
+        Fill = CreateInput<Texture>("Fill", "FILL", null);
+        Spacing = CreateInput<double>("Spacing", "SPACING", 0);
+        Path = CreateInput<ShapeVectorData>("Path", "PATH", null);
+        Alignment = CreateInput<PatternAlignment>("Alignment", "ALIGNMENT", PatternAlignment.Center);
+        Stretching = CreateInput<PatternStretching>("Stretching", "STRETCHING", PatternStretching.PlaceAlong);
+    }
+
+    protected override void OnPaint(RenderContext context, DrawingSurface surface)
+    {
+        float spacing = (float)Spacing.Value;
+
+        if(Fill.Value == null || Path.Value == null)
+            return;
+
+        if (spacing == 0)
+        {
+            spacing = Fill.Value.Size.X;
+        }
+
+        float distance = 0;
+
+        using var path = Path.Value.ToPath(true);
+        if (path == null)
+            return;
+
+        using Paint tilePaint = new Paint();
+        using var snapshot = Fill.Value.DrawingSurface.Snapshot();
+        using var shader = snapshot.ToShader();
+        tilePaint.Shader = shader;
+
+        while (distance < path.Length)
+        {
+            if (Stretching.Value == PatternStretching.PlaceAlong)
+            {
+                PlaceAlongPath(surface, snapshot, path, distance);
+            }
+            else if (Stretching.Value == PatternStretching.StretchToFit)
+            {
+                PlaceStretchToFit(surface, path, distance, spacing, tilePaint);
+            }
+
+
+            distance += spacing;
+        }
+    }
+
+    private void PlaceAlongPath(DrawingSurface surface, Drawie.Backend.Core.Surfaces.ImageData.Image image, VectorPath path, float distance)
+    {
+        var matrix = path.GetMatrixAtDistance(distance, false, PathMeasureMatrixMode.GetPositionAndTangent);
+        if (matrix == null)
+            return;
+
+        if (Alignment.Value == PatternAlignment.Center)
+        {
+            matrix = matrix.Concat(Matrix3X3.CreateTranslation(-Fill.Value.Size.X / 2f, -Fill.Value.Size.Y / 2f));
+        }
+        else if (Alignment.Value == PatternAlignment.Outside)
+        {
+            matrix = matrix.Concat(Matrix3X3.CreateTranslation(0, -Fill.Value.Size.Y));
+        }
+
+        surface.Canvas.Save();
+        surface.Canvas.SetMatrix(surface.Canvas.TotalMatrix.Concat(matrix));
+        surface.Canvas.DrawImage(image, 0, 0);
+        surface.Canvas.Restore();
+    }
+
+    private void PlaceStretchToFit(DrawingSurface surface, VectorPath path, float distance, float spacing, Paint tilePaint)
+    {
+        int texWidth = (int)Fill.Value.Size.X;
+        int texHeight = (int)Fill.Value.Size.Y;
+
+        // Iterate over each column of the texture (1px wide quads)
+        for (int x = 0; x < texWidth; x++)
+        {
+            float u0 = (float)x / texWidth;
+            float u1 = (float)(x + 1) / texWidth;
+
+            float d0 = distance + u0 * spacing;
+            float d1 = distance + u1 * spacing;
+
+            var startSegment = path.GetPositionAndTangentAtDistance(d0, false);
+            var endSegment   = path.GetPositionAndTangentAtDistance(d1, false);
+
+            var startNormal = new VecD(-startSegment.W, startSegment.Z).Normalize();
+            var endNormal   = new VecD(-endSegment.W, endSegment.Z).Normalize();
+
+            float halfHeight = texHeight / 2f;
+
+            // Quad corners following the path
+            var v0 = startSegment.XY - startNormal * halfHeight; // bottom-left
+            var v1 = startSegment.XY + startNormal * halfHeight; // top-left
+            var v2 = endSegment.XY + endNormal * halfHeight;     // top-right
+            var v3 = endSegment.XY - endNormal * halfHeight;     // bottom-right
+
+            var texCoords = new VecF[]
+            {
+                new(x, texHeight),
+                new(x, 0),
+                new(x + 1, 0),
+                new(x + 1, texHeight)
+            };
+
+            var verts = new VecF[] { (VecF)v0, (VecF)v1, (VecF)v2, (VecF)v3 };
+            var indices = new ushort[] { 0, 1, 2, 0, 2, 3 };
+            Color[] colors = { Colors.Transparent, Colors.Transparent, Colors.Transparent, Colors.Transparent };
+
+            using var vertices = new Vertices(VertexMode.Triangles, verts, texCoords, colors, indices);
+            surface.Canvas.DrawVertices(vertices, BlendMode.SrcOver, tilePaint);
+        }
+    }
+
+
+    public override Node CreateCopy()
+    {
+        return new PatternNode();
+    }
+}
+
+public enum PatternAlignment
+{
+   Center,
+   Outside,
+   Inside,
+}
+
+public enum PatternStretching
+{
+    PlaceAlong,
+    StretchToFit,
+}

+ 8 - 0
src/PixiEditor/Models/EnumTranslations.cs

@@ -9,6 +9,7 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Animable;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Effects;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Image;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Matrix;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Handlers.Toolbars;
@@ -127,3 +128,10 @@ using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 
 [assembly: LocalizeEnum<PaintBrushShape>(PaintBrushShape.Circle, "PAINT_BRUSH_SHAPE_CIRCLE")]
 [assembly: LocalizeEnum<PaintBrushShape>(PaintBrushShape.Square, "PAINT_BRUSH_SHAPE_SQUARE")]
+
+[assembly: LocalizeEnum<PatternAlignment>(PatternAlignment.Center, "CENTER_PATTERN_ALIGNMENT")]
+[assembly: LocalizeEnum<PatternAlignment>(PatternAlignment.Outside, "OUTSIDE_PATTERN_ALIGNMENT")]
+[assembly: LocalizeEnum<PatternAlignment>(PatternAlignment.Inside, "INSIDE_PATTERN_ALIGNMENT")]
+
+[assembly: LocalizeEnum<PatternStretching>(PatternStretching.StretchToFit, "STRETCH_TO_FIT_PATTERN_STRETCHING")]
+[assembly: LocalizeEnum<PatternStretching>(PatternStretching.PlaceAlong, "PLACE_ALONG_PATTERN_STRETCHING")]

+ 10 - 0
src/PixiEditor/ViewModels/Document/Nodes/Image/PatternNodeViewModel.cs

@@ -0,0 +1,10 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Image;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.Image;
+
+[NodeViewModel("Pattern", "IMAGE", null)]
+internal class PatternNodeViewModel : NodeViewModel<PatternNode>
+{
+
+}