Browse Source

Shift layer change

Equbuxu 3 years ago
parent
commit
ac6478615c

+ 24 - 8
src/ChunkyImageLib/ChunkyImage.cs

@@ -2,6 +2,7 @@
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
 using OneOf;
+using OneOf.Types;
 using SkiaSharp;
 
 [assembly: InternalsVisibleTo("ChunkyImageLibTest")]
@@ -124,11 +125,23 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         lock (lockObject)
         {
             ThrowIfDisposed();
-            var latestChunk = GetLatestChunk(chunkPos, resolution);
+            OneOf<None, EmptyChunk, Chunk> latestChunk;
+            {
+                var chunk = GetLatestChunk(chunkPos, resolution);
+                if (latestChunksData[resolution].TryGetValue(chunkPos, out var chunkData) && chunkData.IsDeleted)
+                {
+                    latestChunk = new EmptyChunk();
+                }
+                else
+                {
+                    latestChunk = chunk is null ? new None() : chunk;
+                }
+            }
+
             var committedChunk = GetCommittedChunk(chunkPos, resolution);
 
-            // latest chunk does not exist, draw committed if it exists
-            if (latestChunk is null)
+            // draw committed directly
+            if (latestChunk.IsT0 || latestChunk.IsT1 && committedChunk is not null && blendMode != SKBlendMode.Src)
             {
                 if (committedChunk is null)
                     return false;
@@ -136,19 +149,22 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 return true;
             }
 
-            // latest chunk exists
+            // no need to combine with committed, draw directly
             if (blendMode == SKBlendMode.Src || committedChunk is null)
             {
-                // no need to combine with committed, draw directly
-                latestChunk.DrawOnSurface(surface, pos, paint);
-                return true;
+                if (latestChunk.IsT2)
+                {
+                    latestChunk.AsT2.DrawOnSurface(surface, pos, paint);
+                    return true;
+                }
+                return false;
             }
 
             // combine with committed and then draw
             using var tempChunk = Chunk.Create(resolution);
             tempChunk.Surface.SkiaSurface.Canvas.DrawSurface(committedChunk!.Surface.SkiaSurface, 0, 0, ReplacingPaint);
             blendModePaint.BlendMode = blendMode;
-            tempChunk.Surface.SkiaSurface.Canvas.DrawSurface(latestChunk.Surface.SkiaSurface, 0, 0, blendModePaint);
+            tempChunk.Surface.SkiaSurface.Canvas.DrawSurface(latestChunk.AsT2.Surface.SkiaSurface, 0, 0, blendModePaint);
             if (lockTransparency)
                 OperationHelper.ClampAlpha(tempChunk.Surface.SkiaSurface, committedChunk.Surface.SkiaSurface);
             tempChunk.DrawOnSurface(surface, pos, paint);

+ 13 - 10
src/ChunkyImageLib/Operations/ChunkyImageOperation.cs

@@ -51,6 +51,9 @@ internal class ChunkyImageOperation : IDrawOperation
         int leftX = (int)Math.Floor(posOnImage.X);
         int rightX = (int)Math.Ceiling(posOnImage.X);
 
+
+        int chunkPixelSize = chunk.Resolution.PixelSize();
+
         // this is kinda dumb
         if (pos % ChunkyImage.FullChunkSize == VecI.Zero)
         {
@@ -58,20 +61,20 @@ internal class ChunkyImageOperation : IDrawOperation
         }
         else if (pos.X % ChunkyImage.FullChunkSize == 0)
         {
-            imageToDraw.DrawCommittedChunkOn(new VecI((int)posOnImage.X, topY), chunk.Resolution, chunk.Surface.SkiaSurface, new VecI(0, (int)(topY - posOnImage.Y)));
-            imageToDraw.DrawCommittedChunkOn(new VecI((int)posOnImage.X, bottomY), chunk.Resolution, chunk.Surface.SkiaSurface, new VecI(0, (int)(bottomY - posOnImage.Y)));
+            imageToDraw.DrawCommittedChunkOn(new VecI((int)posOnImage.X, topY), chunk.Resolution, chunk.Surface.SkiaSurface, new VecI(0, (int)((topY - posOnImage.Y) * chunkPixelSize)));
+            imageToDraw.DrawCommittedChunkOn(new VecI((int)posOnImage.X, bottomY), chunk.Resolution, chunk.Surface.SkiaSurface, new VecI(0, (int)((bottomY - posOnImage.Y) * chunkPixelSize)));
         }
         else if (pos.Y % ChunkyImage.FullChunkSize == 0)
         {
-            imageToDraw.DrawCommittedChunkOn(new VecI(leftX, (int)posOnImage.Y), chunk.Resolution, chunk.Surface.SkiaSurface, new VecI((int)(leftX - posOnImage.X), 0));
-            imageToDraw.DrawCommittedChunkOn(new VecI(rightX, (int)posOnImage.Y), chunk.Resolution, chunk.Surface.SkiaSurface, new VecI((int)(rightX - posOnImage.X), 0));
+            imageToDraw.DrawCommittedChunkOn(new VecI(leftX, (int)posOnImage.Y), chunk.Resolution, chunk.Surface.SkiaSurface, new VecI((int)((leftX - posOnImage.X) * chunkPixelSize), 0));
+            imageToDraw.DrawCommittedChunkOn(new VecI(rightX, (int)posOnImage.Y), chunk.Resolution, chunk.Surface.SkiaSurface, new VecI((int)((rightX - posOnImage.X) * chunkPixelSize), 0));
         }
         else
         {
-            imageToDraw.DrawCommittedChunkOn(new VecI(leftX, topY), chunk.Resolution, chunk.Surface.SkiaSurface, new VecI((int)(leftX - posOnImage.X), (int)(topY - posOnImage.Y)));
-            imageToDraw.DrawCommittedChunkOn(new VecI(rightX, topY), chunk.Resolution, chunk.Surface.SkiaSurface, new VecI((int)(rightX - posOnImage.X), (int)(topY - posOnImage.Y)));
-            imageToDraw.DrawCommittedChunkOn(new VecI(leftX, bottomY), chunk.Resolution, chunk.Surface.SkiaSurface, new VecI((int)(leftX - posOnImage.X), (int)(bottomY - posOnImage.Y)));
-            imageToDraw.DrawCommittedChunkOn(new VecI(rightX, bottomY), chunk.Resolution, chunk.Surface.SkiaSurface, new VecI((int)(rightX - posOnImage.X), (int)(bottomY - posOnImage.Y)));
+            imageToDraw.DrawCommittedChunkOn(new VecI(leftX, topY), chunk.Resolution, chunk.Surface.SkiaSurface, new VecI((int)((leftX - posOnImage.X) * chunkPixelSize), (int)((topY - posOnImage.Y) * chunkPixelSize)));
+            imageToDraw.DrawCommittedChunkOn(new VecI(rightX, topY), chunk.Resolution, chunk.Surface.SkiaSurface, new VecI((int)((rightX - posOnImage.X) * chunkPixelSize), (int)((topY - posOnImage.Y) * chunkPixelSize)));
+            imageToDraw.DrawCommittedChunkOn(new VecI(leftX, bottomY), chunk.Resolution, chunk.Surface.SkiaSurface, new VecI((int)((leftX - posOnImage.X) * chunkPixelSize), (int)((bottomY - posOnImage.Y) * chunkPixelSize)));
+            imageToDraw.DrawCommittedChunkOn(new VecI(rightX, bottomY), chunk.Resolution, chunk.Surface.SkiaSurface, new VecI((int)((rightX - posOnImage.X) * chunkPixelSize), (int)((bottomY - posOnImage.Y) * chunkPixelSize)));
         }
 
         chunk.Surface.SkiaSurface.Canvas.Restore();
@@ -86,9 +89,9 @@ internal class ChunkyImageOperation : IDrawOperation
     {
         VecI topLeft = pos;
         if (mirrorHorizontal)
-            topLeft.X -= imageToDraw.LatestSize.X;
+            topLeft.X -= imageToDraw.CommittedSize.X;
         if (mirrorVertical)
-            topLeft.Y -= imageToDraw.LatestSize.Y;
+            topLeft.Y -= imageToDraw.CommittedSize.Y;
         return topLeft;
     }
 

+ 78 - 0
src/PixiEditor.ChangeableDocument/Changes/Drawing/ShiftLayer_UpdateableChange.cs

@@ -0,0 +1,78 @@
+namespace PixiEditor.ChangeableDocument.Changes.Drawing;
+internal class ShiftLayer_UpdateableChange : UpdateableChange
+{
+    private readonly Guid layerGuid;
+    private VecI delta;
+    private CommittedChunkStorage? originalLayerChunks;
+
+    [GenerateUpdateableChangeActions]
+    public ShiftLayer_UpdateableChange(Guid layerGuid, VecI delta)
+    {
+        this.delta = delta;
+        this.layerGuid = layerGuid;
+    }
+
+    public override OneOf<Success, Error> InitializeAndValidate(Document target)
+    {
+        var member = target.FindMember(layerGuid);
+        if (member is not Layer)
+            return new Error();
+        return new Success();
+    }
+
+    [UpdateChangeMethod]
+    public void Update(VecI delta)
+    {
+        this.delta = delta;
+    }
+
+    private HashSet<VecI> DrawShiftedLayer(Document target)
+    {
+        var targetImage = ((Layer)target.FindMemberOrThrow(layerGuid)).LayerImage;
+        var prevChunks = targetImage.FindAffectedChunks();
+        targetImage.CancelChanges();
+        targetImage.EnqueueClear();
+        targetImage.EnqueueDrawChunkyImage(delta, targetImage, false, false);
+        var curChunks = targetImage.FindAffectedChunks();
+        curChunks.UnionWith(prevChunks);
+        return curChunks;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, out bool ignoreInUndo)
+    {
+        var chunks = DrawShiftedLayer(target);
+        var image = ((Layer)target.FindMemberOrThrow(layerGuid)).LayerImage;
+
+        if (originalLayerChunks is not null)
+            throw new InvalidOperationException("saved chunks is not null even though we are trying to save them again");
+        originalLayerChunks = new(image, image.FindAffectedChunks());
+        image.CommitChanges();
+
+        ignoreInUndo = delta.TaxicabLength == 0;
+        return new LayerImageChunks_ChangeInfo() { GuidValue = layerGuid, Chunks = chunks };
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
+    {
+        var chunks = DrawShiftedLayer(target);
+        return new LayerImageChunks_ChangeInfo() { GuidValue = layerGuid, Chunks = chunks };
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        if (originalLayerChunks is null)
+            throw new InvalidOperationException("No saved chunks to restore to");
+        var image = ((Layer)target.FindMemberOrThrow(layerGuid)).LayerImage;
+        originalLayerChunks.ApplyChunksToImage(image);
+        var affected = image.FindAffectedChunks();
+        image.CommitChanges();
+        originalLayerChunks.Dispose();
+        originalLayerChunks = null;
+        return new LayerImageChunks_ChangeInfo() { GuidValue = layerGuid, Chunks = affected };
+    }
+
+    public override void Dispose()
+    {
+        originalLayerChunks?.Dispose();
+    }
+}

+ 3 - 2
src/PixiEditorPrototype/Models/Rendering/WriteableBitmapUpdater.cs

@@ -136,8 +136,9 @@ internal class WriteableBitmapUpdater
                 {
                     var pos = chunk * ChunkResolution.Full.PixelSize();
                     // the full res chunks are already rendered so drawing them again should be fast
-                    layer.LayerImage.DrawMostUpToDateChunkOn
-                        (chunk, ChunkResolution.Full, memberVM.PreviewSurface, pos, ReplacingPaint);
+                    if (!layer.LayerImage.DrawMostUpToDateChunkOn
+                        (chunk, ChunkResolution.Full, memberVM.PreviewSurface, pos, ReplacingPaint))
+                        memberVM.PreviewSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkyImage.FullChunkSize, ChunkyImage.FullChunkSize, ClearPaint);
                 }
                 infos.Add(new PreviewDirty_RenderInfo(guid));
             }

+ 1 - 0
src/PixiEditorPrototype/Models/Tool.cs

@@ -4,5 +4,6 @@ internal enum Tool
 {
     Rectangle,
     Select,
+    ShiftLayer,
     FloodFill
 }

+ 19 - 0
src/PixiEditorPrototype/ViewModels/DocumentViewModel.cs

@@ -152,6 +152,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
 
     private bool drawingRectangle = false;
     private bool transformingRectangle = false;
+    private bool shiftingLayer = false;
 
     private bool pastingImage = false;
     private Surface? pastedImage;
@@ -198,6 +199,24 @@ internal class DocumentViewModel : INotifyPropertyChanged
         transformingRectangle = true;
     }
 
+    public void StartUpdateShiftLayer(VecI delta)
+    {
+        if (SelectedStructureMember is not LayerViewModel layer)
+            return;
+        updateableChangeActive = true;
+        shiftingLayer = true;
+        Helpers.ActionAccumulator.AddActions(new ShiftLayer_Action(layer.GuidValue, delta));
+    }
+
+    public void EndShiftLayer()
+    {
+        if (!shiftingLayer)
+            return;
+        updateableChangeActive = false;
+        shiftingLayer = false;
+        Helpers.ActionAccumulator.AddFinishedActions(new EndShiftLayer_Action());
+    }
+
     public void ApplyTransform(object? param)
     {
         if (!transformingRectangle && !pastingImage)

+ 43 - 20
src/PixiEditorPrototype/ViewModels/ViewModelMain.cs

@@ -61,14 +61,14 @@ internal class ViewModelMain : INotifyPropertyChanged
     private Dictionary<Guid, DocumentViewModel> documents = new();
     private Guid activeDocumentGuid;
 
-    private bool mouseIsDown = false;
     private int mouseDownCanvasX = 0;
     private int mouseDownCanvasY = 0;
 
-    private bool startedDrawingRect = false;
-    private bool startedSelectingRect = false;
+    private bool mouseHasMoved = false;
+    private bool mouseIsDown = false;
 
     private Tool activeTool = Tool.Rectangle;
+    private Tool toolOnMouseDown = Tool.Rectangle;
 
     public ViewModelMain()
     {
@@ -91,26 +91,40 @@ internal class ViewModelMain : INotifyPropertyChanged
     {
         if (ActiveDocument is null || ZoomboxMode != ZoomboxMode.Normal || ActiveDocument.TransformViewModel.TransformActive)
             return;
+        if (mouseIsDown)
+        {
+            mouseIsDown = false;
+            ProcessToolMouseUp();
+            mouseHasMoved = false;
+        }
+
         mouseIsDown = true;
         var args = (MouseButtonEventArgs)(param!);
         var source = (System.Windows.Controls.Image)args.Source;
         var pos = args.GetPosition(source);
         mouseDownCanvasX = (int)(pos.X / source.Width * ActiveDocument.Bitmaps[ChunkResolution.Full].PixelWidth);
         mouseDownCanvasY = (int)(pos.Y / source.Height * ActiveDocument.Bitmaps[ChunkResolution.Full].PixelHeight);
-        if (activeTool is Tool.FloodFill)
+        toolOnMouseDown = activeTool;
+        ProcessToolMouseDown(mouseDownCanvasX, mouseDownCanvasY);
+    }
+
+    private void ProcessToolMouseDown(int canvasX, int canvasY)
+    {
+        if (toolOnMouseDown is Tool.FloodFill)
         {
-            ActiveDocument.FloodFill(new VecI(mouseDownCanvasX, mouseDownCanvasY), new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A));
+            ActiveDocument!.FloodFill(new VecI(canvasX, canvasY), new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A));
         }
     }
 
     private void MouseMove(object? param)
     {
-        if (ActiveDocument is null || !mouseIsDown || ZoomboxMode != ZoomboxMode.Normal)
+        if (!mouseIsDown)
             return;
+        mouseHasMoved = true;
         var args = (MouseEventArgs)(param!);
         var source = (System.Windows.Controls.Image)args.Source;
         var pos = args.GetPosition(source);
-        int curX = (int)(pos.X / source.Width * ActiveDocument.Bitmaps[ChunkResolution.Full].PixelWidth);
+        int curX = (int)(pos.X / source.Width * ActiveDocument!.Bitmaps[ChunkResolution.Full].PixelWidth);
         int curY = (int)(pos.Y / source.Height * ActiveDocument.Bitmaps[ChunkResolution.Full].PixelHeight);
 
         ProcessToolMouseMove(curX, curY);
@@ -118,9 +132,8 @@ internal class ViewModelMain : INotifyPropertyChanged
 
     private void ProcessToolMouseMove(int canvasX, int canvasY)
     {
-        if (activeTool == Tool.Rectangle)
+        if (toolOnMouseDown == Tool.Rectangle)
         {
-            startedDrawingRect = true;
             int width = canvasX - mouseDownCanvasX;
             int height = canvasY - mouseDownCanvasY;
             ActiveDocument!.StartUpdateRectangle(new ShapeData(
@@ -131,35 +144,45 @@ internal class ViewModelMain : INotifyPropertyChanged
                         new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A),
                         SKColors.Transparent));
         }
-        else if (activeTool == Tool.Select)
+        else if (toolOnMouseDown == Tool.Select)
         {
-            startedSelectingRect = true;
             ActiveDocument!.StartUpdateSelection(
                 new(mouseDownCanvasX, mouseDownCanvasY),
                 new(canvasX - mouseDownCanvasX, canvasY - mouseDownCanvasY));
         }
+        else if (toolOnMouseDown == Tool.ShiftLayer)
+        {
+            ActiveDocument!.StartUpdateShiftLayer(new(canvasX - mouseDownCanvasX, canvasY - mouseDownCanvasY));
+        }
     }
 
     private void MouseUp(object? param)
     {
-        if (ActiveDocument is null || !mouseIsDown || ZoomboxMode != ZoomboxMode.Normal)
+        if (!mouseIsDown)
             return;
         mouseIsDown = false;
         ProcessToolMouseUp();
+        mouseHasMoved = false;
     }
 
     private void ProcessToolMouseUp()
     {
-        if (startedDrawingRect)
+        if (mouseHasMoved)
         {
-            startedDrawingRect = false;
-            ActiveDocument!.EndRectangleDrawing();
-        }
-        if (startedSelectingRect)
-        {
-            startedSelectingRect = false;
-            ActiveDocument!.EndSelection();
+            switch (toolOnMouseDown)
+            {
+                case Tool.Rectangle:
+                    ActiveDocument!.EndRectangleDrawing();
+                    break;
+                case Tool.Select:
+                    ActiveDocument!.EndSelection();
+                    break;
+                case Tool.ShiftLayer:
+                    ActiveDocument!.EndShiftLayer();
+                    break;
+            }
         }
+
     }
 
     private void ChangeActiveTool(object? param)

+ 1 - 0
src/PixiEditorPrototype/Views/MainWindow.xaml

@@ -187,6 +187,7 @@
             <StackPanel Orientation="Vertical" Background="White">
                 <Button Width="50" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.Rectangle}">Rect</Button>
                 <Button Width="50" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.Select}">Select</Button>
+                <Button Width="50" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.ShiftLayer}">Shift Layer</Button>
                 <Button Width="50" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.FloodFill}">Fill</Button>
                 <colorpicker:PortableColorPicker Margin="5" SelectedColor="{Binding SelectedColor, Mode=TwoWay}" Width="30" Height="30"/>
                 <RadioButton GroupName="zoomboxMode" Margin="5,0" IsChecked="{Binding NormalZoombox, Mode=OneWayToSource}">Normal</RadioButton>