فهرست منبع

Implemented onion skinning rendering

Krzysztof Krysiński 1 هفته پیش
والد
کامیت
f56b3ed7de

+ 1 - 0
src/PixiEditor/Models/Position/ViewportInfo.cs

@@ -20,6 +20,7 @@ internal readonly record struct ViewportInfo(
     ChunkResolution Resolution,
     Guid Id,
     bool Delayed,
+    bool IsScene,
     Action InvalidateVisual)
 {
 }

+ 56 - 5
src/PixiEditor/Models/Rendering/SceneRenderer.cs

@@ -91,11 +91,9 @@ internal class SceneRenderer : IDisposable
     {
         /*if (Document.Renderer.IsBusy || DocumentViewModel.Busy ||
             target.DeviceClipBounds.Size.ShortestAxis <= 0) return;*/
-        //RenderOnionSkin(target, resolution, samplingOptions, targetOutput);
 
         /*TODO:
          - [ ] Onion skinning
-         - [ ] Previews generation
          - [ ] Rendering optimizer
          - [?] Render thread and proper locking/synchronization
          */
@@ -118,7 +116,7 @@ internal class SceneRenderer : IDisposable
         if (shouldRerender)
         {
             return RenderGraph(renderTargetSize, targetMatrix, viewportId, resolution, samplingOptions, affectedArea,
-                visibleDocumentRegion, targetOutput, finalGraph, previewTextures);
+                visibleDocumentRegion, targetOutput, viewport.IsScene, finalGraph, previewTextures);
         }
 
         var cachedTexture = DocumentViewModel.SceneTextures[viewportId];
@@ -131,6 +129,7 @@ internal class SceneRenderer : IDisposable
         AffectedArea area,
         RectI? visibleDocumentRegion,
         string? targetOutput,
+        bool canRenderOnionSkinning,
         IReadOnlyNodeGraph finalGraph, Dictionary<Guid, List<PreviewRenderRequest>>? previewTextures)
     {
         DrawingSurface renderTarget = null;
@@ -158,6 +157,30 @@ internal class SceneRenderer : IDisposable
             renderTarget.Canvas.SetMatrix(targetMatrix);
         }
 
+        bool renderOnionSkinning = canRenderOnionSkinning &&
+                                   DocumentViewModel.AnimationHandler.OnionSkinningEnabledBindable;
+
+        var animationData = Document.AnimationData;
+        double onionOpacity = animationData.OnionOpacity / 100.0;
+        double alphaFalloffMultiplier = 1.0 / animationData.OnionFrames;
+        if (renderOnionSkinning)
+        {
+            for (int i = 1; i <= animationData.OnionFrames; i++)
+            {
+                int frame = DocumentViewModel.AnimationHandler.ActiveFrameTime.Frame - i;
+                if (frame < DocumentViewModel.AnimationHandler.FirstVisibleFrame)
+                {
+                    break;
+                }
+
+                double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
+                RenderContext onionContext = new(renderTarget, frame, resolution, finalSize, Document.Size,
+                    Document.ProcessingColorSpace, samplingOptions, finalOpacity);
+                onionContext.TargetOutput = targetOutput;
+                finalGraph.Execute(onionContext);
+            }
+        }
+
         RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
             resolution, finalSize, Document.Size, Document.ProcessingColorSpace, samplingOptions);
         context.TargetOutput = targetOutput;
@@ -166,12 +189,31 @@ internal class SceneRenderer : IDisposable
         context.PreviewTextures = previewTextures;
         finalGraph.Execute(context);
 
+        if (renderOnionSkinning)
+        {
+            for (int i = 1; i <= animationData.OnionFrames; i++)
+            {
+                int frame = DocumentViewModel.AnimationHandler.ActiveFrameTime.Frame + i;
+                if (frame >= DocumentViewModel.AnimationHandler.LastFrame)
+                {
+                    break;
+                }
+
+                double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
+                RenderContext onionContext = new(renderTarget, frame, resolution, finalSize, Document.Size,
+                    Document.ProcessingColorSpace, samplingOptions, finalOpacity);
+                onionContext.TargetOutput = targetOutput;
+                finalGraph.Execute(onionContext);
+            }
+        }
+
         renderTarget.Canvas.Restore();
 
         return renderTexture;
     }
 
-    private static VecI SolveRenderOutputSize(string? targetOutput, IReadOnlyNodeGraph finalGraph, VecI documentSize)
+    private static VecI SolveRenderOutputSize(string? targetOutput, IReadOnlyNodeGraph finalGraph,
+        VecI documentSize)
     {
         VecI finalSize = documentSize;
         if (targetOutput != null)
@@ -207,7 +249,8 @@ internal class SceneRenderer : IDisposable
         IReadOnlyNodeGraph finalGraph, Dictionary<Guid, List<PreviewRenderRequest>>? previewTextures,
         RectI? visibleDocumentRegion)
     {
-        if (!DocumentViewModel.SceneTextures.TryGetValue(viewportId, out var cachedTexture) || cachedTexture == null ||
+        if (!DocumentViewModel.SceneTextures.TryGetValue(viewportId, out var cachedTexture) ||
+            cachedTexture == null ||
             cachedTexture.IsDisposed)
         {
             return true;
@@ -223,6 +266,9 @@ internal class SceneRenderer : IDisposable
             ChunkResolution = resolution,
             HighResRendering = HighResRendering,
             TargetOutput = targetOutput,
+            OnionFrames = Document.AnimationData.OnionFrames,
+            OnionOpacity = Document.AnimationData.OnionOpacity,
+            OnionSkinning = DocumentViewModel.AnimationHandler.OnionSkinningEnabledBindable,
             GraphCacheHash = finalGraph.GetCacheHash(),
             ZoomLevel = matrix.ScaleX,
             VisibleDocumentRegion =
@@ -362,11 +408,16 @@ struct RenderState
     public int GraphCacheHash { get; set; }
     public RectD VisibleDocumentRegion { get; set; }
     public double ZoomLevel { get; set; }
+    public int OnionFrames { get; set; }
+    public double OnionOpacity { get; set; }
+    public bool OnionSkinning { get; set; }
 
     public bool ShouldRerender(RenderState other)
     {
         return !ChunkResolution.Equals(other.ChunkResolution) || HighResRendering != other.HighResRendering ||
                TargetOutput != other.TargetOutput || GraphCacheHash != other.GraphCacheHash ||
+                OnionFrames != other.OnionFrames || Math.Abs(OnionOpacity - other.OnionOpacity) > 0.05 ||
+               OnionSkinning != other.OnionSkinning ||
                VisibleRegionChanged(other) || ZoomDiff(other) > 0;
     }
 

+ 1 - 0
src/PixiEditor/Views/Main/ViewportControls/FixedViewport.axaml.cs

@@ -150,6 +150,7 @@ internal partial class FixedViewport : UserControl, INotifyPropertyChanged
             CalculateResolution(),
             GuidValue,
             Delayed,
+            false,
             ForceRefreshFinalImage);
     }
 

+ 1 - 1
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs

@@ -528,7 +528,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
             Scene.CalculateTransformMatrix().ToSKMatrix().ToMatrix3X3(),
             CalculateVisibleRegion(),
             ViewportRenderOutput, Scene.CalculateSampling(), Dimensions, CalculateResolution(), GuidValue, Delayed,
-            ForceRefreshFinalImage);
+            true, ForceRefreshFinalImage);
     }
 
     private void Image_MouseDown(object? sender, PointerPressedEventArgs e)