flabbet 1 年之前
父节点
当前提交
300a8d2e63

+ 34 - 0
src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs

@@ -4,7 +4,9 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.DrawingApi.Core;
+using PixiEditor.DrawingApi.Core.Surfaces;
 using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Rendering;
@@ -17,6 +19,38 @@ public class DocumentRenderer
     }
 
     private IReadOnlyDocument Document { get; }
+    public Image LastOnionSkinningFrame { get; set; }
+
+    public Texture RenderDocument(KeyFrameTime frameTime, ChunkResolution resolution, Texture toDrawOn = null, Paint paint = null)
+    {
+        VecI sizeInChunks = Document.Size / resolution.PixelSize();
+        
+        sizeInChunks = new VecI(
+            Math.Max(1, sizeInChunks.X),
+            Math.Max(1, sizeInChunks.Y));
+
+        if (toDrawOn is null)
+        {
+            toDrawOn = new Texture(resolution.PixelSize() * sizeInChunks);
+        }
+        
+        for (int x = 0; x < sizeInChunks.X; x++)
+        {
+            for (int y = 0; y < sizeInChunks.Y; y++)
+            {
+                VecI chunkPos = new(x, y);
+                OneOf<Chunk, EmptyChunk> chunk = RenderChunk(chunkPos, resolution, frameTime);
+                if (chunk.IsT0)
+                {
+                    toDrawOn.DrawingSurface.Canvas.DrawSurface(
+                        chunk.AsT0.Surface.DrawingSurface, 
+                        resolution.PixelSize(), resolution.PixelSize(), paint);
+                }
+            }
+        }
+        
+        return toDrawOn;
+    }
 
     public OneOf<Chunk, EmptyChunk> RenderChunk(VecI chunkPos, ChunkResolution resolution, KeyFrameTime frameTime,
         RectI? globalClippingRect = null)

+ 1 - 0
src/PixiEditor.DrawingApi.Core/Bridge/Operations/ISurfaceImplementation.cs

@@ -17,5 +17,6 @@ public interface ISurfaceImplementation
     public void Dispose(DrawingSurface drawingSurface);
     public object GetNativeSurface(IntPtr objectPointer);
     public void Flush(DrawingSurface drawingSurface);
+    public DrawingSurface FromNative(object native);
 }
 

+ 5 - 0
src/PixiEditor.DrawingApi.Core/Surfaces/DrawingSurface.cs

@@ -80,5 +80,10 @@ namespace PixiEditor.DrawingApi.Core.Surfaces
         {
             DrawingBackendApi.Current.SurfaceImplementation.Flush(this);
         }
+
+        public static DrawingSurface FromNative(object native)
+        {
+            return DrawingBackendApi.Current.SurfaceImplementation.FromNative(native);
+        }
     }
 }

+ 10 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaSurfaceImplementation.cs

@@ -156,5 +156,15 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
         {
             ManagedInstances[drawingSurface.ObjectPointer].Flush(true, true);
         }
+
+        public DrawingSurface FromNative(object native)
+        {
+            if (native is not SKSurface skSurface)
+            {
+                throw new ArgumentException("Native object is not of type SKSurface");
+            }
+
+            return CreateDrawingSurface(skSurface);
+        }
     }
 }

+ 9 - 1
src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs

@@ -151,6 +151,9 @@ internal class DocumentUpdater
             case SetActiveFrame_PassthroughAction info:
                 ProcessActiveFrame(info);
                 break;
+            case ToggleOnionSkinning_PassthroughAction info:
+                ProcessToggleOnionSkinning(info);
+                break;
             case KeyFrameLength_ChangeInfo info:
                 ProcessKeyFrameLength(info);
                 break;
@@ -451,7 +454,12 @@ internal class DocumentUpdater
 
     private void ProcessMoveStructureMember(MoveStructureMember_ChangeInfo info)
     {
-         
+         // TODO: uh why is this empty, find out why
+    }
+    
+    private void ProcessToggleOnionSkinning(ToggleOnionSkinning_PassthroughAction info)
+    {
+        doc.AnimationHandler.SetOnionSkinning(info.IsOnionSkinningEnabled);
     }
     
     private void ProcessCreateRasterKeyFrame(CreateRasterKeyFrame_ChangeInfo info)

+ 6 - 0
src/PixiEditor/Models/DocumentPassthroughActions/ToggleOnionSkinning_PassthroughAction.cs

@@ -0,0 +1,6 @@
+using PixiEditor.ChangeableDocument.Actions;
+using PixiEditor.ChangeableDocument.ChangeInfos;
+
+namespace PixiEditor.Models.DocumentPassthroughActions;
+
+internal record ToggleOnionSkinning_PassthroughAction(bool IsOnionSkinningEnabled) : IAction, IChangeInfo;

+ 2 - 0
src/PixiEditor/Models/Handlers/IAnimationHandler.cs

@@ -7,6 +7,7 @@ internal interface IAnimationHandler
     public IReadOnlyCollection<IKeyFrameHandler> KeyFrames { get; }
     public int ActiveFrameBindable { get; set; }
     public KeyFrameTime ActiveFrameTime { get; }
+    public bool OnionSkinningEnabledBindable { get; set; }
     public void CreateRasterKeyFrame(Guid targetLayerGuid, int frame, Guid? toCloneFrom = null, int? frameToCopyFrom = null);
     public void SetFrameRate(int newFrameRate);
     public void SetActiveFrame(int newFrame);
@@ -18,4 +19,5 @@ internal interface IAnimationHandler
     public void AddSelectedKeyFrame(Guid keyFrameId);
     public void RemoveSelectedKeyFrame(Guid keyFrameId);
     public void ClearSelectedKeyFrames();
+    public void SetOnionSkinning(bool enabled);
 }

+ 23 - 0
src/PixiEditor/Models/Rendering/CanvasUpdater.cs

@@ -8,6 +8,7 @@ using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
 using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.Handlers;
@@ -21,6 +22,9 @@ internal class CanvasUpdater
     private readonly IDocument doc;
     private readonly DocumentInternalParts internals;
 
+    private Image? lastRenderedFrame;
+    private int lastRenderedFrameNumber;
+
     private static readonly Paint ReplacingPaint = new() { BlendMode = BlendMode.Src };
 
     private static readonly Paint ClearPaint = new()
@@ -213,6 +217,25 @@ internal class CanvasUpdater
                 ));
             }
         }
+
+
+        UpdateOnionSkinning(doc.Surfaces[ChunkResolution.Full]);
+    }
+
+    private void UpdateOnionSkinning(Texture screenSurface)
+    {
+        if (doc.AnimationHandler.OnionSkinningEnabledBindable)
+        {
+            if (lastRenderedFrameNumber != doc.AnimationHandler.ActiveFrameBindable)
+            {
+                lastRenderedFrame?.Dispose();
+                lastRenderedFrame = screenSurface.DrawingSurface.Snapshot();
+                lastRenderedFrameNumber = doc.AnimationHandler.ActiveFrameBindable;
+                doc.Renderer.LastOnionSkinningFrame = lastRenderedFrame;
+            }
+
+            lastRenderedFrameNumber = doc.AnimationHandler.ActiveFrameBindable;
+        }
     }
 
     private void RenderChunk(VecI chunkPos, Texture screenSurface, ChunkResolution resolution,

+ 4 - 0
src/PixiEditor/Styles/Templates/Timeline.axaml

@@ -75,6 +75,10 @@
                                     Content="{DynamicResource icon-duplicate}"
                                     ui1:Translator.TooltipKey="DUPLICATE_FRAME"
                                     Command="{TemplateBinding DuplicateKeyFrameCommand}" />
+                            <ToggleButton Classes="pixi-icon"
+                                    Content="onion-skining"
+                                    ui1:Translator.TooltipKey="TOGGLE_ONION_SKINNING"
+                                    Command="{TemplateBinding ToggleOnionSkinningCommand}" />
                             <Button Classes="pixi-icon"
                                     Content="{DynamicResource icon-trash}"
                                     ui1:Translator.TooltipKey="DELETE_FRAME"

+ 56 - 22
src/PixiEditor/ViewModels/Document/AnimationDataViewModel.cs

@@ -18,11 +18,12 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
     public DocumentViewModel Document { get; }
     protected DocumentInternalParts Internals { get; }
     public IReadOnlyCollection<IKeyFrameHandler> KeyFrames => keyFrames;
-    
+
     public IReadOnlyCollection<IKeyFrameHandler> AllKeyFrames => allKeyFrames;
 
     private KeyFrameCollection keyFrames = new KeyFrameCollection();
     private List<IKeyFrameHandler> allKeyFrames = new List<IKeyFrameHandler>();
+    private bool onionSkinningEnabled;
 
     public int ActiveFrameBindable
     {
@@ -46,15 +47,30 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
             if (Document.UpdateableChangeActive)
                 return;
 
-            Internals.ActionAccumulator.AddFinishedActions(new SetFrameRate_Action(value)); 
+            Internals.ActionAccumulator.AddFinishedActions(new SetFrameRate_Action(value));
+        }
+    }
+
+    public bool OnionSkinningEnabledBindable
+    {
+        get => onionSkinningEnabled;
+        set
+        {
+            if (Document.UpdateableChangeActive)
+                return;
+
+            Internals.ActionAccumulator.AddFinishedActions(new ToggleOnionSkinning_PassthroughAction(value));
         }
     }
 
     public int FirstFrame => keyFrames.Count > 0 ? keyFrames.Min(x => x.StartFrameBindable) : 0;
-    public int LastFrame => keyFrames.Count > 0 ? keyFrames.Max(x => x.StartFrameBindable + x.DurationBindable) 
+
+    public int LastFrame => keyFrames.Count > 0
+        ? keyFrames.Max(x => x.StartFrameBindable + x.DurationBindable)
         : DefaultEndFrame;
+
     public int FramesCount => LastFrame - FirstFrame + 1;
-    
+
     private double ActiveNormalizedTime => (double)(ActiveFrameBindable - FirstFrame) / FramesCount;
 
     private int DefaultEndFrame => FrameRateBindable; // 1 second
@@ -67,11 +83,13 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 
     public KeyFrameTime ActiveFrameTime => new KeyFrameTime(ActiveFrameBindable, ActiveNormalizedTime);
 
-    public void CreateRasterKeyFrame(Guid targetLayerGuid, int frame, Guid? toCloneFrom = null, int? frameToCopyFrom = null)
+    public void CreateRasterKeyFrame(Guid targetLayerGuid, int frame, Guid? toCloneFrom = null,
+        int? frameToCopyFrom = null)
     {
         if (!Document.UpdateableChangeActive)
         {
-            Internals.ActionAccumulator.AddFinishedActions(new CreateRasterKeyFrame_Action(targetLayerGuid, Guid.NewGuid(), Math.Max(1, frame),
+            Internals.ActionAccumulator.AddFinishedActions(new CreateRasterKeyFrame_Action(targetLayerGuid,
+                Guid.NewGuid(), Math.Max(1, frame),
                 frameToCopyFrom ?? -1, toCloneFrom ?? Guid.Empty));
         }
     }
@@ -83,7 +101,7 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
             for (var i = 0; i < keyFrameIds.Count; i++)
             {
                 var id = keyFrameIds[i];
-                if(i == keyFrameIds.Count - 1)
+                if (i == keyFrameIds.Count - 1)
                 {
                     Internals.ActionAccumulator.AddFinishedActions(new DeleteKeyFrame_Action(id));
                 }
@@ -94,7 +112,7 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
             }
         }
     }
-    
+
     public void ChangeKeyFramesStartPos(Guid[] infoIds, int infoDelta)
     {
         if (!Document.UpdateableChangeActive)
@@ -102,7 +120,15 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
             Internals.ActionAccumulator.AddActions(new KeyFramesStartPos_Action(infoIds.ToList(), infoDelta));
         }
     }
-    
+
+    public void ToggleOnionSkinning(bool value)
+    {
+        if (!Document.UpdateableChangeActive)
+        {
+            Internals.ActionAccumulator.AddFinishedActions(new ToggleOnionSkinning_PassthroughAction(value));
+        }
+    }
+
     public void EndKeyFramesStartPos()
     {
         if (!Document.UpdateableChangeActive)
@@ -110,33 +136,39 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
             Internals.ActionAccumulator.AddFinishedActions(new EndKeyFramesStartPos_Action());
         }
     }
-    
+
     public void SetFrameRate(int newFrameRate)
     {
         frameRateBindable = newFrameRate;
         OnPropertyChanged(nameof(FrameRateBindable));
         OnPropertyChanged(nameof(DefaultEndFrame));
     }
-    
+
     public void SetActiveFrame(int newFrame)
     {
         _activeFrameBindable = newFrame;
         OnPropertyChanged(nameof(ActiveFrameBindable));
     }
+    
+    public void SetOnionSkinning(bool value)
+    {
+        onionSkinningEnabled = value;
+        OnPropertyChanged(nameof(OnionSkinningEnabledBindable));
+    }
 
     public void SetFrameLength(Guid keyFrameId, int newStartFrame, int newDuration)
     {
-        if(TryFindKeyFrame(keyFrameId, out KeyFrameViewModel keyFrame))
+        if (TryFindKeyFrame(keyFrameId, out KeyFrameViewModel keyFrame))
         {
             keyFrame.SetStartFrame(newStartFrame);
             keyFrame.SetDuration(newDuration);
             keyFrames.NotifyCollectionChanged();
         }
     }
-    
+
     public void SetKeyFrameVisibility(Guid keyFrameId, bool isVisible)
     {
-        if(TryFindKeyFrame(keyFrameId, out KeyFrameViewModel keyFrame))
+        if (TryFindKeyFrame(keyFrameId, out KeyFrameViewModel keyFrame))
         {
             keyFrame.SetVisibility(isVisible);
             keyFrames.NotifyCollectionChanged();
@@ -153,7 +185,8 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
         else
         {
             var group =
-                new KeyFrameGroupViewModel(keyFrame.StartFrameBindable, keyFrame.DurationBindable, id, id, Document, Internals);
+                new KeyFrameGroupViewModel(keyFrame.StartFrameBindable, keyFrame.DurationBindable, id, id, Document,
+                    Internals);
             group.Children.Add((KeyFrameViewModel)keyFrame);
             keyFrames.Add(group);
         }
@@ -174,8 +207,8 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
             {
                 parent.Children.Remove(frame);
                 keyFrames.NotifyCollectionChanged(NotifyCollectionChangedAction.Remove, (KeyFrameViewModel)frame);
-                
-                if(parent.Children.Count == 0)
+
+                if (parent.Children.Count == 0)
                 {
                     keyFrames.Remove(parent as KeyFrameGroupViewModel);
                 }
@@ -185,7 +218,7 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
                 keyFrames.Remove(group);
             }
         });
-        
+
         allKeyFrames.RemoveAll(x => x.Id == keyFrameId);
     }
 
@@ -224,13 +257,13 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
                 parent.Children.Remove(frame);
                 framesToRemove.Add((KeyFrameViewModel)frame);
             });
-            
+
             allKeyFrames.RemoveAll(x => x.Id == keyFrame);
         }
-        
+
         keyFrames.NotifyCollectionChanged(NotifyCollectionChangedAction.Remove, framesToRemove);
     }
-    
+
     public bool FindKeyFrame<T>(Guid guid, out T keyFrameHandler) where T : IKeyFrameHandler
     {
         return TryFindKeyFrame<T>(keyFrames, null, guid, out keyFrameHandler, null);
@@ -243,7 +276,8 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
         return TryFindKeyFrame(keyFrames, null, id, out foundKeyFrame, onFound);
     }
 
-    private bool TryFindKeyFrame<T>(IReadOnlyCollection<IKeyFrameHandler> root, IKeyFrameGroupHandler parent, Guid id, out T? result,
+    private bool TryFindKeyFrame<T>(IReadOnlyCollection<IKeyFrameHandler> root, IKeyFrameGroupHandler parent, Guid id,
+        out T? result,
         Action<IKeyFrameHandler, IKeyFrameGroupHandler?> onFound) where T : IKeyFrameHandler
     {
         for (var i = 0; i < root.Count; i++)

+ 12 - 0
src/PixiEditor/ViewModels/SubViewModels/AnimationsViewModel.cs

@@ -47,6 +47,18 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
             activeDocument.AnimationDataViewModel.FramesCount,
             activeDocument.AnimationDataViewModel.AllKeyFrames.Count);
     }
+
+    [Command.Basic("PixiEditor.Animation.ToggleOnionSkinning", "TOGGLE_ONION_SKINNING",
+        "TOGGLE_ONION_SKINNING_DESCRIPTIVE",
+        ShortcutContext = typeof(TimelineDockViewModel), Key = Key.O, AnalyticsTrack = true)]
+    public void ToggleOnionSkinning()
+    {
+        if (Owner.DocumentManagerSubViewModel.ActiveDocument is null)
+            return;
+        
+        bool value = Owner.DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.OnionSkinningEnabledBindable;
+        Owner.DocumentManagerSubViewModel.ActiveDocument?.AnimationDataViewModel.ToggleOnionSkinning(!value);
+    }
     
     [Command.Basic("PixiEditor.Animation.DeleteKeyFrames", "DELETE_KEY_FRAMES", "DELETE_KEY_FRAMES_DESCRIPTIVE",
         ShortcutContext = typeof(TimelineDockViewModel), Key = Key.Delete, AnalyticsTrack = true)]

+ 9 - 0
src/PixiEditor/Views/Animations/Timeline.cs

@@ -85,6 +85,15 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
     public static readonly StyledProperty<int> DefaultEndFrameProperty = AvaloniaProperty.Register<Timeline, int>(
         nameof(DefaultEndFrame));
 
+    public static readonly StyledProperty<ICommand> ToggleOnionSkinningCommandProperty = AvaloniaProperty.Register<Timeline, ICommand>(
+        nameof(ToggleOnionSkinningCommand));
+
+    public ICommand ToggleOnionSkinningCommand
+    {
+        get => GetValue(ToggleOnionSkinningCommandProperty);
+        set => SetValue(ToggleOnionSkinningCommandProperty, value);
+    }
+
     public int DefaultEndFrame
     {
         get => GetValue(DefaultEndFrameProperty);

+ 1 - 0
src/PixiEditor/Views/Dock/TimelineDockView.axaml

@@ -18,6 +18,7 @@
         NewKeyFrameCommand="{xaml:Command PixiEditor.Animation.CreateRasterKeyFrame}"
         Fps="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.FrameRateBindable, Mode=TwoWay}"
         DefaultEndFrame="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.DefaultEndFrame}"
+        ToggleOnionSkinningCommand="{xaml:Command PixiEditor.Animation.ToggleOnionSkinning}"
         DuplicateKeyFrameCommand="{xaml:Command PixiEditor.Animation.DuplicateRasterKeyFrame}"
         DeleteKeyFrameCommand="{xaml:Command PixiEditor.Animation.DeleteKeyFrames, UseProvided=True}"
         ChangeKeyFramesLengthCommand="{xaml:Command PixiEditor.Animation.ChangeKeyFramesStartPos, UseProvided=True}"/>

+ 21 - 0
src/PixiEditor/Views/Rendering/Scene.cs

@@ -12,9 +12,12 @@ using Avalonia.Skia;
 using Avalonia.Threading;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Bridge;
 using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surfaces;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.DrawingApi.Skia;
 using PixiEditor.DrawingApi.Skia.Extensions;
 using PixiEditor.Extensions.UI.Overlays;
@@ -27,6 +30,7 @@ using PixiEditor.Views.Overlays;
 using PixiEditor.Views.Overlays.Pointers;
 using PixiEditor.Views.Visuals;
 using Bitmap = PixiEditor.DrawingApi.Core.Surfaces.Bitmap;
+using Colors = PixiEditor.DrawingApi.Core.ColorsImpl.Colors;
 using Image = PixiEditor.DrawingApi.Core.Surfaces.ImageData.Image;
 using Point = Avalonia.Point;
 
@@ -522,6 +526,7 @@ internal class DrawSceneOperation : SkiaDrawOperation
 
         using var ctx = DrawingBackendApi.Current.RenderOnDifferentGrContext(lease.GrContext);
 
+        RenderOnionSkin(canvas);
 
         /*var matrixValues = new float[ColorMatrix.Width * ColorMatrix.Height];
         ColorMatrix.TryGetMembers(matrixValues);*/
@@ -541,6 +546,22 @@ internal class DrawSceneOperation : SkiaDrawOperation
         canvas.Restore();
     }
 
+    private void RenderOnionSkin(SKCanvas canvas)
+    {
+        if (Document.AnimationDataViewModel.OnionSkinningEnabledBindable)
+        {
+            var onionSkinFrame = Document.Renderer.LastOnionSkinningFrame;
+            if (onionSkinFrame == null)
+            {
+                return;
+            }
+
+            using Paint onionSkinPaint = new Paint();
+            onionSkinPaint.Color = Colors.White.WithAlpha(64); // 25% opacity
+            canvas.DrawImage((SKImage)onionSkinFrame.Native, 0, 0, onionSkinPaint.Native as SKPaint);
+        }
+    }
+
     private RectI FindRectToRender(float finalScale)
     {
         ShapeCorners surfaceInViewportSpace = SurfaceToViewport(new RectI(VecI.Zero, Surface.Size), finalScale);