Browse Source

Fixed copying multiple layers

flabbet 10 months ago
parent
commit
1005c48659

+ 2 - 2
src/PixiEditor/Models/Controllers/ClipboardController.cs

@@ -64,7 +64,7 @@ internal static class ClipboardController
 
         if (!document.SelectionPathBindable.IsEmpty)
         {
-            var surface = document.TryExtractArea((RectI)document.SelectionPathBindable.TightBounds);
+            var surface = document.TryExtractAreaFromSelected((RectI)document.SelectionPathBindable.TightBounds);
             if (surface.IsT0)
                 return;
 
@@ -78,7 +78,7 @@ internal static class ClipboardController
         }
         else if(document.TransformViewModel.TransformActive)
         {
-            var surface = document.TryExtractArea((RectI)document.TransformViewModel.Corners.AABBBounds.RoundOutwards());
+            var surface = document.TryExtractAreaFromSelected((RectI)document.TransformViewModel.Corners.AABBBounds.RoundOutwards());
             if (surface.IsT0 || surface.IsT1)
                 return;
             

+ 68 - 41
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -30,6 +30,7 @@ using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Bridge;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
 using PixiEditor.DrawingApi.Core.Surfaces.Vector;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Extensions.CommonApi.Palettes;
@@ -51,6 +52,7 @@ using PixiEditor.Parser.Skia;
 using PixiEditor.ViewModels.Document.Nodes;
 using PixiEditor.ViewModels.Document.TransformOverlays;
 using PixiEditor.Views.Overlays.SymmetryOverlay;
+using BlendMode = PixiEditor.DrawingApi.Core.Surfaces.BlendMode;
 using Color = PixiEditor.DrawingApi.Core.ColorsImpl.Color;
 using Colors = PixiEditor.DrawingApi.Core.ColorsImpl.Colors;
 using Node = PixiEditor.Parser.Graph.Node;
@@ -154,7 +156,9 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     private readonly HashSet<IStructureMemberHandler> softSelectedStructureMembers = new();
 
     public bool BlockingUpdateableChangeActive => Internals.ChangeController.IsBlockingChangeActive;
-    public bool IsChangeFeatureActive<T>() where T : IExecutorFeature => Internals.ChangeController.IsChangeOfTypeActive<T>();
+
+    public bool IsChangeFeatureActive<T>() where T : IExecutorFeature =>
+        Internals.ChangeController.IsChangeOfTypeActive<T>();
 
     public bool PointerDragChangeInProgress =>
         Internals.ChangeController.IsBlockingChangeActive && Internals.ChangeController.LeftMousePressed;
@@ -558,45 +562,63 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     /// Takes the selected area and converts it into a surface
     /// </summary>
     /// <returns><see cref="Error"/> on error, <see cref="None"/> for empty <see cref="Surface"/>, <see cref="Surface"/> otherwise.</returns>
-    public OneOf<Error, None, (Surface, RectI)> TryExtractArea(RectI bounds,
-        IStructureMemberHandler? layerToExtractFrom = null)
+    public OneOf<Error, None, (Surface, RectI)> TryExtractAreaFromSelected(
+        RectI bounds)
     {
-        layerToExtractFrom ??= SelectedStructureMember;
-        if (layerToExtractFrom is not ILayerHandler layerVm)
+        var selectedLayers = ExtractSelectedLayers();
+        selectedLayers.Reverse();
+        if (selectedLayers.Count == 0)
             return new Error();
         if (bounds.IsZeroOrNegativeArea)
             return new None();
 
-        IReadOnlyStructureNode? layer = Internals.Tracker.Document.FindMember(layerVm.Id);
-        if (layer is null)
-            return new Error();
-        
-        RectI? memberImageBounds;
-        try
+        RectI finalBounds = default;
+
+        for (int i = 0; i < selectedLayers.Count; i++)
         {
-            if (layer is IReadOnlyImageNode imgNode)
+            var layerVm = StructureHelper.Find(selectedLayers[i]); 
+            IReadOnlyStructureNode? layer = Internals.Tracker.Document.FindMember(layerVm.Id);
+            if (layer is null)
+                return new Error();
+
+            RectI? memberImageBounds;
+            try
+            {
+                if (layer is IReadOnlyImageNode imgNode)
+                {
+                    memberImageBounds = imgNode.GetLayerImageAtFrame(AnimationDataViewModel.ActiveFrameBindable)
+                        .FindChunkAlignedMostUpToDateBounds();
+                }
+                else
+                {
+                    memberImageBounds = (RectI?)layer.GetTightBounds(AnimationDataViewModel.ActiveFrameTime);
+                }
+            }
+            catch (ObjectDisposedException)
+            {
+                return new Error();
+            }
+
+            if (memberImageBounds is null)
+                continue;
+
+            RectI combinedBounds = bounds.Intersect(memberImageBounds.Value);
+            combinedBounds = combinedBounds.Intersect(new RectI(VecI.Zero, SizeBindable));
+
+            if (combinedBounds.IsZeroOrNegativeArea)
+                continue;
+
+            if (i == 0 || finalBounds == default)
             {
-                memberImageBounds = imgNode.GetLayerImageAtFrame(AnimationDataViewModel.ActiveFrameBindable)
-                    .FindChunkAlignedMostUpToDateBounds();
+                finalBounds = combinedBounds;
             }
             else
             {
-                memberImageBounds = (RectI?)layer.GetTightBounds(AnimationDataViewModel.ActiveFrameTime);
+                finalBounds = finalBounds.Union(combinedBounds);
             }
         }
-        catch (ObjectDisposedException)
-        {
-            return new Error();
-        }
 
-        if (memberImageBounds is null)
-            return new None();
-        bounds = bounds.Intersect(memberImageBounds.Value);
-        bounds = bounds.Intersect(new RectI(VecI.Zero, SizeBindable));
-        if (bounds.IsZeroOrNegativeArea)
-            return new None();
-
-        Surface output = new(bounds.Size);
+        Surface output = new(finalBounds.Size);
 
         VectorPath clipPath = new VectorPath(SelectionPathBindable) { FillType = PathFillType.EvenOdd };
         clipPath.Transform(Matrix3X3.CreateTranslation(-bounds.X, -bounds.Y));
@@ -605,21 +627,26 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         {
             output.DrawingSurface.Canvas.ClipPath(clipPath);
         }
+        using Paint paint = new Paint() { BlendMode = BlendMode.SrcOver };
 
-        try
+        foreach (var layer in selectedLayers)
         {
-            using Texture rendered = Renderer.RenderLayer(layerVm.Id, ChunkResolution.Full, AnimationDataViewModel.ActiveFrameTime);
-            using Image snapshot = rendered.DrawingSurface.Snapshot(bounds);
-            output.DrawingSurface.Canvas.DrawImage(snapshot, 0, 0);
-        }
-        catch (ObjectDisposedException)
-        {
-            output.Dispose();
-            return new Error();
+            try
+            {
+                var layerVm = Internals.Tracker.Document.FindMember(layer);
+                using Texture rendered = Renderer.RenderLayer(layerVm.Id, ChunkResolution.Full,
+                    AnimationDataViewModel.ActiveFrameTime);
+                using Image snapshot = rendered.DrawingSurface.Snapshot(bounds);
+                output.DrawingSurface.Canvas.DrawImage(snapshot, 0, 0, paint);
+            }
+            catch (ObjectDisposedException)
+            {
+                output.Dispose();
+                return new Error();
+            }
         }
 
         output.DrawingSurface.Canvas.Restore();
-
         return (output, bounds);
     }
 
@@ -716,7 +743,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
     #region Internal Methods
 
-    // these are intended to only be called from DocumentUpdater
+// these are intended to only be called from DocumentUpdater
 
     public void InternalRaiseLayersChanged(LayersChangedEventArgs args) => LayersChanged?.Invoke(this, args);
 
@@ -828,12 +855,12 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             var foundMember = StructureHelper.Find(member);
             if (foundMember != null)
             {
-                if (foundMember is ImageLayerNodeViewModel layer && selectedMembers.Contains(foundMember.Id) &&
+                if (foundMember is ILayerHandler layer && selectedMembers.Contains(foundMember.Id) &&
                     !result.Contains(layer.Id))
                 {
                     result.Add(layer.Id);
                 }
-                else if (foundMember is FolderNodeViewModel folder && selectedMembers.Contains(foundMember.Id))
+                else if (foundMember is IFolderHandler folder && selectedMembers.Contains(foundMember.Id))
                 {
                     if (includeFoldersWithMask && folder.HasMaskBindable && !result.Contains(folder.Id))
                         result.Add(folder.Id);
@@ -850,7 +877,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         OnPropertyChanged(nameof(AllChangesSaved));
     }
 
-    private void ExtractSelectedLayers(FolderNodeViewModel folder, List<Guid> list,
+    private void ExtractSelectedLayers(IFolderHandler folder, List<Guid> list,
         bool includeFoldersWithMask)
     {
         foreach (var member in folder.Children)