123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181 |
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Linq;
- using System.Numerics;
- using System.Windows.Media;
- using PixiEditor.Helpers.Extensions;
- using PixiEditor.Models.DataHolders;
- using PixiEditor.Models.Layers;
- using PixiEditor.Models.Position;
- namespace PixiEditor.Models.Tools.Tools
- {
- public class CircleTool : ShapeTool
- {
- public override ToolType ToolType => ToolType.Circle;
- public CircleTool()
- {
- Tooltip = "Draws circle on canvas (C). Hold Shift to draw even circle.";
- }
- public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
- {
- int thickness = (int) Toolbar.GetSetting("ToolSize").Value;
- DoubleCords fixedCoordinates = CalculateCoordinatesForShapeRotation(coordinates[^1], coordinates[0]);
- Coordinates[] outline = CreateEllipse(fixedCoordinates.Coords1, fixedCoordinates.Coords2, thickness);
- BitmapPixelChanges pixels = BitmapPixelChanges.FromSingleColoredArray(outline, color);
- if ((bool) Toolbar.GetSetting("Fill").Value)
- {
- Color fillColor = (Color) Toolbar.GetSetting("FillColor").Value;
- pixels.ChangedPixels.AddRangeNewOnly(
- BitmapPixelChanges.FromSingleColoredArray(CalculateFillForEllipse(outline), fillColor)
- .ChangedPixels);
- }
- return new[] {new LayerChange(pixels, layer)};
- }
- /// <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>
- /// <param name="thickness">Thickness of ellipse</param>
- /// <param name="filled">Should ellipse be filled</param>
- /// <returns>Coordinates for ellipse</returns>
- public Coordinates[] CreateEllipse(Coordinates startCoordinates, Coordinates endCoordinates, int thickness,
- bool filled)
- {
- List<Coordinates> output = new List<Coordinates>();
- Coordinates[] outline = CreateEllipse(startCoordinates, endCoordinates, thickness);
- output.AddRange(outline);
- if (filled)
- {
- output.AddRange(CalculateFillForEllipse(outline));
- return output.Distinct().ToArray();
- }
- return output.ToArray();
- }
- /// <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>
- /// <param name="thickness">Thickness of ellipse</param>
- /// <returns>Coordinates for ellipse</returns>
-
- public Coordinates[] CreateEllipse(Coordinates startCoordinates, Coordinates endCoordinates, int thickness)
- {
- 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;
- List<Coordinates> output = new List<Coordinates>();
- Coordinates[] ellipse = MidpointEllipse(radiusX, radiusY, centerX, centerY);
- if (thickness == 1)
- output.AddRange(ellipse);
- else
- output.AddRange(GetThickShape(ellipse, thickness));
- return output.Distinct().ToArray();
- }
- public Coordinates[] MidpointEllipse(double halfWidth, double halfHeight, double centerX, double centerY)
- {
- if (halfWidth < 1 || halfHeight < 1)
- return FallbackRectangle(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(GetRegionPoints(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(GetRegionPoints(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.ToArray();
- }
- private Coordinates[] FallbackRectangle(double halfWidth, double halfHeight, double centerX, double centerY)
- {
- List<Coordinates> coordinates = new List<Coordinates>();
- for (double x = centerX - halfWidth; x <= centerX + halfWidth; x++)
- {
- coordinates.Add(new Coordinates((int)x, (int)(centerY - halfHeight)));
- coordinates.Add(new Coordinates((int)x, (int)(centerY + halfHeight)));
- }
- for (double y = centerY - halfHeight + 1; y <= centerY + halfHeight - 1; y++)
- {
- coordinates.Add(new Coordinates((int)(centerX - halfWidth), (int)y));
- coordinates.Add(new Coordinates((int)(centerX + halfWidth), (int)y));
- }
- return coordinates.ToArray();
- }
- private Coordinates[] CalculateFillForEllipse(Coordinates[] outlineCoordinates)
- {
- List<Coordinates> finalCoordinates = new List<Coordinates>();
- int bottom = outlineCoordinates.Max(x => x.Y);
- int top = outlineCoordinates.Min(x => x.Y);
- for (int i = top + 1; i < bottom; i++)
- {
- var 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++) finalCoordinates.Add(new Coordinates(j, i));
- }
- return finalCoordinates.ToArray();
- }
- private Coordinates[] GetRegionPoints(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));
- outputCoordinates[1] = new Coordinates((int)Math.Floor((-(x - xc) + xc)), (int)Math.Floor(y));
- outputCoordinates[2] = new Coordinates((int)Math.Floor(x), (int)Math.Floor((-(y - yc) + yc)));
- outputCoordinates[3] = new Coordinates((int)Math.Floor((-(x - xc) + xc)), (int)Math.Floor((-(y - yc) + yc)));
- return outputCoordinates;
- }
- }
- }
|