Browse Source

Untested rectangle drawing operation

Equbuxu 3 years ago
parent
commit
2a83cb30e9

+ 36 - 0
src/ChangeableDocument/Actions/Drawing/RectangleActions.cs

@@ -0,0 +1,36 @@
+using ChangeableDocument.Changes;
+using ChangeableDocument.Changes.Drawing;
+using ChunkyImageLib.DataHolders;
+
+namespace ChangeableDocument.Actions.Drawing
+{
+    public record DrawRectangle_Action : IStartOrUpdateChangeAction
+    {
+        public DrawRectangle_Action(Guid layerGuid, ShapeData rectangle)
+        {
+            LayerGuid = layerGuid;
+            Rectangle = rectangle;
+        }
+
+        public Guid LayerGuid { get; }
+        public ShapeData Rectangle { get; }
+
+        void IStartOrUpdateChangeAction.UpdateCorrespodingChange(IUpdateableChange change)
+        {
+            ((DrawRectangle_UpdateableChange)change).Update(Rectangle);
+        }
+
+        IUpdateableChange IStartOrUpdateChangeAction.CreateCorrespondingChange()
+        {
+            return new DrawRectangle_UpdateableChange(LayerGuid, Rectangle);
+        }
+    }
+
+    public record EndDrawRectangle_Action : IEndChangeAction
+    {
+        bool IEndChangeAction.IsChangeTypeMatching(IChange change)
+        {
+            return change is DrawRectangle_UpdateableChange;
+        }
+    }
+}

+ 3 - 3
src/ChangeableDocument/Actions/OpacityActions.cs

@@ -15,17 +15,17 @@ namespace ChangeableDocument.Actions
 
 
         IUpdateableChange IStartOrUpdateChangeAction.CreateCorrespondingChange()
         IUpdateableChange IStartOrUpdateChangeAction.CreateCorrespondingChange()
         {
         {
-            return new UpdateStructureMemberOpacity_Change(MemberGuid, Opacity);
+            return new StructureMemberOpacity_UpdateableChange(MemberGuid, Opacity);
         }
         }
 
 
         void IStartOrUpdateChangeAction.UpdateCorrespodingChange(IUpdateableChange change)
         void IStartOrUpdateChangeAction.UpdateCorrespodingChange(IUpdateableChange change)
         {
         {
-            ((UpdateStructureMemberOpacity_Change)change).Update(Opacity);
+            ((StructureMemberOpacity_UpdateableChange)change).Update(Opacity);
         }
         }
     }
     }
 
 
     public record EndOpacityChange_Action : IEndChangeAction
     public record EndOpacityChange_Action : IEndChangeAction
     {
     {
-        bool IEndChangeAction.IsChangeTypeMatching(IChange change) => change is UpdateStructureMemberOpacity_Change;
+        bool IEndChangeAction.IsChangeTypeMatching(IChange change) => change is StructureMemberOpacity_UpdateableChange;
     }
     }
 }
 }

+ 2 - 2
src/ChangeableDocument/Actions/StructureActions.cs

@@ -67,7 +67,7 @@ public record SetStructureMemberName_Action : IMakeChangeAction
 
 
     IChange IMakeChangeAction.CreateCorrespondingChange()
     IChange IMakeChangeAction.CreateCorrespondingChange()
     {
     {
-        return new SetStructureMemberProperties_Change(GuidValue) { NewName = Name };
+        return new StructureMemberProperties_Change(GuidValue) { NewName = Name };
     }
     }
 }
 }
 
 
@@ -84,6 +84,6 @@ public record SetStructureMemberVisibility_Action : IMakeChangeAction
 
 
     IChange IMakeChangeAction.CreateCorrespondingChange()
     IChange IMakeChangeAction.CreateCorrespondingChange()
     {
     {
-        return new SetStructureMemberProperties_Change(GuidValue) { NewIsVisible = isVisible };
+        return new StructureMemberProperties_Change(GuidValue) { NewIsVisible = isVisible };
     }
     }
 }
 }

+ 8 - 0
src/ChangeableDocument/ChangeInfos/LayerImageChunks_ChangeInfo.cs

@@ -0,0 +1,8 @@
+namespace ChangeableDocument.ChangeInfos
+{
+    public record LayerImageChunks_ChangeInfo : IChangeInfo
+    {
+        public Guid LayerGuid { get; init; }
+        public HashSet<(int, int)>? Chunks { get; init; }
+    }
+}

+ 64 - 0
src/ChangeableDocument/Changes/Drawing/DrawRectangle_UpdateableChange.cs

@@ -0,0 +1,64 @@
+using ChangeableDocument.Changeables;
+using ChangeableDocument.ChangeInfos;
+using ChunkyImageLib;
+using ChunkyImageLib.DataHolders;
+
+namespace ChangeableDocument.Changes.Drawing
+{
+    internal class DrawRectangle_UpdateableChange : IUpdateableChange
+    {
+        private Guid layerGuid;
+        private ShapeData rect;
+        private ChunkStorage? storedChunks;
+        public DrawRectangle_UpdateableChange(Guid layerGuid, ShapeData rectangle)
+        {
+            this.layerGuid = layerGuid;
+            this.rect = rectangle;
+        }
+
+        public void Initialize(Document target) { }
+
+        public void Update(ShapeData updatedRectangle)
+        {
+            rect = updatedRectangle;
+        }
+
+        public IChangeInfo? ApplyTemporarily(Document target)
+        {
+            Layer layer = (Layer)target.FindMemberOrThrow(layerGuid);
+            layer.LayerImage.CancelChanges();
+            layer.LayerImage.DrawRectangle(rect);
+            return new LayerImageChunks_ChangeInfo()
+            {
+                Chunks = layer.LayerImage.FindAffectedChunks(),
+                LayerGuid = layerGuid
+            };
+        }
+
+        public IChangeInfo? Apply(Document target)
+        {
+            Layer layer = (Layer)target.FindMemberOrThrow(layerGuid);
+            var changes = ApplyTemporarily(target);
+            storedChunks = new ChunkStorage(layer.LayerImage, layer.LayerImage.FindAffectedChunks());
+            layer.LayerImage.CommitChanges();
+            return changes;
+        }
+
+        public IChangeInfo? Revert(Document target)
+        {
+            if (storedChunks == null)
+                throw new Exception("No stored chunks to revert to");
+            Layer layer = (Layer)target.FindMemberOrThrow(layerGuid);
+            storedChunks.ApplyChunksToImage(layer.LayerImage);
+            storedChunks.Dispose();
+            storedChunks = null;
+            var changes = new LayerImageChunks_ChangeInfo()
+            {
+                Chunks = layer.LayerImage.FindAffectedChunks(),
+                LayerGuid = layerGuid,
+            };
+            layer.LayerImage.CommitChanges();
+            return changes;
+        }
+    }
+}

+ 5 - 1
src/ChangeableDocument/Changes/IUpdateableChange.cs

@@ -1,6 +1,10 @@
-namespace ChangeableDocument.Changes
+using ChangeableDocument.Changeables;
+using ChangeableDocument.ChangeInfos;
+
+namespace ChangeableDocument.Changes
 {
 {
     internal interface IUpdateableChange : IChange
     internal interface IUpdateableChange : IChange
     {
     {
+        IChangeInfo? ApplyTemporarily(Document target);
     }
     }
 }
 }

+ 6 - 2
src/ChangeableDocument/Changes/UpdateStructureMemberOpacity_Change.cs → src/ChangeableDocument/Changes/StructureMemberOpacity_UpdateableChange.cs

@@ -3,14 +3,14 @@ using ChangeableDocument.ChangeInfos;
 
 
 namespace ChangeableDocument.Changes
 namespace ChangeableDocument.Changes
 {
 {
-    internal class UpdateStructureMemberOpacity_Change : IUpdateableChange
+    internal class StructureMemberOpacity_UpdateableChange : IUpdateableChange
     {
     {
         private Guid memberGuid;
         private Guid memberGuid;
 
 
         private float originalOpacity;
         private float originalOpacity;
         public float NewOpacity { get; private set; }
         public float NewOpacity { get; private set; }
 
 
-        public UpdateStructureMemberOpacity_Change(Guid memberGuid, float opacity)
+        public StructureMemberOpacity_UpdateableChange(Guid memberGuid, float opacity)
         {
         {
             this.memberGuid = memberGuid;
             this.memberGuid = memberGuid;
             NewOpacity = opacity;
             NewOpacity = opacity;
@@ -27,6 +27,8 @@ namespace ChangeableDocument.Changes
             originalOpacity = member.Opacity;
             originalOpacity = member.Opacity;
         }
         }
 
 
+        public IChangeInfo? ApplyTemporarily(Document target) => Apply(target);
+
         public IChangeInfo? Apply(Document document)
         public IChangeInfo? Apply(Document document)
         {
         {
             var member = document.FindMemberOrThrow(memberGuid);
             var member = document.FindMemberOrThrow(memberGuid);
@@ -42,5 +44,7 @@ namespace ChangeableDocument.Changes
 
 
             return new StructureMemberOpacity_ChangeInfo() { GuidValue = memberGuid };
             return new StructureMemberOpacity_ChangeInfo() { GuidValue = memberGuid };
         }
         }
+
+
     }
     }
 }
 }

+ 2 - 2
src/ChangeableDocument/Changes/SetStructureMemberProperties_Change.cs → src/ChangeableDocument/Changes/StructureMemberProperties_Change.cs

@@ -3,7 +3,7 @@ using ChangeableDocument.ChangeInfos;
 
 
 namespace ChangeableDocument.Changes
 namespace ChangeableDocument.Changes
 {
 {
-    internal class SetStructureMemberProperties_Change : IChange
+    internal class StructureMemberProperties_Change : IChange
     {
     {
         private Guid memberGuid;
         private Guid memberGuid;
 
 
@@ -13,7 +13,7 @@ namespace ChangeableDocument.Changes
         private string? originalName;
         private string? originalName;
         public string? NewName { get; init; } = null;
         public string? NewName { get; init; } = null;
 
 
-        public SetStructureMemberProperties_Change(Guid memberGuid)
+        public StructureMemberProperties_Change(Guid memberGuid)
         {
         {
             this.memberGuid = memberGuid;
             this.memberGuid = memberGuid;
         }
         }

+ 2 - 1
src/ChangeableDocument/DocumentChangeTracker.cs

@@ -71,13 +71,14 @@ namespace ChangeableDocument
                                 activeChange.Initialize(document);
                                 activeChange.Initialize(document);
                             }
                             }
                             act.UpdateCorrespodingChange(activeChange);
                             act.UpdateCorrespodingChange(activeChange);
-                            changeInfos.Add(activeChange.Apply(document));
+                            changeInfos.Add(activeChange.ApplyTemporarily(document));
                             break;
                             break;
                         case IEndChangeAction act:
                         case IEndChangeAction act:
                             if (activeChange == null)
                             if (activeChange == null)
                                 throw new Exception("Can't end a change: no changes are active");
                                 throw new Exception("Can't end a change: no changes are active");
                             if (!act.IsChangeTypeMatching(activeChange))
                             if (!act.IsChangeTypeMatching(activeChange))
                                 throw new Exception($"Trying to end a change via action of type {act.GetType()} while a change of type {activeChange.GetType()} is active");
                                 throw new Exception($"Trying to end a change via action of type {act.GetType()} while a change of type {activeChange.GetType()} is active");
+                            changeInfos.Add(activeChange.Apply(document));
                             AddToUndo(activeChange);
                             AddToUndo(activeChange);
                             activeChange = null;
                             activeChange = null;
                             break;
                             break;

+ 23 - 0
src/ChunkyImageLib/Chunk.cs

@@ -0,0 +1,23 @@
+using SkiaSharp;
+
+namespace ChunkyImageLib
+{
+    public class Chunk : IDisposable
+    {
+        internal Surface Surface { get; }
+        internal Chunk()
+        {
+            Surface = new Surface(ChunkPool.ChunkSize, ChunkPool.ChunkSize, SkiaSharp.SKColorType.RgbaF16);
+        }
+
+        public SKImage Snapshot()
+        {
+            return Surface.SkiaSurface.Snapshot();
+        }
+
+        public void Dispose()
+        {
+            Surface.Dispose();
+        }
+    }
+}

+ 8 - 6
src/ChunkyImageLib/ChunkPool.cs

@@ -8,12 +8,14 @@
         private static ChunkPool? instance;
         private static ChunkPool? instance;
         public static ChunkPool Instance => instance ??= new ChunkPool();
         public static ChunkPool Instance => instance ??= new ChunkPool();
 
 
-        private List<ImageData> freeChunks = new();
-        private HashSet<ImageData> usedChunks = new();
+        private List<Chunk> freeChunks = new();
+        private HashSet<Chunk> usedChunks = new();
 
 
-        public ImageData BorrowChunk()
+        public Chunk TransparentChunk { get; } = new Chunk();
+
+        public Chunk BorrowChunk()
         {
         {
-            ImageData chunk;
+            Chunk chunk;
             if (freeChunks.Count > 0)
             if (freeChunks.Count > 0)
             {
             {
                 chunk = freeChunks[^1];
                 chunk = freeChunks[^1];
@@ -21,14 +23,14 @@
             }
             }
             else
             else
             {
             {
-                chunk = new ImageData(ChunkSize, ChunkSize, SkiaSharp.SKColorType.RgbaF16);
+                chunk = new Chunk();
             }
             }
             usedChunks.Add(chunk);
             usedChunks.Add(chunk);
 
 
             return chunk;
             return chunk;
         }
         }
 
 
-        public void ReturnChunk(ImageData chunk)
+        public void ReturnChunk(Chunk chunk)
         {
         {
             if (!usedChunks.Contains(chunk))
             if (!usedChunks.Contains(chunk))
                 throw new Exception("This chunk wasn't borrowed");
                 throw new Exception("This chunk wasn't borrowed");

+ 47 - 0
src/ChunkyImageLib/ChunkStorage.cs

@@ -0,0 +1,47 @@
+namespace ChunkyImageLib
+{
+    public class ChunkStorage : IDisposable
+    {
+        private bool disposed = false;
+        private List<(int, int, Chunk?)> savedChunks = new();
+        public ChunkStorage(ChunkyImage image, HashSet<(int, int)> chunksToSave)
+        {
+            foreach (var (x, y) in chunksToSave)
+            {
+                Chunk? chunk = image.GetChunk(x, y);
+                if (chunk == null)
+                {
+                    savedChunks.Add((x, y, null));
+                    continue;
+                }
+                Chunk copy = ChunkPool.Instance.BorrowChunk();
+                chunk.Surface.CopyTo(copy.Surface);
+                savedChunks.Add((x, y, copy));
+            }
+        }
+
+        public void ApplyChunksToImage(ChunkyImage image)
+        {
+            if (disposed)
+                throw new Exception("This instance has been disposed");
+            foreach (var (x, y, chunk) in savedChunks)
+            {
+                if (chunk == null)
+                    image.DrawImage(x * ChunkPool.ChunkSize, y * ChunkPool.ChunkSize, ChunkPool.Instance.TransparentChunk.Surface);
+                else
+                    image.DrawImage(x * ChunkPool.ChunkSize, y * ChunkPool.ChunkSize, chunk.Surface);
+            }
+        }
+
+        public void Dispose()
+        {
+            if (disposed)
+                return;
+            foreach (var (x, y, chunk) in savedChunks)
+            {
+                if (chunk != null)
+                    ChunkPool.Instance.ReturnChunk(chunk);
+            }
+        }
+    }
+}

+ 31 - 18
src/ChunkyImageLib/ChunkyImage.cs

@@ -1,5 +1,5 @@
-using ChunkyImageLib.Operations;
-using SkiaSharp;
+using ChunkyImageLib.DataHolders;
+using ChunkyImageLib.Operations;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 
 
 [assembly: InternalsVisibleTo("ChunkyImageLibTest")]
 [assembly: InternalsVisibleTo("ChunkyImageLibTest")]
@@ -11,10 +11,10 @@ namespace ChunkyImageLib
 
 
         private Queue<(IOperation, HashSet<(int, int)>)> queuedOperations = new();
         private Queue<(IOperation, HashSet<(int, int)>)> queuedOperations = new();
 
 
-        private Dictionary<(int, int), ImageData> chunks = new();
-        private Dictionary<(int, int), ImageData> uncommitedChunks = new();
+        private Dictionary<(int, int), Chunk> chunks = new();
+        private Dictionary<(int, int), Chunk> uncommitedChunks = new();
 
 
-        public ImageData? GetChunk(int x, int y)
+        public Chunk? GetChunk(int x, int y)
         {
         {
             if (queuedOperations.Count == 0)
             if (queuedOperations.Count == 0)
                 return MaybeGetChunk(x, y, chunks);
                 return MaybeGetChunk(x, y, chunks);
@@ -22,16 +22,24 @@ namespace ChunkyImageLib
             return MaybeGetChunk(x, y, uncommitedChunks) ?? MaybeGetChunk(x, y, chunks);
             return MaybeGetChunk(x, y, uncommitedChunks) ?? MaybeGetChunk(x, y, chunks);
         }
         }
 
 
-        private ImageData? MaybeGetChunk(int x, int y, Dictionary<(int, int), ImageData> from) => from.ContainsKey((x, y)) ? from[(x, y)] : null;
+        private Chunk? MaybeGetChunk(int x, int y, Dictionary<(int, int), Chunk> from) => from.ContainsKey((x, y)) ? from[(x, y)] : null;
 
 
-        public void DrawRectangle(int x, int y, int width, int height, int strokeThickness, SKColor strokeColor, SKColor fillColor)
+        public void DrawRectangle(ShapeData rect)
         {
         {
-            RectangleOperation operation = new(x, y, width, height, strokeThickness, strokeColor, fillColor);
-            queuedOperations.Enqueue((operation, operation.FindAffectedChunks(ChunkPool.ChunkSize)));
+            RectangleOperation operation = new(rect);
+            queuedOperations.Enqueue((operation, operation.FindAffectedChunks()));
+        }
+
+        internal void DrawImage(int x, int y, Surface image)
+        {
+            ImageOperation operation = new(x, y, image);
+            queuedOperations.Enqueue((operation, operation.FindAffectedChunks()));
         }
         }
 
 
         public void CancelChanges()
         public void CancelChanges()
         {
         {
+            foreach (var operation in queuedOperations)
+                operation.Item1.Dispose();
             queuedOperations.Clear();
             queuedOperations.Clear();
             foreach (var (_, chunk) in uncommitedChunks)
             foreach (var (_, chunk) in uncommitedChunks)
             {
             {
@@ -45,6 +53,11 @@ namespace ChunkyImageLib
             ProcessQueueFinal();
             ProcessQueueFinal();
         }
         }
 
 
+        public HashSet<(int, int)> FindAffectedChunks()
+        {
+            return uncommitedChunks.Select(chunk => chunk.Key).ToHashSet();
+        }
+
         private void ProcessQueueFinal()
         private void ProcessQueueFinal()
         {
         {
             foreach (var (operation, operChunks) in queuedOperations)
             foreach (var (operation, operChunks) in queuedOperations)
@@ -53,6 +66,7 @@ namespace ChunkyImageLib
                 {
                 {
                     operation.DrawOnChunk(GetOrCreateCommitedChunk(x, y), x, y);
                     operation.DrawOnChunk(GetOrCreateCommitedChunk(x, y), x, y);
                 }
                 }
+                operation.Dispose();
             }
             }
             queuedOperations.Clear();
             queuedOperations.Clear();
         }
         }
@@ -73,7 +87,7 @@ namespace ChunkyImageLib
 
 
         private void ProcessQueue(int chunkX, int chunkY)
         private void ProcessQueue(int chunkX, int chunkY)
         {
         {
-            ImageData? targetChunk = null;
+            Chunk? targetChunk = null;
             foreach (var (operation, operChunks) in queuedOperations)
             foreach (var (operation, operChunks) in queuedOperations)
             {
             {
                 if (!operChunks.Contains((chunkX, chunkY)))
                 if (!operChunks.Contains((chunkX, chunkY)))
@@ -85,32 +99,31 @@ namespace ChunkyImageLib
 
 
                 operation.DrawOnChunk(targetChunk, chunkX, chunkY);
                 operation.DrawOnChunk(targetChunk, chunkX, chunkY);
             }
             }
-            queuedOperations.Clear();
         }
         }
 
 
-        private ImageData GetOrCreateCommitedChunk(int chunkX, int chunkY)
+        private Chunk GetOrCreateCommitedChunk(int chunkX, int chunkY)
         {
         {
-            ImageData? targetChunk = MaybeGetChunk(chunkX, chunkY, chunks);
+            Chunk? targetChunk = MaybeGetChunk(chunkX, chunkY, chunks);
             if (targetChunk != null)
             if (targetChunk != null)
                 return targetChunk;
                 return targetChunk;
             var newChunk = ChunkPool.Instance.BorrowChunk();
             var newChunk = ChunkPool.Instance.BorrowChunk();
-            newChunk.SkiaSurface.Canvas.Clear();
+            newChunk.Surface.SkiaSurface.Canvas.Clear();
             chunks.Add((chunkX, chunkY), newChunk);
             chunks.Add((chunkX, chunkY), newChunk);
             return newChunk;
             return newChunk;
         }
         }
 
 
-        private ImageData GetOrCreateUncommitedChunk(int chunkX, int chunkY)
+        private Chunk GetOrCreateUncommitedChunk(int chunkX, int chunkY)
         {
         {
-            ImageData? targetChunk;
+            Chunk? targetChunk;
             targetChunk = MaybeGetChunk(chunkX, chunkY, uncommitedChunks);
             targetChunk = MaybeGetChunk(chunkX, chunkY, uncommitedChunks);
             if (targetChunk == null)
             if (targetChunk == null)
             {
             {
                 targetChunk = ChunkPool.Instance.BorrowChunk();
                 targetChunk = ChunkPool.Instance.BorrowChunk();
                 var maybeCommitedChunk = MaybeGetChunk(chunkX, chunkY, chunks);
                 var maybeCommitedChunk = MaybeGetChunk(chunkX, chunkY, chunks);
                 if (maybeCommitedChunk != null)
                 if (maybeCommitedChunk != null)
-                    maybeCommitedChunk.CopyTo(targetChunk);
+                    maybeCommitedChunk.Surface.CopyTo(targetChunk.Surface);
                 else
                 else
-                    targetChunk.SkiaSurface.Canvas.Clear();
+                    targetChunk.Surface.SkiaSurface.Canvas.Clear();
             }
             }
             uncommitedChunks.Add((chunkX, chunkY), targetChunk);
             uncommitedChunks.Add((chunkX, chunkY), targetChunk);
             return targetChunk;
             return targetChunk;

+ 28 - 0
src/ChunkyImageLib/DataHolders/ShapeData.cs

@@ -0,0 +1,28 @@
+using SkiaSharp;
+
+namespace ChunkyImageLib.DataHolders
+{
+    public record ShapeData
+    {
+        public ShapeData(int x, int y, int width, int height, int strokeWidth, SKColor strokeColor, SKColor fillColor)
+        {
+            X = x;
+            Y = y;
+            Width = width;
+            Height = height;
+            StrokeColor = strokeColor;
+            FillColor = fillColor;
+            StrokeWidth = strokeWidth;
+        }
+
+        public int X { get; }
+        public int Y { get; }
+        public int Width { get; }
+        public int Height { get; }
+        public SKColor StrokeColor { get; }
+        public SKColor FillColor { get; }
+        public int StrokeWidth { get; }
+        public int MaxX => X + Width - 1;
+        public int MaxY => Y + Height - 1;
+    }
+}

+ 3 - 3
src/ChunkyImageLib/Operations/IOperation.cs

@@ -1,8 +1,8 @@
 namespace ChunkyImageLib.Operations
 namespace ChunkyImageLib.Operations
 {
 {
-    internal interface IOperation
+    internal interface IOperation : IDisposable
     {
     {
-        void DrawOnChunk(ImageData chunk, int chunkX, int chunkY);
-        HashSet<(int, int)> FindAffectedChunks(int chunkSize);
+        void DrawOnChunk(Chunk chunk, int chunkX, int chunkY);
+        HashSet<(int, int)> FindAffectedChunks();
     }
     }
 }
 }

+ 40 - 0
src/ChunkyImageLib/Operations/ImageOperation.cs

@@ -0,0 +1,40 @@
+namespace ChunkyImageLib.Operations
+{
+    internal record ImageOperation : IOperation
+    {
+        private int x;
+        private int y;
+        private Surface toPaint;
+        public ImageOperation(int x, int y, Surface image)
+        {
+            this.x = x;
+            this.y = y;
+            toPaint = new Surface(image);
+        }
+
+        public void DrawOnChunk(Chunk chunk, int chunkX, int chunkY)
+        {
+            chunk.Surface.SkiaSurface.Canvas.DrawSurface(toPaint.SkiaSurface, x - chunkX * ChunkPool.ChunkSize, y - chunkY * ChunkPool.ChunkSize);
+        }
+
+        public HashSet<(int, int)> FindAffectedChunks()
+        {
+            var (startX, startY) = OperationHelper.GetChunkPos(x, y, ChunkPool.ChunkSize);
+            var (endX, endY) = OperationHelper.GetChunkPos(x + toPaint.Width - 1, y + toPaint.Height - 1, ChunkPool.ChunkSize);
+            HashSet<(int, int)> output = new();
+            for (int cx = startX; cx <= endX; cx++)
+            {
+                for (int cy = startY; cy <= endY; cy++)
+                {
+                    output.Add((cx, cy));
+                }
+            }
+            return output;
+        }
+
+        public void Dispose()
+        {
+            toPaint.Dispose();
+        }
+    }
+}

+ 31 - 41
src/ChunkyImageLib/Operations/RectangleOperation.cs

@@ -1,65 +1,53 @@
-using SkiaSharp;
+using ChunkyImageLib.DataHolders;
+using SkiaSharp;
 
 
 namespace ChunkyImageLib.Operations
 namespace ChunkyImageLib.Operations
 {
 {
     internal record RectangleOperation : IOperation
     internal record RectangleOperation : IOperation
     {
     {
-        public RectangleOperation(int x, int y, int width, int height, int borderThickness, SKColor borderColor, SKColor fillColor)
+        public RectangleOperation(ShapeData rect)
         {
         {
-            StrokeColor = borderColor;
-            FillColor = fillColor;
-            StrokeWidth = borderThickness;
-            X = x;
-            Y = y;
-            Width = width;
-            Height = height;
+            Data = rect;
         }
         }
 
 
-        public SKColor StrokeColor { get; }
-        public SKColor FillColor { get; }
-        public int StrokeWidth { get; }
-        public int X { get; }
-        public int Y { get; }
-        public int Width { get; }
-        public int Height { get; }
-        public int MaxX => X + Width - 1;
-        public int MaxY => Y + Height - 1;
-
-        public void DrawOnChunk(ImageData chunk, int chunkX, int chunkY)
+        public ShapeData Data { get; }
+
+        public void DrawOnChunk(Chunk chunk, int chunkX, int chunkY)
         {
         {
+            var skiaSurf = chunk.Surface.SkiaSurface;
             // use a clipping rectangle with 2x stroke width to make sure stroke doesn't stick outside rect bounds
             // use a clipping rectangle with 2x stroke width to make sure stroke doesn't stick outside rect bounds
-            chunk.SkiaSurface.Canvas.Save();
-            var rect = SKRect.Create(X, Y, Width, Height);
-            chunk.SkiaSurface.Canvas.ClipRect(rect);
+            skiaSurf.Canvas.Save();
+            var rect = SKRect.Create(Data.X - chunkX * ChunkPool.ChunkSize, Data.Y - chunkY * ChunkPool.ChunkSize, Data.Width, Data.Height);
+            skiaSurf.Canvas.ClipRect(rect);
 
 
             // draw fill
             // draw fill
             using SKPaint paint = new()
             using SKPaint paint = new()
             {
             {
-                Color = FillColor,
+                Color = Data.FillColor,
                 Style = SKPaintStyle.Fill,
                 Style = SKPaintStyle.Fill,
             };
             };
 
 
-            if (FillColor.Alpha > 0)
-                chunk.SkiaSurface.Canvas.DrawRect(rect, paint);
+            if (Data.FillColor.Alpha > 0)
+                skiaSurf.Canvas.DrawRect(rect, paint);
 
 
             // draw stroke
             // draw stroke
-            paint.Color = StrokeColor;
+            paint.Color = Data.StrokeColor;
             paint.Style = SKPaintStyle.Stroke;
             paint.Style = SKPaintStyle.Stroke;
-            paint.StrokeWidth = StrokeWidth * 2;
+            paint.StrokeWidth = Data.StrokeWidth * 2;
 
 
-            chunk.SkiaSurface.Canvas.DrawRect(rect, paint);
+            skiaSurf.Canvas.DrawRect(rect, paint);
 
 
             // get rid of the clipping rectangle
             // get rid of the clipping rectangle
-            chunk.SkiaSurface.Canvas.Restore();
+            skiaSurf.Canvas.Restore();
         }
         }
 
 
-        public HashSet<(int, int)> FindAffectedChunks(int chunkSize)
+        public HashSet<(int, int)> FindAffectedChunks()
         {
         {
-            if (Width < 1 || Height < 1 || StrokeColor.Alpha == 0 && FillColor.Alpha == 0)
+            if (Data.Width < 1 || Data.Height < 1 || Data.StrokeColor.Alpha == 0 && Data.FillColor.Alpha == 0)
                 return new();
                 return new();
-            if (FillColor.Alpha != 0 || Width == 1 || Height == 1)
-                return GetChunksForFilled(chunkSize);
-            return GetChunksForStroke(chunkSize);
+            if (Data.FillColor.Alpha != 0 || Data.Width == 1 || Data.Height == 1)
+                return GetChunksForFilled(ChunkPool.ChunkSize);
+            return GetChunksForStroke(ChunkPool.ChunkSize);
         }
         }
 
 
         private static (int, int)? Inset(int min, int max, int inset)
         private static (int, int)? Inset(int min, int max, int inset)
@@ -76,14 +64,14 @@ namespace ChunkyImageLib.Operations
         {
         {
             //we need to account for wide strokes covering multiple chunks
             //we need to account for wide strokes covering multiple chunks
             //find inner stroke boudaries in pixel coords
             //find inner stroke boudaries in pixel coords
-            var xInset = Inset(X, MaxX, StrokeWidth);
-            var yInset = Inset(Y, MaxY, StrokeWidth);
+            var xInset = Inset(Data.X, Data.MaxX, Data.StrokeWidth);
+            var yInset = Inset(Data.Y, Data.MaxY, Data.StrokeWidth);
             if (xInset == null || yInset == null)
             if (xInset == null || yInset == null)
                 return GetChunksForFilled(chunkSize);
                 return GetChunksForFilled(chunkSize);
 
 
             //find two chunk rectanges, outer and inner
             //find two chunk rectanges, outer and inner
-            var (minX, minY) = OperationHelper.GetChunkPos(X, Y, chunkSize);
-            var (maxX, maxY) = OperationHelper.GetChunkPos(MaxX, MaxY, chunkSize);
+            var (minX, minY) = OperationHelper.GetChunkPos(Data.X, Data.Y, chunkSize);
+            var (maxX, maxY) = OperationHelper.GetChunkPos(Data.MaxX, Data.MaxY, chunkSize);
             var (minInsetX, minInsetY) = OperationHelper.GetChunkPos(xInset.Value.Item1, yInset.Value.Item1, chunkSize);
             var (minInsetX, minInsetY) = OperationHelper.GetChunkPos(xInset.Value.Item1, yInset.Value.Item1, chunkSize);
             var (maxInsetX, maxInsetY) = OperationHelper.GetChunkPos(xInset.Value.Item2, yInset.Value.Item2, chunkSize);
             var (maxInsetX, maxInsetY) = OperationHelper.GetChunkPos(xInset.Value.Item2, yInset.Value.Item2, chunkSize);
 
 
@@ -98,8 +86,8 @@ namespace ChunkyImageLib.Operations
 
 
         private HashSet<(int, int)> GetChunksForFilled(int chunkSize)
         private HashSet<(int, int)> GetChunksForFilled(int chunkSize)
         {
         {
-            var (minX, minY) = OperationHelper.GetChunkPos(X, Y, chunkSize);
-            var (maxX, maxY) = OperationHelper.GetChunkPos(MaxX, MaxY, chunkSize);
+            var (minX, minY) = OperationHelper.GetChunkPos(Data.X, Data.Y, chunkSize);
+            var (maxX, maxY) = OperationHelper.GetChunkPos(Data.MaxX, Data.MaxY, chunkSize);
             HashSet<(int, int)> output = new();
             HashSet<(int, int)> output = new();
             AddRectangle(minX, minY, maxX, maxY, output);
             AddRectangle(minX, minY, maxX, maxY, output);
             return output;
             return output;
@@ -115,5 +103,7 @@ namespace ChunkyImageLib.Operations
                 }
                 }
             }
             }
         }
         }
+
+        public void Dispose() { }
     }
     }
 }
 }

+ 11 - 5
src/ChunkyImageLib/ImageData.cs → src/ChunkyImageLib/Surface.cs

@@ -4,16 +4,17 @@ using System.Runtime.InteropServices;
 
 
 namespace ChunkyImageLib
 namespace ChunkyImageLib
 {
 {
-    public class ImageData : IDisposable
+    public class Surface : IDisposable
     {
     {
         private bool disposed;
         private bool disposed;
         private int bytesPerPixel;
         private int bytesPerPixel;
         public SKColorType ColorType { get; }
         public SKColorType ColorType { get; }
-        public IntPtr PixelBuffer { get; }
+        private IntPtr PixelBuffer { get; }
         public SKSurface SkiaSurface { get; }
         public SKSurface SkiaSurface { get; }
         public int Width { get; }
         public int Width { get; }
         public int Height { get; }
         public int Height { get; }
-        public ImageData(int width, int height, SKColorType colorType)
+
+        public Surface(int width, int height, SKColorType colorType)
         {
         {
             if (colorType is not SKColorType.RgbaF16 or SKColorType.Bgra8888)
             if (colorType is not SKColorType.RgbaF16 or SKColorType.Bgra8888)
                 throw new ArgumentException("Unsupported color type");
                 throw new ArgumentException("Unsupported color type");
@@ -28,7 +29,12 @@ namespace ChunkyImageLib
             SkiaSurface = CreateSKSurface();
             SkiaSurface = CreateSKSurface();
         }
         }
 
 
-        public unsafe void CopyTo(ImageData other)
+        public Surface(Surface original) : this(original.Width, original.Height, original.ColorType)
+        {
+            SkiaSurface.Canvas.DrawSurface(original.SkiaSurface, 0, 0);
+        }
+
+        public unsafe void CopyTo(Surface other)
         {
         {
             if (other.Width != Width || other.Height != Height || other.ColorType != ColorType)
             if (other.Width != Width || other.Height != Height || other.ColorType != ColorType)
                 throw new ArgumentException("Target ImageData must have the same format");
                 throw new ArgumentException("Target ImageData must have the same format");
@@ -76,7 +82,7 @@ namespace ChunkyImageLib
             GC.SuppressFinalize(this);
             GC.SuppressFinalize(this);
         }
         }
 
 
-        ~ImageData()
+        ~Surface()
         {
         {
             Marshal.FreeHGlobal(PixelBuffer);
             Marshal.FreeHGlobal(PixelBuffer);
         }
         }

+ 24 - 22
src/ChunkyImageLibTest/RectangleOperationTests.cs

@@ -1,4 +1,5 @@
-using ChunkyImageLib.Operations;
+using ChunkyImageLib;
+using ChunkyImageLib.Operations;
 using SkiaSharp;
 using SkiaSharp;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using Xunit;
 using Xunit;
@@ -7,16 +8,17 @@ namespace ChunkyImageLibTest
 {
 {
     public class RectangleOperationTests
     public class RectangleOperationTests
     {
     {
+        const int chunkSize = ChunkPool.ChunkSize;
 // to keep expected rectangles aligned
 // to keep expected rectangles aligned
 #pragma warning disable format
 #pragma warning disable format
         [Fact]
         [Fact]
         public void FindAffectedChunks_SmallStrokeOnly_FindsCorrectChunks()
         public void FindAffectedChunks_SmallStrokeOnly_FindsCorrectChunks()
         {
         {
-            var (x, y, w, h) = (0, 0, 32, 32);
-            RectangleOperation operation = new(x, y, w, h, 1, SKColors.Black, SKColors.Transparent);
+            var (x, y, w, h) = (0, 0, chunkSize, chunkSize);
+            RectangleOperation operation = new(new(x, y, w, h, 1, SKColors.Black, SKColors.Transparent));
 
 
             HashSet<(int, int)> expected = new() { (0, 0) };
             HashSet<(int, int)> expected = new() { (0, 0) };
-            var actual = operation.FindAffectedChunks(32);
+            var actual = operation.FindAffectedChunks();
 
 
             Assert.Equal(expected, actual);
             Assert.Equal(expected, actual);
         }
         }
@@ -24,11 +26,11 @@ namespace ChunkyImageLibTest
         [Fact]
         [Fact]
         public void FindAffectedChunks_2by2StrokeOnly_FindsCorrectChunks()
         public void FindAffectedChunks_2by2StrokeOnly_FindsCorrectChunks()
         {
         {
-            var (x, y, w, h) = (-32, -32, 64, 64);
-            RectangleOperation operation = new(x, y, w, h, 1, SKColors.Black, SKColors.Transparent);
+            var (x, y, w, h) = (-chunkSize, -chunkSize, chunkSize * 2, chunkSize * 2);
+            RectangleOperation operation = new(new(x, y, w, h, 1, SKColors.Black, SKColors.Transparent));
 
 
             HashSet<(int, int)> expected = new() { (-1, -1), (0, -1), (-1, 0), (0, 0) };
             HashSet<(int, int)> expected = new() { (-1, -1), (0, -1), (-1, 0), (0, 0) };
-            var actual = operation.FindAffectedChunks(32);
+            var actual = operation.FindAffectedChunks();
 
 
             Assert.Equal(expected, actual);
             Assert.Equal(expected, actual);
         }
         }
@@ -36,8 +38,8 @@ namespace ChunkyImageLibTest
         [Fact]
         [Fact]
         public void FindAffectedChunks_3x3PositiveStrokeOnly_FindsCorrectChunks()
         public void FindAffectedChunks_3x3PositiveStrokeOnly_FindsCorrectChunks()
         {
         {
-            var (x, y, w, h) = (48, 48, 64, 64);
-            RectangleOperation operation = new(x, y, w, h, 1, SKColors.Black, SKColors.Transparent);
+            var (x, y, w, h) = (chunkSize + chunkSize / 2, chunkSize + chunkSize / 2, chunkSize * 2, chunkSize * 2);
+            RectangleOperation operation = new(new(x, y, w, h, 1, SKColors.Black, SKColors.Transparent));
 
 
             HashSet<(int, int)> expected = new()
             HashSet<(int, int)> expected = new()
             {
             {
@@ -45,7 +47,7 @@ namespace ChunkyImageLibTest
                 (1, 2),         (3, 2), 
                 (1, 2),         (3, 2), 
                 (1, 3), (2, 3), (3, 3),
                 (1, 3), (2, 3), (3, 3),
             };
             };
-            var actual = operation.FindAffectedChunks(32);
+            var actual = operation.FindAffectedChunks();
 
 
             Assert.Equal(expected, actual);
             Assert.Equal(expected, actual);
         }
         }
@@ -53,8 +55,8 @@ namespace ChunkyImageLibTest
         [Fact]
         [Fact]
         public void FindAffectedChunks_3x3NegativeStrokeOnly_FindsCorrectChunks()
         public void FindAffectedChunks_3x3NegativeStrokeOnly_FindsCorrectChunks()
         {
         {
-            var (x, y, w, h) = (-112, -112, 64, 64);
-            RectangleOperation operation = new(x, y, w, h, 1, SKColors.Black, SKColors.Transparent);
+            var (x, y, w, h) = (-chunkSize * 3 + chunkSize / 2, -chunkSize * 3 + chunkSize / 2, chunkSize * 2, chunkSize * 2);
+            RectangleOperation operation = new(new(x, y, w, h, 1, SKColors.Black, SKColors.Transparent));
 
 
             HashSet<(int, int)> expected = new()
             HashSet<(int, int)> expected = new()
             {
             {
@@ -62,7 +64,7 @@ namespace ChunkyImageLibTest
                 (-4, -3),           (-2, -3),
                 (-4, -3),           (-2, -3),
                 (-4, -2), (-3, -2), (-2, -2),
                 (-4, -2), (-3, -2), (-2, -2),
             };
             };
-            var actual = operation.FindAffectedChunks(32);
+            var actual = operation.FindAffectedChunks();
 
 
             Assert.Equal(expected, actual);
             Assert.Equal(expected, actual);
         }
         }
@@ -70,8 +72,8 @@ namespace ChunkyImageLibTest
         [Fact]
         [Fact]
         public void FindAffectedChunks_3x3PositiveFilled_FindsCorrectChunks()
         public void FindAffectedChunks_3x3PositiveFilled_FindsCorrectChunks()
         {
         {
-            var (x, y, w, h) = (48, 48, 64, 64);
-            RectangleOperation operation = new(x, y, w, h, 1, SKColors.Black, SKColors.White);
+            var (x, y, w, h) = (chunkSize + chunkSize / 2, chunkSize + chunkSize / 2, chunkSize * 2, chunkSize * 2);
+            RectangleOperation operation = new(new(x, y, w, h, 1, SKColors.Black, SKColors.White));
 
 
             HashSet<(int, int)> expected = new()
             HashSet<(int, int)> expected = new()
             {
             {
@@ -79,7 +81,7 @@ namespace ChunkyImageLibTest
                 (1, 2), (2, 2), (3, 2),
                 (1, 2), (2, 2), (3, 2),
                 (1, 3), (2, 3), (3, 3),
                 (1, 3), (2, 3), (3, 3),
             };
             };
-            var actual = operation.FindAffectedChunks(32);
+            var actual = operation.FindAffectedChunks();
 
 
             Assert.Equal(expected, actual);
             Assert.Equal(expected, actual);
         }
         }
@@ -87,8 +89,8 @@ namespace ChunkyImageLibTest
         [Fact]
         [Fact]
         public void FindAffectedChunks_ThickPositiveStroke_FindsCorrectChunks()
         public void FindAffectedChunks_ThickPositiveStroke_FindsCorrectChunks()
         {
         {
-            var (x, y, w, h) = (16, 16, 128, 128);
-            RectangleOperation operation = new(x, y, w, h, 32, SKColors.Black, SKColors.Transparent);
+            var (x, y, w, h) = (chunkSize / 2, chunkSize / 2, chunkSize * 4, chunkSize * 4);
+            RectangleOperation operation = new(new(x, y, w, h, 32, SKColors.Black, SKColors.Transparent));
 
 
             HashSet<(int, int)> expected = new()
             HashSet<(int, int)> expected = new()
             {
             {
@@ -98,7 +100,7 @@ namespace ChunkyImageLibTest
                 (0, 3), (1, 3), (2, 3), (3, 3), (4, 3),
                 (0, 3), (1, 3), (2, 3), (3, 3), (4, 3),
                 (0, 4), (1, 4), (2, 4), (3, 4), (4, 4),
                 (0, 4), (1, 4), (2, 4), (3, 4), (4, 4),
             };
             };
-            var actual = operation.FindAffectedChunks(32);
+            var actual = operation.FindAffectedChunks();
 
 
             Assert.Equal(expected, actual);
             Assert.Equal(expected, actual);
         }
         }
@@ -106,11 +108,11 @@ namespace ChunkyImageLibTest
         [Fact]
         [Fact]
         public void FindAffectedChunks_SmallButThick_FindsCorrectChunks()
         public void FindAffectedChunks_SmallButThick_FindsCorrectChunks()
         {
         {
-            var (x, y, w, h) = (16, 16, 1, 1);
-            RectangleOperation operation = new(x, y, w, h, 256, SKColors.Black, SKColors.White);
+            var (x, y, w, h) = (chunkSize / 2, chunkSize / 2, 1, 1);
+            RectangleOperation operation = new(new(x, y, w, h, 256, SKColors.Black, SKColors.White));
 
 
             HashSet<(int, int)> expected = new() { (0, 0) };
             HashSet<(int, int)> expected = new() { (0, 0) };
-            var actual = operation.FindAffectedChunks(32);
+            var actual = operation.FindAffectedChunks();
 
 
             Assert.Equal(expected, actual);
             Assert.Equal(expected, actual);
         }
         }