浏览代码

Merge pull request #1223 from PixiEditor/pattern-node

Pattern node
Krzysztof Krysiński 1 周之前
父节点
当前提交
95f7f1dd9b

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit bc83f2b962f7e2ff4140bda8dd421aa2309f2488
+Subproject commit b7cc8bd1ee26e2d35e820a0b2a4a8dfdcb8b93ea

+ 9 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs

@@ -58,8 +58,14 @@ public class CreateImageNode : Node
         RenderOutput.ChainToPainterValue();
     }
 
-    private Texture Render(RenderContext context)
+    private Texture? Render(RenderContext context)
     {
+        var size = (VecI)(Size.Value * context.ChunkResolution.Multiplier());
+        if (size.X <= 0 || size.Y <= 0)
+        {
+            return null;
+        }
+
         int id = (Size.Value * context.ChunkResolution.Multiplier()).GetHashCode();
         var surface = textureCache.RequestTexture(id, (VecI)(Size.Value * context.ChunkResolution.Multiplier()), context.ProcessingColorSpace, false);
         surface.DrawingSurface.Canvas.SetMatrix(Matrix3X3.Identity);
@@ -81,6 +87,7 @@ public class CreateImageNode : Node
         RenderContext ctx = context.Clone();
         ctx.RenderSurface = surface.DrawingSurface.Canvas;
         ctx.RenderOutputSize = surface.Size;
+        ctx.VisibleDocumentRegion = null;
 
         float chunkMultiplier = (float)context.ChunkResolution.Multiplier();
 
@@ -96,7 +103,7 @@ public class CreateImageNode : Node
 
     private void OnPaint(RenderContext context, Canvas surface)
     {
-        if(Output.Value == null || Output.Value.IsDisposed) return;
+        if (Output.Value == null || Output.Value.IsDisposed) return;
 
         int saved = surface.Save();
         surface.Scale((float)context.ChunkResolution.InvertedMultiplier());

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

@@ -0,0 +1,166 @@
+using Drawie.Backend.Core;
+using Drawie.Backend.Core.ColorsImpl;
+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.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<Matrix3X3> FillMatrix { 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);
+        FillMatrix = CreateInput<Matrix3X3>("FillMatrix", "MATRIX", Matrix3X3.Identity);
+        Spacing = CreateInput<double>("Spacing", "SPACING_LABEL", 0)
+            .WithRules(x => x.Min(0d));
+        Path = CreateInput<ShapeVectorData>("Path", "SHAPE", null);
+        Alignment = CreateInput<PatternAlignment>("Alignment", "ALIGNMENT", PatternAlignment.Center);
+        Stretching = CreateInput<PatternStretching>("Stretching", "STRETCHING", PatternStretching.StretchToFit);
+    }
+
+    protected override void OnPaint(RenderContext context, Canvas 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(TileMode.Clamp, TileMode.Clamp, FillMatrix.Value);
+        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(Canvas 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.Save();
+        surface.SetMatrix(surface.TotalMatrix.Concat(matrix));
+        surface.DrawImage(image, 0, 0);
+        surface.Restore();
+    }
+
+    private void PlaceStretchToFit(Canvas surface, VectorPath path, float distance, float spacing,
+        Paint tilePaint)
+    {
+        int texWidth = Fill.Value.Size.X;
+        int texHeight = 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;
+
+            VecD start = new VecD(startSegment.X, startSegment.Y);
+            VecD end = new VecD(endSegment.X, endSegment.Y);
+
+            if (Alignment.Value == PatternAlignment.Inside)
+            {
+                start += startNormal * halfHeight;
+                end += endNormal * halfHeight;
+            }
+            else if (Alignment.Value == PatternAlignment.Outside)
+            {
+                start -= startNormal * halfHeight;
+                end -= endNormal * halfHeight;
+            }
+
+            var v0 = start - startNormal * halfHeight;
+            var v1 = start + startNormal * halfHeight;
+            var v2 = end + endNormal * halfHeight;
+            var v3 = end - endNormal * halfHeight;
+
+            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.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/Data/Localization/Languages/en.json

@@ -1167,6 +1167,14 @@
   "LEVELS": "Levels",
   "RGB_POSTERIZATION_MODE": "RGB",
   "LUMINANCE_POSTERIZATION_MODE": "Luminance",
+  "ALIGNMENT": "Alignment",
+  "STRETCHING": "Stretching",
+  "STRETCH_TO_FIT_PATTERN_STRETCHING": "Stretch to fit",
+  "PLACE_ALONG_PATTERN_STRETCHING": "Place along",
+  "CENTER_PATTERN_ALIGNMENT": "Center",
+  "OUTSIDE_PATTERN_ALIGNMENT": "Outside",
+  "INSIDE_PATTERN_ALIGNMENT": "Inside",
+  "PATTERN_NODE": "Pattern on Path",
   "DECIMAL_NUMBER": "Decimal Number",
   "WHOLE_NUMBER": "Whole Number",
   "BOOLEAN": "Boolean (true/false)",

+ 7 - 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,6 +128,12 @@ using DrawingBlendMode = Drawie.Backend.Core.Surfaces.BlendMode;
 [assembly: LocalizeEnum<BlendMode>(BlendMode.Erase, "ERASE_BLEND_MODE")]
 [assembly: LocalizeEnum<BlendMode>(BlendMode.LinearDodge, "LINEAR_DODGE_BLEND_MODE")]
 
+[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")]
 [assembly: LocalizeEnum<GradientType>(GradientType.Linear, "LINEAR_GRADIENT_TYPE")]
 [assembly: LocalizeEnum<GradientType>(GradientType.Radial, "RADIAL_GRADIENT_TYPE")]
 [assembly: LocalizeEnum<GradientType>(GradientType.Conical, "CONICAL_GRADIENT_TYPE")]

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

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Image;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.Image;
+
+[NodeViewModel("PATTERN_NODE", "IMAGE", PixiPerfectIcons.Stamp)]
+internal class PatternNodeViewModel : NodeViewModel<PatternNode>;