Browse Source

Cropped layer previews wip

Equbuxu 2 years ago
parent
commit
d5d953a615

+ 28 - 6
src/ChunkyImageLib/ChunkyImage.cs

@@ -37,7 +37,7 @@ namespace ChunkyImageLib;
 ///     - BlendMode.Src: default mode, the latest chunks are the same as committed ones but with some or all queued operations applied. 
 ///     - BlendMode.Src: default mode, the latest chunks are the same as committed ones but with some or all queued operations applied. 
 ///         This means that operations can work with the existing pixels.
 ///         This means that operations can work with the existing pixels.
 ///     - Any other blend mode: the latest chunks contain only the things drawn by the queued operations.
 ///     - Any other blend mode: the latest chunks contain only the things drawn by the queued operations.
-///         They need to be drawn over the committed chunks to obtain the final image. In this case, operations won't have access to the existing pixels.
+///         They need to be drawn over the committed chunks to obtain the final image. In this case, operations won't have access to the existing pixels. 
 /// </summary>
 /// </summary>
 public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 {
 {
@@ -117,7 +117,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
     }
 
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public RectI? FindLatestBounds()
+    public RectI? FindChunkAlignedMostUpToDateBounds()
     {
     {
         lock (lockObject)
         lock (lockObject)
         {
         {
@@ -129,7 +129,27 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 rect ??= chunkBounds;
                 rect ??= chunkBounds;
                 rect = rect.Value.Union(chunkBounds);
                 rect = rect.Value.Union(chunkBounds);
             }
             }
-            foreach (var (pos, _) in latestChunks[ChunkResolution.Full])
+            foreach (var operation in queuedOperations)
+            {
+                foreach (var pos in operation.affectedArea.Chunks)
+                {
+                    RectI chunkBounds = new RectI(pos * FullChunkSize, new VecI(FullChunkSize));
+                    rect ??= chunkBounds;
+                    rect = rect.Value.Union(chunkBounds);
+                }
+            }
+            return rect;
+        }
+    }
+
+    /// <exception cref="ObjectDisposedException">This image is disposed</exception>
+    public RectI? FindChunkAlignedCommittedBounds()
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            RectI? rect = null;
+            foreach (var (pos, _) in committedChunks[ChunkResolution.Full])
             {
             {
                 RectI chunkBounds = new RectI(pos * FullChunkSize, new VecI(FullChunkSize));
                 RectI chunkBounds = new RectI(pos * FullChunkSize, new VecI(FullChunkSize));
                 rect ??= chunkBounds;
                 rect ??= chunkBounds;
@@ -140,23 +160,25 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
     }
 
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public RectI? FindPreciseCommittedBounds()
+    public RectI? FindTightCommittedBounds(ChunkResolution precision = ChunkResolution.Full)
     {
     {
         lock (lockObject)
         lock (lockObject)
         {
         {
             ThrowIfDisposed();
             ThrowIfDisposed();
 
 
+            var chunkSize = precision.PixelSize();
             RectI? preciseBounds = null;
             RectI? preciseBounds = null;
-            foreach (var (chunkPos, chunk) in committedChunks[ChunkResolution.Full])
+            foreach (var (chunkPos, chunk) in committedChunks[precision])
             {
             {
                 RectI? chunkPreciseBounds = chunk.FindPreciseBounds();
                 RectI? chunkPreciseBounds = chunk.FindPreciseBounds();
                 if(chunkPreciseBounds is null) 
                 if(chunkPreciseBounds is null) 
                     continue;
                     continue;
-                RectI globalChunkBounds = chunkPreciseBounds.Value.Offset(chunkPos * FullChunkSize);
+                RectI globalChunkBounds = chunkPreciseBounds.Value.Offset(chunkPos * chunkSize);
 
 
                 preciseBounds ??= globalChunkBounds;
                 preciseBounds ??= globalChunkBounds;
                 preciseBounds = preciseBounds.Value.Union(globalChunkBounds);
                 preciseBounds = preciseBounds.Value.Union(globalChunkBounds);
             }
             }
+            preciseBounds = (RectI?)preciseBounds?.Scale(precision.InvertedMultiplier()).RoundOutwards();
             preciseBounds = preciseBounds?.Intersect(new RectI(preciseBounds.Value.Pos, CommittedSize));
             preciseBounds = preciseBounds?.Intersect(new RectI(preciseBounds.Value.Pos, CommittedSize));
 
 
             return preciseBounds;
             return preciseBounds;

+ 15 - 0
src/ChunkyImageLib/DataHolders/ChunkResolutionEx.cs

@@ -17,6 +17,21 @@ public static class ChunkResolutionEx
         };
         };
     }
     }
 
 
+    /// <summary>
+    /// Returns the inverted multiplier of the <paramref name="resolution"/>.
+    /// </summary>
+    public static double InvertedMultiplier(this ChunkResolution resolution)
+    {
+        return resolution switch
+        {
+            ChunkResolution.Full => 1,
+            ChunkResolution.Half => 2,
+            ChunkResolution.Quarter => 4,
+            ChunkResolution.Eighth => 8,
+            _ => 1,
+        };
+    }
+
     /// <summary>
     /// <summary>
     /// Returns the size of a chunk of the resolution <paramref name="resolution"/>
     /// Returns the size of a chunk of the resolution <paramref name="resolution"/>
     /// </summary>
     /// </summary>

+ 3 - 1
src/ChunkyImageLib/IReadOnlyChunkyImage.cs

@@ -10,7 +10,9 @@ public interface IReadOnlyChunkyImage
 {
 {
     bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null);
     bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null);
     bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null);
     bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null);
-    RectI? FindLatestBounds();
+    RectI? FindChunkAlignedMostUpToDateBounds();
+    RectI? FindChunkAlignedCommittedBounds();
+    RectI? FindTightCommittedBounds(ChunkResolution precision = ChunkResolution.Full);
     Color GetCommittedPixel(VecI posOnImage);
     Color GetCommittedPixel(VecI posOnImage);
     Color GetMostUpToDatePixel(VecI posOnImage);
     Color GetMostUpToDatePixel(VecI posOnImage);
     bool LatestOrCommittedChunkExists(VecI chunkPos);
     bool LatestOrCommittedChunkExists(VecI chunkPos);

+ 3 - 3
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -52,7 +52,7 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
             throw new ArgumentException(@"The given guid does not belong to a layer.", nameof(layerGuid));
             throw new ArgumentException(@"The given guid does not belong to a layer.", nameof(layerGuid));
 
 
 
 
-        RectI? tightBounds = layer.LayerImage.FindLatestBounds();
+        RectI? tightBounds = layer.LayerImage.FindChunkAlignedMostUpToDateBounds();
 
 
         if (tightBounds is null)
         if (tightBounds is null)
             return null;
             return null;
@@ -69,7 +69,7 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
         return surface;
         return surface;
     }
     }
 
 
-    public RectI? GetLayerTightBounds(Guid layerGuid)
+    public RectI? GetChunkAlignedLayerBounds(Guid layerGuid)
     {
     {
         var layer = (IReadOnlyLayer?)FindMember(layerGuid);
         var layer = (IReadOnlyLayer?)FindMember(layerGuid);
 
 
@@ -77,7 +77,7 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
             throw new ArgumentException(@"The given guid does not belong to a layer.", nameof(layerGuid));
             throw new ArgumentException(@"The given guid does not belong to a layer.", nameof(layerGuid));
 
 
 
 
-        return layer.LayerImage.FindLatestBounds();
+        return layer.LayerImage.FindChunkAlignedMostUpToDateBounds();
     }
     }
     
     
     public void ForEveryReadonlyMember(Action<IReadOnlyStructureMember> action) => ForEveryReadonlyMember(StructureRoot, action);
     public void ForEveryReadonlyMember(Action<IReadOnlyStructureMember> action) => ForEveryReadonlyMember(StructureRoot, action);

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

@@ -46,7 +46,7 @@ public interface IReadOnlyDocument
     void ForEveryReadonlyMember(Action<IReadOnlyStructureMember> action);
     void ForEveryReadonlyMember(Action<IReadOnlyStructureMember> action);
     
     
     public Surface? GetLayerImage(Guid layerGuid);
     public Surface? GetLayerImage(Guid layerGuid);
-    public RectI? GetLayerTightBounds(Guid layerGuid);
+    public RectI? GetChunkAlignedLayerBounds(Guid layerGuid);
 
 
     /// <summary>
     /// <summary>
     /// Finds the member with the <paramref name="guid"/> or returns null if not found
     /// Finds the member with the <paramref name="guid"/> or returns null if not found

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Drawing/TransformSelectedArea_UpdateableChange.cs

@@ -72,7 +72,7 @@ internal class TransformSelectedArea_UpdateableChange : UpdateableChange
     public OneOf<None, (Surface image, RectI extractedRect)> ExtractArea(ChunkyImage image, VectorPath path, RectI pathBounds)
     public OneOf<None, (Surface image, RectI extractedRect)> ExtractArea(ChunkyImage image, VectorPath path, RectI pathBounds)
     {
     {
         // get rid of transparent areas on edges
         // get rid of transparent areas on edges
-        var memberImageBounds = image.FindLatestBounds();
+        var memberImageBounds = image.FindChunkAlignedMostUpToDateBounds();
         if (memberImageBounds is null)
         if (memberImageBounds is null)
             return new None();
             return new None();
         pathBounds = pathBounds.Intersect(memberImageBounds.Value);
         pathBounds = pathBounds.Intersect(memberImageBounds.Value);

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Root/CenterContent_Change.cs

@@ -41,7 +41,7 @@ internal class CenterContent_Change : Change
         foreach (var layerGuid in affectedLayers)
         foreach (var layerGuid in affectedLayers)
         {
         {
             Layer layer = document.FindMemberOrThrow<Layer>(layerGuid);
             Layer layer = document.FindMemberOrThrow<Layer>(layerGuid);
-            RectI? tightBounds = layer.LayerImage.FindPreciseCommittedBounds();
+            RectI? tightBounds = layer.LayerImage.FindTightCommittedBounds();
             if (tightBounds.HasValue)
             if (tightBounds.HasValue)
             {
             {
                 currentBounds = currentBounds.HasValue ? currentBounds.Value.Union(tightBounds.Value) : tightBounds;
                 currentBounds = currentBounds.HasValue ? currentBounds.Value.Union(tightBounds.Value) : tightBounds;

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Root/ClipCanvas_Change.cs

@@ -15,7 +15,7 @@ internal class ClipCanvas_Change : ResizeBasedChangeBase
         {
         {
             if (member is Layer layer)
             if (member is Layer layer)
             {
             {
-                var layerBounds = layer.LayerImage.FindPreciseCommittedBounds();
+                var layerBounds = layer.LayerImage.FindTightCommittedBounds();
                 if (layerBounds.HasValue)
                 if (layerBounds.HasValue)
                 {
                 {
                     bounds ??= layerBounds.Value;
                     bounds ??= layerBounds.Value;

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Root/FlipImage_Change.cs

@@ -54,7 +54,7 @@ internal sealed class FlipImage_Change : Change
         RectI bounds = new RectI(VecI.Zero, img.LatestSize);
         RectI bounds = new RectI(VecI.Zero, img.LatestSize);
         if (membersToFlip.Count > 0)
         if (membersToFlip.Count > 0)
         {
         {
-            var preciseBounds = img.FindPreciseCommittedBounds();
+            var preciseBounds = img.FindTightCommittedBounds();
             if (preciseBounds.HasValue)
             if (preciseBounds.HasValue)
             {
             {
                 bounds = preciseBounds.Value;
                 bounds = preciseBounds.Value;

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Root/RotateImage_Change.cs

@@ -59,7 +59,7 @@ internal sealed class RotateImage_Change : Change
         RectI bounds = new RectI(VecI.Zero, img.CommittedSize);
         RectI bounds = new RectI(VecI.Zero, img.CommittedSize);
         if (membersToRotate.Count > 0)
         if (membersToRotate.Count > 0)
         {
         {
-            var preciseBounds = img.FindPreciseCommittedBounds();
+            var preciseBounds = img.FindTightCommittedBounds();
             if (preciseBounds.HasValue)
             if (preciseBounds.HasValue)
             {
             {
                 bounds = preciseBounds.Value;
                 bounds = preciseBounds.Value;

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

@@ -261,6 +261,11 @@ public struct RectI : IEquatable<RectI>
         return x > left && x < right && y > top && y < bottom;
         return x > left && x < right && y > top && y < bottom;
     }
     }
 
 
+    public readonly bool ContainsExclusive(RectI rect)
+    {
+        return ContainsExclusive(rect.TopLeft) && ContainsExclusive(rect.BottomRight);
+    }
+
     public readonly bool ContainsPixel(VecI pixelTopLeft) => ContainsPixel(pixelTopLeft.X, pixelTopLeft.Y);
     public readonly bool ContainsPixel(VecI pixelTopLeft) => ContainsPixel(pixelTopLeft.X, pixelTopLeft.Y);
     public readonly bool ContainsPixel(int pixelTopLeftX, int pixelTopLeftY)
     public readonly bool ContainsPixel(int pixelTopLeftX, int pixelTopLeftY)
     {
     {

+ 87 - 14
src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs

@@ -13,6 +13,8 @@ using PixiEditor.DrawingApi.Core.Surface;
 using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.Rendering.RenderInfos;
 using PixiEditor.Models.Rendering.RenderInfos;
 using PixiEditor.ViewModels.SubViewModels.Document;
 using PixiEditor.ViewModels.SubViewModels.Document;
+using System.Diagnostics;
+using System.Drawing.Text;
 
 
 namespace PixiEditor.Models.Rendering;
 namespace PixiEditor.Models.Rendering;
 internal class MemberPreviewUpdater
 internal class MemberPreviewUpdater
@@ -20,6 +22,7 @@ internal class MemberPreviewUpdater
     private readonly DocumentViewModel doc;
     private readonly DocumentViewModel doc;
     private readonly DocumentInternalParts internals;
     private readonly DocumentInternalParts internals;
 
 
+    private Dictionary<Guid, RectI> lastTightBounds = new();
     private Dictionary<Guid, AffectedArea> previewDelayedAreas = new();
     private Dictionary<Guid, AffectedArea> previewDelayedAreas = new();
     private Dictionary<Guid, AffectedArea> maskPreviewDelayedAreas = new();
     private Dictionary<Guid, AffectedArea> maskPreviewDelayedAreas = new();
 
 
@@ -52,15 +55,21 @@ internal class MemberPreviewUpdater
 
 
     private List<IRenderInfo> Render(AffectedAreasGatherer chunkGatherer, bool rerenderPreviews)
     private List<IRenderInfo> Render(AffectedAreasGatherer chunkGatherer, bool rerenderPreviews)
     {
     {
+        Stopwatch sw = Stopwatch.StartNew();
         List<IRenderInfo> infos = new();
         List<IRenderInfo> infos = new();
 
 
         var (imagePreviewChunksToRerender, maskPreviewChunksToRerender) = FindPreviewChunksToRerender(chunkGatherer, !rerenderPreviews);
         var (imagePreviewChunksToRerender, maskPreviewChunksToRerender) = FindPreviewChunksToRerender(chunkGatherer, !rerenderPreviews);
         var previewSize = StructureMemberViewModel.CalculatePreviewSize(internals.Tracker.Document.Size);
         var previewSize = StructureMemberViewModel.CalculatePreviewSize(internals.Tracker.Document.Size);
         float scaling = (float)previewSize.X / doc.SizeBindable.X;
         float scaling = (float)previewSize.X / doc.SizeBindable.X;
         UpdateImagePreviews(imagePreviewChunksToRerender, scaling, infos);
         UpdateImagePreviews(imagePreviewChunksToRerender, scaling, infos);
+        if (rerenderPreviews)
+            Trace.WriteLine("image" + (sw.ElapsedTicks * 1000 / (double)Stopwatch.Frequency).ToString());
         UpdateMaskPreviews(maskPreviewChunksToRerender, scaling, infos);
         UpdateMaskPreviews(maskPreviewChunksToRerender, scaling, infos);
 
 
-        return infos;
+        if (rerenderPreviews)
+            Trace.WriteLine(sw.ElapsedTicks * 1000 / (double)Stopwatch.Frequency );
+        sw.Stop();
+        return infos;        
     }
     }
 
 
     private static void AddAreas(Dictionary<Guid, AffectedArea> from, Dictionary<Guid, AffectedArea> to)
     private static void AddAreas(Dictionary<Guid, AffectedArea> from, Dictionary<Guid, AffectedArea> to)
@@ -137,6 +146,77 @@ internal class MemberPreviewUpdater
             infos.Add(new CanvasPreviewDirty_RenderInfo());
             infos.Add(new CanvasPreviewDirty_RenderInfo());
     }
     }
 
 
+    private RectI? FindLayerTightBounds(IReadOnlyLayer layer)
+    {
+        // premature optimization here we go
+        RectI? bounds = layer.LayerImage.FindChunkAlignedCommittedBounds();
+        if (bounds is null)
+            return null;
+
+        int biggest = bounds.Value.Size.LongestAxis;
+        ChunkResolution resolution = biggest switch
+        {
+            > 2048 => ChunkResolution.Eighth,
+            > 1024 => ChunkResolution.Quarter,
+            > 512 => ChunkResolution.Half,
+            _ => ChunkResolution.Full,
+        };
+        return layer.LayerImage.FindTightCommittedBounds(resolution);
+    }
+
+    private void UpdateLayerPreviewSurface(IReadOnlyLayer layer, StructureMemberViewModel memberVM, AffectedArea area, float scaling)
+    {
+        RectI? prevTightBounds = null;
+        if (lastTightBounds.TryGetValue(layer.GuidValue, out RectI tightBounds))
+            prevTightBounds = tightBounds;
+
+        RectI? newTightBounds;
+
+        if (prevTightBounds is null)
+        {
+            newTightBounds = FindLayerTightBounds(layer);
+        }
+        else if (prevTightBounds.Value.ContainsExclusive(area.GlobalArea.Value))
+        {
+            // if the affected area is fully inside the previous tight bounds, the tight bounds couldn't possibly have changed
+            newTightBounds = prevTightBounds.Value;
+        }
+        else
+        {
+            newTightBounds = FindLayerTightBounds(layer);
+        }
+
+        if (newTightBounds is null)
+        {
+            memberVM.PreviewSurface.Canvas.Clear();
+            return;
+        }
+
+        if (newTightBounds == prevTightBounds)
+        {
+            memberVM.PreviewSurface.Canvas.Save();
+            memberVM.PreviewSurface.Canvas.Scale(scaling);
+            memberVM.PreviewSurface.Canvas.ClipRect((RectD)area.GlobalArea);
+
+            foreach (var chunk in area.Chunks)
+            {
+                var pos = chunk * ChunkResolution.Full.PixelSize();
+                if (!layer.LayerImage.DrawMostUpToDateChunkOn(chunk, ChunkResolution.Full, memberVM.PreviewSurface, pos, SmoothReplacingPaint))
+                    memberVM.PreviewSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkyImage.FullChunkSize, ChunkyImage.FullChunkSize, ClearPaint);
+            }
+
+            memberVM.PreviewSurface.Canvas.Restore();
+            return;
+        }
+
+        int biggestAxis = newTightBounds.Value.Size.LongestAxis;
+        RectI targetBounds = (RectI)RectD.FromCenterAndSize(newTightBounds.Value.Center, new(biggestAxis)).RoundOutwards();
+
+        memberVM.PreviewSurface.Canvas.Save();
+        memberVM.PreviewSurface.Canvas.Scale(scaling);
+        memberVM.PreviewSurface.Canvas.Scale()
+    }
+
     private void UpdateMembersImagePreviews(Dictionary<Guid, AffectedArea> imagePreviewChunks, float scaling, List<IRenderInfo> infos)
     private void UpdateMembersImagePreviews(Dictionary<Guid, AffectedArea> imagePreviewChunks, float scaling, List<IRenderInfo> infos)
     {
     {
         foreach (var (guid, area) in imagePreviewChunks)
         foreach (var (guid, area) in imagePreviewChunks)
@@ -148,24 +228,17 @@ internal class MemberPreviewUpdater
                 continue;
                 continue;
             var member = internals.Tracker.Document.FindMemberOrThrow(guid);
             var member = internals.Tracker.Document.FindMemberOrThrow(guid);
 
 
-            memberVM.PreviewSurface.Canvas.Save();
-            memberVM.PreviewSurface.Canvas.Scale(scaling);
-            memberVM.PreviewSurface.Canvas.ClipRect((RectD)area.GlobalArea);
+            
             if (memberVM is LayerViewModel)
             if (memberVM is LayerViewModel)
             {
             {
-                var layer = (IReadOnlyLayer)member;
-                foreach (var chunk in area.Chunks)
-                {
-                    var pos = chunk * ChunkResolution.Full.PixelSize();
-                    // the full res chunks are already rendered so drawing them again should be fast
-                    if (!layer.LayerImage.DrawMostUpToDateChunkOn
-                            (chunk, ChunkResolution.Full, memberVM.PreviewSurface, pos, SmoothReplacingPaint))
-                        memberVM.PreviewSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkyImage.FullChunkSize, ChunkyImage.FullChunkSize, ClearPaint);
-                }
+                UpdateLayerPreviewSurface((IReadOnlyLayer)member, memberVM, area, scaling);
                 infos.Add(new PreviewDirty_RenderInfo(guid));
                 infos.Add(new PreviewDirty_RenderInfo(guid));
             }
             }
             else if (memberVM is FolderViewModel)
             else if (memberVM is FolderViewModel)
             {
             {
+                memberVM.PreviewSurface.Canvas.Save();
+                memberVM.PreviewSurface.Canvas.Scale(scaling);
+                memberVM.PreviewSurface.Canvas.ClipRect((RectD)area.GlobalArea);
                 var folder = (IReadOnlyFolder)member;
                 var folder = (IReadOnlyFolder)member;
                 foreach (var chunk in area.Chunks)
                 foreach (var chunk in area.Chunks)
                 {
                 {
@@ -183,9 +256,9 @@ internal class MemberPreviewUpdater
                         memberVM.PreviewSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkResolution.Full.PixelSize(), ChunkResolution.Full.PixelSize(), ClearPaint);
                         memberVM.PreviewSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkResolution.Full.PixelSize(), ChunkResolution.Full.PixelSize(), ClearPaint);
                     }
                     }
                 }
                 }
+                memberVM.PreviewSurface.Canvas.Restore();
                 infos.Add(new PreviewDirty_RenderInfo(guid));
                 infos.Add(new PreviewDirty_RenderInfo(guid));
             }
             }
-            memberVM.PreviewSurface.Canvas.Restore();
         }
         }
     }
     }
 
 

+ 2 - 2
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.Serialization.cs

@@ -110,7 +110,7 @@ internal partial class DocumentViewModel
     {
     {
         var result = document.GetLayerImage(layer.GuidValue);
         var result = document.GetLayerImage(layer.GuidValue);
 
 
-        var tightBounds = document.GetLayerTightBounds(layer.GuidValue);
+        var tightBounds = document.GetChunkAlignedLayerBounds(layer.GuidValue);
         using var data = result?.DrawingSurface.Snapshot().Encode();
         using var data = result?.DrawingSurface.Snapshot().Encode();
         byte[] bytes = data?.AsSpan().ToArray();
         byte[] bytes = data?.AsSpan().ToArray();
         var serializable = new ImageLayer
         var serializable = new ImageLayer
@@ -130,7 +130,7 @@ internal partial class DocumentViewModel
         if (mask == null) 
         if (mask == null) 
             return null;
             return null;
         
         
-        var maskBound = mask.FindLatestBounds();
+        var maskBound = mask.FindChunkAlignedMostUpToDateBounds();
 
 
         if (maskBound == null)
         if (maskBound == null)
         {
         {

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.cs

@@ -348,7 +348,7 @@ internal partial class DocumentViewModel : NotifyableObject
         RectI? memberImageBounds;
         RectI? memberImageBounds;
         try
         try
         {
         {
-            memberImageBounds = layer.LayerImage.FindLatestBounds();
+            memberImageBounds = layer.LayerImage.FindChunkAlignedMostUpToDateBounds();
         }
         }
         catch (ObjectDisposedException)
         catch (ObjectDisposedException)
         {
         {