Browse Source

Added live preview

Krzysztof Krysiński 3 tuần trước cách đây
mục cha
commit
2c3d6839ff

+ 36 - 5
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Brushes/BrushOutputNode.cs

@@ -1,4 +1,5 @@
-using Drawie.Backend.Core;
+using System.Collections;
+using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
@@ -60,8 +61,9 @@ public class BrushOutputNode : Node
 
 
     protected override bool ExecuteOnlyOnCacheChange => true;
     protected override bool ExecuteOnlyOnCacheChange => true;
 
 
-    private string previewSvg =
+    public const string PreviewSvg =
         "M0.25 99.4606C0.25 99.4606 60.5709 79.3294 101.717 99.4606C147.825 122.019 199.75 99.4606 199.75 99.4606";
         "M0.25 99.4606C0.25 99.4606 60.5709 79.3294 101.717 99.4606C147.825 122.019 199.75 99.4606 199.75 99.4606";
+    public const int YOffsetInPreview = -88;
 
 
     private VectorPath? previewVectorPath;
     private VectorPath? previewVectorPath;
 
 
@@ -170,11 +172,11 @@ public class BrushOutputNode : Node
             VecI.Zero, ChunkResolution.Full, surface.Canvas, VecD.Zero);
             VecI.Zero, ChunkResolution.Full, surface.Canvas, VecD.Zero);
     }
     }
 
 
-    private void DrawStrokePreview(ChunkyImage target, RenderContext context, int maxSize, VecD shift = default)
+    public void DrawStrokePreview(ChunkyImage target, RenderContext context, int maxSize, VecD shift = default)
     {
     {
         if (previewVectorPath == null)
         if (previewVectorPath == null)
         {
         {
-            previewVectorPath = VectorPath.FromSvgPath(previewSvg);
+            previewVectorPath = VectorPath.FromSvgPath(PreviewSvg);
         }
         }
 
 
         float offset = 0;
         float offset = 0;
@@ -198,6 +200,35 @@ public class BrushOutputNode : Node
         }
         }
     }
     }
 
 
+    public IEnumerable<float> DrawStrokePreviewEnumerable(ChunkyImage target, RenderContext context, int maxSize, VecD shift = default)
+    {
+        if (previewVectorPath == null)
+        {
+            previewVectorPath = VectorPath.FromSvgPath(PreviewSvg);
+        }
+
+        float offset = 0;
+        float pressure;
+        VecD pos;
+        while (offset <= target.CommittedSize.X)
+        {
+            pressure = (float)Math.Sin((offset / target.CommittedSize.X) * Math.PI);
+            var vec4D = previewVectorPath.GetPositionAndTangentAtDistance(offset, false);
+            pos = vec4D.XY;
+            pos = new VecD(pos.X, pos.Y + maxSize / 2f) + shift;
+
+            previewEngine.ExecuteBrush(target,
+                new BrushData(context.Graph, Id) { StrokeWidth = maxSize, AntiAliasing = true },
+                [(VecI)pos], context.FrameTime, context.ProcessingColorSpace, context.DesiredSamplingOptions,
+                new PointerInfo(pos, pressure, 0, VecD.Zero, vec4D.ZW),
+                new KeyboardInfo(),
+                new EditorData(Colors.White, Colors.Black));
+
+            offset += 1;
+            yield return offset;
+        }
+    }
+
     private void DrawPointPreview(ChunkyImage img, RenderContext context, int size, VecD pos)
     private void DrawPointPreview(ChunkyImage img, RenderContext context, int size, VecD pos)
     {
     {
         previewEngine.ExecuteBrush(img,
         previewEngine.ExecuteBrush(img,
@@ -244,7 +275,7 @@ public class BrushOutputNode : Node
         context.RenderSurface = StrokePreviewTexture.DrawingSurface.Canvas;
         context.RenderSurface = StrokePreviewTexture.DrawingSurface.Canvas;
 
 
         using var strokeChunkyImage = new ChunkyImage(new VecI(StrokePreviewSizeX, StrokePreviewSizeY), context.ProcessingColorSpace);
         using var strokeChunkyImage = new ChunkyImage(new VecI(StrokePreviewSizeX, StrokePreviewSizeY), context.ProcessingColorSpace);
-        DrawStrokePreview(strokeChunkyImage, context, StrokePreviewSizeY / 2, new VecD(0, -88));
+        DrawStrokePreview(strokeChunkyImage, context, StrokePreviewSizeY / 2, new VecD(0, YOffsetInPreview));
         strokeChunkyImage.CommitChanges();
         strokeChunkyImage.CommitChanges();
         strokeChunkyImage.DrawCommittedChunkOn(
         strokeChunkyImage.DrawCommittedChunkOn(
             VecI.Zero, ChunkResolution.Full, StrokePreviewTexture.DrawingSurface.Canvas, VecD.Zero);
             VecI.Zero, ChunkResolution.Full, StrokePreviewTexture.DrawingSurface.Canvas, VecD.Zero);

+ 3 - 1
src/PixiEditor/Views/Input/BrushItem.axaml

@@ -7,6 +7,7 @@
              xmlns:input="clr-namespace:PixiEditor.Views.Input"
              xmlns:input="clr-namespace:PixiEditor.Views.Input"
              xmlns:surfaces="clr-namespace:Drawie.Backend.Core.Surfaces;assembly=Drawie.Backend.Core"
              xmlns:surfaces="clr-namespace:Drawie.Backend.Core.Surfaces;assembly=Drawie.Backend.Core"
              xmlns:controls="clr-namespace:Drawie.Interop.Avalonia.Core.Controls;assembly=Drawie.Interop.Avalonia.Core"
              xmlns:controls="clr-namespace:Drawie.Interop.Avalonia.Core.Controls;assembly=Drawie.Interop.Avalonia.Core"
+             Background="Transparent" IsHitTestVisible="True"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              x:Class="PixiEditor.Views.Input.BrushItem">
              x:Class="PixiEditor.Views.Input.BrushItem">
     <UserControl.Styles>
     <UserControl.Styles>
@@ -44,8 +45,9 @@
             </StackPanel>
             </StackPanel>
             <controls:DrawieTextureControl Height="50" Width="100"
             <controls:DrawieTextureControl Height="50" Width="100"
                                            SamplingOptions="Bilinear"
                                            SamplingOptions="Bilinear"
+                                           Name="StrokePreviewControl"
                                            Margin="0, 0, 5, 0"
                                            Margin="0, 0, 5, 0"
-                                           Texture="{Binding Brush.StrokePreview}" DockPanel.Dock="Left" />
+                                           Texture="{Binding $parent[input:BrushItem].DrawingStrokeTexture}" DockPanel.Dock="Left" />
         </DockPanel>
         </DockPanel>
     </Grid>
     </Grid>
 </UserControl>
 </UserControl>

+ 139 - 4
src/PixiEditor/Views/Input/BrushItem.axaml.cs

@@ -1,7 +1,20 @@
-using Avalonia;
+using System.Collections;
+using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Metadata;
+using Avalonia.Input;
 using Avalonia.Markup.Xaml;
 using Avalonia.Markup.Xaml;
+using Avalonia.Threading;
+using ChunkyImageLib;
+using ChunkyImageLib.DataHolders;
+using Drawie.Backend.Core;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
+using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Brushes;
+using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.ChangeableDocument.Rendering.ContextData;
 using PixiEditor.Models.BrushEngine;
 using PixiEditor.Models.BrushEngine;
 
 
 namespace PixiEditor.Views.Input;
 namespace PixiEditor.Views.Input;
@@ -10,9 +23,14 @@ internal partial class BrushItem : UserControl
 {
 {
     public static readonly StyledProperty<Brush> BrushProperty = AvaloniaProperty.Register<BrushItem, Brush>("Brush");
     public static readonly StyledProperty<Brush> BrushProperty = AvaloniaProperty.Register<BrushItem, Brush>("Brush");
 
 
-    public BrushItem()
+    public static readonly StyledProperty<Texture> DrawingStrokeTextureProperty =
+        AvaloniaProperty.Register<BrushItem, Texture>(
+            nameof(DrawingStrokeTexture));
+
+    public Texture DrawingStrokeTexture
     {
     {
-        InitializeComponent();
+        get => GetValue(DrawingStrokeTextureProperty);
+        set => SetValue(DrawingStrokeTextureProperty, value);
     }
     }
 
 
     public Brush Brush
     public Brush Brush
@@ -20,5 +38,122 @@ internal partial class BrushItem : UserControl
         get { return (Brush)GetValue(BrushProperty); }
         get { return (Brush)GetValue(BrushProperty); }
         set { SetValue(BrushProperty, value); }
         set { SetValue(BrushProperty, value); }
     }
     }
-}
 
 
+    private bool isPreviewingStroke;
+
+    private ChunkyImage previewImage;
+    private IDisposable previewTimer;
+    private IEnumerator enumerator;
+
+    private Texture previewTexture;
+
+    static BrushItem()
+    {
+        BrushProperty.Changed.AddClassHandler<BrushItem>((x, e) =>
+        {
+            x.StopStrokePreviewLoop();
+            x.DrawingStrokeTexture = x.Brush?.StrokePreview;
+        });
+    }
+
+    public BrushItem()
+    {
+        InitializeComponent();
+    }
+
+    protected override void OnPointerEntered(PointerEventArgs e)
+    {
+        StartStrokePreviewLoop();
+        isPreviewingStroke = true;
+    }
+
+    protected override void OnPointerExited(PointerEventArgs e)
+    {
+        StopStrokePreviewLoop();
+    }
+
+    private void StartStrokePreviewLoop()
+    {
+        if (isPreviewingStroke)
+            return;
+
+        BrushOutputNode? brushNode =
+            Brush.Document?.AccessInternalReadOnlyDocument().NodeGraph.LookupNode(Brush.Id) as BrushOutputNode;
+        if (brushNode == null)
+            return;
+
+        if (previewTexture == null ||
+            previewTexture.Size.X != BrushOutputNode.StrokePreviewSizeX ||
+            previewTexture.Size.Y != BrushOutputNode.StrokePreviewSizeY)
+        {
+            previewTexture?.Dispose();
+            previewTexture =
+                Texture.ForDisplay(new VecI(BrushOutputNode.StrokePreviewSizeX, BrushOutputNode.StrokePreviewSizeY));
+        }
+
+        if (previewImage == null ||
+            previewImage.CommittedSize.X != BrushOutputNode.StrokePreviewSizeX ||
+            previewImage.CommittedSize.Y != BrushOutputNode.StrokePreviewSizeY)
+        {
+            previewImage?.Dispose();
+            previewImage =
+                new ChunkyImage(new VecI(BrushOutputNode.StrokePreviewSizeX, BrushOutputNode.StrokePreviewSizeY),
+                    ColorSpace.CreateSrgb());
+        }
+
+
+        DrawingStrokeTexture = previewTexture;
+
+        previewTexture.DrawingSurface.Canvas.Clear();
+        previewImage.CancelChanges();
+        enumerator = brushNode.DrawStrokePreviewEnumerable(previewImage, CreateContext(),
+            BrushOutputNode.StrokePreviewSizeY / 2,
+            new VecD(0, BrushOutputNode.YOffsetInPreview)).GetEnumerator();
+
+        previewTexture.DrawingSurface.Canvas.Clear();
+        previewTimer = DispatcherTimer.Run(() =>
+        {
+            if (!enumerator.MoveNext())
+            {
+                isPreviewingStroke = false;
+                DrawingStrokeTexture = Brush?.StrokePreview;
+                return false;
+            }
+
+            previewImage.DrawMostUpToDateRegionOn(
+                new RectI(0, 0, previewImage.CommittedSize.X, previewImage.CommittedSize.Y),
+                ChunkResolution.Full,
+                previewTexture.DrawingSurface.Canvas,
+                VecI.Zero, null, SamplingOptions.Bilinear);
+
+            StrokePreviewControl.QueueNextFrame();
+
+            return isPreviewingStroke;
+        }, TimeSpan.FromMilliseconds(8));
+    }
+
+    private RenderContext CreateContext()
+    {
+        return new RenderContext(
+            previewTexture.DrawingSurface.Canvas,
+            0,
+            ChunkResolution.Full,
+            previewTexture.Size,
+            previewTexture.Size,
+            ColorSpace.CreateSrgb(),
+            SamplingOptions.Bilinear,
+            Brush.Document.AccessInternalReadOnlyDocument().NodeGraph);
+    }
+
+    private void StopStrokePreviewLoop()
+    {
+        isPreviewingStroke = false;
+        previewTimer?.Dispose();
+        if (enumerator is IDisposable disposable)
+        {
+            disposable.Dispose();
+        }
+
+        DrawingStrokeTexture = Brush?.StrokePreview;
+    }
+}

+ 1 - 1
src/PixiEditor/Views/Input/BrushPicker.axaml.cs

@@ -141,7 +141,7 @@ internal partial class BrushPicker : UserControl
         PopupToggle.Flyout.Opened += Flyout_Opened;
         PopupToggle.Flyout.Opened += Flyout_Opened;
         SelectCategoriesListBox.ItemsSource = Categories;
         SelectCategoriesListBox.ItemsSource = Categories;
         SelectCategoriesListBox.SelectionChanged += SelectCategoriesListBoxOnSelectionChanged;
         SelectCategoriesListBox.SelectionChanged += SelectCategoriesListBoxOnSelectionChanged;
-        
+
         SelectCategoriesListBox.SelectAll();
         SelectCategoriesListBox.SelectAll();
 
 
     }
     }