/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2015 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/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 Greenshot.Drawing; using System; using System.Drawing; using System.Windows.Forms; 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 aligment 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 the Rectangle an element must be resized an positioned to, in ordder to fit another element, keeping aspect ratio /// /// the rectangle of the element to be resized/repositioned /// the target size/position 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) /// 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 indicating the width and height the element should be scaled to and the position that should be applied to it for proper alignment public static RectangleF GetScaledRectangle(RectangleF currentRect, RectangleF targetRect, bool crop, ContentAlignment alignment) { SizeF newSize = GetScaledSize(currentRect.Size, targetRect.Size, crop); RectangleF newRect = new RectangleF(new Point(0, 0), newSize); return GetAlignedRectangle(newRect, targetRect, alignment); } public static void RationalScale(ref RectangleF originalRectangle, int resizeHandlePosition, PointF resizeHandleCoords) { Scale(ref originalRectangle, resizeHandlePosition, resizeHandleCoords, ScaleOptions.Rational); } public static void CenteredScale(ref RectangleF originalRectangle, int resizeHandlePosition, PointF resizeHandleCoords) { Scale(ref originalRectangle, resizeHandlePosition, resizeHandleCoords, ScaleOptions.Centered); } public static void Scale(ref RectangleF originalRectangle, int resizeHandlePosition, PointF resizeHandleCoords) { Scale(ref originalRectangle, resizeHandlePosition, resizeHandleCoords, null); } /// /// 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, int resizeHandlePosition, PointF resizeHandleCoords, ScaleOptions? options) { if (options == null) { 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 = (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, int resizeHandlePosition, PointF resizeHandleCoords) { switch (resizeHandlePosition) { case Gripper.POSITION_TOP_LEFT: 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 Gripper.POSITION_TOP_CENTER: originalRectangle.Height = originalRectangle.Top + originalRectangle.Height - resizeHandleCoords.Y; originalRectangle.Y = resizeHandleCoords.Y; break; case Gripper.POSITION_TOP_RIGHT: originalRectangle.Width = resizeHandleCoords.X - originalRectangle.Left; originalRectangle.Height = originalRectangle.Top + originalRectangle.Height - resizeHandleCoords.Y; originalRectangle.Y = resizeHandleCoords.Y; break; case Gripper.POSITION_MIDDLE_LEFT: originalRectangle.Width = originalRectangle.Left + originalRectangle.Width - resizeHandleCoords.X; originalRectangle.X = resizeHandleCoords.X; break; case Gripper.POSITION_MIDDLE_RIGHT: originalRectangle.Width = resizeHandleCoords.X - originalRectangle.Left; break; case Gripper.POSITION_BOTTOM_LEFT: originalRectangle.Width = originalRectangle.Left + originalRectangle.Width - resizeHandleCoords.X; originalRectangle.Height = resizeHandleCoords.Y - originalRectangle.Top; originalRectangle.X = resizeHandleCoords.X; break; case Gripper.POSITION_BOTTOM_CENTER: originalRectangle.Height = resizeHandleCoords.Y - originalRectangle.Top; break; case Gripper.POSITION_BOTTOM_RIGHT: 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 constants in Gripper.cs, e.g. Gripper.POSITION_TOP_LEFT /// coordinates of the used handle/gripper, adjusted coordinates will be written to this reference private static void adjustCoordsForRationalScale(RectangleF originalRectangle, int resizeHandlePosition, ref PointF resizeHandleCoords) { float originalRatio = originalRectangle.Width / originalRectangle.Height; float newWidth, newHeight, newRatio; switch (resizeHandlePosition) { case Gripper.POSITION_TOP_LEFT: 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 Gripper.POSITION_TOP_RIGHT: 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 Gripper.POSITION_BOTTOM_LEFT: 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 Gripper.POSITION_BOTTOM_RIGHT: 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) { Scale(boundsBeforeResize, cursorX, cursorY, ref boundsAfterResize, null); } public static void Scale(Rectangle boundsBeforeResize, int cursorX, int cursorY, ref RectangleF boundsAfterResize, IDoubleProcessor angleRoundBehavior) { Scale(boundsBeforeResize, Gripper.POSITION_TOP_LEFT, cursorX, cursorY, ref boundsAfterResize, angleRoundBehavior); } public static void Scale(Rectangle boundsBeforeResize, int 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 ShapeAngleRoundBehavior(); private ShapeAngleRoundBehavior() { } public double Process(double angle) { return Math.Round((angle + 45) / 90) * 90 - 45; } } public class LineAngleRoundBehavior : IDoubleProcessor { public static LineAngleRoundBehavior Instance = new LineAngleRoundBehavior(); private LineAngleRoundBehavior() { } public double Process(double angle) { return Math.Round(angle / 15) * 15; } } public class FixedAngleRoundBehavior : IDoubleProcessor { private readonly double fixedAngle; public FixedAngleRoundBehavior(double fixedAngle) { this.fixedAngle = fixedAngle; } public double Process(double angle) { return fixedAngle; } } /*public static int FindGripperPostition(float anchorX, float anchorY, float gripperX, float gripperY) { if(gripperY > anchorY) { if(gripperX > anchorY) return Gripper.POSITION_BOTTOM_RIGHT; else return Gripper.POSITION_BOTTOM_LEFT; } else { if(gripperX > anchorY) return Gripper.POSITION_TOP_RIGHT; else return Gripper.POSITION_TOP_LEFT; } }*/ } }