Browse Source

Added clip canvas

flabbet 2 years ago
parent
commit
f9f48317c3

+ 24 - 0
src/ChunkyImageLib/Chunk.cs

@@ -60,6 +60,30 @@ public class Chunk : IDisposable
     {
         surface.Canvas.DrawSurface(Surface.DrawingSurface, pos.X, pos.Y, paint);
     }
+    
+    public unsafe RectI? FindPreciseBounds()
+    {
+        RectI? bounds = null;
+        if(returned) return bounds;
+        
+        ulong* ptr = (ulong*)Surface.PixelBuffer;
+        for (int y = 0; y < Surface.Size.Y; y++)
+        {
+            for (int x = 0; x < Surface.Size.X; x++)
+            {
+                int i = y * Surface.Size.X + x;
+                // ptr[i] actually contains 4 16-bit floats. We only care about the first one which is alpha.
+                // An empty pixel can have alpha of 0 or -0 (not sure if -0 actually ever comes up). 0 in hex is 0x0, -0 in hex is 0x8000
+                if ((ptr[i] & 0x1111_0000_0000_0000) != 0 && (ptr[i] & 0x1111_0000_0000_0000) != 0x8000_0000_0000_0000)
+                {
+                    bounds ??= new RectI(x, y, 1, 1);
+                    bounds = bounds.Value.Union(new RectI(x, y, 1, 1));
+                }
+            }
+        }
+        
+        return bounds;
+    }
 
     /// <summary>
     /// Returns the chunk back to the pool

+ 25 - 0
src/ChunkyImageLib/ChunkyImage.cs

@@ -137,6 +137,31 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             return rect;
         }
     }
+    
+    public RectI? FindPreciseBounds()
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            RectI? chunkBounds = FindLatestBounds();
+            if(!chunkBounds.HasValue) return null;
+
+            RectI? preciseBounds = null;
+
+            foreach (var chunk in committedChunks[ChunkResolution.Full])
+            {
+                if(!chunkBounds.Value.Intersects(new RectI(chunk.Key, new VecI(FullChunkSize)))) continue;
+                
+                RectI? chunkPreciseBounds = chunk.Value.FindPreciseBounds();
+                if(!chunkPreciseBounds.HasValue) continue;
+                
+                preciseBounds ??= chunkPreciseBounds.Value;
+                preciseBounds = preciseBounds.Value.Union(chunkPreciseBounds.Value);
+            }
+
+            return preciseBounds;
+        }
+    }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     public ChunkyImage CloneFromCommitted()

+ 7 - 8
src/ChunkyImageLib/Operations/ChunkyImageOperation.cs

@@ -5,16 +5,16 @@ namespace ChunkyImageLib.Operations;
 internal class ChunkyImageOperation : IDrawOperation
 {
     private readonly ChunkyImage imageToDraw;
-    private readonly VecI pos;
+    private readonly VecI targetPos;
     private readonly bool mirrorHorizontal;
     private readonly bool mirrorVertical;
 
     public bool IgnoreEmptyChunks => false;
 
-    public ChunkyImageOperation(ChunkyImage imageToDraw, VecI pos, bool mirrorHorizontal, bool mirrorVertical)
+    public ChunkyImageOperation(ChunkyImage imageToDraw, VecI targetPos, bool mirrorHorizontal, bool mirrorVertical)
     {
         this.imageToDraw = imageToDraw;
-        this.pos = pos;
+        this.targetPos = targetPos;
         this.mirrorHorizontal = mirrorHorizontal;
         this.mirrorVertical = mirrorVertical;
     }
@@ -22,7 +22,6 @@ internal class ChunkyImageOperation : IDrawOperation
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     {
         chunk.Surface.DrawingSurface.Canvas.Save();
-
         {
             VecI pixelPos = chunkPos * ChunkyImage.FullChunkSize;
             VecI topLeftImageCorner = GetTopLeft();
@@ -36,7 +35,7 @@ internal class ChunkyImageOperation : IDrawOperation
         chunkPixelCenter.X += ChunkyImage.FullChunkSize / 2;
         chunkPixelCenter.Y += ChunkyImage.FullChunkSize / 2;
 
-        VecI chunkCenterOnImage = chunkPixelCenter - pos;
+        VecI chunkCenterOnImage = chunkPixelCenter - targetPos;
         VecI chunkSize = chunk.PixelSize;
         if (mirrorHorizontal)
         {
@@ -64,7 +63,7 @@ internal class ChunkyImageOperation : IDrawOperation
             chunk.Surface.DrawingSurface,
             (VecI)((topLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * chunk.Resolution.Multiplier()));
 
-        VecI gridShift = pos % ChunkyImage.FullChunkSize;
+        VecI gridShift = targetPos % ChunkyImage.FullChunkSize;
         if (gridShift.X != 0)
         {
             imageToDraw.DrawCommittedChunkOn(
@@ -100,7 +99,7 @@ internal class ChunkyImageOperation : IDrawOperation
 
     private VecI GetTopLeft()
     {
-        VecI topLeft = pos;
+        VecI topLeft = targetPos;
         if (mirrorHorizontal)
             topLeft.X -= imageToDraw.CommittedSize.X;
         if (mirrorVertical)
@@ -110,7 +109,7 @@ internal class ChunkyImageOperation : IDrawOperation
 
     public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
     {
-        var newPos = pos;
+        var newPos = targetPos;
         if (verAxisX is not null)
             newPos = newPos.ReflectX((int)verAxisX);
         if (horAxisY is not null)

+ 3 - 0
src/PixiEditor.ChangeableDocument/ChangeInfos/Root/Clip_ChangeInfo.cs

@@ -0,0 +1,3 @@
+namespace PixiEditor.ChangeableDocument.ChangeInfos.Root;
+
+public record Clip_ChangeInfo() : IChangeInfo;

+ 55 - 13
src/PixiEditor.ChangeableDocument/Changes/Root/ClipCanvas_Change.cs

@@ -1,24 +1,66 @@
-namespace PixiEditor.ChangeableDocument.Changes.Root;
+using PixiEditor.ChangeableDocument.ChangeInfos.Root;
+using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.DrawingApi.Core.Numerics;
 
-internal class ClipCanvas_Change : UpdateableChange
+namespace PixiEditor.ChangeableDocument.Changes.Root;
+
+internal class ClipCanvas_Change : ResizeBasedChangeBase
 {
-    public override bool InitializeAndValidate(Document target)
+
+    [GenerateMakeChangeAction]
+    public ClipCanvas_Change()
     {
-        throw new NotImplementedException();
+        
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
-        throw new NotImplementedException();
-    }
+        RectI? bounds = null;
+        target.ForEveryMember((member) =>
+        {
+            if (member is Layer layer)
+            {
+                var layerBounds = layer.LayerImage.FindPreciseBounds();
+                if (layerBounds.HasValue)
+                {
+                    bounds ??= layerBounds.Value;
+                    bounds = bounds.Value.Union(layerBounds.Value);
+                }
+            }
+        });
 
-    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
-    {
-        throw new NotImplementedException();
-    }
+        if (!bounds.HasValue)
+        {
+            ignoreInUndo = true;
+            return new None();
+        }
+        
+        RectI newBounds = bounds.Value;
+        
+        target.Size = newBounds.Size;
+        target.VerticalSymmetryAxisX = Math.Clamp(_originalVerAxisX, 0, target.Size.X);
+        target.HorizontalSymmetryAxisY = Math.Clamp(_originalHorAxisY, 0, target.Size.Y);
+        
+        target.ForEveryMember((member) =>
+        {
+            if (member is Layer layer)
+            {
+                Resize(layer.LayerImage, layer.GuidValue, newBounds.Size, -newBounds.Pos, deletedChunks);
+            }
+            
+            if (member.Mask is null)
+                return;
+            
+            Resize(member.Mask, member.GuidValue, newBounds.Size, -newBounds.Pos, deletedMaskChunks);
+        });
+        
+        if (newBounds.IsZeroOrNegativeArea)
+        {
+            ignoreInUndo = true;
+            return new None();
+        }
 
-    public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
-    {
-        throw new NotImplementedException();
+        ignoreInUndo = false;
+        return new Size_ChangeInfo(newBounds.Size, target.VerticalSymmetryAxisX, target.HorizontalSymmetryAxisY);
     }
 }

+ 74 - 0
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeBasedChangeBase.cs

@@ -0,0 +1,74 @@
+using PixiEditor.ChangeableDocument.ChangeInfos.Root;
+using PixiEditor.DrawingApi.Core.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changes.Root;
+
+internal abstract class ResizeBasedChangeBase : Change
+{
+    protected VecI _originalSize;
+    protected int _originalHorAxisY;
+    protected int _originalVerAxisX;
+    protected Dictionary<Guid, CommittedChunkStorage> deletedChunks = new();
+    protected Dictionary<Guid, CommittedChunkStorage> deletedMaskChunks = new();
+    
+    public override bool InitializeAndValidate(Document target)
+    {
+        _originalSize = target.Size;
+        _originalHorAxisY = target.HorizontalSymmetryAxisY;
+        _originalVerAxisX = target.VerticalSymmetryAxisX;
+        return true;
+    }
+    
+    protected void Resize(ChunkyImage img, Guid memberGuid, VecI size, VecI offset, Dictionary<Guid, CommittedChunkStorage> deletedChunksDict)
+    {
+        img.EnqueueResize(size);
+        img.EnqueueClear();
+        img.EnqueueDrawChunkyImage(offset, img);
+
+        deletedChunksDict.Add(memberGuid, new CommittedChunkStorage(img, img.FindAffectedChunks()));
+        img.CommitChanges();
+    }
+    
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        target.Size = _originalSize;
+        target.ForEveryMember((member) =>
+        {
+            if (member is Layer layer)
+            {
+                layer.LayerImage.EnqueueResize(_originalSize);
+                deletedChunks[layer.GuidValue].ApplyChunksToImage(layer.LayerImage);
+                layer.LayerImage.CommitChanges();
+            }
+            
+            if (member.Mask is null)
+                return;
+            member.Mask.EnqueueResize(_originalSize);
+            deletedMaskChunks[member.GuidValue].ApplyChunksToImage(member.Mask);
+            member.Mask.CommitChanges();
+        });
+
+        target.HorizontalSymmetryAxisY = _originalHorAxisY;
+        target.VerticalSymmetryAxisX = _originalVerAxisX;
+
+        DisposeDeletedChunks();
+
+        return new Size_ChangeInfo(_originalSize, _originalVerAxisX, _originalHorAxisY);
+    }
+    
+    private void DisposeDeletedChunks()
+    {
+        foreach (var stored in deletedChunks)
+            stored.Value.Dispose();
+        deletedChunks = new();
+
+        foreach (var stored in deletedMaskChunks)
+            stored.Value.Dispose();
+        deletedMaskChunks = new();
+    }
+    
+    public override void Dispose()
+    {
+        DisposeDeletedChunks();
+    }
+}

+ 10 - 64
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeCanvas_Change.cs

@@ -4,13 +4,8 @@ using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changes.Root;
 
-internal class ResizeCanvas_Change : Change
+internal class ResizeCanvas_Change : ResizeBasedChangeBase
 {
-    private VecI originalSize;
-    private int originalHorAxisY;
-    private int originalVerAxisX;
-    private Dictionary<Guid, CommittedChunkStorage> deletedChunks = new();
-    private Dictionary<Guid, CommittedChunkStorage> deletedMaskChunks = new();
     private VecI newSize;
     private readonly ResizeAnchor anchor;
 
@@ -25,80 +20,31 @@ internal class ResizeCanvas_Change : Change
     {
         if (target.Size == newSize || newSize.X < 1 || newSize.Y < 1)
             return false;
-        originalSize = target.Size;
-        originalHorAxisY = target.HorizontalSymmetryAxisY;
-        originalVerAxisX = target.VerticalSymmetryAxisX;
-        return true;
+        
+        return base.InitializeAndValidate(target);
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
         target.Size = newSize;
-        target.VerticalSymmetryAxisX = Math.Clamp(originalVerAxisX, 0, target.Size.X);
-        target.HorizontalSymmetryAxisY = Math.Clamp(originalHorAxisY, 0, target.Size.Y);
+        target.VerticalSymmetryAxisX = Math.Clamp(_originalVerAxisX, 0, target.Size.X);
+        target.HorizontalSymmetryAxisY = Math.Clamp(_originalHorAxisY, 0, target.Size.Y);
+
+        VecI offset = anchor.FindOffsetFor(_originalSize, newSize);
 
         target.ForEveryMember((member) =>
         {
             if (member is Layer layer)
             {
-                layer.LayerImage.EnqueueResize(newSize);
-                layer.LayerImage.EnqueueClear();
-                layer.LayerImage.EnqueueDrawChunkyImage(anchor.FindOffsetFor(originalSize, newSize), layer.LayerImage);
-
-                deletedChunks.Add(layer.GuidValue, new CommittedChunkStorage(layer.LayerImage, layer.LayerImage.FindAffectedChunks()));
-                layer.LayerImage.CommitChanges();
+                Resize(layer.LayerImage, layer.GuidValue, newSize, offset, deletedChunks);
             }
             if (member.Mask is null)
                 return;
 
-            member.Mask.EnqueueResize(newSize);
-            member.Mask.EnqueueClear();
-            member.Mask.EnqueueDrawChunkyImage(anchor.FindOffsetFor(originalSize, newSize), member.Mask);
-            deletedMaskChunks.Add(member.GuidValue, new CommittedChunkStorage(member.Mask, member.Mask.FindAffectedChunks()));
-            member.Mask.CommitChanges();
+            Resize(member.Mask, member.GuidValue, newSize, offset, deletedMaskChunks);
         });
+        
         ignoreInUndo = false;
         return new Size_ChangeInfo(newSize, target.VerticalSymmetryAxisX, target.HorizontalSymmetryAxisY);
     }
-
-    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
-    {
-        target.Size = originalSize;
-        target.ForEveryMember((member) =>
-        {
-            if (member is Layer layer)
-            {
-                layer.LayerImage.EnqueueResize(originalSize);
-                deletedChunks[layer.GuidValue].ApplyChunksToImage(layer.LayerImage);
-                layer.LayerImage.CommitChanges();
-            }
-            
-            if (member.Mask is null)
-                return;
-            member.Mask.EnqueueResize(originalSize);
-            deletedMaskChunks[member.GuidValue].ApplyChunksToImage(member.Mask);
-            member.Mask.CommitChanges();
-        });
-
-        target.HorizontalSymmetryAxisY = originalHorAxisY;
-        target.VerticalSymmetryAxisX = originalVerAxisX;
-
-        foreach (var stored in deletedChunks)
-            stored.Value.Dispose();
-        deletedChunks = new();
-        
-        foreach (var stored in deletedMaskChunks)
-            stored.Value.Dispose();
-        deletedMaskChunks = new();
-
-        return new Size_ChangeInfo(originalSize, originalVerAxisX, originalHorAxisY);
-    }
-
-    public override void Dispose()
-    {
-        foreach (var layer in deletedChunks)
-            layer.Value.Dispose();
-        foreach (var mask in deletedMaskChunks)
-            mask.Value.Dispose();
-    }
 }

+ 10 - 0
src/PixiEditor.DrawingApi.Core/Numerics/RectI.cs

@@ -236,6 +236,11 @@ public struct RectI : IEquatable<RectI>
         return left < rect.right && right > rect.left && top < rect.bottom && bottom > rect.top;
     }
 
+    /// <summary>
+    ///     Returns the intersection of two rectangles.
+    /// </summary>
+    /// <param name="other">Rectangle to intersect with.</param>
+    /// <returns>Intersected rect.</returns>
     public readonly RectI Intersect(RectI other)
     {
         int left = Math.Max(this.left, other.left);
@@ -255,6 +260,11 @@ public struct RectI : IEquatable<RectI>
             Bottom = bottom
         };
     }
+    
+    public bool Intersects(RectI rectI)
+    {
+        return left < rectI.right && right > rectI.left && top < rectI.bottom && bottom > rectI.top;
+    }
 
     public readonly RectI Union(RectI other)
     {

+ 7 - 0
src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs

@@ -244,4 +244,11 @@ internal class DocumentOperationsModule
         if (finish)
             Internals.ActionAccumulator.AddFinishedActions();
     }
+
+    public void ClipCanvas()
+    {
+        if (Internals.ChangeController.IsChangeActive)
+            return;
+        Internals.ActionAccumulator.AddFinishedActions(new ClipCanvas_Action());
+    }
 }

+ 4 - 1
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentManagerViewModel.cs

@@ -50,7 +50,10 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>
     [Command.Basic("PixiEditor.Document.ClipCanvas", "Clip Canvas", "Clip Canvas", CanExecute = "PixiEditor.HasDocument")]
     public void ClipCanvas()
     {
-        //Owner.BitmapManager.ActiveDocument?.ClipCanvas();
+        if (ActiveDocument is null)
+            return;
+        
+        ActiveDocument?.Operations.ClipCanvas();
     }
 
     [Command.Basic("PixiEditor.Document.ToggleVerticalSymmetryAxis", "Toggle vertical symmetry axis", "Toggle vertical symmetry axis", CanExecute = "PixiEditor.HasDocument")]