Browse Source

Changed evaluation type to Image

flabbet 1 year ago
parent
commit
3fb5c3de94
27 changed files with 187 additions and 138 deletions
  1. 3 2
      src/ChunkyImageLib/ChunkyImage.cs
  2. 5 4
      src/PixiEditor.AvaloniaUI/Models/Rendering/AffectedAreasGatherer.cs
  3. 10 9
      src/PixiEditor.AvaloniaUI/Models/Rendering/MemberPreviewUpdater.cs
  4. 2 2
      src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.Serialization.cs
  5. 26 23
      src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs
  6. 6 6
      src/PixiEditor.ChangeableDocument/Changeables/Document.cs
  7. 2 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IBackgroundInput.cs
  8. 3 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNode.cs
  9. 4 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNodeGraph.cs
  10. 2 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs
  11. 15 8
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CircleNode.cs
  12. 13 10
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs
  13. 31 16
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs
  14. 20 24
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MergeNode.cs
  15. 4 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs
  16. 5 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs
  17. 6 5
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs
  18. 2 1
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs
  19. 4 3
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/NodeOperations.cs
  20. 2 1
      src/PixiEditor.ChangeableDocument/Changes/Structure/MoveStructureMember_Change.cs
  21. 7 8
      src/PixiEditor.ChangeableDocument/Rendering/DocumentEvaluator.cs
  22. 1 1
      src/PixiEditor.ChangeableDocument/Rendering/RenderingContext.cs
  23. 1 0
      src/PixiEditor.DrawingApi.Core/Bridge/Operations/ICanvasImplementation.cs
  24. 3 0
      src/PixiEditor.DrawingApi.Core/Surface/Canvas.cs
  25. 3 0
      src/PixiEditor.DrawingApi.Core/Surface/ImageData/Image.cs
  26. 6 0
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaCanvasImplementation.cs
  27. 1 1
      src/PixiEditor.UI.Common/Accents/Base.axaml

+ 3 - 2
src/ChunkyImageLib/ChunkyImage.cs

@@ -85,7 +85,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
     private VectorPath? clippingPath;
     private double? horizontalSymmetryAxis = null;
     private double? verticalSymmetryAxis = null;
-
+    private float opacity = 1;
+    
     private readonly Dictionary<ChunkResolution, Dictionary<VecI, Chunk>> committedChunks;
     private readonly Dictionary<ChunkResolution, Dictionary<VecI, Chunk>> latestChunks;
     private readonly Dictionary<ChunkResolution, Dictionary<VecI, LatestChunkData>> latestChunksData;
@@ -1140,7 +1141,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable
         affectedAreaPos = (affectedAreaPos - chunkPos * FullChunkSize) * scale;
         affectedAreaSize = affectedAreaSize * scale;
         targetChunk.Surface.DrawingSurface.Canvas.ClipRect(new RectD(affectedAreaPos, affectedAreaSize));
-
+        
         operation.DrawOnChunk(targetChunk, chunkPos);
         targetChunk.Surface.DrawingSurface.Canvas.RestoreToCount(count);
     }

+ 5 - 4
src/PixiEditor.AvaloniaUI/Models/Rendering/AffectedAreasGatherer.cs

@@ -138,14 +138,15 @@ internal class AffectedAreasGatherer
     private void AddAllToImagePreviews(Guid memberGuid, int frame, bool ignoreSelf = false)
     {
         var member = tracker.Document.FindMember(memberGuid);
-        if (member is IReadOnlyLayerNode layer)
+        if (member is IReadOnlyImageNode layer)
         {
-            var result = layer.Execute(frame);
+            var result = layer.GetLayerImageAtFrame(frame);
             if (result == null)
             {
                 AddWholeCanvasToImagePreviews(memberGuid, ignoreSelf);
                 return;
             }
+            
             var chunks = result.FindAllChunks();
             AddToImagePreviews(memberGuid, new AffectedArea(chunks), ignoreSelf);
         }
@@ -160,9 +161,9 @@ internal class AffectedAreasGatherer
     private void AddAllToMainImage(Guid memberGuid, int frame, bool useMask = true)
     {
         var member = tracker.Document.FindMember(memberGuid);
-        if (member is IReadOnlyLayerNode layer)
+        if (member is IReadOnlyImageNode layer)
         {
-            var result = layer.Execute(frame);
+            var result = layer.GetLayerImageAtFrame(frame);
             if (result == null)
             {
                 AddWholeCanvasToMainImage();

+ 10 - 9
src/PixiEditor.AvaloniaUI/Models/Rendering/MemberPreviewUpdater.cs

@@ -583,9 +583,8 @@ internal class MemberPreviewUpdater
         foreach (var chunk in area.Chunks)
         {
             var pos = chunk * ChunkResolution.Full.PixelSize();
-            IReadOnlyChunkyImage? result = layer is IReadOnlyImageNode raster
-                ? raster.GetLayerImageAtFrame(doc.AnimationHandler.ActiveFrameBindable)
-                : layer.Execute(doc.AnimationHandler.ActiveFrameBindable);
+            if (layer is not IReadOnlyImageNode raster) return;
+            IReadOnlyChunkyImage? result = raster.GetLayerImageAtFrame(doc.AnimationHandler.ActiveFrameBindable);
             
             if (!result.DrawCommittedChunkOn(
                     chunk,
@@ -699,17 +698,19 @@ internal class MemberPreviewUpdater
                      nodeVm.ResultPreview = new Surface(StructureHelpers.CalculatePreviewSize(internals.Tracker.Document.Size));
                }
                
-               float scalingX = (float)nodeVm.ResultPreview.Size.X / node.CachedResult.CommittedSize.X;
-               float scalingY = (float)nodeVm.ResultPreview.Size.Y / node.CachedResult.CommittedSize.Y;
+               float scalingX = (float)nodeVm.ResultPreview.Size.X / node.CachedResult.Width;
+               float scalingY = (float)nodeVm.ResultPreview.Size.Y / node.CachedResult.Height;
                
                nodeVm.ResultPreview.DrawingSurface.Canvas.Save();
                nodeVm.ResultPreview.DrawingSurface.Canvas.Scale(scalingX, scalingY);
                
                nodeVm.ResultPreview.DrawingSurface.Canvas.Clear();
-               node.CachedResult.DrawCommittedRegionOn(
-                   new RectI(0, 0, node.CachedResult.CommittedSize.X, node.CachedResult.CommittedSize.Y), ChunkResolution.Full,
-                   nodeVm.ResultPreview.DrawingSurface, new VecI(0, 0), ReplacingPaint);
-               
+
+               if (node.CachedResult != null)
+               {
+                   nodeVm.ResultPreview.DrawingSurface.Canvas.DrawImage(node.CachedResult, new RectD(0, 0, node.CachedResult.Width, node.CachedResult.Height), ReplacingPaint);
+               }
+
                nodeVm.ResultPreview.DrawingSurface.Canvas.Restore();
             }
         });

+ 2 - 2
src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.Serialization.cs

@@ -122,11 +122,11 @@ internal partial class DocumentViewModel
         var result = document.GetLayerRasterizedImage(layer.Id, 0);
 
         var tightBounds = document.GetChunkAlignedLayerBounds(layer.Id, 0);
-        using var data = result?.DrawingSurface.Snapshot().Encode();
+        using var data = result?.Encode();
         byte[] bytes = data?.AsSpan().ToArray();
         var serializable = new ImageLayer
         {
-            Width = result?.Size.X ?? 0, Height = result?.Size.Y ?? 0, OffsetX = tightBounds?.X ?? 0, OffsetY = tightBounds?.Y ?? 0,
+            Width = result?.Width ?? 0, Height = result?.Height ?? 0, OffsetX = tightBounds?.X ?? 0, OffsetY = tightBounds?.Y ?? 0,
             Enabled = layer.IsVisible.Value, BlendMode = (BlendMode)(int)layer.BlendMode.Value, ImageBytes = bytes,
             ClipToMemberBelow = layer.ClipToMemberBelow.Value, Name = layer.MemberName,
             Guid = layer.Id,

+ 26 - 23
src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs

@@ -261,16 +261,16 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         if (builderInstance.ReferenceLayer is { } refLayer)
         {
             acc.AddActions(new SetReferenceLayer_Action(refLayer.Shape, refLayer.ImageBgra8888Bytes.ToImmutableArray(),
-                    refLayer.ImageSize));
+                refLayer.ImageSize));
         }
 
         viewModel.Swatches = new ObservableCollection<PaletteColor>(builderInstance.Swatches);
         viewModel.Palette = new ObservableRangeCollection<PaletteColor>(builderInstance.Palette);
-        
+
         Guid outputNodeGuid = Guid.NewGuid();
-        
+
         acc.AddActions(new CreateNode_Action(typeof(OutputNode), outputNodeGuid));
-        
+
         AddMembers(outputNodeGuid, builderInstance.Children);
         AddAnimationData(builderInstance.AnimationData);
 
@@ -363,21 +363,24 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
                     {
                         rasterKeyFrameBuilder.Id = Guid.NewGuid();
                     }
-                    
+
                     acc.AddActions(
                         new CreateRasterKeyFrame_Action(
                             rasterKeyFrameBuilder.LayerGuid,
                             rasterKeyFrameBuilder.Id,
                             rasterKeyFrameBuilder.StartFrame, -1, default),
-                        new KeyFrameLength_Action(rasterKeyFrameBuilder.Id, rasterKeyFrameBuilder.StartFrame, rasterKeyFrameBuilder.Duration),
+                        new KeyFrameLength_Action(rasterKeyFrameBuilder.Id, rasterKeyFrameBuilder.StartFrame,
+                            rasterKeyFrameBuilder.Duration),
                         new EndKeyFrameLength_Action());
-                    
-                    PasteImage(rasterKeyFrameBuilder.LayerGuid, rasterKeyFrameBuilder.Surface, rasterKeyFrameBuilder.Surface.Surface.Size.X,
-                        rasterKeyFrameBuilder.Surface.Surface.Size.Y, 0, 0, false, rasterKeyFrameBuilder.StartFrame, rasterKeyFrameBuilder.Id);
-                    
+
+                    PasteImage(rasterKeyFrameBuilder.LayerGuid, rasterKeyFrameBuilder.Surface,
+                        rasterKeyFrameBuilder.Surface.Surface.Size.X,
+                        rasterKeyFrameBuilder.Surface.Surface.Size.Y, 0, 0, false, rasterKeyFrameBuilder.StartFrame,
+                        rasterKeyFrameBuilder.Id);
+
                     acc.AddFinishedActions();
                 }
-                else if(keyFrame is GroupKeyFrameBuilder groupKeyFrameBuilder)
+                else if (keyFrame is GroupKeyFrameBuilder groupKeyFrameBuilder)
                 {
                     AddAnimationData(groupKeyFrameBuilder.Children);
                 }
@@ -454,7 +457,8 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         try
         {
             // TODO: Make sure it must be GetLayerImageAtFrame rather than Rasterize()
-            memberImageBounds = layer.GetLayerImageAtFrame(AnimationDataViewModel.ActiveFrameBindable).FindChunkAlignedMostUpToDateBounds();
+            memberImageBounds = layer.GetLayerImageAtFrame(AnimationDataViewModel.ActiveFrameBindable)
+                .FindChunkAlignedMostUpToDateBounds();
         }
         catch (ObjectDisposedException)
         {
@@ -476,7 +480,8 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         output.DrawingSurface.Canvas.ClipPath(clipPath);
         try
         {
-            layer.GetLayerImageAtFrame(AnimationDataViewModel.ActiveFrameBindable).DrawMostUpToDateRegionOn(bounds, ChunkResolution.Full, output.DrawingSurface, VecI.Zero);
+            layer.GetLayerImageAtFrame(AnimationDataViewModel.ActiveFrameBindable)
+                .DrawMostUpToDateRegionOn(bounds, ChunkResolution.Full, output.DrawingSurface, VecI.Zero);
         }
         catch (ObjectDisposedException)
         {
@@ -554,28 +559,26 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             if (scope == DocumentScope.AllLayers)
             {
                 VecI chunkPos = OperationHelper.GetChunkPos(pos, ChunkyImage.FullChunkSize);
-                /*return ChunkRenderer.MergeWholeStructure(chunkPos, ChunkResolution.Full,
-                        Internals.Tracker.Document.StructureRoot, frame, new RectI(pos, VecI.One))
-                    .Match<Color>(
-                        (Chunk chunk) =>
+                return DocumentEvaluator.RenderChunk(chunkPos, ChunkResolution.Full,
+                        Internals.Tracker.Document.NodeGraph,
+                        frame)
+                    .Match(
+                        chunk =>
                         {
                             VecI posOnChunk = pos - chunkPos * ChunkyImage.FullChunkSize;
                             var color = chunk.Surface.GetSRGBPixel(posOnChunk);
                             chunk.Dispose();
                             return color;
                         },
-                        _ => Colors.Transparent
-                    );*/
-                // TODO: Implement this
-                return Colors.Transparent;
+                        _ => Colors.Transparent);
             }
 
             if (SelectedStructureMember is not LayerViewModel layerVm)
                 return Colors.Transparent;
             IReadOnlyStructureNode? maybeMember = Internals.Tracker.Document.FindMember(layerVm.Id);
-            if (maybeMember is not IReadOnlyLayerNode layer)
+            if (maybeMember is not IReadOnlyImageNode layer)
                 return Colors.Transparent;
-            return layer.Execute(frame).GetMostUpToDatePixel(pos);
+            return layer.GetLayerImageAtFrame(frame).GetMostUpToDatePixel(pos);
         }
         catch (ObjectDisposedException)
         {

+ 6 - 6
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -5,6 +5,8 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables;
@@ -62,7 +64,7 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     /// <remarks>So yeah, welcome folks to the multithreaded world, where possibilities are endless! (and chances of objects getting
     /// edited, in between of processing you want to make exist). You might encounter ObjectDisposedException and other mighty creatures here if
     /// you are lucky enough. Have fun!</remarks>
-    public Surface? GetLayerRasterizedImage(Guid layerGuid, int frame)
+    public Image? GetLayerRasterizedImage(Guid layerGuid, int frame)
     {
         var layer = (IReadOnlyLayerNode?)FindMember(layerGuid);
 
@@ -79,12 +81,10 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
 
         Surface surface = new Surface(tightBounds.Value.Size);
 
-        layer.Execute(frame).DrawMostUpToDateRegionOn(
-            tightBounds.Value,
-            ChunkResolution.Full,
-            surface.DrawingSurface, VecI.Zero);
+        using var paint = new Paint();
+        surface.DrawingSurface.Canvas.DrawImage(layer.Execute(frame), (RectD)tightBounds.Value, paint);
 
-        return surface;
+        return surface.DrawingSurface.Snapshot();
     }
 
     public RectI? GetChunkAlignedLayerBounds(Guid layerGuid, int frame)

+ 2 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IBackgroundInput.cs

@@ -1,8 +1,9 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 
 public interface IBackgroundInput
 {
-    InputProperty<ChunkyImage?> Background { get; }
+    InputProperty<Image?> Background { get; }
 }

+ 3 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNode.cs

@@ -1,4 +1,5 @@
 using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
@@ -8,11 +9,10 @@ public interface IReadOnlyNode
     public Guid Id { get; }
     public IReadOnlyCollection<IInputProperty> InputProperties { get; }
     public IReadOnlyCollection<IOutputProperty> OutputProperties { get; }
-    public IReadOnlyCollection<IReadOnlyNode> ConnectedOutputNodes { get; }
     public VecD Position { get; }
-    public IReadOnlyChunkyImage? CachedResult { get; }
+    public Image? CachedResult { get; }
 
-    public IReadOnlyChunkyImage? Execute(KeyFrameTime frame);
+    public Image? Execute(KeyFrameTime frame);
     public bool Validate();
     
     /// <summary>

+ 4 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNodeGraph.cs

@@ -1,4 +1,6 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 
 public interface IReadOnlyNodeGraph
 {
@@ -7,5 +9,5 @@ public interface IReadOnlyNodeGraph
     public void AddNode(IReadOnlyNode node);
     public void RemoveNode(IReadOnlyNode node);
     public bool TryTraverse(Action<IReadOnlyNode> action);
-    public ChunkyImage? Execute(int frame);
+    public Image? Execute(int frame);
 }

+ 2 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs

@@ -1,6 +1,7 @@
 using System.Collections;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 
@@ -93,7 +94,7 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
         return true;
     }
 
-    public ChunkyImage? Execute(int frame)
+    public Image? Execute(int frame)
     {
         if (OutputNode == null) return null;
 

+ 15 - 8
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CircleNode.cs

@@ -1,5 +1,8 @@
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
@@ -12,7 +15,7 @@ public class CircleNode : Node
     public InputProperty<Color> StrokeColor { get; }
     public InputProperty<Color> FillColor { get; }
     public InputProperty<int> StrokeWidth { get; }
-    public OutputProperty<ChunkyImage> Output { get; }
+    public OutputProperty<Image> Output { get; }
     
     public CircleNode() 
     {
@@ -22,18 +25,22 @@ public class CircleNode : Node
         StrokeColor = CreateInput<Color>("StrokeColor", "STROKE_COLOR", new Color(0, 0, 0, 255));
         FillColor = CreateInput<Color>("FillColor", "FILL_COLOR", new Color(0, 0, 0, 255));
         StrokeWidth = CreateInput<int>("StrokeWidth", "STROKE_WIDTH", 1);
-        Output = CreateOutput<ChunkyImage>("Output", "OUTPUT", null);
+        Output = CreateOutput<Image?>("Output", "OUTPUT", null);
     }
     
-    protected override ChunkyImage? OnExecute(KeyFrameTime frameTime)
+    protected override Image? OnExecute(KeyFrameTime frameTime)
     {
-        Output.Value = new ChunkyImage(new VecI(Radius.Value * 2, Radius.Value * 2));
+        Surface workingSurface = new Surface(new VecI(Radius.Value * 2, Radius.Value * 2));
         
-        Output.Value.EnqueueDrawEllipse(
-            RectI.Create(X.Value, Y.Value, Radius.Value * 2, Radius.Value * 2), 
-            FillColor.Value, StrokeColor.Value, StrokeWidth.Value);
+        using Paint paint = new Paint();
+        paint.StrokeWidth = StrokeWidth.Value;
+        paint.Color = StrokeColor.Value;
         
-        Output.Value.CommitChanges();
+        workingSurface.DrawingSurface.Canvas.DrawCircle(Radius.Value, Radius.Value, Radius.Value, paint);
+        
+        Output.Value = workingSurface.DrawingSurface.Snapshot();
+        
+        workingSurface.Dispose();
         
         return Output.Value;
     }

+ 13 - 10
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs

@@ -1,16 +1,17 @@
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 public class FolderNode : StructureNode, IReadOnlyFolderNode
 {
-    public InputProperty<ChunkyImage?> Content { get; }
+    public InputProperty<Image?> Content { get; }
 
     public FolderNode()
     {
-        Content = CreateInput<ChunkyImage?>("Content", "CONTENT", null);
+        Content = CreateInput<Image?>("Content", "CONTENT", null);
     }
 
     public override bool Validate()
@@ -20,7 +21,7 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode
 
     public override Node CreateCopy() => new FolderNode { MemberName = MemberName };
 
-    protected override ChunkyImage? OnExecute(KeyFrameTime frame)
+    protected override Image? OnExecute(KeyFrameTime frame)
     {
         if (!IsVisible.Value || Content.Value == null)
         {
@@ -28,28 +29,30 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode
             return Output.Value;
         }
 
-        VecI size = Content.Value?.CommittedSize ?? Background.Value.CommittedSize;
+        VecI size = Content.Value?.Size ?? Background.Value.Size;
         
-        Output.Value = new ChunkyImage(size);
+        Surface workingSurface = new Surface(size);
 
         if (Background.Value != null)
         {
-            Output.Value.EnqueueDrawUpToDateChunkyImage(VecI.Zero, Background.Value);
+            workingSurface.DrawingSurface.Canvas.DrawImage(Background.Value, 0, 0);
         }
 
         if (Content.Value != null)
         {
-            Output.Value.EnqueueDrawUpToDateChunkyImage(VecI.Zero, Content.Value);
+            workingSurface.DrawingSurface.Canvas.DrawImage(Content.Value, 0, 0);
         }
 
-        Output.Value.CommitChanges();
-
+        Output.Value = workingSurface.DrawingSurface.Snapshot();
+        
+        workingSurface.Dispose();
         return Output.Value;
     }
 
     public override RectI? GetTightBounds(KeyFrameTime frameTime)
     {
-        return Background.Value?.FindTightCommittedBounds();
+        // TODO: Implement GetTightBounds
+        return RectI.Create(0, 0, Content.Value?.Width ?? 0, Content.Value?.Height ?? 0); 
         /*if (Children.Count == 0)
       {
           return null;

+ 31 - 16
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs

@@ -1,5 +1,9 @@
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
@@ -10,6 +14,8 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
     private List<ImageFrame> frames = new List<ImageFrame>();
     private VecI size;
+    
+    private Paint blendPaint = new Paint();
 
     public ImageLayerNode(VecI size)
     {
@@ -20,45 +26,54 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
     public override RectI? GetTightBounds(KeyFrameTime frameTime)
     {
-        return Execute(frameTime).FindTightCommittedBounds();
+        return GetLayerImageAtFrame(frameTime.Frame).FindTightCommittedBounds();
     }
 
     public override bool Validate()
     {
-        return true; 
+        return true;
     }
 
-    protected override ChunkyImage OnExecute(KeyFrameTime frame)
+    protected override Image? OnExecute(KeyFrameTime frame)
     {
-        if (!IsVisible.Value)
+        if (!IsVisible.Value || Opacity.Value <= 0)
         {
             Output.Value = Background.Value;
             return Output.Value;
         }
-        
+
         var imageFrame = frames.FirstOrDefault(x => x.IsInFrame(frame.Frame));
         var frameImage = imageFrame?.Image ?? frames[0].Image;
 
-        ChunkyImage result;
+        Surface workingSurface;
+        
+        blendPaint.Color = new Color(255, 255, 255, (byte)Math.Round(Opacity.Value * 255));
+        blendPaint.BlendMode = DrawingApi.Core.Surface.BlendMode.Src;
 
         if (Background.Value != null)
         {
-            VecI targetSize = GetBiggerSize(frameImage.LatestSize, Background.Value.LatestSize);
-            result = new ChunkyImage(targetSize);
-            result.EnqueueDrawUpToDateChunkyImage(VecI.Zero, Background.Value);
-            result.EnqueueDrawUpToDateChunkyImage(VecI.Zero, frameImage);
-            result.CommitChanges();
+            VecI targetSize = GetBiggerSize(frameImage.LatestSize, Background.Value.Size);
+            workingSurface = new Surface(targetSize);
+            workingSurface.DrawingSurface.Canvas.DrawImage(Background.Value, 0, 0, blendPaint);
+            blendPaint.BlendMode = RenderingContext.GetDrawingBlendMode(BlendMode.Value);
             
-            Output.Value = result;
+            frameImage.DrawMostUpToDateRegionOn(
+                new RectI(0, 0, frameImage.LatestSize.X, frameImage.LatestSize.Y), 
+                ChunkResolution.Full, workingSurface.DrawingSurface, VecI.Zero, blendPaint);
         }
         else
         {
-            result = new ChunkyImage(frameImage.LatestSize);
-            result.EnqueueDrawUpToDateChunkyImage(VecI.Zero, frameImage);
-            result.CommitChanges();
-            Output.Value = result;
+            workingSurface = new Surface(frameImage.LatestSize);
+            
+            frameImage.DrawMostUpToDateRegionOn(
+                new RectI(0, 0, frameImage.LatestSize.X, frameImage.LatestSize.Y), 
+                ChunkResolution.Full, workingSurface.DrawingSurface, VecI.Zero, blendPaint);
         }
+
+
+        Output.Value = workingSurface.DrawingSurface.Snapshot();
         
+        workingSurface.Dispose();
         return Output.Value;
     }
 

+ 20 - 24
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MergeNode.cs

@@ -1,20 +1,21 @@
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 public class MergeNode : Node, IBackgroundInput
 {
-    public InputProperty<ChunkyImage?> Top { get; }
-    public InputProperty<ChunkyImage?> Bottom { get; }
-    public OutputProperty<ChunkyImage> Output { get; }
+    public InputProperty<Image?> Top { get; }
+    public InputProperty<Image?> Bottom { get; }
+    public OutputProperty<Image?> Output { get; }
     
     public MergeNode() 
     {
-        Top = CreateInput<ChunkyImage>("Top", "TOP", null);
-        Bottom = CreateInput<ChunkyImage>("Bottom", "BOTTOM", null);
-        Output = CreateOutput<ChunkyImage>("Output", "OUTPUT", null);
+        Top = CreateInput<Image?>("Top", "TOP", null);
+        Bottom = CreateInput<Image?>("Bottom", "BOTTOM", null);
+        Output = CreateOutput<Image?>("Output", "OUTPUT", null);
     }
     
     public override bool Validate()
@@ -27,7 +28,7 @@ public class MergeNode : Node, IBackgroundInput
         return new MergeNode();
     }
 
-    protected override ChunkyImage? OnExecute(KeyFrameTime frame)
+    protected override Image? OnExecute(KeyFrameTime frame)
     {
         if(Top.Value == null && Bottom.Value == null)
         {
@@ -35,31 +36,26 @@ public class MergeNode : Node, IBackgroundInput
             return null;
         }
         
-        VecI size = Top.Value?.CommittedSize ?? Bottom.Value.CommittedSize;
-
-        if (Output.Value == null || Output.Value.LatestSize != size)
-        {
-            Output.Value = new ChunkyImage(size);
-        }
-        else
-        {
-            Output.Value.EnqueueClear();
-        }
-
-        if (Bottom.Value != null)
+        int width = Top.Value?.Width ?? Bottom.Value.Width;
+        int height = Top.Value?.Height ?? Bottom.Value.Height;
+        
+        Surface workingSurface = new Surface(new VecI(width, height));
+        
+        if(Bottom.Value != null)
         {
-            Output.Value.EnqueueDrawUpToDateChunkyImage(VecI.Zero, Bottom.Value);
+            workingSurface.DrawingSurface.Canvas.DrawImage(Bottom.Value, 0, 0);
         }
         
-        if (Top.Value != null)
+        if(Top.Value != null)
         {
-            Output.Value.EnqueueDrawUpToDateChunkyImage(VecI.Zero, Top.Value);
+            workingSurface.DrawingSurface.Canvas.DrawImage(Top.Value, 0, 0);
         }
         
-        Output.Value.CommitChanges();
+        Output.Value = workingSurface.DrawingSurface.Snapshot();
         
+        workingSurface.Dispose();
         return Output.Value;
     }
 
-    InputProperty<ChunkyImage> IBackgroundInput.Background => Bottom;
+    InputProperty<Image> IBackgroundInput.Background => Bottom;
 }

+ 4 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs

@@ -1,6 +1,7 @@
 using System.Diagnostics;
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
@@ -18,19 +19,19 @@ public abstract class Node : IReadOnlyNode, IDisposable
     public IReadOnlyCollection<InputProperty> InputProperties => inputs;
     public IReadOnlyCollection<OutputProperty> OutputProperties => outputs;
     public IReadOnlyCollection<IReadOnlyNode> ConnectedOutputNodes => _connectedNodes;
-    public IReadOnlyChunkyImage CachedResult { get; private set; }
+    public Image? CachedResult { get; private set; }
 
     IReadOnlyCollection<IInputProperty> IReadOnlyNode.InputProperties => inputs;
     IReadOnlyCollection<IOutputProperty> IReadOnlyNode.OutputProperties => outputs;
     public VecD Position { get; set; }
 
-    public IReadOnlyChunkyImage? Execute(KeyFrameTime frameTime)
+    public Image? Execute(KeyFrameTime frameTime)
     {
         CachedResult = OnExecute(frameTime);
         return CachedResult;
     }
 
-    protected abstract ChunkyImage? OnExecute(KeyFrameTime frameTime);
+    protected abstract Image? OnExecute(KeyFrameTime frameTime);
     public abstract bool Validate();
 
     public void RemoveKeyFrame(Guid keyFrameGuid)

+ 5 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs

@@ -1,14 +1,15 @@
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 public class OutputNode : Node, IBackgroundInput
 {
-    public InputProperty<ChunkyImage?> Input { get; } 
+    public InputProperty<Image?> Input { get; } 
     public OutputNode()
     {
-        Input = CreateInput<ChunkyImage>("Background", "INPUT", null);
+        Input = CreateInput<Image>("Background", "INPUT", null);
     }
     
     public override bool Validate()
@@ -21,10 +22,10 @@ public class OutputNode : Node, IBackgroundInput
         return new OutputNode();
     }
 
-    protected override ChunkyImage? OnExecute(KeyFrameTime frame)
+    protected override Image? OnExecute(KeyFrameTime frame)
     {
         return Input.Value;
     }
 
-    InputProperty<ChunkyImage?> IBackgroundInput.Background => Input;
+    InputProperty<Image?> IBackgroundInput.Background => Input;
 }

+ 6 - 5
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs

@@ -1,13 +1,14 @@
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 public abstract class StructureNode : Node, IReadOnlyStructureNode, IBackgroundInput
 {
-    public InputProperty<ChunkyImage?> Background { get; }
+    public InputProperty<Image?> Background { get; }
     public InputProperty<float> Opacity { get; } 
     public InputProperty<bool> IsVisible { get; }
     public InputProperty<bool> ClipToMemberBelow { get; }
@@ -15,13 +16,13 @@ public abstract class StructureNode : Node, IReadOnlyStructureNode, IBackgroundI
     public InputProperty<ChunkyImage?> Mask { get; }
     public InputProperty<bool> MaskIsVisible { get; }
     
-    public OutputProperty<ChunkyImage?> Output { get; }
+    public OutputProperty<Image?> Output { get; }
     
     public string MemberName { get; set; } = string.Empty;
 
     protected StructureNode()
     {
-        Background = CreateInput<ChunkyImage?>("Background", "BACKGROUND", null);
+        Background = CreateInput<Image?>("Background", "BACKGROUND", null);
         Opacity = CreateInput<float>("Opacity", "OPACITY", 1);
         IsVisible = CreateInput<bool>("IsVisible", "IS_VISIBLE", true);
         ClipToMemberBelow = CreateInput<bool>("ClipToMemberBelow", "CLIP_TO_MEMBER_BELOW", false);
@@ -29,10 +30,10 @@ public abstract class StructureNode : Node, IReadOnlyStructureNode, IBackgroundI
         Mask = CreateInput<ChunkyImage?>("Mask", "MASK", null);
         MaskIsVisible = CreateInput<bool>("MaskIsVisible", "MASK_IS_VISIBLE", true);
         
-        Output = CreateOutput<ChunkyImage?>("Output", "OUTPUT", null);
+        Output = CreateOutput<Image?>("Output", "OUTPUT", null);
     }
 
-    protected abstract override ChunkyImage? OnExecute(KeyFrameTime frameTime);
+    protected abstract override Image? OnExecute(KeyFrameTime frameTime);
     public abstract override bool Validate();
 
     public abstract RectI? GetTightBounds(KeyFrameTime frameTime);

+ 2 - 1
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs

@@ -2,6 +2,7 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
@@ -50,7 +51,7 @@ public interface IReadOnlyDocument
     /// </summary>
     void ForEveryReadonlyMember(Action<IReadOnlyStructureNode> action);
     
-    public Surface? GetLayerRasterizedImage(Guid layerGuid, int frame);
+    public Image? GetLayerRasterizedImage(Guid layerGuid, int frame);
     public RectI? GetChunkAlignedLayerBounds(Guid layerGuid, int frame);
 
     /// <summary>

+ 4 - 3
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/NodeOperations.cs

@@ -2,14 +2,15 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
 
 namespace PixiEditor.ChangeableDocument.Changes.NodeGraph;
 
 public static class NodeOperations
 {
-    public static List<ConnectProperty_ChangeInfo> AppendMember(InputProperty<ChunkyImage?> parentInput,
-        OutputProperty<ChunkyImage> toAddOutput,
-        InputProperty<ChunkyImage> toAddInput, Guid memberId)
+    public static List<ConnectProperty_ChangeInfo> AppendMember(InputProperty<Image?> parentInput,
+        OutputProperty<Image> toAddOutput,
+        InputProperty<Image> toAddInput, Guid memberId)
     {
         List<ConnectProperty_ChangeInfo> changes = new();
         IOutputProperty? previouslyConnected = null;

+ 2 - 1
src/PixiEditor.ChangeableDocument/Changes/Structure/MoveStructureMember_Change.cs

@@ -3,6 +3,7 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 using PixiEditor.ChangeableDocument.Changes.NodeGraph;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
 
 namespace PixiEditor.ChangeableDocument.Changes.Structure;
 
@@ -49,7 +50,7 @@ internal class MoveStructureMember_Change : Change
 
         List<IChangeInfo> changes = new();
 
-        InputProperty<ChunkyImage?> inputProperty = backgroundInput.Background;
+        InputProperty<Image?> inputProperty = backgroundInput.Background;
 
         if (targetNode is FolderNode folder && putInsideFolder)
         {

+ 7 - 8
src/PixiEditor.ChangeableDocument/Rendering/DocumentEvaluator.cs

@@ -1,4 +1,5 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Rendering;
@@ -13,7 +14,7 @@ public static class DocumentEvaluator
         {
             RectI? transformedClippingRect = TransformClipRect(globalClippingRect, resolution, chunkPos);
 
-            ChunkyImage? evaluated = graph.Execute(frame);
+            Image? evaluated = graph.Execute(frame);
             if (evaluated is null)
             {
                 return new EmptyChunk();
@@ -28,9 +29,8 @@ public static class DocumentEvaluator
             {
                 chunk.Surface.DrawingSurface.Canvas.ClipRect((RectD)transformedClippingRect);
             }
-
-            evaluated.DrawMostUpToDateChunkOn(chunkPos, resolution, chunk.Surface.DrawingSurface, VecI.Zero,
-                context.ReplacingPaintWithOpacity);
+            
+            chunk.Surface.DrawingSurface.Canvas.DrawImage(evaluated, 0, 0, context.ReplacingPaintWithOpacity);
 
             chunk.Surface.DrawingSurface.Canvas.Restore();
 
@@ -50,7 +50,7 @@ public static class DocumentEvaluator
         {
             RectI? transformedClippingRect = TransformClipRect(globalClippingRect, resolution, chunkPos);
 
-            IReadOnlyChunkyImage? evaluated = node.Execute(frame);
+            Image? evaluated = node.Execute(frame);
             if (evaluated is null)
             {
                 return new EmptyChunk();
@@ -65,9 +65,8 @@ public static class DocumentEvaluator
             {
                 chunk.Surface.DrawingSurface.Canvas.ClipRect((RectD)transformedClippingRect);
             }
-
-            evaluated.DrawMostUpToDateChunkOn(chunkPos, resolution, chunk.Surface.DrawingSurface, VecI.Zero,
-                context.ReplacingPaintWithOpacity);
+            
+            chunk.Surface.DrawingSurface.Canvas.DrawImage(evaluated, 0, 0, context.ReplacingPaintWithOpacity);
 
             chunk.Surface.DrawingSurface.Canvas.Restore();
 

+ 1 - 1
src/PixiEditor.ChangeableDocument/Rendering/RenderingContext.cs

@@ -24,7 +24,7 @@ internal class RenderingContext : IDisposable
         ReplacingPaintWithOpacity.Color = opacityColor;
     }
 
-    private static DrawingApiBlendMode GetDrawingBlendMode(BlendMode blendMode)
+    public static DrawingApiBlendMode GetDrawingBlendMode(BlendMode blendMode)
     {
         return blendMode switch
         {

+ 1 - 0
src/PixiEditor.DrawingApi.Core/Bridge/Operations/ICanvasImplementation.cs

@@ -39,5 +39,6 @@ namespace PixiEditor.DrawingApi.Core.Bridge.Operations
         public void Dispose(IntPtr objectPointer);
         public object GetNativeCanvas(IntPtr objectPointer);
         public void DrawPaint(IntPtr objectPointer, Paint paint);
+        public void DrawImage(IntPtr objectPointer, Image image, int x, int y, Paint paint);
     }
 }

+ 3 - 0
src/PixiEditor.DrawingApi.Core/Surface/Canvas.cs

@@ -43,6 +43,9 @@ namespace PixiEditor.DrawingApi.Core.Surface
 
         public void DrawImage(Image image, int x, int y) =>
             DrawingBackendApi.Current.CanvasImplementation.DrawImage(ObjectPointer, image, x, y);
+        
+        public void DrawImage(Image image, int x, int y, Paint paint) =>
+            DrawingBackendApi.Current.CanvasImplementation.DrawImage(ObjectPointer, image, x, y, paint);
 
         public void DrawImage(Image image, RectD rect, Paint paint) =>
             DrawingBackendApi.Current.CanvasImplementation.DrawImage(ObjectPointer, image, rect, paint);

+ 3 - 0
src/PixiEditor.DrawingApi.Core/Surface/ImageData/Image.cs

@@ -1,5 +1,6 @@
 using System;
 using PixiEditor.DrawingApi.Core.Bridge;
+using PixiEditor.Numerics;
 
 namespace PixiEditor.DrawingApi.Core.Surface.ImageData
 {
@@ -19,6 +20,8 @@ namespace PixiEditor.DrawingApi.Core.Surface.ImageData
         
         public int Height => DrawingBackendApi.Current.ImageImplementation.GetHeight(ObjectPointer);
         
+        public VecI Size => new VecI(Width, Height);
+        
         public Image(IntPtr objPtr) : base(objPtr)
         {
         }

+ 6 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaCanvasImplementation.cs

@@ -55,6 +55,12 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             canvas.DrawImage(_imageImpl.ManagedInstances[image.ObjectPointer], x, y);
         }
 
+        public void DrawImage(IntPtr objPtr, Image image, int x, int y, Paint paint)
+        {
+            var canvas = ManagedInstances[objPtr];
+            canvas.DrawImage(_imageImpl.ManagedInstances[image.ObjectPointer], x, y, _paintImpl.ManagedInstances[paint.ObjectPointer]);
+        }
+
         public int Save(IntPtr objPtr)
         {
             return ManagedInstances[objPtr].Save();

+ 1 - 1
src/PixiEditor.UI.Common/Accents/Base.axaml

@@ -78,7 +78,7 @@
             <SolidColorBrush x:Key="SelectionFillBrush" Color="{StaticResource SelectionFillColor}"/>
             
             <SolidColorBrush x:Key="DefaultSocketBrush" Color="{StaticResource DefaultSocketColor}"/>
-            <SolidColorBrush x:Key="ChunkyImageSocketBrush" Color="{StaticResource ImageSocketColor}"/>
+            <SolidColorBrush x:Key="ImageSocketBrush" Color="{StaticResource ImageSocketColor}"/>
             <SolidColorBrush x:Key="BooleanSocketBrush" Color="{StaticResource BoolSocketColor}"/>
             <SolidColorBrush x:Key="SingleSocketBrush" Color="{StaticResource FloatSocketColor}"/>