Browse Source

Refactor and fix warnings

Equbuxu 3 years ago
parent
commit
722ebcc3e4

+ 1 - 1
PixiEditor/Helpers/Converters/ViewboxInverseTransformConverter.cs

@@ -15,7 +15,7 @@ namespace PixiEditor.Helpers.Converters
             return transform.Inverse;
             return transform.Inverse;
         }
         }
 
 
-        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
+        public override object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
         {
         {
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }

+ 140 - 0
PixiEditor/Helpers/EllipseGenerator.cs

@@ -0,0 +1,140 @@
+using PixiEditor.Models.Position;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditor.Helpers
+{
+    internal static class EllipseGenerator
+    {
+
+        public static List<Coordinates> GenerateEllipseFromRect(DoubleCoords rect)
+        {
+            float radiusX = (rect.Coords2.X - rect.Coords1.X) / 2.0f;
+            float radiusY = (rect.Coords2.Y - rect.Coords1.Y) / 2.0f;
+            float centerX = (rect.Coords1.X + rect.Coords2.X + 1) / 2.0f;
+            float centerY = (rect.Coords1.Y + rect.Coords2.Y + 1) / 2.0f;
+
+            return GenerateMidpointEllipse(radiusX, radiusY, centerX, centerY);
+        }
+
+        /// <summary>
+        /// Draws an ellipse using it's center and radii
+        ///
+        /// Here is a usage example:
+        /// Let's say you want an ellipse that's 3 pixels wide and 3 pixels tall located in the top right corner of the canvas
+        /// It's center is at (1.5; 1.5). That's in the middle of a pixel
+        /// The radii are both equal to 1. Notice that it's 1 and not 1.5, since we want the ellipse to land in the middle of the pixel, not outside of it.
+        /// See desmos (note the inverted y axis): https://www.desmos.com/calculator/tq9uqg0hcq
+        ///
+        /// Another example:
+        /// 4x4 ellipse in the top right corner of the canvas
+        /// Center is at (2; 2). It's a place where 4 pixels meet
+        /// Both radii are 1.5. Making them 2 would make the ellipse touch the edges of pixels, whereas we want it to stay in the middle
+        /// </summary>
+        public static List<Coordinates> GenerateMidpointEllipse(double halfWidth, double halfHeight, double centerX, double centerY)
+        {
+            if (halfWidth < 1 || halfHeight < 1)
+            {
+                return GenerateFallbackRectangle(halfWidth, halfHeight, centerX, centerY);
+            }
+
+            // ellipse formula: halfHeight^2 * x^2 + halfWidth^2 * y^2 - halfHeight^2 * halfWidth^2 = 0
+
+            // Make sure we are always at the center of a pixel
+            double currentX = Math.Ceiling(centerX - 0.5) + 0.5;
+            double currentY = centerY + halfHeight;
+
+            List<Coordinates> outputCoordinates = new List<Coordinates>();
+
+            double currentSlope;
+
+            // from PI/2 to PI/4
+            do
+            {
+                AddRegionPoints(outputCoordinates, currentX, centerX, currentY, centerY);
+
+                // calculate next pixel coords
+                currentX++;
+
+                if ((Math.Pow(halfHeight, 2) * Math.Pow(currentX - centerX, 2)) +
+                    (Math.Pow(halfWidth, 2) * Math.Pow(currentY - centerY - 0.5, 2)) -
+                    (Math.Pow(halfWidth, 2) * Math.Pow(halfHeight, 2)) >= 0)
+                {
+                    currentY--;
+                }
+
+                // calculate how far we've advanced
+                double derivativeX = 2 * Math.Pow(halfHeight, 2) * (currentX - centerX);
+                double derivativeY = 2 * Math.Pow(halfWidth, 2) * (currentY - centerY);
+                currentSlope = -(derivativeX / derivativeY);
+            }
+            while (currentSlope > -1 && currentY - centerY > 0.5);
+
+            // from PI/4 to 0
+            while (currentY - centerY >= 0)
+            {
+                AddRegionPoints(outputCoordinates, currentX, centerX, currentY, centerY);
+
+                currentY--;
+                if ((Math.Pow(halfHeight, 2) * Math.Pow(currentX - centerX + 0.5, 2)) +
+                    (Math.Pow(halfWidth, 2) * Math.Pow(currentY - centerY, 2)) -
+                    (Math.Pow(halfWidth, 2) * Math.Pow(halfHeight, 2)) < 0)
+                {
+                    currentX++;
+                }
+            }
+
+            return outputCoordinates;
+        }
+
+        private static List<Coordinates> GenerateFallbackRectangle(double halfWidth, double halfHeight, double centerX, double centerY)
+        {
+            List<Coordinates> coordinates = new List<Coordinates>();
+
+            int left = (int)Math.Floor(centerX - halfWidth);
+            int top = (int)Math.Floor(centerY - halfHeight);
+            int right = (int)Math.Floor(centerX + halfWidth);
+            int bottom = (int)Math.Floor(centerY + halfHeight);
+
+            for (int x = left; x <= right; x++)
+            {
+                coordinates.Add(new Coordinates(x, top));
+                coordinates.Add(new Coordinates(x, bottom));
+            }
+
+            for (int y = top; y <= bottom; y++)
+            {
+                coordinates.Add(new Coordinates(left, y));
+                coordinates.Add(new Coordinates(right, y));
+            }
+
+            return coordinates;
+        }
+
+        private static void AddRegionPoints(List<Coordinates> coordinates, double x, double xc, double y, double yc)
+        {
+            int xFloor = (int)Math.Floor(x);
+            int yFloor = (int)Math.Floor(y);
+            int xFloorInv = (int)Math.Floor(-x + 2 * xc);
+            int yFloorInv = (int)Math.Floor(-y + 2 * yc);
+
+            //top and bottom or left and right
+            if (xFloor == xFloorInv || yFloor == yFloorInv)
+            {
+                coordinates.Add(new Coordinates(xFloor, yFloor));
+                coordinates.Add(new Coordinates(xFloorInv, yFloorInv));
+            }
+            //part of the arc
+            else
+            {
+                coordinates.Add(new Coordinates(xFloor, yFloor));
+                coordinates.Add(new Coordinates(xFloorInv, yFloorInv));
+                coordinates.Add(new Coordinates(xFloorInv, yFloor));
+                coordinates.Add(new Coordinates(xFloor, yFloorInv));
+            }
+        }
+    }
+}

+ 157 - 158
PixiEditor/Helpers/Extensions/ParserHelpers.cs

@@ -1,158 +1,157 @@
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Layers;
-using PixiEditor.Parser;

-using PixiEditor.Parser.Skia;

-using SkiaSharp;

-using System;

-using System.Collections.Generic;

-using System.Collections.ObjectModel;
-

-namespace PixiEditor.Helpers.Extensions
-{
-    public static class ParserHelpers
-    {
-        public static Document ToDocument(this SerializableDocument serializableDocument)
-        {
-            Document document = new Document(serializableDocument.Width, serializableDocument.Height)
-            {
-                Layers = serializableDocument.ToLayers(),
-                Swatches = new ObservableCollection<SKColor>(serializableDocument.Swatches.ToSKColors())
-            };
-
-            document.LayerStructure.Groups = serializableDocument.ToGroups(document);
-
-            if (document.Layers.Count > 0)
-            {
-                document.SetMainActiveLayer(0);
-            }
-
-            return document;
-        }
-
-        public static ObservableCollection<Layer> ToLayers(this SerializableDocument document)

-        {

-            ObservableCollection<Layer> layers = new();

-

-            foreach (SerializableLayer slayer in document)

-            {

-                layers.Add(slayer.ToLayer());

-            }

-

-            return layers;

-        }
-
-        public static Layer ToLayer(this SerializableLayer layer)

-        {

-            return new Layer(layer.Name, new Surface(layer.ToSKImage()))

-            {

-                Opacity = layer.Opacity,

-                IsVisible = layer.IsVisible,

-                Offset = new(layer.OffsetX, layer.OffsetY, 0, 0)

-            };

-        }
-
-        public static ObservableCollection<GuidStructureItem> ToGroups(this SerializableDocument sdocument, Document document)

-        {

-            ObservableCollection<GuidStructureItem> groups = new();

-

-            if (sdocument.Groups == null)

-            {

-                return groups;

-            }

-

-            foreach (SerializableGroup sgroup in sdocument.Groups)

-            {

-                groups.Add(sgroup.ToGroup(null, document));

-            }

-

-            return groups;

-        }
-
-        public static GuidStructureItem ToGroup(this SerializableGroup sgroup, GuidStructureItem parent, Document document)

-        {

-            GuidStructureItem group = new GuidStructureItem(sgroup.Name, Guid.Empty)

-            {

-                Opacity = sgroup.Opacity,

-                IsVisible = sgroup.IsVisible,

-                Parent = parent,

-                StartLayerGuid = document.Layers[sgroup.StartLayer].LayerGuid,

-                EndLayerGuid = document.Layers[sgroup.EndLayer].LayerGuid

-            };

-

-            group.Subgroups = new(sgroup.Subgroups.ToGroups(document, group));

-

-            return group;

-        }

-
-        public static SerializableDocument ToSerializable(this Document document)

-        {

-            return new SerializableDocument(document.Width, document.Height,

-                                            document.LayerStructure.Groups.ToSerializable(document),

-                                            document.Layers.ToSerializable()).AddSwatches(document.Swatches);

-        }
-
-        public static IEnumerable<SerializableLayer> ToSerializable(this IEnumerable<Layer> layers)

-        {

-            foreach (Layer layer in layers)

-            {

-                yield return layer.ToSerializable();

-            }

-        }
-
-        public static SerializableLayer ToSerializable(this Layer layer)

-        {

-            return new SerializableLayer(layer.Width, layer.Height, layer.OffsetX, layer.OffsetY)

-            {

-                IsVisible = layer.IsVisible,

-                Opacity = layer.Opacity,

-                Name = layer.Name

-            }.FromSKImage(layer.LayerBitmap.SkiaSurface.Snapshot());

-        }
-
-        public static IEnumerable<SerializableGroup> ToSerializable(this IEnumerable<GuidStructureItem> groups, Document document)

-        {

-            foreach (GuidStructureItem group in groups)

-            {

-                yield return group.ToSerializable(document);

-            }

-        }
-
-        public static SerializableGroup ToSerializable(this GuidStructureItem group, Document document)

-        {

-            SerializableGroup serializable = new SerializableGroup(group.Name, group.Subgroups.ToSerializable(document))

-            {

-                Opacity = group.Opacity,

-                IsVisible = group.IsVisible

-            };

-

-            for (int i = 0; i < document.Layers.Count; i++)

-            {

-                if (group.StartLayerGuid == document.Layers[i].LayerGuid)

-                {

-                    serializable.StartLayer = i;

-                }

-

-                if (group.EndLayerGuid == document.Layers[i].LayerGuid)

-                {

-                    serializable.EndLayer = i;

-                }

-            }

-

-            return serializable;

-        }

-

-        private static IEnumerable<GuidStructureItem> ToGroups(this IEnumerable<SerializableGroup> groups, Document document, GuidStructureItem parent)

-        {

-            foreach (SerializableGroup sgroup in groups)

-            {

-                yield return sgroup.ToGroup(parent, document);

-            }

-        }
-
-        private static SerializableDocument AddSwatches(this SerializableDocument document, IEnumerable<SKColor> colors)

-        {

-            document.Swatches.AddRange(colors);

-            return document;

-        }
-    }
-}
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Layers;
+using PixiEditor.Parser;
+using PixiEditor.Parser.Skia;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace PixiEditor.Helpers.Extensions
+{
+    public static class ParserHelpers
+    {
+        public static Document ToDocument(this SerializableDocument serializableDocument)
+        {
+            Document document = new Document(serializableDocument.Width, serializableDocument.Height)
+            {
+                Layers = serializableDocument.ToLayers(),
+                Swatches = new ObservableCollection<SKColor>(serializableDocument.Swatches.ToSKColors())
+            };
+
+            document.LayerStructure.Groups = serializableDocument.ToGroups(document);
+
+            if (document.Layers.Count > 0)
+            {
+                document.SetMainActiveLayer(0);
+            }
+
+            return document;
+        }
+
+        public static ObservableCollection<Layer> ToLayers(this SerializableDocument document)
+        {
+            ObservableCollection<Layer> layers = new();
+            foreach (SerializableLayer slayer in document)
+            {
+                layers.Add(slayer.ToLayer());
+            }
+
+            return layers;
+        }
+
+        public static Layer ToLayer(this SerializableLayer layer)
+        {
+            return new Layer(layer.Name, new Surface(layer.ToSKImage()))
+            {
+                Opacity = layer.Opacity,
+                IsVisible = layer.IsVisible,
+                Offset = new(layer.OffsetX, layer.OffsetY, 0, 0)
+            };
+        }
+
+        public static ObservableCollection<GuidStructureItem> ToGroups(this SerializableDocument sdocument, Document document)
+        {
+            ObservableCollection<GuidStructureItem> groups = new();
+
+            if (sdocument.Groups == null)
+            {
+                return groups;
+            }
+
+            foreach (SerializableGroup sgroup in sdocument.Groups)
+            {
+                groups.Add(sgroup.ToGroup(null, document));
+            }
+
+            return groups;
+        }
+
+        public static GuidStructureItem ToGroup(this SerializableGroup sgroup, GuidStructureItem parent, Document document)
+        {
+            GuidStructureItem group = new GuidStructureItem(sgroup.Name, Guid.Empty)
+            {
+                Opacity = sgroup.Opacity,
+                IsVisible = sgroup.IsVisible,
+                Parent = parent,
+                StartLayerGuid = document.Layers[sgroup.StartLayer].LayerGuid,
+                EndLayerGuid = document.Layers[sgroup.EndLayer].LayerGuid
+            };
+
+            group.Subgroups = new(sgroup.Subgroups.ToGroups(document, group));
+
+            return group;
+        }
+
+        public static SerializableDocument ToSerializable(this Document document)
+        {
+            return new SerializableDocument(document.Width, document.Height,
+                                            document.LayerStructure.Groups.ToSerializable(document),
+                                            document.Layers.ToSerializable()).AddSwatches(document.Swatches);
+        }
+
+        public static IEnumerable<SerializableLayer> ToSerializable(this IEnumerable<Layer> layers)
+        {
+            foreach (Layer layer in layers)
+            {
+                yield return layer.ToSerializable();
+            }
+        }
+
+        public static SerializableLayer ToSerializable(this Layer layer)
+        {
+            return new SerializableLayer(layer.Width, layer.Height, layer.OffsetX, layer.OffsetY)
+            {
+                IsVisible = layer.IsVisible,
+                Opacity = layer.Opacity,
+                Name = layer.Name
+            }.FromSKImage(layer.LayerBitmap.SkiaSurface.Snapshot());
+        }
+
+        public static IEnumerable<SerializableGroup> ToSerializable(this IEnumerable<GuidStructureItem> groups, Document document)
+        {
+            foreach (GuidStructureItem group in groups)
+            {
+                yield return group.ToSerializable(document);
+            }
+        }
+
+        public static SerializableGroup ToSerializable(this GuidStructureItem group, Document document)
+        {
+            SerializableGroup serializable = new SerializableGroup(group.Name, group.Subgroups.ToSerializable(document))
+            {
+                Opacity = group.Opacity,
+                IsVisible = group.IsVisible
+            };
+
+            for (int i = 0; i < document.Layers.Count; i++)
+            {
+                if (group.StartLayerGuid == document.Layers[i].LayerGuid)
+                {
+                    serializable.StartLayer = i;
+                }
+
+                if (group.EndLayerGuid == document.Layers[i].LayerGuid)
+                {
+                    serializable.EndLayer = i;
+                }
+            }
+
+            return serializable;
+        }
+
+        private static IEnumerable<GuidStructureItem> ToGroups(this IEnumerable<SerializableGroup> groups, Document document, GuidStructureItem parent)
+        {
+            foreach (SerializableGroup sgroup in groups)
+            {
+                yield return sgroup.ToGroup(parent, document);
+            }
+        }
+
+        private static SerializableDocument AddSwatches(this SerializableDocument document, IEnumerable<SKColor> colors)
+        {
+            document.Swatches.AddRange(colors);
+            return document;
+        }
+    }
+}

+ 1 - 1
PixiEditor/Helpers/Extensions/PixiParserHelper.cs

@@ -5,7 +5,7 @@ namespace PixiEditor.Helpers.Extensions
 {
 {
     public static class PixiParserHelper
     public static class PixiParserHelper
     {
     {
-        public static SKRectI GetRect(this SerializableLayer layer) => 
+        public static SKRectI GetRect(this SerializableLayer layer) =>
             SKRectI.Create(layer.OffsetX, layer.OffsetY, layer.Width, layer.Height);
             SKRectI.Create(layer.OffsetX, layer.OffsetY, layer.Width, layer.Height);
     }
     }
 }
 }

+ 38 - 38
PixiEditor/Models/Controllers/BitmapManager.cs

@@ -138,7 +138,44 @@ namespace PixiEditor.Models.Controllers
             {
             {
                 throw new InvalidOperationException($"'{activeTool.GetType().Name}' is either not a Tool or can't inherit '{nameof(Tool)}' directly.\nChanges the base type to either '{nameof(BitmapOperationTool)}' or '{nameof(ReadonlyTool)}'");
                 throw new InvalidOperationException($"'{activeTool.GetType().Name}' is either not a Tool or can't inherit '{nameof(Tool)}' directly.\nChanges the base type to either '{nameof(BitmapOperationTool)}' or '{nameof(ReadonlyTool)}'");
             }
             }
-        }
+        }
+
+        public void HighlightPixels(Coordinates newPosition)
+        {
+            if (ActiveDocument == null || ActiveDocument.Layers.Count == 0 || _tools.ActiveTool.HideHighlight)
+            {
+                return;
+            }
+
+            var previewLayer = ActiveDocument.PreviewLayer;
+
+            if (_tools.ToolSize != previewLayerSize || previewLayer.IsReset)
+            {
+                previewLayerSize = _tools.ToolSize;
+                halfSize = (int)Math.Floor(_tools.ToolSize / 2f);
+                previewLayer.CreateNewBitmap(_tools.ToolSize, _tools.ToolSize);
+
+                Coordinates cords = new Coordinates(halfSize, halfSize);
+
+                previewLayer.Offset = new Thickness(0, 0, 0, 0);
+                _highlightPen.Draw(previewLayer, cords, cords, _highlightColor, _tools.ToolSize);
+
+                AdjustOffset(newPosition, previewLayer);
+
+            }
+
+            previewLayer.InvokeLayerBitmapChange();
+
+            AdjustOffset(newPosition, previewLayer);
+
+            if (newPosition.X > ActiveDocument.Width
+                || newPosition.Y > ActiveDocument.Height
+                || newPosition.X < 0 || newPosition.Y < 0)
+            {
+                previewLayer.Reset();
+                previewLayerSize = -1;
+            }
+        }
 
 
         private void BitmapManager_DocumentChanged(object sender, DocumentChangedEventArgs e)
         private void BitmapManager_DocumentChanged(object sender, DocumentChangedEventArgs e)
         {
         {
@@ -211,43 +248,6 @@ namespace PixiEditor.Models.Controllers
             HighlightPixels(MousePositionConverter.CurrentCoordinates);
             HighlightPixels(MousePositionConverter.CurrentCoordinates);
 
 
             startPosition = null;
             startPosition = null;
-        }
-
-        public void HighlightPixels(Coordinates newPosition)
-        {
-            if (ActiveDocument == null || ActiveDocument.Layers.Count == 0 || _tools.ActiveTool.HideHighlight)
-            {
-                return;
-            }
-
-            var previewLayer = ActiveDocument.PreviewLayer;
-
-            if (_tools.ToolSize != previewLayerSize || previewLayer.IsReset)
-            {
-                previewLayerSize = _tools.ToolSize;
-                halfSize = (int)Math.Floor(_tools.ToolSize / 2f);
-                previewLayer.CreateNewBitmap(_tools.ToolSize, _tools.ToolSize);
-
-                Coordinates cords = new Coordinates(halfSize, halfSize);
-
-                previewLayer.Offset = new Thickness(0, 0, 0, 0);
-                _highlightPen.Draw(previewLayer, cords, cords, _highlightColor, _tools.ToolSize);
-
-                AdjustOffset(newPosition, previewLayer);
-
-            }
-
-            previewLayer.InvokeLayerBitmapChange();
-
-            AdjustOffset(newPosition, previewLayer);
-
-            if (newPosition.X > ActiveDocument.Width
-                || newPosition.Y > ActiveDocument.Height
-                || newPosition.X < 0 || newPosition.Y < 0)
-            {
-                previewLayer.Reset();
-                previewLayerSize = -1;
-            }
         }
         }
 
 
         private void AdjustOffset(Coordinates newPosition, Layer previewLayer)
         private void AdjustOffset(Coordinates newPosition, Layer previewLayer)

+ 23 - 22
PixiEditor/Models/DataHolders/Document/Document.Operations.cs

@@ -77,6 +77,29 @@ namespace PixiEditor.Models.DataHolders
                 $"Flip layer: {flip}"));
                 $"Flip layer: {flip}"));
         }
         }
 
 
+        /// <summary>
+        ///     Resizes all document layers using NearestNeighbor interpolation.
+        /// </summary>
+        /// <param name="newWidth">New document width.</param>
+        /// <param name="newHeight">New document height.</param>
+        public void Resize(int newWidth, int newHeight)
+        {
+            object[] reverseArgs = { Width, Height };
+            object[] args = { newWidth, newHeight };
+            StorageBasedChange change = new StorageBasedChange(this, Layers);
+
+            ResizeDocument(newWidth, newHeight);
+
+            UndoManager.AddUndoChange(
+                change.ToChange(
+                    RestoreDocumentLayersProcess,
+                    reverseArgs,
+                    ResizeDocumentProcess,
+                    args,
+                    "Resize document"));
+        }
+
+
         private void FlipDocumentProcess(object[] processArgs)
         private void FlipDocumentProcess(object[] processArgs)
         {
         {
             FlipType flip = (FlipType)processArgs[0];
             FlipType flip = (FlipType)processArgs[0];
@@ -180,28 +203,6 @@ namespace PixiEditor.Models.DataHolders
                 new DocumentSizeChangedEventArgs(oldWidth, oldHeight, Width, Height));
                 new DocumentSizeChangedEventArgs(oldWidth, oldHeight, Width, Height));
         }
         }
 
 
-        /// <summary>
-        ///     Resizes all document layers using NearestNeighbor interpolation.
-        /// </summary>
-        /// <param name="newWidth">New document width.</param>
-        /// <param name="newHeight">New document height.</param>
-        public void Resize(int newWidth, int newHeight)
-        {
-            object[] reverseArgs = { Width, Height };
-            object[] args = { newWidth, newHeight };
-            StorageBasedChange change = new StorageBasedChange(this, Layers);
-
-            ResizeDocument(newWidth, newHeight);
-
-            UndoManager.AddUndoChange(
-                change.ToChange(
-                    RestoreDocumentLayersProcess,
-                    reverseArgs,
-                    ResizeDocumentProcess,
-                    args,
-                    "Resize document"));
-        }
-
         private void RestoreDocumentLayersProcess(Layer[] layers, UndoLayer[] data, object[] args)
         private void RestoreDocumentLayersProcess(Layer[] layers, UndoLayer[] data, object[] args)
         {
         {
             Width = (int)args[0];
             Width = (int)args[0];

+ 70 - 71
PixiEditor/Models/ImageManipulation/ToolCalculator.cs

@@ -44,84 +44,14 @@ namespace PixiEditor.Models.ImageManipulation
             PerformFloodFIll(layer, floodFillQueue, colorToReplace, ref dirtyRect, width, height, 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,
         public static void GenerateEllipseNonAlloc(Coordinates start, Coordinates end, bool fill,
             List<Coordinates> output)
             List<Coordinates> output)
         {
         {
-
             DoubleCoords fixedCoordinates = CalculateCoordinatesForShapeRotation(start, end);
             DoubleCoords fixedCoordinates = CalculateCoordinatesForShapeRotation(start, end);
 
 
             CreateEllipse(fixedCoordinates.Coords1, fixedCoordinates.Coords2, output);
             CreateEllipse(fixedCoordinates.Coords1, fixedCoordinates.Coords2, output);
 
 
-            if(fill)
+            if (fill)
             {
             {
                 CalculateFillForEllipse(output);
                 CalculateFillForEllipse(output);
             }
             }
@@ -193,6 +123,75 @@ namespace PixiEditor.Models.ImageManipulation
             return new DoubleCoords(startingCords, secondCoordinates);
             return new DoubleCoords(startingCords, secondCoordinates);
         }
         }
 
 
+        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++;
+                }
+            }
+        }
+
         private static void CalculateFillForEllipse(List<Coordinates> outlineCoordinates)
         private static void CalculateFillForEllipse(List<Coordinates> outlineCoordinates)
         {
         {
             if (!outlineCoordinates.Any())
             if (!outlineCoordinates.Any())

+ 2 - 1
PixiEditor/Models/Tools/Tools/BrightnessTool.cs

@@ -13,6 +13,7 @@ using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
 using SkiaSharp;
 using SkiaSharp;
 using System.Windows;
 using System.Windows;
+using PixiEditor.Helpers;
 
 
 namespace PixiEditor.Models.Tools.Tools
 namespace PixiEditor.Models.Tools.Tools
 {
 {
@@ -113,7 +114,7 @@ namespace PixiEditor.Models.Tools.Tools
         {
         {
             cachedCircleSize = newCircleSize;
             cachedCircleSize = newCircleSize;
             DoubleCoords rect = CoordinatesCalculator.CalculateThicknessCenter(new Coordinates(0, 0), newCircleSize);
             DoubleCoords rect = CoordinatesCalculator.CalculateThicknessCenter(new Coordinates(0, 0), newCircleSize);
-            List<Coordinates> circle = CircleTool.GenerateEllipseFromRect(rect);
+            List<Coordinates> circle = EllipseGenerator.GenerateEllipseFromRect(rect);
             circle.Sort((a, b) => a.Y != b.Y ? a.Y - b.Y : a.X - b.X);
             circle.Sort((a, b) => a.Y != b.Y ? a.Y - b.Y : a.X - b.X);
 
 
             circleCache.Clear();
             circleCache.Clear();

+ 20 - 144
PixiEditor/Models/Tools/Tools/CircleTool.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Models.Layers;
+using PixiEditor.Helpers;
+using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using SkiaSharp;
 using SkiaSharp;
@@ -14,37 +15,6 @@ namespace PixiEditor.Models.Tools.Tools
 {
 {
     public class CircleTool : ShapeTool
     public class CircleTool : ShapeTool
     {
     {
-        public CircleTool()
-        {
-            ActionDisplay = "Click and move mouse to draw a circle. Hold Shift to draw an even one.";
-        }
-
-        public override string Tooltip => "Draws circle on canvas (C). Hold Shift to draw even circle.";
-
-        public override void OnKeyDown(KeyEventArgs e)
-        {
-            if (e.Key == Key.LeftShift)
-            {
-                ActionDisplay = "Click and move mouse to draw an even circle.";
-            }
-        }
-
-        public override void OnKeyUp(KeyEventArgs e)
-        {
-            if (e.Key == Key.LeftShift)
-            {
-                ActionDisplay = "Click and move mouse to draw a circle. Hold Shift to draw an even one.";
-            }
-        }
-
-        public override void Use(Layer layer, List<Coordinates> coordinates, SKColor color)
-        {
-            int thickness = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
-            var hasFillColor = Toolbar.GetSetting<BoolSetting>("Fill").Value;
-            Color temp = Toolbar.GetSetting<ColorSetting>("FillColor").Value;
-            SKColor fill = new SKColor(temp.R, temp.G, temp.B, temp.A);
-            DrawEllipseFromCoordinates(layer, coordinates[^1], coordinates[0], color, fill, thickness, hasFillColor);
-        }
 
 
         public static void DrawEllipseFromCoordinates(Layer layer, Coordinates first, Coordinates second,
         public static void DrawEllipseFromCoordinates(Layer layer, Coordinates first, Coordinates second,
             SKColor color, SKColor fillColor, int thickness, bool hasFillColor)
             SKColor color, SKColor fillColor, int thickness, bool hasFillColor)
@@ -62,7 +32,7 @@ namespace PixiEditor.Models.Tools.Tools
 
 
             using (SKPaint paint = new SKPaint())
             using (SKPaint paint = new SKPaint())
             {
             {
-                List<Coordinates> outline = GenerateEllipseFromRect(corners);
+                List<Coordinates> outline = EllipseGenerator.GenerateEllipseFromRect(corners);
                 if (hasFillColor)
                 if (hasFillColor)
                 {
                 {
                     DrawEllipseFill(layer, fillColor, outline);
                     DrawEllipseFill(layer, fillColor, outline);
@@ -123,131 +93,37 @@ namespace PixiEditor.Models.Tools.Tools
             }
             }
         }
         }
 
 
-        public static List<Coordinates> GenerateEllipseFromRect(DoubleCoords rect)
+        public CircleTool()
         {
         {
-            float radiusX = (rect.Coords2.X - rect.Coords1.X) / 2.0f;
-            float radiusY = (rect.Coords2.Y - rect.Coords1.Y) / 2.0f;
-            float centerX = (rect.Coords1.X + rect.Coords2.X + 1) / 2.0f;
-            float centerY = (rect.Coords1.Y + rect.Coords2.Y + 1) / 2.0f;
-
-            return GenerateMidpointEllipse(radiusX, radiusY, centerX, centerY);
+            ActionDisplay = "Click and move mouse to draw a circle. Hold Shift to draw an even one.";
         }
         }
 
 
-        /// <summary>
-        /// Draws an ellipse using it's center and radii
-        ///
-        /// Here is a usage example:
-        /// Let's say you want an ellipse that's 3 pixels wide and 3 pixels tall located in the top right corner of the canvas
-        /// It's center is at (1.5; 1.5). That's in the middle of a pixel
-        /// The radii are both equal to 1. Notice that it's 1 and not 1.5, since we want the ellipse to land in the middle of the pixel, not outside of it.
-        /// See desmos (note the inverted y axis): https://www.desmos.com/calculator/tq9uqg0hcq
-        ///
-        /// Another example:
-        /// 4x4 ellipse in the top right corner of the canvas
-        /// Center is at (2; 2). It's a place where 4 pixels meet
-        /// Both radii are 1.5. Making them 2 would make the ellipse touch the edges of pixels, whereas we want it to stay in the middle
-        /// </summary>
-        public static List<Coordinates> GenerateMidpointEllipse(double halfWidth, double halfHeight, double centerX, double centerY)
-        {
-            if (halfWidth < 1 || halfHeight < 1)
-            {
-                return GenerateFallbackRectangle(halfWidth, halfHeight, centerX, centerY);
-            }
-
-            // ellipse formula: halfHeight^2 * x^2 + halfWidth^2 * y^2 - halfHeight^2 * halfWidth^2 = 0
-
-            // Make sure we are always at the center of a pixel
-            double currentX = Math.Ceiling(centerX - 0.5) + 0.5;
-            double currentY = centerY + halfHeight;
-
-            List<Coordinates> outputCoordinates = new List<Coordinates>();
-
-            double currentSlope;
-
-            // from PI/2 to PI/4
-            do
-            {
-                AddRegionPoints(outputCoordinates, currentX, centerX, currentY, centerY);
-
-                // calculate next pixel coords
-                currentX++;
-
-                if ((Math.Pow(halfHeight, 2) * Math.Pow(currentX - centerX, 2)) +
-                    (Math.Pow(halfWidth, 2) * Math.Pow(currentY - centerY - 0.5, 2)) -
-                    (Math.Pow(halfWidth, 2) * Math.Pow(halfHeight, 2)) >= 0)
-                {
-                    currentY--;
-                }
-
-                // calculate how far we've advanced
-                double derivativeX = 2 * Math.Pow(halfHeight, 2) * (currentX - centerX);
-                double derivativeY = 2 * Math.Pow(halfWidth, 2) * (currentY - centerY);
-                currentSlope = -(derivativeX / derivativeY);
-            }
-            while (currentSlope > -1 && currentY - centerY > 0.5);
+        public override string Tooltip => "Draws circle on canvas (C). Hold Shift to draw even circle.";
 
 
-            // from PI/4 to 0
-            while (currentY - centerY >= 0)
+        public override void OnKeyDown(KeyEventArgs e)
+        {
+            if (e.Key == Key.LeftShift)
             {
             {
-                AddRegionPoints(outputCoordinates, currentX, centerX, currentY, centerY);
-
-                currentY--;
-                if ((Math.Pow(halfHeight, 2) * Math.Pow(currentX - centerX + 0.5, 2)) +
-                    (Math.Pow(halfWidth, 2) * Math.Pow(currentY - centerY, 2)) -
-                    (Math.Pow(halfWidth, 2) * Math.Pow(halfHeight, 2)) < 0)
-                {
-                    currentX++;
-                }
+                ActionDisplay = "Click and move mouse to draw an even circle.";
             }
             }
-
-            return outputCoordinates;
         }
         }
 
 
-        private static List<Coordinates> GenerateFallbackRectangle(double halfWidth, double halfHeight, double centerX, double centerY)
+        public override void OnKeyUp(KeyEventArgs e)
         {
         {
-            List<Coordinates> coordinates = new List<Coordinates>();
-
-            int left = (int)Math.Floor(centerX - halfWidth);
-            int top = (int)Math.Floor(centerY - halfHeight);
-            int right = (int)Math.Floor(centerX + halfWidth);
-            int bottom = (int)Math.Floor(centerY + halfHeight);
-
-            for (int x = left; x <= right; x++)
-            {
-                coordinates.Add(new Coordinates(x, top));
-                coordinates.Add(new Coordinates(x, bottom));
-            }
-
-            for (int y = top; y <= bottom; y++)
+            if (e.Key == Key.LeftShift)
             {
             {
-                coordinates.Add(new Coordinates(left, y));
-                coordinates.Add(new Coordinates(right, y));
+                ActionDisplay = "Click and move mouse to draw a circle. Hold Shift to draw an even one.";
             }
             }
-
-            return coordinates;
         }
         }
 
 
-        private static void AddRegionPoints(List<Coordinates> coordinates, double x, double xc, double y, double yc)
+        public override void Use(Layer layer, List<Coordinates> coordinates, SKColor color)
         {
         {
-            int xFloor = (int)Math.Floor(x);
-            int yFloor = (int)Math.Floor(y);
-            int xFloorInv = (int)Math.Floor(-x + 2 * xc);
-            int yFloorInv = (int)Math.Floor(-y + 2 * yc);
-
-            //top and bottom or left and right
-            if (xFloor == xFloorInv || yFloor == yFloorInv)
-            {
-                coordinates.Add(new Coordinates(xFloor, yFloor));
-                coordinates.Add(new Coordinates(xFloorInv, yFloorInv));
-            }
-            //part of the arc
-            else
-            {
-                coordinates.Add(new Coordinates(xFloor, yFloor));
-                coordinates.Add(new Coordinates(xFloorInv, yFloorInv));
-                coordinates.Add(new Coordinates(xFloorInv, yFloor));
-                coordinates.Add(new Coordinates(xFloor, yFloorInv));
-            }
+            int thickness = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
+            var hasFillColor = Toolbar.GetSetting<BoolSetting>("Fill").Value;
+            Color temp = Toolbar.GetSetting<ColorSetting>("FillColor").Value;
+            SKColor fill = new SKColor(temp.R, temp.G, temp.B, temp.A);
+            DrawEllipseFromCoordinates(layer, coordinates[^1], coordinates[0], color, fill, thickness, hasFillColor);
         }
         }
+
     }
     }
 }
 }

+ 237 - 237
PixiEditor/Models/Tools/Tools/MoveTool.cs

@@ -1,237 +1,237 @@
-using PixiEditor.Models.Controllers;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Enums;
-using PixiEditor.Models.ImageManipulation;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Undo;

-using PixiEditor.ViewModels;
-using SkiaSharp;

-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows;
-using System.Windows.Input;
-using Transform = PixiEditor.Models.ImageManipulation.Transform;
-
-namespace PixiEditor.Models.Tools.Tools
-{
-    public class MoveTool : BitmapOperationTool
-    {
-        private Layer[] affectedLayers;
-        private Dictionary<Guid, bool> clearedPixels = new Dictionary<Guid, bool>();
-        private Coordinates[] currentSelection;
-        private Coordinates lastMouseMove;
-        private Dictionary<Guid, SKColor[]> startPixelColors;
-        private Dictionary<Guid, SKColor[]> endPixelColors;
-        private Dictionary<Guid, Thickness> startingOffsets;
-        private Coordinates[] startSelection;
-        private bool updateViewModelSelection = true;

-

-        public MoveTool(BitmapManager bitmapManager)
-        {
-            ActionDisplay = "Hold mouse to move selected pixels. Hold Ctrl to move all layers.";
-            Cursor = Cursors.Arrow;
-            RequiresPreviewLayer = true;
-            UseDefaultUndoMethod = true;
-
-            BitmapManager = bitmapManager;
-        }

-

-        public override string Tooltip => "Moves selected pixels (V). Hold Ctrl to move all layers.";

-

-        public override bool HideHighlight => true;

-

-        public bool MoveAll { get; set; }
-
-        private BitmapManager BitmapManager { get; }
-
-        public override void OnKeyDown(KeyEventArgs e)

-        {

-            if (e.Key == Key.LeftCtrl)

-            {

-                ActionDisplay = "Hold mouse to move all selected layers.";

-            }

-        }
-
-        public override void OnKeyUp(KeyEventArgs e)

-        {

-            if (e.Key == Key.LeftCtrl)

-            {

-                ActionDisplay = "Hold mouse to move selected pixels. Hold Ctrl to move all layers.";

-            }

-        }
-
-        public override void AfterAddedUndo(UndoManager undoManager)
-        {

-            //if (currentSelection == null || currentSelection.Length == 0)
-            //{
-            //    return;
-            //}
-            //Change changes = undoManager.UndoStack.Peek();

-            //// Inject to default undo system change custom changes made by this tool
-            //foreach (var item in startPixelColors)
-            //{
-            //    BitmapPixelChanges beforeMovePixels = BitmapPixelChanges.FromArrays(startSelection, item.Value);
-            //    BitmapPixelChanges afterMovePixels = BitmapPixelChanges.FromArrays(currentSelection, endPixelColors[item.Key]);
-            //    Guid layerGuid = item.Key;
-            //    var oldValue = (LayerChange[])changes.OldValue;

-            //    if (oldValue.Any(x => x.LayerGuid == layerGuid))
-            //    {
-            //        var layer = oldValue.First(x => x.LayerGuid == layerGuid);
-            //        layer.PixelChanges.ChangedPixels.AddRangeOverride(afterMovePixels.ChangedPixels);
-            //        layer.PixelChanges.ChangedPixels
-            //            .AddRangeOverride(beforeMovePixels.ChangedPixels);

-            //        ((LayerChange[])changes.NewValue).First(x => x.LayerGuid == layerGuid).PixelChanges.ChangedPixels
-            //            .AddRangeNewOnly(BitmapPixelChanges
-            //                .FromSingleColoredArray(startSelection, SKColors.Transparent)
-            //                .ChangedPixels);
-            //    }
-            //}
-        }
-
-        // This adds undo if there is no selection, reason why this isn't in AfterUndoAdded,
-        // is because it doesn't fire if no pixel changes were made.
-        public override void OnStoppedRecordingMouseUp(MouseEventArgs e)
-        {
-            if (currentSelection != null && currentSelection.Length == 0)
-            {
-                BitmapManager.ActiveDocument.UndoManager.AddUndoChange(new Change(
-                    ApplyOffsets,
-                    new object[] { startingOffsets },
-                    ApplyOffsets,
-                    new object[] { GetOffsets(affectedLayers) },
-                    "Move layers"));
-            }
-        }
-
-        public override void OnStart(Coordinates startPos)
-        {
-            ResetSelectionValues(startPos);

-            // Move offset if no selection
-            Document doc = BitmapManager.ActiveDocument;
-            Selection selection = doc.ActiveSelection;
-            if (selection != null && selection.SelectedPoints.Count > 0)
-            {
-                currentSelection = selection.SelectedPoints.ToArray();
-            }
-            else
-            {
-                currentSelection = Array.Empty<Coordinates>();
-            }

-            if (Keyboard.IsKeyDown(Key.LeftCtrl) || MoveAll)
-            {
-                affectedLayers = doc.Layers.Where(x => x.IsVisible).ToArray();
-            }
-            else
-            {
-                affectedLayers = doc.Layers.Where(x => x.IsActive && doc.GetFinalLayerIsVisible(x)).ToArray();
-            }

-            startSelection = currentSelection;
-            startPixelColors = BitmapUtils.GetPixelsForSelection(affectedLayers, startSelection);
-            startingOffsets = GetOffsets(affectedLayers);
-        }
-
-        public override void Use(Layer layer, List<Coordinates> mouseMove, SKColor color)
-        {
-            //LayerChange[] result = new LayerChange[affectedLayers.Length];
-            //var end = mouseMove[0];

-            //var lastSelection = currentSelection.ToArray();
-            //for (int i = 0; i < affectedLayers.Length; i++)
-            //{
-            //    if (currentSelection.Length > 0)
-            //    {
-            //        endPixelColors = BitmapUtils.GetPixelsForSelection(affectedLayers, currentSelection);
-            //        var changes = MoveSelection(affectedLayers[i], mouseMove);

-            //        ClearSelectedPixels(affectedLayers[i], lastSelection);
-
-            //        changes = RemoveTransparentPixels(changes);
-
-            //        result[i] = new LayerChange(changes, affectedLayers[i]);
-            //    }
-            //    else
-            //    {
-            //        var vector = Transform.GetTranslation(lastMouseMove, end);
-            //        affectedLayers[i].Offset = new Thickness(affectedLayers[i].OffsetX + vector.X, affectedLayers[i].OffsetY + vector.Y, 0, 0);
-            //        result[i] = new LayerChange(BitmapPixelChanges.Empty, affectedLayers[i]);
-            //    }
-            //}
-

-            //lastMouseMove = end;
-
-            // return result;
-        }

-        public BitmapPixelChanges MoveSelection(Layer layer, IEnumerable<Coordinates> mouseMove)
-        {
-            Coordinates end = mouseMove.First();
-
-            currentSelection = TranslateSelection(end);
-            if (updateViewModelSelection)
-            {
-                ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.SetSelection(currentSelection, SelectionType.New);
-            }

-            lastMouseMove = end;
-            return BitmapPixelChanges.FromArrays(currentSelection, startPixelColors[layer.LayerGuid]);
-        }

-        private void ApplyOffsets(object[] parameters)
-        {
-            Dictionary<Guid, Thickness> offsets = (Dictionary<Guid, Thickness>)parameters[0];
-            foreach (var offset in offsets)
-            {
-                Layer layer = ViewModelMain.Current?.BitmapManager?.
-                    ActiveDocument?.Layers?.First(x => x.LayerGuid == offset.Key);
-                layer.Offset = offset.Value;
-            }
-        }
-
-        private Dictionary<Guid, Thickness> GetOffsets(Layer[] layers)
-        {
-            Dictionary<Guid, Thickness> dict = new Dictionary<Guid, Thickness>();
-            for (int i = 0; i < layers.Length; i++)
-            {
-                dict.Add(layers[i].LayerGuid, layers[i].Offset);
-            }
-
-            return dict;
-        }
-
-        private BitmapPixelChanges RemoveTransparentPixels(BitmapPixelChanges pixels)
-        {
-            foreach (var item in pixels.ChangedPixels.Where(x => x.Value.Alpha == 0).ToList())
-            {
-                pixels.ChangedPixels.Remove(item.Key);
-            }
-            return pixels;
-        }
-
-        private void ResetSelectionValues(Coordinates start)
-        {
-            lastMouseMove = start;
-            clearedPixels = new Dictionary<Guid, bool>();
-            endPixelColors = new Dictionary<Guid, SKColor[]>();
-            currentSelection = null;
-            affectedLayers = null;
-            updateViewModelSelection = true;
-            startPixelColors = null;
-            startSelection = null;
-        }
-
-        private Coordinates[] TranslateSelection(Coordinates end)
-        {
-            Coordinates translation = Transform.GetTranslation(lastMouseMove, end);
-            return Transform.Translate(currentSelection, translation);
-        }
-
-        private void ClearSelectedPixels(Layer layer, Coordinates[] selection)
-        {
-            Guid layerGuid = layer.LayerGuid;
-            if (!clearedPixels.ContainsKey(layerGuid) || clearedPixels[layerGuid] == false)
-            {
-                BitmapManager.ActiveDocument.Layers.First(x => x == layer)
-                    .SetPixels(BitmapPixelChanges.FromSingleColoredArray(selection, SKColors.Transparent));
-
-                clearedPixels[layerGuid] = true;
-            }
-        }
-    }
-}
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.ImageManipulation;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Undo;
+using PixiEditor.ViewModels;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Input;
+using Transform = PixiEditor.Models.ImageManipulation.Transform;
+
+namespace PixiEditor.Models.Tools.Tools
+{
+    public class MoveTool : BitmapOperationTool
+    {
+        private Layer[] affectedLayers;
+        private Dictionary<Guid, bool> clearedPixels = new Dictionary<Guid, bool>();
+        private Coordinates[] currentSelection;
+        private Coordinates lastMouseMove;
+        private Dictionary<Guid, SKColor[]> startPixelColors;
+        private Dictionary<Guid, SKColor[]> endPixelColors;
+        private Dictionary<Guid, Thickness> startingOffsets;
+        private Coordinates[] startSelection;
+        private bool updateViewModelSelection = true;
+
+        public MoveTool(BitmapManager bitmapManager)
+        {
+            ActionDisplay = "Hold mouse to move selected pixels. Hold Ctrl to move all layers.";
+            Cursor = Cursors.Arrow;
+            RequiresPreviewLayer = true;
+            UseDefaultUndoMethod = true;
+
+            BitmapManager = bitmapManager;
+        }
+
+        public override string Tooltip => "Moves selected pixels (V). Hold Ctrl to move all layers.";
+
+        public override bool HideHighlight => true;
+
+        public bool MoveAll { get; set; }
+
+        private BitmapManager BitmapManager { get; }
+
+        public override void OnKeyDown(KeyEventArgs e)
+        {
+            if (e.Key == Key.LeftCtrl)
+            {
+                ActionDisplay = "Hold mouse to move all selected layers.";
+            }
+        }
+
+        public override void OnKeyUp(KeyEventArgs e)
+        {
+            if (e.Key == Key.LeftCtrl)
+            {
+                ActionDisplay = "Hold mouse to move selected pixels. Hold Ctrl to move all layers.";
+            }
+        }
+
+        public override void AfterAddedUndo(UndoManager undoManager)
+        {
+            //if (currentSelection == null || currentSelection.Length == 0)
+            //{
+            //    return;
+            //}
+            //Change changes = undoManager.UndoStack.Peek();
+            //// Inject to default undo system change custom changes made by this tool
+            //foreach (var item in startPixelColors)
+            //{
+            //    BitmapPixelChanges beforeMovePixels = BitmapPixelChanges.FromArrays(startSelection, item.Value);
+            //    BitmapPixelChanges afterMovePixels = BitmapPixelChanges.FromArrays(currentSelection, endPixelColors[item.Key]);
+            //    Guid layerGuid = item.Key;
+            //    var oldValue = (LayerChange[])changes.OldValue;
+            //    if (oldValue.Any(x => x.LayerGuid == layerGuid))
+            //    {
+            //        var layer = oldValue.First(x => x.LayerGuid == layerGuid);
+            //        layer.PixelChanges.ChangedPixels.AddRangeOverride(afterMovePixels.ChangedPixels);
+            //        layer.PixelChanges.ChangedPixels
+            //            .AddRangeOverride(beforeMovePixels.ChangedPixels);
+            //        ((LayerChange[])changes.NewValue).First(x => x.LayerGuid == layerGuid).PixelChanges.ChangedPixels
+            //            .AddRangeNewOnly(BitmapPixelChanges
+            //                .FromSingleColoredArray(startSelection, SKColors.Transparent)
+            //                .ChangedPixels);
+            //    }
+            //}
+        }
+
+        // This adds undo if there is no selection, reason why this isn't in AfterUndoAdded,
+        // is because it doesn't fire if no pixel changes were made.
+        public override void OnStoppedRecordingMouseUp(MouseEventArgs e)
+        {
+            if (currentSelection != null && currentSelection.Length == 0)
+            {
+                BitmapManager.ActiveDocument.UndoManager.AddUndoChange(new Change(
+                    ApplyOffsets,
+                    new object[] { startingOffsets },
+                    ApplyOffsets,
+                    new object[] { GetOffsets(affectedLayers) },
+                    "Move layers"));
+            }
+        }
+
+        public override void OnStart(Coordinates startPos)
+        {
+            ResetSelectionValues(startPos);
+            // Move offset if no selection
+            Document doc = BitmapManager.ActiveDocument;
+            Selection selection = doc.ActiveSelection;
+            if (selection != null && selection.SelectedPoints.Count > 0)
+            {
+                currentSelection = selection.SelectedPoints.ToArray();
+            }
+            else
+            {
+                currentSelection = Array.Empty<Coordinates>();
+            }
+            if (Keyboard.IsKeyDown(Key.LeftCtrl) || MoveAll)
+            {
+                affectedLayers = doc.Layers.Where(x => x.IsVisible).ToArray();
+            }
+            else
+            {
+                affectedLayers = doc.Layers.Where(x => x.IsActive && doc.GetFinalLayerIsVisible(x)).ToArray();
+            }
+            startSelection = currentSelection;
+            startPixelColors = BitmapUtils.GetPixelsForSelection(affectedLayers, startSelection);
+            startingOffsets = GetOffsets(affectedLayers);
+        }
+
+        public override void Use(Layer layer, List<Coordinates> mouseMove, SKColor color)
+        {
+            //LayerChange[] result = new LayerChange[affectedLayers.Length];
+            //var end = mouseMove[0];
+            //var lastSelection = currentSelection.ToArray();
+            //for (int i = 0; i < affectedLayers.Length; i++)
+            //{
+            //    if (currentSelection.Length > 0)
+            //    {
+            //        endPixelColors = BitmapUtils.GetPixelsForSelection(affectedLayers, currentSelection);
+            //        var changes = MoveSelection(affectedLayers[i], mouseMove);
+            //        ClearSelectedPixels(affectedLayers[i], lastSelection);
+
+            //        changes = RemoveTransparentPixels(changes);
+
+            //        result[i] = new LayerChange(changes, affectedLayers[i]);
+            //    }
+            //    else
+            //    {
+            //        var vector = Transform.GetTranslation(lastMouseMove, end);
+            //        affectedLayers[i].Offset = new Thickness(affectedLayers[i].OffsetX + vector.X, affectedLayers[i].OffsetY + vector.Y, 0, 0);
+            //        result[i] = new LayerChange(BitmapPixelChanges.Empty, affectedLayers[i]);
+            //    }
+            //}
+
+            //lastMouseMove = end;
+
+            // return result;
+        }
+        public BitmapPixelChanges MoveSelection(Layer layer, IEnumerable<Coordinates> mouseMove)
+        {
+            Coordinates end = mouseMove.First();
+
+            currentSelection = TranslateSelection(end);
+            if (updateViewModelSelection)
+            {
+                ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.SetSelection(currentSelection, SelectionType.New);
+            }
+            lastMouseMove = end;
+            return BitmapPixelChanges.FromArrays(currentSelection, startPixelColors[layer.LayerGuid]);
+        }
+        private void ApplyOffsets(object[] parameters)
+        {
+            Dictionary<Guid, Thickness> offsets = (Dictionary<Guid, Thickness>)parameters[0];
+            foreach (var offset in offsets)
+            {
+                Layer layer = ViewModelMain.Current?.BitmapManager?.
+                    ActiveDocument?.Layers?.First(x => x.LayerGuid == offset.Key);
+                layer.Offset = offset.Value;
+            }
+        }
+
+        private Dictionary<Guid, Thickness> GetOffsets(Layer[] layers)
+        {
+            Dictionary<Guid, Thickness> dict = new Dictionary<Guid, Thickness>();
+            for (int i = 0; i < layers.Length; i++)
+            {
+                dict.Add(layers[i].LayerGuid, layers[i].Offset);
+            }
+
+            return dict;
+        }
+
+        private BitmapPixelChanges RemoveTransparentPixels(BitmapPixelChanges pixels)
+        {
+            foreach (var item in pixels.ChangedPixels.Where(x => x.Value.Alpha == 0).ToList())
+            {
+                pixels.ChangedPixels.Remove(item.Key);
+            }
+            return pixels;
+        }
+
+        private void ResetSelectionValues(Coordinates start)
+        {
+            lastMouseMove = start;
+            clearedPixels = new Dictionary<Guid, bool>();
+            endPixelColors = new Dictionary<Guid, SKColor[]>();
+            currentSelection = null;
+            affectedLayers = null;
+            updateViewModelSelection = true;
+            startPixelColors = null;
+            startSelection = null;
+        }
+
+        private Coordinates[] TranslateSelection(Coordinates end)
+        {
+            Coordinates translation = Transform.GetTranslation(lastMouseMove, end);
+            return Transform.Translate(currentSelection, translation);
+        }
+
+        private void ClearSelectedPixels(Layer layer, Coordinates[] selection)
+        {
+            Guid layerGuid = layer.LayerGuid;
+            if (!clearedPixels.ContainsKey(layerGuid) || clearedPixels[layerGuid] == false)
+            {
+                BitmapManager.ActiveDocument.Layers.First(x => x == layer)
+                    .SetPixels(BitmapPixelChanges.FromSingleColoredArray(selection, SKColors.Transparent));
+
+                clearedPixels[layerGuid] = true;
+            }
+        }
+    }
+}

+ 8 - 7
PixiEditor/Views/UserControls/PlainLayerView.xaml.cs

@@ -27,6 +27,14 @@ namespace PixiEditor.Views.UserControls
             SizeChanged += OnControlSizeChanged;
             SizeChanged += OnControlSizeChanged;
         }
         }
 
 
+        public void Resize(int newWidth, int newHeight)
+        {
+            renderer?.Dispose();
+            renderer = new SurfaceRenderer(newWidth, newHeight);
+            image.Source = renderer.FinalBitmap;
+            Update();
+        }
+
         private static void OnLayerChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
         private static void OnLayerChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
         {
         {
             var view = (PlainLayerView)sender;
             var view = (PlainLayerView)sender;
@@ -39,13 +47,6 @@ namespace PixiEditor.Views.UserControls
                 view.Resize(layer.Width, layer.Height);
                 view.Resize(layer.Width, layer.Height);
             }
             }
         }
         }
-        public void Resize(int newWidth, int newHeight)
-        {
-            renderer?.Dispose();
-            renderer = new SurfaceRenderer(newWidth, newHeight);
-            image.Source = renderer.FinalBitmap;
-            Update();
-        }
 
 
         private void Update()
         private void Update()
         {
         {

+ 2 - 2
PixiEditorTests/PixiEditorTests.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 <Project Sdk="Microsoft.NET.Sdk">
 
 
   <PropertyGroup>
   <PropertyGroup>
-    <TargetFramework>net5.0-windows</TargetFramework>
+    <TargetFramework>net6.0-windows10.0.22000.0</TargetFramework>
 
 
     <IsPackable>false</IsPackable>
     <IsPackable>false</IsPackable>
 
 
@@ -35,7 +35,7 @@
       <PrivateAssets>all</PrivateAssets>
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
     </PackageReference>
-    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
     <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
     <PackageReference Include="Moq" Version="4.16.1" />
     <PackageReference Include="Moq" Version="4.16.1" />