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()
         {
-            return new UpdateStructureMemberOpacity_Change(MemberGuid, Opacity);
+            return new StructureMemberOpacity_UpdateableChange(MemberGuid, Opacity);
         }
 
         void IStartOrUpdateChangeAction.UpdateCorrespodingChange(IUpdateableChange change)
         {
-            ((UpdateStructureMemberOpacity_Change)change).Update(Opacity);
+            ((StructureMemberOpacity_UpdateableChange)change).Update(Opacity);
         }
     }
 
     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()
     {
-        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()
     {
-        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
     {
+        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
 {
-    internal class UpdateStructureMemberOpacity_Change : IUpdateableChange
+    internal class StructureMemberOpacity_UpdateableChange : IUpdateableChange
     {
         private Guid memberGuid;
 
         private float originalOpacity;
         public float NewOpacity { get; private set; }
 
-        public UpdateStructureMemberOpacity_Change(Guid memberGuid, float opacity)
+        public StructureMemberOpacity_UpdateableChange(Guid memberGuid, float opacity)
         {
             this.memberGuid = memberGuid;
             NewOpacity = opacity;
@@ -27,6 +27,8 @@ namespace ChangeableDocument.Changes
             originalOpacity = member.Opacity;
         }
 
+        public IChangeInfo? ApplyTemporarily(Document target) => Apply(target);
+
         public IChangeInfo? Apply(Document document)
         {
             var member = document.FindMemberOrThrow(memberGuid);
@@ -42,5 +44,7 @@ namespace ChangeableDocument.Changes
 
             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
 {
-    internal class SetStructureMemberProperties_Change : IChange
+    internal class StructureMemberProperties_Change : IChange
     {
         private Guid memberGuid;
 
@@ -13,7 +13,7 @@ namespace ChangeableDocument.Changes
         private string? originalName;
         public string? NewName { get; init; } = null;
 
-        public SetStructureMemberProperties_Change(Guid memberGuid)
+        public StructureMemberProperties_Change(Guid memberGuid)
         {
             this.memberGuid = memberGuid;
         }

+ 2 - 1
src/ChangeableDocument/DocumentChangeTracker.cs

@@ -71,13 +71,14 @@ namespace ChangeableDocument
                                 activeChange.Initialize(document);
                             }
                             act.UpdateCorrespodingChange(activeChange);
-                            changeInfos.Add(activeChange.Apply(document));
+                            changeInfos.Add(activeChange.ApplyTemporarily(document));
                             break;
                         case IEndChangeAction act:
                             if (activeChange == null)
                                 throw new Exception("Can't end a change: no changes are active");
                             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");
+                            changeInfos.Add(activeChange.Apply(document));
                             AddToUndo(activeChange);
                             activeChange = null;
                             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;
         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)
             {
                 chunk = freeChunks[^1];
@@ -21,14 +23,14 @@
             }
             else
             {
-                chunk = new ImageData(ChunkSize, ChunkSize, SkiaSharp.SKColorType.RgbaF16);
+                chunk = new Chunk();
             }
             usedChunks.Add(chunk);
 
             return chunk;
         }
 
-        public void ReturnChunk(ImageData chunk)
+        public void ReturnChunk(Chunk chunk)
         {
             if (!usedChunks.Contains(chunk))
                 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;
 
 [assembly: InternalsVisibleTo("ChunkyImageLibTest")]
@@ -11,10 +11,10 @@ namespace ChunkyImageLib
 
         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)
                 return MaybeGetChunk(x, y, chunks);
@@ -22,16 +22,24 @@ namespace ChunkyImageLib
             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()
         {
+            foreach (var operation in queuedOperations)
+                operation.Item1.Dispose();
             queuedOperations.Clear();
             foreach (var (_, chunk) in uncommitedChunks)
             {
@@ -45,6 +53,11 @@ namespace ChunkyImageLib
             ProcessQueueFinal();
         }
 
+        public HashSet<(int, int)> FindAffectedChunks()
+        {
+            return uncommitedChunks.Select(chunk => chunk.Key).ToHashSet();
+        }
+
         private void ProcessQueueFinal()
         {
             foreach (var (operation, operChunks) in queuedOperations)
@@ -53,6 +66,7 @@ namespace ChunkyImageLib
                 {
                     operation.DrawOnChunk(GetOrCreateCommitedChunk(x, y), x, y);
                 }
+                operation.Dispose();
             }
             queuedOperations.Clear();
         }
@@ -73,7 +87,7 @@ namespace ChunkyImageLib
 
         private void ProcessQueue(int chunkX, int chunkY)
         {
-            ImageData? targetChunk = null;
+            Chunk? targetChunk = null;
             foreach (var (operation, operChunks) in queuedOperations)
             {
                 if (!operChunks.Contains((chunkX, chunkY)))
@@ -85,32 +99,31 @@ namespace ChunkyImageLib
 
                 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)
                 return targetChunk;
             var newChunk = ChunkPool.Instance.BorrowChunk();
-            newChunk.SkiaSurface.Canvas.Clear();
+            newChunk.Surface.SkiaSurface.Canvas.Clear();
             chunks.Add((chunkX, chunkY), 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);
             if (targetChunk == null)
             {
                 targetChunk = ChunkPool.Instance.BorrowChunk();
                 var maybeCommitedChunk = MaybeGetChunk(chunkX, chunkY, chunks);
                 if (maybeCommitedChunk != null)
-                    maybeCommitedChunk.CopyTo(targetChunk);
+                    maybeCommitedChunk.Surface.CopyTo(targetChunk.Surface);
                 else
-                    targetChunk.SkiaSurface.Canvas.Clear();
+                    targetChunk.Surface.SkiaSurface.Canvas.Clear();
             }
             uncommitedChunks.Add((chunkX, chunkY), 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
 {
-    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
 {
     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
-            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
             using SKPaint paint = new()
             {
-                Color = FillColor,
+                Color = Data.FillColor,
                 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
-            paint.Color = StrokeColor;
+            paint.Color = Data.StrokeColor;
             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
-            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();
-            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)
@@ -76,14 +64,14 @@ namespace ChunkyImageLib.Operations
         {
             //we need to account for wide strokes covering multiple chunks
             //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)
                 return GetChunksForFilled(chunkSize);
 
             //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 (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)
         {
-            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();
             AddRectangle(minX, minY, maxX, maxY, 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
 {
-    public class ImageData : IDisposable
+    public class Surface : IDisposable
     {
         private bool disposed;
         private int bytesPerPixel;
         public SKColorType ColorType { get; }
-        public IntPtr PixelBuffer { get; }
+        private IntPtr PixelBuffer { get; }
         public SKSurface SkiaSurface { get; }
         public int Width { 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)
                 throw new ArgumentException("Unsupported color type");
@@ -28,7 +29,12 @@ namespace ChunkyImageLib
             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)
                 throw new ArgumentException("Target ImageData must have the same format");
@@ -76,7 +82,7 @@ namespace ChunkyImageLib
             GC.SuppressFinalize(this);
         }
 
-        ~ImageData()
+        ~Surface()
         {
             Marshal.FreeHGlobal(PixelBuffer);
         }

+ 24 - 22
src/ChunkyImageLibTest/RectangleOperationTests.cs

@@ -1,4 +1,5 @@
-using ChunkyImageLib.Operations;
+using ChunkyImageLib;
+using ChunkyImageLib.Operations;
 using SkiaSharp;
 using System.Collections.Generic;
 using Xunit;
@@ -7,16 +8,17 @@ namespace ChunkyImageLibTest
 {
     public class RectangleOperationTests
     {
+        const int chunkSize = ChunkPool.ChunkSize;
 // to keep expected rectangles aligned
 #pragma warning disable format
         [Fact]
         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) };
-            var actual = operation.FindAffectedChunks(32);
+            var actual = operation.FindAffectedChunks();
 
             Assert.Equal(expected, actual);
         }
@@ -24,11 +26,11 @@ namespace ChunkyImageLibTest
         [Fact]
         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) };
-            var actual = operation.FindAffectedChunks(32);
+            var actual = operation.FindAffectedChunks();
 
             Assert.Equal(expected, actual);
         }
@@ -36,8 +38,8 @@ namespace ChunkyImageLibTest
         [Fact]
         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()
             {
@@ -45,7 +47,7 @@ namespace ChunkyImageLibTest
                 (1, 2),         (3, 2), 
                 (1, 3), (2, 3), (3, 3),
             };
-            var actual = operation.FindAffectedChunks(32);
+            var actual = operation.FindAffectedChunks();
 
             Assert.Equal(expected, actual);
         }
@@ -53,8 +55,8 @@ namespace ChunkyImageLibTest
         [Fact]
         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()
             {
@@ -62,7 +64,7 @@ namespace ChunkyImageLibTest
                 (-4, -3),           (-2, -3),
                 (-4, -2), (-3, -2), (-2, -2),
             };
-            var actual = operation.FindAffectedChunks(32);
+            var actual = operation.FindAffectedChunks();
 
             Assert.Equal(expected, actual);
         }
@@ -70,8 +72,8 @@ namespace ChunkyImageLibTest
         [Fact]
         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()
             {
@@ -79,7 +81,7 @@ namespace ChunkyImageLibTest
                 (1, 2), (2, 2), (3, 2),
                 (1, 3), (2, 3), (3, 3),
             };
-            var actual = operation.FindAffectedChunks(32);
+            var actual = operation.FindAffectedChunks();
 
             Assert.Equal(expected, actual);
         }
@@ -87,8 +89,8 @@ namespace ChunkyImageLibTest
         [Fact]
         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()
             {
@@ -98,7 +100,7 @@ namespace ChunkyImageLibTest
                 (0, 3), (1, 3), (2, 3), (3, 3), (4, 3),
                 (0, 4), (1, 4), (2, 4), (3, 4), (4, 4),
             };
-            var actual = operation.FindAffectedChunks(32);
+            var actual = operation.FindAffectedChunks();
 
             Assert.Equal(expected, actual);
         }
@@ -106,11 +108,11 @@ namespace ChunkyImageLibTest
         [Fact]
         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) };
-            var actual = operation.FindAffectedChunks(32);
+            var actual = operation.FindAffectedChunks();
 
             Assert.Equal(expected, actual);
         }