Browse Source

Merge branch 'refs/heads/node-backend' into funcy-nodes

# Conflicts:
#	src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNode.cs
CPKreuz 1 year ago
parent
commit
69d4c6c8ba
38 changed files with 407 additions and 178 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. 1 1
      src/PixiEditor.AvaloniaUI/Views/Visuals/SurfaceControl.cs
  7. 6 6
      src/PixiEditor.ChangeableDocument/Changeables/Document.cs
  8. 2 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IBackgroundInput.cs
  9. 3 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNode.cs
  10. 4 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNodeGraph.cs
  11. 2 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs
  12. 15 8
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CircleNode.cs
  13. 13 10
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs
  14. 31 16
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs
  15. 20 24
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MergeNode.cs
  16. 4 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs
  17. 5 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs
  18. 6 5
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs
  19. 2 1
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs
  20. 4 3
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/NodeOperations.cs
  21. 2 1
      src/PixiEditor.ChangeableDocument/Changes/Structure/MoveStructureMember_Change.cs
  22. 7 8
      src/PixiEditor.ChangeableDocument/Rendering/DocumentEvaluator.cs
  23. 1 1
      src/PixiEditor.ChangeableDocument/Rendering/RenderingContext.cs
  24. 1 0
      src/PixiEditor.DrawingApi.Core/Bridge/IDrawingBackend.cs
  25. 2 0
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IPaintImplementation.cs
  26. 15 0
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IShaderImplementation.cs
  27. 2 0
      src/PixiEditor.DrawingApi.Core/Bridge/Operations/ICanvasImplementation.cs
  28. 1 0
      src/PixiEditor.DrawingApi.Core/Bridge/Operations/IImageImplementation.cs
  29. 21 10
      src/PixiEditor.DrawingApi.Core/Surface/Canvas.cs
  30. 9 1
      src/PixiEditor.DrawingApi.Core/Surface/ImageData/Image.cs
  31. 20 3
      src/PixiEditor.DrawingApi.Core/Surface/PaintImpl/Paint.cs
  32. 30 0
      src/PixiEditor.DrawingApi.Core/Surface/PaintImpl/Shader.cs
  33. 39 23
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaCanvasImplementation.cs
  34. 9 0
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaImageImplementation.cs
  35. 16 1
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPaintImplementation.cs
  36. 62 0
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaShaderImplementation.cs
  37. 5 1
      src/PixiEditor.DrawingApi.Skia/SkiaDrawingBackend.cs
  38. 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)
         {

+ 1 - 1
src/PixiEditor.AvaloniaUI/Views/Visuals/SurfaceControl.cs

@@ -72,7 +72,7 @@ internal class SurfaceControl : Control
         {
             result = Stretch.CalculateSize(availableSize, new Size(source.Size.X, source.Size.Y));
         }
-        else
+        else if(Width > 0 && Height > 0)
         {
             result = Stretch.CalculateSize(availableSize, new Size(Width, Height));
         }

+ 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,13 +9,12 @@ 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 string InternalName { 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,7 +19,7 @@ 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; }
 
     public virtual string InternalName { get; }
 
@@ -31,13 +32,13 @@ public abstract class Node : IReadOnlyNode, IDisposable
     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/IDrawingBackend.cs

@@ -18,5 +18,6 @@ namespace PixiEditor.DrawingApi.Core.Bridge
         public IImgDataImplementation ImgDataImplementation { get; }
         public IBitmapImplementation BitmapImplementation { get; }
         public IColorFilterImplementation ColorFilterImplementation { get; set; }
+        public IShaderImplementation ShaderImplementation { get; set; }
     }
 }

+ 2 - 0
src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IPaintImplementation.cs

@@ -28,5 +28,7 @@ namespace PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl
 
         public void SetColorFilter(Paint paint, ColorFilter value);
         public object GetNativePaint(IntPtr objectPointer);
+        public Shader GetShader(Paint paint);
+        public void SetShader(Paint paint, Shader shader);
     }
 }

+ 15 - 0
src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IShaderImplementation.cs

@@ -0,0 +1,15 @@
+using System;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.Numerics;
+
+namespace PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl;
+
+public interface IShaderImplementation
+{
+    public IntPtr CreateShader();
+    public void Dispose(IntPtr shaderObjPointer);
+    public Shader? CreateFromSksl(string sksl, bool isOpaque, out string errors);
+    public Shader CreateLinearGradient(VecI p1, VecI p2, Color[] colors);
+    public object GetNativeShader(IntPtr objectPointer);
+}

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

@@ -38,5 +38,7 @@ namespace PixiEditor.DrawingApi.Core.Bridge.Operations
         public void DrawBitmap(IntPtr objPtr, Bitmap bitmap, int x, int y);
         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);
     }
 }

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

@@ -20,5 +20,6 @@ namespace PixiEditor.DrawingApi.Core.Bridge.Operations
         public int GetWidth(IntPtr objectPointer);
         public int GetHeight(IntPtr objectPointer);
         public object GetNativeImage(IntPtr objectPointer);
+        public Image Clone(Image image);
     }
 }

+ 21 - 10
src/PixiEditor.DrawingApi.Core/Surface/Canvas.cs

@@ -10,9 +10,9 @@ using PixiEditor.Numerics;
 namespace PixiEditor.DrawingApi.Core.Surface
 {
     public delegate void SurfaceChangedEventHandler(RectD? changedRect);
+
     public class Canvas : NativeObject
     {
-
         public override object Native => DrawingBackendApi.Current.CanvasImplementation.GetNativeCanvas(ObjectPointer);
         public event SurfaceChangedEventHandler? Changed;
 
@@ -21,6 +21,7 @@ namespace PixiEditor.DrawingApi.Core.Surface
         }
 
         public void DrawPixel(VecI position, Paint drawingPaint) => DrawPixel(position.X, position.Y, drawingPaint);
+
         public void DrawPixel(int posX, int posY, Paint drawingPaint)
         {
             DrawingBackendApi.Current.CanvasImplementation.DrawPixel(ObjectPointer, posX, posY, drawingPaint);
@@ -34,15 +35,19 @@ namespace PixiEditor.DrawingApi.Core.Surface
         }
 
         public void DrawSurface(DrawingSurface original, int x, int y) => DrawSurface(original, x, y, null);
-        
+
         public void DrawSurface(DrawingSurface surfaceToDraw, VecI size, Paint paint)
         {
             DrawSurface(surfaceToDraw, size.X, size.Y, paint);
         }
 
-        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) =>
+            DrawingBackendApi.Current.CanvasImplementation.DrawImage(ObjectPointer, image, x, y);
         
-        public void DrawImage(Image image, RectD rect, Paint paint) => 
+        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);
 
         public int Save()
@@ -54,7 +59,7 @@ namespace PixiEditor.DrawingApi.Core.Surface
         {
             DrawingBackendApi.Current.CanvasImplementation.Restore(ObjectPointer);
         }
-        
+
         public void Scale(float s) => Scale(s, s);
 
         /// <param name="size">The amount to scale.</param>
@@ -93,7 +98,7 @@ namespace PixiEditor.DrawingApi.Core.Surface
             DrawingBackendApi.Current.CanvasImplementation.DrawPath(ObjectPointer, path, paint);
             Changed?.Invoke(path.Bounds);
         }
-        
+
         public void DrawPoint(VecI pos, Paint paint)
         {
             DrawingBackendApi.Current.CanvasImplementation.DrawPoint(ObjectPointer, pos, paint);
@@ -111,20 +116,20 @@ namespace PixiEditor.DrawingApi.Core.Surface
             DrawingBackendApi.Current.CanvasImplementation.DrawRect(ObjectPointer, x, y, width, height, paint);
             Changed?.Invoke(new RectD(x, y, width, height));
         }
-        
+
         public void DrawCircle(int x, int y, int radius, Paint paint)
         {
             DrawingBackendApi.Current.CanvasImplementation.DrawCircle(ObjectPointer, x, y, radius, paint);
             Changed?.Invoke(new RectD(x - radius, y - radius, radius * 2, radius * 2));
         }
-        
+
         public void DrawRect(RectI rect, Paint paint) => DrawRect(rect.X, rect.Y, rect.Width, rect.Height, paint);
 
         public void ClipPath(VectorPath clipPath) => ClipPath(clipPath, ClipOperation.Intersect);
 
         public void ClipPath(VectorPath clipPath, ClipOperation clipOperation) =>
             ClipPath(clipPath, clipOperation, false);
-        
+
         public void ClipPath(VectorPath clipPath, ClipOperation clipOperation, bool antialias)
         {
             DrawingBackendApi.Current.CanvasImplementation.ClipPath(ObjectPointer, clipPath, clipOperation, antialias);
@@ -140,7 +145,7 @@ namespace PixiEditor.DrawingApi.Core.Surface
             DrawingBackendApi.Current.CanvasImplementation.Clear(ObjectPointer);
             Changed?.Invoke(null);
         }
-        
+
         public void Clear(Color color)
         {
             DrawingBackendApi.Current.CanvasImplementation.Clear(ObjectPointer, color);
@@ -190,6 +195,12 @@ namespace PixiEditor.DrawingApi.Core.Surface
             Changed?.Invoke(null);
         }
 
+        public void DrawPaint(Paint paint)
+        {
+            DrawingBackendApi.Current.CanvasImplementation.DrawPaint(ObjectPointer, paint);
+            Changed?.Invoke(null);
+        }
+
         public override void Dispose()
         {
             DrawingBackendApi.Current.CanvasImplementation.Dispose(ObjectPointer);

+ 9 - 1
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
 {
@@ -11,7 +12,7 @@ namespace PixiEditor.DrawingApi.Core.Surface.ImageData
     ///     <para />
     ///     <para>An image always has a non-zero dimensions. If there is a request to create a new image, either directly or via a surface, and either of the requested dimensions are zero, then <see langword="null" /> will be returned.</para>
     /// </remarks>
-    public class Image : NativeObject
+    public class Image : NativeObject, ICloneable
     {
         public override object Native => DrawingBackendApi.Current.ImageImplementation.GetNativeImage(ObjectPointer);
 
@@ -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)
         {
         }
@@ -52,5 +55,10 @@ namespace PixiEditor.DrawingApi.Core.Surface.ImageData
         {
             return DrawingBackendApi.Current.ImageImplementation.Encode(this, format, quality);
         }
+
+        public object Clone()
+        {
+            return DrawingBackendApi.Current.ImageImplementation.Clone(this);
+        }
     }
 }

+ 20 - 3
src/PixiEditor.DrawingApi.Core/Surface/PaintImpl/Paint.cs

@@ -9,6 +9,9 @@ namespace PixiEditor.DrawingApi.Core.Surface.PaintImpl
     /// </summary>
     public class Paint : NativeObject
     {
+        private ColorFilter? colorFilter;
+        private Shader? shader;
+        
         public override object Native => DrawingBackendApi.Current.PaintImplementation.GetNativePaint(ObjectPointer);
 
         public Color Color
@@ -53,10 +56,24 @@ namespace PixiEditor.DrawingApi.Core.Surface.PaintImpl
             set => DrawingBackendApi.Current.PaintImplementation.SetStrokeWidth(this, value);
         }
         
-        public ColorFilter ColorFilter 
+        public ColorFilter ColorFilter
+        {
+            get => colorFilter ??= DrawingBackendApi.Current.PaintImplementation.GetColorFilter(this);
+            set
+            {
+                DrawingBackendApi.Current.PaintImplementation.SetColorFilter(this, value);
+                colorFilter = value;
+            }
+        }
+
+        public Shader Shader
         {
-            get => DrawingBackendApi.Current.PaintImplementation.GetColorFilter(this);
-            set => DrawingBackendApi.Current.PaintImplementation.SetColorFilter(this, value);
+            get => shader ??= DrawingBackendApi.Current.PaintImplementation.GetShader(this);
+            set
+            {
+                DrawingBackendApi.Current.PaintImplementation.SetShader(this, value);
+                shader = value;
+            }
         }
 
         public Paint(IntPtr objPtr) : base(objPtr)

+ 30 - 0
src/PixiEditor.DrawingApi.Core/Surface/PaintImpl/Shader.cs

@@ -0,0 +1,30 @@
+using System;
+using PixiEditor.DrawingApi.Core.Bridge;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.Numerics;
+
+namespace PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+
+public class Shader : NativeObject
+{
+    public override object Native => DrawingBackendApi.Current.ShaderImplementation.GetNativeShader(ObjectPointer);
+    
+    public Shader(IntPtr objPtr) : base(objPtr)
+    {
+    }
+    
+    public static Shader? CreateFromSksl(string sksl, bool isOpaque, out string errors)
+    {
+       return DrawingBackendApi.Current.ShaderImplementation.CreateFromSksl(sksl, isOpaque, out errors);
+    }
+
+    public override void Dispose()
+    {
+        DrawingBackendApi.Current.PaintImplementation.Dispose(ObjectPointer);
+    }
+
+    public static Shader CreateLinearGradient(VecI p1, VecI p2, Color[] colors)
+    {
+        return DrawingBackendApi.Current.ShaderImplementation.CreateLinearGradient(p1, p2, colors);
+    }
+}

+ 39 - 23
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaCanvasImplementation.cs

@@ -19,41 +19,46 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
         private readonly SkObjectImplementation<SKBitmap> _bitmapImpl;
         private readonly SkObjectImplementation<SKPath> _pathImpl;
 
-        public SkiaCanvasImplementation(SkObjectImplementation<SKPaint> paintImpl, SkObjectImplementation<SKImage> imageImpl, SkObjectImplementation<SKBitmap> bitmapImpl, SkObjectImplementation<SKPath> pathImpl)
+        public SkiaCanvasImplementation(SkObjectImplementation<SKPaint> paintImpl,
+            SkObjectImplementation<SKImage> imageImpl, SkObjectImplementation<SKBitmap> bitmapImpl,
+            SkObjectImplementation<SKPath> pathImpl)
         {
             _paintImpl = paintImpl;
             _imageImpl = imageImpl;
             _bitmapImpl = bitmapImpl;
             _pathImpl = pathImpl;
         }
-        
+
         public void SetSurfaceImplementation(SkiaSurfaceImplementation surfaceImpl)
         {
             _surfaceImpl = surfaceImpl;
         }
-        
+
         public void DrawPixel(IntPtr objectPointer, int posX, int posY, Paint drawingPaint)
         {
-            ManagedInstances[objectPointer].DrawPoint(
-                posX, 
-                posY, 
-                _paintImpl.ManagedInstances[drawingPaint.ObjectPointer]);
+            var canvas = ManagedInstances[objectPointer];
+            canvas.DrawPoint(posX, posY, _paintImpl.ManagedInstances[drawingPaint.ObjectPointer]);
         }
 
         public void DrawSurface(IntPtr objPtr, DrawingSurface drawingSurface, int x, int y, Paint? paint)
         {
-            ManagedInstances[objPtr]
-                .DrawSurface(
-                    _surfaceImpl.ManagedInstances[drawingSurface.ObjectPointer],
-                    x, y, 
-                    paint != null ? _paintImpl.ManagedInstances[paint.ObjectPointer] : null);
+            var canvas = ManagedInstances[objPtr];
+            canvas.DrawSurface(
+                _surfaceImpl.ManagedInstances[drawingSurface.ObjectPointer],
+                x, y,
+                paint != null ? _paintImpl.ManagedInstances[paint.ObjectPointer] : null);
         }
 
         public void DrawImage(IntPtr objPtr, Image image, int x, int y)
         {
-            ManagedInstances[objPtr]
-                .DrawImage(
-                    _imageImpl.ManagedInstances[image.ObjectPointer], x, y);
+            var canvas = ManagedInstances[objPtr];
+            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)
@@ -79,15 +84,15 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
         public void DrawPath(IntPtr objPtr, VectorPath path, Paint paint)
         {
             ManagedInstances[objPtr].DrawPath(
-                _pathImpl[path.ObjectPointer], 
+                _pathImpl[path.ObjectPointer],
                 _paintImpl[paint.ObjectPointer]);
         }
 
         public void DrawPoint(IntPtr objPtr, VecI pos, Paint paint)
         {
             ManagedInstances[objPtr].DrawPoint(
-                pos.X, 
-                pos.Y, 
+                pos.X,
+                pos.Y,
                 _paintImpl[paint.ObjectPointer]);
         }
 
@@ -101,12 +106,16 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
 
         public void DrawRect(IntPtr objPtr, int x, int y, int width, int height, Paint paint)
         {
-            ManagedInstances[objPtr].DrawRect(x, y, width, height, _paintImpl[paint.ObjectPointer]);
+            SKPaint skPaint = _paintImpl[paint.ObjectPointer];
+            
+            var canvas = ManagedInstances[objPtr];
+            canvas.DrawRect(x, y, width, height, skPaint);
         }
 
         public void DrawCircle(IntPtr objPtr, int x, int y, int radius, Paint paint)
         {
-            ManagedInstances[objPtr].DrawCircle(x, y, radius, _paintImpl[paint.ObjectPointer]);
+            var canvas = ManagedInstances[objPtr];
+            canvas.DrawCircle(x, y, radius, _paintImpl[paint.ObjectPointer]);
         }
 
         public void ClipPath(IntPtr objPtr, VectorPath clipPath, ClipOperation clipOperation, bool antialias)
@@ -133,7 +142,14 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
 
         public void DrawLine(IntPtr objPtr, VecI from, VecI to, Paint paint)
         {
-            ManagedInstances[objPtr].DrawLine(from.X, from.Y, to.X, to.Y, _paintImpl[paint.ObjectPointer]);
+            var canvas = ManagedInstances[objPtr];
+            canvas.DrawLine(from.X, from.Y, to.X, to.Y, _paintImpl[paint.ObjectPointer]);
+        }
+
+        public void DrawPaint(IntPtr objectPointer, Paint paint)
+        {
+            var canvas = ManagedInstances[objectPointer];
+            canvas.DrawPaint(_paintImpl[paint.ObjectPointer]);
         }
 
         public void Flush(IntPtr objPtr)
@@ -171,7 +187,7 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
         {
             ManagedInstances[objPtr].DrawImage(
                 _imageImpl[image.ObjectPointer],
-                rect.ToSKRect(), 
+                rect.ToSKRect(),
                 _paintImpl[paint.ObjectPointer]);
         }
 
@@ -183,7 +199,7 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
         public void Dispose(IntPtr objectPointer)
         {
             ManagedInstances[objectPointer].Dispose();
-            
+
             ManagedInstances.TryRemove(objectPointer, out _);
         }
 

+ 9 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaImageImplementation.cs

@@ -110,6 +110,15 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
         {
             return ManagedInstances[objectPointer].Height;
         }
+        
+        public Image Clone(Image image)
+        {
+            var native = ManagedInstances[image.ObjectPointer];
+            var encoded = native.Encode();
+            var clone = SKImage.FromEncodedData(encoded);
+            ManagedInstances[clone.Handle] = clone;
+            return new Image(clone.Handle);
+        }
 
         public object GetNativeImage(IntPtr objectPointer)
         {

+ 16 - 1
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPaintImplementation.cs

@@ -10,11 +10,14 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
     public class SkiaPaintImplementation : SkObjectImplementation<SKPaint>, IPaintImplementation
     {
         private readonly SkiaColorFilterImplementation colorFilterImplementation;
+        private readonly SkiaShaderImplementation shaderImplementation;
  
-        public SkiaPaintImplementation(SkiaColorFilterImplementation colorFilterImpl)
+        public SkiaPaintImplementation(SkiaColorFilterImplementation colorFilterImpl, SkiaShaderImplementation shaderImpl)
         {
             colorFilterImplementation = colorFilterImpl;
+            shaderImplementation = shaderImpl;
         }
+
         
         public IntPtr CreatePaint()
         {
@@ -145,6 +148,18 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             SKPaint skPaint = ManagedInstances[paint.ObjectPointer];
             skPaint.ColorFilter = colorFilterImplementation[value.ObjectPointer];
         }
+        
+        public Shader GetShader(Paint paint)
+        {
+            SKPaint skPaint = ManagedInstances[paint.ObjectPointer];
+            return new Shader(skPaint.Shader.Handle);
+        }
+        
+        public void SetShader(Paint paint, Shader shader)
+        {
+            SKPaint skPaint = ManagedInstances[paint.ObjectPointer];
+            skPaint.Shader = shaderImplementation[shader.ObjectPointer];
+        }
 
         public object GetNativePaint(IntPtr objectPointer)
         {

+ 62 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaShaderImplementation.cs

@@ -0,0 +1,62 @@
+using System;
+using PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.Numerics;
+using SkiaSharp;
+
+namespace PixiEditor.DrawingApi.Skia.Implementations
+{
+    public class SkiaShaderImplementation : SkObjectImplementation<SKShader>, IShaderImplementation
+    {
+        public SkiaShaderImplementation()
+        {
+        }
+
+        public IntPtr CreateShader()
+        {
+            SKShader skShader = SKShader.CreateEmpty();
+            ManagedInstances[skShader.Handle] = skShader;
+            return skShader.Handle;
+        }
+
+        public Shader? CreateFromSksl(string sksl, bool isOpaque, out string errors)
+        {
+            SKRuntimeEffect effect = SKRuntimeEffect.Create(sksl, out errors);
+            
+            if (string.IsNullOrEmpty(errors))
+            {
+                SKShader shader = effect.ToShader(isOpaque);
+                ManagedInstances[shader.Handle] = shader;
+                return new Shader(shader.Handle);
+            }
+            
+            return null;
+        }
+        
+        public Shader CreateLinearGradient(VecI p1, VecI p2, Color[] colors)
+        {
+            SKShader shader = SKShader.CreateLinearGradient(
+                new SKPoint(p1.X, p1.Y), 
+                new SKPoint(p2.X, p2.Y),
+                CastUtility.UnsafeArrayCast<Color, SKColor>(colors),
+                null, 
+                SKShaderTileMode.Clamp);
+            ManagedInstances[shader.Handle] = shader;
+            return new Shader(shader.Handle);
+        }
+
+        public object GetNativeShader(IntPtr objectPointer)
+        {
+            return ManagedInstances[objectPointer]; 
+        }
+
+        public void Dispose(IntPtr shaderObjPointer)
+        {
+            if (!ManagedInstances.TryGetValue(shaderObjPointer, out var shader)) return;
+            shader.Dispose();
+            ManagedInstances.TryRemove(shaderObjPointer, out _);
+        }
+    }
+}

+ 5 - 1
src/PixiEditor.DrawingApi.Skia/SkiaDrawingBackend.cs

@@ -19,6 +19,7 @@ namespace PixiEditor.DrawingApi.Skia
         public IColorSpaceImplementation ColorSpaceImplementation { get; }
         public IBitmapImplementation BitmapImplementation { get; }
         public IColorFilterImplementation ColorFilterImplementation { get; set; }
+        public IShaderImplementation ShaderImplementation { get; set; }
 
         public SkiaDrawingBackend()
         {
@@ -33,7 +34,10 @@ namespace PixiEditor.DrawingApi.Skia
             SkiaColorFilterImplementation colorFilterImpl = new SkiaColorFilterImplementation();
             ColorFilterImplementation = colorFilterImpl;
             
-            SkiaPaintImplementation paintImpl = new SkiaPaintImplementation(colorFilterImpl);
+            SkiaShaderImplementation shader = new SkiaShaderImplementation();
+            ShaderImplementation = shader;
+            
+            SkiaPaintImplementation paintImpl = new SkiaPaintImplementation(colorFilterImpl, shader);
             PaintImplementation = paintImpl;
             
             SkiaPathImplementation pathImpl = new SkiaPathImplementation();

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

@@ -85,7 +85,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}"/>
             <SolidColorBrush x:Key="DoubleSocketBrush" Color="{StaticResource DoubleSocketColor}"/>