Browse Source

unfinished rework without requirement to pass active frame to backend

flabbet 1 year ago
parent
commit
967885f65b
35 changed files with 144 additions and 325 deletions
  1. 0 15
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/ChangeExecutionController.cs
  2. 0 3
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/Public/DocumentEventsModule.cs
  3. 2 11
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/Public/DocumentOperationsModule.cs
  4. 0 1
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/Public/DocumentToolsModule.cs
  5. 0 40
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/AnimationFrameExecutor.cs
  6. 3 3
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/ColorPickerToolExecutor.cs
  7. 0 3
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/UpdateableChangeExecutor.cs
  8. 6 0
      src/PixiEditor.AvaloniaUI/Models/DocumentPassthroughActions/SetActiveFrame_PassthroughAction.cs
  9. 1 1
      src/PixiEditor.AvaloniaUI/Models/Files/ImageFileType.cs
  10. 1 1
      src/PixiEditor.AvaloniaUI/Models/Handlers/IDocument.cs
  11. 2 3
      src/PixiEditor.AvaloniaUI/ViewModels/Document/AnimationDataViewModel.cs
  12. 2 2
      src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.Serialization.cs
  13. 12 41
      src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs
  14. 0 1
      src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/AnimationsViewModel.cs
  15. 1 1
      src/PixiEditor.AvaloniaUI/Views/Dialogs/ExportFilePopup.axaml.cs
  16. 1 1
      src/PixiEditor.AvaloniaUI/Views/Main/Navigation.axaml.cs
  17. 0 77
      src/PixiEditor.ChangeableDocument/Changeables/Animations/AnimationData.cs
  18. 0 9
      src/PixiEditor.ChangeableDocument/Changeables/Animations/GroupKeyFrame.cs
  19. 0 3
      src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrame.cs
  20. 15 0
      src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrameTime.cs
  21. 0 8
      src/PixiEditor.ChangeableDocument/Changeables/Animations/RasterKeyFrame.cs
  22. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Document.cs
  23. 7 0
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IChunkyImageEvaluable.cs
  24. 6 0
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IKeyFrameProperty.cs
  25. 0 1
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyAnimationData.cs
  26. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs
  27. 0 3
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyKeyFrame.cs
  28. 3 2
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyLayer.cs
  29. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyRasterLayer.cs
  30. 3 2
      src/PixiEditor.ChangeableDocument/Changeables/Layer.cs
  31. 44 6
      src/PixiEditor.ChangeableDocument/Changeables/RasterLayer.cs
  32. 0 60
      src/PixiEditor.ChangeableDocument/Changes/Animation/ActiveFrame_UpdateableChange.cs
  33. 4 2
      src/PixiEditor.ChangeableDocument/Changes/Drawing/CombineStructureMembersOnto_Change.cs
  34. 6 4
      src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillChunkCache.cs
  35. 21 17
      src/PixiEditor.ChangeableDocument/Rendering/ChunkRenderer.cs

+ 0 - 15
src/PixiEditor.AvaloniaUI/Models/DocumentModels/ChangeExecutionController.cs

@@ -200,19 +200,4 @@ internal class ChangeExecutionController
     {
         currentSession?.OnSelectedObjectNudged(distance);
     }
-
-    public void ActiveAnimationFrameStartedInlet()
-    {
-        currentSession?.OnActiveAnimationFrameStarted();
-    }
-    
-    public void ActiveAnimationFrameChangedInlet(int newFrame)
-    {
-        currentSession?.ActiveFrameChanged(newFrame);
-    }
-    
-    public void ActiveAnimationFrameEndedInlet()
-    {
-        currentSession?.OnActiveAnimationFrameEnded();
-    }
 }

+ 0 - 3
src/PixiEditor.AvaloniaUI/Models/DocumentModels/Public/DocumentEventsModule.cs

@@ -46,7 +46,4 @@ internal class DocumentEventsModule
     public void OnSymmetryDragStarted(SymmetryAxisDirection dir) => Internals.ChangeController.SymmetryDragStartedInlet(dir);
     public void OnSymmetryDragged(SymmetryAxisDragInfo info) => Internals.ChangeController.SymmetryDraggedInlet(info);
     public void OnSymmetryDragEnded(SymmetryAxisDirection dir) => Internals.ChangeController.SymmetryDragEndedInlet(dir);
-    public void StartChangeActiveFrame() => Internals.ChangeController.ActiveAnimationFrameStartedInlet();
-    public void ChangeActiveFrame(int activeFrame) => Internals.ChangeController.ActiveAnimationFrameChangedInlet(activeFrame);
-    public void EndChangeActiveFrame() => Internals.ChangeController.ActiveAnimationFrameEndedInlet();
 }

+ 2 - 11
src/PixiEditor.AvaloniaUI/Models/DocumentModels/Public/DocumentOperationsModule.cs

@@ -319,6 +319,7 @@ internal class DocumentOperationsModule : IDocumentOperations
     /// </summary>
     public void ClearSoftSelectedMembers() => Internals.ActionAccumulator.AddActions(new ClearSoftSelectedMembers_PassthroughAction());
     
+    public void SetActiveFrame(int newFrame) => Internals.ActionAccumulator.AddActions(new SetActiveFrame_PassthroughAction(newFrame));
     public void AddSelectedKeyFrame(Guid keyFrameGuid) => Internals.ActionAccumulator.AddActions(new AddSelectedKeyFrame_PassthroughAction(keyFrameGuid));
     
     public void RemoveSelectedKeyFrame(Guid keyFrameGuid) => Internals.ActionAccumulator.AddActions(new RemoveSelectedKeyFrame_PassthroughAction(keyFrameGuid));
@@ -389,7 +390,7 @@ internal class DocumentOperationsModule : IDocumentOperations
         Internals.ActionAccumulator.AddActions(
             new CreateStructureMember_Action(parent.GuidValue, newGuid, index, StructureMemberType.Layer),
             new StructureMemberName_Action(newGuid, child.NameBindable),
-            new CombineStructureMembersOnto_Action(members.ToHashSet(), newGuid));
+            new CombineStructureMembersOnto_Action(members.ToHashSet(), newGuid, Document.AnimationHandler.ActiveFrameBindable));
         foreach (var member in members)
             Internals.ActionAccumulator.AddActions(new DeleteStructureMember_Action(member));
         Internals.ActionAccumulator.AddActions(new ChangeBoundary_Action());
@@ -610,14 +611,4 @@ internal class DocumentOperationsModule : IDocumentOperations
 
         Internals.ActionAccumulator.AddFinishedActions(new SetSelection_Action(inverse.Op(selection, VectorPathOp.Difference)));
     }
-
-    public void SetActiveFrame(int value)
-    {
-        if (Internals.ChangeController.IsChangeActive || value is < 0)
-            return;
-        
-        Internals.ActionAccumulator.AddFinishedActions(
-            new ActiveFrame_Action(value),
-            new EndActiveFrame_Action());
-    }
 }

+ 0 - 1
src/PixiEditor.AvaloniaUI/Models/DocumentModels/Public/DocumentToolsModule.cs

@@ -16,7 +16,6 @@ internal class DocumentToolsModule
     }
 
     public void UseSymmetry(SymmetryAxisDirection dir) => Internals.ChangeController.TryStartExecutor(new SymmetryExecutor(dir));
-    public void UseActiveFrame(int activeFrame) => Internals.ChangeController.TryStartExecutor(new AnimationFrameExecutor(activeFrame));
 
     public void UseOpacitySlider() => Internals.ChangeController.TryStartExecutor<StructureMemberOpacityExecutor>();
 

+ 0 - 40
src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/AnimationFrameExecutor.cs

@@ -1,40 +0,0 @@
-using PixiEditor.AvaloniaUI.Models.Tools;
-using PixiEditor.ChangeableDocument.Actions.Generated;
-using PixiEditor.ChangeableDocument.ChangeInfos.Animation;
-
-namespace PixiEditor.AvaloniaUI.Models.DocumentModels.UpdateableChangeExecutors;
-
-internal class AnimationFrameExecutor : UpdateableChangeExecutor
-{
-    private readonly int activeFrame;
-    
-    public AnimationFrameExecutor(int activeFrame)
-    {
-        this.activeFrame = activeFrame;
-    }
-    
-    public override ExecutionState Start()
-    {
-        internals.ActionAccumulator.AddActions(new ActiveFrame_Action(activeFrame));
-        
-        return ExecutionState.Success;
-    }
-
-    public override void ActiveFrameChanged(int newActiveFrame)
-    {
-        if (newActiveFrame == activeFrame)
-            return;
-        internals.ActionAccumulator.AddActions(new ActiveFrame_Action(newActiveFrame));
-    }
-
-    public override void OnActiveAnimationFrameEnded()
-    {
-        internals.ActionAccumulator.AddFinishedActions(new EndActiveFrame_Action());
-        onEnded?.Invoke(this);
-    }
-
-    public override void ForceStop()
-    {
-        internals.ActionAccumulator.AddFinishedActions(new EndActiveFrame_Action());
-    }
-}

+ 3 - 3
src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/ColorPickerToolExecutor.cs

@@ -26,7 +26,7 @@ internal class ColorPickerToolExecutor : UpdateableChangeExecutor
         includeReference = tool.PickFromReferenceLayer && document!.ReferenceLayerHandler.ReferenceBitmap is not null;
         includeCanvas = tool.PickFromCanvas;
         
-        colorsViewModel.PrimaryColor = document.PickColor(controller.LastPrecisePosition, scope, includeReference, includeCanvas, document.ReferenceLayerHandler.IsTopMost);
+        colorsViewModel.PrimaryColor = document.PickColor(controller.LastPrecisePosition, scope, includeReference, includeCanvas, document.AnimationHandler.ActiveFrameBindable, document.ReferenceLayerHandler.IsTopMost);
         return ExecutionState.Success;
     }
 
@@ -34,12 +34,12 @@ internal class ColorPickerToolExecutor : UpdateableChangeExecutor
     {
         if (!includeReference)
             return;
-        colorsViewModel.PrimaryColor = document.PickColor(pos, scope, includeReference, includeCanvas, document.ReferenceLayerHandler.IsTopMost);
+        colorsViewModel.PrimaryColor = document.PickColor(pos, scope, includeReference, includeCanvas, document.AnimationHandler.ActiveFrameBindable, document.ReferenceLayerHandler.IsTopMost);
     }
 
     public override void OnPixelPositionChange(VecI pos)
     {
-        colorsViewModel.PrimaryColor = document.PickColor(pos, scope, includeReference, includeCanvas, document.ReferenceLayerHandler.IsTopMost);
+        colorsViewModel.PrimaryColor = document.PickColor(pos, scope, includeReference, includeCanvas, document.AnimationHandler.ActiveFrameBindable, document.ReferenceLayerHandler.IsTopMost);
     }
 
     public override void OnLeftMouseButtonUp()

+ 0 - 3
src/PixiEditor.AvaloniaUI/Models/DocumentModels/UpdateableChangeExecutors/UpdateableChangeExecutor.cs

@@ -62,7 +62,4 @@ internal abstract class UpdateableChangeExecutor
     public virtual void OnMidChangeUndo() { }
     public virtual void OnMidChangeRedo() { }
     public virtual void OnSelectedObjectNudged(VecI distance) { }
-    public virtual void OnActiveAnimationFrameStarted() { }
-    public virtual void ActiveFrameChanged(int newActiveFrame) { }
-    public virtual void OnActiveAnimationFrameEnded() { }
 }

+ 6 - 0
src/PixiEditor.AvaloniaUI/Models/DocumentPassthroughActions/SetActiveFrame_PassthroughAction.cs

@@ -0,0 +1,6 @@
+using PixiEditor.ChangeableDocument.Actions;
+using PixiEditor.ChangeableDocument.ChangeInfos;
+
+namespace PixiEditor.AvaloniaUI.Models.DocumentPassthroughActions;
+
+internal record SetActiveFrame_PassthroughAction(int Frame) : IChangeInfo, IAction;

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/Files/ImageFileType.cs

@@ -30,7 +30,7 @@ internal abstract class ImageFileType : IoFileType
         }
         else
         {
-            var maybeBitmap = document.TryRenderWholeImage();
+            var maybeBitmap = document.TryRenderWholeImage(0);
             if (maybeBitmap.IsT0)
                 return SaveResult.ConcurrencyError;
             

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/Handlers/IDocument.cs

@@ -50,7 +50,7 @@ internal interface IDocument : IHandler
     public void SetVerticalSymmetryAxisEnabled(bool infoState);
     public void UpdateSelectionPath(VectorPath infoNewPath);
     public void SetSize(VecI infoSize);
-    public Color PickColor(VecD controllerLastPrecisePosition, DocumentScope scope, bool includeReference, bool includeCanvas, bool isTopMost);
+    public Color PickColor(VecD controllerLastPrecisePosition, DocumentScope scope, bool includeReference, bool includeCanvas, int frame, bool isTopMost);
     public List<Guid> ExtractSelectedLayers(bool includeFoldersWithMask = false);
     public void UpdateSavedState();
 }

+ 2 - 3
src/PixiEditor.AvaloniaUI/ViewModels/Document/AnimationDataViewModel.cs

@@ -3,6 +3,7 @@ using System.Collections.Specialized;
 using CommunityToolkit.Mvvm.ComponentModel;
 using PixiEditor.AnimationRenderer.Core;
 using PixiEditor.AvaloniaUI.Models.DocumentModels;
+using PixiEditor.AvaloniaUI.Models.DocumentPassthroughActions;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
@@ -27,9 +28,7 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
             if (Document.UpdateableChangeActive)
                 return;
 
-            Internals.ActionAccumulator.AddFinishedActions(
-                new ActiveFrame_Action(value),
-                new EndActiveFrame_Action());
+            Internals.ActionAccumulator.AddActions(new SetActiveFrame_PassthroughAction(value));
         }
     }
 

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

@@ -36,7 +36,7 @@ internal partial class DocumentViewModel
         {
             Width = Width, Height = Height,
             Swatches = ToCollection(Swatches), Palette = ToCollection(Palette),
-            RootFolder = root, PreviewImage = (TryRenderWholeImage().Value as Surface)?.DrawingSurface.Snapshot().Encode().AsSpan().ToArray(),
+            RootFolder = root, PreviewImage = (TryRenderWholeImage(AnimationDataViewModel.ActiveFrameBindable).Value as Surface)?.DrawingSurface.Snapshot().Encode().AsSpan().ToArray(),
             ReferenceLayer = GetReferenceLayer(doc),
             AnimationData = ToAnimationData(doc.AnimationData)
         };
@@ -118,7 +118,7 @@ internal partial class DocumentViewModel
     
     private static ImageLayer ToSerializable(IReadOnlyLayer layer, IReadOnlyDocument document)
     {
-        var result = document.GetLayerRasterizedImage(layer.GuidValue);
+        var result = document.GetLayerRasterizedImage(layer.GuidValue, 0);
 
         var tightBounds = document.GetChunkAlignedLayerBounds(layer.GuidValue);
         using var data = result?.DrawingSurface.Snapshot().Encode();

+ 12 - 41
src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs

@@ -362,9 +362,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
                             rasterKeyFrameBuilder.StartFrame,
                             false),
                         new KeyFrameLength_Action(rasterKeyFrameBuilder.Id, rasterKeyFrameBuilder.StartFrame, rasterKeyFrameBuilder.Duration),
-                        new EndKeyFrameLength_Action(),
-                        new ActiveFrame_Action(rasterKeyFrameBuilder.StartFrame + rasterKeyFrameBuilder.Duration),
-                        new EndActiveFrame_Action());
+                        new EndKeyFrameLength_Action());
                     
                     PasteImage(rasterKeyFrameBuilder.LayerGuid, rasterKeyFrameBuilder.Surface, rasterKeyFrameBuilder.Surface.Surface.Size.X,
                         rasterKeyFrameBuilder.Surface.Surface.Size.Y, 0, 0, false);
@@ -396,7 +394,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     /// Tries rendering the whole document
     /// </summary>
     /// <returns><see cref="Error"/> if the ChunkyImage was disposed, otherwise a <see cref="Surface"/> of the rendered document</returns>
-    public OneOf<Error, Surface> TryRenderWholeImage()
+    public OneOf<Error, Surface> TryRenderWholeImage(int frame)
     {
         try
         {
@@ -407,7 +405,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
                 for (int j = 0; j < sizeInChunks.Y; j++)
                 {
                     var maybeChunk = ChunkRenderer.MergeWholeStructure(new(i, j), ChunkResolution.Full,
-                        Internals.Tracker.Document.StructureRoot);
+                        Internals.Tracker.Document.StructureRoot, frame);
                     if (maybeChunk.IsT1)
                         continue;
                     using Chunk chunk = maybeChunk.AsT0;
@@ -487,7 +485,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     /// <param name="includeReference">Should the color be picked from the reference layer</param>
     /// <param name="includeCanvas">Should the color be picked from the canvas</param>
     /// <param name="referenceTopmost">Is the reference layer topmost. (Only affects the result is includeReference and includeCanvas are set.)</param>
-    public Color PickColor(VecD pos, DocumentScope scope, bool includeReference, bool includeCanvas,
+    public Color PickColor(VecD pos, DocumentScope scope, bool includeReference, bool includeCanvas, int frame,
         bool referenceTopmost = false)
     {
         if (scope == DocumentScope.SingleLayer && includeReference && includeCanvas)
@@ -495,7 +493,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
         if (includeCanvas && includeReference)
         {
-            Color canvasColor = PickColorFromCanvas((VecI)pos, scope);
+            Color canvasColor = PickColorFromCanvas((VecI)pos, scope, frame);
             Color? potentialReferenceColor = PickColorFromReferenceLayer(pos);
             if (potentialReferenceColor is not { } referenceColor)
                 return canvasColor;
@@ -514,7 +512,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         }
 
         if (includeCanvas)
-            return PickColorFromCanvas((VecI)pos, scope);
+            return PickColorFromCanvas((VecI)pos, scope, frame);
         if (includeReference)
             return PickColorFromReferenceLayer(pos) ?? Colors.Transparent;
         return Colors.Transparent;
@@ -536,7 +534,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         return bitmap.GetSRGBPixel(new VecI((int)transformed.X, (int)transformed.Y));
     }
 
-    public Color PickColorFromCanvas(VecI pos, DocumentScope scope)
+    public Color PickColorFromCanvas(VecI pos, DocumentScope scope, int frame)
     {
         // there is a tiny chance that the image might get disposed by another thread
         try
@@ -547,7 +545,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             {
                 VecI chunkPos = OperationHelper.GetChunkPos(pos, ChunkyImage.FullChunkSize);
                 return ChunkRenderer.MergeWholeStructure(chunkPos, ChunkResolution.Full,
-                        Internals.Tracker.Document.StructureRoot, new RectI(pos, VecI.One))
+                        Internals.Tracker.Document.StructureRoot, frame, new RectI(pos, VecI.One))
                     .Match<Color>(
                         (Chunk chunk) =>
                         {
@@ -565,7 +563,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             IReadOnlyStructureMember? maybeMember = Internals.Tracker.Document.FindMember(layerVm.GuidValue);
             if (maybeMember is not IReadOnlyLayer layer)
                 return Colors.Transparent;
-            return layer.Rasterize().GetMostUpToDatePixel(pos);
+            return layer.Rasterize(frame).GetMostUpToDatePixel(pos);
         }
         catch (ObjectDisposedException)
         {
@@ -738,11 +736,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         Image[] images = new Image[framesCount];
         for (int i = firstFrame; i < lastFrame; i++)
         {
-            Internals.Tracker.ProcessActionsSync(new List<IAction>
-            {
-                new ActiveFrame_Action(i), new EndActiveFrame_Action()
-            });
-            var surface = TryRenderWholeImage();
+            var surface = TryRenderWholeImage(i);
             if (surface.IsT0)
             {
                 continue;
@@ -757,10 +751,6 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             surface.AsT1.Dispose();
         }
 
-        Internals.Tracker.ProcessActionsSync(new List<IAction>
-        {
-            new ActiveFrame_Action(activeFrame), new EndActiveFrame_Action()
-        });
         return images;
     }
 
@@ -781,11 +771,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
         for (int i = firstFrame; i < lastFrame; i++)
         {
-            Internals.Tracker.ProcessActionsSync(new List<IAction>
-            {
-                new ActiveFrame_Action(i), new EndActiveFrame_Action()
-            });
-            var surface = TryRenderWholeImage();
+            var surface = TryRenderWholeImage(i);
             if (surface.IsT0)
             {
                 continue;
@@ -794,11 +780,6 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             processFrameAction(surface.AsT1, i - firstFrame);
             surface.AsT1.Dispose();
         }
-
-        Internals.Tracker.ProcessActionsSync(new List<IAction>
-        {
-            new ActiveFrame_Action(activeFrame), new EndActiveFrame_Action()
-        });
     }
 
     public bool RenderFrames(string tempRenderingPath, Func<Surface, Surface> processFrameAction = null)
@@ -819,15 +800,9 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         var firstFrame = keyFrames.Min(x => x.StartFrameBindable);
         var lastFrame = keyFrames.Max(x => x.StartFrameBindable + x.DurationBindable);
 
-        int activeFrame = AnimationDataViewModel.ActiveFrameBindable;
-
         for (int i = firstFrame; i < lastFrame; i++)
         {
-            Internals.Tracker.ProcessActionsSync(new List<IAction>
-            {
-                new ActiveFrame_Action(i), new EndActiveFrame_Action()
-            });
-            var surface = TryRenderWholeImage();
+            var surface = TryRenderWholeImage(i);
             if (surface.IsT0)
             {
                 return false;
@@ -843,10 +818,6 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             stream.Position = 0;
         }
 
-        Internals.Tracker.ProcessActionsSync(new List<IAction>
-        {
-            new ActiveFrame_Action(activeFrame), new EndActiveFrame_Action()
-        });
         return true;
     }
 

+ 0 - 1
src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/AnimationsViewModel.cs

@@ -98,7 +98,6 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
             return;
         
         Owner.DocumentManagerSubViewModel.ActiveDocument.EventInlet.StartChangeActiveFrame();
-        Owner.DocumentManagerSubViewModel.ActiveDocument.Tools.UseActiveFrame(newActiveFrame);
     }
     
     [Command.Internal("PixiEditor.Document.ChangeActiveFrame", CanExecute = "PixiEditor.HasDocument")]

+ 1 - 1
src/PixiEditor.AvaloniaUI/Views/Dialogs/ExportFilePopup.axaml.cs

@@ -250,7 +250,7 @@ internal partial class ExportFilePopup : PixiEditorPopup
         }
         else
         {
-            var rendered = document.TryRenderWholeImage();
+            var rendered = document.TryRenderWholeImage(0);
             if (rendered.IsT1)
             {
                 VecI previewSize = CalculatePreviewSize(rendered.AsT1.Size);

+ 1 - 1
src/PixiEditor.AvaloniaUI/Views/Main/Navigation.axaml.cs

@@ -159,7 +159,7 @@ internal partial class Navigation : UserControl
 
         ColorCursorPosition = newPos;
 
-        var color = Document.PickColor(new(x, y), DocumentScope.AllLayers, false, true);
+        var color = Document.PickColor(new(x, y), DocumentScope.AllLayers, false, true, Document.AnimationDataViewModel.ActiveFrameBindable);
         ColorCursorColor = Color.FromArgb(color.A, color.R, color.G, color.B);
     }
 }

+ 0 - 77
src/PixiEditor.ChangeableDocument/Changeables/Animations/AnimationData.cs

@@ -4,24 +4,10 @@ namespace PixiEditor.ChangeableDocument.Changeables.Animations;
 
 internal class AnimationData : IReadOnlyAnimationData
 {
-    private int _activeFrame;
-
-    public int ActiveFrame
-    {
-        get => _activeFrame;
-        set
-        {
-            _activeFrame = value < 0 ? 0 : value;
-
-            OnPreviewFrameChanged();
-        }
-    }
-
     public IReadOnlyList<IReadOnlyKeyFrame> KeyFrames => keyFrames;
 
     private List<KeyFrame> keyFrames = new List<KeyFrame>();
     private readonly Document document;
-    private List<KeyFrame> lastActiveKeyFrames = new List<KeyFrame>();
     
     public AnimationData(Document document)
     {
@@ -41,21 +27,14 @@ internal class AnimationData : IReadOnlyAnimationData
             createdGroup.Children.Add(keyFrame);
             keyFrames.Add(createdGroup);
         }
-        
-        keyFrame.KeyFrameChanged += KeyFrameChanged;
-        
-        UpdateKeyFrames(keyFrames);
     }
 
     public void RemoveKeyFrame(Guid createdKeyFrameId)
     {
         TryFindKeyFrameCallback<KeyFrame>(createdKeyFrameId, out _, (frame, parent) =>
         {
-            frame.KeyFrameChanged -= KeyFrameChanged;
             parent?.Children.Remove(frame);
         });
-        
-        UpdateKeyFrames(keyFrames);
     }
 
     public bool TryFindKeyFrame<T>(Guid id, out T keyFrame) where T : IReadOnlyKeyFrame
@@ -63,11 +42,6 @@ internal class AnimationData : IReadOnlyAnimationData
         return TryFindKeyFrameCallback(id, out keyFrame, null);
     }
     
-    private void KeyFrameChanged()
-    {
-        UpdateKeyFrames(keyFrames);
-    }
-
     private bool TryFindKeyFrameCallback<T>(Guid id, out T? foundKeyFrame,
         Action<KeyFrame, GroupKeyFrame?> onFound = null) where T : IReadOnlyKeyFrame
     {
@@ -100,55 +74,4 @@ internal class AnimationData : IReadOnlyAnimationData
         result = default;
         return false;
     }
-
-    private void OnPreviewFrameChanged()
-    {
-        if (KeyFrames == null)
-        {
-            return;
-        }
-
-        UpdateKeyFrames(keyFrames);
-    }
-
-    private void UpdateKeyFrames(List<KeyFrame> root)
-    {
-        foreach (var keyFrame in root)
-        {
-            if (!keyFrame.IsVisible)
-            {
-                if (lastActiveKeyFrames.Contains(keyFrame))
-                {
-                    keyFrame.Deactivated(ActiveFrame);
-                    lastActiveKeyFrames.Remove(keyFrame);
-                }
-            }
-            else
-            {
-                bool isWithinRange = keyFrame.IsWithinRange(ActiveFrame);
-                if (lastActiveKeyFrames.Contains(keyFrame))
-                {
-                    if (!isWithinRange)
-                    {
-                        keyFrame.Deactivated(ActiveFrame);
-                        lastActiveKeyFrames.Remove(keyFrame);
-                    }
-                    else
-                    {
-                        keyFrame.ActiveFrameChanged(ActiveFrame);
-                    }
-                }
-                else if (isWithinRange)
-                {
-                    keyFrame.ActiveFrameChanged(ActiveFrame);
-                    lastActiveKeyFrames.Add(keyFrame);
-                }
-            }
-
-            if (keyFrame is GroupKeyFrame group)
-            {
-                UpdateKeyFrames(group.Children);
-            }
-        }
-    }
 }

+ 0 - 9
src/PixiEditor.ChangeableDocument/Changeables/Animations/GroupKeyFrame.cs

@@ -33,15 +33,6 @@ internal class GroupKeyFrame : KeyFrame, IKeyFrameChildrenContainer
         }
     }
 
-    public override void Deactivated(int atFrame)
-    {
-        //if(atFrame >= EndFrame) return;
-        if (document.TryFindMember<RasterLayer>(LayerGuid, out var layer))
-        {
-           layer.LayerImage = originalLayerImage;
-        }
-    }
-
     public override bool IsWithinRange(int frame)
     {
         return frame >= StartFrame && frame < EndFrame + 1;

+ 0 - 3
src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrame.cs

@@ -64,9 +64,6 @@ public abstract class KeyFrame : IReadOnlyKeyFrame, IDisposable
         Id = Guid.NewGuid();
     }
     
-    public virtual void ActiveFrameChanged(int atFrame) { }
-    public virtual void Deactivated(int atFrame) { }
-
     public virtual bool IsWithinRange(int frame)
     {
         return frame >= StartFrame && frame < EndFrame;

+ 15 - 0
src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrameTime.cs

@@ -0,0 +1,15 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Animations;
+
+public record KeyFrameTime
+{
+    public KeyFrameTime(int Frame, float NormalizedTime)
+    {
+        this.Frame = Frame;
+        this.NormalizedTime = NormalizedTime;
+    }
+
+    public int Frame { get; init; }
+    public float NormalizedTime { get; init; }
+    
+    public static implicit operator KeyFrameTime(int frame) => new KeyFrameTime(frame, 0);
+}

+ 0 - 8
src/PixiEditor.ChangeableDocument/Changeables/Animations/RasterKeyFrame.cs

@@ -17,14 +17,6 @@ internal class RasterKeyFrame : KeyFrame, IReadOnlyRasterKeyFrame
         Document = document;
     }
     
-    public override void ActiveFrameChanged(int atFrame)
-    {
-        if (Document.TryFindMember<RasterLayer>(LayerGuid, out var layer))
-        {
-            layer.LayerImage = Image;
-        }
-    }
-
     public override KeyFrame Clone()
     {
         var image = Image.CloneFromCommitted();

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

@@ -53,7 +53,7 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     /// <remarks>So yeah, welcome folks to the multithreaded world, where possibilities are endless! (and chances of objects getting
     /// edited, in between of processing you want to make exist). You might encounter ObjectDisposedException and other mighty creatures here if
     /// you are lucky enough. Have fun!</remarks>
-    public Surface? GetLayerRasterizedImage(Guid layerGuid)
+    public Surface? GetLayerRasterizedImage(Guid layerGuid, int frame)
     {
         var layer = (IReadOnlyLayer?)FindMember(layerGuid);
 
@@ -70,7 +70,7 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
 
         Surface surface = new Surface(tightBounds.Value.Size);
 
-        layer.Rasterize().DrawMostUpToDateRegionOn(
+        layer.Rasterize(frame).DrawMostUpToDateRegionOn(
             tightBounds.Value,
             ChunkResolution.Full,
             surface.DrawingSurface, VecI.Zero);

+ 7 - 0
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IChunkyImageEvaluable.cs

@@ -0,0 +1,7 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
+
+public interface IChunkyImageProperty : IKeyFrameProperty
+{
+    public IReadOnlyChunkyImage LayerImage { get; }
+}
+

+ 6 - 0
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IKeyFrameProperty.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
+
+public interface IKeyFrameProperty
+{
+    
+}

+ 0 - 1
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyAnimationData.cs

@@ -2,7 +2,6 @@
 
 public interface IReadOnlyAnimationData
 {
-    public int ActiveFrame { get; }
     public IReadOnlyList<IReadOnlyKeyFrame> KeyFrames { get; }
     public bool TryFindKeyFrame<T>(Guid id, out T keyFrame) where T : IReadOnlyKeyFrame;
 }

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

@@ -48,7 +48,7 @@ public interface IReadOnlyDocument
     /// </summary>
     void ForEveryReadonlyMember(Action<IReadOnlyStructureMember> action);
     
-    public Surface? GetLayerRasterizedImage(Guid layerGuid);
+    public Surface? GetLayerRasterizedImage(Guid layerGuid, int frame);
     public RectI? GetChunkAlignedLayerBounds(Guid layerGuid);
 
     /// <summary>

+ 0 - 3
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyKeyFrame.cs

@@ -7,7 +7,4 @@ public interface IReadOnlyKeyFrame
     public Guid LayerGuid { get; }
     public Guid Id { get; }
     public bool IsVisible { get; }
-    
-    public void ActiveFrameChanged(int atFrame);
-    public void Deactivated(int atFrame);
 }

+ 3 - 2
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyLayer.cs

@@ -1,8 +1,9 @@
-using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
 public interface IReadOnlyLayer : IReadOnlyStructureMember
 {
-    public ChunkyImage Rasterize();
+    public ChunkyImage Rasterize(KeyFrameTime frameTime);
 }

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

@@ -1,6 +1,6 @@
 namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
-public interface IReadOnlyRasterLayer : ITransparencyLockable
+public interface IReadOnlyRasterLayer : ITransparencyLockable, IChunkyImageProperty
 {
     /// <summary>
     /// The chunky image of the layer

+ 3 - 2
src/PixiEditor.ChangeableDocument/Changeables/Layer.cs

@@ -1,9 +1,10 @@
-using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables;
 
 internal abstract class Layer : StructureMember, IReadOnlyLayer
 {
-    public abstract ChunkyImage Rasterize();
+    public abstract ChunkyImage Rasterize(KeyFrameTime frameTime);
 }

+ 44 - 6
src/PixiEditor.ChangeableDocument/Changeables/RasterLayer.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Numerics;
 
@@ -8,12 +9,19 @@ internal class RasterLayer : Layer, IReadOnlyRasterLayer
 {
     // Don't forget to update CreateLayer_ChangeInfo, DocumentUpdater.ProcessCreateStructureMember and Layer.Clone when adding new properties
     public bool LockTransparency { get; set; } = false;
-    public ChunkyImage LayerImage { get; set; }
-    IReadOnlyChunkyImage IReadOnlyRasterLayer.LayerImage => LayerImage;
 
+    public ChunkyImage LayerImage
+    {
+         get
+    }
+    IReadOnlyChunkyImage IReadOnlyRasterLayer.LayerImage => LayerImage;
+    IReadOnlyChunkyImage IChunkyImageProperty.LayerImage => LayerImage;
+    
+    private List<ImageFrame> frameImages = new();
+    
     public RasterLayer(VecI size)
     {
-        LayerImage = new(size);
+        frameImages.Add(new ImageFrame(0, 0, new(size)));
     }
 
     public RasterLayer(ChunkyImage image)
@@ -28,11 +36,22 @@ internal class RasterLayer : Layer, IReadOnlyRasterLayer
     {
         LayerImage.Dispose();
         Mask?.Dispose();
+        foreach (var frame in frameImages)
+        {
+            frame.Image.Dispose();
+        }
     }
 
-    public override ChunkyImage Rasterize()
+    public override ChunkyImage Rasterize(KeyFrameTime frameTime)
     {
-        return LayerImage;
+        if (frameImages.Count == 0)
+        {
+            return LayerImage;
+        }
+        
+        ImageFrame frame = frameImages.FirstOrDefault(x => x.IsInFrame(frameTime.Frame));
+        
+        return frame?.Image ?? LayerImage;
     }
 
     public override RectI? GetTightBounds()
@@ -59,3 +78,22 @@ internal class RasterLayer : Layer, IReadOnlyRasterLayer
         };
     }
 }
+
+class ImageFrame
+{
+    int StartFrame { get; set; }
+    int EndFrame { get; set; }
+    public ChunkyImage Image { get; set; }
+    
+    public ImageFrame(int startFrame, int endFrame, ChunkyImage image)
+    {
+        StartFrame = startFrame;
+        EndFrame = endFrame;
+        Image = image;
+    }
+    
+    public bool IsInFrame(int frame)
+    {
+        return frame >= StartFrame && frame <= EndFrame;
+    }
+}

+ 0 - 60
src/PixiEditor.ChangeableDocument/Changes/Animation/ActiveFrame_UpdateableChange.cs

@@ -1,60 +0,0 @@
-using PixiEditor.ChangeableDocument.ChangeInfos.Animation;
-
-namespace PixiEditor.ChangeableDocument.Changes.Animation;
-
-internal class ActiveFrame_UpdateableChange : UpdateableChange
-{
-    private int newFrame;
-    private int originalFrame;
-    
-    [GenerateUpdateableChangeActions]
-    public ActiveFrame_UpdateableChange(int activeFrame)
-    {
-        newFrame = activeFrame;
-    }
-    
-    [UpdateChangeMethod]
-    public void Update(int activeFrame)
-    {
-        newFrame = activeFrame;
-    }
-    
-    public override bool InitializeAndValidate(Document target)
-    {
-        originalFrame = target.AnimationData.ActiveFrame;
-        return true;
-    }
-    
-    public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
-    {
-        if (target.AnimationData.ActiveFrame == newFrame)
-        {
-            return new None();
-        }
-        target.AnimationData.ActiveFrame = newFrame;
-        return new ActiveFrame_ChangeInfo(newFrame);
-    }
-
-    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
-    {
-        ignoreInUndo = false;
-        if (target.AnimationData.ActiveFrame == newFrame)
-        {
-            return new None();
-        }
-        
-        target.AnimationData.ActiveFrame = newFrame;
-        return new ActiveFrame_ChangeInfo(newFrame);
-    }
-
-    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
-    {
-        target.AnimationData.ActiveFrame = originalFrame;
-        return new ActiveFrame_ChangeInfo(originalFrame);
-    }
-
-    public override bool IsMergeableWith(Change other)
-    {
-        return other is ActiveFrame_UpdateableChange;
-    }
-}

+ 4 - 2
src/PixiEditor.ChangeableDocument/Changes/Drawing/CombineStructureMembersOnto_Change.cs

@@ -9,15 +9,17 @@ internal class CombineStructureMembersOnto_Change : Change
     private HashSet<Guid> membersToMerge;
 
     private HashSet<Guid> layersToCombine = new();
+    private int frame;
 
     private Guid targetLayer;
     private CommittedChunkStorage? originalChunks;
 
     [GenerateMakeChangeAction]
-    public CombineStructureMembersOnto_Change(HashSet<Guid> membersToMerge, Guid targetLayer)
+    public CombineStructureMembersOnto_Change(HashSet<Guid> membersToMerge, Guid targetLayer, int frame)
     {
         this.membersToMerge = new HashSet<Guid>(membersToMerge);
         this.targetLayer = targetLayer;
+        this.frame = frame;
     }
 
     public override bool InitializeAndValidate(Document target)
@@ -64,7 +66,7 @@ internal class CombineStructureMembersOnto_Change : Change
         toDrawOn.LayerImage.EnqueueClear();
         foreach (var chunk in chunksToCombine)
         {
-            OneOf<Chunk, EmptyChunk> combined = ChunkRenderer.MergeChosenMembers(chunk, ChunkResolution.Full, target.StructureRoot, layersToCombine);
+            OneOf<Chunk, EmptyChunk> combined = ChunkRenderer.MergeChosenMembers(chunk, ChunkResolution.Full, target.StructureRoot, frame, layersToCombine);
             if (combined.IsT0)
             {
                 toDrawOn.LayerImage.EnqueueDrawImage(chunk * ChunkyImage.FullChunkSize, combined.AsT0.Surface);

+ 6 - 4
src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillChunkCache.cs

@@ -14,6 +14,7 @@ internal class FloodFillChunkCache : IDisposable
     private readonly HashSet<Guid>? membersToRender;
     private readonly IReadOnlyFolder? structureRoot;
     private readonly IReadOnlyChunkyImage? image;
+    private readonly int frame;
 
     private readonly Dictionary<VecI, OneOf<Chunk, EmptyChunk>> acquiredChunks = new();
 
@@ -22,10 +23,11 @@ internal class FloodFillChunkCache : IDisposable
         this.image = image;
     }
 
-    public FloodFillChunkCache(HashSet<Guid> membersToRender, IReadOnlyFolder structureRoot)
+    public FloodFillChunkCache(HashSet<Guid> membersToRender, IReadOnlyFolder structureRoot, int frame)
     {
         this.membersToRender = membersToRender;
         this.structureRoot = structureRoot;
+        this.frame = frame;
     }
 
     public bool ChunkExistsInStorage(VecI pos)
@@ -38,15 +40,15 @@ internal class FloodFillChunkCache : IDisposable
     public OneOf<Chunk, EmptyChunk> GetChunk(VecI pos)
     {
         // the chunk was already acquired before, return cached
-        if (acquiredChunks.ContainsKey(pos))
-            return acquiredChunks[pos];
+        if (acquiredChunks.TryGetValue(pos, out var foundChunk))
+            return foundChunk;
 
         // need to get the chunk by merging multiple members
         if (image is null)
         {
             if (structureRoot is null || membersToRender is null)
                 throw new InvalidOperationException();
-            var chunk = ChunkRenderer.MergeChosenMembers(pos, ChunkResolution.Full, structureRoot, membersToRender);
+            var chunk = ChunkRenderer.MergeChosenMembers(pos, ChunkResolution.Full, structureRoot, frame, membersToRender);
             acquiredChunks[pos] = chunk;
             return chunk;
         }

+ 21 - 17
src/PixiEditor.ChangeableDocument/Rendering/ChunkRenderer.cs

@@ -21,13 +21,13 @@ public static class ChunkRenderer
         return (RectI?)rect.Scale(multiplier).Translate(-pixelChunkPos).RoundOutwards();
     }
 
-    public static OneOf<Chunk, EmptyChunk> MergeWholeStructure(VecI chunkPos, ChunkResolution resolution, IReadOnlyFolder root, RectI? globalClippingRect = null)
+    public static OneOf<Chunk, EmptyChunk> MergeWholeStructure(VecI chunkPos, ChunkResolution resolution, IReadOnlyFolder root, int frame, RectI? globalClippingRect = null)
     {
         using RenderingContext context = new();
         try
         {
             RectI? transformedClippingRect = TransformClipRect(globalClippingRect, resolution, chunkPos);
-            return MergeFolderContents(context, chunkPos, resolution, root, new All(), transformedClippingRect);
+            return MergeFolderContents(context, chunkPos, resolution, root, frame, new All(), transformedClippingRect);
         }
         catch (ObjectDisposedException)
         {
@@ -35,13 +35,13 @@ public static class ChunkRenderer
         }
     }
 
-    public static OneOf<Chunk, EmptyChunk> MergeChosenMembers(VecI chunkPos, ChunkResolution resolution, IReadOnlyFolder root, HashSet<Guid> members, RectI? globalClippingRect = null)
+    public static OneOf<Chunk, EmptyChunk> MergeChosenMembers(VecI chunkPos, ChunkResolution resolution, IReadOnlyFolder root, int frame, HashSet<Guid> members, RectI? globalClippingRect = null)
     {
         using RenderingContext context = new();
         try
         {
             RectI? transformedClippingRect = TransformClipRect(globalClippingRect, resolution, chunkPos);
-            return MergeFolderContents(context, chunkPos, resolution, root, members, transformedClippingRect);
+            return MergeFolderContents(context, chunkPos, resolution, root, frame, members, transformedClippingRect);
         }
         catch (ObjectDisposedException)
         {
@@ -53,6 +53,7 @@ public static class ChunkRenderer
         RenderingContext context,
         Chunk targetChunk,
         VecI chunkPos,
+        int frame,
         ChunkResolution resolution,
         IReadOnlyLayer layer,
         OneOf<FilledChunk, EmptyChunk, Chunk> clippingChunk,
@@ -77,7 +78,7 @@ public static class ChunkRenderer
             targetChunk.Surface.DrawingSurface.Canvas.ClipRect((RectD)transformedClippingRect);
         }
 
-        if (!layer.Rasterize().DrawMostUpToDateChunkOn(chunkPos, resolution, renderingResult.Surface.DrawingSurface, VecI.Zero, context.ReplacingPaintWithOpacity))
+        if (!layer.Rasterize(frame).DrawMostUpToDateChunkOn(chunkPos, resolution, renderingResult.Surface.DrawingSurface, VecI.Zero, context.ReplacingPaintWithOpacity))
         {
             renderingResult.Dispose();
             return new EmptyChunk();
@@ -107,6 +108,7 @@ public static class ChunkRenderer
         RenderingContext context,
         Chunk targetChunk,
         VecI chunkPos,
+        int frame,
         ChunkResolution resolution,
         IReadOnlyLayer layer,
         OneOf<FilledChunk, EmptyChunk, Chunk> clippingChunk,
@@ -116,7 +118,7 @@ public static class ChunkRenderer
             return new EmptyChunk();
 
         if (layer.Mask is not null && layer.MaskIsVisible)
-            return RenderLayerWithMask(context, targetChunk, chunkPos, resolution, layer, clippingChunk, transformedClippingRect);
+            return RenderLayerWithMask(context, targetChunk, chunkPos, frame, resolution, layer, clippingChunk, transformedClippingRect);
 
         context.UpdateFromMember(layer);
         Chunk renderingResult = Chunk.Create(resolution);
@@ -127,7 +129,7 @@ public static class ChunkRenderer
             targetChunk.Surface.DrawingSurface.Canvas.Save();
             targetChunk.Surface.DrawingSurface.Canvas.ClipRect((RectD)transformedClippingRect);
         }
-        if (!layer.Rasterize().DrawMostUpToDateChunkOn(chunkPos, resolution, renderingResult.Surface.DrawingSurface, VecI.Zero, context.ReplacingPaintWithOpacity))
+        if (!layer.Rasterize(frame).DrawMostUpToDateChunkOn(chunkPos, resolution, renderingResult.Surface.DrawingSurface, VecI.Zero, context.ReplacingPaintWithOpacity))
         {
             renderingResult.Dispose();
             return new EmptyChunk();
@@ -145,12 +147,12 @@ public static class ChunkRenderer
         return renderingResult;
     }
 
-    private static void RenderLayer(
-        RenderingContext context,
+    private static void RenderLayer(RenderingContext context,
         Chunk targetChunk,
         VecI chunkPos,
         ChunkResolution resolution,
         IReadOnlyLayer layer,
+        int frame,
         OneOf<FilledChunk, EmptyChunk, Chunk> clippingChunk,
         RectI? transformedClippingRect)
     {
@@ -158,7 +160,7 @@ public static class ChunkRenderer
             return;
         if (layer.Mask is not null && layer.MaskIsVisible)
         {
-            var result = RenderLayerWithMask(context, targetChunk, chunkPos, resolution, layer, clippingChunk, transformedClippingRect);
+            var result = RenderLayerWithMask(context, targetChunk, chunkPos, frame, resolution, layer, clippingChunk, transformedClippingRect);
             if (result.IsT1)
                 result.AsT1.Dispose();
             return;
@@ -166,7 +168,7 @@ public static class ChunkRenderer
         // clipping chunk requires a temp chunk anyway so we could as well reuse the code from RenderLayerSaveResult
         if (clippingChunk.IsT2)
         {
-            var result = RenderLayerSaveResult(context, targetChunk, chunkPos, resolution, layer, clippingChunk, transformedClippingRect);
+            var result = RenderLayerSaveResult(context, targetChunk, chunkPos, frame, resolution, layer, clippingChunk, transformedClippingRect);
             if (result.IsT1)
                 result.AsT1.Dispose();
             return;
@@ -178,7 +180,7 @@ public static class ChunkRenderer
             targetChunk.Surface.DrawingSurface.Canvas.Save();
             targetChunk.Surface.DrawingSurface.Canvas.ClipRect((RectD)transformedClippingRect);
         }
-        layer.Rasterize().DrawMostUpToDateChunkOn(chunkPos, resolution, targetChunk.Surface.DrawingSurface, VecI.Zero, context.BlendModeOpacityPaint);
+        layer.Rasterize(frame).DrawMostUpToDateChunkOn(chunkPos, resolution, targetChunk.Surface.DrawingSurface, VecI.Zero, context.BlendModeOpacityPaint);
         if (transformedClippingRect is not null)
             targetChunk.Surface.DrawingSurface.Canvas.Restore();
     }
@@ -189,6 +191,7 @@ public static class ChunkRenderer
         VecI chunkPos,
         ChunkResolution resolution,
         IReadOnlyFolder folder,
+        int frame,
         OneOf<FilledChunk, EmptyChunk, Chunk> clippingChunk,
         OneOf<All, HashSet<Guid>> membersToMerge,
         RectI? transformedClippingRect)
@@ -202,7 +205,7 @@ public static class ChunkRenderer
         )
             return new EmptyChunk();
 
-        OneOf<Chunk, EmptyChunk> maybeContents = MergeFolderContents(context, chunkPos, resolution, folder, membersToMerge, transformedClippingRect);
+        OneOf<Chunk, EmptyChunk> maybeContents = MergeFolderContents(context, chunkPos, resolution, folder, frame, membersToMerge, transformedClippingRect);
         if (maybeContents.IsT1)
             return new EmptyChunk();
         Chunk contents = maybeContents.AsT0;
@@ -245,6 +248,7 @@ public static class ChunkRenderer
         VecI chunkPos,
         ChunkResolution resolution,
         IReadOnlyFolder folder,
+        int frame,
         OneOf<All, HashSet<Guid>> membersToMerge,
         RectI? transformedClippingRect)
     {
@@ -279,12 +283,12 @@ public static class ChunkRenderer
             {
                 if (needToSaveClippingChunk)
                 {
-                    OneOf<EmptyChunk, Chunk> result = RenderLayerSaveResult(context, targetChunk, chunkPos, resolution, layer, clippingChunk, transformedClippingRect);
+                    OneOf<EmptyChunk, Chunk> result = RenderLayerSaveResult(context, targetChunk, chunkPos, frame, resolution, layer, clippingChunk, transformedClippingRect);
                     clippingChunk = result.IsT0 ? result.AsT0 : result.AsT1;
                 }
                 else
                 {
-                    RenderLayer(context, targetChunk, chunkPos, resolution, layer, clippingChunk, transformedClippingRect);
+                    RenderLayer(context, targetChunk, chunkPos, resolution, layer, frame, clippingChunk, transformedClippingRect);
                 }
                 continue;
             }
@@ -300,12 +304,12 @@ public static class ChunkRenderer
                 OneOf<All, HashSet<Guid>> innerMembersToMerge = shouldRenderAllChildren ? new All() : membersToMerge;
                 if (needToSaveClippingChunk)
                 {
-                    OneOf<EmptyChunk, Chunk> result = RenderFolder(context, targetChunk, chunkPos, resolution, innerFolder, clippingChunk, innerMembersToMerge, transformedClippingRect);
+                    OneOf<EmptyChunk, Chunk> result = RenderFolder(context, targetChunk, chunkPos, resolution, innerFolder, frame, clippingChunk, innerMembersToMerge, transformedClippingRect);
                     clippingChunk = result.IsT0 ? result.AsT0 : result.AsT1;
                 }
                 else
                 {
-                    RenderFolder(context, targetChunk, chunkPos, resolution, innerFolder, clippingChunk, innerMembersToMerge, transformedClippingRect);
+                    RenderFolder(context, targetChunk, chunkPos, resolution, innerFolder, frame, clippingChunk, innerMembersToMerge, transformedClippingRect);
                 }
             }
         }