Browse Source

Fixed transform disabling on paste and outdated transform bounds

Krzysztof Krysiński 1 month ago
parent
commit
9d40e7e1dc

+ 103 - 11
src/ChunkyImageLib/ChunkyImage.cs

@@ -62,8 +62,10 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
     private readonly object lockObject = new();
     private int commitCounter = 0;
 
-    private RectI cachedPreciseBounds = RectI.Empty;
-    private int lastBoundsCacheHash = -1;
+    private RectI cachedPreciseCommitedBounds = RectI.Empty;
+    private RectI cachedPreciseLatestBounds = RectI.Empty;
+    private int lastCommitedBoundsCacheHash = -1;
+    private int lastLatestBoundsCacheHash = -1;
 
     public const int FullChunkSize = ChunkPool.FullChunkSize;
     private static Paint ClippingPaint { get; } = new Paint() { BlendMode = BlendMode.DstIn };
@@ -197,9 +199,9 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
         {
             ThrowIfDisposed();
 
-            if (lastBoundsCacheHash == GetCacheHash())
+            if (lastCommitedBoundsCacheHash == GetCacheHash())
             {
-                return cachedPreciseBounds;
+                return cachedPreciseCommitedBounds;
             }
 
             var chunkSize = suggestedResolution.PixelSize();
@@ -247,8 +249,85 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
             preciseBounds = (RectI?)preciseBounds?.Scale(suggestedResolution.InvertedMultiplier()).RoundOutwards();
             preciseBounds = preciseBounds?.Intersect(new RectI(preciseBounds.Value.Pos, CommittedSize));
 
-            cachedPreciseBounds = preciseBounds.GetValueOrDefault();
-            lastBoundsCacheHash = GetCacheHash();
+            cachedPreciseCommitedBounds = preciseBounds.GetValueOrDefault();
+            lastCommitedBoundsCacheHash = GetCacheHash();
+
+            return preciseBounds;
+        }
+    }
+
+    public RectI? FindTightLatestBounds(ChunkResolution suggestedResolution = ChunkResolution.Full,
+        bool fallbackToChunkAligned = false)
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+
+            if(queuedOperations.Count == 0)
+            {
+                return FindTightCommittedBounds(suggestedResolution, fallbackToChunkAligned);
+            }
+
+            if (lastLatestBoundsCacheHash == GetCacheHash())
+            {
+                return cachedPreciseLatestBounds;
+            }
+
+            var chunkSize = suggestedResolution.PixelSize();
+            var multiplier = suggestedResolution.Multiplier();
+            RectI scaledLatestSize = (RectI)(new RectD(VecI.Zero, LatestSize * multiplier)).RoundOutwards();
+
+            RectI? preciseBounds = null;
+
+            var possibleChunks = new HashSet<VecI>();
+            foreach (var (pos, _) in committedChunks[ChunkResolution.Full])
+                possibleChunks.Add(pos);
+
+            foreach (var (pos, _) in latestChunks[ChunkResolution.Full])
+                possibleChunks.Add(pos);
+
+            foreach (var chunkPos in possibleChunks)
+            {
+                var chunk = GetLatestChunk(chunkPos, suggestedResolution) ?? GetCommittedChunk(chunkPos, suggestedResolution);
+                if (chunk != null)
+                {
+                    RectI visibleArea = new RectI(chunkPos * chunkSize, new VecI(chunkSize))
+                        .Intersect(scaledLatestSize).Translate(-chunkPos * chunkSize);
+
+                    RectI? chunkPreciseBounds = chunk.FindPreciseBounds(visibleArea);
+                    if (chunkPreciseBounds is null)
+                        continue;
+                    RectI globalChunkBounds = chunkPreciseBounds.Value.Offset(chunkPos * chunkSize);
+
+                    preciseBounds ??= globalChunkBounds;
+                    preciseBounds = preciseBounds.Value.Union(globalChunkBounds);
+                }
+                else
+                {
+                    if (fallbackToChunkAligned)
+                    {
+                        return FindChunkAlignedMostUpToDateBounds();
+                    }
+
+                    RectI visibleArea = new RectI(chunkPos * FullChunkSize, new VecI(FullChunkSize))
+                        .Intersect(new RectI(VecI.Zero, LatestSize)).Translate(-chunkPos * FullChunkSize);
+
+                    RectI? chunkPreciseBounds = chunk.FindPreciseBounds(visibleArea);
+                    if (chunkPreciseBounds is null)
+                        continue;
+                    RectI globalChunkBounds = (RectI)chunkPreciseBounds.Value.Scale(multiplier)
+                        .Offset(chunkPos * chunkSize).RoundOutwards();
+
+                    preciseBounds ??= globalChunkBounds;
+                    preciseBounds = preciseBounds.Value.Union(globalChunkBounds);
+                }
+            }
+
+            preciseBounds = (RectI?)preciseBounds?.Scale(suggestedResolution.InvertedMultiplier()).RoundOutwards();
+            preciseBounds = preciseBounds?.Intersect(new RectI(preciseBounds.Value.Pos, LatestSize));
+
+            cachedPreciseLatestBounds = preciseBounds.GetValueOrDefault();
+            lastLatestBoundsCacheHash = GetCacheHash();
 
             return preciseBounds;
         }
@@ -1516,10 +1595,23 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
 
     public int GetCacheHash()
     {
-        return commitCounter + queuedOperations.Count + operationCounter + activeClips.Count
-               + (int)blendMode + (lockTransparency ? 1 : 0)
-               + (horizontalSymmetryAxis is not null ? (int)(horizontalSymmetryAxis * 100) : 0)
-               + (verticalSymmetryAxis is not null ? (int)(verticalSymmetryAxis * 100) : 0)
-               + (clippingPath is not null ? 1 : 0);
+        HashCode hash = new HashCode();
+        hash.Add(commitCounter);
+        hash.Add(queuedOperations.Count);
+        hash.Add(operationCounter);
+        foreach (var queuedOperation in queuedOperations)
+        {
+            hash.Add(queuedOperation.affectedArea.GlobalArea?.GetHashCode() ?? 0);
+        }
+        hash.Add(activeClips.Count);
+        hash.Add((int)blendMode);
+        hash.Add(lockTransparency);
+        if (horizontalSymmetryAxis is not null)
+            hash.Add((int)(horizontalSymmetryAxis * 100));
+        if (verticalSymmetryAxis is not null)
+            hash.Add((int)(verticalSymmetryAxis * 100));
+        if (clippingPath is not null)
+            hash.Add(1);
+        return hash.ToHashCode();
     }
 }

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs

@@ -47,7 +47,7 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
     public override RectD? GetTightBounds(KeyFrameTime frameTime)
     {
-        return (RectD?)GetLayerImageAtFrame(frameTime.Frame).FindTightCommittedBounds();
+        return (RectD?)GetLayerImageAtFrame(frameTime.Frame).FindTightLatestBounds();
     }
 
     public override RectD? GetApproxBounds(KeyFrameTime frameTime)

+ 8 - 2
src/PixiEditor/Models/Controllers/ClipboardController.cs

@@ -295,12 +295,18 @@ internal static class ClipboardController
 
                 manager.Owner.ToolsSubViewModel.SetActiveTool<MoveToolViewModel>(false);
                 document.Operations.SetSelectedMember(guid.Value);
-                document.Operations.PasteImageWithTransform(dataImage.Image, position, guid.Value, false);
+                document.Operations.InvokeCustomAction(() =>
+                {
+                    document.Operations.PasteImageWithTransform(dataImage.Image, position, guid.Value, false);
+                });
             }
             else
             {
                 manager.Owner.ToolsSubViewModel.SetActiveTool<MoveToolViewModel>(false);
-                document.Operations.PasteImageWithTransform(dataImage.Image, position);
+                document.Operations.InvokeCustomAction(() =>
+                {
+                    document.Operations.PasteImageWithTransform(dataImage.Image, position);
+                });
             }
 
             return true;

+ 3 - 4
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformSelectedExecutor.cs

@@ -200,8 +200,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
             }
             else
             {
-                if (document.SoftSelectedStructureMembers.Contains(topMost) ||
-                    document.SelectedStructureMember?.Id == topMost.Id)
+                if (document.SoftSelectedStructureMembers.Contains(topMost) || document.SelectedStructureMember?.Id == topMost.Id)
                 {
                     Deselect(smallestSizeDifferenceList);
                 }
@@ -254,8 +253,8 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
             bool deselectingWasMain = document.SelectedStructureMember?.Id == topMost.Id;
             if (deselectingWasMain)
             {
-                Guid? nextMain = document.SoftSelectedStructureMembers?.FirstOrDefault().Id;
-                List<Guid> softSelected = document.SoftSelectedStructureMembers?
+                Guid? nextMain = document.SoftSelectedStructureMembers.FirstOrDefault()?.Id;
+                List<Guid> softSelected = document.SoftSelectedStructureMembers
                     .Select(x => x.Id).Where(x => x != nextMain.Value).ToList();
 
                 document.Operations.ClearSoftSelectedMembers();

+ 8 - 5
src/PixiEditor/ViewModels/Tools/Tools/MoveToolViewModel.cs

@@ -79,18 +79,21 @@ internal class MoveToolViewModel : ToolViewModel, IMoveToolHandler
 
     public override void KeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown, Key argsKey)
     {
-        DuplicateOnMove = ctrlIsDown && argsKey is Key.None or Key.LeftCtrl or Key.RightCtrl && !shiftIsDown && !altIsDown;
+        DuplicateOnMove = ctrlIsDown && argsKey is Key.None or Key.LeftCtrl or Key.RightCtrl && !shiftIsDown &&
+                          !altIsDown;
     }
-    
+
     protected override void OnSelected(bool restoring)
     {
-        if (TransformingSelectedArea)
+        if (TransformingSelectedArea || restoring)
         {
             return;
         }
 
         DuplicateOnMove = false;
-        ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Operations.TransformSelectedArea(true);
+        var activeDoc = ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument;
+
+        activeDoc?.Operations.TransformSelectedArea(true);
     }
 
     protected override void OnDeselecting(bool transient)
@@ -125,7 +128,7 @@ internal class MoveToolViewModel : ToolViewModel, IMoveToolHandler
     {
         DuplicateOnMove = false;
     }
-    
+
     public override void OnPreRedoInlet()
     {
         DuplicateOnMove = false;