Browse Source

Merge pull request #508 from PixiEditor/symmery-half-pixels

Symmetry: snap to half-pixels
Krzysztof Krysiński 2 years ago
parent
commit
f9aad942ea
31 changed files with 137 additions and 113 deletions
  1. 4 4
      src/ChunkyImageLib/ChunkyImage.cs
  2. 2 2
      src/ChunkyImageLib/DataHolders/ShapeCorners.cs
  3. 2 2
      src/ChunkyImageLib/DataHolders/ShapeData.cs
  4. 5 5
      src/ChunkyImageLib/Operations/BresenhamLineOperation.cs
  5. 3 3
      src/ChunkyImageLib/Operations/ChunkyImageOperation.cs
  6. 4 4
      src/ChunkyImageLib/Operations/ClearPathOperation.cs
  7. 3 3
      src/ChunkyImageLib/Operations/ClearRegionOperation.cs
  8. 5 5
      src/ChunkyImageLib/Operations/DrawingSurfaceLineOperation.cs
  9. 3 3
      src/ChunkyImageLib/Operations/EllipseOperation.cs
  10. 1 1
      src/ChunkyImageLib/Operations/IMirroredDrawOperation.cs
  11. 4 4
      src/ChunkyImageLib/Operations/ImageOperation.cs
  12. 4 4
      src/ChunkyImageLib/Operations/PathOperation.cs
  13. 3 3
      src/ChunkyImageLib/Operations/PixelOperation.cs
  14. 4 3
      src/ChunkyImageLib/Operations/PixelsOperation.cs
  15. 4 4
      src/ChunkyImageLib/Operations/RectangleOperation.cs
  16. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Root/Size_ChangeInfo.cs
  17. 1 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Root/SymmetryAxisPosition_ChangeInfo.cs
  18. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Document.cs
  19. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs
  20. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Root/ResizeBasedChangeBase.cs
  21. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Root/ResizeImage_Change.cs
  22. 6 6
      src/PixiEditor.ChangeableDocument/Changes/Root/RotateImage_Change.cs
  23. 5 5
      src/PixiEditor.ChangeableDocument/Changes/Root/SymmetryAxisPosition_UpdateableChange.cs
  24. 11 0
      src/PixiEditor.DrawingApi.Core/Numerics/RectD.cs
  25. 10 0
      src/PixiEditor.DrawingApi.Core/Numerics/RectI.cs
  26. 14 0
      src/PixiEditor.DrawingApi.Core/Numerics/VecI.cs
  27. 0 13
      src/PixiEditor/Models/DocumentModels/ChangeExecutionController.cs
  28. 3 3
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/SymmetryExecutor.cs
  29. 8 8
      src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.cs
  30. 1 1
      src/PixiEditor/Views/UserControls/Overlays/SymmetryOverlay/SymmetryAxisDragInfo.cs
  31. 18 17
      src/PixiEditor/Views/UserControls/Overlays/SymmetryOverlay/SymmetryOverlay.cs

+ 4 - 4
src/ChunkyImageLib/ChunkyImage.cs

@@ -82,8 +82,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     private BlendMode blendMode = BlendMode.Src;
     private BlendMode blendMode = BlendMode.Src;
     private bool lockTransparency = false;
     private bool lockTransparency = false;
     private VectorPath? clippingPath;
     private VectorPath? clippingPath;
-    private int? horizontalSymmetryAxis = null;
-    private int? verticalSymmetryAxis = null;
+    private double? horizontalSymmetryAxis = null;
+    private double? verticalSymmetryAxis = null;
 
 
     private readonly Dictionary<ChunkResolution, Dictionary<VecI, Chunk>> committedChunks;
     private readonly Dictionary<ChunkResolution, Dictionary<VecI, Chunk>> committedChunks;
     private readonly Dictionary<ChunkResolution, Dictionary<VecI, Chunk>> latestChunks;
     private readonly Dictionary<ChunkResolution, Dictionary<VecI, Chunk>> latestChunks;
@@ -429,7 +429,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
     }
 
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void SetHorizontalAxisOfSymmetry(int position)
+    public void SetHorizontalAxisOfSymmetry(double position)
     {
     {
         lock (lockObject)
         lock (lockObject)
         {
         {
@@ -441,7 +441,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
     }
 
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void SetVerticalAxisOfSymmetry(int position)
+    public void SetVerticalAxisOfSymmetry(double position)
     {
     {
         lock (lockObject)
         lock (lockObject)
         {
         {

+ 2 - 2
src/ChunkyImageLib/DataHolders/ShapeCorners.cs

@@ -127,7 +127,7 @@ public struct ShapeCorners
         return crossTop == crossRight && crossTop == crossLeft && crossTop == crossBottom;
         return crossTop == crossRight && crossTop == crossLeft && crossTop == crossBottom;
     }
     }
 
 
-    public ShapeCorners AsMirroredAcrossHorAxis(int horAxisY) => new ShapeCorners
+    public ShapeCorners AsMirroredAcrossHorAxis(double horAxisY) => new ShapeCorners
     {
     {
         BottomLeft = BottomLeft.ReflectY(horAxisY),
         BottomLeft = BottomLeft.ReflectY(horAxisY),
         BottomRight = BottomRight.ReflectY(horAxisY),
         BottomRight = BottomRight.ReflectY(horAxisY),
@@ -135,7 +135,7 @@ public struct ShapeCorners
         TopRight = TopRight.ReflectY(horAxisY)
         TopRight = TopRight.ReflectY(horAxisY)
     };
     };
 
 
-    public ShapeCorners AsMirroredAcrossVerAxis(int verAxisX) => new ShapeCorners
+    public ShapeCorners AsMirroredAcrossVerAxis(double verAxisX) => new ShapeCorners
     {
     {
         BottomLeft = BottomLeft.ReflectX(verAxisX),
         BottomLeft = BottomLeft.ReflectX(verAxisX),
         BottomRight = BottomRight.ReflectX(verAxisX),
         BottomRight = BottomRight.ReflectX(verAxisX),

+ 2 - 2
src/ChunkyImageLib/DataHolders/ShapeData.cs

@@ -26,9 +26,9 @@ public record struct ShapeData
     public double Angle { get; }
     public double Angle { get; }
     public int StrokeWidth { get; }
     public int StrokeWidth { get; }
 
 
-    public ShapeData AsMirroredAcrossHorAxis(int horAxisY)
+    public ShapeData AsMirroredAcrossHorAxis(double horAxisY)
         => new ShapeData(Center.ReflectY(horAxisY), new(Size.X, -Size.Y), -Angle, StrokeWidth, StrokeColor, FillColor, BlendMode);
         => new ShapeData(Center.ReflectY(horAxisY), new(Size.X, -Size.Y), -Angle, StrokeWidth, StrokeColor, FillColor, BlendMode);
-    public ShapeData AsMirroredAcrossVerAxis(int verAxisX)
+    public ShapeData AsMirroredAcrossVerAxis(double verAxisX)
         => new ShapeData(Center.ReflectX(verAxisX), new(-Size.X, Size.Y), -Angle, StrokeWidth, StrokeColor, FillColor, BlendMode);
         => new ShapeData(Center.ReflectX(verAxisX), new(-Size.X, Size.Y), -Angle, StrokeWidth, StrokeColor, FillColor, BlendMode);
 
 
 }
 }

+ 5 - 5
src/ChunkyImageLib/Operations/BresenhamLineOperation.cs

@@ -44,19 +44,19 @@ internal class BresenhamLineOperation : IMirroredDrawOperation
         return new AffectedArea(OperationHelper.FindChunksTouchingRectangle(bounds, ChunkyImage.FullChunkSize), bounds);
         return new AffectedArea(OperationHelper.FindChunksTouchingRectangle(bounds, ChunkyImage.FullChunkSize), bounds);
     }
     }
 
 
-    public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
+    public IDrawOperation AsMirrored(double? verAxisX, double? horAxisY)
     {
     {
         RectI newFrom = new RectI(from, new VecI(1));
         RectI newFrom = new RectI(from, new VecI(1));
         RectI newTo = new RectI(to, new VecI(1));
         RectI newTo = new RectI(to, new VecI(1));
         if (verAxisX is not null)
         if (verAxisX is not null)
         {
         {
-            newFrom = newFrom.ReflectX((int)verAxisX);
-            newTo = newTo.ReflectX((int)verAxisX);
+            newFrom = (RectI)newFrom.ReflectX((double)verAxisX).Round();
+            newTo = (RectI)newTo.ReflectX((double)verAxisX).Round();
         }
         }
         if (horAxisY is not null)
         if (horAxisY is not null)
         {
         {
-            newFrom = newFrom.ReflectY((int)horAxisY);
-            newTo = newTo.ReflectY((int)horAxisY);
+            newFrom = (RectI)newFrom.ReflectY((double)horAxisY).Round();
+            newTo = (RectI)newTo.ReflectY((double)horAxisY).Round();
         }
         }
         return new BresenhamLineOperation(newFrom.Pos, newTo.Pos, color, blendMode);
         return new BresenhamLineOperation(newFrom.Pos, newTo.Pos, color, blendMode);
     }
     }

+ 3 - 3
src/ChunkyImageLib/Operations/ChunkyImageOperation.cs

@@ -108,13 +108,13 @@ internal class ChunkyImageOperation : IMirroredDrawOperation
         return topLeft;
         return topLeft;
     }
     }
 
 
-    public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
+    public IDrawOperation AsMirrored(double? verAxisX, double? horAxisY)
     {
     {
         var newPos = targetPos;
         var newPos = targetPos;
         if (verAxisX is not null)
         if (verAxisX is not null)
-            newPos = newPos.ReflectX((int)verAxisX);
+            newPos = (VecI)newPos.ReflectX((double)verAxisX).Round();
         if (horAxisY is not null)
         if (horAxisY is not null)
-            newPos = newPos.ReflectY((int)horAxisY);
+            newPos = (VecI)newPos.ReflectY((double)horAxisY).Round();
         return new ChunkyImageOperation(imageToDraw, newPos, mirrorHorizontal ^ (verAxisX is not null), mirrorVertical ^ (horAxisY is not null));
         return new ChunkyImageOperation(imageToDraw, newPos, mirrorHorizontal ^ (verAxisX is not null), mirrorVertical ^ (horAxisY is not null));
     }
     }
 
 

+ 4 - 4
src/ChunkyImageLib/Operations/ClearPathOperation.cs

@@ -38,17 +38,17 @@ internal class ClearPathOperation : IMirroredDrawOperation
         path.Dispose();
         path.Dispose();
     }
     }
 
 
-    public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
+    public IDrawOperation AsMirrored(double? verAxisX, double? horAxisY)
     {
     {
-        var matrix = Matrix3X3.CreateScale(verAxisX is not null ? -1 : 1, horAxisY is not null ? -1 : 1, verAxisX ?? 0, horAxisY ?? 0);
+        var matrix = Matrix3X3.CreateScale(verAxisX is not null ? -1 : 1, horAxisY is not null ? -1 : 1, (float?)verAxisX ?? 0, (float?)horAxisY ?? 0);
         using var copy = new VectorPath(path);
         using var copy = new VectorPath(path);
         copy.Transform(matrix);
         copy.Transform(matrix);
 
 
         var newRect = pathTightBounds;
         var newRect = pathTightBounds;
         if (verAxisX is not null)
         if (verAxisX is not null)
-            newRect = newRect.ReflectX((int)verAxisX);
+            newRect = (RectI)newRect.ReflectX((double)verAxisX).Round();
         if (horAxisY is not null)
         if (horAxisY is not null)
-            newRect = newRect.ReflectY((int)horAxisY);
+            newRect = (RectI)newRect.ReflectY((double)horAxisY).Round();
         return new ClearPathOperation(copy, newRect);
         return new ClearPathOperation(copy, newRect);
     }
     }
 }
 }

+ 3 - 3
src/ChunkyImageLib/Operations/ClearRegionOperation.cs

@@ -31,13 +31,13 @@ internal class ClearRegionOperation : IMirroredDrawOperation
     }
     }
     public void Dispose() { }
     public void Dispose() { }
 
 
-    public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
+    public IDrawOperation AsMirrored(double? verAxisX, double? horAxisY)
     {
     {
         var newRect = rect;
         var newRect = rect;
         if (verAxisX is not null)
         if (verAxisX is not null)
-            newRect = newRect.ReflectX((int)verAxisX);
+            newRect = (RectI)newRect.ReflectX((double)verAxisX).Round();
         if (horAxisY is not null)
         if (horAxisY is not null)
-            newRect = newRect.ReflectY((int)horAxisY);
+            newRect = (RectI)newRect.ReflectY((double)horAxisY).Round();
         return new ClearRegionOperation(newRect);
         return new ClearRegionOperation(newRect);
     }
     }
 }
 }

+ 5 - 5
src/ChunkyImageLib/Operations/DrawingSurfaceLineOperation.cs

@@ -44,19 +44,19 @@ internal class DrawingSurfaceLineOperation : IMirroredDrawOperation
         return new AffectedArea(OperationHelper.FindChunksTouchingRectangle(bounds, ChunkyImage.FullChunkSize), bounds);
         return new AffectedArea(OperationHelper.FindChunksTouchingRectangle(bounds, ChunkyImage.FullChunkSize), bounds);
     }
     }
 
 
-    public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
+    public IDrawOperation AsMirrored(double? verAxisX, double? horAxisY)
     {
     {
         VecI newFrom = from;
         VecI newFrom = from;
         VecI newTo = to;
         VecI newTo = to;
         if (verAxisX is not null)
         if (verAxisX is not null)
         {
         {
-            newFrom = newFrom.ReflectX((int)verAxisX);
-            newTo = newTo.ReflectX((int)verAxisX);
+            newFrom = (VecI)newFrom.ReflectX((double)verAxisX).Round();
+            newTo = (VecI)newTo.ReflectX((double)verAxisX).Round();
         }
         }
         if (horAxisY is not null)
         if (horAxisY is not null)
         {
         {
-            newFrom = newFrom.ReflectY((int)horAxisY);
-            newTo = newTo.ReflectY((int)horAxisY);
+            newFrom = (VecI)newFrom.ReflectY((double)horAxisY).Round();
+            newTo = (VecI)newTo.ReflectY((double)horAxisY).Round();
         }
         }
         return new DrawingSurfaceLineOperation(newFrom, newTo, paint.StrokeCap, paint.StrokeWidth, paint.Color, paint.BlendMode);
         return new DrawingSurfaceLineOperation(newFrom, newTo, paint.StrokeCap, paint.StrokeWidth, paint.Color, paint.BlendMode);
     }
     }

+ 3 - 3
src/ChunkyImageLib/Operations/EllipseOperation.cs

@@ -105,13 +105,13 @@ internal class EllipseOperation : IMirroredDrawOperation
         return new AffectedArea(chunks, location);
         return new AffectedArea(chunks, location);
     }
     }
 
 
-    public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
+    public IDrawOperation AsMirrored(double? verAxisX, double? horAxisY)
     {
     {
         RectI newLocation = location;
         RectI newLocation = location;
         if (verAxisX is not null)
         if (verAxisX is not null)
-            newLocation = newLocation.ReflectX((int)verAxisX);
+            newLocation = (RectI)newLocation.ReflectX((double)verAxisX).Round();
         if (horAxisY is not null)
         if (horAxisY is not null)
-            newLocation = newLocation.ReflectY((int)horAxisY);
+            newLocation = (RectI)newLocation.ReflectY((double)horAxisY).Round();
         return new EllipseOperation(newLocation, strokeColor, fillColor, strokeWidth, paint);
         return new EllipseOperation(newLocation, strokeColor, fillColor, strokeWidth, paint);
     }
     }
 
 

+ 1 - 1
src/ChunkyImageLib/Operations/IMirroredDrawOperation.cs

@@ -2,5 +2,5 @@
 
 
 internal interface IMirroredDrawOperation : IDrawOperation
 internal interface IMirroredDrawOperation : IDrawOperation
 {
 {
-    IDrawOperation AsMirrored(int? verAxisX, int? horAxisY);
+    IDrawOperation AsMirrored(double? verAxisX, double? horAxisY);
 }
 }

+ 4 - 4
src/ChunkyImageLib/Operations/ImageOperation.cs

@@ -106,22 +106,22 @@ internal class ImageOperation : IMirroredDrawOperation
         customPaint?.Dispose();
         customPaint?.Dispose();
     }
     }
 
 
-    public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
+    public IDrawOperation AsMirrored(double? verAxisX, double? horAxisY)
     {
     {
         if (verAxisX is not null && horAxisY is not null)
         if (verAxisX is not null && horAxisY is not null)
         {
         {
             return new ImageOperation
             return new ImageOperation
-                (corners.AsMirroredAcrossVerAxis((int)verAxisX).AsMirroredAcrossHorAxis((int)horAxisY), toPaint, customPaint, imageWasCopied);
+                (corners.AsMirroredAcrossVerAxis((double)verAxisX).AsMirroredAcrossHorAxis((double)horAxisY), toPaint, customPaint, imageWasCopied);
         }
         }
         if (verAxisX is not null)
         if (verAxisX is not null)
         {
         {
             return new ImageOperation
             return new ImageOperation
-                (corners.AsMirroredAcrossVerAxis((int)verAxisX), toPaint, customPaint, imageWasCopied);
+                (corners.AsMirroredAcrossVerAxis((double)verAxisX), toPaint, customPaint, imageWasCopied);
         }
         }
         if (horAxisY is not null)
         if (horAxisY is not null)
         {
         {
             return new ImageOperation
             return new ImageOperation
-                (corners.AsMirroredAcrossHorAxis((int)horAxisY), toPaint, customPaint, imageWasCopied);
+                (corners.AsMirroredAcrossHorAxis((double)horAxisY), toPaint, customPaint, imageWasCopied);
         }
         }
         return new ImageOperation(corners, toPaint, customPaint, imageWasCopied);
         return new ImageOperation(corners, toPaint, customPaint, imageWasCopied);
     }
     }

+ 4 - 4
src/ChunkyImageLib/Operations/PathOperation.cs

@@ -40,17 +40,17 @@ internal class PathOperation : IMirroredDrawOperation
         return new AffectedArea(OperationHelper.FindChunksTouchingRectangle(bounds, ChunkyImage.FullChunkSize), bounds);
         return new AffectedArea(OperationHelper.FindChunksTouchingRectangle(bounds, ChunkyImage.FullChunkSize), bounds);
     }
     }
 
 
-    public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
+    public IDrawOperation AsMirrored(double? verAxisX, double? horAxisY)
     {
     {
-        var matrix = Matrix3X3.CreateScale(verAxisX is not null ? -1 : 1, horAxisY is not null ? -1 : 1, verAxisX ?? 0, horAxisY ?? 0);
+        var matrix = Matrix3X3.CreateScale(verAxisX is not null ? -1 : 1, horAxisY is not null ? -1 : 1, (float?)verAxisX ?? 0, (float?)horAxisY ?? 0);
         using var copy = new VectorPath(path);
         using var copy = new VectorPath(path);
         copy.Transform(matrix);
         copy.Transform(matrix);
 
 
         RectI newBounds = bounds;
         RectI newBounds = bounds;
         if (verAxisX is not null)
         if (verAxisX is not null)
-            newBounds = newBounds.ReflectX((int)verAxisX);
+            newBounds = (RectI)newBounds.ReflectX((double)verAxisX).Round();
         if (horAxisY is not null)
         if (horAxisY is not null)
-            newBounds = newBounds.ReflectY((int)horAxisY);
+            newBounds = (RectI)newBounds.ReflectY((double)horAxisY).Round();
         return new PathOperation(copy, paint.Color, paint.StrokeWidth, paint.StrokeCap, paint.BlendMode, newBounds);
         return new PathOperation(copy, paint.Color, paint.StrokeWidth, paint.StrokeCap, paint.BlendMode, newBounds);
     }
     }
 
 

+ 3 - 3
src/ChunkyImageLib/Operations/PixelOperation.cs

@@ -40,13 +40,13 @@ internal class PixelOperation : IMirroredDrawOperation
         return new AffectedArea(new HashSet<VecI>() { OperationHelper.GetChunkPos(pixel, ChunkyImage.FullChunkSize) }, new RectI(pixel, VecI.One));
         return new AffectedArea(new HashSet<VecI>() { OperationHelper.GetChunkPos(pixel, ChunkyImage.FullChunkSize) }, new RectI(pixel, VecI.One));
     }
     }
 
 
-    public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
+    public IDrawOperation AsMirrored(double? verAxisX, double? horAxisY)
     {
     {
         RectI pixelRect = new RectI(pixel, new VecI(1, 1));
         RectI pixelRect = new RectI(pixel, new VecI(1, 1));
         if (verAxisX is not null)
         if (verAxisX is not null)
-            pixelRect = pixelRect.ReflectX((int)verAxisX);
+            pixelRect = (RectI)pixelRect.ReflectX((double)verAxisX).Round();
         if (horAxisY is not null)
         if (horAxisY is not null)
-            pixelRect = pixelRect.ReflectY((int)horAxisY);
+            pixelRect = (RectI)pixelRect.ReflectY((double)horAxisY).Round();
         return new PixelOperation(pixelRect.Pos, color, blendMode);
         return new PixelOperation(pixelRect.Pos, color, blendMode);
     }
     }
 
 

+ 4 - 3
src/ChunkyImageLib/Operations/PixelsOperation.cs

@@ -1,4 +1,5 @@
 using System.ComponentModel.DataAnnotations.Schema;
 using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Numerics;
@@ -52,11 +53,11 @@ internal class PixelsOperation : IMirroredDrawOperation
         return new AffectedArea(affectedChunks, affectedArea);
         return new AffectedArea(affectedChunks, affectedArea);
     }
     }
 
 
-    public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
+    public IDrawOperation AsMirrored(double? verAxisX, double? horAxisY)
     {
     {
         var arr = pixels.Select(pixel => new VecI(
         var arr = pixels.Select(pixel => new VecI(
-            verAxisX is not null ? 2 * (int)verAxisX - (int)pixel.X - 1 : (int)pixel.X,
-            horAxisY is not null ? 2 * (int)horAxisY - (int)pixel.Y - 1 : (int)pixel.Y
+            verAxisX is not null ? (int)Math.Round(2 * (double)verAxisX - (int)pixel.X - 1) : (int)pixel.X,
+            horAxisY is not null ? (int)Math.Round(2 * (double)horAxisY - (int)pixel.Y - 1) : (int)pixel.Y
         ));
         ));
         return new PixelsOperation(arr, color, blendMode);
         return new PixelsOperation(arr, color, blendMode);
     }
     }

+ 4 - 4
src/ChunkyImageLib/Operations/RectangleOperation.cs

@@ -72,14 +72,14 @@ internal class RectangleOperation : IMirroredDrawOperation
 
 
     public void Dispose() { }
     public void Dispose() { }
 
 
-    public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
+    public IDrawOperation AsMirrored(double? verAxisX, double? horAxisY)
     {
     {
         if (verAxisX is not null && horAxisY is not null)
         if (verAxisX is not null && horAxisY is not null)
-            return new RectangleOperation(Data.AsMirroredAcrossHorAxis((int)horAxisY).AsMirroredAcrossVerAxis((int)verAxisX));
+            return new RectangleOperation(Data.AsMirroredAcrossHorAxis((double)horAxisY).AsMirroredAcrossVerAxis((double)verAxisX));
         else if (verAxisX is not null)
         else if (verAxisX is not null)
-            return new RectangleOperation(Data.AsMirroredAcrossVerAxis((int)verAxisX));
+            return new RectangleOperation(Data.AsMirroredAcrossVerAxis((double)verAxisX));
         else if (horAxisY is not null)
         else if (horAxisY is not null)
-            return new RectangleOperation(Data.AsMirroredAcrossHorAxis((int)horAxisY));
+            return new RectangleOperation(Data.AsMirroredAcrossHorAxis((double)horAxisY));
         return new RectangleOperation(Data);
         return new RectangleOperation(Data);
     }
     }
 }
 }

+ 1 - 1
src/PixiEditor.ChangeableDocument/ChangeInfos/Root/Size_ChangeInfo.cs

@@ -2,4 +2,4 @@
 
 
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Root;
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Root;
 
 
-public record class Size_ChangeInfo(VecI Size, int VerticalSymmetryAxisX, int HorizontalSymmetryAxisY) : IChangeInfo;
+public record class Size_ChangeInfo(VecI Size, double VerticalSymmetryAxisX, double HorizontalSymmetryAxisY) : IChangeInfo;

+ 1 - 1
src/PixiEditor.ChangeableDocument/ChangeInfos/Root/SymmetryAxisPosition_ChangeInfo.cs

@@ -1,4 +1,4 @@
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.ChangeableDocument.Enums;
 
 
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Root;
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Root;
-public record class SymmetryAxisPosition_ChangeInfo(SymmetryAxisDirection Direction, int NewPosition) : IChangeInfo;
+public record class SymmetryAxisPosition_ChangeInfo(SymmetryAxisDirection Direction, double NewPosition) : IChangeInfo;

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -26,8 +26,8 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     public VecI Size { get; set; } = DefaultSize;
     public VecI Size { get; set; } = DefaultSize;
     public bool HorizontalSymmetryAxisEnabled { get; set; }
     public bool HorizontalSymmetryAxisEnabled { get; set; }
     public bool VerticalSymmetryAxisEnabled { get; set; }
     public bool VerticalSymmetryAxisEnabled { get; set; }
-    public int HorizontalSymmetryAxisY { get; set; }
-    public int VerticalSymmetryAxisX { get; set; }
+    public double HorizontalSymmetryAxisY { get; set; }
+    public double VerticalSymmetryAxisX { get; set; }
 
 
     public void Dispose()
     public void Dispose()
     {
     {

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs

@@ -33,12 +33,12 @@ public interface IReadOnlyDocument
     /// <summary>
     /// <summary>
     /// The position of the horizontal symmetry axis (Mirrors top and bottom)
     /// The position of the horizontal symmetry axis (Mirrors top and bottom)
     /// </summary>
     /// </summary>
-    int HorizontalSymmetryAxisY { get; }
+    double HorizontalSymmetryAxisY { get; }
 
 
     /// <summary>
     /// <summary>
     /// The position of the vertical symmetry axis (Mirrors left and right)
     /// The position of the vertical symmetry axis (Mirrors left and right)
     /// </summary>
     /// </summary>
-    int VerticalSymmetryAxisX { get; }
+    double VerticalSymmetryAxisX { get; }
 
 
     /// <summary>
     /// <summary>
     /// Performs the specified action on each readonly member of the document
     /// Performs the specified action on each readonly member of the document

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeBasedChangeBase.cs

@@ -6,8 +6,8 @@ namespace PixiEditor.ChangeableDocument.Changes.Root;
 internal abstract class ResizeBasedChangeBase : Change
 internal abstract class ResizeBasedChangeBase : Change
 {
 {
     protected VecI _originalSize;
     protected VecI _originalSize;
-    protected int _originalHorAxisY;
-    protected int _originalVerAxisX;
+    protected double _originalHorAxisY;
+    protected double _originalVerAxisX;
     protected Dictionary<Guid, CommittedChunkStorage> deletedChunks = new();
     protected Dictionary<Guid, CommittedChunkStorage> deletedChunks = new();
     protected Dictionary<Guid, CommittedChunkStorage> deletedMaskChunks = new();
     protected Dictionary<Guid, CommittedChunkStorage> deletedMaskChunks = new();
     
     

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeImage_Change.cs

@@ -12,8 +12,8 @@ internal class ResizeImage_Change : Change
     private readonly VecI newSize;
     private readonly VecI newSize;
     private readonly ResamplingMethod method;
     private readonly ResamplingMethod method;
     private VecI originalSize;
     private VecI originalSize;
-    private int originalHorAxisY;
-    private int originalVerAxisX;
+    private double originalHorAxisY;
+    private double originalVerAxisX;
     
     
     private Dictionary<Guid, CommittedChunkStorage> savedChunks = new();
     private Dictionary<Guid, CommittedChunkStorage> savedChunks = new();
     private Dictionary<Guid, CommittedChunkStorage> savedMaskChunks = new();
     private Dictionary<Guid, CommittedChunkStorage> savedMaskChunks = new();

+ 6 - 6
src/PixiEditor.ChangeableDocument/Changes/Root/RotateImage_Change.cs

@@ -14,8 +14,8 @@ internal sealed class RotateImage_Change : Change
     private List<Guid> membersToRotate;
     private List<Guid> membersToRotate;
     
     
     private VecI originalSize;
     private VecI originalSize;
-    private int originalHorAxisY;
-    private int originalVerAxisX;
+    private double originalHorAxisY;
+    private double originalVerAxisX;
     private Dictionary<Guid, CommittedChunkStorage> deletedChunks = new();
     private Dictionary<Guid, CommittedChunkStorage> deletedChunks = new();
     private Dictionary<Guid, CommittedChunkStorage> deletedMaskChunks = new();
     private Dictionary<Guid, CommittedChunkStorage> deletedMaskChunks = new();
 
 
@@ -161,12 +161,12 @@ internal sealed class RotateImage_Change : Change
 
 
         VecI newSize = new VecI(newWidth, newHeight);
         VecI newSize = new VecI(newWidth, newHeight);
 
 
-        float normalizedSymmX = originalVerAxisX / Math.Max(target.Size.X, 0.1f);
-        float normalizedSymmY = originalHorAxisY / Math.Max(target.Size.Y, 0.1f);
+        double normalizedSymmX = originalVerAxisX / Math.Max(target.Size.X, 0.1f);
+        double normalizedSymmY = originalHorAxisY / Math.Max(target.Size.Y, 0.1f);
 
 
         target.Size = newSize;
         target.Size = newSize;
-        target.VerticalSymmetryAxisX = (int)(newSize.X * normalizedSymmX);
-        target.HorizontalSymmetryAxisY = (int)(newSize.Y * normalizedSymmY);
+        target.VerticalSymmetryAxisX = Math.Round(newSize.X * normalizedSymmX * 2) / 2;
+        target.HorizontalSymmetryAxisY = Math.Round(newSize.Y * normalizedSymmY * 2) / 2;
 
 
         target.ForEveryMember((member) =>
         target.ForEveryMember((member) =>
         {
         {

+ 5 - 5
src/PixiEditor.ChangeableDocument/Changes/Root/SymmetryAxisPosition_UpdateableChange.cs

@@ -5,18 +5,18 @@ namespace PixiEditor.ChangeableDocument.Changes.Root;
 internal class SymmetryAxisPosition_UpdateableChange : UpdateableChange
 internal class SymmetryAxisPosition_UpdateableChange : UpdateableChange
 {
 {
     private readonly SymmetryAxisDirection direction;
     private readonly SymmetryAxisDirection direction;
-    private int newPos;
-    private int originalPos;
+    private double newPos;
+    private double originalPos;
 
 
     [GenerateUpdateableChangeActions]
     [GenerateUpdateableChangeActions]
-    public SymmetryAxisPosition_UpdateableChange(SymmetryAxisDirection direction, int pos)
+    public SymmetryAxisPosition_UpdateableChange(SymmetryAxisDirection direction, double pos)
     {
     {
         this.direction = direction;
         this.direction = direction;
         newPos = pos;
         newPos = pos;
     }
     }
 
 
     [UpdateChangeMethod]
     [UpdateChangeMethod]
-    public void Update(int pos)
+    public void Update(double pos)
     {
     {
         newPos = pos;
         newPos = pos;
     }
     }
@@ -32,7 +32,7 @@ internal class SymmetryAxisPosition_UpdateableChange : UpdateableChange
         return true;
         return true;
     }
     }
 
 
-    private void SetPosition(Document target, int position)
+    private void SetPosition(Document target, double position)
     {
     {
         if (direction == SymmetryAxisDirection.Horizontal)
         if (direction == SymmetryAxisDirection.Horizontal)
             target.HorizontalSymmetryAxisY = position;
             target.HorizontalSymmetryAxisY = position;

+ 11 - 0
src/PixiEditor.DrawingApi.Core/Numerics/RectD.cs

@@ -260,6 +260,17 @@ public struct RectD : IEquatable<RectD>
         }
         }
     }
     }
 
 
+    public readonly RectD Round()
+    {
+        return new RectD()
+        {
+            Left = Math.Round(left),
+            Right = Math.Round(right),
+            Top = Math.Round(top),
+            Bottom = Math.Round(bottom)
+        };
+    }
+
     public readonly RectD RoundOutwards()
     public readonly RectD RoundOutwards()
     {
     {
         return new RectD()
         return new RectD()

+ 10 - 0
src/PixiEditor.DrawingApi.Core/Numerics/RectI.cs

@@ -149,6 +149,16 @@ public struct RectI : IEquatable<RectI>
         };
         };
     }
     }
 
 
+    public readonly RectD ReflectX(double verLineX)
+    {
+        return RectD.FromTwoPoints(Pos.ReflectX(verLineX), (Pos + Size).ReflectX(verLineX));
+    }
+
+    public readonly RectD ReflectY(double horLineY)
+    {
+        return RectD.FromTwoPoints(Pos.ReflectY(horLineY), (Pos + Size).ReflectY(horLineY));
+    }
+
     public readonly RectI ReflectX(int verLineX)
     public readonly RectI ReflectX(int verLineX)
     {
     {
         return RectI.FromTwoPoints(Pos.ReflectX(verLineX), (Pos + Size).ReflectX(verLineX));
         return RectI.FromTwoPoints(Pos.ReflectX(verLineX), (Pos + Size).ReflectX(verLineX));

+ 14 - 0
src/PixiEditor.DrawingApi.Core/Numerics/VecI.cs

@@ -53,6 +53,20 @@ public struct VecI : IEquatable<VecI>
     {
     {
         return new(X, 2 * lineY - Y);
         return new(X, 2 * lineY - Y);
     }
     }
+    /// <summary>
+    /// Reflects the vector across a vertical line with the specified x position
+    /// </summary>
+    public VecD ReflectX(double lineX)
+    {
+        return new(2 * lineX - X, Y);
+    }
+    /// <summary>
+    /// Reflects the vector across a horizontal line with the specified y position
+    /// </summary>
+    public VecD ReflectY(double lineY)
+    {
+        return new(X, 2 * lineY - Y);
+    }
     public static VecI operator +(VecI a, VecI b)
     public static VecI operator +(VecI a, VecI b)
     {
     {
         return new VecI(a.X + b.X, a.Y + b.Y);
         return new VecI(a.X + b.X, a.Y + b.Y);

+ 0 - 13
src/PixiEditor/Models/DocumentModels/ChangeExecutionController.cs

@@ -15,9 +15,6 @@ internal class ChangeExecutionController
     public ShapeCorners LastTransformState { get; private set; }
     public ShapeCorners LastTransformState { get; private set; }
     public VecI LastPixelPosition => lastPixelPos;
     public VecI LastPixelPosition => lastPixelPos;
     public VecD LastPrecisePosition => lastPrecisePos;
     public VecD LastPrecisePosition => lastPrecisePos;
-    public float LastOpacityValue = 1f;
-    public int LastHorizontalSymmetryAxisPosition { get; private set; }
-    public int LastVerticalSymmetryAxisPosition { get; private set; }
     public bool IsChangeActive => currentSession is not null;
     public bool IsChangeActive => currentSession is not null;
 
 
     private readonly DocumentViewModel document;
     private readonly DocumentViewModel document;
@@ -148,7 +145,6 @@ internal class ChangeExecutionController
     public void OpacitySliderDragStartedInlet() => currentSession?.OnOpacitySliderDragStarted();
     public void OpacitySliderDragStartedInlet() => currentSession?.OnOpacitySliderDragStarted();
     public void OpacitySliderDraggedInlet(float newValue)
     public void OpacitySliderDraggedInlet(float newValue)
     {
     {
-        LastOpacityValue = newValue;
         currentSession?.OnOpacitySliderDragged(newValue);
         currentSession?.OnOpacitySliderDragged(newValue);
     }
     }
     public void OpacitySliderDragEndedInlet() => currentSession?.OnOpacitySliderDragEnded();
     public void OpacitySliderDragEndedInlet() => currentSession?.OnOpacitySliderDragEnded();
@@ -156,15 +152,6 @@ internal class ChangeExecutionController
     public void SymmetryDragStartedInlet(SymmetryAxisDirection dir) => currentSession?.OnSymmetryDragStarted(dir);
     public void SymmetryDragStartedInlet(SymmetryAxisDirection dir) => currentSession?.OnSymmetryDragStarted(dir);
     public void SymmetryDraggedInlet(SymmetryAxisDragInfo info)
     public void SymmetryDraggedInlet(SymmetryAxisDragInfo info)
     {
     {
-        switch (info.Direction)
-        {
-            case SymmetryAxisDirection.Horizontal:
-                LastHorizontalSymmetryAxisPosition = info.NewPosition;
-                break;
-            case SymmetryAxisDirection.Vertical:
-                LastVerticalSymmetryAxisPosition = info.NewPosition;
-                break;
-        }
         currentSession?.OnSymmetryDragged(info);
         currentSession?.OnSymmetryDragged(info);
     }
     }
 
 

+ 3 - 3
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/SymmetryExecutor.cs

@@ -18,10 +18,10 @@ internal class SymmetryExecutor : UpdateableChangeExecutor
             !document.VerticalSymmetryAxisEnabledBindable && dir == SymmetryAxisDirection.Vertical)
             !document.VerticalSymmetryAxisEnabledBindable && dir == SymmetryAxisDirection.Vertical)
             return ExecutionState.Error;
             return ExecutionState.Error;
 
 
-        int lastPos = dir switch
+        double lastPos = dir switch
         {
         {
-            SymmetryAxisDirection.Horizontal => controller.LastHorizontalSymmetryAxisPosition,
-            SymmetryAxisDirection.Vertical => controller.LastVerticalSymmetryAxisPosition,
+            SymmetryAxisDirection.Horizontal => document.HorizontalSymmetryAxisYBindable,
+            SymmetryAxisDirection.Vertical => document.VerticalSymmetryAxisXBindable,
             _ => throw new NotImplementedException(),
             _ => throw new NotImplementedException(),
         };
         };
         internals.ActionAccumulator.AddActions(new SymmetryAxisPosition_Action(dir, lastPos));
         internals.ActionAccumulator.AddActions(new SymmetryAxisPosition_Action(dir, lastPos));

+ 8 - 8
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.cs

@@ -102,11 +102,11 @@ internal partial class DocumentViewModel : NotifyableObject
     public int Height => size.Y;
     public int Height => size.Y;
     public VecI SizeBindable => size;
     public VecI SizeBindable => size;
 
 
-    private int horizontalSymmetryAxisY;
-    public int HorizontalSymmetryAxisYBindable => horizontalSymmetryAxisY;
+    private double horizontalSymmetryAxisY;
+    public double HorizontalSymmetryAxisYBindable => horizontalSymmetryAxisY;
 
 
-    private int verticalSymmetryAxisX;
-    public int VerticalSymmetryAxisXBindable => verticalSymmetryAxisX;
+    private double verticalSymmetryAxisX;
+    public double VerticalSymmetryAxisXBindable => verticalSymmetryAxisX;
 
 
     private readonly HashSet<StructureMemberViewModel> softSelectedStructureMembers = new();
     private readonly HashSet<StructureMemberViewModel> softSelectedStructureMembers = new();
     public IReadOnlyCollection<StructureMemberViewModel> SoftSelectedStructureMembers => softSelectedStructureMembers;
     public IReadOnlyCollection<StructureMemberViewModel> SoftSelectedStructureMembers => softSelectedStructureMembers;
@@ -197,9 +197,9 @@ internal partial class DocumentViewModel : NotifyableObject
         viewModel.Internals.ChangeController.SymmetryDraggedInlet(new SymmetryAxisDragInfo(SymmetryAxisDirection.Vertical, builderInstance.Width / 2));
         viewModel.Internals.ChangeController.SymmetryDraggedInlet(new SymmetryAxisDragInfo(SymmetryAxisDirection.Vertical, builderInstance.Width / 2));
 
 
         acc.AddActions(
         acc.AddActions(
-            new SymmetryAxisPosition_Action(SymmetryAxisDirection.Horizontal, builderInstance.Height / 2),
+            new SymmetryAxisPosition_Action(SymmetryAxisDirection.Horizontal, (double)builderInstance.Height / 2),
             new EndSymmetryAxisPosition_Action(),
             new EndSymmetryAxisPosition_Action(),
-            new SymmetryAxisPosition_Action(SymmetryAxisDirection.Vertical, builderInstance.Width / 2),
+            new SymmetryAxisPosition_Action(SymmetryAxisDirection.Vertical, (double)builderInstance.Width / 2),
             new EndSymmetryAxisPosition_Action());
             new EndSymmetryAxisPosition_Action());
 
 
         if (builderInstance.ReferenceLayer is { } refLayer)
         if (builderInstance.ReferenceLayer is { } refLayer)
@@ -476,13 +476,13 @@ internal partial class DocumentViewModel : NotifyableObject
         RaisePropertyChanged(nameof(HorizontalSymmetryAxisEnabledBindable));
         RaisePropertyChanged(nameof(HorizontalSymmetryAxisEnabledBindable));
     }
     }
 
 
-    public void InternalSetVerticalSymmetryAxisX(int verticalSymmetryAxisX)
+    public void InternalSetVerticalSymmetryAxisX(double verticalSymmetryAxisX)
     {
     {
         this.verticalSymmetryAxisX = verticalSymmetryAxisX;
         this.verticalSymmetryAxisX = verticalSymmetryAxisX;
         RaisePropertyChanged(nameof(VerticalSymmetryAxisXBindable));
         RaisePropertyChanged(nameof(VerticalSymmetryAxisXBindable));
     }
     }
 
 
-    public void InternalSetHorizontalSymmetryAxisY(int horizontalSymmetryAxisY)
+    public void InternalSetHorizontalSymmetryAxisY(double horizontalSymmetryAxisY)
     {
     {
         this.horizontalSymmetryAxisY = horizontalSymmetryAxisY;
         this.horizontalSymmetryAxisY = horizontalSymmetryAxisY;
         RaisePropertyChanged(nameof(HorizontalSymmetryAxisYBindable));
         RaisePropertyChanged(nameof(HorizontalSymmetryAxisYBindable));

+ 1 - 1
src/PixiEditor/Views/UserControls/Overlays/SymmetryOverlay/SymmetryAxisDragInfo.cs

@@ -2,4 +2,4 @@
 
 
 namespace PixiEditor.Views.UserControls.SymmetryOverlay;
 namespace PixiEditor.Views.UserControls.SymmetryOverlay;
 #nullable enable
 #nullable enable
-internal record class SymmetryAxisDragInfo(SymmetryAxisDirection Direction, int NewPosition);
+internal record class SymmetryAxisDragInfo(SymmetryAxisDirection Direction, double NewPosition);

+ 18 - 17
src/PixiEditor/Views/UserControls/Overlays/SymmetryOverlay/SymmetryOverlay.cs

@@ -18,22 +18,22 @@ namespace PixiEditor.Views.UserControls.Overlays.SymmetryOverlay;
 internal class SymmetryOverlay : Control
 internal class SymmetryOverlay : Control
 {
 {
     public static readonly DependencyProperty HorizontalAxisYProperty =
     public static readonly DependencyProperty HorizontalAxisYProperty =
-        DependencyProperty.Register(nameof(HorizontalAxisY), typeof(int), typeof(SymmetryOverlay),
-            new(0, OnPositionUpdate));
+        DependencyProperty.Register(nameof(HorizontalAxisY), typeof(double), typeof(SymmetryOverlay),
+            new(0.0, OnPositionUpdate));
 
 
-    public int HorizontalAxisY
+    public double HorizontalAxisY
     {
     {
-        get => (int)GetValue(HorizontalAxisYProperty);
+        get => (double)GetValue(HorizontalAxisYProperty);
         set => SetValue(HorizontalAxisYProperty, value);
         set => SetValue(HorizontalAxisYProperty, value);
     }
     }
 
 
     public static readonly DependencyProperty VerticalAxisXProperty =
     public static readonly DependencyProperty VerticalAxisXProperty =
-        DependencyProperty.Register(nameof(VerticalAxisX), typeof(int), typeof(SymmetryOverlay),
-            new(0, OnPositionUpdate));
+        DependencyProperty.Register(nameof(VerticalAxisX), typeof(double), typeof(SymmetryOverlay),
+            new(0.0, OnPositionUpdate));
 
 
-    public int VerticalAxisX
+    public double VerticalAxisX
     {
     {
-        get => (int)GetValue(VerticalAxisXProperty);
+        get => (double)GetValue(VerticalAxisXProperty);
         set => SetValue(VerticalAxisXProperty, value);
         set => SetValue(VerticalAxisXProperty, value);
     }
     }
 
 
@@ -114,8 +114,8 @@ internal class SymmetryOverlay : Control
 
 
     private double PenThickness => 1.0 / ZoomboxScale;
     private double PenThickness => 1.0 / ZoomboxScale;
 
 
-    private int horizontalAxisY;
-    private int verticalAxisX;
+    private double horizontalAxisY;
+    private double verticalAxisX;
 
 
     private MouseUpdateController mouseUpdateController;
     private MouseUpdateController mouseUpdateController;
 
 
@@ -334,7 +334,7 @@ internal class SymmetryOverlay : Control
         UpdateHovered(null);
         UpdateHovered(null);
     }
     }
 
 
-    private void CallSymmetryDragCommand(SymmetryAxisDirection direction, int position)
+    private void CallSymmetryDragCommand(SymmetryAxisDirection direction, double position)
     {
     {
         SymmetryAxisDragInfo dragInfo = new(direction, position);
         SymmetryAxisDragInfo dragInfo = new(direction, position);
         if (DragCommand is not null && DragCommand.CanExecute(dragInfo))
         if (DragCommand is not null && DragCommand.CanExecute(dragInfo))
@@ -373,8 +373,6 @@ internal class SymmetryOverlay : Control
 
 
     protected void MouseMoved(object sender, MouseEventArgs e)
     protected void MouseMoved(object sender, MouseEventArgs e)
     {
     {
-        /*base.OnMouseMove(e);*/
-
         var pos = ToVecD(e.GetPosition(this));
         var pos = ToVecD(e.GetPosition(this));
         UpdateHovered(IsTouchingHandle(pos));
         UpdateHovered(IsTouchingHandle(pos));
 
 
@@ -382,22 +380,25 @@ internal class SymmetryOverlay : Control
             return;
             return;
         if (capturedDirection == SymmetryAxisDirection.Horizontal)
         if (capturedDirection == SymmetryAxisDirection.Horizontal)
         {
         {
-            horizontalAxisY = (int)Math.Round(Math.Clamp(pos.Y, 0, ActualHeight));
+            horizontalAxisY = Math.Round(Math.Clamp(pos.Y, 0, ActualHeight) * 2) / 2;
 
 
             if (Keyboard.IsKeyDown(Key.LeftShift))
             if (Keyboard.IsKeyDown(Key.LeftShift))
             {
             {
-                horizontalAxisY = (int)(Math.Round(horizontalAxisY / RenderSize.Height * 8) / 8 * RenderSize.Height);
+                double temp = Math.Round(horizontalAxisY / RenderSize.Height * 8) / 8 * RenderSize.Height;
+                horizontalAxisY = Math.Round(temp * 2) / 2;
             }
             }
 
 
             CallSymmetryDragCommand((SymmetryAxisDirection)capturedDirection, horizontalAxisY);
             CallSymmetryDragCommand((SymmetryAxisDirection)capturedDirection, horizontalAxisY);
         }
         }
         else if (capturedDirection == SymmetryAxisDirection.Vertical)
         else if (capturedDirection == SymmetryAxisDirection.Vertical)
         {
         {
-            verticalAxisX = (int)Math.Round(Math.Clamp(pos.X, 0, ActualWidth));
+            verticalAxisX = Math.Round(Math.Clamp(pos.X, 0, ActualWidth) * 2) / 2;
 
 
             if (Keyboard.IsKeyDown(Key.LeftShift))
             if (Keyboard.IsKeyDown(Key.LeftShift))
             {
             {
-                verticalAxisX = (int)(Math.Round(verticalAxisX / RenderSize.Width * 8) / 8 * RenderSize.Width);
+
+                double temp = Math.Round(verticalAxisX / RenderSize.Width * 8) / 8 * RenderSize.Width;
+                verticalAxisX = Math.Round(temp * 2) / 2;
             }
             }
 
 
             CallSymmetryDragCommand((SymmetryAxisDirection)capturedDirection, verticalAxisX);
             CallSymmetryDragCommand((SymmetryAxisDirection)capturedDirection, verticalAxisX);