Browse Source

Merge pull request #437 from PixiEditor/apply-mask

Added Apply Mask feature
Krzysztof Krysiński 2 years ago
parent
commit
c012b0f912

+ 20 - 7
src/ChunkyImageLib/ChunkyImage.cs

@@ -538,6 +538,16 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             EnqueueOperation(operation);
         }
     }
+    
+    public void EnqueueApplyMask(ChunkyImage mask)
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            ApplyMaskOperation operation = new(mask);
+            EnqueueOperation(operation);
+        }
+    }
 
     /// <param name="customBounds">Bounds used for affected chunks, will be computed from path in O(n) if null is passed</param>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
@@ -655,12 +665,15 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     {
         List<IDrawOperation> operations = new(4) { operation };
 
-        if (horizontalSymmetryAxis is not null && verticalSymmetryAxis is not null)
-            operations.Add(operation.AsMirrored(verticalSymmetryAxis, horizontalSymmetryAxis));
-        if (horizontalSymmetryAxis is not null)
-            operations.Add(operation.AsMirrored(null, horizontalSymmetryAxis));
-        if (verticalSymmetryAxis is not null)
-            operations.Add(operation.AsMirrored(verticalSymmetryAxis, null));
+        if (operation is IMirroredDrawOperation mirroredOperation)
+        {
+            if (horizontalSymmetryAxis is not null && verticalSymmetryAxis is not null)
+                operations.Add(mirroredOperation.AsMirrored(verticalSymmetryAxis, horizontalSymmetryAxis));
+            if (horizontalSymmetryAxis is not null)
+                operations.Add(mirroredOperation.AsMirrored(null, horizontalSymmetryAxis));
+            if (verticalSymmetryAxis is not null)
+                operations.Add(mirroredOperation.AsMirrored(verticalSymmetryAxis, null));
+        }
 
         foreach (var op in operations)
         {
@@ -854,7 +867,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <returns>
-    /// All chunks that have something in them, including latest (uncommitted) ones
+    ///     All chunks that have something in them, including latest (uncommitted) ones
     /// </returns>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     public HashSet<VecI> FindAllChunks()

+ 34 - 0
src/ChunkyImageLib/Operations/ApplyMaskOperation.cs

@@ -0,0 +1,34 @@
+using ChunkyImageLib.DataHolders;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+
+namespace ChunkyImageLib.Operations;
+
+internal class ApplyMaskOperation : IDrawOperation
+{
+    private ChunkyImage mask;
+    private Paint clippingPaint = new Paint() { BlendMode = BlendMode.DstIn };
+
+    public bool IgnoreEmptyChunks => true;
+
+    public ApplyMaskOperation(ChunkyImage maskToApply)
+    {
+        mask = maskToApply;
+    }
+    
+    public HashSet<VecI> FindAffectedChunks(VecI imageSize)
+    {
+        return mask.FindCommittedChunks();
+    }
+    
+    public void DrawOnChunk(Chunk chunk, VecI chunkPos)
+    {
+        mask.DrawCommittedChunkOn(chunkPos, chunk.Resolution, chunk.Surface.DrawingSurface, VecI.Zero, clippingPaint);
+    }
+
+    public void Dispose()
+    {
+        clippingPaint.Dispose();
+    }
+}

+ 1 - 1
src/ChunkyImageLib/Operations/BresenhamLineOperation.cs

@@ -5,7 +5,7 @@ using PixiEditor.DrawingApi.Core.Surface;
 using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace ChunkyImageLib.Operations;
-internal class BresenhamLineOperation : IDrawOperation
+internal class BresenhamLineOperation : IMirroredDrawOperation
 {
     public bool IgnoreEmptyChunks => false;
     private readonly VecI from;

+ 1 - 1
src/ChunkyImageLib/Operations/ChunkyImageOperation.cs

@@ -2,7 +2,7 @@
 using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace ChunkyImageLib.Operations;
-internal class ChunkyImageOperation : IDrawOperation
+internal class ChunkyImageOperation : IMirroredDrawOperation
 {
     private readonly ChunkyImage imageToDraw;
     private readonly VecI targetPos;

+ 1 - 1
src/ChunkyImageLib/Operations/ClearPathOperation.cs

@@ -3,7 +3,7 @@ using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace ChunkyImageLib.Operations;
-internal class ClearPathOperation : IDrawOperation
+internal class ClearPathOperation : IMirroredDrawOperation
 {
     private VectorPath path;
     private RectI pathTightBounds;

+ 1 - 1
src/ChunkyImageLib/Operations/ClearRegionOperation.cs

@@ -2,7 +2,7 @@
 
 namespace ChunkyImageLib.Operations;
 
-internal class ClearRegionOperation : IDrawOperation
+internal class ClearRegionOperation : IMirroredDrawOperation
 {
     private RectI rect;
 

+ 1 - 1
src/ChunkyImageLib/Operations/DrawingSurfaceLineOperation.cs

@@ -5,7 +5,7 @@ using PixiEditor.DrawingApi.Core.Surface;
 using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace ChunkyImageLib.Operations;
-internal class DrawingSurfaceLineOperation : IDrawOperation
+internal class DrawingSurfaceLineOperation : IMirroredDrawOperation
 {
     public bool IgnoreEmptyChunks => false;
 

+ 1 - 1
src/ChunkyImageLib/Operations/EllipseOperation.cs

@@ -6,7 +6,7 @@ using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace ChunkyImageLib.Operations;
-internal class EllipseOperation : IDrawOperation
+internal class EllipseOperation : IMirroredDrawOperation
 {
     public bool IgnoreEmptyChunks => false;
 

+ 0 - 1
src/ChunkyImageLib/Operations/IDrawOperation.cs

@@ -8,5 +8,4 @@ internal interface IDrawOperation : IOperation
     bool IgnoreEmptyChunks { get; }
     void DrawOnChunk(Chunk chunk, VecI chunkPos);
     HashSet<VecI> FindAffectedChunks(VecI imageSize);
-    IDrawOperation AsMirrored(int? verAxisX, int? horAxisY);
 }

+ 6 - 0
src/ChunkyImageLib/Operations/IMirroredDrawOperation.cs

@@ -0,0 +1,6 @@
+namespace ChunkyImageLib.Operations;
+
+internal interface IMirroredDrawOperation : IDrawOperation
+{
+    IDrawOperation AsMirrored(int? verAxisX, int? horAxisY);
+}

+ 1 - 1
src/ChunkyImageLib/Operations/ImageOperation.cs

@@ -5,7 +5,7 @@ using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace ChunkyImageLib.Operations;
 
-internal class ImageOperation : IDrawOperation
+internal class ImageOperation : IMirroredDrawOperation
 {
     private Matrix3X3 transformMatrix;
     private ShapeCorners corners;

+ 1 - 1
src/ChunkyImageLib/Operations/PathOperation.cs

@@ -6,7 +6,7 @@ using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace ChunkyImageLib.Operations;
-internal class PathOperation : IDrawOperation
+internal class PathOperation : IMirroredDrawOperation
 {
     private readonly VectorPath path;
 

+ 1 - 1
src/ChunkyImageLib/Operations/PixelOperation.cs

@@ -6,7 +6,7 @@ using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace ChunkyImageLib.Operations;
 
-internal class PixelOperation : IDrawOperation
+internal class PixelOperation : IMirroredDrawOperation
 {
     public bool IgnoreEmptyChunks => false;
     private readonly VecI pixel;

+ 1 - 1
src/ChunkyImageLib/Operations/PixelsOperation.cs

@@ -6,7 +6,7 @@ using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace ChunkyImageLib.Operations;
 
-internal class PixelsOperation : IDrawOperation
+internal class PixelsOperation : IMirroredDrawOperation
 {
     public bool IgnoreEmptyChunks => false;
     private readonly Point[] pixels;

+ 1 - 1
src/ChunkyImageLib/Operations/RectangleOperation.cs

@@ -4,7 +4,7 @@ using PixiEditor.DrawingApi.Core.Surface;
 
 namespace ChunkyImageLib.Operations;
 
-internal class RectangleOperation : IDrawOperation
+internal class RectangleOperation : IMirroredDrawOperation
 {
     public RectangleOperation(ShapeData rect)
     {

+ 0 - 5
src/ChunkyImageLib/Operations/ReplaceColorOperation.cs

@@ -51,11 +51,6 @@ internal class ReplaceColorOperation : IDrawOperation
         return OperationHelper.FindChunksTouchingRectangle(new RectI(VecI.Zero, imageSize), ChunkyImage.FullChunkSize);
     }
 
-    public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
-    {
-        return new ReplaceColorOperation(this.oldColor, this.newColor);
-    }
-
     public void Dispose()
     {
     }

+ 45 - 0
src/PixiEditor.ChangeableDocument/Changes/Structure/ApplyMask_Change.cs

@@ -0,0 +1,45 @@
+using PixiEditor.ChangeableDocument.Changes.Drawing;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+
+namespace PixiEditor.ChangeableDocument.Changes.Structure;
+
+internal sealed class ApplyMask_Change : Change
+{
+    private Guid structureMemberGuid;
+
+    private CommittedChunkStorage? savedChunks;
+
+    [GenerateMakeChangeAction]
+    public ApplyMask_Change(Guid structureMemberGuid)
+    {
+        this.structureMemberGuid = structureMemberGuid;
+    }
+        
+    public override bool InitializeAndValidate(Document target)
+    {
+        var member = target.FindMember(structureMemberGuid);
+        bool isValid = member is not (null or Folder) && member.Mask is not null;
+
+        return isValid;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        var layer = (Layer)target.FindMember(structureMemberGuid)!;
+        layer!.LayerImage.EnqueueApplyMask(layer.Mask!);
+        ignoreInUndo = false;
+        var layerInfo = new LayerImageChunks_ChangeInfo(structureMemberGuid, layer.LayerImage.FindAffectedChunks());
+        savedChunks = new CommittedChunkStorage(layer.LayerImage, layerInfo.Chunks);
+        
+        layer.LayerImage.CommitChanges();
+        return layerInfo;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        var affected = DrawingChangeHelper.ApplyStoredChunksDisposeAndSetToNull(target, structureMemberGuid, false, ref savedChunks);
+        return new LayerImageChunks_ChangeInfo(structureMemberGuid, affected);
+    }
+}

+ 8 - 7
src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs

@@ -1,12 +1,7 @@
 using System.Collections.Immutable;
-using System.Windows.Interop;
-using System.Windows.Shapes;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
-using ChunkyImageLib.Operations;
-using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Actions.Undo;
-using PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
@@ -14,9 +9,7 @@ using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 using PixiEditor.Models.DocumentPassthroughActions;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Position;
-using PixiEditor.Parser;
 using PixiEditor.ViewModels.SubViewModels.Document;
-using PixiEditor.Views.UserControls.Overlays.TransformOverlay;
 
 namespace PixiEditor.Models.DocumentModels.Public;
 #nullable enable
@@ -197,6 +190,14 @@ internal class DocumentOperationsModule
             return;
         Internals.ActionAccumulator.AddFinishedActions(new DeleteStructureMemberMask_Action(member.GuidValue));
     }
+    
+    public void ApplyMask(StructureMemberViewModel member)
+    {
+        if (Internals.ChangeController.IsChangeActive)
+            return;
+        
+        Internals.ActionAccumulator.AddFinishedActions(new ApplyMask_Action(member.GuidValue), new DeleteStructureMemberMask_Action(member.GuidValue));
+    }
 
     public void SetSelectedMember(Guid memberGuid) => Internals.ActionAccumulator.AddActions(new SetSelectedMember_PassthroughAction(memberGuid));
 

+ 11 - 0
src/PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs

@@ -242,6 +242,17 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         
         member.MaskIsVisibleBindable = !member.MaskIsVisibleBindable;
     }
+    
+    [Command.Basic("PixiEditor.Layer.ApplyMask", "Apply mask", "Apply mask", CanExecute = "PixiEditor.Layer.ActiveLayerHasMask")]
+    public void ApplyMask()
+    {
+        var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
+        var member = doc?.SelectedStructureMember;
+        if (member is null || !member.HasMaskBindable)
+            return;
+        
+        doc!.Operations.ApplyMask(member);
+    }
 
     [Command.Basic("PixiEditor.Layer.ToggleVisible", "Toggle visibility", "Toggle visibility", CanExecute = "PixiEditor.HasDocument")]
     public void ToggleVisible()

+ 1 - 0
src/PixiEditor/Views/UserControls/Layers/LayerControl.xaml

@@ -151,6 +151,7 @@
                     IsChecked="{Binding PlacementTarget.Tag.Layer.MaskIsVisibleBindable, RelativeSource={RelativeSource AncestorType=ContextMenu}}" 
                     IsEnabled="{Binding PlacementTarget.Tag.Layer.HasMaskBindable, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
                     Header="Enable mask"/>
+                <MenuItem Header="Apply mask" Command="{cmds:Command PixiEditor.Layer.ApplyMask}"/>
                 <Separator/>
                 <MenuItem Header="Merge selected" Command="{cmds:Command PixiEditor.Layer.MergeSelected}"/>
                 <MenuItem Header="Merge with above" Command="{cmds:Command PixiEditor.Layer.MergeWithAbove}"/>