Skip to content

Commit

Permalink
Merge pull request #508 from PixiEditor/symmery-half-pixels
Browse files Browse the repository at this point in the history
Symmetry: snap to half-pixels
  • Loading branch information
flabbet committed Apr 10, 2023
2 parents c9a2f8e + a913b5a commit f9aad94
Show file tree
Hide file tree
Showing 31 changed files with 137 additions and 113 deletions.
8 changes: 4 additions & 4 deletions src/ChunkyImageLib/ChunkyImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ public int QueueLength
private BlendMode blendMode = BlendMode.Src;
private bool lockTransparency = false;
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>> latestChunks;
Expand Down Expand Up @@ -429,7 +429,7 @@ public void SetBlendMode(BlendMode mode)
}

/// <exception cref="ObjectDisposedException">This image is disposed</exception>
public void SetHorizontalAxisOfSymmetry(int position)
public void SetHorizontalAxisOfSymmetry(double position)
{
lock (lockObject)
{
Expand All @@ -441,7 +441,7 @@ public void SetHorizontalAxisOfSymmetry(int position)
}

/// <exception cref="ObjectDisposedException">This image is disposed</exception>
public void SetVerticalAxisOfSymmetry(int position)
public void SetVerticalAxisOfSymmetry(double position)
{
lock (lockObject)
{
Expand Down
4 changes: 2 additions & 2 deletions src/ChunkyImageLib/DataHolders/ShapeCorners.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,15 @@ public bool IsPointInside(VecD point)
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),
BottomRight = BottomRight.ReflectY(horAxisY),
TopLeft = TopLeft.ReflectY(horAxisY),
TopRight = TopRight.ReflectY(horAxisY)
};

public ShapeCorners AsMirroredAcrossVerAxis(int verAxisX) => new ShapeCorners
public ShapeCorners AsMirroredAcrossVerAxis(double verAxisX) => new ShapeCorners
{
BottomLeft = BottomLeft.ReflectX(verAxisX),
BottomRight = BottomRight.ReflectX(verAxisX),
Expand Down
4 changes: 2 additions & 2 deletions src/ChunkyImageLib/DataHolders/ShapeData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ public ShapeData(VecD center, VecD size, double rotation, int strokeWidth, Color
public double Angle { 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);
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);

}
10 changes: 5 additions & 5 deletions src/ChunkyImageLib/Operations/BresenhamLineOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,19 @@ public AffectedArea FindAffectedArea(VecI imageSize)
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 newTo = new RectI(to, new VecI(1));
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)
{
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);
}
Expand Down
6 changes: 3 additions & 3 deletions src/ChunkyImageLib/Operations/ChunkyImageOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,13 @@ private VecI GetTopLeft()
return topLeft;
}

public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
public IDrawOperation AsMirrored(double? verAxisX, double? horAxisY)
{
var newPos = targetPos;
if (verAxisX is not null)
newPos = newPos.ReflectX((int)verAxisX);
newPos = (VecI)newPos.ReflectX((double)verAxisX).Round();
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));
}

Expand Down
8 changes: 4 additions & 4 deletions src/ChunkyImageLib/Operations/ClearPathOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,17 @@ public void 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);
copy.Transform(matrix);

var newRect = pathTightBounds;
if (verAxisX is not null)
newRect = newRect.ReflectX((int)verAxisX);
newRect = (RectI)newRect.ReflectX((double)verAxisX).Round();
if (horAxisY is not null)
newRect = newRect.ReflectY((int)horAxisY);
newRect = (RectI)newRect.ReflectY((double)horAxisY).Round();
return new ClearPathOperation(copy, newRect);
}
}
6 changes: 3 additions & 3 deletions src/ChunkyImageLib/Operations/ClearRegionOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ public AffectedArea FindAffectedArea(VecI imageSize)
}
public void Dispose() { }

public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
public IDrawOperation AsMirrored(double? verAxisX, double? horAxisY)
{
var newRect = rect;
if (verAxisX is not null)
newRect = newRect.ReflectX((int)verAxisX);
newRect = (RectI)newRect.ReflectX((double)verAxisX).Round();
if (horAxisY is not null)
newRect = newRect.ReflectY((int)horAxisY);
newRect = (RectI)newRect.ReflectY((double)horAxisY).Round();
return new ClearRegionOperation(newRect);
}
}
10 changes: 5 additions & 5 deletions src/ChunkyImageLib/Operations/DrawingSurfaceLineOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,19 @@ public AffectedArea FindAffectedArea(VecI imageSize)
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 newTo = to;
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)
{
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);
}
Expand Down
6 changes: 3 additions & 3 deletions src/ChunkyImageLib/Operations/EllipseOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,13 @@ public AffectedArea FindAffectedArea(VecI imageSize)
return new AffectedArea(chunks, location);
}

public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
public IDrawOperation AsMirrored(double? verAxisX, double? horAxisY)
{
RectI newLocation = location;
if (verAxisX is not null)
newLocation = newLocation.ReflectX((int)verAxisX);
newLocation = (RectI)newLocation.ReflectX((double)verAxisX).Round();
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);
}

Expand Down
2 changes: 1 addition & 1 deletion src/ChunkyImageLib/Operations/IMirroredDrawOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

internal interface IMirroredDrawOperation : IDrawOperation
{
IDrawOperation AsMirrored(int? verAxisX, int? horAxisY);
IDrawOperation AsMirrored(double? verAxisX, double? horAxisY);
}
8 changes: 4 additions & 4 deletions src/ChunkyImageLib/Operations/ImageOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,22 +106,22 @@ public void 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)
{
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)
{
return new ImageOperation
(corners.AsMirroredAcrossVerAxis((int)verAxisX), toPaint, customPaint, imageWasCopied);
(corners.AsMirroredAcrossVerAxis((double)verAxisX), toPaint, customPaint, imageWasCopied);
}
if (horAxisY is not null)
{
return new ImageOperation
(corners.AsMirroredAcrossHorAxis((int)horAxisY), toPaint, customPaint, imageWasCopied);
(corners.AsMirroredAcrossHorAxis((double)horAxisY), toPaint, customPaint, imageWasCopied);
}
return new ImageOperation(corners, toPaint, customPaint, imageWasCopied);
}
Expand Down
8 changes: 4 additions & 4 deletions src/ChunkyImageLib/Operations/PathOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@ public AffectedArea FindAffectedArea(VecI imageSize)
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);
copy.Transform(matrix);

RectI newBounds = bounds;
if (verAxisX is not null)
newBounds = newBounds.ReflectX((int)verAxisX);
newBounds = (RectI)newBounds.ReflectX((double)verAxisX).Round();
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);
}

Expand Down
6 changes: 3 additions & 3 deletions src/ChunkyImageLib/Operations/PixelOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ public AffectedArea FindAffectedArea(VecI imageSize)
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));
if (verAxisX is not null)
pixelRect = pixelRect.ReflectX((int)verAxisX);
pixelRect = (RectI)pixelRect.ReflectX((double)verAxisX).Round();
if (horAxisY is not null)
pixelRect = pixelRect.ReflectY((int)horAxisY);
pixelRect = (RectI)pixelRect.ReflectY((double)horAxisY).Round();
return new PixelOperation(pixelRect.Pos, color, blendMode);
}

Expand Down
7 changes: 4 additions & 3 deletions src/ChunkyImageLib/Operations/PixelsOperation.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using ChunkyImageLib.DataHolders;
using PixiEditor.DrawingApi.Core.ColorsImpl;
using PixiEditor.DrawingApi.Core.Numerics;
Expand Down Expand Up @@ -52,11 +53,11 @@ public AffectedArea FindAffectedArea(VecI imageSize)
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(
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);
}
Expand Down
8 changes: 4 additions & 4 deletions src/ChunkyImageLib/Operations/RectangleOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,14 @@ public AffectedArea FindAffectedArea(VecI imageSize)

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)
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)
return new RectangleOperation(Data.AsMirroredAcrossVerAxis((int)verAxisX));
return new RectangleOperation(Data.AsMirroredAcrossVerAxis((double)verAxisX));
else if (horAxisY is not null)
return new RectangleOperation(Data.AsMirroredAcrossHorAxis((int)horAxisY));
return new RectangleOperation(Data.AsMirroredAcrossHorAxis((double)horAxisY));
return new RectangleOperation(Data);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

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;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using PixiEditor.ChangeableDocument.Enums;

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;
4 changes: 2 additions & 2 deletions src/PixiEditor.ChangeableDocument/Changeables/Document.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
public VecI Size { get; set; } = DefaultSize;
public bool HorizontalSymmetryAxisEnabled { 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()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ public interface IReadOnlyDocument
/// <summary>
/// The position of the horizontal symmetry axis (Mirrors top and bottom)
/// </summary>
int HorizontalSymmetryAxisY { get; }
double HorizontalSymmetryAxisY { get; }

/// <summary>
/// The position of the vertical symmetry axis (Mirrors left and right)
/// </summary>
int VerticalSymmetryAxisX { get; }
double VerticalSymmetryAxisX { get; }

/// <summary>
/// Performs the specified action on each readonly member of the document
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ namespace PixiEditor.ChangeableDocument.Changes.Root;
internal abstract class ResizeBasedChangeBase : Change
{
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> deletedMaskChunks = new();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ internal class ResizeImage_Change : Change
private readonly VecI newSize;
private readonly ResamplingMethod method;
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> savedMaskChunks = new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ internal sealed class RotateImage_Change : Change
private List<Guid> membersToRotate;

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> deletedMaskChunks = new();

Expand Down Expand Up @@ -161,12 +161,12 @@ private OneOf<None, IChangeInfo, List<IChangeInfo>> RotateWholeImage(Document ta

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.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) =>
{
Expand Down
Loading

0 comments on commit f9aad94

Please sign in to comment.