Browse Source

Basic brush stuff is working

Krzysztof Krysiński 3 weeks ago
parent
commit
81f275bdee

+ 2 - 2
src/ChunkyImageLib/ChunkyImage.cs

@@ -745,12 +745,12 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
     /// <param name="customBounds">Bounds used for affected chunks, will be computed from path in O(n) if null is passed</param>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     public void EnqueueDrawPath(VectorPath path, Paintable paintable, float strokeWidth, StrokeCap strokeCap,
-        BlendMode blendMode, PaintStyle style, RectI? customBounds = null)
+        BlendMode blendMode, PaintStyle style, bool antiAliasing, RectI? customBounds = null)
     {
         lock (lockObject)
         {
             ThrowIfDisposed();
-            PathOperation operation = new(path, paintable, strokeWidth, strokeCap, blendMode, style, customBounds);
+            PathOperation operation = new(path, paintable, strokeWidth, strokeCap, blendMode, style, antiAliasing, customBounds);
             EnqueueOperation(operation);
         }
     }

+ 5 - 2
src/ChunkyImageLib/Operations/PathOperation.cs

@@ -15,6 +15,8 @@ internal class PathOperation : IMirroredDrawOperation
     private readonly Paint paint;
     private readonly RectI bounds;
 
+    private bool antiAliasing;
+
     public bool IgnoreEmptyChunks => false;
 
     public PathOperation(VectorPath path, Color color, float strokeWidth, StrokeCap cap, BlendMode blendMode, RectI? customBounds = null)
@@ -26,8 +28,9 @@ internal class PathOperation : IMirroredDrawOperation
         bounds = floatBounds.Inflate((int)Math.Ceiling(strokeWidth) + 1);
     }
 
-    public PathOperation(VectorPath path, Paintable paintable, float strokeWidth, StrokeCap cap, BlendMode blendMode, PaintStyle style, RectI? customBounds = null)
+    public PathOperation(VectorPath path, Paintable paintable, float strokeWidth, StrokeCap cap, BlendMode blendMode, PaintStyle style, bool antiAliasing, RectI? customBounds = null)
     {
+        this.antiAliasing = antiAliasing;
         this.path = new VectorPath(path);
         paint = new() { Paintable = paintable, Style = style, StrokeWidth = strokeWidth, StrokeCap = cap, BlendMode = blendMode };
 
@@ -37,7 +40,7 @@ internal class PathOperation : IMirroredDrawOperation
 
     public void DrawOnChunk(Chunk targetChunk, VecI chunkPos)
     {
-        paint.IsAntiAliased = targetChunk.Resolution != ChunkResolution.Full;
+        paint.IsAntiAliased = antiAliasing || targetChunk.Resolution != ChunkResolution.Full;
         var surf = targetChunk.Surface.DrawingSurface;
         surf.Canvas.Save();
         surf.Canvas.Scale((float)targetChunk.Resolution.Multiplier());

+ 0 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Brushes/BrushOutputNode.cs

@@ -9,7 +9,6 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Brushes;
 public class BrushOutputNode : Node
 {
     public InputProperty<ShapeVectorData> VectorShape { get; }
-    public InputProperty<Paintable> Paint { get; }
 
     public BrushOutputNode()
     {

+ 16 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/Matrix3X3BaseNode.cs

@@ -5,6 +5,7 @@ using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Context;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using PixiEditor.ChangeableDocument.Rendering;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Matrix;
@@ -13,21 +14,36 @@ public abstract class Matrix3X3BaseNode : RenderNode, IRenderInput
 {
     public RenderInputProperty Background { get; }
     public FuncInputProperty<Float3x3> Input { get; }
+    public InputProperty<ShapeVectorData> InputVector { get; }
     public FuncOutputProperty<Float3x3> Matrix { get; }
+    public OutputProperty<ShapeVectorData> OutputVector { get; }
 
     public Matrix3X3BaseNode()
     {
         Background = CreateRenderInput("Background", "IMAGE");
         Input = CreateFuncInput<Float3x3>("Input", "INPUT_MATRIX",
             new Float3x3("") { ConstantValue = Matrix3X3.Identity });
+        InputVector = CreateInput<ShapeVectorData>("VectorShape", "SHAPE_LABEL", null);
         Matrix = CreateFuncOutput<Float3x3>("Matrix", "OUTPUT_MATRIX",
             (c) => CalculateMatrix(c, c.GetValue(Input)));
+
+        OutputVector = CreateOutput<ShapeVectorData>("OutputVector", "OUTPUT_VECTOR", null);
         Output.FirstInChain = null;
         AllowHighDpiRendering = true;
     }
 
     protected override void OnExecute(RenderContext context)
     {
+        if (InputVector.Value != null)
+        {
+            var clone = InputVector.Value.Clone() as ShapeVectorData;
+            Matrix3X3 mtx = Matrix.Value.Invoke(FuncContext.NoContext).GetConstant() as Matrix3X3? ??
+                            Matrix3X3.Identity;
+
+            clone.TransformationMatrix = clone.TransformationMatrix.PostConcat(mtx);
+            OutputVector.Value = clone;
+        }
+
         if (Background.Value == null)
             return;
 

+ 43 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/PointerInfoNode.cs

@@ -0,0 +1,43 @@
+using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.ChangeableDocument.Rendering.ContextData;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+[NodeInfo("PointerInfo")]
+public class PointerInfoNode : Node
+{
+    public OutputProperty<double> Pressure { get; }
+    public OutputProperty<double> Twist { get; }
+    public OutputProperty<VecD> Tilt { get; }
+    public OutputProperty<VecD> MovementDirection { get; }
+    public OutputProperty<double> Rotation { get; }
+
+    public PointerInfoNode()
+    {
+        Pressure = CreateOutput<double>("Pressure", "PRESSURE", 1.0);
+        Twist = CreateOutput<double>("Twist", "TWIST", 0.0);
+        Tilt = CreateOutput<VecD>("Tilt", "TILT", new VecD(0, 0));
+        MovementDirection = CreateOutput<VecD>("MovementDirection", "MOVEMENT_DIRECTION", new VecD(0, 0));
+        Rotation = CreateOutput<double>("Rotation", "ROTATION", 0.0);
+    }
+
+    protected override void OnExecute(RenderContext context)
+    {
+        if (!context.FullRerender && context.PointerInfo.Equals(default))
+        {
+            return;
+        }
+
+        Pressure.Value = context.PointerInfo.Pressure;
+        Twist.Value = context.PointerInfo.Twist;
+        Tilt.Value = context.PointerInfo.Tilt;
+        MovementDirection.Value = context.PointerInfo.MovementDirection;
+        Rotation.Value = context.PointerInfo.Rotation;
+    }
+
+    public override Node CreateCopy()
+    {
+        return new PointerInfoNode();
+    }
+}

+ 14 - 3
src/PixiEditor.ChangeableDocument/Changes/Drawing/LineBasedPen_UpdateableChange.cs

@@ -31,6 +31,7 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
     private int frame;
     private VecF lastPos;
     private int lastAppliedPointIndex = -1;
+    private BrushOutputNode? brushOutputNode;
 
     [GenerateUpdateableChangeActions]
     public LineBasedPen_UpdateableChange(Guid memberGuid, Color color, VecI pos, float strokeWidth, bool erasing,
@@ -79,6 +80,10 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
         }
 
         this.strokeWidth = strokeWidth;
+        if (brushOutputNode != null)
+        {
+            brushData = new BrushData(brushOutputNode.VectorShape.Value);
+        }
     }
 
     public override bool InitializeAndValidate(Document target)
@@ -95,7 +100,7 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
 
         if (brushOutputGuid != Guid.Empty)
         {
-            BrushOutputNode? brushOutputNode = target.FindNode<BrushOutputNode>(brushOutputGuid);
+            brushOutputNode = target.FindNode<BrushOutputNode>(brushOutputGuid);
             if (brushOutputNode != null)
             {
                 brushData = new BrushData(brushOutputNode.VectorShape.Value);
@@ -144,8 +149,14 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
                 var path = brushData.VectorShape.ToPath(true);
                 path.Offset(brushData.VectorShape.TransformedAABB.Pos - brushData.VectorShape.GeometryAABB.Pos);
                 path.Offset(rect.Center - path.Bounds.Center);
-                path.Transform(Matrix3X3.CreateScale(rect.Size.X / (float)path.Bounds.Width, rect.Size.Y / (float)path.Bounds.Height, (float)rect.Center.X, (float)rect.Center.Y));
-                image.EnqueueDrawPath(path, finalPaintable, 1, StrokeCap.Butt, blendMode, PaintStyle.StrokeAndFill);
+                /*VecD scale = new VecD(rect.Size.X / (float)path.Bounds.Width, rect.Size.Y / (float)path.Bounds.Height);
+                if (scale.IsNaNOrInfinity())
+                {
+                    scale = VecD.Zero;
+                }
+                VecD uniformScale = new VecD(Math.Min(scale.X, scale.Y));
+                path.Transform(Matrix3X3.CreateScale((float)uniformScale.X, (float)uniformScale.Y, (float)rect.Center.X, (float)rect.Center.Y));*/
+                image.EnqueueDrawPath(path, finalPaintable, 1, StrokeCap.Butt, blendMode, PaintStyle.StrokeAndFill, true);
             }
         }
 

+ 23 - 0
src/PixiEditor.ChangeableDocument/Rendering/ContextData/PointerInfo.cs

@@ -0,0 +1,23 @@
+using Drawie.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Rendering.ContextData;
+
+public record struct PointerInfo
+{
+    public VecD PositionOnCanvas { get; }
+    public float Pressure { get; }
+    public float Twist { get; }
+    public VecD Tilt { get; }
+    public VecD MovementDirection { get; }
+    public double Rotation { get; }
+
+    public PointerInfo(VecD positionOnCanvas, float pressure, float twist, VecD tilt, VecD movementDirection)
+    {
+        PositionOnCanvas = positionOnCanvas;
+        Pressure = pressure;
+        Twist = twist;
+        Tilt = tilt;
+        MovementDirection = movementDirection;
+        Rotation = Math.Atan2(movementDirection.Y, movementDirection.X);
+    }
+}

+ 6 - 7
src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs

@@ -2,6 +2,7 @@
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Rendering.ContextData;
 using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 using DrawingApiBlendMode = Drawie.Backend.Core.Surfaces.BlendMode;
 
@@ -18,9 +19,10 @@ public class RenderContext
     public VecI DocumentSize { get; set; }
     public DrawingSurface RenderSurface { get; set; }
     public bool FullRerender { get; set; } = false;
-    
+
+    public PointerInfo PointerInfo { get; set; }
     public ColorSpace ProcessingColorSpace { get; set; }
-    public string? TargetOutput { get; set; }   
+    public string? TargetOutput { get; set; }
 
 
     public RenderContext(DrawingSurface renderSurface, KeyFrameTime frameTime, ChunkResolution chunkResolution,
@@ -63,10 +65,7 @@ public class RenderContext
 
     public RenderContext Clone()
     {
-        return new RenderContext(RenderSurface, FrameTime, ChunkResolution, RenderOutputSize, DocumentSize, ProcessingColorSpace, Opacity)
-        {
-            FullRerender = FullRerender,
-            TargetOutput = TargetOutput
-        };
+        return new RenderContext(RenderSurface, FrameTime, ChunkResolution, RenderOutputSize, DocumentSize,
+            ProcessingColorSpace, Opacity) { FullRerender = FullRerender, TargetOutput = TargetOutput, PointerInfo = PointerInfo};
     }
 }

+ 8 - 4
src/PixiEditor/Models/Rendering/SceneRenderer.cs

@@ -1,4 +1,5 @@
-using ChunkyImageLib.DataHolders;
+using Avalonia.Input;
+using ChunkyImageLib.DataHolders;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
@@ -9,6 +10,7 @@ using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
+using PixiEditor.ChangeableDocument.Rendering.ContextData;
 using PixiEditor.Models.Handlers;
 
 namespace PixiEditor.Models.Rendering;
@@ -34,7 +36,7 @@ internal class SceneRenderer : IDisposable
         DocumentViewModel = documentViewModel;
     }
 
-    public void RenderScene(DrawingSurface target, ChunkResolution resolution, string? targetOutput = null)
+    public void RenderScene(DrawingSurface target, ChunkResolution resolution, PointerInfo pointerInfo, string? targetOutput = null)
     {
         if (Document.Renderer.IsBusy || DocumentViewModel.Busy ||
             target.DeviceClipBounds.Size.ShortestAxis <= 0) return;
@@ -55,7 +57,7 @@ internal class SceneRenderer : IDisposable
                 cachedTextures[adjustedTargetOutput]?.Dispose();
             }
 
-            var rendered = RenderGraph(target, resolution, targetOutput, finalGraph);
+            var rendered = RenderGraph(target, resolution, targetOutput, finalGraph, pointerInfo);
             cachedTextures[adjustedTargetOutput] = rendered;
             return;
         }
@@ -69,7 +71,7 @@ internal class SceneRenderer : IDisposable
     }
 
     private Texture RenderGraph(DrawingSurface target, ChunkResolution resolution, string? targetOutput,
-        IReadOnlyNodeGraph finalGraph)
+        IReadOnlyNodeGraph finalGraph, PointerInfo pointerInfo)
     {
         DrawingSurface renderTarget = target;
         Texture? renderTexture = null;
@@ -105,6 +107,8 @@ internal class SceneRenderer : IDisposable
 
         RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
             resolution, finalSize, Document.Size, Document.ProcessingColorSpace);
+        context.PointerInfo = pointerInfo;
+
         context.TargetOutput = targetOutput;
         finalGraph.Execute(context);
 

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

@@ -0,0 +1,10 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes;
+
+[NodeViewModel("POINTER_INFO_NODE", "INPUTS", null)]
+internal class PointerInfoNodeViewModel : NodeViewModel<PointerInfoNode>
+{
+
+}

+ 37 - 4
src/PixiEditor/Views/Rendering/Scene.cs

@@ -30,6 +30,8 @@ using PixiEditor.Models.Rendering;
 using Drawie.Numerics;
 using Drawie.Skia;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
+using PixiEditor.ChangeableDocument.Rendering.ContextData;
+using PixiEditor.Common;
 using PixiEditor.UI.Common.Localization;
 using PixiEditor.ViewModels.Document;
 using PixiEditor.ViewModels.Document.Nodes.Workspace;
@@ -173,7 +175,10 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
     private PixelSize lastSize = PixelSize.Empty;
     private Cursor lastCursor;
-    private VecD lastMousePosition;
+    private VecD lastMousePositionOnCanvas;
+    private Point lastDirCalculationPoint;
+
+    private PointerInfo lastPointerInfo;
 
     public static readonly StyledProperty<string> RenderOutputProperty =
         AvaloniaProperty.Register<Scene, string>("RenderOutput");
@@ -312,7 +317,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         DrawOverlays(renderTexture.DrawingSurface, bounds, OverlayRenderSorting.Background);
         try
         {
-            SceneRenderer.RenderScene(renderTexture.DrawingSurface, CalculateResolution(), renderOutput);
+            SceneRenderer.RenderScene(renderTexture.DrawingSurface, CalculateResolution(), lastPointerInfo, renderOutput);
         }
         catch (Exception e)
         {
@@ -365,7 +370,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
                         continue;
                     }
 
-                    overlay.PointerPosition = lastMousePosition;
+                    overlay.PointerPosition = lastMousePositionOnCanvas;
 
                     overlay.ZoomScale = Scale;
 
@@ -384,6 +389,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
     protected override void OnPointerEntered(PointerEventArgs e)
     {
         base.OnPointerEntered(e);
+        lastPointerInfo = ConstructPointerInfo(e);
         if (AllOverlays != null)
         {
             OverlayPointerArgs args = ConstructPointerArgs(e);
@@ -423,13 +429,14 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
     protected override void OnPointerMoved(PointerEventArgs e)
     {
+        lastPointerInfo = ConstructPointerInfo(e);
         base.OnPointerMoved(e);
         try
         {
             if (AllOverlays != null)
             {
                 OverlayPointerArgs args = ConstructPointerArgs(e);
-                lastMousePosition = args.Point;
+                lastMousePositionOnCanvas = args.Point;
 
                 Cursor finalCursor = DefaultCursor;
 
@@ -491,6 +498,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         base.OnPointerPressed(e);
         try
         {
+            lastPointerInfo = ConstructPointerInfo(e);
             if (AllOverlays != null)
             {
                 OverlayPointerArgs args = ConstructPointerArgs(e);
@@ -525,6 +533,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         base.OnPointerExited(e);
         try
         {
+            lastPointerInfo = ConstructPointerInfo(e);
             if (AllOverlays != null)
             {
                 OverlayPointerArgs args = ConstructPointerArgs(e);
@@ -553,6 +562,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         base.OnPointerExited(e);
         try
         {
+            lastPointerInfo = ConstructPointerInfo(e);
             if (AllOverlays != null)
             {
                 OverlayPointerArgs args = ConstructPointerArgs(e);
@@ -638,6 +648,29 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         };
     }
 
+    private PointerInfo ConstructPointerInfo(PointerEventArgs e)
+    {
+        var data = e.GetCurrentPoint(this);
+        VecD lastPoint = new VecD(lastDirCalculationPoint.X, lastDirCalculationPoint.Y);
+        VecD currentPoint = new VecD(data.Position.X, data.Position.Y);
+        if (VecD.Distance(lastPoint, currentPoint) > 20)
+        {
+            lastDirCalculationPoint = Lerp(lastPoint, currentPoint, 0.5f);
+        }
+
+        var properties = data.Properties;
+        VecD position = ToCanvasSpace(data.Position);
+        Point dir = lastDirCalculationPoint - data.Position;
+        VecD vecDir = new VecD(dir.X, dir.Y);
+        VecD dirNormalized = vecDir.Length > 0 ? vecDir.Normalize() : lastPointerInfo.MovementDirection;
+        return new PointerInfo(position, properties.Pressure, properties.Twist, new VecD(properties.XTilt, properties.YTilt), dirNormalized);
+    }
+
+    private static Point Lerp(VecD a, VecD b, float t)
+    {
+        return new Point(a.X + (b.X - a.X) * t, a.Y + (b.Y - a.Y) * t);
+    }
+
     private void FocusOverlay()
     {
         Focus();