Browse Source

Merge branch 'quickfill' of github.com:PixiEditor/PixiEditor into quickfill

CPKreuz 3 years ago
parent
commit
0f182043e6

+ 26 - 3
PixiEditor/Models/DataHolders/Document/Document.Operations.cs

@@ -1,6 +1,7 @@
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
 using PixiEditor.Models.Undo;
 using SkiaSharp;
 using System;
@@ -87,19 +88,41 @@ namespace PixiEditor.Models.DataHolders
 
                     var canvas = layer.LayerBitmap.SkiaSurface.Canvas;
 
-                    canvas.Clear();
+                    layer.ClipCanvas();
 
                     if (flip == FlipType.Horizontal)
                     {
-                        canvas.Translate(layer.MaxWidth + layer.OffsetX, 0);
+                        canvas.Translate(layer.Width, 0);
                         canvas.Scale(-1, 1, 0, 0);
                     }
                     else
                     {
-                        canvas.Translate(0, layer.MaxHeight + layer.OffsetY);
+                        canvas.Translate(0, layer.Width);
                         canvas.Scale(1, -1, 0, 0);
                     }
 
+                    // Flip offset based on document and layer center point
+                    var documentCenter = new Coordinates(Width / 2, Height / 2);
+                    var layerCenter = new Coordinates(layer.Width / 2, layer.Height / 2);
+
+                    int newOffsetX = layer.OffsetX;
+                    int newOffsetY = layer.OffsetY;
+
+                    if (flip == FlipType.Horizontal)
+                    {
+                        newOffsetX += layerCenter.X;
+                        int diff = documentCenter.X - newOffsetX;
+                        newOffsetX = layer.OffsetX + (diff * 2);
+                    }
+                    else if(flip == FlipType.Vertical)
+                    {
+                        newOffsetY += layerCenter.Y;
+                        int diff = documentCenter.Y - newOffsetY;
+                        newOffsetY = layer.OffsetY + (diff * 2);
+                    }
+
+                    layer.Offset = new Thickness(newOffsetX, newOffsetY, 0, 0);
+
                     canvas.DrawImage(copy, default(SKPoint));
                     copy.Dispose();
                 }

+ 107 - 1
PixiEditor/Models/ImageManipulation/ShapeCalculator.cs → PixiEditor/Models/ImageManipulation/ToolCalculator.cs

@@ -1,12 +1,118 @@
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools;
+using PixiEditor.Models.Tools.Tools;
+using SkiaSharp;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Windows;
 
 namespace PixiEditor.Models.ImageManipulation
 {
-    public static class ShapeCalculator
+    public static class ToolCalculator
     {
+        /// <summary>
+        /// This function calculates fill and outputs it into Coordinates list.
+        /// </summary>
+        /// <remarks>All coordinates are calculated without layer offset.
+        /// If you want to take consideration offset, just do it in CalculateBresenhamLine in PerformLinearFill function.</remarks>
+        /// <param name="layer">Layer to calculate fill from.</param>
+        /// <param name="startingCoords">Starting fill coordinates</param>
+        /// <param name="maxWidth">Maximum fill width</param>
+        /// <param name="maxHeight">Maximum fill height</param>
+        /// <param name="newColor">Replacement color to stop on</param>
+        /// <param name="output">List with output coordinates.</param>
+        public static void GetLinearFillAbsolute(Layer layer, Coordinates startingCoords, int maxWidth, int maxHeight, SKColor newColor, List<Coordinates> output)
+        {
+            Queue<FloodFillRange> floodFillQueue = new Queue<FloodFillRange>();
+            SKColor colorToReplace = layer.GetPixelWithOffset(startingCoords.X, startingCoords.Y);
+            if ((colorToReplace.Alpha == 0 && newColor.Alpha == 0) ||
+                colorToReplace == newColor)
+                return;
+
+            int width = maxWidth;
+            int height = maxHeight;
+            if (startingCoords.X < 0 || startingCoords.Y < 0 || startingCoords.X >= width || startingCoords.Y >= height)
+                return;
+            var visited = new bool[width * height];
+
+            Int32Rect dirtyRect = new Int32Rect(startingCoords.X, startingCoords.Y, 1, 1);
+
+            PerformLinearFill(layer, floodFillQueue, startingCoords, width, colorToReplace, ref dirtyRect, visited, output);
+            PerformFloodFIll(layer, floodFillQueue, colorToReplace, ref dirtyRect, width, height, visited, output);
+        }
+
+        private static void PerformLinearFill(
+            Layer layer, Queue<FloodFillRange> floodFillQueue,
+            Coordinates coords, int width, SKColor colorToReplace, ref Int32Rect dirtyRect, bool[] visited, List<Coordinates> output)
+        {
+            // Find the Left Edge of the Color Area
+            int fillXLeft = coords.X;
+            while (true)
+            {
+                // Indicate that this pixel has been checked
+                int pixelIndex = (coords.Y * width) + fillXLeft;
+                visited[pixelIndex] = true;
+
+                // Move one pixel to the left
+                fillXLeft--;
+                // Exit the loop if we're at edge of the bitmap or the color area
+                if (fillXLeft < 0 || visited[pixelIndex - 1] || layer.GetPixelWithOffset(fillXLeft, coords.Y) != colorToReplace)
+                    break;
+            }
+            int lastCheckedPixelLeft = fillXLeft + 1;
+
+            // Find the Right Edge of the Color Area
+            int fillXRight = coords.X;
+            while (true)
+            {
+                int pixelIndex = (coords.Y * width) + fillXRight;
+                visited[pixelIndex] = true;
+
+                fillXRight++;
+                if (fillXRight >= width || visited[pixelIndex + 1] || layer.GetPixelWithOffset(fillXRight, coords.Y) != colorToReplace)
+                    break;
+            }
+            int lastCheckedPixelRight = fillXRight - 1;
+
+            int relativeY = coords.Y - layer.OffsetY;
+            LineTool.CalculateBresenhamLine(new Coordinates(lastCheckedPixelLeft, relativeY), new Coordinates(lastCheckedPixelRight, relativeY), output);
+            dirtyRect = dirtyRect.Expand(new Int32Rect(lastCheckedPixelLeft, coords.Y, lastCheckedPixelRight - lastCheckedPixelLeft + 1, 1));
+
+            FloodFillRange range = new FloodFillRange(lastCheckedPixelLeft, lastCheckedPixelRight, coords.Y);
+            floodFillQueue.Enqueue(range);
+        }
+
+        private static void PerformFloodFIll(
+            Layer layer, Queue<FloodFillRange> floodFillQueue,
+            SKColor colorToReplace, ref Int32Rect dirtyRect, int width, int height, bool[] pixelsVisited, List<Coordinates> output)
+        {
+            while (floodFillQueue.Count > 0)
+            {
+                FloodFillRange range = floodFillQueue.Dequeue();
+
+                //START THE LOOP UPWARDS AND DOWNWARDS
+                int upY = range.Y - 1; //so we can pass the y coord by ref
+                int downY = range.Y + 1;
+                int downPixelxIndex = (width * (range.Y + 1)) + range.StartX;
+                int upPixelIndex = (width * (range.Y - 1)) + range.StartX;
+                for (int i = range.StartX; i <= range.EndX; i++)
+                {
+                    //START LOOP UPWARDS
+                    //if we're not above the top of the bitmap and the pixel above this one is within the color tolerance
+                    if (range.Y > 0 && (!pixelsVisited[upPixelIndex]) && layer.GetPixelWithOffset(i, upY) == colorToReplace)
+                        PerformLinearFill(layer, floodFillQueue, new Coordinates(i, upY), width, colorToReplace, ref dirtyRect, pixelsVisited, output);
+                    //START LOOP DOWNWARDS
+                    if (range.Y < (height - 1) && (!pixelsVisited[downPixelxIndex]) && layer.GetPixelWithOffset(i, downY) == colorToReplace)
+                        PerformLinearFill(layer, floodFillQueue, new Coordinates(i, downY), width, colorToReplace, ref dirtyRect, pixelsVisited, output);
+                    downPixelxIndex++;
+                    upPixelIndex++;
+                }
+            }
+        }
+
         public static void GenerateEllipseNonAlloc(Coordinates start, Coordinates end, bool fill,
             List<Coordinates> output)
         {

+ 15 - 7
PixiEditor/Models/Layers/Layer.cs

@@ -476,10 +476,7 @@ namespace PixiEditor.Models.Layers
             }
         }
 
-        /// <summary>
-        ///     Changes size of bitmap to fit content.
-        /// </summary>
-        public void ClipCanvas()
+        public Int32Rect GetContentDimensions()
         {
             DoubleCords points = GetEdgePoints();
             int smallestX = points.Coords1.X;
@@ -489,13 +486,24 @@ namespace PixiEditor.Models.Layers
 
             if (smallestX < 0 && smallestY < 0 && biggestX < 0 && biggestY < 0)
             {
-                return;
+                return Int32Rect.Empty;
             }
 
             int width = biggestX - smallestX + 1;
             int height = biggestY - smallestY + 1;
-            ResizeCanvas(0, 0, smallestX, smallestY, width, height);
-            Offset = new Thickness(OffsetX + smallestX, OffsetY + smallestY, 0, 0);
+            return new Int32Rect(smallestX, smallestY, width, height);
+        }
+
+        /// <summary>
+        ///     Changes size of bitmap to fit content.
+        /// </summary>
+        public void ClipCanvas()
+        {
+            var dimensions = GetContentDimensions();
+            if (dimensions == Int32Rect.Empty) return;
+
+            ResizeCanvas(0, 0, dimensions.X, dimensions.Y, dimensions.Width, dimensions.Height);
+            Offset = new Thickness(OffsetX + dimensions.X, OffsetY + dimensions.Y, 0, 0);
         }
 
         public void Reset()

+ 5 - 0
PixiEditor/Models/Position/Coordinates.cs

@@ -29,6 +29,11 @@ namespace PixiEditor.Models.Position
             return new Coordinates(coordiantes.X - size, coordiantes.Y - size);
         }
 
+        public static Coordinates operator -(Coordinates coordiantes1, Coordinates coordinates2)
+        {
+            return new Coordinates(coordiantes1.X - coordinates2.X, coordiantes1.Y - coordinates2.Y);
+        }
+
         public static bool operator ==(Coordinates c1, Coordinates c2)
         {
             return c2.X == c1.X && c2.Y == c1.Y;

+ 12 - 8
PixiEditor/Models/Tools/Tools/LineTool.cs

@@ -17,17 +17,23 @@ namespace PixiEditor.Models.Tools.Tools
         private SKPaint paint = new SKPaint() { Style = SKPaintStyle.Stroke };
 
         public static List<Coordinates> GetBresenhamLine(Coordinates start, Coordinates end)
+        {
+            List<Coordinates> output = new List<Coordinates>();
+            CalculateBresenhamLine(start, end, output);
+            return output;
+        }
+
+        public static void CalculateBresenhamLine(Coordinates start, Coordinates end, List<Coordinates> output)
         {
             int x1 = start.X;
             int x2 = end.X;
             int y1 = start.Y;
             int y2 = end.Y;
 
-            List<Coordinates> coordinates = new List<Coordinates>();
             if (x1 == x2 && y1 == y2)
             {
-                coordinates.Add(start);
-                return coordinates;
+                output.Add(start);
+                return;
             }
 
             int d, dx, dy, ai, bi, xi, yi;
@@ -55,7 +61,7 @@ namespace PixiEditor.Models.Tools.Tools
                 dy = y1 - y2;
             }
 
-            coordinates.Add(new Coordinates(x, y));
+            output.Add(new Coordinates(x, y));
 
             if (dx > dy)
             {
@@ -77,7 +83,7 @@ namespace PixiEditor.Models.Tools.Tools
                         x += xi;
                     }
 
-                    coordinates.Add(new Coordinates(x, y));
+                    output.Add(new Coordinates(x, y));
                 }
             }
             else
@@ -100,11 +106,9 @@ namespace PixiEditor.Models.Tools.Tools
                         y += yi;
                     }
 
-                    coordinates.Add(new Coordinates(x, y));
+                    output.Add(new Coordinates(x, y));
                 }
             }
-
-            return coordinates;
         }
 
         public LineTool()

+ 14 - 10
PixiEditor/Models/Tools/Tools/MagicWandTool.cs

@@ -17,13 +17,12 @@ namespace PixiEditor.Models.Tools.Tools
 {
     public class MagicWandTool : ReadonlyTool, ICachedDocumentTool
     {
-        private readonly FloodFillTool floodFill;
-
         private static Selection ActiveSelection { get => ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection; }
 
         private BitmapManager BitmapManager { get; }
 
         private IEnumerable<Coordinates> oldSelection;
+        private List<Coordinates> newSelection = new List<Coordinates>();
 
         public override string Tooltip => "Magic Wand (W). Flood's the selection";
 
@@ -56,20 +55,25 @@ namespace PixiEditor.Models.Tools.Tools
 
             Selection selection = BitmapManager.ActiveDocument.ActiveSelection;
 
-            /*selection.SetSelection(
-                floodFill.LinearFill(
-                    layer,
-                    new Coordinates((int)document.MouseXOnCanvas, (int)document.MouseYOnCanvas),
-                    SKColors.White
-                    ).ChangedPixels.Keys,
-                selectionType);*/
+            newSelection.Clear();
+
+            ToolCalculator.GetLinearFillAbsolute(
+                   layer,
+                   new Coordinates(
+                       (int)document.MouseXOnCanvas,
+                       (int)document.MouseYOnCanvas),
+                   BitmapManager.ActiveDocument.Width,
+                   BitmapManager.ActiveDocument.Height,
+                   SKColors.White,
+                   newSelection);
+
+            selection.SetSelection(newSelection, selectionType);
 
             SelectionHelpers.AddSelectionUndoStep(ViewModelMain.Current.BitmapManager.ActiveDocument, oldSelection, selectionType);
         }
 
         public MagicWandTool(BitmapManager manager)
         {
-            floodFill = new FloodFillTool(manager);
             BitmapManager = manager;
 
             Toolbar = new MagicWandToolbar();

+ 2 - 2
PixiEditor/Models/Tools/Tools/SelectTool.cs

@@ -68,7 +68,7 @@ namespace PixiEditor.Models.Tools.Tools
         public IEnumerable<Coordinates> GetRectangleSelectionForPoints(Coordinates start, Coordinates end)
         {
             List<Coordinates> result = new List<Coordinates>();
-            ShapeCalculator.GenerateRectangleNonAlloc(
+            ToolCalculator.GenerateRectangleNonAlloc(
                 start, end, true, 1, result);
             return result;
         }
@@ -76,7 +76,7 @@ namespace PixiEditor.Models.Tools.Tools
         public IEnumerable<Coordinates> GetCircleSelectionForPoints(Coordinates start, Coordinates end)
         {
             List<Coordinates> result = new List<Coordinates>();
-            ShapeCalculator.GenerateEllipseNonAlloc(
+            ToolCalculator.GenerateEllipseNonAlloc(
                 start, end, true, result);
             return result;
         }