Browse Source

Fixed Previews

flabbet 1 year ago
parent
commit
02850751e4

+ 100 - 69
src/PixiEditor.AvaloniaUI/Models/Rendering/MemberPreviewUpdater.cs

@@ -20,6 +20,7 @@ using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 using PixiEditor.Numerics;
 using PixiEditor.Numerics;
 
 
 namespace PixiEditor.AvaloniaUI.Models.Rendering;
 namespace PixiEditor.AvaloniaUI.Models.Rendering;
+
 internal class MemberPreviewUpdater
 internal class MemberPreviewUpdater
 {
 {
     private const float smoothingThreshold = 1.5f;
     private const float smoothingThreshold = 1.5f;
@@ -33,9 +34,15 @@ internal class MemberPreviewUpdater
     private Dictionary<Guid, AffectedArea> mainPreviewAreasAccumulator = new();
     private Dictionary<Guid, AffectedArea> mainPreviewAreasAccumulator = new();
     private Dictionary<Guid, AffectedArea> maskPreviewAreasAccumulator = new();
     private Dictionary<Guid, AffectedArea> maskPreviewAreasAccumulator = new();
 
 
-    private static readonly Paint SmoothReplacingPaint = new() { BlendMode = BlendMode.Src, FilterQuality = FilterQuality.Medium, IsAntiAliased = true };
+    private static readonly Paint SmoothReplacingPaint = new()
+    {
+        BlendMode = BlendMode.Src, FilterQuality = FilterQuality.Medium, IsAntiAliased = true
+    };
+
     private static readonly Paint ReplacingPaint = new() { BlendMode = BlendMode.Src };
     private static readonly Paint ReplacingPaint = new() { BlendMode = BlendMode.Src };
-    private static readonly Paint ClearPaint = new() { BlendMode = BlendMode.Src, Color = DrawingApi.Core.ColorsImpl.Colors.Transparent };
+
+    private static readonly Paint ClearPaint =
+        new() { BlendMode = BlendMode.Src, Color = DrawingApi.Core.ColorsImpl.Colors.Transparent };
 
 
     public MemberPreviewUpdater(IDocument doc, DocumentInternalParts internals)
     public MemberPreviewUpdater(IDocument doc, DocumentInternalParts internals)
     {
     {
@@ -55,9 +62,9 @@ internal class MemberPreviewUpdater
 
 
         Dictionary<Guid, (VecI previewSize, RectI tightBounds)?>? changedMainPreviewBounds = null;
         Dictionary<Guid, (VecI previewSize, RectI tightBounds)?>? changedMainPreviewBounds = null;
         Dictionary<Guid, (VecI previewSize, RectI tightBounds)?>? changedMaskPreviewBounds = null;
         Dictionary<Guid, (VecI previewSize, RectI tightBounds)?>? changedMaskPreviewBounds = null;
-        
+
         int atFrame = doc.AnimationHandler.ActiveFrameBindable;
         int atFrame = doc.AnimationHandler.ActiveFrameBindable;
-        
+
         await Task.Run(() =>
         await Task.Run(() =>
         {
         {
             changedMainPreviewBounds = FindChangedTightBounds(atFrame, false);
             changedMainPreviewBounds = FindChangedTightBounds(atFrame, false);
@@ -65,7 +72,8 @@ internal class MemberPreviewUpdater
         }).ConfigureAwait(true);
         }).ConfigureAwait(true);
 
 
         RecreatePreviewBitmaps(changedMainPreviewBounds!, changedMaskPreviewBounds!);
         RecreatePreviewBitmaps(changedMainPreviewBounds!, changedMaskPreviewBounds!);
-        var renderInfos = await Task.Run(() => Render(changedMainPreviewBounds!, changedMaskPreviewBounds)).ConfigureAwait(true);
+        var renderInfos = await Task.Run(() => Render(changedMainPreviewBounds!, changedMaskPreviewBounds))
+            .ConfigureAwait(true);
 
 
         CleanupUnusedTightBounds();
         CleanupUnusedTightBounds();
 
 
@@ -97,7 +105,7 @@ internal class MemberPreviewUpdater
         AddAreasToAccumulator(chunkGatherer);
         AddAreasToAccumulator(chunkGatherer);
         if (!rerenderPreviews)
         if (!rerenderPreviews)
             return new List<IRenderInfo>();
             return new List<IRenderInfo>();
-        
+
         int frame = doc.AnimationHandler.ActiveFrameBindable;
         int frame = doc.AnimationHandler.ActiveFrameBindable;
 
 
         var changedMainPreviewBounds = FindChangedTightBounds(frame, false);
         var changedMainPreviewBounds = FindChangedTightBounds(frame, false);
@@ -200,6 +208,7 @@ internal class MemberPreviewUpdater
             VecI previewSize = StructureHelpers.CalculatePreviewSize(tightBounds.Value.Size);
             VecI previewSize = StructureHelpers.CalculatePreviewSize(tightBounds.Value.Size);
             newPreviewBitmapSizes.Add(guid, (previewSize, tightBounds.Value));
             newPreviewBitmapSizes.Add(guid, (previewSize, tightBounds.Value));
         }
         }
+
         return newPreviewBitmapSizes;
         return newPreviewBitmapSizes;
     }
     }
 
 
@@ -207,7 +216,7 @@ internal class MemberPreviewUpdater
     /// Recreates the preview bitmaps using the passed sizes (or deletes them when new size is null)
     /// Recreates the preview bitmaps using the passed sizes (or deletes them when new size is null)
     /// </summary>
     /// </summary>
     private void RecreatePreviewBitmaps(
     private void RecreatePreviewBitmaps(
-        Dictionary<Guid, (VecI previewSize, RectI tightBounds)?> newPreviewSizes, 
+        Dictionary<Guid, (VecI previewSize, RectI tightBounds)?> newPreviewSizes,
         Dictionary<Guid, (VecI previewSize, RectI tightBounds)?> newMaskSizes)
         Dictionary<Guid, (VecI previewSize, RectI tightBounds)?> newMaskSizes)
     {
     {
         // update previews
         // update previews
@@ -215,36 +224,22 @@ internal class MemberPreviewUpdater
         {
         {
             IStructureMemberHandler member = doc.StructureHelper.FindOrThrow(guid);
             IStructureMemberHandler member = doc.StructureHelper.FindOrThrow(guid);
 
 
-            bool hasAnimation = doc.AnimationHandler.FindKeyFrame<IKeyFrameGroupHandler>(guid, out IKeyFrameGroupHandler? animationGroup);
-            IKeyFrameHandler? keyFrame = hasAnimation ? animationGroup!.Children.FirstOrDefault(x => IsWithinRange(x, doc.AnimationHandler.ActiveFrameBindable)) : null;
-
             if (newSize is null)
             if (newSize is null)
             {
             {
                 member.PreviewSurface?.Dispose();
                 member.PreviewSurface?.Dispose();
                 member.PreviewSurface = null;
                 member.PreviewSurface = null;
-
-                keyFrame?.PreviewSurface?.Dispose();
-                if (keyFrame != null)
-                {
-                    keyFrame.PreviewSurface = null;
-                }
             }
             }
             else
             else
             {
             {
-                if (member.PreviewSurface is not null && member.PreviewSurface.Size.X == newSize.Value.previewSize.X && member.PreviewSurface.Size.Y == newSize.Value.previewSize.Y)
+                if (member.PreviewSurface is not null && member.PreviewSurface.Size.X == newSize.Value.previewSize.X &&
+                    member.PreviewSurface.Size.Y == newSize.Value.previewSize.Y)
                 {
                 {
                     member.PreviewSurface!.DrawingSurface.Canvas.Clear();
                     member.PreviewSurface!.DrawingSurface.Canvas.Clear();
-                    keyFrame?.PreviewSurface?.DrawingSurface.Canvas.Clear();
                 }
                 }
                 else
                 else
                 {
                 {
                     member.PreviewSurface?.Dispose();
                     member.PreviewSurface?.Dispose();
-                    member.PreviewSurface = new Surface(newSize.Value.previewSize); // TODO: premul bgra8888 was here
-                    keyFrame?.PreviewSurface?.Dispose();
-                    if (keyFrame != null)
-                    {
-                        keyFrame.PreviewSurface = new Surface(newSize.Value.previewSize);
-                    }
+                    member.PreviewSurface = new Surface(newSize.Value.previewSize);
                 }
                 }
             }
             }
 
 
@@ -272,11 +267,13 @@ internal class MemberPreviewUpdater
         }
         }
     }
     }
 
 
+
     /// <summary>
     /// <summary>
     /// Returns the previosly known committed tight bounds if there are no reasons to believe they have changed (based on the passed <paramref name="currentlyAffectedArea"/>).
     /// Returns the previosly known committed tight bounds if there are no reasons to believe they have changed (based on the passed <paramref name="currentlyAffectedArea"/>).
     /// Otherwise, calculates the new bounds via <see cref="FindLayerTightBounds"/> and returns them.
     /// Otherwise, calculates the new bounds via <see cref="FindLayerTightBounds"/> and returns them.
     /// </summary>
     /// </summary>
-    private RectI? GetOrFindMemberTightBounds(IReadOnlyStructureMember member, int atFrame, AffectedArea currentlyAffectedArea, bool forMask)
+    private RectI? GetOrFindMemberTightBounds(IReadOnlyStructureMember member, int atFrame,
+        AffectedArea currentlyAffectedArea, bool forMask)
     {
     {
         if (forMask && member.Mask is null)
         if (forMask && member.Mask is null)
             throw new InvalidOperationException();
             throw new InvalidOperationException();
@@ -288,7 +285,8 @@ internal class MemberPreviewUpdater
         if (targetLastCollection.TryGetValue(member.GuidValue, out RectI tightBounds))
         if (targetLastCollection.TryGetValue(member.GuidValue, out RectI tightBounds))
             prevTightBounds = tightBounds;
             prevTightBounds = tightBounds;
 
 
-        if (prevTightBounds is not null && currentlyAffectedArea.GlobalArea is not null && prevTightBounds.Value.ContainsExclusive(currentlyAffectedArea.GlobalArea.Value))
+        if (prevTightBounds is not null && currentlyAffectedArea.GlobalArea is not null &&
+            prevTightBounds.Value.ContainsExclusive(currentlyAffectedArea.GlobalArea.Value))
         {
         {
             // if the affected area is fully inside the previous tight bounds, the tight bounds couldn't possibly have changed
             // if the affected area is fully inside the previous tight bounds, the tight bounds couldn't possibly have changed
             return prevTightBounds.Value;
             return prevTightBounds.Value;
@@ -337,7 +335,7 @@ internal class MemberPreviewUpdater
         foreach (var child in folder.Children)
         foreach (var child in folder.Children)
         {
         {
             RectI? curBounds = null;
             RectI? curBounds = null;
-            
+
             if (child is IReadOnlyLayer childLayer)
             if (child is IReadOnlyLayer childLayer)
                 curBounds = FindLayerTightBounds(childLayer, frame, false);
                 curBounds = FindLayerTightBounds(childLayer, frame, false);
             else if (child is IReadOnlyFolder childFolder)
             else if (child is IReadOnlyFolder childFolder)
@@ -395,9 +393,11 @@ internal class MemberPreviewUpdater
         // don't forget to get rid of the bitmap recreation code in DocumentUpdater
         // don't forget to get rid of the bitmap recreation code in DocumentUpdater
     }
     }
 
 
-    private (Dictionary<Guid, AffectedArea> main, Dictionary<Guid, AffectedArea> mask) GetChunksToRerenderAndResetAccumulator()
+    private (Dictionary<Guid, AffectedArea> main, Dictionary<Guid, AffectedArea> mask)
+        GetChunksToRerenderAndResetAccumulator()
     {
     {
-        var result = (mainPreviewPostponedChunks: mainPreviewAreasAccumulator, maskPreviewPostponedChunks: maskPreviewAreasAccumulator);
+        var result = (mainPreviewPostponedChunks: mainPreviewAreasAccumulator,
+            maskPreviewPostponedChunks: maskPreviewAreasAccumulator);
         mainPreviewAreasAccumulator = new();
         mainPreviewAreasAccumulator = new();
         maskPreviewAreasAccumulator = new();
         maskPreviewAreasAccumulator = new();
         return result;
         return result;
@@ -406,15 +406,16 @@ internal class MemberPreviewUpdater
     /// <summary>
     /// <summary>
     /// Re-renders the preview of the whole canvas which is shown as the tab icon
     /// Re-renders the preview of the whole canvas which is shown as the tab icon
     /// </summary>
     /// </summary>
-    private void RenderWholeCanvasPreview(Dictionary<Guid, AffectedArea> mainPreviewChunks, Dictionary<Guid, AffectedArea> maskPreviewChunks, List<IRenderInfo> infos)
+    private void RenderWholeCanvasPreview(Dictionary<Guid, AffectedArea> mainPreviewChunks,
+        Dictionary<Guid, AffectedArea> maskPreviewChunks, List<IRenderInfo> infos)
     {
     {
         var cumulative = mainPreviewChunks
         var cumulative = mainPreviewChunks
             .Concat(maskPreviewChunks)
             .Concat(maskPreviewChunks)
             .Aggregate(new AffectedArea(), (set, pair) =>
             .Aggregate(new AffectedArea(), (set, pair) =>
-        {
-            set.UnionWith(pair.Value);
-            return set;
-        });
+            {
+                set.UnionWith(pair.Value);
+                return set;
+            });
         if (cumulative.GlobalArea is null)
         if (cumulative.GlobalArea is null)
             return;
             return;
 
 
@@ -433,29 +434,33 @@ internal class MemberPreviewUpdater
                 _ => ChunkResolution.Eighth,
                 _ => ChunkResolution.Eighth,
             };
             };
             var pos = chunkPos * resolution.PixelSize();
             var pos = chunkPos * resolution.PixelSize();
-            var rendered = ChunkRenderer.MergeWholeStructure(chunkPos, resolution, internals.Tracker.Document.StructureRoot, doc.AnimationHandler.ActiveFrameBindable);
+            var rendered = ChunkRenderer.MergeWholeStructure(chunkPos, resolution,
+                internals.Tracker.Document.StructureRoot, doc.AnimationHandler.ActiveFrameBindable);
             doc.PreviewSurface.DrawingSurface.Canvas.Save();
             doc.PreviewSurface.DrawingSurface.Canvas.Save();
             doc.PreviewSurface.DrawingSurface.Canvas.Scale(scaling);
             doc.PreviewSurface.DrawingSurface.Canvas.Scale(scaling);
             doc.PreviewSurface.DrawingSurface.Canvas.ClipRect((RectD)cumulative.GlobalArea);
             doc.PreviewSurface.DrawingSurface.Canvas.ClipRect((RectD)cumulative.GlobalArea);
             doc.PreviewSurface.DrawingSurface.Canvas.Scale(1 / (float)resolution.Multiplier());
             doc.PreviewSurface.DrawingSurface.Canvas.Scale(1 / (float)resolution.Multiplier());
             if (rendered.IsT1)
             if (rendered.IsT1)
             {
             {
-                doc.PreviewSurface.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, resolution.PixelSize(), resolution.PixelSize(), ClearPaint);
+                doc.PreviewSurface.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, resolution.PixelSize(),
+                    resolution.PixelSize(), ClearPaint);
             }
             }
             else if (rendered.IsT0)
             else if (rendered.IsT0)
             {
             {
                 using var renderedChunk = rendered.AsT0;
                 using var renderedChunk = rendered.AsT0;
                 renderedChunk.DrawOnSurface(doc.PreviewSurface.DrawingSurface, pos, SmoothReplacingPaint);
                 renderedChunk.DrawOnSurface(doc.PreviewSurface.DrawingSurface, pos, SmoothReplacingPaint);
             }
             }
+
             doc.PreviewSurface.DrawingSurface.Canvas.Restore();
             doc.PreviewSurface.DrawingSurface.Canvas.Restore();
         }
         }
+
         if (somethingChanged)
         if (somethingChanged)
             infos.Add(new CanvasPreviewDirty_RenderInfo());
             infos.Add(new CanvasPreviewDirty_RenderInfo());
     }
     }
 
 
     private void RenderMainPreviews(
     private void RenderMainPreviews(
-        Dictionary<Guid, AffectedArea> mainPreviewChunks, 
-        Dictionary<Guid, (VecI previewSize, RectI tightBounds)?> recreatedPreviewSizes, 
+        Dictionary<Guid, AffectedArea> mainPreviewChunks,
+        Dictionary<Guid, (VecI previewSize, RectI tightBounds)?> recreatedPreviewSizes,
         List<IRenderInfo> infos)
         List<IRenderInfo> infos)
     {
     {
         foreach (var guid in mainPreviewChunks.Select(a => a.Key).Concat(recreatedPreviewSizes.Select(a => a.Key)))
         foreach (var guid in mainPreviewChunks.Select(a => a.Key).Concat(recreatedPreviewSizes.Select(a => a.Key)))
@@ -463,7 +468,7 @@ internal class MemberPreviewUpdater
             // find the true affected area
             // find the true affected area
             AffectedArea? affArea = null;
             AffectedArea? affArea = null;
             RectI? tightBounds = null;
             RectI? tightBounds = null;
-            
+
             if (mainPreviewChunks.TryGetValue(guid, out AffectedArea areaFromChunks))
             if (mainPreviewChunks.TryGetValue(guid, out AffectedArea areaFromChunks))
                 affArea = areaFromChunks;
                 affArea = areaFromChunks;
 
 
@@ -472,10 +477,13 @@ internal class MemberPreviewUpdater
                 if (value is null)
                 if (value is null)
                     continue;
                     continue;
                 tightBounds = value.Value.tightBounds;
                 tightBounds = value.Value.tightBounds;
-                affArea = new AffectedArea(OperationHelper.FindChunksTouchingRectangle(value.Value.tightBounds, ChunkyImage.FullChunkSize), value.Value.tightBounds);
+                affArea = new AffectedArea(
+                    OperationHelper.FindChunksTouchingRectangle(value.Value.tightBounds, ChunkyImage.FullChunkSize),
+                    value.Value.tightBounds);
             }
             }
 
 
-            if (affArea is null || affArea.Value.GlobalArea is null || affArea.Value.GlobalArea.Value.IsZeroOrNegativeArea)
+            if (affArea is null || affArea.Value.GlobalArea is null ||
+                affArea.Value.GlobalArea.Value.IsZeroOrNegativeArea)
                 continue;
                 continue;
 
 
             // re-render the area
             // re-render the area
@@ -500,12 +508,16 @@ internal class MemberPreviewUpdater
                 {
                 {
                     if (keyFrame is IKeyFrameGroupHandler group)
                     if (keyFrame is IKeyFrameGroupHandler group)
                     {
                     {
-                        var target = group.Children.FirstOrDefault(x => IsWithinRange(x, doc.AnimationHandler.ActiveFrameBindable));
-                        if (target is not null)
-                            RenderAnimationFramePreview(memberVM, target);
+                        foreach (var child in group.Children)
+                        {
+                            if (member is IReadOnlyRasterLayer rasterLayer) 
+                            {
+                                RenderAnimationFramePreview(rasterLayer, child, affArea.Value);
+                            }
+                        }
                     }
                     }
                 }
                 }
-                
+
                 infos.Add(new PreviewDirty_RenderInfo(guid));
                 infos.Add(new PreviewDirty_RenderInfo(guid));
             }
             }
             else if (memberVM is IFolderHandler)
             else if (memberVM is IFolderHandler)
@@ -519,16 +531,12 @@ internal class MemberPreviewUpdater
             }
             }
         }
         }
     }
     }
-    
-    private static bool IsWithinRange(IKeyFrameHandler keyFrame, int frame)
-    {
-        return keyFrame.IsVisible && keyFrame.StartFrameBindable <= frame && frame < keyFrame.StartFrameBindable + keyFrame.DurationBindable;
-    }
 
 
     /// <summary>
     /// <summary>
     /// Re-render the <paramref name="area"/> of the main preview of the <paramref name="memberVM"/> folder
     /// Re-render the <paramref name="area"/> of the main preview of the <paramref name="memberVM"/> folder
     /// </summary>
     /// </summary>
-    private void RenderFolderMainPreview(IReadOnlyFolder folder, IStructureMemberHandler memberVM, AffectedArea area, VecI position, float scaling)
+    private void RenderFolderMainPreview(IReadOnlyFolder folder, IStructureMemberHandler memberVM, AffectedArea area,
+        VecI position, float scaling)
     {
     {
         memberVM.PreviewSurface.DrawingSurface.Canvas.Save();
         memberVM.PreviewSurface.DrawingSurface.Canvas.Save();
         memberVM.PreviewSurface.DrawingSurface.Canvas.Scale(scaling);
         memberVM.PreviewSurface.DrawingSurface.Canvas.Scale(scaling);
@@ -539,24 +547,29 @@ internal class MemberPreviewUpdater
             var pos = chunk * ChunkResolution.Full.PixelSize();
             var pos = chunk * ChunkResolution.Full.PixelSize();
             // drawing in full res here is kinda slow
             // drawing in full res here is kinda slow
             // we could switch to a lower resolution based on (canvas size / preview size) to make it run faster
             // we could switch to a lower resolution based on (canvas size / preview size) to make it run faster
-            OneOf<Chunk, EmptyChunk> rendered = ChunkRenderer.MergeWholeStructure(chunk, ChunkResolution.Full, folder, doc.AnimationHandler.ActiveFrameBindable);
+            OneOf<Chunk, EmptyChunk> rendered = ChunkRenderer.MergeWholeStructure(chunk, ChunkResolution.Full, folder,
+                doc.AnimationHandler.ActiveFrameBindable);
             if (rendered.IsT0)
             if (rendered.IsT0)
             {
             {
-                memberVM.PreviewSurface.DrawingSurface.Canvas.DrawSurface(rendered.AsT0.Surface.DrawingSurface, pos, scaling < smoothingThreshold ? SmoothReplacingPaint : ReplacingPaint);
+                memberVM.PreviewSurface.DrawingSurface.Canvas.DrawSurface(rendered.AsT0.Surface.DrawingSurface, pos,
+                    scaling < smoothingThreshold ? SmoothReplacingPaint : ReplacingPaint);
                 rendered.AsT0.Dispose();
                 rendered.AsT0.Dispose();
             }
             }
             else
             else
             {
             {
-                memberVM.PreviewSurface.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkResolution.Full.PixelSize(), ChunkResolution.Full.PixelSize(), ClearPaint);
+                memberVM.PreviewSurface.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkResolution.Full.PixelSize(),
+                    ChunkResolution.Full.PixelSize(), ClearPaint);
             }
             }
         }
         }
+
         memberVM.PreviewSurface.DrawingSurface.Canvas.Restore();
         memberVM.PreviewSurface.DrawingSurface.Canvas.Restore();
     }
     }
 
 
     /// <summary>
     /// <summary>
     /// Re-render the <paramref name="area"/> of the main preview of the <paramref name="memberVM"/> layer
     /// Re-render the <paramref name="area"/> of the main preview of the <paramref name="memberVM"/> layer
     /// </summary>
     /// </summary>
-    private void RenderLayerMainPreview(IReadOnlyLayer layer, IStructureMemberHandler memberVM, AffectedArea area, VecI position, float scaling)
+    private void RenderLayerMainPreview(IReadOnlyLayer layer, IStructureMemberHandler memberVM, AffectedArea area,
+        VecI position, float scaling)
     {
     {
         memberVM.PreviewSurface.DrawingSurface.Canvas.Save();
         memberVM.PreviewSurface.DrawingSurface.Canvas.Save();
         memberVM.PreviewSurface.DrawingSurface.Canvas.Scale(scaling);
         memberVM.PreviewSurface.DrawingSurface.Canvas.Scale(scaling);
@@ -566,29 +579,43 @@ internal class MemberPreviewUpdater
         foreach (var chunk in area.Chunks)
         foreach (var chunk in area.Chunks)
         {
         {
             var pos = chunk * ChunkResolution.Full.PixelSize();
             var pos = chunk * ChunkResolution.Full.PixelSize();
-            if (!layer.Rasterize(doc.AnimationHandler.ActiveFrameBindable).DrawCommittedChunkOn(chunk, ChunkResolution.Full, memberVM.PreviewSurface.DrawingSurface, pos, scaling < smoothingThreshold ? SmoothReplacingPaint : ReplacingPaint))
-                memberVM.PreviewSurface.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkyImage.FullChunkSize, ChunkyImage.FullChunkSize, ClearPaint);
+            if (!layer.Rasterize(doc.AnimationHandler.ActiveFrameBindable).DrawCommittedChunkOn(chunk,
+                    ChunkResolution.Full, memberVM.PreviewSurface.DrawingSurface, pos,
+                    scaling < smoothingThreshold ? SmoothReplacingPaint : ReplacingPaint))
+                memberVM.PreviewSurface.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkyImage.FullChunkSize,
+                    ChunkyImage.FullChunkSize, ClearPaint);
         }
         }
 
 
         memberVM.PreviewSurface.DrawingSurface.Canvas.Restore();
         memberVM.PreviewSurface.DrawingSurface.Canvas.Restore();
     }
     }
-    
-    private void RenderAnimationFramePreview(IStructureMemberHandler memberVM, IKeyFrameHandler keyFrame)
+
+    private void RenderAnimationFramePreview(IReadOnlyRasterLayer layer, IKeyFrameHandler keyFrameVM, AffectedArea area)
     {
     {
-        if (keyFrame.PreviewSurface is null)
+        if (keyFrameVM.PreviewSurface is null)
         {
         {
-            if(memberVM.PreviewSurface is not null)
-                keyFrame.PreviewSurface = new Surface(memberVM.PreviewSurface.Size);
-            else
-                return;
+            keyFrameVM.PreviewSurface = new Surface(StructureHelpers.CalculatePreviewSize(internals.Tracker.Document.Size));
+        }
+        
+        keyFrameVM.PreviewSurface!.DrawingSurface.Canvas.Save();
+        float scaling = (float)keyFrameVM.PreviewSurface.Size.X / internals.Tracker.Document.Size.X;
+        keyFrameVM.PreviewSurface.DrawingSurface.Canvas.Scale(scaling);
+        foreach (var chunk in area.Chunks)
+        {
+            var pos = chunk * ChunkResolution.Full.PixelSize();
+            if (!layer.GetLayerImageByKeyFrameGuid(keyFrameVM.Id).DrawCommittedChunkOn(chunk, ChunkResolution.Full,
+                    keyFrameVM.PreviewSurface!.DrawingSurface, pos, ReplacingPaint))
+            {
+                keyFrameVM.PreviewSurface!.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkyImage.FullChunkSize,
+                    ChunkyImage.FullChunkSize, ClearPaint);
+            }
         }
         }
 
 
-        keyFrame.PreviewSurface.DrawingSurface.Canvas.DrawSurface(memberVM.PreviewSurface.DrawingSurface, VecI.Zero, ReplacingPaint);
+        keyFrameVM.PreviewSurface!.DrawingSurface.Canvas.Restore();
     }
     }
 
 
     private void RenderMaskPreviews(
     private void RenderMaskPreviews(
         Dictionary<Guid, AffectedArea> maskPreviewChunks,
         Dictionary<Guid, AffectedArea> maskPreviewChunks,
-        Dictionary<Guid, (VecI previewSize, RectI tightBounds)?> recreatedMaskSizes, 
+        Dictionary<Guid, (VecI previewSize, RectI tightBounds)?> recreatedMaskSizes,
         List<IRenderInfo> infos)
         List<IRenderInfo> infos)
     {
     {
         foreach (Guid guid in maskPreviewChunks.Select(a => a.Key).Concat(recreatedMaskSizes.Select(a => a.Key)))
         foreach (Guid guid in maskPreviewChunks.Select(a => a.Key).Concat(recreatedMaskSizes.Select(a => a.Key)))
@@ -605,10 +632,13 @@ internal class MemberPreviewUpdater
                 if (value is null)
                 if (value is null)
                     continue;
                     continue;
                 tightBounds = value.Value.tightBounds;
                 tightBounds = value.Value.tightBounds;
-                affArea = new AffectedArea(OperationHelper.FindChunksTouchingRectangle(value.Value.tightBounds, ChunkyImage.FullChunkSize), value.Value.tightBounds);
+                affArea = new AffectedArea(
+                    OperationHelper.FindChunksTouchingRectangle(value.Value.tightBounds, ChunkyImage.FullChunkSize),
+                    value.Value.tightBounds);
             }
             }
 
 
-            if (affArea is null || affArea.Value.GlobalArea is null || affArea.Value.GlobalArea.Value.IsZeroOrNegativeArea)
+            if (affArea is null || affArea.Value.GlobalArea is null ||
+                affArea.Value.GlobalArea.Value.IsZeroOrNegativeArea)
                 continue;
                 continue;
 
 
             // re-render the area
             // re-render the area
@@ -634,7 +664,8 @@ internal class MemberPreviewUpdater
             {
             {
                 var pos = chunk * ChunkResolution.Full.PixelSize();
                 var pos = chunk * ChunkResolution.Full.PixelSize();
                 member.Mask!.DrawMostUpToDateChunkOn
                 member.Mask!.DrawMostUpToDateChunkOn
-                    (chunk, ChunkResolution.Full, memberVM.MaskPreviewSurface.DrawingSurface, pos, scaling < smoothingThreshold ? SmoothReplacingPaint : ReplacingPaint);
+                (chunk, ChunkResolution.Full, memberVM.MaskPreviewSurface.DrawingSurface, pos,
+                    scaling < smoothingThreshold ? SmoothReplacingPaint : ReplacingPaint);
             }
             }
 
 
             memberVM.MaskPreviewSurface.DrawingSurface.Canvas.Restore();
             memberVM.MaskPreviewSurface.DrawingSurface.Canvas.Restore();

+ 1 - 1
src/PixiEditor.AvaloniaUI/Styles/Templates/KeyFrame.axaml

@@ -31,7 +31,7 @@
                         </Border.Background>
                         </Border.Background>
                         <visuals:SurfaceControl
                         <visuals:SurfaceControl
                             Surface="{Binding Item.PreviewSurface, RelativeSource={RelativeSource TemplatedParent}}"
                             Surface="{Binding Item.PreviewSurface, RelativeSource={RelativeSource TemplatedParent}}"
-                            Stretch="Uniform" Width="30" Height="30">
+                            Stretch="Uniform" Width="60" Height="60">
                             <ui:RenderOptionsBindable.BitmapInterpolationMode>
                             <ui:RenderOptionsBindable.BitmapInterpolationMode>
                                 <MultiBinding Converter="{converters:WidthToBitmapScalingModeConverter}">
                                 <MultiBinding Converter="{converters:WidthToBitmapScalingModeConverter}">
                                     <Binding Path="Item.PreviewSurface.Size.X"
                                     <Binding Path="Item.PreviewSurface.Size.X"

+ 2 - 0
src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs

@@ -14,7 +14,9 @@ using PixiEditor.AvaloniaUI.Helpers.Extensions;
 using PixiEditor.AvaloniaUI.Models.Controllers;
 using PixiEditor.AvaloniaUI.Models.Controllers;
 using PixiEditor.AvaloniaUI.Models.DocumentModels;
 using PixiEditor.AvaloniaUI.Models.DocumentModels;
 using PixiEditor.AvaloniaUI.Models.DocumentModels.Public;
 using PixiEditor.AvaloniaUI.Models.DocumentModels.Public;
+using PixiEditor.AvaloniaUI.Models.DocumentPassthroughActions;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.Models.Handlers;
+using PixiEditor.AvaloniaUI.Models.Position;
 using PixiEditor.AvaloniaUI.Models.Structures;
 using PixiEditor.AvaloniaUI.Models.Structures;
 using PixiEditor.AvaloniaUI.Models.Tools;
 using PixiEditor.AvaloniaUI.Models.Tools;
 using PixiEditor.AvaloniaUI.ViewModels.Document.TransformOverlays;
 using PixiEditor.AvaloniaUI.ViewModels.Document.TransformOverlays;

+ 2 - 0
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyRasterLayer.cs

@@ -6,6 +6,8 @@ public interface IReadOnlyRasterLayer : ITransparencyLockable
     /// The chunky image of the layer
     /// The chunky image of the layer
     /// </summary>
     /// </summary>
     IReadOnlyChunkyImage GetLayerImageAtFrame(int frame);
     IReadOnlyChunkyImage GetLayerImageAtFrame(int frame);
+
+    public IReadOnlyChunkyImage GetLayerImageByKeyFrameGuid(Guid keyFrameGuid);
     void SetLayerImageAtFrame(int frame, IReadOnlyChunkyImage image);
     void SetLayerImageAtFrame(int frame, IReadOnlyChunkyImage image);
     public void ForEveryFrame(Action<IReadOnlyChunkyImage> action);
     public void ForEveryFrame(Action<IReadOnlyChunkyImage> action);
 }
 }

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changeables/RasterLayer.cs

@@ -36,6 +36,7 @@ internal class RasterLayer : Layer, IReadOnlyRasterLayer
     }
     }
 
 
     IReadOnlyChunkyImage IReadOnlyRasterLayer.GetLayerImageAtFrame(int frame) => GetLayerImageAtFrame(frame);
     IReadOnlyChunkyImage IReadOnlyRasterLayer.GetLayerImageAtFrame(int frame) => GetLayerImageAtFrame(frame);
+    IReadOnlyChunkyImage IReadOnlyRasterLayer.GetLayerImageByKeyFrameGuid(Guid keyFrameGuid) => GetLayerImageByKeyFrameGuid(keyFrameGuid);
     void IReadOnlyRasterLayer.SetLayerImageAtFrame(int frame, IReadOnlyChunkyImage newLayerImage) => SetLayerImageAtFrame(frame, (ChunkyImage)newLayerImage);
     void IReadOnlyRasterLayer.SetLayerImageAtFrame(int frame, IReadOnlyChunkyImage newLayerImage) => SetLayerImageAtFrame(frame, (ChunkyImage)newLayerImage);
 
 
     void IReadOnlyRasterLayer.ForEveryFrame(Action<IReadOnlyChunkyImage> action) => ForEveryFrame(action);
     void IReadOnlyRasterLayer.ForEveryFrame(Action<IReadOnlyChunkyImage> action) => ForEveryFrame(action);