mirror of
https://github.com/greenshot/greenshot
synced 2025-08-14 10:47:02 -07:00
Use Fractions to represent zoom factor
The goal is to be able to get as close as possible to perfect 66.(6)% (2/3) zoom factor, and also remove types mismatch between the editor form and the surface.
This commit is contained in:
parent
464e5e872f
commit
dcf75fd081
5 changed files with 183 additions and 29 deletions
|
@ -307,15 +307,16 @@ namespace Greenshot.Drawing
|
|||
[NonSerialized]
|
||||
private Matrix _inverseZoomMatrix = new Matrix(1, 0, 0, 1, 0, 0);
|
||||
[NonSerialized]
|
||||
private float _zoomFactor = 1.0f;
|
||||
public float ZoomFactor
|
||||
private Fraction _zoomFactor = Fraction.Identity;
|
||||
public Fraction ZoomFactor
|
||||
{
|
||||
get => _zoomFactor;
|
||||
set
|
||||
{
|
||||
_zoomFactor = value;
|
||||
var inverse = _zoomFactor.Inverse();
|
||||
_zoomMatrix = new Matrix(_zoomFactor, 0, 0, _zoomFactor, 0, 0);
|
||||
_inverseZoomMatrix = new Matrix(1f / _zoomFactor, 0, 0, 1f / _zoomFactor, 0, 0);
|
||||
_inverseZoomMatrix = new Matrix(inverse, 0, 0, inverse, 0, 0);
|
||||
UpdateSize();
|
||||
}
|
||||
}
|
||||
|
@ -1439,9 +1440,9 @@ namespace Greenshot.Drawing
|
|||
LOG.Debug("Empty cliprectangle??");
|
||||
return;
|
||||
}
|
||||
Rectangle imageClipRectangle = ZoomClipRectangle(targetClipRectangle, 1.0 / _zoomFactor, 2);
|
||||
Rectangle imageClipRectangle = ZoomClipRectangle(targetClipRectangle, _zoomFactor.Inverse(), 2);
|
||||
|
||||
bool isZoomedIn = ZoomFactor > 1f;
|
||||
bool isZoomedIn = _zoomFactor > Fraction.Identity;
|
||||
if (_elements.HasIntersectingFilters(imageClipRectangle) || isZoomedIn)
|
||||
{
|
||||
if (_buffer != null)
|
||||
|
@ -1467,7 +1468,7 @@ namespace Greenshot.Drawing
|
|||
//graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
DrawBackground(graphics, imageClipRectangle);
|
||||
graphics.DrawImage(Image, imageClipRectangle, imageClipRectangle, GraphicsUnit.Pixel);
|
||||
graphics.SetClip(ZoomClipRectangle(Rectangle.Round(targetGraphics.ClipBounds), 1.0 / _zoomFactor, 2));
|
||||
graphics.SetClip(ZoomClipRectangle(Rectangle.Round(targetGraphics.ClipBounds), _zoomFactor.Inverse(), 2));
|
||||
_elements.Draw(graphics, _buffer, RenderMode.EDIT, imageClipRectangle);
|
||||
}
|
||||
targetGraphics.ScaleTransform(_zoomFactor, _zoomFactor);
|
||||
|
|
18
Greenshot/Forms/ImageEditorForm.Designer.cs
generated
18
Greenshot/Forms/ImageEditorForm.Designer.cs
generated
|
@ -1718,7 +1718,7 @@ namespace Greenshot {
|
|||
//
|
||||
this.zoom25MenuItem.Name = "zoom25MenuItem";
|
||||
this.zoom25MenuItem.Size = new System.Drawing.Size(209, 22);
|
||||
this.zoom25MenuItem.Tag = "25";
|
||||
this.zoom25MenuItem.Tag = "1/4";
|
||||
this.zoom25MenuItem.Text = "25%";
|
||||
this.zoom25MenuItem.Click += new System.EventHandler(this.ZoomSetValueMenuItemClick);
|
||||
//
|
||||
|
@ -1726,7 +1726,7 @@ namespace Greenshot {
|
|||
//
|
||||
this.zoom50MenuItem.Name = "zoom50MenuItem";
|
||||
this.zoom50MenuItem.Size = new System.Drawing.Size(209, 22);
|
||||
this.zoom50MenuItem.Tag = "50";
|
||||
this.zoom50MenuItem.Tag = "1/2";
|
||||
this.zoom50MenuItem.Text = "50%";
|
||||
this.zoom50MenuItem.Click += new System.EventHandler(this.ZoomSetValueMenuItemClick);
|
||||
//
|
||||
|
@ -1734,7 +1734,7 @@ namespace Greenshot {
|
|||
//
|
||||
this.zoom66MenuItem.Name = "zoom66MenuItem";
|
||||
this.zoom66MenuItem.Size = new System.Drawing.Size(209, 22);
|
||||
this.zoom66MenuItem.Tag = "66";
|
||||
this.zoom66MenuItem.Tag = "2/3";
|
||||
this.zoom66MenuItem.Text = "66%";
|
||||
this.zoom66MenuItem.Click += new System.EventHandler(this.ZoomSetValueMenuItemClick);
|
||||
//
|
||||
|
@ -1742,7 +1742,7 @@ namespace Greenshot {
|
|||
//
|
||||
this.zoom75MenuItem.Name = "zoom75MenuItem";
|
||||
this.zoom75MenuItem.Size = new System.Drawing.Size(209, 22);
|
||||
this.zoom75MenuItem.Tag = "75";
|
||||
this.zoom75MenuItem.Tag = "3/4";
|
||||
this.zoom75MenuItem.Text = "75%";
|
||||
this.zoom75MenuItem.Click += new System.EventHandler(this.ZoomSetValueMenuItemClick);
|
||||
//
|
||||
|
@ -1757,7 +1757,7 @@ namespace Greenshot {
|
|||
this.zoomActualSizeMenuItem.Name = "zoomActualSizeMenuItem";
|
||||
this.zoomActualSizeMenuItem.ShortcutKeyDisplayString = "Ctrl+0";
|
||||
this.zoomActualSizeMenuItem.Size = new System.Drawing.Size(209, 22);
|
||||
this.zoomActualSizeMenuItem.Tag = "100";
|
||||
this.zoomActualSizeMenuItem.Tag = "1/1";
|
||||
this.zoomActualSizeMenuItem.Text = "100% - Actual Size";
|
||||
this.zoomActualSizeMenuItem.Click += new System.EventHandler(this.ZoomSetValueMenuItemClick);
|
||||
//
|
||||
|
@ -1770,7 +1770,7 @@ namespace Greenshot {
|
|||
//
|
||||
this.zoom200MenuItem.Name = "zoom200MenuItem";
|
||||
this.zoom200MenuItem.Size = new System.Drawing.Size(209, 22);
|
||||
this.zoom200MenuItem.Tag = "200";
|
||||
this.zoom200MenuItem.Tag = "2/1";
|
||||
this.zoom200MenuItem.Text = "200%";
|
||||
this.zoom200MenuItem.Click += new System.EventHandler(this.ZoomSetValueMenuItemClick);
|
||||
//
|
||||
|
@ -1778,7 +1778,7 @@ namespace Greenshot {
|
|||
//
|
||||
this.zoom300MenuItem.Name = "zoom300MenuItem";
|
||||
this.zoom300MenuItem.Size = new System.Drawing.Size(209, 22);
|
||||
this.zoom300MenuItem.Tag = "300";
|
||||
this.zoom300MenuItem.Tag = "3/1";
|
||||
this.zoom300MenuItem.Text = "300%";
|
||||
this.zoom300MenuItem.Click += new System.EventHandler(this.ZoomSetValueMenuItemClick);
|
||||
//
|
||||
|
@ -1786,7 +1786,7 @@ namespace Greenshot {
|
|||
//
|
||||
this.zoom400MenuItem.Name = "zoom400MenuItem";
|
||||
this.zoom400MenuItem.Size = new System.Drawing.Size(209, 22);
|
||||
this.zoom400MenuItem.Tag = "400";
|
||||
this.zoom400MenuItem.Tag = "4/1";
|
||||
this.zoom400MenuItem.Text = "400%";
|
||||
this.zoom400MenuItem.Click += new System.EventHandler(this.ZoomSetValueMenuItemClick);
|
||||
//
|
||||
|
@ -1794,7 +1794,7 @@ namespace Greenshot {
|
|||
//
|
||||
this.zoom600MenuItem.Name = "zoom600MenuItem";
|
||||
this.zoom600MenuItem.Size = new System.Drawing.Size(209, 22);
|
||||
this.zoom600MenuItem.Tag = "600";
|
||||
this.zoom600MenuItem.Tag = "6/1";
|
||||
this.zoom600MenuItem.Text = "600%";
|
||||
this.zoom600MenuItem.Click += new System.EventHandler(this.ZoomSetValueMenuItemClick);
|
||||
//
|
||||
|
|
|
@ -69,8 +69,7 @@ namespace Greenshot {
|
|||
/// <summary>
|
||||
/// All provided zoom values (in percents) in ascending order.
|
||||
/// </summary>
|
||||
private readonly int[] ZOOM_VALUES = new[] { 25, 50, 66, 75, 100, 200, 300, 400, 600 };
|
||||
private int _zoomValue = 100;
|
||||
private readonly Fraction[] ZOOM_VALUES = new Fraction[] { (1, 4), (1, 2), (2, 3), (3, 4), (1 ,1), (2, 1), (3, 1), (4, 1), (6, 1) };
|
||||
|
||||
/// <summary>
|
||||
/// An Implementation for the IImageEditor, this way Plugins have access to the HWND handles wich can be used with Win32 API calls.
|
||||
|
@ -1533,14 +1532,16 @@ namespace Greenshot {
|
|||
}
|
||||
|
||||
private void ZoomInMenuItemClick(object sender, EventArgs e) {
|
||||
var nextIndex = Array.FindIndex(ZOOM_VALUES, v => v > _zoomValue);
|
||||
var zoomValue = Surface.ZoomFactor;
|
||||
var nextIndex = Array.FindIndex(ZOOM_VALUES, v => v > zoomValue);
|
||||
var nextValue = nextIndex < 0 ? ZOOM_VALUES[ZOOM_VALUES.Length - 1] : ZOOM_VALUES[nextIndex];
|
||||
|
||||
ZoomSetValue(nextValue);
|
||||
}
|
||||
|
||||
private void ZoomOutMenuItemClick(object sender, EventArgs e) {
|
||||
var nextIndex = Array.FindLastIndex(ZOOM_VALUES, v => v < _zoomValue);
|
||||
var zoomValue = Surface.ZoomFactor;
|
||||
var nextIndex = Array.FindLastIndex(ZOOM_VALUES, v => v < zoomValue);
|
||||
var nextValue = nextIndex < 0 ? ZOOM_VALUES[0] : ZOOM_VALUES[nextIndex];
|
||||
|
||||
ZoomSetValue(nextValue);
|
||||
|
@ -1548,7 +1549,7 @@ namespace Greenshot {
|
|||
|
||||
private void ZoomSetValueMenuItemClick(object sender, EventArgs e) {
|
||||
var senderMenuItem = (ToolStripMenuItem)sender;
|
||||
int zoomPercent = int.Parse((string)senderMenuItem.Tag);
|
||||
var zoomPercent = Fraction.Parse((string)senderMenuItem.Tag);
|
||||
|
||||
ZoomSetValue(zoomPercent);
|
||||
}
|
||||
|
@ -1559,8 +1560,8 @@ namespace Greenshot {
|
|||
var maxImageSize = maxWindowSize - chromeSize;
|
||||
var imageSize = Surface.Image.Size;
|
||||
|
||||
static bool isFit(int zoom, int source, int boundary)
|
||||
=> source * zoom / 100 <= boundary;
|
||||
static bool isFit(Fraction scale, int source, int boundary)
|
||||
=> (int)(source * scale) <= boundary;
|
||||
|
||||
var nextIndex = Array.FindLastIndex(
|
||||
ZOOM_VALUES,
|
||||
|
@ -1572,7 +1573,7 @@ namespace Greenshot {
|
|||
ZoomSetValue(nextValue);
|
||||
}
|
||||
|
||||
private void ZoomSetValue(int value) {
|
||||
private void ZoomSetValue(Fraction value) {
|
||||
var surface = Surface as Surface;
|
||||
var panel = surface?.Parent as Panel;
|
||||
if (panel == null)
|
||||
|
@ -1596,14 +1597,13 @@ namespace Greenshot {
|
|||
}
|
||||
|
||||
// Set the new zoom value
|
||||
_zoomValue = value;
|
||||
Surface.ZoomFactor = 1f * value / 100;
|
||||
Surface.ZoomFactor = value;
|
||||
Size = GetOptimalWindowSize();
|
||||
AlignCanvasPositionAfterResize();
|
||||
|
||||
// Update zoom controls
|
||||
string valueString = value.ToString();
|
||||
zoomStatusDropDownBtn.Text = valueString + "%";
|
||||
zoomStatusDropDownBtn.Text = ((int)(100 * (double)value)).ToString() + "%";
|
||||
var valueString = value.ToString();
|
||||
foreach (var item in zoomMenuStrip.Items) {
|
||||
if (item is ToolStripMenuItem menuItem) {
|
||||
menuItem.Checked = menuItem.Tag as string == valueString;
|
||||
|
|
152
GreenshotPlugin/Core/Fraction.cs
Normal file
152
GreenshotPlugin/Core/Fraction.cs
Normal file
|
@ -0,0 +1,152 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace GreenshotPlugin.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Basic Fraction (Rational) numbers with features only needed to represent scale factors.
|
||||
/// </summary>
|
||||
public readonly struct Fraction : IEquatable<Fraction>, IComparable<Fraction>
|
||||
{
|
||||
public static Fraction Identity { get; } = new Fraction(1, 1);
|
||||
|
||||
public uint Numerator { get; }
|
||||
public uint Denominator { get; }
|
||||
|
||||
public Fraction(uint numerator, uint denominator)
|
||||
{
|
||||
if (denominator == 0)
|
||||
{
|
||||
throw new ArgumentException("Can't divide by zero.", nameof(denominator));
|
||||
}
|
||||
if (numerator == 0)
|
||||
{
|
||||
throw new ArgumentException("Zero is not supported by this implementation.", nameof(numerator));
|
||||
}
|
||||
var gcd = GreatestCommonDivisor(numerator, denominator);
|
||||
Numerator = numerator / gcd;
|
||||
Denominator = denominator / gcd;
|
||||
}
|
||||
|
||||
public Fraction Inverse()
|
||||
=> new Fraction(Denominator, Numerator);
|
||||
|
||||
#region Parse
|
||||
|
||||
private static readonly Regex PARSE_REGEX = new Regex(@"^([1-9][0-9]*)\/([1-9][0-9]*)$", RegexOptions.Compiled);
|
||||
public static bool TryParse(string str, out Fraction result)
|
||||
{
|
||||
var match = PARSE_REGEX.Match(str);
|
||||
if (!match.Success)
|
||||
{
|
||||
result = Identity;
|
||||
return false;
|
||||
}
|
||||
var numerator = uint.Parse(match.Groups[1].Value);
|
||||
var denominator = uint.Parse(match.Groups[2].Value);
|
||||
result = new Fraction(numerator, denominator);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Fraction Parse(string str)
|
||||
=> TryParse(str, out var result)
|
||||
? result
|
||||
: throw new ArgumentException($"Could not parse the input \"{str}\".", nameof(str));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides, interface implementations
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Numerator}/{Denominator}";
|
||||
|
||||
public override bool Equals(object obj)
|
||||
=> obj is Fraction fraction && Equals(fraction);
|
||||
|
||||
public bool Equals(Fraction other)
|
||||
=> Numerator == other.Numerator && Denominator == other.Denominator;
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hashCode = -1534900553;
|
||||
hashCode = hashCode * -1521134295 + Numerator.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + Denominator.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public int CompareTo(Fraction other)
|
||||
=> (int)(Numerator * other.Denominator) - (int)(other.Numerator * Denominator);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Equality operators
|
||||
|
||||
public static bool operator ==(Fraction left, Fraction right)
|
||||
=> left.Equals(right);
|
||||
|
||||
public static bool operator !=(Fraction left, Fraction right)
|
||||
=> !(left == right);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Comparison operators
|
||||
|
||||
public static bool operator <(Fraction left, Fraction right)
|
||||
=> left.CompareTo(right) < 0;
|
||||
|
||||
public static bool operator <=(Fraction left, Fraction right)
|
||||
=> left.CompareTo(right) <= 0;
|
||||
|
||||
public static bool operator >(Fraction left, Fraction right)
|
||||
=> left.CompareTo(right) > 0;
|
||||
|
||||
public static bool operator >=(Fraction left, Fraction right)
|
||||
=> left.CompareTo(right) >= 0;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Scale operators
|
||||
|
||||
public static Fraction operator *(Fraction left, Fraction right)
|
||||
=> new Fraction(left.Numerator * right.Numerator, left.Denominator * right.Denominator);
|
||||
|
||||
public static Fraction operator *(Fraction left, uint right)
|
||||
=> new Fraction(left.Numerator * right, left.Denominator);
|
||||
|
||||
public static Fraction operator *(uint left, Fraction right)
|
||||
=> new Fraction(left * right.Numerator, right.Denominator);
|
||||
|
||||
public static Fraction operator /(Fraction left, Fraction right)
|
||||
=> new Fraction(left.Numerator * right.Denominator, left.Denominator * right.Numerator);
|
||||
|
||||
public static Fraction operator /(Fraction left, uint right)
|
||||
=> new Fraction(left.Numerator, left.Denominator * right);
|
||||
|
||||
public static Fraction operator /(uint left, Fraction right)
|
||||
=> new Fraction(left * right.Denominator, right.Numerator);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Type conversion operators
|
||||
|
||||
public static implicit operator double(Fraction fraction)
|
||||
=> 1.0 * fraction.Numerator / fraction.Denominator;
|
||||
|
||||
public static implicit operator float(Fraction fraction)
|
||||
=> 1.0f * fraction.Numerator / fraction.Denominator;
|
||||
|
||||
public static implicit operator Fraction(uint number)
|
||||
=> new Fraction(number, 1u);
|
||||
|
||||
public static implicit operator Fraction((uint numerator, uint demoninator) tuple)
|
||||
=> new Fraction(tuple.numerator, tuple.demoninator);
|
||||
|
||||
#endregion
|
||||
|
||||
private static uint GreatestCommonDivisor(uint a, uint b)
|
||||
=> (b != 0) ? GreatestCommonDivisor(b, a % b) : a;
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ using System;
|
|||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using GreenshotPlugin.Core;
|
||||
using GreenshotPlugin.Effects;
|
||||
using GreenshotPlugin.Interfaces.Drawing;
|
||||
|
||||
|
@ -193,9 +194,9 @@ namespace GreenshotPlugin.Interfaces
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Zoom value applied to the surface. 1.0f for actual size (100%).
|
||||
/// Zoom value applied to the surface.
|
||||
/// </summary>
|
||||
float ZoomFactor { get; set; }
|
||||
Fraction ZoomFactor { get; set; }
|
||||
/// <summary>
|
||||
/// Translate a point from image coorditate space to surface coordinate space.
|
||||
/// </summary>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue