Quellcode durchsuchen

onio somewhat working, resolution todo

flabbet vor 1 Jahr
Ursprung
Commit
c22de0b316

+ 36 - 19
src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs

@@ -13,27 +13,36 @@ namespace PixiEditor.ChangeableDocument.Rendering;
 
 public class DocumentRenderer
 {
+    private Paint ClearPaint { get; } = new Paint()
+    {
+        BlendMode = BlendMode.Src, Color = PixiEditor.DrawingApi.Core.ColorsImpl.Colors.Transparent
+    };
+
     public DocumentRenderer(IReadOnlyDocument document)
     {
         Document = document;
     }
 
     private IReadOnlyDocument Document { get; }
-    public Image LastOnionSkinningFrame { get; set; }
+    public Texture OnionSkinTexture { get; set; }
 
-    public Texture RenderDocument(KeyFrameTime frameTime, ChunkResolution resolution, Texture toDrawOn = null, Paint paint = null)
+    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);
+            VecI size = new VecI(
+                Math.Min(Document.Size.X, resolution.PixelSize() * sizeInChunks.X),
+                Math.Min(Document.Size.Y, resolution.PixelSize() * sizeInChunks.Y));
+            toDrawOn = new Texture(size);
         }
-        
+
         for (int x = 0; x < sizeInChunks.X; x++)
         {
             for (int y = 0; y < sizeInChunks.Y; y++)
@@ -43,12 +52,18 @@ public class DocumentRenderer
                 if (chunk.IsT0)
                 {
                     toDrawOn.DrawingSurface.Canvas.DrawSurface(
-                        chunk.AsT0.Surface.DrawingSurface, 
-                        resolution.PixelSize(), resolution.PixelSize(), paint);
+                        chunk.AsT0.Surface.DrawingSurface,
+                        chunkPos.Multiply(new VecI(resolution.PixelSize())), paint);
+                }
+                else
+                {
+                    var pos = chunkPos * resolution.PixelSize();
+                    toDrawOn.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, resolution.PixelSize(),
+                        resolution.PixelSize(), ClearPaint);
                 }
             }
         }
-        
+
         return toDrawOn;
     }
 
@@ -84,9 +99,10 @@ public class DocumentRenderer
                 return new EmptyChunk();
             }
 
-            var result = ChunkFromResult(resolution, transformedClippingRect, evaluated.DrawingSurface.Snapshot(), context);
+            var result = ChunkFromResult(resolution, transformedClippingRect, evaluated.DrawingSurface.Snapshot(),
+                context);
             evaluated.Dispose();
-            
+
             return result;
         }
         catch (ObjectDisposedException)
@@ -120,7 +136,8 @@ public class DocumentRenderer
         }
     }
 
-    private static OneOf<Chunk, EmptyChunk> RenderChunkOnGraph(VecI chunkPos, ChunkResolution resolution, RectI? globalClippingRect,
+    private static OneOf<Chunk, EmptyChunk> RenderChunkOnGraph(VecI chunkPos, ChunkResolution resolution,
+        RectI? globalClippingRect,
         IReadOnlyNodeGraph graph, RenderingContext context)
     {
         RectI? transformedClippingRect = TransformClipRect(globalClippingRect, resolution, chunkPos);
@@ -148,11 +165,11 @@ public class DocumentRenderer
         int height = (int)(ChunkyImage.FullChunkSize * resolution.Multiplier());
 
         RectD sourceRect = new(x, y, width, height);
-            
+
         RectD availableRect = new(0, 0, evaluated.Size.X, evaluated.Size.Y);
-            
+
         sourceRect = sourceRect.Intersect(availableRect);
-            
+
         if (sourceRect.IsZeroOrNegativeArea)
         {
             chunk.Dispose();
@@ -160,8 +177,8 @@ public class DocumentRenderer
         }
 
         using var chunkSnapshot = evaluated.DrawingSurface.Snapshot((RectI)sourceRect);
-            
-        if(context.IsDisposed) return new EmptyChunk();
+
+        if (context.IsDisposed) return new EmptyChunk();
 
         chunk.Surface.DrawingSurface.Canvas.DrawImage(chunkSnapshot, 0, 0, context.ReplacingPaintWithOpacity);
 
@@ -175,7 +192,7 @@ public class DocumentRenderer
         NodeGraph membersOnlyGraph = new();
 
         OutputNode outputNode = new();
-        
+
         membersOnlyGraph.AddNode(outputNode);
 
         List<LayerNode> layersInOrder = new();
@@ -194,11 +211,11 @@ public class DocumentRenderer
         {
             var clone = (LayerNode)layer.Clone();
             membersOnlyGraph.AddNode(clone);
-            
+
             clone.Output.ConnectTo(lastInput);
             lastInput = clone.Background;
         }
-        
+
         return membersOnlyGraph;
     }
 

+ 4 - 0
src/PixiEditor.DrawingApi.Core/Surfaces/ImageData/Image.cs

@@ -24,13 +24,17 @@ namespace PixiEditor.DrawingApi.Core.Surfaces.ImageData
         public ImageInfo Info => DrawingBackendApi.Current.ImageImplementation.GetImageInfo(ObjectPointer);
         
         public VecI Size => new VecI(Width, Height);
+        public bool IsDisposed => isDisposed;
         
+        private bool isDisposed;
+
         public Image(IntPtr objPtr) : base(objPtr)
         {
         }
 
         public override void Dispose()
         {
+            isDisposed = true;
             DrawingBackendApi.Current.ImageImplementation.DisposeImage(this);
         }
 

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

@@ -20,4 +20,6 @@ internal interface IAnimationHandler
     public void RemoveSelectedKeyFrame(Guid keyFrameId);
     public void ClearSelectedKeyFrames();
     public void SetOnionSkinning(bool enabled);
+    public int FirstFrame { get; }
+    public int LastFrame { get; }
 }

+ 3 - 0
src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs

@@ -137,6 +137,9 @@ internal class AffectedAreasGatherer
                     AddWholeCanvasToMainImage();
                     AddWholeCanvasToEveryImagePreview();
                     break;
+                case ToggleOnionSkinning_PassthroughAction:
+                    AddWholeCanvasToMainImage();
+                    break;
             }
         }
     }

+ 73 - 10
src/PixiEditor/Models/Rendering/CanvasUpdater.cs

@@ -4,6 +4,7 @@ using Avalonia.Threading;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Numerics;
@@ -22,8 +23,8 @@ internal class CanvasUpdater
     private readonly IDocument doc;
     private readonly DocumentInternalParts internals;
 
-    private Image? lastRenderedFrame;
-    private int lastRenderedFrameNumber;
+    private Dictionary<int, Texture> renderedFramesCache = new();
+    private int lastRenderedFrameNumber = -1;
 
     private static readonly Paint ReplacingPaint = new() { BlendMode = BlendMode.Src };
 
@@ -193,6 +194,12 @@ internal class CanvasUpdater
     private void UpdateMainImage(Dictionary<ChunkResolution, HashSet<VecI>> chunksToRerender,
         RectI? globalClippingRectangle, List<IRenderInfo> infos)
     {
+        if (chunksToRerender.Count == 0)
+            return;
+
+        //TODO: based on active viewport resolution
+        UpdateOnionSkinning(doc.Surfaces[ChunkResolution.Full]);
+
         foreach (var (resolution, chunks) in chunksToRerender)
         {
             int chunkSize = resolution.PixelSize();
@@ -217,27 +224,83 @@ internal class CanvasUpdater
                 ));
             }
         }
-
-
-        UpdateOnionSkinning(doc.Surfaces[ChunkResolution.Full]);
     }
 
-    private void UpdateOnionSkinning(Texture screenSurface)
+    private void UpdateOnionSkinning(Texture lastRendered)
     {
         if (doc.AnimationHandler.OnionSkinningEnabledBindable)
         {
+            if (lastRenderedFrameNumber > 0)
+            {
+                UpdateLastRenderedFrame(lastRendered, lastRenderedFrameNumber);
+            }
+
             if (lastRenderedFrameNumber != doc.AnimationHandler.ActiveFrameBindable)
             {
-                lastRenderedFrame?.Dispose();
-                lastRenderedFrame = screenSurface.DrawingSurface.Snapshot();
-                lastRenderedFrameNumber = doc.AnimationHandler.ActiveFrameBindable;
-                doc.Renderer.LastOnionSkinningFrame = lastRenderedFrame;
+                int previousFrameIndex = doc.AnimationHandler.ActiveFrameBindable - 1;
+                int nextFrameIndex = doc.AnimationHandler.ActiveFrameBindable + 1;
+
+                if (doc.Renderer.OnionSkinTexture == null || doc.Renderer.OnionSkinTexture.Size != doc.SizeBindable)
+                {
+                    doc.Renderer.OnionSkinTexture = new Texture(doc.SizeBindable);
+                }
+
+                doc.Renderer.OnionSkinTexture.DrawingSurface.Canvas.Clear();
+
+                if (!renderedFramesCache.ContainsKey(previousFrameIndex))
+                {
+                    RenderNextOnionSkinningFrame(previousFrameIndex);
+                }
+
+                if (!renderedFramesCache.ContainsKey(nextFrameIndex))
+                {
+                    RenderNextOnionSkinningFrame(nextFrameIndex);
+                }
+
+                DrawOnionSkinningFrame(previousFrameIndex, doc.Renderer.OnionSkinTexture);
+                DrawOnionSkinningFrame(nextFrameIndex, doc.Renderer.OnionSkinTexture);
             }
 
             lastRenderedFrameNumber = doc.AnimationHandler.ActiveFrameBindable;
         }
     }
 
+    private void RenderNextOnionSkinningFrame(int frameIndex)
+    {
+        int firstFrame = doc.AnimationHandler.FirstFrame;
+        int lastFrame = doc.AnimationHandler.LastFrame;
+        if (frameIndex < firstFrame || frameIndex >= lastFrame)
+            return;
+
+        double newNormalizedTime = (double)(frameIndex - firstFrame) / (lastFrame - firstFrame);
+
+        KeyFrameTime newFrameTime = new(frameIndex, newNormalizedTime);
+
+        using Texture rendered = doc.Renderer.RenderDocument(newFrameTime, ChunkResolution.Full);
+        UpdateLastRenderedFrame(rendered, frameIndex);
+    }
+
+    private void UpdateLastRenderedFrame(Texture rendered, int index)
+    {
+        if (renderedFramesCache.ContainsKey(index))
+        {
+            renderedFramesCache[index].Dispose();
+            renderedFramesCache[index] = new Texture(rendered);
+        }
+        else
+        {
+            renderedFramesCache.Add(index, new Texture(rendered));
+        }
+    }
+
+    private void DrawOnionSkinningFrame(int frameIndex, Texture onionSkinTexture)
+    {
+        if (renderedFramesCache.TryGetValue(frameIndex, out var frame))
+        {
+            onionSkinTexture.DrawingSurface.Canvas.DrawSurface(frame.DrawingSurface, 0, 0);
+        }
+    }
+
     private void RenderChunk(VecI chunkPos, Texture screenSurface, ChunkResolution resolution,
         RectI? globalClippingRectangle, RectI? globalScaledClippingRectangle)
     {

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

@@ -78,7 +78,9 @@
                             <ToggleButton Classes="pixi-icon"
                                     Content="onion-skining"
                                     ui1:Translator.TooltipKey="TOGGLE_ONION_SKINNING"
-                                    Command="{TemplateBinding ToggleOnionSkinningCommand}" />
+                                    Command="{TemplateBinding ToggleOnionSkinningCommand}" 
+                                    CommandParameter="{Binding Path=IsChecked, RelativeSource={RelativeSource Self}}"
+                                    />
                             <Button Classes="pixi-icon"
                                     Content="{DynamicResource icon-trash}"
                                     ui1:Translator.TooltipKey="DELETE_FRAME"

+ 2 - 3
src/PixiEditor/ViewModels/SubViewModels/AnimationsViewModel.cs

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

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

@@ -18,7 +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}"
+        ToggleOnionSkinningCommand="{xaml:Command PixiEditor.Animation.ToggleOnionSkinning, UseProvided=True}"
         DuplicateKeyFrameCommand="{xaml:Command PixiEditor.Animation.DuplicateRasterKeyFrame}"
         DeleteKeyFrameCommand="{xaml:Command PixiEditor.Animation.DeleteKeyFrames, UseProvided=True}"
         ChangeKeyFramesLengthCommand="{xaml:Command PixiEditor.Animation.ChangeKeyFramesStartPos, UseProvided=True}"/>

+ 20 - 6
src/PixiEditor/Views/Rendering/Scene.cs

@@ -151,7 +151,8 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         RectD dirtyBounds = new RectD(0, 0, Document.Width / resolutionScale, Document.Height / resolutionScale);
         Rect dirtyRect = new Rect(0, 0, Document.Width / resolutionScale, Document.Height / resolutionScale);
 
-        using var operation = new DrawSceneOperation(Surface, Document, CanvasPos, Scale * resolutionScale, angle,
+        using var operation = new DrawSceneOperation(Surface, Document, CanvasPos, Scale * resolutionScale,
+            resolutionScale, angle,
             FlipX, FlipY,
             dirtyRect,
             Bounds,
@@ -484,6 +485,7 @@ internal class DrawSceneOperation : SkiaDrawOperation
     public DocumentViewModel Document { get; set; }
     public VecD ContentPosition { get; set; }
     public double Scale { get; set; }
+    public double ResolutionScale { get; set; }
     public double Angle { get; set; }
     public bool FlipX { get; set; }
     public bool FlipY { get; set; }
@@ -496,6 +498,7 @@ internal class DrawSceneOperation : SkiaDrawOperation
     private bool hardwareAccelerationAvailable = DrawingBackendApi.Current.IsHardwareAccelerated;
 
     public DrawSceneOperation(Texture surface, DocumentViewModel document, VecD contentPosition, double scale,
+        double resolutionScale,
         double angle, bool flipX, bool flipY, Rect dirtyBounds, Rect viewportBounds, double opacity) : base(dirtyBounds)
     {
         Surface = surface;
@@ -506,6 +509,7 @@ internal class DrawSceneOperation : SkiaDrawOperation
         FlipX = flipX;
         FlipY = flipY;
         ViewportBounds = viewportBounds;
+        ResolutionScale = resolutionScale;
         _paint.Color = _paint.Color.WithAlpha((byte)(opacity * 255));
         SurfaceRectToRender = FindRectToRender((float)scale);
     }
@@ -530,7 +534,7 @@ internal class DrawSceneOperation : SkiaDrawOperation
 
         /*var matrixValues = new float[ColorMatrix.Width * ColorMatrix.Height];
         ColorMatrix.TryGetMembers(matrixValues);*/
-        
+
         if (!hardwareAccelerationAvailable)
         {
             // snapshotting wanted region on CPU is faster than rendering whole surface on CPU,
@@ -550,18 +554,28 @@ internal class DrawSceneOperation : SkiaDrawOperation
     {
         if (Document.AnimationDataViewModel.OnionSkinningEnabledBindable)
         {
-            var onionSkinFrame = Document.Renderer.LastOnionSkinningFrame;
-            if (onionSkinFrame == null)
+            var onionSkinTexture = Document.Renderer.OnionSkinTexture;
+
+            if (onionSkinTexture == 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);
+            onionSkinPaint.Color = Colors.White.WithAlpha(128); // 50% opacity
+
+            int count = canvas.Save();
+
+            canvas.Scale(1f / (float)ResolutionScale, 1f / (float)ResolutionScale);
+
+            canvas.DrawSurface(onionSkinTexture.DrawingSurface.Native as SKSurface, 0, 0,
+                onionSkinPaint.Native as SKPaint);
+
+            canvas.RestoreToCount(count);
         }
     }
 
+
     private RectI FindRectToRender(float finalScale)
     {
         ShapeCorners surfaceInViewportSpace = SurfaceToViewport(new RectI(VecI.Zero, Surface.Size), finalScale);