Browse Source

Line based pen

Equbuxu 3 years ago
parent
commit
908562f599

+ 11 - 0
src/ChunkyImageLib/ChunkyImage.cs

@@ -375,6 +375,17 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
     }
 
+    public void EnqueueDrawBresenhamLine(VecI from, VecI to, SKColor color)
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            BresenhamLineOperation operation = new(from, to, color);
+            EnqueueOperation(operation);
+        }
+    }
+
+
     public void EnqueueDrawSkiaLine(VecI from, VecI to, SKStrokeCap strokeCap, float strokeWidth, SKColor color)
     {
         lock (lockObject)

+ 109 - 0
src/ChunkyImageLib/Operations/BresenhamLineHelper.cs

@@ -0,0 +1,109 @@
+using ChunkyImageLib.DataHolders;
+using SkiaSharp;
+
+namespace ChunkyImageLib.Operations;
+public static class BresenhamLineHelper
+{
+    public static SKPoint[] GetBresenhamLine(VecI start, VecI end)
+    {
+        int count = Math.Abs((start - end).LongestAxis) + 1;
+        if (count > 100000)
+            return new SKPoint[0];
+        SKPoint[] output = new SKPoint[count];
+        CalculateBresenhamLine(start, end, output);
+        return output;
+    }
+
+    private static void CalculateBresenhamLine(VecI start, VecI end, SKPoint[] output)
+    {
+        int index = 0;
+
+        int x1 = start.X;
+        int x2 = end.X;
+        int y1 = start.Y;
+        int y2 = end.Y;
+
+        if (x1 == x2 && y1 == y2)
+        {
+            output[index] = start;
+            return;
+        }
+
+        int d, dx, dy, ai, bi, xi, yi;
+        int x = x1, y = y1;
+
+        if (x1 < x2)
+        {
+            xi = 1;
+            dx = x2 - x1;
+        }
+        else
+        {
+            xi = -1;
+            dx = x1 - x2;
+        }
+
+        if (y1 < y2)
+        {
+            yi = 1;
+            dy = y2 - y1;
+        }
+        else
+        {
+            yi = -1;
+            dy = y1 - y2;
+        }
+
+        output[index] = new SKPoint(x, y);
+        index++;
+
+        if (dx > dy)
+        {
+            ai = (dy - dx) * 2;
+            bi = dy * 2;
+            d = bi - dx;
+
+            while (x != x2)
+            {
+                if (d >= 0)
+                {
+                    x += xi;
+                    y += yi;
+                    d += ai;
+                }
+                else
+                {
+                    d += bi;
+                    x += xi;
+                }
+
+                output[index] = new SKPoint(x, y);
+                index++;
+            }
+        }
+        else
+        {
+            ai = (dx - dy) * 2;
+            bi = dx * 2;
+            d = bi - dy;
+
+            while (y != y2)
+            {
+                if (d >= 0)
+                {
+                    x += xi;
+                    y += yi;
+                    d += ai;
+                }
+                else
+                {
+                    d += bi;
+                    y += yi;
+                }
+
+                output[index] = new SKPoint(x, y);
+                index++;
+            }
+        }
+    }
+}

+ 58 - 0
src/ChunkyImageLib/Operations/BresenhamLineOperation.cs

@@ -0,0 +1,58 @@
+using ChunkyImageLib.DataHolders;
+using SkiaSharp;
+
+namespace ChunkyImageLib.Operations;
+internal class BresenhamLineOperation : IDrawOperation
+{
+    public bool IgnoreEmptyChunks => false;
+    private readonly VecI from;
+    private readonly VecI to;
+    private readonly SKPoint[] points;
+    private SKPaint paint;
+
+    public BresenhamLineOperation(VecI from, VecI to, SKColor color)
+    {
+        this.from = from;
+        this.to = to;
+        paint = new SKPaint() { Color = color };
+        points = BresenhamLineHelper.GetBresenhamLine(from, to);
+    }
+
+    public void DrawOnChunk(Chunk chunk, VecI chunkPos)
+    {
+        var surf = chunk.Surface.SkiaSurface;
+        surf.Canvas.Save();
+        surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
+        surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);
+        surf.Canvas.DrawPoints(SKPointMode.Points, points, paint);
+        surf.Canvas.Restore();
+    }
+
+    public HashSet<VecI> FindAffectedChunks()
+    {
+        RectI bounds = RectI.FromTwoPoints(from, to);
+        return OperationHelper.FindChunksTouchingRectangle(bounds, ChunkyImage.FullChunkSize);
+    }
+
+    public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
+    {
+        VecI newFrom = from;
+        VecI newTo = to;
+        if (verAxisX is not null)
+        {
+            newFrom = newFrom.ReflectX((int)verAxisX);
+            newTo = newFrom.ReflectX((int)verAxisX);
+        }
+        if (horAxisY is not null)
+        {
+            newFrom = newFrom.ReflectY((int)horAxisY);
+            newTo = newFrom.ReflectY((int)horAxisY);
+        }
+        return new BresenhamLineOperation(newFrom, newTo, paint.Color);
+    }
+
+    public void Dispose()
+    {
+        paint.Dispose();
+    }
+}

+ 121 - 0
src/PixiEditor.ChangeableDocument/Changes/Drawing/LineBasedPen_UpdateableChange.cs

@@ -0,0 +1,121 @@
+using SkiaSharp;
+
+namespace PixiEditor.ChangeableDocument.Changes.Drawing;
+internal class LineBasedPen_UpdateableChange : UpdateableChange
+{
+    private readonly Guid memberGuid;
+    private readonly SKColor color;
+    private readonly int strokeWidth;
+    private readonly bool drawOnMask;
+
+    bool firstApply = true;
+
+    private CommittedChunkStorage? storedChunks;
+    private readonly List<VecI> points = new();
+
+    [GenerateUpdateableChangeActions]
+    public LineBasedPen_UpdateableChange(Guid memberGuid, SKColor color, VecI pos, int strokeWidth, bool drawOnMask)
+    {
+        this.memberGuid = memberGuid;
+        this.color = color;
+        this.strokeWidth = strokeWidth;
+        this.drawOnMask = drawOnMask;
+        points.Add(pos);
+    }
+
+    [UpdateChangeMethod]
+    public void Update(VecI pos)
+    {
+        points.Add(pos);
+    }
+
+    public override OneOf<Success, Error> InitializeAndValidate(Document target)
+    {
+        if (!DrawingChangeHelper.IsValidForDrawing(target, memberGuid, drawOnMask))
+            return new Error();
+        if (strokeWidth < 1)
+            return new Error();
+        var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
+        DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
+        return new Success();
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
+    {
+        var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
+
+        var (from, to) = points.Count > 1 ? (points[^2], points[^1]) : (points[0], points[0]);
+
+        int opCount = image.QueueLength;
+
+        if (strokeWidth == 1)
+            image.EnqueueDrawBresenhamLine(from, to, color);
+        else
+            image.EnqueueDrawSkiaLine(from, to, SKStrokeCap.Round, strokeWidth, color);
+        var affChunks = image.FindAffectedChunks(opCount);
+
+        return DrawingChangeHelper.CreateChunkChangeInfo(memberGuid, affChunks, drawOnMask);
+    }
+
+    private void FastforwardEnqueueDrawLines(ChunkyImage targetImage)
+    {
+        if (points.Count == 1)
+        {
+            if (strokeWidth == 1)
+                targetImage.EnqueueDrawBresenhamLine(points[0], points[0], color);
+            else
+                targetImage.EnqueueDrawSkiaLine(points[0], points[0], SKStrokeCap.Round, strokeWidth, color);
+            return;
+        }
+        for (int i = 1; i < points.Count; i++)
+        {
+            if (strokeWidth == 1)
+                targetImage.EnqueueDrawBresenhamLine(points[i - 1], points[i], color);
+            else
+                targetImage.EnqueueDrawSkiaLine(points[i - 1], points[i], SKStrokeCap.Round, strokeWidth, color);
+        }
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, out bool ignoreInUndo)
+    {
+        if (storedChunks is not null)
+            throw new InvalidOperationException("Trying to save chunks while there are saved chunks already");
+        var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
+
+        ignoreInUndo = false;
+        if (firstApply)
+        {
+            firstApply = false;
+
+            var affChunks = image.FindAffectedChunks();
+            storedChunks = new CommittedChunkStorage(image, affChunks);
+            image.CommitChanges();
+
+            return DrawingChangeHelper.CreateChunkChangeInfo(memberGuid, affChunks, drawOnMask);
+        }
+        else
+        {
+            DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
+
+            FastforwardEnqueueDrawLines(image);
+            var affChunks = image.FindAffectedChunks();
+            storedChunks = new CommittedChunkStorage(image, affChunks);
+            image.CommitChanges();
+
+            return DrawingChangeHelper.CreateChunkChangeInfo(memberGuid, affChunks, drawOnMask);
+        }
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        if (storedChunks is null)
+            throw new InvalidOperationException("No saved chunks to revert to");
+        var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
+        storedChunks.ApplyChunksToImage(image);
+        var affected = image.FindAffectedChunks();
+        image.CommitChanges();
+        storedChunks.Dispose();
+        storedChunks = null;
+        return DrawingChangeHelper.CreateChunkChangeInfo(memberGuid, affected, drawOnMask);
+    }
+}

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

@@ -4,6 +4,7 @@ internal enum Tool
 {
     Rectangle,
     PathBasedPen,
+    LineBasedPen,
     Select,
     Lasso,
     ShiftLayer,

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

@@ -196,6 +196,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
     private bool selectingLasso = false;
     private bool drawingRectangle = false;
     private bool drawingPathBasedPen = false;
+    private bool drawingLineBasedPen = false;
     private bool transformingRectangle = false;
     private bool shiftingLayer = false;
 
@@ -249,6 +250,32 @@ internal class DocumentViewModel : INotifyPropertyChanged
         Helpers.ActionAccumulator.AddFinishedActions(new EndPathBasedPen_Action());
     }
 
+    public void StartUpdateLineBasedPen(VecI pos)
+    {
+        if (SelectedStructureMember is null)
+            return;
+        bool drawOnMask = SelectedStructureMember.HasMask && SelectedStructureMember.ShouldDrawOnMask;
+        if (SelectedStructureMember is not LayerViewModel && !drawOnMask)
+            return;
+        updateableChangeActive = true;
+        drawingLineBasedPen = true;
+        Helpers.ActionAccumulator.AddActions(new LineBasedPen_Action(
+            SelectedStructureMember.GuidValue,
+            new SKColor(owner.SelectedColor.R, owner.SelectedColor.G, owner.SelectedColor.B, owner.SelectedColor.A),
+            pos,
+            (int)owner.StrokeWidth,
+            drawOnMask));
+    }
+
+    public void EndLineBasedPen()
+    {
+        if (!drawingLineBasedPen)
+            return;
+        drawingLineBasedPen = false;
+        updateableChangeActive = false;
+        Helpers.ActionAccumulator.AddFinishedActions(new EndLineBasedPen_Action());
+    }
+
     public void StartUpdateRectangle(ShapeData data)
     {
         if (SelectedStructureMember is null)

+ 11 - 0
src/PixiEditorPrototype/ViewModels/ViewModelMain.cs

@@ -134,6 +134,10 @@ internal class ViewModelMain : INotifyPropertyChanged
         {
             ActiveDocument!.StartUpdatePathBasedPen(pos);
         }
+        else if (toolOnMouseDown == Tool.LineBasedPen)
+        {
+            ActiveDocument!.StartUpdateLineBasedPen((VecI)pos);
+        }
     }
 
     private void MouseMove(object? param)
@@ -181,6 +185,10 @@ internal class ViewModelMain : INotifyPropertyChanged
         {
             ActiveDocument!.StartUpdatePathBasedPen(canvasPos);
         }
+        else if (toolOnMouseDown == Tool.LineBasedPen)
+        {
+            ActiveDocument!.StartUpdateLineBasedPen((VecI)canvasPos);
+        }
     }
 
     private void MouseUp(object? param)
@@ -218,6 +226,9 @@ internal class ViewModelMain : INotifyPropertyChanged
             case Tool.PathBasedPen:
                 ActiveDocument!.EndPathBasedPen();
                 break;
+            case Tool.LineBasedPen:
+                ActiveDocument!.EndLineBasedPen();
+                break;
         }
     }
 

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

@@ -213,6 +213,7 @@
             <StackPanel Orientation="Vertical" Background="White">
                 <Button Width="70" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.Rectangle}">Rect</Button>
                 <Button Width="70" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.PathBasedPen}">Path Pen</Button>
+                <Button Width="70" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.LineBasedPen}">Line Pen</Button>
                 <Button Width="70" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.Select}">Select</Button>
                 <Button Width="70" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.Lasso}">Lasso</Button>
                 <Button Width="70" Margin="5" Command="{Binding ChangeActiveToolCommand}" CommandParameter="{x:Static models:Tool.ShiftLayer}">Shift Layer</Button>