Browse Source

Refactor circle generation

Equbuxu 3 years ago
parent
commit
c84716d79d

+ 43 - 15
PixiEditor/Helpers/EllipseGenerator.cs

@@ -1,6 +1,7 @@
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
@@ -9,15 +10,40 @@ namespace PixiEditor.Helpers
 {
 {
     internal static class EllipseGenerator
     internal static class EllipseGenerator
     {
     {
-
-        public static List<Coordinates> GenerateEllipseFromRect(DoubleCoords rect)
+        public static List<DoubleCoords> SplitEllipseIntoLines(List<Coordinates> ellipse)
+        {
+            List<DoubleCoords> lines = new();
+            var sorted = ellipse.OrderBy(
+                a => a,
+                Comparer<Coordinates>.Create((a, b) => a.Y != b.Y ? a.Y - b.Y : a.X - b.X)
+                );
+
+            int minX = int.MaxValue;
+            int maxX = int.MinValue;
+            Coordinates? prev = null;
+            foreach (var point in sorted)
+            {
+                if (prev.HasValue && point.Y != prev.Value.Y)
+                {
+                    int prevY = prev.Value.Y;
+                    lines.Add(new DoubleCoords(new(minX, prevY), new(maxX, prevY)));
+                    minX = int.MaxValue;
+                    maxX = int.MinValue;
+                }
+                minX = Math.Min(point.X, minX);
+                maxX = Math.Max(point.X, maxX);
+                prev = point;
+            }
+            lines.Add(new DoubleCoords(new(minX, prev.Value.Y), new(maxX, prev.Value.Y)));
+            return lines;
+        }
+        public static List<Coordinates> GenerateEllipseFromRect(DoubleCoords rect, List<Coordinates> listToFill = null)
         {
         {
             float radiusX = (rect.Coords2.X - rect.Coords1.X) / 2.0f;
             float radiusX = (rect.Coords2.X - rect.Coords1.X) / 2.0f;
             float radiusY = (rect.Coords2.Y - rect.Coords1.Y) / 2.0f;
             float radiusY = (rect.Coords2.Y - rect.Coords1.Y) / 2.0f;
             float centerX = (rect.Coords1.X + rect.Coords2.X + 1) / 2.0f;
             float centerX = (rect.Coords1.X + rect.Coords2.X + 1) / 2.0f;
             float centerY = (rect.Coords1.Y + rect.Coords2.Y + 1) / 2.0f;
             float centerY = (rect.Coords1.Y + rect.Coords2.Y + 1) / 2.0f;
-
-            return GenerateMidpointEllipse(radiusX, radiusY, centerX, centerY);
+            return GenerateMidpointEllipse(radiusX, radiusY, centerX, centerY, listToFill);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -34,11 +60,18 @@ namespace PixiEditor.Helpers
         /// Center is at (2; 2). It's a place where 4 pixels meet
         /// 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
         /// 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>
         /// </summary>
-        public static List<Coordinates> GenerateMidpointEllipse(double halfWidth, double halfHeight, double centerX, double centerY)
+        public static List<Coordinates> GenerateMidpointEllipse(
+            double halfWidth,
+            double halfHeight,
+            double centerX,
+            double centerY,
+            List<Coordinates> listToFill = null)
         {
         {
+            listToFill ??= new List<Coordinates>();
             if (halfWidth < 1 || halfHeight < 1)
             if (halfWidth < 1 || halfHeight < 1)
             {
             {
-                return GenerateFallbackRectangle(halfWidth, halfHeight, centerX, centerY);
+                AddFallbackRectangle(halfWidth, halfHeight, centerX, centerY, listToFill);
+                return listToFill;
             }
             }
 
 
             // ellipse formula: halfHeight^2 * x^2 + halfWidth^2 * y^2 - halfHeight^2 * halfWidth^2 = 0
             // ellipse formula: halfHeight^2 * x^2 + halfWidth^2 * y^2 - halfHeight^2 * halfWidth^2 = 0
@@ -47,14 +80,13 @@ namespace PixiEditor.Helpers
             double currentX = Math.Ceiling(centerX - 0.5) + 0.5;
             double currentX = Math.Ceiling(centerX - 0.5) + 0.5;
             double currentY = centerY + halfHeight;
             double currentY = centerY + halfHeight;
 
 
-            List<Coordinates> outputCoordinates = new List<Coordinates>();
 
 
             double currentSlope;
             double currentSlope;
 
 
             // from PI/2 to PI/4
             // from PI/2 to PI/4
             do
             do
             {
             {
-                AddRegionPoints(outputCoordinates, currentX, centerX, currentY, centerY);
+                AddRegionPoints(listToFill, currentX, centerX, currentY, centerY);
 
 
                 // calculate next pixel coords
                 // calculate next pixel coords
                 currentX++;
                 currentX++;
@@ -76,7 +108,7 @@ namespace PixiEditor.Helpers
             // from PI/4 to 0
             // from PI/4 to 0
             while (currentY - centerY >= 0)
             while (currentY - centerY >= 0)
             {
             {
-                AddRegionPoints(outputCoordinates, currentX, centerX, currentY, centerY);
+                AddRegionPoints(listToFill, currentX, centerX, currentY, centerY);
 
 
                 currentY--;
                 currentY--;
                 if ((Math.Pow(halfHeight, 2) * Math.Pow(currentX - centerX + 0.5, 2)) +
                 if ((Math.Pow(halfHeight, 2) * Math.Pow(currentX - centerX + 0.5, 2)) +
@@ -87,13 +119,11 @@ namespace PixiEditor.Helpers
                 }
                 }
             }
             }
 
 
-            return outputCoordinates;
+            return listToFill;
         }
         }
 
 
-        private static List<Coordinates> GenerateFallbackRectangle(double halfWidth, double halfHeight, double centerX, double centerY)
+        private static void AddFallbackRectangle(double halfWidth, double halfHeight, double centerX, double centerY, List<Coordinates> coordinates)
         {
         {
-            List<Coordinates> coordinates = new List<Coordinates>();
-
             int left = (int)Math.Floor(centerX - halfWidth);
             int left = (int)Math.Floor(centerX - halfWidth);
             int top = (int)Math.Floor(centerY - halfHeight);
             int top = (int)Math.Floor(centerY - halfHeight);
             int right = (int)Math.Floor(centerX + halfWidth);
             int right = (int)Math.Floor(centerX + halfWidth);
@@ -110,8 +140,6 @@ namespace PixiEditor.Helpers
                 coordinates.Add(new Coordinates(left, y));
                 coordinates.Add(new Coordinates(left, y));
                 coordinates.Add(new Coordinates(right, y));
                 coordinates.Add(new Coordinates(right, y));
             }
             }
-
-            return coordinates;
         }
         }
 
 
         private static void AddRegionPoints(List<Coordinates> coordinates, double x, double xc, double y, double yc)
         private static void AddRegionPoints(List<Coordinates> coordinates, double x, double xc, double y, double yc)

+ 9 - 104
PixiEditor/Models/ImageManipulation/ToolCalculator.cs

@@ -1,3 +1,4 @@
+using PixiEditor.Helpers;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
@@ -6,8 +7,10 @@ using PixiEditor.Models.Tools.Tools;
 using SkiaSharp;
 using SkiaSharp;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Linq;
 using System.Windows;
 using System.Windows;
+using System.Windows.Input;
 
 
 namespace PixiEditor.Models.ImageManipulation
 namespace PixiEditor.Models.ImageManipulation
 {
 {
@@ -48,9 +51,8 @@ namespace PixiEditor.Models.ImageManipulation
             List<Coordinates> output)
             List<Coordinates> output)
         {
         {
             DoubleCoords fixedCoordinates = CalculateCoordinatesForShapeRotation(start, end);
             DoubleCoords fixedCoordinates = CalculateCoordinatesForShapeRotation(start, end);
-
-            CreateEllipse(fixedCoordinates.Coords1, fixedCoordinates.Coords2, output);
-
+            
+            EllipseGenerator.GenerateEllipseFromRect(fixedCoordinates, output);
             if (fill)
             if (fill)
             {
             {
                 CalculateFillForEllipse(output);
                 CalculateFillForEllipse(output);
@@ -195,115 +197,18 @@ namespace PixiEditor.Models.ImageManipulation
         private static void CalculateFillForEllipse(List<Coordinates> outlineCoordinates)
         private static void CalculateFillForEllipse(List<Coordinates> outlineCoordinates)
         {
         {
             if (!outlineCoordinates.Any())
             if (!outlineCoordinates.Any())
-            {
                 return;
                 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++)
-                {
-                    outlineCoordinates.Add(new Coordinates(j, i));
-                }
-            }
-        }
-
-
-        /// <summary>
-        ///     Calculates ellipse points for specified coordinates and thickness.
-        /// </summary>
-        /// <param name="startCoordinates">Top left coordinate of ellipse.</param>
-        /// <param name="endCoordinates">Bottom right coordinate of ellipse.</param>
-        private static void CreateEllipse(Coordinates startCoordinates, Coordinates endCoordinates, List<Coordinates> output)
-        {
-            double radiusX = (endCoordinates.X - startCoordinates.X) / 2.0;
-            double radiusY = (endCoordinates.Y - startCoordinates.Y) / 2.0;
-            double centerX = (startCoordinates.X + endCoordinates.X + 1) / 2.0;
-            double centerY = (startCoordinates.Y + endCoordinates.Y + 1) / 2.0;
-
-            MidpointEllipse(radiusX, radiusY, centerX, centerY, output);
-        }
-
-        private static void MidpointEllipse(double halfWidth, double halfHeight, double centerX, double centerY, List<Coordinates> output)
-        {
-            if (halfWidth < 1 || halfHeight < 1)
-            {
-                FallbackRectangle(halfWidth, halfHeight, centerX, centerY, output);
-            }
-
-            // 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;
-
-            double currentSlope;
-
-            // from PI/2 to middle
-            do
+            var lines = EllipseGenerator.SplitEllipseIntoLines(outlineCoordinates);
+            foreach (var line in lines)
             {
             {
-                GetRegionPoints(currentX, centerX, currentY, centerY, output);
-
-                // 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)
-            {
-                GetRegionPoints(currentX, centerX, currentY, centerY, output);
-
-                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)
+                for (int i = line.Coords1.X; i <= line.Coords2.X; i++)
                 {
                 {
-                    currentX++;
+                    outlineCoordinates.Add(new Coordinates(i, line.Coords1.Y));
                 }
                 }
             }
             }
         }
         }
 
 
-        private static void FallbackRectangle(double halfWidth, double halfHeight, double centerX, double centerY, List<Coordinates> output)
-        {
-            for (double x = centerX - halfWidth; x <= centerX + halfWidth; x++)
-            {
-                output.Add(new Coordinates((int)x, (int)(centerY - halfHeight)));
-                output.Add(new Coordinates((int)x, (int)(centerY + halfHeight)));
-            }
-
-            for (double y = centerY - halfHeight + 1; y <= centerY + halfHeight - 1; y++)
-            {
-                output.Add(new Coordinates((int)(centerX - halfWidth), (int)y));
-                output.Add(new Coordinates((int)(centerX + halfWidth), (int)y));
-            }
-        }
-
-        private static void GetRegionPoints(double x, double xc, double y, double yc, List<Coordinates> output)
-        {
-            output.Add(new Coordinates((int)Math.Floor(x), (int)Math.Floor(y)));
-            output.Add(new Coordinates((int)Math.Floor(-(x - xc) + xc), (int)Math.Floor(y)));
-            output.Add(new Coordinates((int)Math.Floor(x), (int)Math.Floor(-(y - yc) + yc)));
-            output.Add(new Coordinates((int)Math.Floor(-(x - xc) + xc), (int)Math.Floor(-(y - yc) + yc)));
-        }
-
         private static void CalculateRectangleFillNonAlloc(Coordinates start, Coordinates end, int thickness, List<Coordinates> output)
         private static void CalculateRectangleFillNonAlloc(Coordinates start, Coordinates end, int thickness, List<Coordinates> output)
         {
         {
             int offset = (int)Math.Ceiling(thickness / 2f);
             int offset = (int)Math.Ceiling(thickness / 2f);

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

@@ -115,25 +115,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 = EllipseGenerator.GenerateEllipseFromRect(rect);
             List<Coordinates> circle = EllipseGenerator.GenerateEllipseFromRect(rect);
-            circle.Sort((a, b) => a.Y != b.Y ? a.Y - b.Y : a.X - b.X);
-
-            circleCache.Clear();
-
-            int minX = int.MaxValue;
-            int maxX = int.MinValue;
-            for (int i = 0; i < circle.Count; i++)
-            {
-                if (i >= 1 && circle[i].Y != circle[i - 1].Y)
-                {
-                    int prevY = circle[i - 1].Y;
-                    circleCache.Add(new DoubleCoords(new(minX, prevY), new(maxX, prevY)));
-                    minX = int.MaxValue;
-                    maxX = int.MinValue;
-                }
-                minX = Math.Min(circle[i].X, minX);
-                maxX = Math.Max(circle[i].X, maxX);
-            }
-            circleCache.Add(new DoubleCoords(new(minX, circle[^1].Y), new(maxX, circle[^1].Y)));
+            circleCache = EllipseGenerator.SplitEllipseIntoLines(circle);
         }
         }
     }
     }
 }
 }