Browse Source

cutom sizes wip

Krzysztof Krysiński 3 months ago
parent
commit
dad88c68b4

+ 38 - 14
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Workspace/CustomOutputNode.cs

@@ -10,21 +10,24 @@ public class CustomOutputNode : Node, IRenderInput, IPreviewRenderable
 {
     public const string OutputNamePropertyName = "OutputName";
     public const string IsDefaultExportPropertyName = "IsDefaultExport";
-    public const string ExportSizePropertyName = "ExportSize";
+    public const string SizePropertyName = "Size";
     public RenderInputProperty Input { get; }
     public InputProperty<string> OutputName { get; }
     public InputProperty<bool> IsDefaultExport { get; }
-    public InputProperty<VecI> ExportSize { get; }
+    public InputProperty<VecI> Size { get; }
 
     private VecI? lastDocumentSize;
+
+    private TextureCache textureCache = new TextureCache();
+
     public CustomOutputNode()
     {
         Input = new RenderInputProperty(this, OutputNode.InputPropertyName, "BACKGROUND", null);
         AddInputProperty(Input);
-        
+
         OutputName = CreateInput(OutputNamePropertyName, "OUTPUT_NAME", "");
         IsDefaultExport = CreateInput(IsDefaultExportPropertyName, "IS_DEFAULT_EXPORT", false);
-        ExportSize = CreateInput(ExportSizePropertyName, "EXPORT_SIZE", VecI.Zero);
+        Size = CreateInput(SizePropertyName, "SIZE", VecI.Zero);
     }
 
     public override Node CreateCopy()
@@ -36,25 +39,46 @@ public class CustomOutputNode : Node, IRenderInput, IPreviewRenderable
     {
         if (context.TargetOutput == OutputName.Value)
         {
-            lastDocumentSize = context.DocumentSize;
+            VecI targetSize = Size.Value.ShortestAxis <= 0
+                ? context.DocumentSize
+                : Size.Value;
+
+            lastDocumentSize = targetSize;
+
+            DrawingSurface targetSurface = context.RenderSurface;
+
+            if(context.DocumentSize != targetSize)
+            {
+                targetSurface = textureCache.RequestTexture(0, targetSize, context.ProcessingColorSpace).DrawingSurface;
+            }
+
+            int saved = targetSurface.Canvas.Save();
+            targetSurface.Canvas.ClipRect(new RectD(0, 0, targetSize.X, targetSize.Y));
 
-            int saved = context.RenderSurface.Canvas.Save();
-            context.RenderSurface.Canvas.ClipRect(new RectD(0, 0, context.DocumentSize.X, context.DocumentSize.Y));
-            Input.Value?.Paint(context, context.RenderSurface);
+            RenderContext outputContext = new RenderContext(context.RenderSurface, context.FrameTime, context.ChunkResolution,
+                targetSize, context.ProcessingColorSpace, context.Opacity) { TargetOutput = OutputName.Value, };
 
-            context.RenderSurface.Canvas.RestoreToCount(saved);
+            Input.Value?.Paint(outputContext, targetSurface);
+
+            targetSurface.Canvas.RestoreToCount(saved);
+
+            if (targetSurface != context.RenderSurface)
+            {
+                context.RenderSurface.Canvas.DrawSurface(targetSurface, 0, 0);
+            }
         }
     }
 
     RenderInputProperty IRenderInput.Background => Input;
+
     public RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
     {
         if (lastDocumentSize == null)
         {
             return null;
         }
-        
-        return new RectD(0, 0, lastDocumentSize.Value.X, lastDocumentSize.Value.Y); 
+
+        return new RectD(0, 0, lastDocumentSize.Value.X, lastDocumentSize.Value.Y);
     }
 
     public bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
@@ -63,12 +87,12 @@ public class CustomOutputNode : Node, IRenderInput, IPreviewRenderable
         {
             return false;
         }
-        
+
         int saved = renderOn.Canvas.Save();
         Input.Value.Paint(context, renderOn);
-        
+
         renderOn.Canvas.RestoreToCount(saved);
-        
+
         return true;
     }
 }

+ 1 - 1
src/PixiEditor/Models/Handlers/INodeGraphHandler.cs

@@ -21,5 +21,5 @@ internal interface INodeGraphHandler
    public void RemoveConnection(Guid nodeId, string property);
    public void RemoveConnections(Guid nodeId);
    public void UpdateAvailableRenderOutputs();
-   public void GetComputedPropertyValue(INodePropertyHandler property);
+   public void RequestUpdateComputedPropertyValue(INodePropertyHandler property);
 }

+ 2 - 0
src/PixiEditor/Models/Handlers/INodeHandler.cs

@@ -31,4 +31,6 @@ public interface INodeHandler : INotifyPropertyChanged, IDisposable
     public void TraverseForwards(Func<INodeHandler, INodeHandler, bool> func);
     public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, bool> func);
     public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, INodePropertyHandler, bool> func);
+    public IReadOnlyDictionary<string, INodePropertyHandler> InputPropertyMap { get; }
+    public IReadOnlyDictionary<string, INodePropertyHandler> OutputPropertyMap { get; }
 }

+ 27 - 1
src/PixiEditor/Models/Rendering/SceneRenderer.cs

@@ -10,6 +10,7 @@ using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
 using PixiEditor.Models.Handlers;
 
 namespace PixiEditor.Models.Rendering;
@@ -92,8 +93,9 @@ internal class SceneRenderer : IDisposable
             restoreCanvas = true;
         }
 
+        VecI finalSize = SolveDocSize(targetOutput, finalGraph);
         RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
-            resolution, Document.Size, Document.ProcessingColorSpace);
+            resolution, finalSize, Document.ProcessingColorSpace);
         context.TargetOutput = targetOutput;
         finalGraph.Execute(context);
 
@@ -110,6 +112,30 @@ internal class SceneRenderer : IDisposable
         return renderTexture;
     }
 
+    private VecI SolveDocSize(string? targetOutput, IReadOnlyNodeGraph finalGraph)
+    {
+        VecI finalSize = Document.Size;
+        if (targetOutput != null)
+        {
+            var outputNode = finalGraph.AllNodes.FirstOrDefault(n =>
+                n is CustomOutputNode outputNode && outputNode.OutputName.Value == targetOutput);
+
+            if (outputNode is CustomOutputNode customOutputNode)
+            {
+                if (customOutputNode.Size.Value.ShortestAxis > 0)
+                {
+                    finalSize = customOutputNode.Size.Value;
+                }
+            }
+            else
+            {
+                finalSize = Document.Size;
+            }
+        }
+
+        return finalSize;
+    }
+
     private bool RenderInDocumentSize()
     {
         return !HighResRendering || !HighDpiRenderNodePresent(Document.NodeGraph);

+ 4 - 4
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -602,7 +602,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
             Internals.ActionAccumulator.AddActions(
                 new GetComputedPropertyValue_Action(node.Id, CustomOutputNode.OutputNamePropertyName, true),
-                new GetComputedPropertyValue_Action(node.Id, CustomOutputNode.ExportSizePropertyName, true));
+                new GetComputedPropertyValue_Action(node.Id, CustomOutputNode.SizePropertyName, true));
         }
 
         block.ExecuteQueuedActions();
@@ -628,7 +628,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             }
 
             VecI originalSize =
-                exportZone.Inputs.FirstOrDefault(x => x.PropertyName == CustomOutputNode.ExportSizePropertyName)
+                exportZone.Inputs.FirstOrDefault(x => x.PropertyName == CustomOutputNode.SizePropertyName)
                     ?.ComputedValue as VecI? ?? SizeBindable;
             if (originalSize.ShortestAxis <= 0)
             {
@@ -663,7 +663,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             Internals.ActionAccumulator.AddActions(
                 new GetComputedPropertyValue_Action(node.Id, CustomOutputNode.OutputNamePropertyName, true),
                 new GetComputedPropertyValue_Action(node.Id, CustomOutputNode.IsDefaultExportPropertyName, true),
-                new GetComputedPropertyValue_Action(node.Id, CustomOutputNode.ExportSizePropertyName, true));
+                new GetComputedPropertyValue_Action(node.Id, CustomOutputNode.SizePropertyName, true));
         }
 
         block.ExecuteQueuedActions();
@@ -684,7 +684,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             return SizeBindable;
 
         var exportSize =
-            exportNode.Inputs.FirstOrDefault(x => x.PropertyName == CustomOutputNode.ExportSizePropertyName);
+            exportNode.Inputs.FirstOrDefault(x => x.PropertyName == CustomOutputNode.SizePropertyName);
 
         if (exportSize is null)
             return SizeBindable;

+ 40 - 17
src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs

@@ -27,6 +27,8 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
     public StructureTree StructureTree { get; } = new();
     public INodeHandler? OutputNode { get; private set; }
 
+    public Dictionary<string, INodeHandler> CustomRenderOutputs { get; } = new();
+
     private DocumentInternalParts Internals { get; }
 
     public NodeGraphViewModel(DocumentViewModel documentViewModel, DocumentInternalParts internals)
@@ -115,7 +117,7 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
             connection.OutputProperty.ConnectedInputs.Remove(connection.InputProperty);
             Connections.Remove(connection);
         }
-        
+
         var node = AllNodes.FirstOrDefault(x => x.Id == nodeId);
         if (node != null)
         {
@@ -224,9 +226,25 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
         Internals.ActionAccumulator.AddFinishedActions(new UpdatePropertyValue_Action(node.Id, property, value));
     }
 
-    public void GetComputedPropertyValue(INodePropertyHandler property)
+    public void RequestUpdateComputedPropertyValue(INodePropertyHandler property)
+    {
+        Internals.ActionAccumulator.AddActions(
+            new GetComputedPropertyValue_Action(property.Node.Id, property.PropertyName, property.IsInput));
+    }
+
+    public T GetComputedPropertyValue<T>(INodePropertyHandler property)
     {
-        Internals.ActionAccumulator.AddFinishedActions(new GetComputedPropertyValue_Action(property.Node.Id, property.PropertyName, property.IsInput));
+        var node = Internals.Tracker.Document.NodeGraph.AllNodes.FirstOrDefault(x => x.Id == property.Node.Id);
+        if (property.IsInput)
+        {
+            var prop = node.GetInputProperty(property.PropertyName);
+            if (prop == null) return default;
+            return prop.Value is T value ? value : default;
+        }
+
+        var output = node.GetOutputProperty(property.PropertyName);
+        if (output == null) return default;
+        return output.Value is T outputValue ? outputValue : default;
     }
 
     public void EndChangeNodePosition()
@@ -273,7 +291,7 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
 
     public void RemoveNodes(Guid[] selectedNodes)
     {
-        List<IAction> actions = new(); 
+        List<IAction> actions = new();
 
         for (int i = 0; i < selectedNodes.Length; i++)
         {
@@ -331,10 +349,10 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
 
         Internals.ActionAccumulator.AddFinishedActions(action);
     }
-    
+
     public void UpdateAvailableRenderOutputs()
     {
-        List<string> outputs = new();
+        Dictionary<string, INodeHandler> outputs = new();
         foreach (var node in AllNodes)
         {
             if (node.InternalName == typeof(CustomOutputNode).GetCustomAttribute<NodeInfoAttribute>().UniqueName)
@@ -344,40 +362,45 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
 
                 if (nameInput is { Value: string name } && !string.IsNullOrEmpty(name))
                 {
-                    if(outputs.Contains(name)) continue;
-                    
-                    outputs.Add(name);
+                    outputs[name] = node;
                 }
             }
             else if (node.InternalName == typeof(OutputNode).GetCustomAttribute<NodeInfoAttribute>().UniqueName)
             {
-                outputs.Insert(0, "DEFAULT");
+                outputs["DEFAULT"] = node;
             }
         }
-        
+
         RemoveExcessiveRenderOutputs(outputs);
         AddMissingRenderOutputs(outputs);
     }
 
-    private void RemoveExcessiveRenderOutputs(List<string> outputs)
+    private void RemoveExcessiveRenderOutputs(Dictionary<string, INodeHandler> outputs)
     {
         for (int i = AvailableRenderOutputs.Count - 1; i >= 0; i--)
         {
-            if (!outputs.Contains(AvailableRenderOutputs[i]))
+            if (!outputs.ContainsKey(AvailableRenderOutputs[i]))
             {
                 AvailableRenderOutputs.RemoveAt(i);
             }
+
+            if (CustomRenderOutputs.ContainsKey(AvailableRenderOutputs[i]))
+            {
+                CustomRenderOutputs.Remove(AvailableRenderOutputs[i]);
+            }
         }
     }
-    
-    private void AddMissingRenderOutputs(List<string> outputs)
+
+    private void AddMissingRenderOutputs(Dictionary<string, INodeHandler> outputs)
     {
         foreach (var output in outputs)
         {
-            if (!AvailableRenderOutputs.Contains(output))
+            if (!AvailableRenderOutputs.Contains(output.Key))
             {
-                AvailableRenderOutputs.Add(output);
+                AvailableRenderOutputs.Add(output.Key);
             }
+
+            CustomRenderOutputs[output.Key] = output.Value;
         }
     }
 

+ 125 - 9
src/PixiEditor/ViewModels/Nodes/NodeViewModel.cs

@@ -1,4 +1,6 @@
 using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.ComponentModel;
 using System.Reflection;
 using Avalonia;
 using Avalonia.Media;
@@ -26,14 +28,22 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
     private IBrush? categoryBrush;
     private string? nodeNameBindable;
     private VecD position;
-    private ObservableRangeCollection<INodePropertyHandler> inputs = new();
-    private ObservableRangeCollection<INodePropertyHandler> outputs = new();
+    private ObservableRangeCollection<INodePropertyHandler> inputs;
+    private ObservableRangeCollection<INodePropertyHandler> outputs;
     private PreviewPainter resultPainter;
     private bool isSelected;
     private string? icon;
 
     protected Guid id;
 
+    public IReadOnlyDictionary<string, INodePropertyHandler> InputPropertyMap => inputPropertyMap;
+    public IReadOnlyDictionary<string, INodePropertyHandler> OutputPropertyMap => outputPropertyMap;
+
+    private Dictionary<string, INodePropertyHandler> inputPropertyMap = new Dictionary<string, INodePropertyHandler>();
+
+    private Dictionary<string, INodePropertyHandler> outputPropertyMap =
+        new Dictionary<string, INodePropertyHandler>();
+
     public Guid Id { get => id; private set => id = value; }
 
     public LocalizedString DisplayName
@@ -104,13 +114,45 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
     public ObservableRangeCollection<INodePropertyHandler> Inputs
     {
         get => inputs;
-        set => SetProperty(ref inputs, value);
+        set
+        {
+            if (inputs != null)
+            {
+                inputs.CollectionChanged -= UpdateInputPropertyMapEvent;
+            }
+
+            if (SetProperty(ref inputs, value))
+            {
+                AddInputPropertyMap();
+            }
+
+            if (inputs != null)
+            {
+                inputs.CollectionChanged += UpdateInputPropertyMapEvent;
+            }
+        }
     }
 
     public ObservableRangeCollection<INodePropertyHandler> Outputs
     {
         get => outputs;
-        set => SetProperty(ref outputs, value);
+        set
+        {
+            if (outputs != null)
+            {
+                outputs.CollectionChanged -= UpdateOutputPropertyMapEvent;
+            }
+
+            if (SetProperty(ref outputs, value))
+            {
+                AddOutputPropertyMap();
+            }
+
+            if (outputs != null)
+            {
+                outputs.CollectionChanged += UpdateOutputPropertyMapEvent;
+            }
+        }
     }
 
     public PreviewPainter ResultPainter
@@ -144,6 +186,9 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
 
         displayName = attribute.DisplayName;
         Category = attribute.Category;
+
+        Inputs = new ObservableRangeCollection<INodePropertyHandler>();
+        Outputs = new ObservableRangeCollection<INodePropertyHandler>();
     }
 
     public NodeViewModel(string nodeNameBindable, Guid id, VecD position, DocumentViewModel document,
@@ -154,6 +199,9 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         this.position = position;
         Document = document;
         Internals = internals;
+
+        Inputs = new ObservableRangeCollection<INodePropertyHandler>();
+        Outputs = new ObservableRangeCollection<INodePropertyHandler>();
     }
 
     public void SetPosition(VecD newPosition)
@@ -350,7 +398,8 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         }
     }
 
-    public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, INodePropertyHandler, bool> func)
+    public void TraverseForwards(
+        Func<INodeHandler, INodeHandler, INodePropertyHandler, INodePropertyHandler, bool> func)
     {
         var visited = new HashSet<INodeHandler>();
         var queueNodes = new Queue<(INodeHandler, INodeHandler, INodePropertyHandler, INodePropertyHandler)>();
@@ -380,6 +429,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         }
     }
 
+
     public virtual void Dispose()
     {
         ResultPainter?.Dispose();
@@ -387,22 +437,88 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
 
     public NodePropertyViewModel FindInputProperty(string propName)
     {
-        return Inputs.FirstOrDefault(x => x.PropertyName == propName) as NodePropertyViewModel;
+        if (string.IsNullOrEmpty(propName))
+        {
+            return null;
+        }
+
+        return inputPropertyMap.TryGetValue(propName, out var prop) ? prop as NodePropertyViewModel : null;
     }
 
     public NodePropertyViewModel<T> FindInputProperty<T>(string propName)
     {
-        return Inputs.FirstOrDefault(x => x.PropertyName == propName) as NodePropertyViewModel<T>;
+        if (string.IsNullOrEmpty(propName))
+        {
+            return null;
+        }
+
+        return inputPropertyMap.TryGetValue(propName, out var prop) ? prop as NodePropertyViewModel<T> : null;
     }
 
     public NodePropertyViewModel FindOutputProperty(string propName)
     {
-        return Outputs.FirstOrDefault(x => x.PropertyName == propName) as NodePropertyViewModel;
+        if (string.IsNullOrEmpty(propName))
+        {
+            return null;
+        }
+
+        return outputPropertyMap.TryGetValue(propName, out var prop) ? prop as NodePropertyViewModel : null;
     }
 
     public NodePropertyViewModel<T> FindOutputProperty<T>(string propName)
     {
-        return Outputs.FirstOrDefault(x => x.PropertyName == propName) as NodePropertyViewModel<T>;
+        if (string.IsNullOrEmpty(propName))
+        {
+            return null;
+        }
+
+        return outputPropertyMap.TryGetValue(propName, out var prop) ? prop as NodePropertyViewModel<T> : null;
+    }
+
+    private void UpdateInputPropertyMapEvent(object? sender,
+        NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
+    {
+        AddInputPropertyMap();
+    }
+
+    private void UpdateOutputPropertyMapEvent(object? sender,
+        NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
+    {
+        AddOutputPropertyMap();
+    }
+
+    private void AddInputPropertyMap()
+    {
+        inputPropertyMap.Clear();
+        if (Inputs == null)
+        {
+            return;
+        }
+
+        foreach (var item in Inputs)
+        {
+            if (item == null) continue;
+            inputPropertyMap[item.PropertyName] = item;
+        }
+    }
+
+    private void AddOutputPropertyMap()
+    {
+        outputPropertyMap.Clear();
+        if (Outputs == null)
+        {
+            return;
+        }
+
+        foreach (var item in Outputs)
+        {
+            if (item == null)
+            {
+                continue;
+            }
+
+            outputPropertyMap[item.PropertyName] = item;
+        }
     }
 }
 

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/NodeGraphManagerViewModel.cs

@@ -94,6 +94,6 @@ internal class NodeGraphManagerViewModel : SubViewModel<ViewModelMain>
     [Command.Internal("PixiEditor.NodeGraph.GetComputedPropertyValue")]
     public void GetComputedPropertyValue(INodePropertyHandler property)
     {
-        Owner.DocumentManagerSubViewModel.ActiveDocument?.NodeGraph.GetComputedPropertyValue(property);
+        Owner.DocumentManagerSubViewModel.ActiveDocument?.NodeGraph.RequestUpdateComputedPropertyValue(property);
     }
 }

+ 0 - 7
src/PixiEditor/Views/Main/ViewportControls/ViewportOverlays.cs

@@ -132,11 +132,6 @@ internal class ViewportOverlays
         Binding isVisBinding = new() { Source = Viewport, Path = "GridLinesVisible", Mode = BindingMode.OneWay };
         gridLinesOverlay.Bind(Visual.IsVisibleProperty, isVisBinding);
 
-        Binding binding = new() { Source = Viewport, Path = "Document.Width", Mode = BindingMode.OneWay };
-        gridLinesOverlay.Bind(GridLinesOverlay.PixelWidthProperty, binding);
-        binding = new Binding { Source = Viewport, Path = "Document.Height", Mode = BindingMode.OneWay };
-        gridLinesOverlay.Bind(GridLinesOverlay.PixelHeightProperty, binding);
-
         Binding xBinding = new() { Source = Viewport, Path = "GridLinesXSize", Mode = BindingMode.OneWay };
         gridLinesOverlay.Bind(GridLinesOverlay.GridXSizeProperty, xBinding);
         Binding yBinding = new() { Source = Viewport, Path = "GridLinesYSize", Mode = BindingMode.OneWay };
@@ -177,7 +172,6 @@ internal class ViewportOverlays
         {
             Source = Viewport, Path = "Document.AnySymmetryAxisEnabledBindable", Mode = BindingMode.OneWay
         };
-        Binding sizeBinding = new() { Source = Viewport, Path = "Document.SizeBindable", Mode = BindingMode.OneWay };
         Binding isHitTestVisibleBinding = new()
         {
             Source = Viewport,
@@ -203,7 +197,6 @@ internal class ViewportOverlays
         };
 
         symmetryOverlay.Bind(Visual.IsVisibleProperty, isVisibleBinding);
-        symmetryOverlay.Bind(SymmetryOverlay.SizeProperty, sizeBinding);
         symmetryOverlay.Bind(InputElement.IsHitTestVisibleProperty, isHitTestVisibleBinding);
         symmetryOverlay.Bind(SymmetryOverlay.HorizontalAxisVisibleProperty, horizontalAxisVisibleBinding);
         symmetryOverlay.Bind(SymmetryOverlay.VerticalAxisVisibleProperty, verticalAxisVisibleBinding);

+ 2 - 20
src/PixiEditor/Views/Overlays/GridLinesOverlay.cs

@@ -18,24 +18,6 @@ public class GridLinesOverlay : Overlay
     public static readonly StyledProperty<double> GridYSizeProperty = AvaloniaProperty.Register<GridLinesOverlay, double>(
         nameof(GridYSize));
 
-    public static readonly StyledProperty<int> PixelWidthProperty = AvaloniaProperty.Register<GridLinesOverlay, int>(
-        nameof(PixelWidth));
-
-    public static readonly StyledProperty<int> PixelHeightProperty = AvaloniaProperty.Register<GridLinesOverlay, int>(
-        nameof(PixelHeight));
-
-    public int PixelHeight
-    {
-        get => GetValue(PixelHeightProperty);
-        set => SetValue(PixelHeightProperty, value);
-    }
-
-    public int PixelWidth
-    {
-        get => GetValue(PixelWidthProperty);
-        set => SetValue(PixelWidthProperty, value);
-    }
-
     public double GridXSize
     {
         get => GetValue(GridXSizeProperty);
@@ -74,8 +56,8 @@ public class GridLinesOverlay : Overlay
     {
         // Draw lines in vertical and horizontal directions, size should be relative to the scale
 
-        double width = PixelWidth;
-        double height = PixelHeight;
+        double width = canvasBounds.Width;
+        double height = canvasBounds.Height;
 
         double columnWidth = GridXSize;
         double rowHeight = GridYSize;

+ 36 - 39
src/PixiEditor/Views/Overlays/SymmetryOverlay/SymmetryOverlay.cs

@@ -83,7 +83,6 @@ internal class SymmetryOverlay : Overlay
 
     private SymmetryAxisDirection? capturedDirection;
     private SymmetryAxisDirection? hoveredDirection;
-    public static readonly StyledProperty<VecI> SizeProperty = AvaloniaProperty.Register<SymmetryOverlay, VecI>(nameof(Size));
 
     private const double HandleSize = 12;
     private VectorPath handleGeometry = Handle.GetHandleGeometry("MarkerHandle").Path;
@@ -100,16 +99,12 @@ internal class SymmetryOverlay : Overlay
     
     private float PenThickness => 1.0f / (float)ZoomScale;
 
-    public VecI Size    
-    {
-        get { return (VecI)GetValue(SizeProperty); }
-        set { SetValue(SizeProperty, value); }
-    }
-
     private double horizontalAxisY;
     private double verticalAxisX;
     private VecD pointerPosition;
 
+    private VecF lastSize;
+
     static SymmetryOverlay()
     {
         AffectsRender<SymmetryOverlay>(HorizontalAxisVisibleProperty);
@@ -125,6 +120,8 @@ internal class SymmetryOverlay : Overlay
         if (!HorizontalAxisVisible && !VerticalAxisVisible)
             return;
 
+        VecF size = (VecF)canvasBounds.Size;
+        lastSize = size;
         checkerBlack.StrokeWidth = PenThickness;
         float dashWidth = DashWidth / (float)ZoomScale;
         checkerWhite.PathEffect?.Dispose();
@@ -141,12 +138,12 @@ internal class SymmetryOverlay : Overlay
             {
                 if (horizontalAxisY != 0)
                 {
-                    DrawHorizontalRuler(drawingContext, false);
+                    DrawHorizontalRuler(drawingContext, false, size);
                 }
 
-                if (horizontalAxisY != (int)Size.Y)
+                if (horizontalAxisY != (int)size.Y)
                 {
-                    DrawHorizontalRuler(drawingContext, true);
+                    DrawHorizontalRuler(drawingContext, true, size);
                 }
             }
 
@@ -160,14 +157,14 @@ internal class SymmetryOverlay : Overlay
             
             save = drawingContext.Save();
             drawingContext.Translate(0, (float)horizontalAxisY);
-            drawingContext.RotateDegrees(180, Size.X / 2, 0);
+            drawingContext.RotateDegrees(180, size.X / 2, 0);
             drawingContext.Scale((float)HandleSize / (float)ZoomScale, (float)HandleSize / (float)ZoomScale);
             drawingContext.DrawPath(handleGeometry, borderPen);
 
             drawingContext.RestoreToCount(save);
 
-            drawingContext.DrawLine(new(0, horizontalAxisY), new(Size.X, horizontalAxisY), checkerBlack);
-            drawingContext.DrawLine(new(0, horizontalAxisY), new(Size.X, horizontalAxisY), checkerWhite);
+            drawingContext.DrawLine(new(0, horizontalAxisY), new(size.X, horizontalAxisY), checkerBlack);
+            drawingContext.DrawLine(new(0, horizontalAxisY), new(size.X, horizontalAxisY), checkerWhite);
         }
         if (VerticalAxisVisible)
         {
@@ -175,12 +172,12 @@ internal class SymmetryOverlay : Overlay
             {
                 if (verticalAxisX != 0)
                 {
-                    DrawVerticalRuler(drawingContext, false);
+                    DrawVerticalRuler(drawingContext, false, size);
                 }
 
-                if (verticalAxisX != (int)Size.X)
+                if (verticalAxisX != (int)size.X)
                 {
-                    DrawVerticalRuler(drawingContext, true);
+                    DrawVerticalRuler(drawingContext, true, size);
                 }
             }
 
@@ -197,32 +194,32 @@ internal class SymmetryOverlay : Overlay
             saved = drawingContext.Save();
             drawingContext.RotateDegrees(90);
             drawingContext.Translate(0, (float)-verticalAxisX);
-            drawingContext.RotateDegrees(180, Size.Y / 2, 0);
+            drawingContext.RotateDegrees(180, size.Y / 2, 0);
             drawingContext.Scale((float)HandleSize / (float)ZoomScale, (float)HandleSize / (float)ZoomScale);
             drawingContext.DrawPath(handleGeometry, borderPen);
 
             drawingContext.RestoreToCount(saved);
 
-            drawingContext.DrawLine(new(verticalAxisX, 0), new(verticalAxisX, Size.Y), checkerBlack);
-            drawingContext.DrawLine(new(verticalAxisX, 0), new(verticalAxisX, Size.Y), checkerWhite);
+            drawingContext.DrawLine(new(verticalAxisX, 0), new(verticalAxisX, size.Y), checkerBlack);
+            drawingContext.DrawLine(new(verticalAxisX, 0), new(verticalAxisX, size.Y), checkerWhite);
         }
     }
 
-    private void DrawHorizontalRuler(Canvas drawingContext, bool upper)
+    private void DrawHorizontalRuler(Canvas drawingContext, bool upper, VecF size)
     {
-        double start = upper ? Size.Y : 0;
-        bool drawRight = pointerPosition.X > Size.X / 2;
-        double xOffset = drawRight ? Size.X - RulerOffset * PenThickness * 2 : 0;
+        double start = upper ? size.Y : 0;
+        bool drawRight = pointerPosition.X > size.X / 2;
+        double xOffset = drawRight ? size.X - RulerOffset * PenThickness * 2 : 0;
 
         drawingContext.DrawLine(new VecD(RulerOffset * PenThickness + xOffset, start), new VecD(RulerOffset * PenThickness + xOffset, horizontalAxisY), rulerPen);
         drawingContext.DrawLine(new VecD((RulerOffset - RulerWidth) * PenThickness + xOffset, start), new VecD((RulerOffset + RulerWidth) * PenThickness + xOffset, start), rulerPen);
         drawingContext.DrawLine(new VecD((RulerOffset - RulerWidth) * PenThickness + xOffset, horizontalAxisY), new VecD((RulerOffset + RulerWidth) * PenThickness + xOffset, horizontalAxisY), rulerPen);
 
-        string text = upper ? $"{start - horizontalAxisY}{new LocalizedString("PIXEL_UNIT")} ({(start - horizontalAxisY) / Size.Y * 100:F1}%)" : $"{horizontalAxisY}{new LocalizedString("PIXEL_UNIT")} ({horizontalAxisY / Size.Y * 100:F1}%)";
+        string text = upper ? $"{start - horizontalAxisY}{new LocalizedString("PIXEL_UNIT")} ({(start - horizontalAxisY) / size.Y * 100:F1}%)" : $"{horizontalAxisY}{new LocalizedString("PIXEL_UNIT")} ({horizontalAxisY / size.Y * 100:F1}%)";
 
         using Font font = Font.CreateDefault(14f / (float)ZoomScale);
         
-        if (Size.Y < font.Size * 2.5 || horizontalAxisY == (int)Size.Y && upper || horizontalAxisY == 0 && !upper)
+        if (size.Y < font.Size * 2.5 || horizontalAxisY == (int)size.Y && upper || horizontalAxisY == 0 && !upper)
         {
             return;
         }
@@ -231,27 +228,27 @@ internal class SymmetryOverlay : Overlay
 
         if (upper)
         {
-            textY += Size.Y / 2f;
+            textY += size.Y / 2f;
         }
 
         drawingContext.DrawText(text, new VecD(RulerOffset * PenThickness - (drawRight ? -1 : 1) + xOffset, textY), drawRight ? TextAlign.Left : TextAlign.Right, font, textPaint);
     }
 
-    private void DrawVerticalRuler(Canvas drawingContext, bool right)
+    private void DrawVerticalRuler(Canvas drawingContext, bool right, VecF size)
     {
-        double start = right ? Size.X : 0;
-        bool drawBottom = pointerPosition.Y > Size.Y / 2;
-        double yOffset = drawBottom ? Size.Y - RulerOffset * PenThickness * 2 : 0;
+        double start = right ? size.X : 0;
+        bool drawBottom = pointerPosition.Y > size.Y / 2;
+        double yOffset = drawBottom ? size.Y - RulerOffset * PenThickness * 2 : 0;
 
         drawingContext.DrawLine(new VecD(start, RulerOffset * PenThickness + yOffset), new VecD(verticalAxisX, RulerOffset * PenThickness + yOffset), rulerPen);
         drawingContext.DrawLine(new VecD(start, (RulerOffset - RulerWidth) * PenThickness + yOffset), new VecD(start, (RulerOffset + RulerWidth) * PenThickness + yOffset), rulerPen);
         drawingContext.DrawLine(new VecD(verticalAxisX, (RulerOffset - RulerWidth) * PenThickness + yOffset), new VecD(verticalAxisX, (RulerOffset + RulerWidth) * PenThickness + yOffset), rulerPen);
 
-        string text = right ? $"{start - verticalAxisX}{new LocalizedString("PIXEL_UNIT")} ({(start - verticalAxisX) / Size.X * 100:F1}%)" : $"{verticalAxisX}{new LocalizedString("PIXEL_UNIT")} ({verticalAxisX / Size.X * 100:F1}%)";
+        string text = right ? $"{start - verticalAxisX}{new LocalizedString("PIXEL_UNIT")} ({(start - verticalAxisX) / size.X * 100:F1}%)" : $"{verticalAxisX}{new LocalizedString("PIXEL_UNIT")} ({verticalAxisX / size.X * 100:F1}%)";
 
         using Font font = Font.CreateDefault(14f / (float)ZoomScale);
         
-        if (Size.X < font.MeasureText(text) * 2.5 || verticalAxisX == (int)Size.X && right || verticalAxisX == 0 && !right)
+        if (size.X < font.MeasureText(text) * 2.5 || verticalAxisX == (int)size.X && right || verticalAxisX == 0 && !right)
         {
             return;
         }
@@ -260,7 +257,7 @@ internal class SymmetryOverlay : Overlay
 
         if (right)
         {
-            textX += Size.X / 2;
+            textX += size.X / 2;
         }
 
         double textY = RulerOffset * PenThickness - ((drawBottom ? 5 : 2 + font.Size) * PenThickness) + yOffset;
@@ -276,9 +273,9 @@ internal class SymmetryOverlay : Overlay
     {
         double radius = HandleSize * 4 / ZoomScale / 2;
         VecD left = new(-radius, horizontalAxisY);
-        VecD right = new(Size.X + radius, horizontalAxisY);
+        VecD right = new(lastSize.X + radius, horizontalAxisY);
         VecD up = new(verticalAxisX, -radius);
-        VecD down = new(verticalAxisX, Size.Y + radius);
+        VecD down = new(verticalAxisX, lastSize.Y + radius);
 
         if (HorizontalAxisVisible && (Math.Abs((left - position).LongestAxis) < radius || Math.Abs((right - position).LongestAxis) < radius))
             return SymmetryAxisDirection.Horizontal;
@@ -336,11 +333,11 @@ internal class SymmetryOverlay : Overlay
             return;
         if (capturedDirection == SymmetryAxisDirection.Horizontal)
         {
-            horizontalAxisY = Math.Round(Math.Clamp(args.Point.Y, 0, Size.Y) * 2) / 2;
+            horizontalAxisY = Math.Round(Math.Clamp(args.Point.Y, 0, lastSize.Y) * 2) / 2;
 
             if (args.Modifiers.HasFlag(KeyModifiers.Shift))
             {
-                double temp = Math.Round(horizontalAxisY / Size.Y * 8) / 8 * Size.Y;
+                double temp = Math.Round(horizontalAxisY / lastSize.Y * 8) / 8 * lastSize.Y;
                 horizontalAxisY = Math.Round(temp * 2) / 2;
             }
 
@@ -348,12 +345,12 @@ internal class SymmetryOverlay : Overlay
         }
         else if (capturedDirection == SymmetryAxisDirection.Vertical)
         {
-            verticalAxisX = Math.Round(Math.Clamp(args.Point.X, 0, Size.X) * 2) / 2;
+            verticalAxisX = Math.Round(Math.Clamp(args.Point.X, 0, lastSize.X) * 2) / 2;
 
             if (args.Modifiers.HasFlag(KeyModifiers.Control))
             {
 
-                double temp = Math.Round(verticalAxisX / Size.X * 8) / 8 * Size.X;
+                double temp = Math.Round(verticalAxisX / lastSize.X * 8) / 8 * lastSize.X;
                 verticalAxisX = Math.Round(temp * 2) / 2;
             }
 

+ 36 - 13
src/PixiEditor/Views/Rendering/Scene.cs

@@ -28,8 +28,10 @@ using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.Rendering;
 using Drawie.Numerics;
 using Drawie.Skia;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.ViewModels.Document;
+using PixiEditor.ViewModels.Document.Nodes.Workspace;
 using PixiEditor.Views.Overlays;
 using PixiEditor.Views.Overlays.Pointers;
 using PixiEditor.Views.Visuals;
@@ -74,8 +76,9 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         set => SetValue(AutoBackgroundScaleProperty, value);
     }
 
-    public static readonly StyledProperty<double> CustomBackgroundScaleXProperty = AvaloniaProperty.Register<Scene, double>(
-        nameof(CustomBackgroundScaleX));
+    public static readonly StyledProperty<double> CustomBackgroundScaleXProperty =
+        AvaloniaProperty.Register<Scene, double>(
+            nameof(CustomBackgroundScaleX));
 
     public double CustomBackgroundScaleX
     {
@@ -83,8 +86,9 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         set => SetValue(CustomBackgroundScaleXProperty, value);
     }
 
-    public static readonly StyledProperty<double> CustomBackgroundScaleYProperty = AvaloniaProperty.Register<Scene, double>(
-        nameof(CustomBackgroundScaleY));
+    public static readonly StyledProperty<double> CustomBackgroundScaleYProperty =
+        AvaloniaProperty.Register<Scene, double>(
+            nameof(CustomBackgroundScaleY));
 
     public double CustomBackgroundScaleY
     {
@@ -290,7 +294,9 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
         renderTexture.Canvas.SetMatrix(matrix.ToSKMatrix().ToMatrix3X3());
 
-        RectD dirtyBounds = new RectD(0, 0, Document.Width, Document.Height);
+        VecI outputSize = FindOutputSize();
+
+        RectD dirtyBounds = new RectD(0, 0, outputSize.X, outputSize.Y);
         RenderScene(dirtyBounds);
 
         renderTexture.Canvas.Restore();
@@ -298,26 +304,23 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
     private void RenderScene(RectD bounds)
     {
+        var renderOutput = RenderOutput == "DEFAULT" ? null : RenderOutput;
         DrawCheckerboard(renderTexture.DrawingSurface, bounds);
         DrawOverlays(renderTexture.DrawingSurface, bounds, OverlayRenderSorting.Background);
         try
         {
-            SceneRenderer.RenderScene(renderTexture.DrawingSurface, CalculateResolution(),
-                RenderOutput == "DEFAULT" ? null : RenderOutput);
+            SceneRenderer.RenderScene(renderTexture.DrawingSurface, CalculateResolution(), renderOutput);
         }
         catch (Exception e)
         {
             renderTexture.DrawingSurface.Canvas.Clear();
-            using Paint paint = new Paint
-            {
-                Color = Colors.White,
-                IsAntiAliased = true
-            };
+            using Paint paint = new Paint { Color = Colors.White, IsAntiAliased = true };
 
             using Font defaultSizedFont = Font.CreateDefault();
             defaultSizedFont.Size = 24;
 
-            renderTexture.DrawingSurface.Canvas.DrawText(new LocalizedString("ERROR_GRAPH"), renderTexture.Size / 2f, TextAlign.Center, defaultSizedFont, paint);
+            renderTexture.DrawingSurface.Canvas.DrawText(new LocalizedString("ERROR_GRAPH"), renderTexture.Size / 2f,
+                TextAlign.Center, defaultSizedFont, paint);
         }
 
         DrawOverlays(renderTexture.DrawingSurface, bounds, OverlayRenderSorting.Foreground);
@@ -385,6 +388,26 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         }
     }
 
+    private VecI FindOutputSize()
+    {
+        VecI outputSize = Document.SizeBindable;
+
+        if (!string.IsNullOrEmpty(RenderOutput))
+        {
+            if (Document.NodeGraph.CustomRenderOutputs.TryGetValue(RenderOutput, out var node))
+            {
+                var prop = node?.Inputs.FirstOrDefault(x => x.PropertyName == CustomOutputNode.SizePropertyName);
+                if (prop != null)
+                {
+                    VecI size = Document.NodeGraph.GetComputedPropertyValue<VecI>(prop);
+                    outputSize = size;
+                }
+            }
+        }
+
+        return outputSize;
+    }
+
     protected override void OnPointerMoved(PointerEventArgs e)
     {
         base.OnPointerMoved(e);