Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Created Fraction type #1169

Merged
merged 8 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Pinta.Core/Algorithms/Mathematics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,17 @@ public static TNumber InvLerp<TNumber> (
TNumber offset = value - from;
return offset / valueSpan;
}

public static Fraction<TInt> Clamp<TInt> (
Fraction<TInt> original,
Fraction<TInt> min,
Fraction<TInt> max
)
where TInt : IBinaryInteger<TInt>
{
if (min > max) throw new ArgumentException ("Minimum should be less than maximum");
if (min > original) return min;
if (max < original) return max;
return original;
}
}
30 changes: 14 additions & 16 deletions Pinta.Core/Classes/DocumentWorkspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,7 @@ public double Scale {
private static Size CoercedToPositive (Size baseSize)
=> new (
Width: Math.Max (baseSize.Width, 1),
Height: Math.Max (baseSize.Height, 1)
);
Height: Math.Max (baseSize.Height, 1));

private static Size GetNewViewSize (Size imageSize, double scale)
{
Expand Down Expand Up @@ -229,19 +228,21 @@ public void ScrollCanvas (PointI delta)
/// </param>
public PointD ViewPointToCanvas (PointD viewPoint)
{
var sf = new ScaleFactor (document.ImageSize.Width, ViewSize.Width);
var pt = sf.ScalePoint (viewPoint - Offset);
return new PointD (pt.X, pt.Y);
Fraction<int> sf = ScaleFactor.CreateClamped (document.ImageSize.Width, ViewSize.Width);
PointD pt = sf.ScalePoint (viewPoint - Offset);
return new (pt.X, pt.Y);
}

/// <summary>
/// Converts a point from canvas coordinates to view coordinates
/// </summary>
public PointD CanvasPointToView (PointD canvasPoint)
{
var sf = new ScaleFactor (document.ImageSize.Width, ViewSize.Width);
var pt = sf.UnscalePoint (canvasPoint);
return new PointD (pt.X + Offset.X, pt.Y + Offset.Y);
Fraction<int> sf = ScaleFactor.CreateClamped (document.ImageSize.Width, ViewSize.Width);
PointD pt = sf.UnscalePoint (canvasPoint);
return new (
X: pt.X + Offset.X,
Y: pt.Y + Offset.Y);
}

public void ZoomIn ()
Expand Down Expand Up @@ -281,20 +282,17 @@ public void ZoomManually ()

public void ZoomToCanvasRectangle (RectangleD rect)
{
double ratio;

if (document.ImageSize.Width / rect.Width <= document.ImageSize.Height / rect.Height)
ratio = document.ImageSize.Width / rect.Width;
else
ratio = document.ImageSize.Height / rect.Height;
double ratio =
(document.ImageSize.Width / rect.Width <= document.ImageSize.Height / rect.Height)
? document.ImageSize.Width / rect.Width
: document.ImageSize.Height / rect.Height;

PintaCore.Actions.View.ZoomComboBox.ComboBox.GetEntry ().SetText (ViewActions.ToPercent (ratio));
GLib.MainContext.Default ().Iteration (false); //Force update of scrollbar upper before recenter

PointD newPoint = new (
X: rect.X + rect.Width / 2,
Y: rect.Y + rect.Height / 2
);
Y: rect.Y + rect.Height / 2);

RecenterView (newPoint);
}
Expand Down
78 changes: 78 additions & 0 deletions Pinta.Core/Classes/Fraction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.Numerics;

namespace Pinta.Core;

/// <summary>Represents a reduced fraction</summary>
/// <remarks>
/// Only positive values are supported for now.
/// At the time of this writing, support for negative
/// numbers is not needed.
/// </remarks>
public readonly struct Fraction<TInt> where TInt : IBinaryInteger<TInt>
{
public TInt Numerator { get; }
public TInt Denominator { get; }
public Fraction (TInt numerator, TInt denominator)
{
if (denominator <= TInt.Zero) throw new ArgumentOutOfRangeException (nameof (denominator), "must be greater than 0(denominator = " + denominator + ")");
if (numerator < TInt.Zero) throw new ArgumentOutOfRangeException (nameof (numerator), "must be greater than 0(numerator = " + numerator + ")");
if (numerator == TInt.Zero) {
Numerator = TInt.Zero;
Denominator = TInt.One;
} else {
TInt gcd = Mathematics.EuclidGCD (numerator, denominator);
TInt reducedNumerator = numerator / gcd;
TInt reducedDenominator = denominator / gcd;
Numerator = reducedNumerator;
Denominator = reducedDenominator;
}
}

public static bool operator == (Fraction<TInt> lhs, Fraction<TInt> rhs)
=> lhs.Equals (rhs);

public static bool operator != (Fraction<TInt> lhs, Fraction<TInt> rhs)
=> !lhs.Equals (rhs);

public static bool operator < (Fraction<TInt> lhs, Fraction<TInt> rhs)
=> (lhs.Numerator * rhs.Denominator) < (rhs.Numerator * lhs.Denominator);

public static bool operator > (Fraction<TInt> lhs, Fraction<TInt> rhs)
=> (lhs.Numerator * rhs.Denominator) > (rhs.Numerator * lhs.Denominator);

public bool Equals (Fraction<TInt> other)
=> Numerator == other.Numerator && Denominator == other.Denominator;

public override bool Equals (object? obj)
{
if (obj is not Fraction<TInt> other) return false;
return Equals (other);
}

public override int GetHashCode ()
=> Numerator.GetHashCode () ^ Denominator.GetHashCode ();
}

public static class FractionExtensions
{
public static bool LessThan<TInt> (
this in Fraction<TInt> lhs,
TInt rhsNumerator,
TInt rhsDenominator
)
where TInt : IBinaryInteger<TInt>
{
return (lhs.Numerator * rhsDenominator) < (lhs.Denominator * rhsNumerator);
}

public static bool GreaterThan<TInt> (
this in Fraction<TInt> lhs,
TInt rhsNumerator,
TInt rhsDenominator
)
where TInt : IBinaryInteger<TInt>
{
return (lhs.Numerator * rhsDenominator) > (lhs.Denominator * rhsNumerator);
}
}
78 changes: 25 additions & 53 deletions Pinta.Core/Classes/ScaleFactor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,75 +7,47 @@
// Ported to Pinta by: Jonathan Pobst <[email protected]> //
/////////////////////////////////////////////////////////////////////////////////

using System;

namespace Pinta.Core;

/// <summary>
/// Encapsulates functionality for zooming/scaling coordinates.
/// Includes methods for Size[F]'s, Point[F]'s, Rectangle[F]'s,
/// and various scalars
/// </summary>
public readonly struct ScaleFactor
public static class ScaleFactor
{
private readonly int denominator;
private readonly int numerator;

public double Ratio { get; }

public static readonly ScaleFactor OneToOne = new (1, 1);
public static readonly ScaleFactor MinValue = new (1, 100);
public static readonly ScaleFactor MaxValue = new (32, 1);
public static Fraction<int> OneToOne { get; } = new (1, 1);
public static Fraction<int> MinValue { get; } = new (1, 100);
public static Fraction<int> MaxValue { get; } = new (32, 1);

public readonly int ScaleScalar (int x) =>
(int) (((long) x * numerator) / denominator);
public static int ScaleScalar (this in Fraction<int> fraction, int x) =>
(int) (((long) x * fraction.Numerator) / fraction.Denominator);

public readonly int UnscaleScalar (int x) =>
(int) (((long) x * denominator) / numerator);
public static int UnscaleScalar (this in Fraction<int> fraction, int x) =>
(int) (((long) x * fraction.Denominator) / fraction.Numerator);

public readonly double ScaleScalar (double x) =>
x * numerator / denominator;
public static double ScaleScalar (this in Fraction<int> fraction, double x) =>
x * fraction.Numerator / fraction.Denominator;

public readonly double UnscaleScalar (double x) =>
x * denominator / numerator;
public static double UnscaleScalar (this in Fraction<int> fraction, double x) =>
x * fraction.Denominator / fraction.Numerator;

public readonly PointD ScalePoint (PointD p) =>
new (ScaleScalar (p.X), ScaleScalar (p.Y));
public static PointD ScalePoint (this in Fraction<int> fraction, PointD p) =>
new (fraction.ScaleScalar (p.X), fraction.ScaleScalar (p.Y));

public readonly PointD UnscalePoint (PointD p) =>
new (UnscaleScalar (p.X), UnscaleScalar (p.Y));
public static PointD UnscalePoint (this in Fraction<int> fraction, PointD p) =>
new (fraction.UnscaleScalar (p.X), fraction.UnscaleScalar (p.Y));

public static bool operator < (ScaleFactor lhs, ScaleFactor rhs) =>
(lhs.numerator * rhs.denominator) < (rhs.numerator * lhs.denominator);
public static double ComputeRatio (this in Fraction<int> fraction)
=> fraction.Numerator / (double) fraction.Denominator;

public static bool operator > (ScaleFactor lhs, ScaleFactor rhs) =>
(lhs.numerator * rhs.denominator) > (rhs.numerator * lhs.denominator);

public ScaleFactor (int numerator, int denominator)
/// <returns>
/// Fraction representing the scale factor,
/// clamped to <see cref="MinValue"/> and <see cref="MaxValue"/>
/// </returns>
public static Fraction<int> CreateClamped (int numerator, int denominator)
{
if (denominator <= 0)
throw new ArgumentOutOfRangeException (nameof (denominator), "must be greater than 0(denominator = " + denominator + ")");

if (numerator <= 0)
throw new ArgumentOutOfRangeException (nameof (numerator), "must be greater than 0(numerator = " + numerator + ")");

// Clamp
if ((numerator * MinValue.denominator) < (MinValue.numerator * denominator)) {
numerator = MinValue.numerator;
denominator = MinValue.denominator;
} else if ((MaxValue.numerator * denominator) > (MaxValue.numerator * denominator)) {
numerator = MaxValue.numerator;
denominator = MaxValue.denominator;
}

int gcd = Mathematics.EuclidGCD (numerator, denominator);

int reducedNumerator = numerator / gcd;
int reducedDenominator = denominator / gcd;

this.numerator = reducedNumerator;
this.denominator = reducedDenominator;

Ratio = reducedNumerator / (double) reducedDenominator;
Fraction<int> baseFraction = new (numerator, denominator);
return Mathematics.Clamp (baseFraction, MinValue, MaxValue);
}
}
23 changes: 13 additions & 10 deletions Pinta.Gui.Widgets/Widgets/Canvas/CanvasRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ public sealed class CanvasRenderer

private Size source_size;
private Size destination_size;
private ScaleFactor scale_factor;
private Fraction<int> scale_factor;
private double scale_ratio;

private ImmutableArray<int>? d_2_s_lookup_x;
private ImmutableArray<int>? d_2_s_lookup_y;
Expand All @@ -49,7 +50,9 @@ public void Initialize (Size sourceSize, Size destinationSize)
source_size = sourceSize;
destination_size = destinationSize;

scale_factor = new ScaleFactor (source_size.Width, destination_size.Width);
Fraction<int> scaleFactor = ScaleFactor.CreateClamped (source_size.Width, destination_size.Width);
scale_factor = scaleFactor;
scale_ratio = scale_factor.ComputeRatio ();

d_2_s_lookup_x = null;
d_2_s_lookup_y = null;
Expand All @@ -66,7 +69,7 @@ public void Render (

// Our rectangle of interest
RectangleD r = new RectangleI (offset, dst.GetBounds ().Size).ToDouble ();
bool is_one_to_one = scale_factor.Ratio == 1;
bool is_one_to_one = scale_ratio == 1;

using Cairo.Context g = new (dst);

Expand All @@ -87,14 +90,14 @@ public void Render (

if (!is_one_to_one) {
// Scale the source surface based on the zoom level.
double inv_scale = 1.0 / scale_factor.Ratio;
double inv_scale = 1.0 / scale_ratio;
g.Scale (inv_scale, inv_scale);
}

g.Transform (layer.Transform);

// Use nearest-neighbor interpolation when zoomed in so that there isn't any smoothing.
ResamplingMode filter = (scale_factor.Ratio <= 1) ? ResamplingMode.NearestNeighbor : ResamplingMode.Bilinear;
ResamplingMode filter = (scale_ratio <= 1) ? ResamplingMode.NearestNeighbor : ResamplingMode.Bilinear;

g.SetSourceSurface (surf, filter);

Expand All @@ -121,7 +124,7 @@ private int GetMinGridLineDistance (ICanvasGridService canvasGrid)
int cellWidth = canvasGrid.CellWidth;

int minCanvasDistance = Math.Min (cellHeight, cellWidth);
double minRenderedDistance = minCanvasDistance / scale_factor.Ratio;
double minRenderedDistance = minCanvasDistance / scale_ratio;

return (int) minRenderedDistance;
}
Expand Down Expand Up @@ -189,7 +192,7 @@ private void RenderPixelGrid (Cairo.ImageSurface dst, PointI offset, ICanvasGrid
private static ImmutableArray<int> CreateLookupX (
int srcWidth,
int dstWidth,
ScaleFactor scaleFactor)
Fraction<int> scaleFactor)
{
int length = dstWidth + 1;
var lookup = ImmutableArray.CreateBuilder<int> (length);
Expand All @@ -207,7 +210,7 @@ private static ImmutableArray<int> CreateLookupX (
private static ImmutableArray<int> CreateLookupY (
int srcHeight,
int dstHeight,
ScaleFactor scaleFactor)
Fraction<int> scaleFactor)
{
int length = dstHeight + 1;
var lookup = ImmutableArray.CreateBuilder<int> (length);
Expand All @@ -225,7 +228,7 @@ private static ImmutableArray<int> CreateLookupY (
private static ImmutableArray<int> CreateS2DLookupX (
int srcWidth,
int dstWidth,
ScaleFactor scaleFactor)
Fraction<int> scaleFactor)
{
int length = srcWidth + 1;
var lookup = ImmutableArray.CreateBuilder<int> (length);
Expand All @@ -243,7 +246,7 @@ private static ImmutableArray<int> CreateS2DLookupX (
private static ImmutableArray<int> CreateS2DLookupY (
int srcHeight,
int dstHeight,
ScaleFactor scaleFactor)
Fraction<int> scaleFactor)
{
int length = srcHeight + 1;
var lookup = ImmutableArray.CreateBuilder<int> (length);
Expand Down
Loading
Loading