Fix Inconsistent Scale Behavior when Scaling Objects with Shift Modifier (#300)

* Fix Objects Growing near Infinity when Scaling w Aspect Ratio Maintained
* Refactor adjustCoordsForRationalScale to Get Rid of Code Duplication and Improve Comprehensibility
* Rename getNewSizeForRationalScale to GetNewSizeForRationalScale for Consistency
This commit is contained in:
jklingen 2021-03-27 19:54:31 +01:00 committed by GitHub
commit 4a959621ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,318 +1,339 @@
/* /*
* Greenshot - a free and open source screenshot tool * Greenshot - a free and open source screenshot tool
* Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom
* *
* For more information see: http://getgreenshot.org/ * For more information see: http://getgreenshot.org/
* The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 1 of the License, or * the Free Software Foundation, either version 1 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
using System; using System;
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using Greenshot.Drawing; using Greenshot.Drawing;
namespace Greenshot.Helpers { namespace Greenshot.Helpers {
/// <summary> /// <summary>
/// Offers a few helper functions for scaling/aligning an element with another element /// Offers a few helper functions for scaling/aligning an element with another element
/// </summary> /// </summary>
public static class ScaleHelper { public static class ScaleHelper {
[Flags] [Flags]
public enum ScaleOptions { public enum ScaleOptions {
/// <summary> /// <summary>
/// Default scale behavior. /// Default scale behavior.
/// </summary> /// </summary>
Default = 0x00, Default = 0x00,
/// <summary> /// <summary>
/// Scale a rectangle in two our four directions, mirrored at it's center coordinates /// Scale a rectangle in two our four directions, mirrored at it's center coordinates
/// </summary> /// </summary>
Centered = 0x01, Centered = 0x01,
/// <summary> /// <summary>
/// Scale a rectangle maintaining it's aspect ratio /// Scale a rectangle maintaining it's aspect ratio
/// </summary> /// </summary>
Rational = 0x02 Rational = 0x02
} }
/// <summary> /// <summary>
/// calculates the Size an element must be resized to, in order to fit another element, keeping aspect ratio /// calculates the Size an element must be resized to, in order to fit another element, keeping aspect ratio
/// </summary> /// </summary>
/// <param name="currentSize">the size of the element to be resized</param> /// <param name="currentSize">the size of the element to be resized</param>
/// <param name="targetSize">the target size of the element</param> /// <param name="targetSize">the target size of the element</param>
/// <param name="crop">in case the aspect ratio of currentSize and targetSize differs: shall the scaled size fit into targetSize (i.e. that one of its dimensions is smaller - false) or vice versa (true)</param> /// <param name="crop">in case the aspect ratio of currentSize and targetSize differs: shall the scaled size fit into targetSize (i.e. that one of its dimensions is smaller - false) or vice versa (true)</param>
/// <returns>a new SizeF object indicating the width and height the element should be scaled to</returns> /// <returns>a new SizeF object indicating the width and height the element should be scaled to</returns>
public static SizeF GetScaledSize(SizeF currentSize, SizeF targetSize, bool crop) { public static SizeF GetScaledSize(SizeF currentSize, SizeF targetSize, bool crop) {
float wFactor = targetSize.Width/currentSize.Width; float wFactor = targetSize.Width/currentSize.Width;
float hFactor = targetSize.Height/currentSize.Height; float hFactor = targetSize.Height/currentSize.Height;
float factor = crop ? Math.Max(wFactor, hFactor) : Math.Min(wFactor, hFactor); float factor = crop ? Math.Max(wFactor, hFactor) : Math.Min(wFactor, hFactor);
return new SizeF(currentSize.Width * factor, currentSize.Height * factor); return new SizeF(currentSize.Width * factor, currentSize.Height * factor);
} }
/// <summary> /// <summary>
/// calculates the position of an element depending on the desired alignment within a RectangleF /// calculates the position of an element depending on the desired alignment within a RectangleF
/// </summary> /// </summary>
/// <param name="currentRect">the bounds of the element to be aligned</param> /// <param name="currentRect">the bounds of the element to be aligned</param>
/// <param name="targetRect">the rectangle reference for alignment of the element</param> /// <param name="targetRect">the rectangle reference for alignment of the element</param>
/// <param name="alignment">the System.Drawing.ContentAlignment value indicating how the element is to be aligned should the width or height differ from targetSize</param> /// <param name="alignment">the System.Drawing.ContentAlignment value indicating how the element is to be aligned should the width or height differ from targetSize</param>
/// <returns>a new RectangleF object with Location aligned aligned to targetRect</returns> /// <returns>a new RectangleF object with Location aligned aligned to targetRect</returns>
public static RectangleF GetAlignedRectangle(RectangleF currentRect, RectangleF targetRect, ContentAlignment alignment) { public static RectangleF GetAlignedRectangle(RectangleF currentRect, RectangleF targetRect, ContentAlignment alignment) {
RectangleF newRect = new RectangleF(targetRect.Location, currentRect.Size); RectangleF newRect = new RectangleF(targetRect.Location, currentRect.Size);
switch(alignment) { switch(alignment) {
case ContentAlignment.TopCenter: case ContentAlignment.TopCenter:
newRect.X = (targetRect.Width - currentRect.Width) / 2; newRect.X = (targetRect.Width - currentRect.Width) / 2;
break; break;
case ContentAlignment.TopRight: case ContentAlignment.TopRight:
newRect.X = targetRect.Width - currentRect.Width; newRect.X = targetRect.Width - currentRect.Width;
break; break;
case ContentAlignment.MiddleLeft: case ContentAlignment.MiddleLeft:
newRect.Y = (targetRect.Height - currentRect.Height) / 2; newRect.Y = (targetRect.Height - currentRect.Height) / 2;
break; break;
case ContentAlignment.MiddleCenter: case ContentAlignment.MiddleCenter:
newRect.Y = (targetRect.Height - currentRect.Height) / 2; newRect.Y = (targetRect.Height - currentRect.Height) / 2;
newRect.X = (targetRect.Width - currentRect.Width) / 2; newRect.X = (targetRect.Width - currentRect.Width) / 2;
break; break;
case ContentAlignment.MiddleRight: case ContentAlignment.MiddleRight:
newRect.Y = (targetRect.Height - currentRect.Height) / 2; newRect.Y = (targetRect.Height - currentRect.Height) / 2;
newRect.X = targetRect.Width - currentRect.Width; newRect.X = targetRect.Width - currentRect.Width;
break; break;
case ContentAlignment.BottomLeft: case ContentAlignment.BottomLeft:
newRect.Y = targetRect.Height - currentRect.Height; newRect.Y = targetRect.Height - currentRect.Height;
break; break;
case ContentAlignment.BottomCenter: case ContentAlignment.BottomCenter:
newRect.Y = targetRect.Height - currentRect.Height; newRect.Y = targetRect.Height - currentRect.Height;
newRect.X = (targetRect.Width - currentRect.Width) / 2; newRect.X = (targetRect.Width - currentRect.Width) / 2;
break; break;
case ContentAlignment.BottomRight: case ContentAlignment.BottomRight:
newRect.Y = targetRect.Height - currentRect.Height; newRect.Y = targetRect.Height - currentRect.Height;
newRect.X = targetRect.Width - currentRect.Width; newRect.X = targetRect.Width - currentRect.Width;
break; break;
} }
return newRect; return newRect;
} }
/// <summary> /// <summary>
/// Calculates target size of a given rectangle scaled by dragging one of its handles (corners) /// Calculates target size of a given rectangle scaled by dragging one of its handles (corners)
/// </summary> /// </summary>
/// <param name="originalRectangle">bounds of the current rectangle, scaled values will be written to this reference</param> /// <param name="originalRectangle">bounds of the current rectangle, scaled values will be written to this reference</param>
/// <param name="resizeHandlePosition">position of the handle/gripper being used for resized, see constants in Gripper.cs, e.g. Gripper.POSITION_TOP_LEFT</param> /// <param name="resizeHandlePosition">position of the handle/gripper being used for resized, see constants in Gripper.cs, e.g. Gripper.POSITION_TOP_LEFT</param>
/// <param name="resizeHandleCoords">coordinates of the used handle/gripper</param> /// <param name="resizeHandleCoords">coordinates of the used handle/gripper</param>
/// <param name="options">ScaleOptions to use when scaling</param> /// <param name="options">ScaleOptions to use when scaling</param>
public static void Scale(ref RectangleF originalRectangle, Positions resizeHandlePosition, PointF resizeHandleCoords, ScaleOptions? options) { public static void Scale(ref RectangleF originalRectangle, Positions resizeHandlePosition, PointF resizeHandleCoords, ScaleOptions? options) {
options ??= GetScaleOptions(); options ??= GetScaleOptions();
if ((options & ScaleOptions.Rational) == ScaleOptions.Rational) { if ((options & ScaleOptions.Rational) == ScaleOptions.Rational) {
AdjustCoordsForRationalScale(originalRectangle, resizeHandlePosition, ref resizeHandleCoords); AdjustCoordsForRationalScale(originalRectangle, resizeHandlePosition, ref resizeHandleCoords);
} }
if ((options & ScaleOptions.Centered) == ScaleOptions.Centered) { if ((options & ScaleOptions.Centered) == ScaleOptions.Centered) {
// store center coordinates of rectangle // store center coordinates of rectangle
float rectCenterX = originalRectangle.Left + originalRectangle.Width / 2; float rectCenterX = originalRectangle.Left + originalRectangle.Width / 2;
float rectCenterY = originalRectangle.Top + originalRectangle.Height / 2; float rectCenterY = originalRectangle.Top + originalRectangle.Height / 2;
// scale rectangle using handle coordinates // scale rectangle using handle coordinates
Scale(ref originalRectangle, resizeHandlePosition, resizeHandleCoords); Scale(ref originalRectangle, resizeHandlePosition, resizeHandleCoords);
// mirror handle coordinates via rectangle center coordinates // mirror handle coordinates via rectangle center coordinates
resizeHandleCoords.X -= 2 * (resizeHandleCoords.X - rectCenterX); resizeHandleCoords.X -= 2 * (resizeHandleCoords.X - rectCenterX);
resizeHandleCoords.Y -= 2 * (resizeHandleCoords.Y - rectCenterY); resizeHandleCoords.Y -= 2 * (resizeHandleCoords.Y - rectCenterY);
// scale again with opposing handle and mirrored coordinates // scale again with opposing handle and mirrored coordinates
resizeHandlePosition = (Positions)((((int)resizeHandlePosition) + 4) % 8); resizeHandlePosition = (Positions)((((int)resizeHandlePosition) + 4) % 8);
Scale(ref originalRectangle, resizeHandlePosition, resizeHandleCoords); Scale(ref originalRectangle, resizeHandlePosition, resizeHandleCoords);
} else { } else {
Scale(ref originalRectangle, resizeHandlePosition, resizeHandleCoords); Scale(ref originalRectangle, resizeHandlePosition, resizeHandleCoords);
} }
} }
/// <summary> /// <summary>
/// Calculates target size of a given rectangle scaled by dragging one of its handles (corners) /// Calculates target size of a given rectangle scaled by dragging one of its handles (corners)
/// </summary> /// </summary>
/// <param name="originalRectangle">bounds of the current rectangle, scaled values will be written to this reference</param> /// <param name="originalRectangle">bounds of the current rectangle, scaled values will be written to this reference</param>
/// <param name="resizeHandlePosition">position of the handle/gripper being used for resized, see constants in Gripper.cs, e.g. Gripper.POSITION_TOP_LEFT</param> /// <param name="resizeHandlePosition">position of the handle/gripper being used for resized, see constants in Gripper.cs, e.g. Gripper.POSITION_TOP_LEFT</param>
/// <param name="resizeHandleCoords">coordinates of the used handle/gripper</param> /// <param name="resizeHandleCoords">coordinates of the used handle/gripper</param>
private static void Scale(ref RectangleF originalRectangle, Positions resizeHandlePosition, PointF resizeHandleCoords) { private static void Scale(ref RectangleF originalRectangle, Positions resizeHandlePosition, PointF resizeHandleCoords) {
switch(resizeHandlePosition) { switch(resizeHandlePosition) {
case Positions.TopLeft: case Positions.TopLeft:
originalRectangle.Width = originalRectangle.Left + originalRectangle.Width - resizeHandleCoords.X; originalRectangle.Width = originalRectangle.Left + originalRectangle.Width - resizeHandleCoords.X;
originalRectangle.Height = originalRectangle.Top + originalRectangle.Height - resizeHandleCoords.Y; originalRectangle.Height = originalRectangle.Top + originalRectangle.Height - resizeHandleCoords.Y;
originalRectangle.X = resizeHandleCoords.X; originalRectangle.X = resizeHandleCoords.X;
originalRectangle.Y = resizeHandleCoords.Y; originalRectangle.Y = resizeHandleCoords.Y;
break; break;
case Positions.TopCenter: case Positions.TopCenter:
originalRectangle.Height = originalRectangle.Top + originalRectangle.Height - resizeHandleCoords.Y; originalRectangle.Height = originalRectangle.Top + originalRectangle.Height - resizeHandleCoords.Y;
originalRectangle.Y = resizeHandleCoords.Y; originalRectangle.Y = resizeHandleCoords.Y;
break; break;
case Positions.TopRight: case Positions.TopRight:
originalRectangle.Width = resizeHandleCoords.X - originalRectangle.Left; originalRectangle.Width = resizeHandleCoords.X - originalRectangle.Left;
originalRectangle.Height = originalRectangle.Top + originalRectangle.Height - resizeHandleCoords.Y; originalRectangle.Height = originalRectangle.Top + originalRectangle.Height - resizeHandleCoords.Y;
originalRectangle.Y = resizeHandleCoords.Y; originalRectangle.Y = resizeHandleCoords.Y;
break; break;
case Positions.MiddleLeft: case Positions.MiddleLeft:
originalRectangle.Width = originalRectangle.Left + originalRectangle.Width - resizeHandleCoords.X; originalRectangle.Width = originalRectangle.Left + originalRectangle.Width - resizeHandleCoords.X;
originalRectangle.X = resizeHandleCoords.X; originalRectangle.X = resizeHandleCoords.X;
break; break;
case Positions.MiddleRight: case Positions.MiddleRight:
originalRectangle.Width = resizeHandleCoords.X - originalRectangle.Left; originalRectangle.Width = resizeHandleCoords.X - originalRectangle.Left;
break; break;
case Positions.BottomLeft: case Positions.BottomLeft:
originalRectangle.Width = originalRectangle.Left + originalRectangle.Width - resizeHandleCoords.X; originalRectangle.Width = originalRectangle.Left + originalRectangle.Width - resizeHandleCoords.X;
originalRectangle.Height = resizeHandleCoords.Y - originalRectangle.Top; originalRectangle.Height = resizeHandleCoords.Y - originalRectangle.Top;
originalRectangle.X = resizeHandleCoords.X; originalRectangle.X = resizeHandleCoords.X;
break; break;
case Positions.BottomCenter: case Positions.BottomCenter:
originalRectangle.Height = resizeHandleCoords.Y - originalRectangle.Top; originalRectangle.Height = resizeHandleCoords.Y - originalRectangle.Top;
break; break;
case Positions.BottomRight: case Positions.BottomRight:
originalRectangle.Width = resizeHandleCoords.X - originalRectangle.Left; originalRectangle.Width = resizeHandleCoords.X - originalRectangle.Left;
originalRectangle.Height = resizeHandleCoords.Y - originalRectangle.Top; originalRectangle.Height = resizeHandleCoords.Y - originalRectangle.Top;
break; break;
default: default:
throw new ArgumentException("Position cannot be handled: "+resizeHandlePosition); throw new ArgumentException("Position cannot be handled: "+resizeHandlePosition);
} }
} }
/// <summary> /// <summary>
/// Adjusts resizeHandleCoords so that aspect ratio is kept after resizing a given rectangle with provided arguments /// Adjusts resizeHandleCoords so that aspect ratio is kept after resizing a given rectangle with provided arguments.
/// </summary> /// An adjustment can always be done in two ways, e.g. *in*crease width until fit or *de*crease height until fit.
/// <param name="originalRectangle">bounds of the current rectangle</param> /// To avoid objects growing near infinity unexpectedly in certain combinations, the adjustment will choose the
/// <param name="resizeHandlePosition">position of the handle/gripper being used for resized, see Position</param> /// option resulting in the smaller rectangle.
/// <param name="resizeHandleCoords">coordinates of the used handle/gripper, adjusted coordinates will be written to this reference</param> /// </summary>
private static void AdjustCoordsForRationalScale(RectangleF originalRectangle, Positions resizeHandlePosition, ref PointF resizeHandleCoords) { /// <param name="originalRectangle">bounds of the current rectangle</param>
float originalRatio = originalRectangle.Width / originalRectangle.Height; /// <param name="resizeHandlePosition">position of the handle/gripper being used for resized, see Position</param>
float newWidth, newHeight, newRatio; /// <param name="resizeHandleCoords">coordinates of the used handle/gripper, adjusted coordinates will be written to this reference</param>
switch(resizeHandlePosition) { private static void AdjustCoordsForRationalScale(RectangleF originalRectangle, Positions resizeHandlePosition, ref PointF resizeHandleCoords) {
SizeF selectedRectangle, newSize;
case Positions.TopLeft:
newWidth = originalRectangle.Right - resizeHandleCoords.X; switch(resizeHandlePosition) {
newHeight = originalRectangle.Bottom - resizeHandleCoords.Y;
newRatio = newWidth / newHeight; case Positions.TopLeft:
if(newRatio > originalRatio) { // FIXME selectedRectangle = new SizeF(originalRectangle.Right - resizeHandleCoords.X, originalRectangle.Bottom - resizeHandleCoords.Y);
resizeHandleCoords.X = originalRectangle.Right - newHeight * originalRatio; newSize = GetNewSizeForRationalScale(originalRectangle.Size, selectedRectangle);
} else if(newRatio < originalRatio) { resizeHandleCoords.X = originalRectangle.Right - newSize.Width;
resizeHandleCoords.Y = originalRectangle.Bottom - newWidth / originalRatio; resizeHandleCoords.Y = originalRectangle.Bottom - newSize.Height;
} break;
break;
case Positions.TopRight:
case Positions.TopRight: selectedRectangle = new SizeF(resizeHandleCoords.X - originalRectangle.Left, originalRectangle.Bottom - resizeHandleCoords.Y);
newWidth = resizeHandleCoords.X - originalRectangle.Left; newSize = GetNewSizeForRationalScale(originalRectangle.Size, selectedRectangle);
newHeight = originalRectangle.Bottom - resizeHandleCoords.Y; resizeHandleCoords.X = originalRectangle.Left + newSize.Width;
newRatio = newWidth / newHeight; resizeHandleCoords.Y = originalRectangle.Bottom - newSize.Height;
if(newRatio > originalRatio) { // FIXME break;
resizeHandleCoords.X = newHeight * originalRatio + originalRectangle.Left;
} else if(newRatio < originalRatio) { case Positions.BottomLeft:
resizeHandleCoords.Y = originalRectangle.Bottom - newWidth / originalRatio; selectedRectangle = new SizeF(originalRectangle.Right - resizeHandleCoords.X, resizeHandleCoords.Y - originalRectangle.Top);
} newSize = GetNewSizeForRationalScale(originalRectangle.Size, selectedRectangle);
break; resizeHandleCoords.X = originalRectangle.Right - newSize.Width;
resizeHandleCoords.Y = originalRectangle.Top + newSize.Height;
case Positions.BottomLeft: break;
newWidth = originalRectangle.Right - resizeHandleCoords.X;
newHeight = resizeHandleCoords.Y - originalRectangle.Top; case Positions.BottomRight:
newRatio = newWidth / newHeight; selectedRectangle = new SizeF(resizeHandleCoords.X - originalRectangle.Left, resizeHandleCoords.Y - originalRectangle.Top);
if(newRatio > originalRatio) { newSize = GetNewSizeForRationalScale(originalRectangle.Size, selectedRectangle);
resizeHandleCoords.X = originalRectangle.Right - newHeight * originalRatio; resizeHandleCoords.X = originalRectangle.Left + newSize.Width;
} else if(newRatio < originalRatio) { resizeHandleCoords.Y = originalRectangle.Top + newSize.Height;
resizeHandleCoords.Y = newWidth / originalRatio + originalRectangle.Top; break;
} }
break; }
case Positions.BottomRight: /// <summary>
newWidth = resizeHandleCoords.X - originalRectangle.Left; /// For an original size, and a selected size, returns the the largest possible size that
newHeight = resizeHandleCoords.Y - originalRectangle.Top; /// * has the same aspect ratio as the original
newRatio = newWidth / newHeight; /// * fits into selected size
if(newRatio > originalRatio) { /// </summary>
resizeHandleCoords.X = newHeight * originalRatio + originalRectangle.Left; /// <param name="originalSize">size to be considered for keeping aspect ratio</param>
} else if(newRatio < originalRatio) { /// <param name="selectedSize">selection size (i.e. the size we'd produce if we wouldn't keep aspect ratio)</param>
resizeHandleCoords.Y = newWidth / originalRatio + originalRectangle.Top; /// <returns></returns>
} private static SizeF GetNewSizeForRationalScale(SizeF originalSize, SizeF selectedSize)
break; {
} SizeF newSize = selectedSize;
} float originalRatio = originalSize.Width / originalSize.Height;
float selectedRatio = selectedSize.Width / selectedSize.Height;
public static void Scale(Rectangle boundsBeforeResize, int cursorX, int cursorY, ref RectangleF boundsAfterResize, IDoubleProcessor angleRoundBehavior) { // will fix orientation if the scaling causes size to be flipped in any direction
int flippedRatioSign = Math.Sign(selectedRatio) * Math.Sign(originalRatio);
Scale(boundsBeforeResize, Positions.TopLeft, cursorX, cursorY, ref boundsAfterResize, angleRoundBehavior); if (Math.Abs(selectedRatio) > Math.Abs(originalRatio))
} {
// scaled rectangle (ratio) would be wider than original
public static void Scale(Rectangle boundsBeforeResize, Positions gripperPosition, int cursorX, int cursorY, ref RectangleF boundsAfterResize, IDoubleProcessor angleRoundBehavior) { // keep height and tweak width to maintain aspect ratio
newSize.Width = selectedSize.Height * originalRatio * flippedRatioSign;
ScaleOptions opts = GetScaleOptions(); }
else if (Math.Abs(selectedRatio) < Math.Abs(originalRatio))
bool rationalScale = (opts & ScaleOptions.Rational) == ScaleOptions.Rational; {
bool centeredScale = (opts & ScaleOptions.Centered) == ScaleOptions.Centered; // scaled rectangle (ratio) would be taller than original
// keep width and tweak height to maintain aspect ratio
if(rationalScale) { newSize.Height = selectedSize.Width / originalRatio * flippedRatioSign;
double angle = GeometryHelper.Angle2D(boundsBeforeResize.X, boundsBeforeResize.Y, cursorX, cursorY); }
return newSize;
if(angleRoundBehavior != null) { }
angle = angleRoundBehavior.Process(angle);
} public static void Scale(Rectangle boundsBeforeResize, int cursorX, int cursorY, ref RectangleF boundsAfterResize) {
Scale(boundsBeforeResize, cursorX, cursorY, ref boundsAfterResize, null);
int dist = GeometryHelper.Distance2D(boundsBeforeResize.X, boundsBeforeResize.Y, cursorX, cursorY); }
boundsAfterResize.Width = (int)Math.Round(dist * Math.Cos(angle / 180 * Math.PI)); public static void Scale(Rectangle boundsBeforeResize, int cursorX, int cursorY, ref RectangleF boundsAfterResize, IDoubleProcessor angleRoundBehavior) {
boundsAfterResize.Height = (int)Math.Round(dist * Math.Sin(angle / 180 * Math.PI));
} Scale(boundsBeforeResize, Positions.TopLeft, cursorX, cursorY, ref boundsAfterResize, angleRoundBehavior);
}
if(centeredScale) {
float wdiff = boundsAfterResize.Width - boundsBeforeResize.Width; public static void Scale(Rectangle boundsBeforeResize, Positions gripperPosition, int cursorX, int cursorY, ref RectangleF boundsAfterResize, IDoubleProcessor angleRoundBehavior) {
float hdiff = boundsAfterResize.Height - boundsBeforeResize.Height;
boundsAfterResize.Width += wdiff; ScaleOptions opts = GetScaleOptions();
boundsAfterResize.Height += hdiff;
boundsAfterResize.X -= wdiff; bool rationalScale = (opts & ScaleOptions.Rational) == ScaleOptions.Rational;
boundsAfterResize.Y -= hdiff; bool centeredScale = (opts & ScaleOptions.Centered) == ScaleOptions.Centered;
}
if(rationalScale) {
double angle = GeometryHelper.Angle2D(boundsBeforeResize.X, boundsBeforeResize.Y, cursorX, cursorY);
}
if(angleRoundBehavior != null) {
/// <returns>the current ScaleOptions depending on modifier keys held down</returns> angle = angleRoundBehavior.Process(angle);
public static ScaleOptions GetScaleOptions() { }
bool anchorAtCenter = (Control.ModifierKeys & Keys.Control) != 0;
bool maintainAspectRatio = (Control.ModifierKeys & Keys.Shift) != 0; int dist = GeometryHelper.Distance2D(boundsBeforeResize.X, boundsBeforeResize.Y, cursorX, cursorY);
ScaleOptions opts = ScaleOptions.Default;
if(anchorAtCenter) opts |= ScaleOptions.Centered; boundsAfterResize.Width = (int)Math.Round(dist * Math.Cos(angle / 180 * Math.PI));
if(maintainAspectRatio) opts |= ScaleOptions.Rational; boundsAfterResize.Height = (int)Math.Round(dist * Math.Sin(angle / 180 * Math.PI));
return opts; }
}
if(centeredScale) {
public interface IDoubleProcessor { float wdiff = boundsAfterResize.Width - boundsBeforeResize.Width;
double Process(double d); float hdiff = boundsAfterResize.Height - boundsBeforeResize.Height;
} boundsAfterResize.Width += wdiff;
boundsAfterResize.Height += hdiff;
public class ShapeAngleRoundBehavior : IDoubleProcessor { boundsAfterResize.X -= wdiff;
public static ShapeAngleRoundBehavior Instance = new(); boundsAfterResize.Y -= hdiff;
private ShapeAngleRoundBehavior() {} }
public double Process(double angle) {
return Math.Round((angle+45)/90)*90 - 45;
} }
}
public class LineAngleRoundBehavior : IDoubleProcessor { /// <returns>the current ScaleOptions depending on modifier keys held down</returns>
public static LineAngleRoundBehavior Instance = new(); public static ScaleOptions GetScaleOptions() {
private LineAngleRoundBehavior() {} bool anchorAtCenter = (Control.ModifierKeys & Keys.Control) != 0;
public double Process(double angle) { bool maintainAspectRatio = (Control.ModifierKeys & Keys.Shift) != 0;
return Math.Round(angle/15)*15; ScaleOptions opts = ScaleOptions.Default;
} if(anchorAtCenter) opts |= ScaleOptions.Centered;
} if(maintainAspectRatio) opts |= ScaleOptions.Rational;
} return opts;
} }
public interface IDoubleProcessor {
double Process(double d);
}
public class ShapeAngleRoundBehavior : IDoubleProcessor {
public static ShapeAngleRoundBehavior Instance = new();
private ShapeAngleRoundBehavior() {}
public double Process(double angle) {
return Math.Round((angle+45)/90)*90 - 45;
}
}
public class LineAngleRoundBehavior : IDoubleProcessor {
public static LineAngleRoundBehavior Instance = new();
private LineAngleRoundBehavior() {}
public double Process(double angle) {
return Math.Round(angle/15)*15;
}
}
}
}