Browse Source

Restoring original image

flabbet 1 year ago
parent
commit
5ad9dbdffb

+ 3 - 1
src/PixiEditor.AnimationRenderer.FFmpeg/FFMpegRenderer.cs

@@ -1,4 +1,5 @@
 using FFMpegCore;
+using FFMpegCore.Arguments;
 using FFMpegCore.Enums;
 using PixiEditor.AnimationRenderer.Core;
 
@@ -21,7 +22,8 @@ public class FFMpegRenderer : IAnimationRenderer
             .OutputToFile($"{framesPath}/output.mp4", true, options =>
             {
                 options.WithVideoCodec(VideoCodec.LibX264)
-                    .WithFramerate(frameRate);
+                    .WithFramerate(frameRate)
+                    .ForcePixelFormat("yuv420p");
             })
             .ProcessAsynchronously();
     }

+ 9 - 8
src/PixiEditor.AvaloniaUI/Styles/Templates/Timeline.axaml

@@ -80,13 +80,8 @@
                             <TreeView ItemsSource="{TemplateBinding KeyFrames}">
                                 <TreeView.ItemContainerTheme>
                                     <ControlTheme TargetType="TreeViewItem">
-                                        <Setter Property="ClipToBounds" Value="False" />
-                                        <Setter Property="Margin">
-                                            <MultiBinding Converter="{converters:DurationToMarginConverter}">
-                                                <Binding Path="DataContext.StartFrameBindable" RelativeSource="{RelativeSource Self}" />
-                                                <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType=animations:Timeline}" Path="Scale" />
-                                            </MultiBinding>
-                                        </Setter>
+                                        <Setter Property="ClipToBounds" Value="False"/>
+                                        <Setter Property="Margin" Value="0 5"></Setter>
                                         <Setter Property="Template">
                                             <ControlTemplate>
                                                 <StackPanel Orientation="Horizontal">
@@ -117,7 +112,7 @@
                                         </Grid>
                                     </TreeDataTemplate>
                                     <DataTemplate DataType="document:RasterKeyFrameViewModel">
-                                        <animations:KeyFrame Margin="0 10"
+                                        <animations:KeyFrame
                                                              Scale="{Binding Scale, RelativeSource={RelativeSource FindAncestor, AncestorType=animations:Timeline}}"
                                                              Item="{Binding}">
                                             <animations:KeyFrame.Width>
@@ -126,6 +121,12 @@
                                                     <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType=animations:Timeline}" Path="Scale" />
                                                 </MultiBinding>
                                             </animations:KeyFrame.Width>
+                                            <animations:KeyFrame.Margin>
+                                                <MultiBinding Converter="{converters:DurationToMarginConverter}">
+                                                    <Binding Path="Item.StartFrameBindable" RelativeSource="{RelativeSource Self}" />
+                                                    <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType=animations:Timeline}" Path="Scale" />
+                                                </MultiBinding>
+                                            </animations:KeyFrame.Margin>
                                         </animations:KeyFrame>
                                     </DataTemplate>
                                 </TreeView.DataTemplates>

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

@@ -57,6 +57,7 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
         {
             keyFrame.SetStartFrame(newStartFrame);
             keyFrame.SetDuration(newDuration);
+            keyFrames.NotifyCollectionChanged();
         }
     }
 
@@ -84,6 +85,8 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
         {
             parent.Children.Remove(frame);
         });
+        
+        keyFrames.NotifyCollectionChanged();
     }
     
     public bool FindKeyFrame<T>(Guid guid, out T keyFrameHandler) where T : IKeyFrameHandler

+ 1 - 1
src/PixiEditor.AvaloniaUI/ViewModels/Document/KeyFrameGroupViewModel.cs

@@ -9,7 +9,7 @@ internal class KeyFrameGroupViewModel : KeyFrameViewModel, IKeyFrameGroupHandler
     public ObservableCollection<IKeyFrameHandler> Children { get; } = new ObservableCollection<IKeyFrameHandler>();
 
     public override int StartFrameBindable => Children.Count > 0 ? Children.Min(x => x.StartFrameBindable) : 0;
-    public override int DurationBindable => Children.Count > 0 ? Children.Max(x => x.StartFrameBindable + x.DurationBindable) : 0;
+    public override int DurationBindable => Children.Count > 0 ? Children.Max(x => x.StartFrameBindable + x.DurationBindable) - StartFrameBindable : 0;
 
     public KeyFrameGroupViewModel(int startFrame, int duration, Guid layerGuid, Guid id, DocumentViewModel doc, DocumentInternalParts internalParts) 
         : base(startFrame, duration, layerGuid, id, doc, internalParts)

+ 13 - 10
src/PixiEditor.AvaloniaUI/Views/Animations/Timeline.cs

@@ -42,8 +42,9 @@ internal class Timeline : TemplatedControl
         set => SetValue(NewKeyFrameCommandProperty, value);
     }
 
-    public static readonly StyledProperty<ICommand> DuplicateKeyFrameCommandProperty = AvaloniaProperty.Register<Timeline, ICommand>(
-        "DuplicateKeyFrameCommand");
+    public static readonly StyledProperty<ICommand> DuplicateKeyFrameCommandProperty =
+        AvaloniaProperty.Register<Timeline, ICommand>(
+            "DuplicateKeyFrameCommand");
 
     public ICommand DuplicateKeyFrameCommand
     {
@@ -87,21 +88,23 @@ internal class Timeline : TemplatedControl
     {
         base.OnApplyTemplate(e);
         _playToggle = e.NameScope.Find<ToggleButton>("PART_PlayToggle");
-        
+
         if (_playToggle != null)
         {
             _playToggle.Click += PlayToggleOnClick;
         }
     }
-    
+
     private void PlayTimerOnTick(object? sender, EventArgs e)
     {
-        ActiveFrame++;
-        
-        if (ActiveFrame >= KeyFrames.FrameCount - 1)
+        if (ActiveFrame >= KeyFrames.FrameCount)
         {
             ActiveFrame = 0;
         }
+        else
+        {
+            ActiveFrame++;
+        }
     }
 
     private void PlayToggleOnClick(object? sender, RoutedEventArgs e)
@@ -120,17 +123,17 @@ internal class Timeline : TemplatedControl
             IsPlaying = false;
         }
     }
-    
+
     public void Play()
     {
         IsPlaying = true;
     }
-    
+
     public void Pause()
     {
         IsPlaying = false;
     }
-    
+
     private static void IsPlayingChanged(AvaloniaPropertyChangedEventArgs e)
     {
         if (e.Sender is not Timeline timeline)

+ 32 - 26
src/PixiEditor.ChangeableDocument/Changeables/Animations/AnimationData.cs

@@ -2,7 +2,7 @@
 
 namespace PixiEditor.ChangeableDocument.Changeables.Animations;
 
-public class AnimationData : IReadOnlyAnimationData
+internal class AnimationData : IReadOnlyAnimationData
 {
     private int _activeFrame;
 
@@ -20,7 +20,7 @@ public class AnimationData : IReadOnlyAnimationData
             {
                 _activeFrame = value;
             }
-            
+
             OnPreviewFrameChanged(lastFrame);
         }
     }
@@ -28,7 +28,13 @@ public class AnimationData : IReadOnlyAnimationData
     public IReadOnlyList<IReadOnlyKeyFrame> KeyFrames => keyFrames;
 
     private List<KeyFrame> keyFrames = new List<KeyFrame>();
+    private readonly Document document;
     
+    public AnimationData(Document document)
+    {
+        this.document = document;
+    }
+
     public void AddKeyFrame(KeyFrame keyFrame)
     {
         Guid id = keyFrame.LayerGuid;
@@ -38,7 +44,7 @@ public class AnimationData : IReadOnlyAnimationData
         }
         else
         {
-            GroupKeyFrame createdGroup = new GroupKeyFrame(id, keyFrame.StartFrame);
+            GroupKeyFrame createdGroup = new GroupKeyFrame(id, keyFrame.StartFrame, document);
             createdGroup.Children.Add(keyFrame);
             keyFrames.Add(createdGroup);
         }
@@ -54,18 +60,20 @@ public class AnimationData : IReadOnlyAnimationData
             }
         });
     }
-    
+
     public bool TryFindKeyFrame<T>(Guid id, out T keyFrame) where T : IReadOnlyKeyFrame
     {
         return TryFindKeyFrameCallback(id, out keyFrame, null);
     }
 
-    private bool TryFindKeyFrameCallback<T>(Guid id, out T? foundKeyFrame, Action<KeyFrame, GroupKeyFrame?> onFound = null) where T : IReadOnlyKeyFrame
+    private bool TryFindKeyFrameCallback<T>(Guid id, out T? foundKeyFrame,
+        Action<KeyFrame, GroupKeyFrame?> onFound = null) where T : IReadOnlyKeyFrame
     {
         return TryFindKeyFrame(keyFrames, null, id, out foundKeyFrame, onFound);
     }
 
-    private bool TryFindKeyFrame<T>(List<KeyFrame> root, GroupKeyFrame parent, Guid id, out T? result, Action<KeyFrame, GroupKeyFrame?> onFound) where T : IReadOnlyKeyFrame
+    private bool TryFindKeyFrame<T>(List<KeyFrame> root, GroupKeyFrame parent, Guid id, out T? result,
+        Action<KeyFrame, GroupKeyFrame?> onFound) where T : IReadOnlyKeyFrame
     {
         for (var i = 0; i < root.Count; i++)
         {
@@ -90,7 +98,7 @@ public class AnimationData : IReadOnlyAnimationData
         result = default;
         return false;
     }
-    
+
     private void OnPreviewFrameChanged(int lastFrame)
     {
         if (KeyFrames == null)
@@ -105,29 +113,27 @@ public class AnimationData : IReadOnlyAnimationData
     {
         foreach (var keyFrame in root)
         {
-            if (keyFrame is GroupKeyFrame group)
-            {
-                NotifyKeyFrames(lastFrame, group.Children);
-            }
-            else
+            bool isWithinRange = keyFrame.IsWithinRange(ActiveFrame);
+            if (keyFrame.IsWithinRange(lastFrame))
             {
-                if (IsWithinRange(keyFrame, lastFrame))
+                if (!isWithinRange)
+                {
+                    keyFrame.Deactivated(ActiveFrame);
+                }
+                else
                 {
-                    if (!IsWithinRange(keyFrame, ActiveFrame))
-                    {
-                        keyFrame.Deactivated(ActiveFrame);
-                    }
-                    else
-                    {
-                        keyFrame.ActiveFrameChanged(ActiveFrame);
-                    }
+                    keyFrame.ActiveFrameChanged(ActiveFrame);
                 }
             }
+            else if (isWithinRange)
+            {
+                keyFrame.ActiveFrameChanged(ActiveFrame);
+            }
+            
+            if (keyFrame is GroupKeyFrame group)
+            {
+                NotifyKeyFrames(lastFrame, group.Children);
+            }
         }
     }
-
-    private bool IsWithinRange(KeyFrame keyFrame, int frame)
-    {
-        return frame >= keyFrame.StartFrame && frame < keyFrame.StartFrame + keyFrame.Duration;
-    }
 }

+ 27 - 2
src/PixiEditor.ChangeableDocument/Changeables/Animations/GroupKeyFrame.cs

@@ -1,10 +1,35 @@
 namespace PixiEditor.ChangeableDocument.Changeables.Animations;
 
-public class GroupKeyFrame : KeyFrame
+internal class GroupKeyFrame : KeyFrame
 {
+    private ChunkyImage originalLayerImage;
+    private Document document;
     public List<KeyFrame> Children { get; } = new List<KeyFrame>();
-    public GroupKeyFrame(Guid layerGuid, int startFrame) : base(layerGuid, startFrame)
+    public override int Duration => Children.Count > 0 ? Children.Max(x => x.StartFrame + x.Duration) - StartFrame : 0;
+    public override int StartFrame => Children.Count > 0 ? Children.Min(x => x.StartFrame) : 0;
+
+    public GroupKeyFrame(Guid layerGuid, int startFrame, Document document) : base(layerGuid, startFrame)
     {
         Id = layerGuid;
+        this.document = document;
+        
+        if (document.TryFindMember<RasterLayer>(LayerGuid, out var layer))
+        {
+            originalLayerImage = layer.LayerImage;
+        }
+    }
+
+    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;
     }
 }

+ 40 - 4
src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrame.cs

@@ -4,19 +4,55 @@ namespace PixiEditor.ChangeableDocument.Changeables.Animations;
 
 public abstract class KeyFrame : IReadOnlyKeyFrame
 {
-    public int StartFrame { get; set; }
-    public int Duration { get; set; }
+    private int startFrame;
+    private int duration;
+
+    public virtual int StartFrame
+    {
+        get => startFrame;
+        set
+        {
+            if (value < 0)
+            {
+                value = 0;
+            }
+
+            startFrame = value;
+        }
+    }
+
+    public virtual int Duration
+    {
+        get => duration;
+        set
+        {
+            if (value < 1)
+            {
+                value = 1;
+            }
+
+            duration = value;
+        }
+    }
+    
+    public int EndFrame => StartFrame + Duration;
+    
     public Guid LayerGuid { get; }
     public Guid Id { get; set; }
 
     protected KeyFrame(Guid layerGuid, int startFrame)
     {
         LayerGuid = layerGuid;
-        StartFrame = startFrame;
-        Duration = 1;
+        this.startFrame = startFrame;
+        duration = 1;
         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;
+    }
 }

+ 1 - 11
src/PixiEditor.ChangeableDocument/Changeables/Animations/RasterKeyFrame.cs

@@ -4,9 +4,7 @@ internal class RasterKeyFrame : KeyFrame
 {
     public ChunkyImage Image { get; set; }
     public Document Document { get; set; }
-
-    private ChunkyImage originalLayerImage;
-
+    
     public RasterKeyFrame(Guid targetLayerGuid, int startFrame, Document document, ChunkyImage? cloneFrom = null)
         : base(targetLayerGuid, startFrame)
     {
@@ -22,12 +20,4 @@ internal class RasterKeyFrame : KeyFrame
             layer.LayerImage = Image;
         }
     }
-
-    public override void Deactivated(int atFrame)
-    {
-        if (Document.TryFindMember<RasterLayer>(LayerGuid, out var layer))
-        {
-            //layer.LayerImage = originalLayerImage;
-        }
-    }
 }

+ 6 - 1
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -26,12 +26,17 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     internal Folder StructureRoot { get; } = new() { GuidValue = Guid.Empty };
     internal Selection Selection { get; } = new();
     internal ReferenceLayer? ReferenceLayer { get; set; }
-    internal AnimationData AnimationData { get; } = new();
+    internal AnimationData AnimationData { get; }
     public VecI Size { get; set; } = DefaultSize;
     public bool HorizontalSymmetryAxisEnabled { get; set; }
     public bool VerticalSymmetryAxisEnabled { get; set; }
     public double HorizontalSymmetryAxisY { get; set; }
     public double VerticalSymmetryAxisX { get; set; }
+    
+    public Document()
+    {
+        AnimationData = new AnimationData(this);
+    }
 
     public void Dispose()
     {

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

@@ -27,6 +27,10 @@ internal class ActiveFrame_UpdateableChange : UpdateableChange
     
     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);
     }
@@ -34,6 +38,11 @@ internal class ActiveFrame_UpdateableChange : UpdateableChange
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
         ignoreInUndo = true;
+        if (target.AnimationData.ActiveFrame == newFrame)
+        {
+            return new None();
+        }
+        
         target.AnimationData.ActiveFrame = newFrame;
         return new ActiveFrame_ChangeInfo(newFrame);
     }