Browse Source

BrushShapeOverlay done

Equbuxu 3 years ago
parent
commit
3ab7422c53
23 changed files with 204 additions and 30 deletions
  1. 19 0
      src/PixiEditor/Helpers/Converters/InverseBoolToVisibilityConverter.cs
  2. 5 7
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/EllipseToolExecutor.cs
  3. 1 1
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/LineToolExecutor.cs
  4. 5 6
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RectangleToolExecutor.cs
  5. 10 0
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/ShapeToolExecutor.cs
  6. 2 0
      src/PixiEditor/ViewModels/SubViewModels/Tools/ShapeTool.cs
  7. 3 0
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tool.cs
  8. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/ToolSettings/Toolbars/MagicWandToolbar.cs
  9. 3 0
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/BrightnessToolViewModel.cs
  10. 3 0
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/ColorPickerToolViewModel.cs
  11. 4 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/EraserToolViewModel.cs
  12. 3 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/FloodFillToolViewModel.cs
  13. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/LineToolViewModel.cs
  14. 3 0
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/MagicWandToolViewModel.cs
  15. 2 0
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/MoveToolViewModel.cs
  16. 2 0
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/MoveViewportToolViewModel.cs
  17. 2 0
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/PenToolViewModel.cs
  18. 8 2
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/RectangleToolViewModel.cs
  19. 3 0
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/SelectToolViewModel.cs
  20. 3 0
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/ZoomToolViewModel.cs
  21. 2 0
      src/PixiEditor/Views/UserControls/BrushShapeOverlay/BrushShape.cs
  22. 117 10
      src/PixiEditor/Views/UserControls/BrushShapeOverlay/BrushShapeOverlay.cs
  23. 2 0
      src/PixiEditor/Views/UserControls/Viewport.xaml

+ 19 - 0
src/PixiEditor/Helpers/Converters/InverseBoolToVisibilityConverter.cs

@@ -0,0 +1,19 @@
+using System.Globalization;
+using System.Windows;
+
+namespace PixiEditor.Helpers.Converters;
+
+internal class InverseBoolToVisibilityConverter : SingleInstanceConverter<InverseBoolToVisibilityConverter>
+{
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        bool boolean = (bool)value;
+        return boolean ? Visibility.Collapsed : Visibility.Visible;
+    }
+
+    public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        throw new NotImplementedException();
+    }
+}
+

+ 5 - 7
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/EllipseToolExecutor.cs

@@ -8,14 +8,12 @@ internal class EllipseToolExecutor : ShapeToolExecutor<EllipseToolViewModel>
 {
     private void DrawEllipseOrCircle(VecI curPos)
     {
-        RectI rect = RectI.FromTwoPoints(startPos, curPos);
-        if (rect.Width == 0)
-            rect.Width = 1;
-        if (rect.Height == 0)
-            rect.Height = 1;
-
+        RectI rect;
         if (toolViewModel!.DrawCircle)
-            rect.Width = rect.Height = Math.Min(rect.Width, rect.Height);
+            rect = GetSquaredCoordinates(startPos, curPos);
+        else
+            rect = RectI.FromTwoPixels(startPos, curPos);
+
         lastRect = rect;
 
         internals!.ActionAccumulator.AddActions(new DrawEllipse_Action(memberGuid, rect, strokeColor, fillColor, strokeWidth, drawOnMask));

+ 1 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/LineToolExecutor.cs

@@ -35,7 +35,7 @@ internal class LineToolExecutor : ShapeToolExecutor<LineToolViewModel>
 
     private void DrawLine(VecI curPos)
     {
-        RectI rect = RectI.FromTwoPoints(startPos, curPos);
+        RectI rect = RectI.FromTwoPixels(startPos, curPos);
         if (rect.Width == 0)
             rect.Width = 1;
         if (rect.Height == 0)

+ 5 - 6
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RectangleToolExecutor.cs

@@ -8,12 +8,11 @@ internal class RectangleToolExecutor : ShapeToolExecutor<RectangleToolViewModel>
 {
     private void DrawRectangle(VecI curPos)
     {
-        RectI rect = RectI.FromTwoPoints(startPos, curPos);
-        if (rect.Width == 0)
-            rect.Width = 1;
-        if (rect.Height == 0)
-            rect.Height = 1;
-        
+        RectI rect;
+        if (toolViewModel!.DrawSquare)
+            rect = GetSquaredCoordinates(startPos, curPos);
+        else
+            rect = RectI.FromTwoPixels(startPos, curPos);
         lastRect = rect;
 
         internals!.ActionAccumulator.AddActions(new DrawRectangle_Action(memberGuid, new ShapeData(rect.Center, rect.Size, 0, strokeWidth, strokeColor, fillColor), drawOnMask));

+ 10 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/ShapeToolExecutor.cs

@@ -51,6 +51,16 @@ internal abstract class ShapeToolExecutor<T> : UpdateableChangeExecutor where T
     protected abstract IAction TransformMovedAction(ShapeData data, ShapeCorners corners);
     protected abstract IAction EndDrawAction();
 
+    protected RectI GetSquaredCoordinates(VecI startPos, VecI curPos)
+    {
+        VecI pos1 = (VecI)(((VecD)curPos).ProjectOntoLine(startPos, startPos + new VecD(1, 1)) - new VecD(0.25).Multiply((curPos - startPos).Signs())).Round();
+        VecI pos2 = (VecI)(((VecD)curPos).ProjectOntoLine(startPos, startPos + new VecD(1, -1)) - new VecD(0.25).Multiply((curPos - startPos).Signs())).Round();
+
+        if ((pos1 - curPos).LengthSquared > (pos2 - curPos).LengthSquared)
+            return RectI.FromTwoPixels(startPos, (VecI)pos2);
+        return RectI.FromTwoPixels(startPos, (VecI)pos1);
+    }
+
     public override void OnTransformMoved(ShapeCorners corners)
     {
         if (!transforming)

+ 2 - 0
src/PixiEditor/ViewModels/SubViewModels/Tools/ShapeTool.cs

@@ -1,10 +1,12 @@
 using System.Windows.Input;
 using PixiEditor.ViewModels.SubViewModels.Tools.ToolSettings.Toolbars;
+using PixiEditor.Views.UserControls.BrushShapeOverlay;
 
 namespace PixiEditor.ViewModels.SubViewModels.Tools;
 
 internal abstract class ShapeTool : ToolViewModel
 {
+    public override BrushShape BrushShape => BrushShape.Pixel;
     public ShapeTool()
     {
         Cursor = Cursors.Cross;

+ 3 - 0
src/PixiEditor/ViewModels/SubViewModels/Tools/Tool.cs

@@ -2,6 +2,7 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.ViewModels.SubViewModels.Tools.ToolSettings.Toolbars;
+using PixiEditor.Views.UserControls.BrushShapeOverlay;
 
 namespace PixiEditor.ViewModels.SubViewModels.Tools;
 
@@ -15,6 +16,8 @@ internal abstract class ToolViewModel : NotifyableObject
 
     public virtual string ImagePath => $"/Images/Tools/{ToolName}Image.png";
 
+    public virtual BrushShape BrushShape => BrushShape.Square;
+
     public virtual bool HideHighlight { get; }
 
     public abstract string Tooltip { get; }

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/ToolSettings/Toolbars/MagicWandToolbar.cs

@@ -4,7 +4,7 @@ using PixiEditor.ViewModels.SubViewModels.Tools.ToolSettings.Settings;
 
 namespace PixiEditor.ViewModels.SubViewModels.Tools.ToolSettings.Toolbars;
 
-internal class MagicWandToolbar : BasicToolbar
+internal class MagicWandToolbar : Toolbar
 {
     public SelectionMode SelectMode => GetSetting<EnumSetting<SelectionMode>>(nameof(SelectMode)).Value;
     public DocumentScope DocumentScope => GetSetting<EnumSetting<DocumentScope>>(nameof(DocumentScope)).Value;

+ 3 - 0
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/BrightnessToolViewModel.cs

@@ -2,6 +2,7 @@
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Enums;
 using PixiEditor.ViewModels.SubViewModels.Tools.ToolSettings.Toolbars;
+using PixiEditor.Views.UserControls.BrushShapeOverlay;
 
 namespace PixiEditor.ViewModels.SubViewModels.Tools.Tools;
 
@@ -20,6 +21,8 @@ internal class BrightnessToolViewModel : ToolViewModel
 
     public override string Tooltip => $"Makes pixels brighter or darker ({Shortcut}). Hold Ctrl to make pixels darker.";
 
+    public override BrushShape BrushShape => BrushShape.Circle;
+
     public BrightnessMode Mode { get; set; } = BrightnessMode.Default;
 
     public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)

+ 3 - 0
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/ColorPickerToolViewModel.cs

@@ -1,6 +1,7 @@
 using System.Windows.Input;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.Models.Commands.Attributes.Commands;
+using PixiEditor.Views.UserControls.BrushShapeOverlay;
 
 namespace PixiEditor.ViewModels.SubViewModels.Tools.Tools;
 
@@ -16,6 +17,8 @@ internal class ColorPickerToolViewModel : ToolViewModel
 
     public override bool HideHighlight => true;
 
+    public override BrushShape BrushShape => BrushShape.Pixel;
+
     public override string Tooltip => $"Picks the primary color from the canvas. ({Shortcut})";
 
     public override void OnLeftMouseButtonDown(VecD pos)

+ 4 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/EraserToolViewModel.cs

@@ -2,6 +2,7 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.ViewModels.SubViewModels.Tools.ToolSettings.Toolbars;
+using PixiEditor.Views.UserControls.BrushShapeOverlay;
 
 namespace PixiEditor.ViewModels.SubViewModels.Tools.Tools;
 
@@ -14,8 +15,10 @@ internal class EraserToolViewModel : ToolViewModel
         Toolbar = new BasicToolbar();
     }
 
+    public override BrushShape BrushShape => BrushShape.Circle;
+
     public override string Tooltip => $"Erasers color from pixel. ({Shortcut})";
-    
+
     public override void OnLeftMouseButtonDown(VecD pos)
     {
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseEraserTool();

+ 3 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/FloodFillToolViewModel.cs

@@ -1,6 +1,6 @@
 using System.Windows.Input;
 using PixiEditor.Models.Commands.Attributes.Commands;
-using SkiaSharp;
+using PixiEditor.Views.UserControls.BrushShapeOverlay;
 
 namespace PixiEditor.ViewModels.SubViewModels.Tools.Tools;
 
@@ -9,6 +9,8 @@ internal class FloodFillToolViewModel : ToolViewModel
 {
     private SKPaint fillPaint = new SKPaint() { BlendMode = SKBlendMode.Src };
 
+    public override BrushShape BrushShape => BrushShape.Pixel;
+
     public FloodFillToolViewModel()
     {
         ActionDisplay = "Press on an area to fill it.";

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/LineToolViewModel.cs

@@ -25,7 +25,7 @@ internal class LineToolViewModel : ShapeTool
         else
             ActionDisplay = defaultActionDisplay;
     }
-    
+
     public override void OnLeftMouseButtonDown(VecD pos)
     {
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseLineTool();

+ 3 - 0
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/MagicWandToolViewModel.cs

@@ -1,6 +1,7 @@
 using System.Windows.Input;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.ViewModels.SubViewModels.Tools.ToolSettings.Toolbars;
+using PixiEditor.Views.UserControls.BrushShapeOverlay;
 
 namespace PixiEditor.ViewModels.SubViewModels.Tools.Tools;
 
@@ -9,6 +10,8 @@ internal class MagicWandToolViewModel : ToolViewModel
 {
     public override string Tooltip => $"Magic Wand ({Shortcut}). Flood's the selection";
 
+    public override BrushShape BrushShape => BrushShape.Pixel;
+
     public MagicWandToolViewModel()
     {
         Toolbar = new MagicWandToolbar();

+ 2 - 0
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/MoveToolViewModel.cs

@@ -1,5 +1,6 @@
 using System.Windows.Input;
 using PixiEditor.Models.Commands.Attributes.Commands;
+using PixiEditor.Views.UserControls.BrushShapeOverlay;
 
 namespace PixiEditor.ViewModels.SubViewModels.Tools.Tools;
 
@@ -16,6 +17,7 @@ internal class MoveToolViewModel : ToolViewModel
 
     public override string Tooltip => $"Moves selected pixels ({Shortcut}). Hold Ctrl to move all layers.";
 
+    public override BrushShape BrushShape => BrushShape.Hidden;
     public override bool HideHighlight => true;
 
     public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)

+ 2 - 0
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/MoveViewportToolViewModel.cs

@@ -1,5 +1,6 @@
 using System.Windows.Input;
 using PixiEditor.Models.Commands.Attributes.Commands;
+using PixiEditor.Views.UserControls.BrushShapeOverlay;
 
 namespace PixiEditor.ViewModels.SubViewModels.Tools.Tools;
 
@@ -12,6 +13,7 @@ internal class MoveViewportToolViewModel : ToolViewModel
         ActionDisplay = "Click and move to pan viewport.";
     }
 
+    public override BrushShape BrushShape => BrushShape.Hidden;
     public override bool HideHighlight => true;
     public override string Tooltip => $"Move viewport. ({Shortcut})";
 }

+ 2 - 0
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/PenToolViewModel.cs

@@ -2,12 +2,14 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.ViewModels.SubViewModels.Tools.ToolSettings.Toolbars;
+using PixiEditor.Views.UserControls.BrushShapeOverlay;
 
 namespace PixiEditor.ViewModels.SubViewModels.Tools.Tools
 {
     [Command.Tool(Key = Key.B)]
     internal class PenToolViewModel : ShapeTool
     {
+        public override BrushShape BrushShape => BrushShape.Circle;
         public PenToolViewModel()
         {
             Cursor = Cursors.Pen;

+ 8 - 2
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/RectangleToolViewModel.cs

@@ -16,15 +16,21 @@ internal class RectangleToolViewModel : ShapeTool
     public override string Tooltip => $"Draws rectangle on canvas ({Shortcut}). Hold Shift to draw a square.";
 
     public bool Filled { get; set; } = false;
-
+    public bool DrawSquare { get; private set; } = false;
     public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
     {
         if (shiftIsDown)
+        {
+            DrawSquare = true;
             ActionDisplay = "Click and move to draw a square.";
+        }
         else
+        {
+            DrawSquare = false;
             ActionDisplay = defaultActionDisplay;
+        }
     }
-    
+
     public override void OnLeftMouseButtonDown(VecD pos)
     {
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseRectangleTool();

+ 3 - 0
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/SelectToolViewModel.cs

@@ -3,6 +3,7 @@ using ChunkyImageLib.DataHolders;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.ViewModels.SubViewModels.Tools.ToolSettings.Toolbars;
+using PixiEditor.Views.UserControls.BrushShapeOverlay;
 
 namespace PixiEditor.ViewModels.SubViewModels.Tools.Tools;
 
@@ -17,6 +18,8 @@ internal class SelectToolViewModel : ToolViewModel
 
     public SelectionMode SelectionType { get; set; } = SelectionMode.Add;
 
+    public override BrushShape BrushShape => BrushShape.Pixel;
+
     public override string Tooltip => $"Selects area. ({Shortcut})";
 
     public override void OnLeftMouseButtonDown(VecD pos)

+ 3 - 0
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/ZoomToolViewModel.cs

@@ -1,5 +1,6 @@
 using System.Windows.Input;
 using PixiEditor.Models.Commands.Attributes.Commands;
+using PixiEditor.Views.UserControls.BrushShapeOverlay;
 
 namespace PixiEditor.ViewModels.SubViewModels.Tools.Tools;
 
@@ -15,6 +16,8 @@ internal class ZoomToolViewModel : ToolViewModel
 
     private string defaultActionDisplay = "Click and move to zoom. Click to zoom in, hold ctrl and click to zoom out.";
 
+    public override BrushShape BrushShape => BrushShape.Hidden;
+
     public ZoomToolViewModel()
     {
         ActionDisplay = defaultActionDisplay;

+ 2 - 0
src/PixiEditor/Views/UserControls/BrushShapeOverlay/BrushShape.cs

@@ -1,6 +1,8 @@
 namespace PixiEditor.Views.UserControls.BrushShapeOverlay;
 internal enum BrushShape
 {
+    Hidden,
+    Pixel,
     Square,
     Circle
 }

+ 117 - 10
src/PixiEditor/Views/UserControls/BrushShapeOverlay/BrushShapeOverlay.cs

@@ -22,6 +22,16 @@ internal class BrushShapeOverlay : Control
     public static readonly DependencyProperty MouseReferenceProperty =
         DependencyProperty.Register(nameof(MouseReference), typeof(UIElement), typeof(BrushShapeOverlay), new(null));
 
+    public static readonly DependencyProperty BrushShapeProperty =
+        DependencyProperty.Register(nameof(BrushShape), typeof(BrushShape), typeof(BrushShapeOverlay),
+            new FrameworkPropertyMetadata(BrushShape.Circle, FrameworkPropertyMetadataOptions.AffectsRender));
+
+    public BrushShape BrushShape
+    {
+        get => (BrushShape)GetValue(BrushShapeProperty);
+        set => SetValue(BrushShapeProperty, value);
+    }
+
     public UIElement? MouseReference
     {
         get => (UIElement?)GetValue(MouseReferenceProperty);
@@ -46,8 +56,7 @@ internal class BrushShapeOverlay : Control
         set => SetValue(ZoomboxScaleProperty, value);
     }
 
-    private Pen whitePen = new Pen(Brushes.White, 1);
-    private Pen blackPen = new Pen(Brushes.Black, 1);
+    private Pen whitePen = new Pen(Brushes.LightGray, 1);
     private Point lastMousePos = new();
 
     public BrushShapeOverlay()
@@ -72,7 +81,7 @@ internal class BrushShapeOverlay : Control
 
     private void SourceMouseMove(object sender, MouseEventArgs args)
     {
-        if (MouseReference is null)
+        if (MouseReference is null || BrushShape == BrushShape.Hidden)
             return;
         lastMousePos = args.GetPosition(MouseReference);
         InvalidateVisual();
@@ -84,29 +93,128 @@ internal class BrushShapeOverlay : Control
             (Point)(new Point(Math.Floor(lastMousePos.X), Math.Floor(lastMousePos.Y)) - new Point(BrushSize / 2, BrushSize / 2)),
             new Size(BrushSize, BrushSize)
             );
-        var rectI = new RectI((int)winRect.X, (int)winRect.Y, (int)winRect.Width, (int)winRect.Height);
+        switch (BrushShape)
+        {
+            case BrushShape.Pixel:
+                drawingContext.DrawRectangle(
+                    null, whitePen, new Rect(new Point(Math.Floor(lastMousePos.X), Math.Floor(lastMousePos.Y)), new Size(1, 1)));
+                break;
+            case BrushShape.Square:
+                drawingContext.DrawRectangle(null, whitePen, winRect);
+                break;
+            case BrushShape.Circle:
+                DrawCircleBrushShape(drawingContext, winRect);
+                break;
+        }
+    }
 
+    private void DrawCircleBrushShape(DrawingContext drawingContext, Rect winRect)
+    {
+        var rectI = new RectI((int)winRect.X, (int)winRect.Y, (int)winRect.Width, (int)winRect.Height);
         if (BrushSize < 3)
         {
-            drawingContext.DrawRectangle(null, blackPen, winRect);
+            drawingContext.DrawRectangle(null, whitePen, winRect);
+        }
+        else if (BrushSize == 3)
+        {
+            var lp = new VecI((int)lastMousePos.X, (int)lastMousePos.Y);
+            PathFigure figure = new PathFigure()
+            {
+                StartPoint = new Point(lp.X, lp.Y),
+                Segments = new PathSegmentCollection()
+                {
+                    new LineSegment(new(lp.X, lp.Y - 1), true),
+                    new LineSegment(new(lp.X + 1, lp.Y - 1), true),
+                    new LineSegment(new(lp.X + 1, lp.Y), true),
+                    new LineSegment(new(lp.X + 2, lp.Y), true),
+                    new LineSegment(new(lp.X + 2, lp.Y + 1), true),
+                    new LineSegment(new(lp.X + 2, lp.Y + 1), true),
+                    new LineSegment(new(lp.X + 1, lp.Y + 1), true),
+                    new LineSegment(new(lp.X + 1, lp.Y + 2), true),
+                    new LineSegment(new(lp.X, lp.Y + 2), true),
+                    new LineSegment(new(lp.X, lp.Y + 1), true),
+                    new LineSegment(new(lp.X - 1, lp.Y + 1), true),
+                    new LineSegment(new(lp.X - 1, lp.Y), true),
+                },
+                IsClosed = true
+            };
+
+            var geometry = new PathGeometry(new PathFigure[] { figure });
+            drawingContext.DrawGeometry(null, whitePen, geometry);
+        }
+        else if (BrushSize > 200)
+        {
+            VecD center = rectI.Center;
+            drawingContext.DrawEllipse(null, whitePen, new Point(center.X, center.Y), rectI.Width / 2.0, rectI.Height / 2.0);
         }
         else
         {
             var geometry = ConstructEllipseOutline(rectI);
             drawingContext.DrawGeometry(null, whitePen, geometry);
         }
-        //drawingContext.DrawRectangle(null, whitePen, winRect.inf);
     }
 
+    private static int Mod(int x, int m) => (x % m + m) % m;
+
     private static PathGeometry ConstructEllipseOutline(RectI rectangle)
     {
-        var points = EllipseHelper.GenerateEllipseFromRect(rectangle);
         var center = rectangle.Center;
+        var points = EllipseHelper.GenerateEllipseFromRect(rectangle);
         points.Sort((vec, vec2) => Math.Sign((vec - center).Angle - (vec2 - center).Angle));
+        List<VecI> finalPoints = new();
+        for (int i = 0; i < points.Count; i++)
+        {
+            VecI prev = points[Mod(i - 1, points.Count)];
+            VecI point = points[i];
+            VecI next = points[Mod(i + 1, points.Count)];
+
+            bool atBottom = point.Y >= center.Y;
+            bool onRight = point.X >= center.X;
+            if (atBottom)
+            {
+                if (onRight)
+                {
+                    if (prev.Y != point.Y)
+                        finalPoints.Add(new(point.X + 1, point.Y));
+                    finalPoints.Add(new(point.X + 1, point.Y + 1));
+                    if (next.X != point.X)
+                        finalPoints.Add(new(point.X, point.Y + 1));
+
+                }
+                else
+                {
+                    if (prev.X != point.X)
+                        finalPoints.Add(new(point.X + 1, point.Y + 1));
+                    finalPoints.Add(new(point.X, point.Y + 1));
+                    if (next.Y != point.Y)
+                        finalPoints.Add(point);
+                }
+            }
+            else
+            {
+                if (onRight)
+                {
+                    if (prev.X != point.X)
+                        finalPoints.Add(point);
+                    finalPoints.Add(new(point.X + 1, point.Y));
+                    if (next.Y != point.Y)
+                        finalPoints.Add(new(point.X + 1, point.Y + 1));
+                }
+                else
+                {
+                    if (prev.Y != point.Y)
+                        finalPoints.Add(new(point.X, point.Y + 1));
+                    finalPoints.Add(point);
+                    if (next.X != point.X)
+                        finalPoints.Add(new(point.X + 1, point.Y));
+                }
+            }
+        }
+
         PathFigure figure = new PathFigure()
         {
-            StartPoint = new Point(points[0].X, points[0].Y),
-            Segments = new PathSegmentCollection(points.Select(static point => new LineSegment(new Point(point.X, point.Y), true))),
+            StartPoint = new Point(finalPoints[0].X, finalPoints[0].Y),
+            Segments = new PathSegmentCollection(finalPoints.Select(static point => new LineSegment(new Point(point.X, point.Y), true))),
             IsClosed = true
         };
 
@@ -119,6 +227,5 @@ internal class BrushShapeOverlay : Control
         var self = (BrushShapeOverlay)obj;
         double newScale = (double)args.NewValue;
         self.whitePen.Thickness = 1.0 / newScale;
-        self.blackPen.Thickness = 1.0 / newScale;
     }
 }

+ 2 - 0
src/PixiEditor/Views/UserControls/Viewport.xaml

@@ -111,10 +111,12 @@
                         ZoomboxScale="{Binding Zoombox.Scale}" />
                     <brush:BrushShapeOverlay
                         IsHitTestVisible="False"
+                        Visibility="{Binding Document.TransformViewModel.TransformActive, Converter={converters:InverseBoolToVisibilityConverter}}"
                         ZoomboxScale="{Binding Zoombox.Scale}"
                         MouseEventSource="{Binding Zoombox.Tag.BackgroundGrid, Mode=OneTime}"
                         MouseReference="{Binding Zoombox.Tag.MainImage, Mode=OneTime}"
                         BrushSize="{Binding ToolsSubViewModel.ActiveBasicToolbar.ToolSize, Source={vm:MainVM}}"
+                        BrushShape="{Binding ToolsSubViewModel.ActiveTool.BrushShape, Source={vm:MainVM}, FallbackValue={x:Static brush:BrushShape.Hidden}}"
                         />
                     <to:TransformOverlay
                         Cursor="Arrow"