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