ソースを参照

Fixed animation exporting and timeline update

Krzysztof Krysiński 3 ヶ月 前
コミット
613734580f

+ 2 - 2
src/PixiEditor/Models/Files/VideoFileType.cs

@@ -21,7 +21,7 @@ internal abstract class VideoFileType : IoFileType
         job?.Report(0, new LocalizedString("WARMING_UP"));
 
         int frameRendered = 0;
-        int totalFrames = document.AnimationDataViewModel.GetVisibleFramesCount();
+        int totalFrames = document.AnimationDataViewModel.GetLastVisibleFrame() - 1;
 
         document.RenderFrames(frames, surface =>
         {
@@ -65,7 +65,7 @@ internal abstract class VideoFileType : IoFileType
         job?.Report(0, new LocalizedString("WARMING_UP"));
 
         int frameRendered = 0;
-        int totalFrames = document.AnimationDataViewModel.FramesCount;
+        int totalFrames = document.AnimationDataViewModel.GetLastVisibleFrame() - 1;
 
         document.RenderFrames(frames, surface =>
         {

+ 3 - 3
src/PixiEditor/Models/IO/Exporter.cs

@@ -188,7 +188,7 @@ internal class Exporter
             Directory.CreateDirectory(directory);
         }
 
-        int totalFrames = document.AnimationDataViewModel.GetVisibleFramesCount();
+        int totalFrames = document.AnimationDataViewModel.GetLastVisibleFrame() - 1;
         document.RenderFramesProgressive(
             (surface, frame) =>
         {
@@ -198,11 +198,11 @@ internal class Exporter
             if (exportConfig.ExportSize != surface.Size)
             {
                 var resized = surface.ResizeNearestNeighbor(exportConfig.ExportSize);
-                SaveAsPng(Path.Combine(directory, $"{frame}.png"), resized);
+                SaveAsPng(Path.Combine(directory, $"{frame + 1}.png"), resized);
             }
             else
             {
-                SaveAsPng(Path.Combine(directory, $"{frame}.png"), surface);
+                SaveAsPng(Path.Combine(directory, $"{frame + 1}.png"), surface);
             }
 
         }, CancellationToken.None, exportConfig.ExportOutput);

+ 1 - 1
src/PixiEditor/Styles/Templates/Timeline.axaml

@@ -29,7 +29,7 @@
                             BorderBrush="{DynamicResource ThemeBorderMidBrush}">
                         <DockPanel Grid.Row="0" Grid.Column="0" LastChildFill="False" Margin="5 0">
                             <controls:SizeInput Unit="FPS"
-                                             Width="80" Height="25" HorizontalAlignment="Left"
+                                             Width="90" Height="25" HorizontalAlignment="Left"
                                              Size="{Binding Fps, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
 
                             <Button Classes="pixi-icon" DockPanel.Dock="Right"

+ 7 - 8
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -1197,10 +1197,10 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         if (token.IsCancellationRequested)
             return [];
 
-        int firstFrame = AnimationDataViewModel.GetFirstVisibleFrame();
+        int firstFrame = 1;
         int lastFrame = AnimationDataViewModel.GetLastVisibleFrame();
 
-        int framesCount = lastFrame - firstFrame;
+        int framesCount = lastFrame;
 
         Image[] images = new Image[framesCount];
 
@@ -1238,10 +1238,9 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     public void RenderFramesProgressive(Action<Surface, int> processFrameAction, CancellationToken token,
         string? renderOutput)
     {
-        int firstFrame = AnimationDataViewModel.GetFirstVisibleFrame();
-        int framesCount = AnimationDataViewModel.GetLastVisibleFrame();
-        int lastFrame = firstFrame + framesCount;
-
+        int firstFrame = 1;
+        int lastFrame = AnimationDataViewModel.GetLastVisibleFrame();
+        int totalFrames = lastFrame - firstFrame;
         int activeFrame = AnimationDataViewModel.ActiveFrameBindable;
 
         for (int i = firstFrame; i < lastFrame; i++)
@@ -1249,7 +1248,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             if (token.IsCancellationRequested)
                 return;
 
-            KeyFrameTime frameTime = new KeyFrameTime(i, (double)(i - firstFrame) / framesCount);
+            KeyFrameTime frameTime = new KeyFrameTime(i, (double)(i - firstFrame) / totalFrames);
 
             var surface = TryRenderWholeImage(frameTime, renderOutput);
             if (surface.IsT0)
@@ -1265,7 +1264,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     public bool RenderFrames(List<Image> frames, Func<Surface, Surface> processFrameAction = null,
         string? renderOutput = null)
     {
-        var firstFrame = AnimationDataViewModel.GetFirstVisibleFrame();
+        var firstFrame = 1;
         var lastFrame = AnimationDataViewModel.GetLastVisibleFrame();
 
         for (int i = firstFrame; i < lastFrame; i++)

+ 54 - 4
src/PixiEditor/Views/Animations/Timeline.cs

@@ -221,10 +221,12 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
         StepStartCommand = new RelayCommand(() =>
         {
             var keyFramesWithinActiveFrame = KeyFrames.Where(x => x.IsVisible
-                                                                  && x.StartFrameBindable < ActiveFrame).SelectMany(x => x.Children).ToList();
+                                                                  && x.StartFrameBindable < ActiveFrame)
+                .SelectMany(x => x.Children).ToList();
             if (keyFramesWithinActiveFrame.Count > 0)
             {
-                List<int> snapPoints = keyFramesWithinActiveFrame.Select(x => x.StartFrameBindable + x.DurationBindable - 1).ToList();
+                List<int> snapPoints = keyFramesWithinActiveFrame
+                    .Select(x => x.StartFrameBindable + x.DurationBindable - 1).ToList();
                 snapPoints.AddRange(KeyFrames.Select(x => x.StartFrameBindable));
                 snapPoints.RemoveAll(x => x >= ActiveFrame);
 
@@ -239,10 +241,12 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
         StepEndCommand = new RelayCommand(() =>
         {
             var keyFramesWithinActiveFrame = KeyFrames.Where(x => x.IsVisible
-                                                                  && x.StartFrameBindable + x.DurationBindable - 1 > ActiveFrame).SelectMany(x => x.Children).ToList();
+                                                                  && x.StartFrameBindable + x.DurationBindable - 1 >
+                                                                  ActiveFrame).SelectMany(x => x.Children).ToList();
             if (keyFramesWithinActiveFrame.Count > 0)
             {
-                List<int> snapPoints = keyFramesWithinActiveFrame.Select(x => x.StartFrameBindable + x.DurationBindable - 1).ToList();
+                List<int> snapPoints = keyFramesWithinActiveFrame
+                    .Select(x => x.StartFrameBindable + x.DurationBindable - 1).ToList();
                 snapPoints.AddRange(KeyFrames.Select(x => x.StartFrameBindable));
                 snapPoints.RemoveAll(x => x <= ActiveFrame);
 
@@ -424,6 +428,8 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
                 dragStartFrame += delta;
             }
         }
+
+        PropertyChanged(this, new PropertyChangedEventArgs(nameof(EndFrame)));
     }
 
     private void ClearSelectedKeyFrames(CelViewModel? keyFrame)
@@ -667,6 +673,17 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
         {
             newCollection.KeyFrameAdded += timeline.KeyFrames_KeyFrameAdded;
             newCollection.KeyFrameRemoved += timeline.KeyFrames_KeyFrameRemoved;
+
+            foreach (var item in newCollection)
+            {
+                foreach (var child in item.Children)
+                {
+                    if (child is CelViewModel cel)
+                    {
+                        cel.PropertyChanged += timeline.KeyFrameOnPropertyChanged;
+                    }
+                }
+            }
         }
 
         if (timeline.PropertyChanged != null)
@@ -679,6 +696,34 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
     private void KeyFrames_KeyFrameAdded(CelViewModel cel)
     {
         cel.PropertyChanged += KeyFrameOnPropertyChanged;
+        if (cel is CelGroupViewModel group)
+        {
+            group.Children.CollectionChanged += GroupChildren_CollectionChanged;
+        }
+
+        PropertyChanged(this, new PropertyChangedEventArgs(nameof(SelectedKeyFrames)));
+        PropertyChanged(this, new PropertyChangedEventArgs(nameof(EndFrame)));
+    }
+
+    private void GroupChildren_CollectionChanged(object? sender,
+        System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+    {
+        if (e.NewItems != null)
+        {
+            foreach (CelViewModel cel in e.NewItems)
+            {
+                cel.PropertyChanged += KeyFrameOnPropertyChanged;
+            }
+        }
+
+        if (e.OldItems != null)
+        {
+            foreach (CelViewModel cel in e.OldItems)
+            {
+                cel.PropertyChanged -= KeyFrameOnPropertyChanged;
+            }
+        }
+
         PropertyChanged(this, new PropertyChangedEventArgs(nameof(SelectedKeyFrames)));
         PropertyChanged(this, new PropertyChangedEventArgs(nameof(EndFrame)));
     }
@@ -689,6 +734,11 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
         {
             cel.Document.AnimationDataViewModel.RemoveSelectedKeyFrame(cel.Id);
             cel.PropertyChanged -= KeyFrameOnPropertyChanged;
+
+            if (cel is CelGroupViewModel group)
+            {
+                group.Children.CollectionChanged -= GroupChildren_CollectionChanged;
+            }
         }
 
         PropertyChanged(this, new PropertyChangedEventArgs(nameof(SelectedKeyFrames)));

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

@@ -258,7 +258,7 @@ internal partial class ExportFilePopup : PixiEditorPopup
         {
             foreach (var frame in videoPreviewFrames)
             {
-                frame.Dispose();
+                frame?.Dispose();
             }
         }
     }