Browse Source

Fixed circle tool and round brush

flabbet 3 years ago
parent
commit
8cb2576e5a

+ 0 - 1
PixiEditor/Models/Controllers/BitmapManager.cs

@@ -257,7 +257,6 @@ namespace PixiEditor.Models.Controllers
                 _highlightPen.Draw(previewLayer, cords, cords, _highlightColor, ToolSize);
 
                 AdjustOffset(newPosition, previewLayer);
-
             }
 
             previewLayer.InvokeLayerBitmapChange();

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

@@ -1,6 +1,7 @@
 using SkiaSharp;
 using System;
 using System.Diagnostics;
+using System.Windows;
 
 namespace PixiEditor.Models.Position
 {
@@ -26,6 +27,11 @@ namespace PixiEditor.Models.Position
             return new Coordinates(tuple.width, tuple.height);
         }
 
+        public static Coordinates operator -(Coordinates coordiantes, Thickness thickness)
+        {
+            return new Coordinates(coordiantes.X - (int)thickness.Left, coordiantes.Y - (int)thickness.Top);
+        }
+
         public static Coordinates operator -(Coordinates coordiantes, int size)
         {
             return new Coordinates(coordiantes.X - size, coordiantes.Y - size);

+ 13 - 0
PixiEditor/Models/Tools/Brushes/Brush.cs

@@ -0,0 +1,13 @@
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+
+namespace PixiEditor.Models.Tools.Brushes
+{
+    public abstract class Brush
+    {
+        public abstract void Draw(Layer layer, Tool tool, Coordinates coordinates, SKPaint paint);
+    }
+}

+ 23 - 0
PixiEditor/Models/Tools/Brushes/CircleBrush.cs

@@ -0,0 +1,23 @@
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools.Tools;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
+using SkiaSharp;
+using System;
+
+namespace PixiEditor.Models.Tools.Brushes
+{
+    public class CircleBrush : Brush
+    {
+        public override void Draw(Layer layer, Tool tool, Coordinates coordinates, SKPaint paint)
+        {
+            int toolSize = tool.Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
+            int halfSize = (int)Math.Ceiling(toolSize / 2f);
+            int modifier = toolSize % 2 != 0 ? 1 : 0;
+            Coordinates topLeft = new Coordinates(coordinates.X - halfSize + modifier, coordinates.Y - halfSize + modifier);
+            Coordinates bottomRight = new Coordinates(coordinates.X + halfSize - 1, coordinates.Y + halfSize - 1);
+
+            CircleTool.DrawEllipseFromRect(layer, topLeft, bottomRight, paint.Color, paint.Color, 1, true);
+        }
+    }
+}

+ 14 - 0
PixiEditor/Models/Tools/Brushes/CircleBrushOverrides/InterestingShapeBrush.cs

@@ -0,0 +1,14 @@
+namespace PixiEditor.Models.Tools.Brushes
+{
+    public class InterestingShapeBrush : MatrixBrush
+    {
+        public static readonly int[,] InterestingShapeMatrix = new int[,]
+        {
+            { 1, 1, 1 },
+            { 0, 1, 0 },
+            { 0, 1, 0 }
+        };
+
+        public override int[,] BrushMatrix => InterestingShapeMatrix;
+    }
+}

+ 0 - 23
PixiEditor/Models/Tools/Brushes/CrossBrush.cs

@@ -1,23 +0,0 @@
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using SkiaSharp;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace PixiEditor.Models.Tools.Brushes
-{
-    public class CrossBrush : CustomBrush
-    {
-        public static readonly int[,] CrossMatrix = new int[3, 3]
-        {
-            { 0, 1, 0 },
-            { 1, 1, 1 },
-            { 0, 1, 0 }
-        };
-
-        public override int[,] BrushMatrix => CrossMatrix;
-    }
-}

+ 0 - 18
PixiEditor/Models/Tools/Brushes/PointBrush.cs

@@ -1,18 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace PixiEditor.Models.Tools.Brushes
-{
-    public class PointBrush : CustomBrush
-    {
-        public static readonly int[,] PointMatrix = new int[,]
-        {
-            { 1 }
-        };
-
-        public override int[,] BrushMatrix => PointMatrix;
-    }
-}

+ 11 - 4
PixiEditor/Models/Tools/Brushes/CustomBrush.cs → PixiEditor/Models/Tools/MatrixBrush.cs

@@ -1,12 +1,13 @@
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools.Brushes;
 using SkiaSharp;
 using System;
 using System.Collections.Generic;
 
-namespace PixiEditor.Models.Tools.Brushes
+namespace PixiEditor.Models.Tools
 {
-    public abstract class CustomBrush
+    public abstract class MatrixBrush : Brush
     {
         public abstract int[,] BrushMatrix { get; }
 
@@ -16,11 +17,16 @@ namespace PixiEditor.Models.Tools.Brushes
         private SKPoint[] _cachedBakedMatrix;
         private SKPoint[] _cachedGetAtPointBakedMatrix;
 
-        public CustomBrush()
+        public MatrixBrush()
         {
             InitMatrix();
         }
 
+        public override void Draw(Layer layer, Tool tool, Coordinates coordinates, SKPaint paint)
+        {
+            layer.LayerBitmap.SkiaSurface.Canvas.DrawPoints(SKPointMode.Points, GetAtPoint(coordinates, layer.OffsetX, layer.OffsetY), paint);
+        }
+
         //We can easily handle .pixi to brush parsing
 
         /// <summary>
@@ -63,7 +69,7 @@ namespace PixiEditor.Models.Tools.Brushes
                 InitMatrix();
             }
 
-            for(int i = 0; i < _cachedGetAtPointBakedMatrix.Length; i++)
+            for (int i = 0; i < _cachedGetAtPointBakedMatrix.Length; i++)
             {
                 _cachedGetAtPointBakedMatrix[i] = new SKPoint(
                     _cachedBakedMatrix[i].X + point.X - offsetX,
@@ -83,5 +89,6 @@ namespace PixiEditor.Models.Tools.Brushes
                 Array.Copy(_cachedBakedMatrix, _cachedGetAtPointBakedMatrix, BakedMatrix.Length);
             }
         }
+
     }
 }

+ 1 - 1
PixiEditor/Models/Tools/ShapeTool.cs

@@ -58,7 +58,7 @@ namespace PixiEditor.Models.Tools
         // TODO: Add cache for lines 31, 32 (hopefully it would speed up calculation)
         public abstract override void Use(Layer layer, List<Coordinates> coordinates, SKColor color);
 
-        protected static void ThickenShape(Layer layer, SKColor color, IEnumerable<Coordinates> shape, int thickness)
+        public static void ThickenShape(Layer layer, SKColor color, IEnumerable<Coordinates> shape, int thickness)
         {
             foreach (Coordinates item in shape)
             {

+ 157 - 11
PixiEditor/Models/Tools/Tools/CircleTool.cs

@@ -4,6 +4,7 @@ using PixiEditor.Models.Tools.ToolSettings.Settings;
 using SkiaSharp;
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using System.Windows;
 using System.Windows.Input;
 using System.Windows.Media;
@@ -39,7 +40,15 @@ namespace PixiEditor.Models.Tools.Tools
         {
             int thickness = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
             var hasFillColor = Toolbar.GetSetting<BoolSetting>("Fill").Value;
-            DoubleCords fixedCoordinates = CalculateCoordinatesForShapeRotation(coordinates[^1], coordinates[0]);
+            Color temp = Toolbar.GetSetting<ColorSetting>("FillColor").Value;
+            SKColor fill = new SKColor(temp.R, temp.G, temp.B, temp.A);
+            DrawEllipseFromRect(layer, coordinates[^1], coordinates[0], color, fill, thickness, hasFillColor);
+        }
+
+        public static void DrawEllipseFromRect(Layer layer, Coordinates first, Coordinates second,
+            SKColor color, SKColor fillColor, int thickness, bool hasFillColor)
+        {
+            DoubleCords fixedCoordinates = CalculateCoordinatesForShapeRotation(first, second);
 
             int halfThickness = (int)Math.Ceiling(thickness / 2.0);
             Int32Rect dirtyRect = new Int32Rect(
@@ -53,28 +62,165 @@ namespace PixiEditor.Models.Tools.Tools
             {
                 float radiusX = (fixedCoordinates.Coords2.X - fixedCoordinates.Coords1.X) / 2.0f;
                 float radiusY = (fixedCoordinates.Coords2.Y - fixedCoordinates.Coords1.Y) / 2.0f;
-                float centerX = (fixedCoordinates.Coords1.X + fixedCoordinates.Coords2.X + 1) / 2.0f - layer.OffsetX;
-                float centerY = (fixedCoordinates.Coords1.Y + fixedCoordinates.Coords2.Y + 1) / 2.0f - layer.OffsetY;
+                float centerX = (fixedCoordinates.Coords1.X + fixedCoordinates.Coords2.X + 1) / 2.0f;
+                float centerY = (fixedCoordinates.Coords1.Y + fixedCoordinates.Coords2.Y + 1) / 2.0f;
                 paint.BlendMode = SKBlendMode.Src;
 
+                paint.Color = color;
+                paint.StrokeWidth = thickness;
+                paint.Style = SKPaintStyle.Stroke;
+                var outline = DrawEllipse(layer, color, centerX, centerY, radiusX, radiusY, thickness);
+
                 if (hasFillColor)
                 {
-                    Color temp = Toolbar.GetSetting<ColorSetting>("FillColor").Value;
-                    SKColor fillColor = new SKColor(temp.R, temp.G, temp.B, temp.A);
-
                     paint.Color = fillColor;
                     paint.Style = SKPaintStyle.StrokeAndFill;
-                    layer.LayerBitmap.SkiaSurface.Canvas.DrawOval(centerX, centerY, radiusX, radiusY, paint);
+                    DrawEllipseFill(layer, fillColor, outline);
                 }
 
-                paint.Color = color;
-                paint.StrokeWidth = thickness;
-                paint.Style = SKPaintStyle.Stroke;
-                layer.LayerBitmap.SkiaSurface.Canvas.DrawOval(centerX, centerY, radiusX, radiusY, paint);
+                // An Idea, use Skia DrawOval for bigger sizes.
             }
 
             layer.InvokeLayerBitmapChange(dirtyRect);
+        }
+
+        /// <summary>
+        ///     Calculates ellipse points for specified coordinates and thickness.
+        /// </summary>
+        /// <param name="thickness">Thickness of ellipse.</param>
+        public static IEnumerable<Coordinates> DrawEllipse(Layer layer, SKColor color, float centerX, float centerY, float radiusX, float radiusY, int thickness)
+        {
+
+            IEnumerable<Coordinates> ellipse = GenerateMidpointEllipse(layer, color, radiusX, radiusY, centerX, centerY);
+            if (thickness > 1)
+            {
+                ShapeTool.ThickenShape(layer, color, ellipse, thickness);
+            }
+
+            return ellipse;
+        }
+
+        public static List<Coordinates> GenerateMidpointEllipse(Layer layer, SKColor color, double halfWidth, double halfHeight, double centerX, double centerY)
+        {
+            if (halfWidth < 1 || halfHeight < 1)
+            {
+                return DrawFallbackRectangle(layer, color, 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 middle
+            do
+            {
+                outputCoordinates.AddRange(DrawRegionPoints(layer, color, 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 middle to 0
+            while (currentY - centerY >= 0)
+            {
+                outputCoordinates.AddRange(DrawRegionPoints(layer, color, 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;
+        }
+
+        public static void DrawEllipseFill(Layer layer, SKColor color, IEnumerable<Coordinates> outlineCoordinates)
+        {
+            if (!outlineCoordinates.Any())
+            {
+                return;
+            }
+
+            int bottom = outlineCoordinates.Max(x => x.Y);
+            int top = outlineCoordinates.Min(x => x.Y);
+            for (int i = top + 1; i < bottom; i++)
+            {
+                IEnumerable<Coordinates> rowCords = outlineCoordinates.Where(x => x.Y == i);
+                int right = rowCords.Max(x => x.X);
+                int left = rowCords.Min(x => x.X);
+                for (int j = left + 1; j < right; j++)
+                {
+                    layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(new SKPoint(j - layer.OffsetX, i - layer.OffsetY), color);
+                }
+            }
+        }
+
+        private static List<Coordinates> DrawFallbackRectangle(Layer layer, SKColor color, double halfWidth, double halfHeight, double centerX, double centerY)
+        {
+            List<Coordinates> coordinates = new List<Coordinates>();
+
+            for (double x = centerX - halfWidth; x <= centerX + halfWidth; x++)
+            {
+                var cords = new Coordinates((int)x, (int)(centerY - halfHeight));
+                coordinates.Add(cords);
+                layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(new SKPoint(cords.X - layer.OffsetX, cords.Y - layer.OffsetY), color);
+
+                cords = new Coordinates((int)x, (int)(centerY + halfHeight));
+                coordinates.Add(cords);
+                layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(new SKPoint(cords.X - layer.OffsetX, cords.Y - layer.OffsetY), color);
+
+            }
+
+            for (double y = centerY - halfHeight + 1; y <= centerY + halfHeight - 1; y++)
+            {
+                var cords = new Coordinates((int)(centerX - halfWidth), (int)y);
+                coordinates.Add(cords);
+                layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(new SKPoint(cords.X - layer.OffsetX, cords.Y - layer.OffsetY), color);
+
+                cords = new Coordinates((int)(centerX + halfWidth), (int)y);
+                coordinates.Add(cords);
+                layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(new SKPoint(cords.X - layer.OffsetX, cords.Y - layer.OffsetY), color);
+
+            }
+
+            return coordinates;
+        }
+
+        private static Coordinates[] DrawRegionPoints(Layer layer, SKColor color, double x, double xc, double y, double yc)
+        {
+            Coordinates[] outputCoordinates = new Coordinates[4];
+            outputCoordinates[0] = new Coordinates((int)Math.Floor(x), (int)Math.Floor(y));
+            layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(new SKPoint(outputCoordinates[0].X - layer.OffsetX, outputCoordinates[0].Y - layer.OffsetY), color);
+            outputCoordinates[1] = new Coordinates((int)Math.Floor(-(x - xc) + xc), (int)Math.Floor(y));
+            layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(new SKPoint(outputCoordinates[1].X - layer.OffsetX, outputCoordinates[1].Y - layer.OffsetY), color);
+            outputCoordinates[2] = new Coordinates((int)Math.Floor(x), (int)Math.Floor(-(y - yc) + yc));
+            layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(new SKPoint(outputCoordinates[2].X - layer.OffsetX, outputCoordinates[2].Y - layer.OffsetY), color);
+            outputCoordinates[3] = new Coordinates((int)Math.Floor(-(x - xc) + xc), (int)Math.Floor(-(y - yc) + yc));
+            layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(new SKPoint(outputCoordinates[3].X - layer.OffsetX, outputCoordinates[3].Y - layer.OffsetY), color);
 
+            return outputCoordinates;
         }
     }
 }

+ 14 - 38
PixiEditor/Models/Tools/Tools/PenTool.cs

@@ -15,6 +15,10 @@ namespace PixiEditor.Models.Tools.Tools
 {
     public class PenTool : ShapeTool
     {
+        public Brush Brush { get; set; }
+        public List<Brush> Brushes { get; } = new List<Brush>();
+
+
         private readonly SizeSetting toolSizeSetting;
         private readonly BoolSetting pixelPerfectSetting;
         private readonly List<Coordinates> confirmedPixels = new List<Coordinates>();
@@ -26,9 +30,6 @@ namespace PixiEditor.Models.Tools.Tools
 
         private BitmapManager BitmapManager { get; }
 
-        private Dictionary<int, CustomBrush> _customBrushes = new Dictionary<int, CustomBrush>();
-
-        private bool _drawCustomBrush = false;
 
         public PenTool(BitmapManager bitmapManager)
         {
@@ -40,10 +41,12 @@ namespace PixiEditor.Models.Tools.Tools
             ClearPreviewLayerOnEachIteration = false;
             BitmapManager = bitmapManager;
             paint.BlendMode = SKBlendMode.Src;
-            lineTool = new LineTool();
-            lineTool.AutomaticallyResizeCanvas = AutomaticallyResizeCanvas;
-            _customBrushes.Add(1, new PointBrush());
-            _customBrushes.Add(3, new CrossBrush());
+            Brushes.Add(new CircleBrush());
+            Brush = Brushes[0];
+            lineTool = new LineTool
+            {
+                AutomaticallyResizeCanvas = AutomaticallyResizeCanvas
+            };
         }
 
         public override string Tooltip => "Standard brush. (B)";
@@ -62,6 +65,7 @@ namespace PixiEditor.Models.Tools.Tools
         public override void Use(Layer layer, List<Coordinates> coordinates, SKColor color)
         {
             Coordinates startingCords = coordinates.Count > 1 ? coordinates[1] : coordinates[0];
+            paint.Color = color;
             if (AutomaticallyResizeCanvas)
             {
                 layer.DynamicResizeAbsolute(coordinates.Max(x => x.X), coordinates.Max(x => x.Y), coordinates.Min(x => x.X), coordinates.Min(x => x.Y));
@@ -83,24 +87,12 @@ namespace PixiEditor.Models.Tools.Tools
             SKBlendMode blendMode = SKBlendMode.Src)
         {
 
-            SKStrokeCap cap = SKStrokeCap.Round;
-            _drawCustomBrush = _customBrushes.ContainsKey(toolSize);
-            CustomBrush customBrush = null;
+            SKStrokeCap cap = SKStrokeCap.Butt;
             paint.Color = color;
 
-            if (_drawCustomBrush)
-            {
-                cap = SKStrokeCap.Butt;
-                customBrush = _customBrushes[toolSize];
-            }
-
             if (!pixelPerfect)
             {
-                if(_drawCustomBrush)
-                {
-                    DrawCustomBrush(layer, latestCords, customBrush);
-                }
-
+                Brush.Draw(layer, this, latestCords, paint);
                 lineTool.DrawLine(layer, startingCoords, latestCords, color, toolSize, blendMode, cap);
                 return;
             }
@@ -112,10 +104,7 @@ namespace PixiEditor.Models.Tools.Tools
                     confirmedPixels.Add(latestCords);
                 }
 
-                if (_drawCustomBrush)
-                {
-                    DrawCustomBrush(layer, latestCords, customBrush);
-                }
+                Brush.Draw(layer, this, latestCords, paint);
 
                 lineTool.DrawLine(layer, startingCoords, latestCords, color, toolSize, blendMode, cap);
                 SetPixelToCheck(LineTool.GetBresenhamLine(startingCoords, latestCords));
@@ -143,14 +132,6 @@ namespace PixiEditor.Models.Tools.Tools
             lastChangedPixel = latestCords;
         }
 
-        private void DrawCustomBrush(Layer layer, Coordinates latestCords, CustomBrush customBrush)
-        {
-            layer.LayerBitmap.SkiaSurface.Canvas.DrawPoints(
-                                    SKPointMode.Points,
-                                    customBrush.GetAtPoint(latestCords, layer.OffsetX, layer.OffsetY),
-                                    paint);
-        }
-
         private void MovePixelsToCheck(byte alpha)
         {
             if (alpha != 0)
@@ -211,10 +192,5 @@ namespace PixiEditor.Models.Tools.Tools
             layer.InvokeLayerBitmapChange(dirtyRect);
             return alpha;
         }
-
-        internal void Draw(Layer previewLayer, object zero1, object zero2, SKColor highlightColor, int toolSize)
-        {
-            throw new NotImplementedException();
-        }
     }
 }