Browse Source

Canvas resize + Disposable changes

Equbuxu 3 years ago
parent
commit
9a97796535
54 changed files with 492 additions and 173 deletions
  1. 18 0
      src/ChangeableDocument/Actions/Document/ResizeCanvas_Action.cs
  2. 1 1
      src/ChangeableDocument/Actions/Drawing/Rectangle/DrawRectangle_Action.cs
  3. 1 1
      src/ChangeableDocument/Actions/Drawing/Rectangle/EndDrawRectangle_Action.cs
  4. 1 1
      src/ChangeableDocument/Actions/Drawing/Selection/ClearSelection_Action.cs
  5. 1 1
      src/ChangeableDocument/Actions/Drawing/Selection/EndSelectRectangle_Action.cs
  6. 1 1
      src/ChangeableDocument/Actions/Drawing/Selection/SelectRectangle_Action.cs
  7. 1 1
      src/ChangeableDocument/Actions/Properties/EndOpacityChange_Action.cs
  8. 1 1
      src/ChangeableDocument/Actions/Properties/OpacityChange_Action.cs
  9. 1 1
      src/ChangeableDocument/Actions/Properties/SetStructureMemberName_Action.cs
  10. 1 1
      src/ChangeableDocument/Actions/Properties/SetStructureMemberVisibility_Action.cs
  11. 1 1
      src/ChangeableDocument/Actions/Structure/CreateStructureMember_Action.cs
  12. 1 1
      src/ChangeableDocument/Actions/Structure/DeleteStructureMember_Action.cs
  13. 1 1
      src/ChangeableDocument/Actions/Structure/MoveStructureMember_Action.cs
  14. 1 1
      src/ChangeableDocument/Actions/Undo/Redo_Action.cs
  15. 1 1
      src/ChangeableDocument/Actions/Undo/Undo_Action.cs
  16. 1 1
      src/ChangeableDocument/ChangeInfos/LayerImageChunks_ChangeInfo.cs
  17. 1 1
      src/ChangeableDocument/ChangeInfos/Selection_ChangeInfo.cs
  18. 6 0
      src/ChangeableDocument/ChangeInfos/Size_ChangeInfo.cs
  19. 3 3
      src/ChangeableDocument/ChangeInfos/StructureChangeInfos.cs
  20. 1 1
      src/ChangeableDocument/ChangeInfos/StructureMemberOpacity_ChangeInfo.cs
  21. 1 1
      src/ChangeableDocument/ChangeInfos/StructureMemberProperties_ChangeInfo.cs
  22. 6 4
      src/ChangeableDocument/Changeables/Document.cs
  23. 12 4
      src/ChangeableDocument/Changeables/Folder.cs
  24. 4 1
      src/ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs
  25. 4 4
      src/ChangeableDocument/Changeables/Interfaces/IReadOnlyStructureMember.cs
  26. 22 7
      src/ChangeableDocument/Changeables/Layer.cs
  27. 1 1
      src/ChangeableDocument/Changeables/Selection.cs
  28. 6 5
      src/ChangeableDocument/Changeables/StructureMember.cs
  29. 7 3
      src/ChangeableDocument/Changes/CreateStructureMember_Change.cs
  30. 7 1
      src/ChangeableDocument/Changes/DeleteStructureMember_Change.cs
  31. 5 0
      src/ChangeableDocument/Changes/Drawing/ClearSelection_Change.cs
  32. 5 0
      src/ChangeableDocument/Changes/Drawing/DrawRectangle_UpdateableChange.cs
  33. 5 0
      src/ChangeableDocument/Changes/Drawing/SelectRectangle_UpdateableChange.cs
  34. 1 1
      src/ChangeableDocument/Changes/IChange.cs
  35. 3 1
      src/ChangeableDocument/Changes/MoveStructureMember_Change.cs
  36. 84 0
      src/ChangeableDocument/Changes/ResizeCanvas_Change.cs
  37. 4 4
      src/ChangeableDocument/Changes/StructureMemberOpacity_UpdateableChange.cs
  38. 10 8
      src/ChangeableDocument/Changes/StructureMemberProperties_Change.cs
  39. 4 2
      src/ChangeableDocument/DocumentChangeTracker.cs
  40. 7 3
      src/ChunkyImageLib/ChunkPool.cs
  41. 171 76
      src/ChunkyImageLib/ChunkyImage.cs
  42. 1 1
      src/ChunkyImageLib/CommitedChunkStorage.cs
  43. 1 1
      src/ChunkyImageLib/Operations/ClearOperation.cs
  44. 1 1
      src/ChunkyImageLib/Operations/IDrawOperation.cs
  45. 1 1
      src/ChunkyImageLib/Operations/ImageOperation.cs
  46. 1 1
      src/ChunkyImageLib/Operations/RectangleOperation.cs
  47. 14 0
      src/ChunkyImageLib/Operations/ResizeOperation.cs
  48. 1 1
      src/ChunkyImageLib/Surface.cs
  49. 6 2
      src/PixiEditorPrototype/Models/ActionAccumulator.cs
  50. 20 3
      src/PixiEditorPrototype/Models/DocumentUpdater.cs
  51. 11 2
      src/PixiEditorPrototype/ViewModels/DocumentViewModel.cs
  52. 6 6
      src/PixiEditorPrototype/ViewModels/StructureMemberViewModel.cs
  53. 12 5
      src/PixiEditorPrototype/Views/DocumentView.xaml
  54. 4 3
      src/StructureRenderer/Renderer.cs

+ 18 - 0
src/ChangeableDocument/Actions/Document/ResizeCanvas_Action.cs

@@ -0,0 +1,18 @@
+using ChangeableDocument.Changes;
+using ChunkyImageLib.DataHolders;
+
+namespace ChangeableDocument.Actions.Document
+{
+    public record class ResizeCanvas_Action : IMakeChangeAction
+    {
+        public Vector2i Size { get; }
+        public ResizeCanvas_Action(Vector2i size)
+        {
+            Size = size;
+        }
+        IChange IMakeChangeAction.CreateCorrespondingChange()
+        {
+            return new ResizeCanvas_Change(Size);
+        }
+    }
+}

+ 1 - 1
src/ChangeableDocument/Actions/Drawing/Rectangle/DrawRectangle_Action.cs

@@ -4,7 +4,7 @@ using ChunkyImageLib.DataHolders;
 
 namespace ChangeableDocument.Actions.Drawing.Rectangle
 {
-    public record struct DrawRectangle_Action : IStartOrUpdateChangeAction
+    public record class DrawRectangle_Action : IStartOrUpdateChangeAction
     {
         public DrawRectangle_Action(Guid layerGuid, ShapeData rectangle)
         {

+ 1 - 1
src/ChangeableDocument/Actions/Drawing/Rectangle/EndDrawRectangle_Action.cs

@@ -3,7 +3,7 @@ using ChangeableDocument.Changes.Drawing;
 
 namespace ChangeableDocument.Actions.Drawing.Rectangle
 {
-    public record struct EndDrawRectangle_Action : IEndChangeAction
+    public record class EndDrawRectangle_Action : IEndChangeAction
     {
         bool IEndChangeAction.IsChangeTypeMatching(IChange change)
         {

+ 1 - 1
src/ChangeableDocument/Actions/Drawing/Selection/ClearSelection_Action.cs

@@ -3,7 +3,7 @@ using ChangeableDocument.Changes.Drawing;
 
 namespace ChangeableDocument.Actions.Drawing.Selection
 {
-    public record struct ClearSelection_Action : IMakeChangeAction
+    public record class ClearSelection_Action : IMakeChangeAction
     {
         IChange IMakeChangeAction.CreateCorrespondingChange()
         {

+ 1 - 1
src/ChangeableDocument/Actions/Drawing/Selection/EndSelectRectangle_Action.cs

@@ -3,7 +3,7 @@ using ChangeableDocument.Changes.Drawing;
 
 namespace ChangeableDocument.Actions.Drawing.Selection
 {
-    public record struct EndSelectRectangle_Action : IEndChangeAction
+    public record class EndSelectRectangle_Action : IEndChangeAction
     {
         bool IEndChangeAction.IsChangeTypeMatching(IChange change)
         {

+ 1 - 1
src/ChangeableDocument/Actions/Drawing/Selection/SelectRectangle_Action.cs

@@ -4,7 +4,7 @@ using ChunkyImageLib.DataHolders;
 
 namespace ChangeableDocument.Actions.Drawing.Selection
 {
-    public record struct SelectRectangle_Action : IStartOrUpdateChangeAction
+    public record class SelectRectangle_Action : IStartOrUpdateChangeAction
     {
         public Vector2i Pos { get; }
         public Vector2i Size { get; }

+ 1 - 1
src/ChangeableDocument/Actions/Properties/EndOpacityChange_Action.cs

@@ -2,7 +2,7 @@
 
 namespace ChangeableDocument.Actions.Properties
 {
-    public record struct EndOpacityChange_Action : IEndChangeAction
+    public record class EndOpacityChange_Action : IEndChangeAction
     {
         bool IEndChangeAction.IsChangeTypeMatching(IChange change) => change is StructureMemberOpacity_UpdateableChange;
     }

+ 1 - 1
src/ChangeableDocument/Actions/Properties/OpacityChange_Action.cs

@@ -2,7 +2,7 @@
 
 namespace ChangeableDocument.Actions.Properties
 {
-    public record struct OpacityChange_Action : IStartOrUpdateChangeAction
+    public record class OpacityChange_Action : IStartOrUpdateChangeAction
     {
         public OpacityChange_Action(Guid memberGuid, float opacity)
         {

+ 1 - 1
src/ChangeableDocument/Actions/Properties/SetStructureMemberName_Action.cs

@@ -2,7 +2,7 @@
 
 namespace ChangeableDocument.Actions.Properties;
 
-public record struct SetStructureMemberName_Action : IMakeChangeAction
+public record class SetStructureMemberName_Action : IMakeChangeAction
 {
     public SetStructureMemberName_Action(string name, Guid guidValue)
     {

+ 1 - 1
src/ChangeableDocument/Actions/Properties/SetStructureMemberVisibility_Action.cs

@@ -2,7 +2,7 @@
 
 namespace ChangeableDocument.Actions.Properties;
 
-public record struct SetStructureMemberVisibility_Action : IMakeChangeAction
+public record class SetStructureMemberVisibility_Action : IMakeChangeAction
 {
     public SetStructureMemberVisibility_Action(bool isVisible, Guid guidValue)
     {

+ 1 - 1
src/ChangeableDocument/Actions/Structure/CreateStructureMember_Action.cs

@@ -2,7 +2,7 @@
 
 namespace ChangeableDocument.Actions.Structure;
 
-public record struct CreateStructureMember_Action : IMakeChangeAction
+public record class CreateStructureMember_Action : IMakeChangeAction
 {
     public CreateStructureMember_Action(Guid parentGuid, int index, StructureMemberType type)
     {

+ 1 - 1
src/ChangeableDocument/Actions/Structure/DeleteStructureMember_Action.cs

@@ -2,7 +2,7 @@
 
 namespace ChangeableDocument.Actions.Structure;
 
-public record struct DeleteStructureMember_Action : IMakeChangeAction
+public record class DeleteStructureMember_Action : IMakeChangeAction
 {
     public DeleteStructureMember_Action(Guid guidValue)
     {

+ 1 - 1
src/ChangeableDocument/Actions/Structure/MoveStructureMember_Action.cs

@@ -2,7 +2,7 @@
 
 namespace ChangeableDocument.Actions.Structure;
 
-public record struct MoveStructureMember_Action : IMakeChangeAction
+public record class MoveStructureMember_Action : IMakeChangeAction
 {
     public MoveStructureMember_Action(Guid member, Guid targetFolder, int index)
     {

+ 1 - 1
src/ChangeableDocument/Actions/Undo/RedoAction.cs → src/ChangeableDocument/Actions/Undo/Redo_Action.cs

@@ -1,5 +1,5 @@
 namespace ChangeableDocument.Actions.Undo;
 
-public record struct RedoAction : IAction
+public record class Redo_Action : IAction
 {
 }

+ 1 - 1
src/ChangeableDocument/Actions/Undo/UndoAction.cs → src/ChangeableDocument/Actions/Undo/Undo_Action.cs

@@ -1,5 +1,5 @@
 namespace ChangeableDocument.Actions.Undo;
 
-public record struct UndoAction : IAction
+public record class Undo_Action : IAction
 {
 }

+ 1 - 1
src/ChangeableDocument/ChangeInfos/LayerImageChunks_ChangeInfo.cs

@@ -2,7 +2,7 @@
 
 namespace ChangeableDocument.ChangeInfos
 {
-    public record struct LayerImageChunks_ChangeInfo : IChangeInfo
+    public record class LayerImageChunks_ChangeInfo : IChangeInfo
     {
         public Guid LayerGuid { get; init; }
         public HashSet<Vector2i>? Chunks { get; init; }

+ 1 - 1
src/ChangeableDocument/ChangeInfos/Selection_ChangeInfo.cs

@@ -2,7 +2,7 @@
 
 namespace ChangeableDocument.ChangeInfos
 {
-    public record struct Selection_ChangeInfo : IChangeInfo
+    public record class Selection_ChangeInfo : IChangeInfo
     {
         public HashSet<Vector2i>? Chunks { get; init; }
     }

+ 6 - 0
src/ChangeableDocument/ChangeInfos/Size_ChangeInfo.cs

@@ -0,0 +1,6 @@
+namespace ChangeableDocument.ChangeInfos
+{
+    public record class Size_ChangeInfo : IChangeInfo
+    {
+    }
+}

+ 3 - 3
src/ChangeableDocument/ChangeInfos/StructureChangeInfos.cs

@@ -1,16 +1,16 @@
 namespace ChangeableDocument.ChangeInfos
 {
-    public record struct CreateStructureMember_ChangeInfo : IChangeInfo
+    public record class CreateStructureMember_ChangeInfo : IChangeInfo
     {
         public Guid GuidValue { get; init; }
     }
 
-    public record struct DeleteStructureMember_ChangeInfo : IChangeInfo
+    public record class DeleteStructureMember_ChangeInfo : IChangeInfo
     {
         public Guid GuidValue { get; init; }
     }
 
-    public record struct MoveStructureMember_ChangeInfo : IChangeInfo
+    public record class MoveStructureMember_ChangeInfo : IChangeInfo
     {
         public Guid GuidValue { get; init; }
     }

+ 1 - 1
src/ChangeableDocument/ChangeInfos/StructureMemberOpacity_ChangeInfo.cs

@@ -1,6 +1,6 @@
 namespace ChangeableDocument.ChangeInfos
 {
-    public record struct StructureMemberOpacity_ChangeInfo : IChangeInfo
+    public record class StructureMemberOpacity_ChangeInfo : IChangeInfo
     {
         public Guid GuidValue { get; init; }
     }

+ 1 - 1
src/ChangeableDocument/ChangeInfos/StructureMemberProperties_ChangeInfo.cs

@@ -1,6 +1,6 @@
 namespace ChangeableDocument.ChangeInfos
 {
-    public record struct StructureMemberProperties_ChangeInfo : IChangeInfo
+    public record class StructureMemberProperties_ChangeInfo : IChangeInfo
     {
         public Guid GuidValue { get; init; }
         public bool IsVisibleChanged { get; init; } = false;

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

@@ -1,4 +1,5 @@
 using ChangeableDocument.Changeables.Interfaces;
+using ChunkyImageLib.DataHolders;
 
 namespace ChangeableDocument.Changeables
 {
@@ -11,9 +12,10 @@ namespace ChangeableDocument.Changeables
         IReadOnlyStructureMember IReadOnlyDocument.FindMemberOrThrow(Guid guid) => FindMemberOrThrow(guid);
         (IReadOnlyStructureMember, IReadOnlyFolder) IReadOnlyDocument.FindChildAndParentOrThrow(Guid guid) => FindChildAndParentOrThrow(guid);
 
-        internal Folder StructureRoot { get; } = new() { ReadOnlyGuidValue = Guid.Empty };
+        public static Vector2i DefaultSize { get; } = new Vector2i(64, 64);
+        internal Folder StructureRoot { get; } = new() { GuidValue = Guid.Empty };
         internal Selection Selection { get; } = new();
-
+        public Vector2i Size { get; set; } = DefaultSize;
         public StructureMember FindMemberOrThrow(Guid guid) => FindMember(guid) ?? throw new Exception("Could not find member with guid " + guid.ToString());
         public StructureMember? FindMember(Guid guid)
         {
@@ -39,13 +41,13 @@ namespace ChangeableDocument.Changeables
 
         private bool FillMemberPath(Folder folder, Guid guid, List<StructureMember> toFill)
         {
-            if (folder.ReadOnlyGuidValue == guid)
+            if (folder.GuidValue == guid)
             {
                 return true;
             }
             foreach (var member in folder.Children)
             {
-                if (member is Layer childLayer && childLayer.ReadOnlyGuidValue == guid)
+                if (member is Layer childLayer && childLayer.GuidValue == guid)
                 {
                     toFill.Add(member);
                     return true;

+ 12 - 4
src/ChangeableDocument/Changeables/Folder.cs

@@ -17,12 +17,20 @@ namespace ChangeableDocument.Changeables
 
             return new Folder()
             {
-                ReadOnlyGuidValue = ReadOnlyGuidValue,
-                ReadOnlyIsVisible = ReadOnlyIsVisible,
-                ReadOnlyName = ReadOnlyName,
-                ReadOnlyOpacity = ReadOnlyOpacity,
+                GuidValue = GuidValue,
+                IsVisible = IsVisible,
+                Name = Name,
+                Opacity = Opacity,
                 Children = clonedChildren
             };
         }
+
+        public override void Dispose()
+        {
+            foreach (var child in Children)
+            {
+                child.Dispose();
+            }
+        }
     }
 }

+ 4 - 1
src/ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs

@@ -1,9 +1,12 @@
-namespace ChangeableDocument.Changeables.Interfaces
+using ChunkyImageLib.DataHolders;
+
+namespace ChangeableDocument.Changeables.Interfaces
 {
     public interface IReadOnlyDocument
     {
         IReadOnlyFolder ReadOnlyStructureRoot { get; }
         IReadOnlySelection ReadOnlySelection { get; }
+        Vector2i Size { get; }
         IReadOnlyStructureMember? FindMember(Guid guid);
         IReadOnlyStructureMember FindMemberOrThrow(Guid guid);
         (IReadOnlyStructureMember, IReadOnlyFolder) FindChildAndParentOrThrow(Guid guid);

+ 4 - 4
src/ChangeableDocument/Changeables/Interfaces/IReadOnlyStructureMember.cs

@@ -2,9 +2,9 @@
 {
     public interface IReadOnlyStructureMember
     {
-        bool ReadOnlyIsVisible { get; }
-        string ReadOnlyName { get; }
-        Guid ReadOnlyGuidValue { get; }
-        float ReadOnlyOpacity { get; }
+        bool IsVisible { get; }
+        string Name { get; }
+        Guid GuidValue { get; }
+        float Opacity { get; }
     }
 }

+ 22 - 7
src/ChangeableDocument/Changeables/Layer.cs

@@ -1,22 +1,37 @@
 using ChangeableDocument.Changeables.Interfaces;
 using ChunkyImageLib;
+using ChunkyImageLib.DataHolders;
 
 namespace ChangeableDocument.Changeables
 {
     internal class Layer : StructureMember, IReadOnlyLayer
     {
-        public ChunkyImage LayerImage { get; set; } = new();
+        public ChunkyImage LayerImage { get; set; }
         IReadOnlyChunkyImage IReadOnlyLayer.ReadOnlyLayerImage => LayerImage;
 
+        public Layer(Vector2i size)
+        {
+            LayerImage = new(size);
+        }
+
+        public Layer(ChunkyImage image)
+        {
+            LayerImage = image;
+        }
+
+        public override void Dispose()
+        {
+            LayerImage.Dispose();
+        }
+
         internal override Layer Clone()
         {
-            return new Layer()
+            return new Layer(LayerImage.CloneFromLatest())
             {
-                ReadOnlyGuidValue = ReadOnlyGuidValue,
-                ReadOnlyIsVisible = ReadOnlyIsVisible,
-                ReadOnlyName = ReadOnlyName,
-                ReadOnlyOpacity = ReadOnlyOpacity,
-                LayerImage = LayerImage.CloneFromLatest()
+                GuidValue = GuidValue,
+                IsVisible = IsVisible,
+                Name = Name,
+                Opacity = Opacity
             };
         }
     }

+ 1 - 1
src/ChangeableDocument/Changeables/Selection.cs

@@ -8,7 +8,7 @@ namespace ChangeableDocument.Changeables
     {
         public static SKColor SelectionColor { get; } = SKColors.CornflowerBlue;
         public bool IsEmptyAndInactive { get; set; } = true;
-        public ChunkyImage SelectionImage { get; set; } = new();
+        public ChunkyImage SelectionImage { get; set; } = new(new(64, 64));
 
         public IReadOnlyChunkyImage ReadOnlySelectionImage => SelectionImage;
         public bool ReadOnlyIsEmptyAndInactive => IsEmptyAndInactive;

+ 6 - 5
src/ChangeableDocument/Changeables/StructureMember.cs

@@ -2,13 +2,14 @@
 
 namespace ChangeableDocument.Changeables
 {
-    internal abstract class StructureMember : IChangeable, IReadOnlyStructureMember
+    internal abstract class StructureMember : IChangeable, IReadOnlyStructureMember, IDisposable
     {
-        public float ReadOnlyOpacity { get; set; } = 1f;
-        public bool ReadOnlyIsVisible { get; set; } = true;
-        public string ReadOnlyName { get; set; } = "Unnamed";
-        public Guid ReadOnlyGuidValue { get; init; }
+        public float Opacity { get; set; } = 1f;
+        public bool IsVisible { get; set; } = true;
+        public string Name { get; set; } = "Unnamed";
+        public Guid GuidValue { get; init; }
 
         internal abstract StructureMember Clone();
+        public abstract void Dispose();
     }
 }

+ 7 - 3
src/ChangeableDocument/Changes/CreateStructureMember_Change.cs

@@ -29,8 +29,8 @@ namespace ChangeableDocument.Changes
 
             StructureMember member = type switch
             {
-                StructureMemberType.Layer => new Layer() { ReadOnlyGuidValue = newMemberGuid },
-                StructureMemberType.Folder => new Folder() { ReadOnlyGuidValue = newMemberGuid },
+                StructureMemberType.Layer => new Layer(document.Size) { GuidValue = newMemberGuid },
+                StructureMemberType.Folder => new Folder() { GuidValue = newMemberGuid },
                 _ => throw new Exception("Cannon create member of type " + type.ToString())
             };
 
@@ -42,9 +42,13 @@ namespace ChangeableDocument.Changes
         public IChangeInfo Revert(Document document)
         {
             var folder = (Folder)document.FindMemberOrThrow(parentFolderGuid);
-            folder.Children.RemoveAt(folder.Children.FindIndex(child => child.ReadOnlyGuidValue == newMemberGuid));
+            var child = document.FindMemberOrThrow(newMemberGuid);
+            child.Dispose();
+            folder.Children.RemoveAt(folder.Children.FindIndex(child => child.GuidValue == newMemberGuid));
 
             return new DeleteStructureMember_ChangeInfo() { GuidValue = newMemberGuid };
         }
+
+        public void Dispose() { }
     }
 }

+ 7 - 1
src/ChangeableDocument/Changes/DeleteStructureMember_Change.cs

@@ -19,7 +19,7 @@ namespace ChangeableDocument.Changes
             var (member, parent) = document.FindChildAndParentOrThrow(memberGuid);
 
             originalIndex = parent.Children.IndexOf(member);
-            parentGuid = parent.ReadOnlyGuidValue;
+            parentGuid = parent.GuidValue;
             savedCopy = member.Clone();
         }
 
@@ -27,6 +27,7 @@ namespace ChangeableDocument.Changes
         {
             var (member, parent) = document.FindChildAndParentOrThrow(memberGuid);
             parent.Children.Remove(member);
+            member.Dispose();
             return new DeleteStructureMember_ChangeInfo() { GuidValue = memberGuid };
         }
 
@@ -37,5 +38,10 @@ namespace ChangeableDocument.Changes
             parent.Children.Insert(originalIndex, savedCopy!.Clone());
             return new CreateStructureMember_ChangeInfo() { GuidValue = memberGuid };
         }
+
+        public void Dispose()
+        {
+            savedCopy!.Dispose();
+        }
     }
 }

+ 5 - 0
src/ChangeableDocument/Changes/Drawing/ClearSelection_Change.cs

@@ -45,5 +45,10 @@ namespace ChangeableDocument.Changes.Drawing
 
             return new Selection_ChangeInfo() { Chunks = affChunks };
         }
+
+        public void Dispose()
+        {
+            savedSelection?.Dispose();
+        }
     }
 }

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

@@ -65,5 +65,10 @@ namespace ChangeableDocument.Changes.Drawing
             layer.LayerImage.CommitChanges();
             return changes;
         }
+
+        public void Dispose()
+        {
+            storedChunks?.Dispose();
+        }
     }
 }

+ 5 - 0
src/ChangeableDocument/Changes/Drawing/SelectRectangle_UpdateableChange.cs

@@ -60,5 +60,10 @@ namespace ChangeableDocument.Changes.Drawing
             target.Selection.SelectionImage.CommitChanges();
             return changes;
         }
+
+        public void Dispose()
+        {
+            originalSelectionState?.Dispose();
+        }
     }
 }

+ 1 - 1
src/ChangeableDocument/Changes/IChange.cs

@@ -3,7 +3,7 @@ using ChangeableDocument.ChangeInfos;
 
 namespace ChangeableDocument.Changes
 {
-    internal interface IChange
+    internal interface IChange : IDisposable
     {
         void Initialize(Document target);
         IChangeInfo? Apply(Document target);

+ 3 - 1
src/ChangeableDocument/Changes/MoveStructureMember_Change.cs

@@ -23,7 +23,7 @@ namespace ChangeableDocument.Changes
         public void Initialize(Document document)
         {
             var (member, curFolder) = document.FindChildAndParentOrThrow(memberGuid);
-            originalFolderGuid = curFolder.ReadOnlyGuidValue;
+            originalFolderGuid = curFolder.GuidValue;
             originalFolderIndex = curFolder.Children.IndexOf(member);
         }
 
@@ -47,5 +47,7 @@ namespace ChangeableDocument.Changes
             Move(target, memberGuid, originalFolderGuid, originalFolderIndex);
             return new MoveStructureMember_ChangeInfo() { GuidValue = memberGuid };
         }
+
+        public void Dispose() { }
     }
 }

+ 84 - 0
src/ChangeableDocument/Changes/ResizeCanvas_Change.cs

@@ -0,0 +1,84 @@
+using ChangeableDocument.Changeables;
+using ChangeableDocument.ChangeInfos;
+using ChunkyImageLib;
+using ChunkyImageLib.DataHolders;
+
+namespace ChangeableDocument.Changes
+{
+    internal class ResizeCanvas_Change : IChange
+    {
+        private Vector2i originalSize;
+        private Dictionary<Guid, CommitedChunkStorage> deletedChunks = new();
+        private CommitedChunkStorage? selectionChunkStorage;
+        private Vector2i newSize;
+        public ResizeCanvas_Change(Vector2i size)
+        {
+            newSize = size;
+        }
+        public void Initialize(Document target)
+        {
+            originalSize = target.Size;
+        }
+
+        private void ForEachLayer(Folder folder, Action<Layer> action)
+        {
+            foreach (var child in folder.Children)
+            {
+                if (child is Layer layer)
+                {
+                    action(layer);
+                }
+                else if (child is Folder innerFolder)
+                    ForEachLayer(innerFolder, action);
+            }
+        }
+
+        public IChangeInfo? Apply(Document target)
+        {
+            target.Size = newSize;
+
+            ForEachLayer(target.StructureRoot, (layer) =>
+            {
+                layer.LayerImage.Resize(newSize);
+                deletedChunks.Add(layer.GuidValue, new CommitedChunkStorage(layer.LayerImage, layer.LayerImage.FindAffectedChunks()));
+                layer.LayerImage.CommitChanges();
+            });
+
+            target.Selection.SelectionImage.Resize(newSize);
+            selectionChunkStorage = new(target.Selection.SelectionImage, target.Selection.SelectionImage.FindAffectedChunks());
+            target.Selection.SelectionImage.CommitChanges();
+
+            return new Size_ChangeInfo();
+        }
+
+        public IChangeInfo? Revert(Document target)
+        {
+            target.Size = originalSize;
+            ForEachLayer(target.StructureRoot, (layer) =>
+            {
+                layer.LayerImage.Resize(originalSize);
+                deletedChunks[layer.GuidValue].ApplyChunksToImage(layer.LayerImage);
+                layer.LayerImage.CommitChanges();
+            });
+
+            target.Selection.SelectionImage.Resize(originalSize);
+            selectionChunkStorage!.ApplyChunksToImage(target.Selection.SelectionImage);
+            target.Selection.SelectionImage.CommitChanges();
+            selectionChunkStorage.Dispose();
+            selectionChunkStorage = null;
+
+            foreach (var stored in deletedChunks)
+                stored.Value.Dispose();
+            deletedChunks = new();
+
+            return new Size_ChangeInfo();
+        }
+
+        public void Dispose()
+        {
+            foreach (var layer in deletedChunks)
+                layer.Value.Dispose();
+            selectionChunkStorage?.Dispose();
+        }
+    }
+}

+ 4 - 4
src/ChangeableDocument/Changes/StructureMemberOpacity_UpdateableChange.cs

@@ -24,7 +24,7 @@ namespace ChangeableDocument.Changes
         public void Initialize(Document document)
         {
             var member = document.FindMemberOrThrow(memberGuid);
-            originalOpacity = member.ReadOnlyOpacity;
+            originalOpacity = member.Opacity;
         }
 
         public IChangeInfo? ApplyTemporarily(Document target) => Apply(target);
@@ -32,7 +32,7 @@ namespace ChangeableDocument.Changes
         public IChangeInfo? Apply(Document document)
         {
             var member = document.FindMemberOrThrow(memberGuid);
-            member.ReadOnlyOpacity = NewOpacity;
+            member.Opacity = NewOpacity;
 
             return new StructureMemberOpacity_ChangeInfo() { GuidValue = memberGuid };
         }
@@ -40,11 +40,11 @@ namespace ChangeableDocument.Changes
         public IChangeInfo? Revert(Document document)
         {
             var member = document.FindMemberOrThrow(memberGuid);
-            member.ReadOnlyOpacity = originalOpacity;
+            member.Opacity = originalOpacity;
 
             return new StructureMemberOpacity_ChangeInfo() { GuidValue = memberGuid };
         }
 
-
+        public void Dispose() { }
     }
 }

+ 10 - 8
src/ChangeableDocument/Changes/StructureMemberProperties_Change.cs

@@ -21,19 +21,19 @@ namespace ChangeableDocument.Changes
         public void Initialize(Document document)
         {
             var member = document.FindMemberOrThrow(memberGuid);
-            if (NewIsVisible != null) originalIsVisible = member.ReadOnlyIsVisible;
-            if (NewName != null) originalName = member.ReadOnlyName;
+            if (NewIsVisible != null) originalIsVisible = member.IsVisible;
+            if (NewName != null) originalName = member.Name;
         }
 
         public IChangeInfo? Apply(Document document)
         {
             var member = document.FindMemberOrThrow(memberGuid);
-            if (NewIsVisible != null) member.ReadOnlyIsVisible = NewIsVisible.Value;
-            if (NewName != null) member.ReadOnlyName = NewName;
+            if (NewIsVisible != null) member.IsVisible = NewIsVisible.Value;
+            if (NewName != null) member.Name = NewName;
 
             return new StructureMemberProperties_ChangeInfo()
             {
-                GuidValue = member.ReadOnlyGuidValue,
+                GuidValue = member.GuidValue,
                 IsVisibleChanged = NewIsVisible != null,
                 NameChanged = NewName != null
             };
@@ -42,15 +42,17 @@ namespace ChangeableDocument.Changes
         public IChangeInfo? Revert(Document document)
         {
             var member = document.FindMemberOrThrow(memberGuid);
-            if (NewIsVisible != null) member.ReadOnlyIsVisible = originalIsVisible;
-            if (NewName != null) member.ReadOnlyName = originalName!;
+            if (NewIsVisible != null) member.IsVisible = originalIsVisible;
+            if (NewName != null) member.Name = originalName!;
 
             return new StructureMemberProperties_ChangeInfo()
             {
-                GuidValue = member.ReadOnlyGuidValue,
+                GuidValue = member.GuidValue,
                 IsVisibleChanged = NewIsVisible != null,
                 NameChanged = NewName != null,
             };
         }
+
+        public void Dispose() { }
     }
 }

+ 4 - 2
src/ChangeableDocument/DocumentChangeTracker.cs

@@ -25,6 +25,8 @@ namespace ChangeableDocument
         private void AddToUndo(IChange change)
         {
             undoStack.Push(change);
+            foreach (var changeToDispose in redoStack)
+                changeToDispose.Dispose();
             redoStack.Clear();
         }
 
@@ -83,10 +85,10 @@ namespace ChangeableDocument
                             AddToUndo(activeChange);
                             activeChange = null;
                             break;
-                        case UndoAction act:
+                        case Undo_Action act:
                             changeInfos.Add(Undo());
                             break;
-                        case RedoAction act:
+                        case Redo_Action act:
                             changeInfos.Add(Redo());
                             break;
                         default:

+ 7 - 3
src/ChunkyImageLib/ChunkPool.cs

@@ -15,7 +15,7 @@ namespace ChunkyImageLib
 
         public Chunk TransparentChunk { get; } = new Chunk();
 
-        public Chunk BorrowChunk()
+        public Chunk BorrowChunk(object borrowee)
         {
             Chunk chunk;
             if (freeChunks.Count > 0)
@@ -35,7 +35,11 @@ namespace ChunkyImageLib
         public void ReturnChunk(Chunk chunk)
         {
             if (!usedChunks.Contains(chunk))
-                throw new Exception("This chunk wasn't borrowed");
+            {
+                if (freeChunks.Contains(chunk))
+                    throw new Exception("This chunk has already been returned");
+                throw new Exception("This chunk wasn't borrowed or was already returned and disposed");
+            }
             usedChunks.Remove(chunk);
             freeChunks.Add(chunk);
 
@@ -44,7 +48,7 @@ namespace ChunkyImageLib
 
         private void MaybeDisposeChunks()
         {
-            for (int i = freeChunks.Count - 1; i >= 100; i++)
+            for (int i = freeChunks.Count - 1; i > 200; i--)
             {
                 freeChunks[i].Dispose();
                 freeChunks.RemoveAt(i);

+ 171 - 76
src/ChunkyImageLib/ChunkyImage.cs

@@ -6,26 +6,38 @@ using System.Runtime.CompilerServices;
 [assembly: InternalsVisibleTo("ChunkyImageLibTest")]
 namespace ChunkyImageLib
 {
-    public class ChunkyImage : IReadOnlyChunkyImage
+    public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     {
-        private Queue<(IOperation, HashSet<Vector2i>)> queuedOperations = new();
+        private struct LatestChunkData
+        {
+            public int QueueProgress { get; set; } = 0;
+            public bool IsDeleted { get; set; } = false;
+        }
+        private bool disposed = false;
 
-        private Dictionary<Vector2i, Chunk> commitedChunks = new();
-        private Dictionary<Vector2i, Chunk> latestChunks = new();
+        public static int ChunkSize => ChunkPool.ChunkSize;
+        private static SKPaint ClippingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.DstIn };
         private Chunk tempChunk;
 
-        private static SKPaint ClippingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.DstIn };
+        public Vector2i CommitedSize { get; private set; }
+        public Vector2i LatestSize { get; private set; }
 
-        public static int ChunkSize => ChunkPool.ChunkSize;
+        private List<(IOperation operation, HashSet<Vector2i> affectedChunks)> queuedOperations = new();
 
-        public ChunkyImage()
+        private Dictionary<Vector2i, Chunk> commitedChunks = new();
+        private Dictionary<Vector2i, Chunk> latestChunks = new();
+        private Dictionary<Vector2i, LatestChunkData> latestChunksData = new();
+
+        public ChunkyImage(Vector2i size)
         {
-            tempChunk = ChunkPool.Instance.BorrowChunk();
+            CommitedSize = size;
+            LatestSize = size;
+            tempChunk = ChunkPool.Instance.BorrowChunk(this);
         }
 
         public ChunkyImage CloneFromLatest()
         {
-            ChunkyImage output = new();
+            ChunkyImage output = new(LatestSize);
             var chunks = FindAllChunks();
             foreach (var chunk in chunks)
             {
@@ -37,14 +49,20 @@ namespace ChunkyImageLib
             return output;
         }
 
+        /// <summary>
+        /// Returns the latest version of the chunk, with uncommited changes applied if they exist
+        /// </summary>
         public Chunk? GetLatestChunk(Vector2i pos)
         {
             if (queuedOperations.Count == 0)
                 return MaybeGetChunk(pos, commitedChunks);
-            ProcessQueue(pos);
+            ProcessQueueForChunk(pos);
             return MaybeGetChunk(pos, latestChunks) ?? MaybeGetChunk(pos, commitedChunks);
         }
 
+        /// <summary>
+        /// Returns the commited version of the chunk ignoring any uncommited changes
+        /// </summary>
         internal Chunk? GetCommitedChunk(Vector2i pos)
         {
             return MaybeGetChunk(pos, commitedChunks);
@@ -55,25 +73,43 @@ namespace ChunkyImageLib
         public void DrawRectangle(ShapeData rect)
         {
             RectangleOperation operation = new(rect);
-            queuedOperations.Enqueue((operation, operation.FindAffectedChunks(this)));
+            EnqueueOperation(operation);
         }
 
         internal void DrawImage(Vector2i pos, Surface image)
         {
             ImageOperation operation = new(pos, image);
-            queuedOperations.Enqueue((operation, operation.FindAffectedChunks(this)));
+            EnqueueOperation(operation);
         }
 
         public void Clear()
         {
             ClearOperation operation = new();
-            queuedOperations.Enqueue((operation, operation.FindAffectedChunks(this)));
+            EnqueueOperation(operation);
         }
 
         public void ApplyRasterClip(ChunkyImage clippingMask)
         {
             RasterClipOperation operation = new(clippingMask);
-            queuedOperations.Enqueue((operation, new()));
+            EnqueueOperation(operation, new());
+        }
+
+        public void Resize(Vector2i newSize)
+        {
+            ResizeOperation operation = new(newSize);
+            LatestSize = newSize;
+            EnqueueOperation(operation, FindAllChunksOutsideBounds(newSize));
+        }
+
+        private void EnqueueOperation(IDrawOperation operation)
+        {
+            var chunks = operation.FindAffectedChunks(this);
+            chunks.RemoveWhere(pos => IsOutsideBounds(pos, LatestSize));
+            EnqueueOperation(operation, chunks);
+        }
+        private void EnqueueOperation(IOperation operation, HashSet<Vector2i> chunks)
+        {
+            queuedOperations.Add((operation, chunks));
         }
 
         public void CancelChanges()
@@ -85,7 +121,9 @@ namespace ChunkyImageLib
             {
                 ChunkPool.Instance.ReturnChunk(chunk);
             }
+            LatestSize = CommitedSize;
             latestChunks.Clear();
+            latestChunksData.Clear();
         }
 
         public void CommitChanges()
@@ -93,14 +131,20 @@ namespace ChunkyImageLib
             var affectedChunks = FindAffectedChunks();
             foreach (var chunk in affectedChunks)
             {
-                ProcessQueue(chunk);
+                ProcessQueueForChunk(chunk);
             }
-            foreach (var (operation, operChunks) in queuedOperations)
+            foreach (var (operation, _) in queuedOperations)
+            {
                 operation.Dispose();
-            queuedOperations.Clear();
+            }
             CommitLatestChunks();
+            CommitedSize = LatestSize;
+            queuedOperations.Clear();
         }
 
+        /// <summary>
+        /// Returns all chunks that have something in them, including latest (uncommited) ones
+        /// </summary>
         public HashSet<Vector2i> FindAllChunks()
         {
             var allChunks = commitedChunks.Select(chunk => chunk.Key).ToHashSet();
@@ -112,6 +156,9 @@ namespace ChunkyImageLib
             return allChunks;
         }
 
+        /// <summary>
+        /// Returns chunks affected by operations that haven't been commited yet
+        /// </summary>
         public HashSet<Vector2i> FindAffectedChunks()
         {
             var chunks = latestChunks.Select(chunk => chunk.Key).ToHashSet();
@@ -126,70 +173,98 @@ namespace ChunkyImageLib
         {
             foreach (var (pos, chunk) in latestChunks)
             {
+                LatestChunkData data = latestChunksData[pos];
+                if (data.QueueProgress != queuedOperations.Count)
+                    throw new Exception("Trying to commit chunk that wasn't fully processed");
+
                 if (commitedChunks.ContainsKey(pos))
                 {
                     var oldChunk = commitedChunks[pos];
                     commitedChunks.Remove(pos);
                     ChunkPool.Instance.ReturnChunk(oldChunk);
                 }
-                commitedChunks.Add(pos, chunk);
+                if (!data.IsDeleted)
+                    commitedChunks.Add(pos, chunk);
+                else
+                    ChunkPool.Instance.ReturnChunk(chunk);
             }
+
             latestChunks.Clear();
+            latestChunksData.Clear();
         }
 
-        private void ProcessQueue(Vector2i chunkPos)
+        private void ProcessQueueForChunk(Vector2i chunkPos)
         {
-            Chunk? targetChunk = null;
-            List<RasterClipOperation> clips = new();
-            foreach (var (operation, operChunks) in queuedOperations)
+            Chunk targetChunk = GetOrCreateLatestChunk(chunkPos);
+            if (!latestChunksData.TryGetValue(chunkPos, out LatestChunkData chunkData))
+                chunkData = new() { QueueProgress = 0, IsDeleted = !commitedChunks.ContainsKey(chunkPos) };
+
+            if (chunkData.QueueProgress == queuedOperations.Count)
+                return;
+
+            List<Chunk> activeClips = new();
+            bool isFullyMaskedOut = false;
+            for (int i = 0; i < queuedOperations.Count; i++)
             {
-                if (operation is IChunkOperation chunkOperation)
+                var (operation, operChunks) = queuedOperations[i];
+                if (operation is RasterClipOperation clipOperation)
                 {
-                    if (!operChunks.Contains(chunkPos))
-                        continue;
-                    operChunks.Remove(chunkPos);
-
-                    if (targetChunk == null)
-                        targetChunk = GetOrCreateLatestChunk(chunkPos);
-
-                    if (clips.Count == 0)
-                    {
-                        chunkOperation.DrawOnChunk(targetChunk, chunkPos);
-                        continue;
-                    }
-
-                    List<Chunk> masks = new();
-                    foreach (var clip in clips)
-                    {
-                        var chunk = clip.ClippingMask.GetCommitedChunk(chunkPos);
-                        if (chunk == null)
-                        {
-                            //the chunked is fully masked out, no point to draw any further operations
-                            return;
-                        }
-                        masks.Add(chunk);
-                    }
-
-                    if (masks.Count == 0)
-                    {
-                        chunkOperation.DrawOnChunk(targetChunk, chunkPos);
-                    }
+                    var chunk = clipOperation.ClippingMask.GetCommitedChunk(chunkPos);
+                    if (chunk != null)
+                        activeClips.Add(chunk);
                     else
-                    {
-                        tempChunk.Surface.SkiaSurface.Canvas.Clear();
-                        chunkOperation.DrawOnChunk(tempChunk, chunkPos);
-                        foreach (var mask in masks)
-                        {
-                            tempChunk.Surface.SkiaSurface.Canvas.DrawSurface(mask.Surface.SkiaSurface, 0, 0, ClippingPaint);
-                        }
-                        tempChunk.DrawOnSurface(targetChunk.Surface.SkiaSurface, new(0, 0));
-                    }
+                        isFullyMaskedOut = true;
+                }
+
+                if (!operChunks.Contains(chunkPos))
+                    continue;
+                if (chunkData.QueueProgress <= i)
+                    chunkData.IsDeleted = ApplyOperationToChunk(operation, activeClips, isFullyMaskedOut, targetChunk, chunkPos, chunkData);
+            }
+
+            chunkData.QueueProgress = queuedOperations.Count;
+            latestChunksData[chunkPos] = chunkData;
+        }
+
+        private bool ApplyOperationToChunk(
+            IOperation operation,
+            List<Chunk> activeClips,
+            bool isFullyMaskedOut,
+            Chunk targetChunk,
+            Vector2i chunkPos,
+            LatestChunkData chunkData)
+        {
+            if (operation is ClearOperation)
+                return true;
+
+            if (operation is IDrawOperation chunkOperation)
+            {
+                if (isFullyMaskedOut)
+                    return chunkData.IsDeleted;
+
+                if (chunkData.IsDeleted)
+                    targetChunk.Surface.SkiaSurface.Canvas.Clear();
+                if (activeClips.Count == 0)
+                {
+                    chunkOperation.DrawOnChunk(targetChunk, chunkPos);
+                    return false;
                 }
-                else if (operation is RasterClipOperation clipOperation)
+
+                tempChunk.Surface.SkiaSurface.Canvas.Clear();
+                chunkOperation.DrawOnChunk(tempChunk, chunkPos);
+                foreach (var mask in activeClips)
                 {
-                    clips.Add(clipOperation);
+                    tempChunk.Surface.SkiaSurface.Canvas.DrawSurface(mask.Surface.SkiaSurface, 0, 0, ClippingPaint);
                 }
+                tempChunk.DrawOnSurface(targetChunk.Surface.SkiaSurface, new(0, 0));
+                return false;
+            }
+
+            if (operation is ResizeOperation resizeOperation)
+            {
+                return IsOutsideBounds(chunkPos, resizeOperation.Size);
             }
+            return chunkData.IsDeleted;
         }
 
         public bool CheckIfCommitedIsEmpty()
@@ -198,13 +273,33 @@ namespace ChunkyImageLib
             return commitedChunks.Count == 0;
         }
 
+        private HashSet<Vector2i> FindAllChunksOutsideBounds(Vector2i size)
+        {
+            var chunks = FindAllChunks();
+            chunks.RemoveWhere(pos => !IsOutsideBounds(pos, size));
+            return chunks;
+        }
+
+        private static bool IsOutsideBounds(Vector2i chunkPos, Vector2i imageSize)
+        {
+            return chunkPos.X < 0 || chunkPos.Y < 0 || chunkPos.X * ChunkSize >= imageSize.X || chunkPos.Y * ChunkSize >= imageSize.Y;
+        }
+
         private void FindAndDeleteEmptyCommitedChunks()
         {
+            if (queuedOperations.Count != 0)
+                throw new Exception("This method cannot be used while any operations are queued");
+            HashSet<Vector2i> toRemove = new();
             foreach (var (pos, chunk) in commitedChunks)
             {
                 if (IsChunkEmpty(chunk))
-                    commitedChunks.Remove(pos);
+                {
+                    toRemove.Add(pos);
+                    ChunkPool.Instance.ReturnChunk(chunk);
+                }
             }
+            foreach (var pos in toRemove)
+                commitedChunks.Remove(pos);
         }
 
         private unsafe bool IsChunkEmpty(Chunk chunk)
@@ -220,24 +315,13 @@ namespace ChunkyImageLib
             return true;
         }
 
-        private Chunk GetOrCreateCommitedChunk(Vector2i chunkPos)
-        {
-            Chunk? targetChunk = MaybeGetChunk(chunkPos, commitedChunks);
-            if (targetChunk != null)
-                return targetChunk;
-            var newChunk = ChunkPool.Instance.BorrowChunk();
-            newChunk.Surface.SkiaSurface.Canvas.Clear();
-            commitedChunks[chunkPos] = newChunk;
-            return newChunk;
-        }
-
         private Chunk GetOrCreateLatestChunk(Vector2i chunkPos)
         {
             Chunk? targetChunk;
             targetChunk = MaybeGetChunk(chunkPos, latestChunks);
             if (targetChunk == null)
             {
-                targetChunk = ChunkPool.Instance.BorrowChunk();
+                targetChunk = ChunkPool.Instance.BorrowChunk(this);
                 var maybeCommitedChunk = MaybeGetChunk(chunkPos, commitedChunks);
 
                 if (maybeCommitedChunk != null)
@@ -249,5 +333,16 @@ namespace ChunkyImageLib
             }
             return targetChunk;
         }
+
+        public void Dispose()
+        {
+            if (disposed)
+                return;
+            CancelChanges();
+            ChunkPool.Instance.ReturnChunk(tempChunk);
+            foreach (var chunk in commitedChunks)
+                ChunkPool.Instance.ReturnChunk(chunk.Value);
+            disposed = true;
+        }
     }
 }

+ 1 - 1
src/ChunkyImageLib/CommitedChunkStorage.cs

@@ -16,7 +16,7 @@ namespace ChunkyImageLib
                     savedChunks.Add((chunkPos, null));
                     continue;
                 }
-                Chunk copy = ChunkPool.Instance.BorrowChunk();
+                Chunk copy = ChunkPool.Instance.BorrowChunk(this);
                 chunk.Surface.CopyTo(copy.Surface);
                 savedChunks.Add((chunkPos, copy));
             }

+ 1 - 1
src/ChunkyImageLib/Operations/ClearOperation.cs

@@ -2,7 +2,7 @@
 
 namespace ChunkyImageLib.Operations
 {
-    internal record class ClearOperation : IChunkOperation
+    internal record class ClearOperation : IDrawOperation
     {
         public void DrawOnChunk(Chunk chunk, Vector2i chunkPos)
         {

+ 1 - 1
src/ChunkyImageLib/Operations/IChunkOperation.cs → src/ChunkyImageLib/Operations/IDrawOperation.cs

@@ -2,7 +2,7 @@
 
 namespace ChunkyImageLib.Operations
 {
-    internal interface IChunkOperation : IOperation
+    internal interface IDrawOperation : IOperation
     {
         void DrawOnChunk(Chunk chunk, Vector2i chunkPos);
         HashSet<Vector2i> FindAffectedChunks(IReadOnlyChunkyImage image);

+ 1 - 1
src/ChunkyImageLib/Operations/ImageOperation.cs

@@ -3,7 +3,7 @@ using SkiaSharp;
 
 namespace ChunkyImageLib.Operations
 {
-    internal record class ImageOperation : IChunkOperation
+    internal record class ImageOperation : IDrawOperation
     {
         private Vector2i pos;
         private Surface toPaint;

+ 1 - 1
src/ChunkyImageLib/Operations/RectangleOperation.cs

@@ -3,7 +3,7 @@ using SkiaSharp;
 
 namespace ChunkyImageLib.Operations
 {
-    internal record class RectangleOperation : IChunkOperation
+    internal record class RectangleOperation : IDrawOperation
     {
         public RectangleOperation(ShapeData rect)
         {

+ 14 - 0
src/ChunkyImageLib/Operations/ResizeOperation.cs

@@ -0,0 +1,14 @@
+using ChunkyImageLib.DataHolders;
+
+namespace ChunkyImageLib.Operations
+{
+    internal record class ResizeOperation : IOperation
+    {
+        public Vector2i Size { get; }
+        public ResizeOperation(Vector2i size)
+        {
+            Size = size;
+        }
+        public void Dispose() { }
+    }
+}

+ 1 - 1
src/ChunkyImageLib/Surface.cs

@@ -17,7 +17,7 @@ namespace ChunkyImageLib
         {
             if (width < 1 || height < 1)
                 throw new ArgumentException("Width and height must be >1");
-            if (width > 10000 || height > 1000)
+            if (width > 10000 || height > 10000)
                 throw new ArgumentException("Width and height must be <=10000");
 
             Width = width;

+ 6 - 2
src/PixiEditorPrototype/Models/ActionAccumulator.cs

@@ -2,6 +2,7 @@
 using ChangeableDocument.Actions;
 using ChangeableDocument.ChangeInfos;
 using PixiEditorPrototype.ViewModels;
+using SkiaSharp;
 using StructureRenderer;
 using StructureRenderer.RenderInfos;
 using System.Collections.Generic;
@@ -53,11 +54,14 @@ namespace PixiEditorPrototype.Models
                 document.FinalBitmap.Lock();
                 var renderResult = await renderer.ProcessChanges(result!, document.FinalBitmapSurface, new(document.FinalBitmap.PixelWidth, document.FinalBitmap.PixelHeight));
 
+                SKRectI finalRect = SKRectI.Create(0, 0, document.FinalBitmap.PixelWidth, document.FinalBitmap.PixelHeight);
                 foreach (IRenderInfo info in renderResult)
                 {
-                    if (info is DirtyRect_RenderInfo dirtyRect)
+                    if (info is DirtyRect_RenderInfo dirtyRectInfo)
                     {
-                        document.FinalBitmap.AddDirtyRect(new(dirtyRect.Pos.X, dirtyRect.Pos.Y, dirtyRect.Size.X, dirtyRect.Size.Y));
+                        SKRectI dirtyRect = SKRectI.Create(dirtyRectInfo.Pos, dirtyRectInfo.Size);
+                        dirtyRect.Intersect(finalRect);
+                        document.FinalBitmap.AddDirtyRect(new(dirtyRect.Left, dirtyRect.Top, dirtyRect.Width, dirtyRect.Height));
                     }
                 }
                 document.FinalBitmap.Unlock();

+ 20 - 3
src/PixiEditorPrototype/Models/DocumentUpdater.cs

@@ -1,7 +1,10 @@
 using ChangeableDocument.Changeables.Interfaces;
 using ChangeableDocument.ChangeInfos;
 using PixiEditorPrototype.ViewModels;
+using SkiaSharp;
 using System;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
 
 namespace PixiEditorPrototype.Models
 {
@@ -35,13 +38,27 @@ namespace PixiEditorPrototype.Models
                 case MoveStructureMember_ChangeInfo info:
                     ProcessMoveStructureMember(info);
                     break;
+                case Size_ChangeInfo info:
+                    ProcessSize(info);
+                    break;
             }
         }
 
+        private void ProcessSize(Size_ChangeInfo info)
+        {
+            doc.FinalBitmapSurface.Dispose();
+
+            doc.FinalBitmap = new WriteableBitmap(doc.Tracker.Document.Size.X, doc.Tracker.Document.Size.Y, 96, 96, PixelFormats.Pbgra32, null);
+            doc.FinalBitmapSurface = SKSurface.Create(
+                new SKImageInfo(doc.FinalBitmap.PixelWidth, doc.FinalBitmap.PixelHeight, SKColorType.Bgra8888, SKAlphaType.Premul),
+                doc.FinalBitmap.BackBuffer,
+                doc.FinalBitmap.BackBufferStride);
+        }
+
         private void ProcessCreateStructureMember(CreateStructureMember_ChangeInfo info)
         {
             var (member, parentFolder) = doc.Tracker.Document.FindChildAndParentOrThrow(info.GuidValue);
-            var parentFolderVM = (FolderViewModel)doc.StructureHelper.FindOrThrow(parentFolder.ReadOnlyGuidValue);
+            var parentFolderVM = (FolderViewModel)doc.StructureHelper.FindOrThrow(parentFolder.GuidValue);
 
             int index = parentFolder.ReadOnlyChildren.IndexOf(member);
 
@@ -58,7 +75,7 @@ namespace PixiEditorPrototype.Models
             {
                 foreach (IReadOnlyStructureMember child in folder2.ReadOnlyChildren)
                 {
-                    ProcessCreateStructureMember(new CreateStructureMember_ChangeInfo() { GuidValue = child.ReadOnlyGuidValue });
+                    ProcessCreateStructureMember(new CreateStructureMember_ChangeInfo() { GuidValue = child.GuidValue });
                 }
             }
         }
@@ -88,7 +105,7 @@ namespace PixiEditorPrototype.Models
             var (member, targetFolder) = doc.Tracker.Document.FindChildAndParentOrThrow(info.GuidValue);
 
             int index = targetFolder.ReadOnlyChildren.IndexOf(member);
-            var targetFolderVM = (FolderViewModel)doc.StructureHelper.FindOrThrow(targetFolder.ReadOnlyGuidValue);
+            var targetFolderVM = (FolderViewModel)doc.StructureHelper.FindOrThrow(targetFolder.GuidValue);
 
             curFolderVM.Children.Remove(memberVM);
             targetFolderVM.Children.Insert(index, memberVM);

+ 11 - 2
src/PixiEditorPrototype/ViewModels/DocumentViewModel.cs

@@ -1,4 +1,5 @@
 using ChangeableDocument;
+using ChangeableDocument.Actions.Document;
 using ChangeableDocument.Actions.Drawing.Rectangle;
 using ChangeableDocument.Actions.Drawing.Selection;
 using ChangeableDocument.Actions.Structure;
@@ -46,6 +47,7 @@ namespace PixiEditorPrototype.ViewModels
         public RelayCommand? DeleteStructureMemberCommand { get; }
         public RelayCommand? ChangeSelectedItemCommand { get; }
         public RelayCommand? ChangeActiveToolCommand { get; }
+        public RelayCommand? ResizeCanvasCommand { get; }
 
         public RelayCommand? MouseDownCommand { get; }
         public RelayCommand? MouseMoveCommand { get; }
@@ -64,6 +66,8 @@ namespace PixiEditorPrototype.ViewModels
         public SKSurface FinalBitmapSurface { get; set; }
 
         public Color SelectedColor { get; set; } = Colors.Black;
+        public int ResizeWidth { get; set; }
+        public int ResizeHeight { get; set; }
 
         public DocumentViewModel()
         {
@@ -81,6 +85,7 @@ namespace PixiEditorPrototype.ViewModels
             DeleteStructureMemberCommand = new RelayCommand(DeleteStructureMember);
             ChangeSelectedItemCommand = new RelayCommand(ChangeSelectedItem);
             ChangeActiveToolCommand = new RelayCommand(ChangeActiveTool);
+            ResizeCanvasCommand = new RelayCommand(ResizeCanvas);
 
             MouseDownCommand = new RelayCommand(MouseDown);
             MouseMoveCommand = new RelayCommand(MouseMove);
@@ -184,14 +189,18 @@ namespace PixiEditorPrototype.ViewModels
 
         public void Undo(object? param)
         {
-            ActionAccumulator.AddAction(new UndoAction());
+            ActionAccumulator.AddAction(new Undo_Action());
         }
 
         public void Redo(object? param)
         {
-            ActionAccumulator.AddAction(new RedoAction());
+            ActionAccumulator.AddAction(new Redo_Action());
         }
 
+        public void ResizeCanvas(object? param)
+        {
+            ActionAccumulator.AddAction(new ResizeCanvas_Action(new(ResizeWidth, ResizeHeight)));
+        }
         private void ChangeSelectedItem(object? param)
         {
             SelectedStructureMember = (StructureMemberViewModel?)((RoutedPropertyChangedEventArgs<object>?)param)?.NewValue;

+ 6 - 6
src/PixiEditorPrototype/ViewModels/StructureMemberViewModel.cs

@@ -13,24 +13,24 @@ namespace PixiEditorPrototype.ViewModels
 
         public string Name
         {
-            get => member.ReadOnlyName;
-            set => Document.ActionAccumulator.AddAction(new SetStructureMemberName_Action(value, member.ReadOnlyGuidValue));
+            get => member.Name;
+            set => Document.ActionAccumulator.AddAction(new SetStructureMemberName_Action(value, member.GuidValue));
         }
 
         public bool IsVisible
         {
-            get => member.ReadOnlyIsVisible;
-            set => Document.ActionAccumulator.AddAction(new SetStructureMemberVisibility_Action(value, member.ReadOnlyGuidValue));
+            get => member.IsVisible;
+            set => Document.ActionAccumulator.AddAction(new SetStructureMemberVisibility_Action(value, member.GuidValue));
         }
 
         public float Opacity
         {
-            get => member.ReadOnlyOpacity;
+            get => member.Opacity;
         }
 
         public Guid GuidValue
         {
-            get => member.ReadOnlyGuidValue;
+            get => member.GuidValue;
         }
 
         public RelayCommand MoveUpCommand { get; }

+ 12 - 5
src/PixiEditorPrototype/Views/DocumentView.xaml

@@ -69,11 +69,18 @@
             </DockPanel>
         </Border>
         <Border BorderThickness="1" Background="White" BorderBrush="Black" DockPanel.Dock="Top" Margin="5">
-            <StackPanel Orientation="Horizontal" Background="White">
-                <Button Width="50" Margin="5" Command="{Binding UndoCommand}">Undo</Button>
-                <Button Width="50" Margin="5" Command="{Binding RedoCommand}">Redo</Button>
-                <Button Width="100" Margin="5" Command="{Binding ClearSelectionCommand}">Clear selection</Button>
-            </StackPanel>
+            <DockPanel>
+                <StackPanel Orientation="Horizontal" Background="White">
+                    <Button Width="50" Margin="5" Command="{Binding UndoCommand}">Undo</Button>
+                    <Button Width="50" Margin="5" Command="{Binding RedoCommand}">Redo</Button>
+                    <Button Width="100" Margin="5" Command="{Binding ClearSelectionCommand}">Clear selection</Button>
+                </StackPanel>
+                <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right">
+                    <TextBox Width="30" Margin="5" Text="{Binding ResizeWidth}"/>
+                    <TextBox Width="30" Margin="5" Text="{Binding ResizeHeight}"/>
+                    <Button Width="50" Margin="5" Command="{Binding ResizeCanvasCommand}">Resize</Button>
+                </StackPanel>
+            </DockPanel>
         </Border>
         <Border BorderThickness="1" Background="White" BorderBrush="Black" DockPanel.Dock="Left" Margin="5">
             <StackPanel Orientation="Vertical" Background="White">

+ 4 - 3
src/StructureRenderer/Renderer.cs

@@ -54,6 +54,7 @@ namespace StructureRenderer
                     case CreateStructureMember_ChangeInfo:
                     case DeleteStructureMember_ChangeInfo:
                     case MoveStructureMember_ChangeInfo:
+                    case Size_ChangeInfo:
                         return null;
                     case StructureMemberOpacity_ChangeInfo opacityChangeInfo:
                         var memberWithOpacity = tracker.Document.FindMemberOrThrow(opacityChangeInfo.GuidValue);
@@ -179,7 +180,7 @@ namespace StructureRenderer
             surface?.SkiaSurface.Canvas.Clear();
             foreach (var child in folder.ReadOnlyChildren)
             {
-                if (!child.ReadOnlyIsVisible)
+                if (!child.IsVisible)
                     continue;
                 if (child is IReadOnlyLayer layer)
                 {
@@ -188,7 +189,7 @@ namespace StructureRenderer
                         continue;
                     if (surface == null)
                         throw new Exception("Not enough surfaces have been allocated to draw the entire layer tree");
-                    PaintToDrawChunksWith.Color = new SKColor(255, 255, 255, (byte)Math.Round(child.ReadOnlyOpacity * 255));
+                    PaintToDrawChunksWith.Color = new SKColor(255, 255, 255, (byte)Math.Round(child.Opacity * 255));
                     chunk.DrawOnSurface(surface.SkiaSurface, new(0, 0), PaintToDrawChunksWith);
                 }
                 else if (child is IReadOnlyFolder innerFolder)
@@ -198,7 +199,7 @@ namespace StructureRenderer
                         continue;
                     if (surface == null)
                         throw new Exception("Not enough surfaces have been allocated to draw the entire layer tree");
-                    PaintToDrawChunksWith.Color = new SKColor(255, 255, 255, (byte)Math.Round(child.ReadOnlyOpacity * 255));
+                    PaintToDrawChunksWith.Color = new SKColor(255, 255, 255, (byte)Math.Round(child.Opacity * 255));
                     surface.SkiaSurface.Canvas.DrawSurface(renderedSurface.SkiaSurface, 0, 0, PaintToDrawChunksWith);
                 }
             }