Skip to content

Commit

Permalink
Merge branch 'PintaProject:master' into improvement7
Browse files Browse the repository at this point in the history
  • Loading branch information
Lehonti authored Dec 11, 2024
2 parents ef3f66b + e828c6f commit 6d63ed9
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 79 deletions.
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

0 comments on commit 6d63ed9

Please sign in to comment.