Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Re-size a rotated rectangle using mouse drag in c#

Tags:

c#

math

winforms

It will seems to most of the guys here that it's a repeated question but it isn't.

I am having a very hard problem. I have a rectangle which can be re-sized when it's not rotated and all work fine. But when it's rotated the algorithm is not working. The code is below:

internal void resizeToPoint(int tmpX, int tmpY, Rectangle limits)
{
    Matrix m = new Matrix();
    m.Reset();

    m.Translate(mLayoutRectangle.X + mParent.getAbsXOffset(DEPTH_LEVEL.APA) + mLayoutRectangle.Width / 2, mLayoutRectangle.Y + mParent.getAbsYOffset(DEPTH_LEVEL.APA) + mLayoutRectangle.Height / 2);
    m.Rotate(-(int)mAngle);
    m.Translate(-(mLayoutRectangle.X + mParent.getAbsXOffset(DEPTH_LEVEL.APA) + mLayoutRectangle.Width / 2), -(mLayoutRectangle.Y + mParent.getAbsYOffset(DEPTH_LEVEL.APA) + mLayoutRectangle.Height / 2));

    Point pPrime = new Point();
    pPrime.X = (int)((m.Elements[0] * tmpX + m.Elements[2] * tmpY) + (int)m.Elements[4]);
    pPrime.Y = (int)((m.Elements[1] * tmpX + m.Elements[3] * tmpY) + (int)m.Elements[5]);

    Point BottomRight = new Point(mLayoutRectangle.Width + getAbsXOffset(), mLayoutRectangle.Height + getAbsYOffset());
    Point TopRight = new Point(mLayoutRectangle.Width + getAbsXOffset(), getAbsYOffset());
    Point TopLeft = new Point(getAbsXOffset(), getAbsYOffset());
    Point BottomLeft = new Point(getAbsXOffset(), mLayoutRectangle.Height + getAbsYOffset());

    Point TopMiddle = new Point((mLayoutRectangle.Width / 2) + getAbsXOffset(), getAbsYOffset());
    Point RightMiddle = new Point(mLayoutRectangle.Width + getAbsXOffset(), mLayoutRectangle.Height / 2 + getAbsYOffset());
    Point BottomMiddle = new Point(getAbsXOffset() + mLayoutRectangle.Width / 2, mLayoutRectangle.Height + getAbsYOffset());
    Point LeftMiddle = new Point(getAbsXOffset(), getAbsYOffset() + mLayoutRectangle.Width / 2);

    Rectangle newLocationRectangle = mLayoutRectangle;
    Double widthRatioHeight = 1;

    if (this is ImageDesignElement)
    {
        widthRatioHeight = (double)((ImageDesignElement)this).getMonochromeImage().Width / (double)((ImageDesignElement)this).getMonochromeImage().Height;
        //widthRatioHeight = (double)mLayoutRectangle.Width / (double)mLayoutRectangle.Height;
    }

    int rectangleOldWidth = newLocationRectangle.Width;
    int rectangleOldHeight = newLocationRectangle.Height;
    int rectangleOldX = newLocationRectangle.X;
    int rectangleOldY = newLocationRectangle.Y;


    switch (mSelectedHandleForScaling)
    {
        case HANDLE_TYPE.TOP_LEFT:
            if (newLocationRectangle.Height - (pPrime.Y - TopLeft.Y) > 0)
            {
                newLocationRectangle.Height -= (pPrime.Y - TopLeft.Y);
                newLocationRectangle.Y += (pPrime.Y - TopLeft.Y);
                mInitialScalePoint.X = pPrime.X;
                mInitialScalePoint.Y = pPrime.Y;
            }
            if (newLocationRectangle.Width - (pPrime.X - TopLeft.X) > 0)
            {
                newLocationRectangle.Width -= (pPrime.X - TopLeft.X);
                newLocationRectangle.X += (pPrime.X - TopLeft.X);
                mInitialScalePoint.X = pPrime.X;
                mInitialScalePoint.Y = pPrime.Y;
            }
            break;

        case HANDLE_TYPE.TOP_MIDDLE:
            if (newLocationRectangle.Height - (pPrime.Y - TopMiddle.Y) > 0)
            {
                newLocationRectangle.Height -= (pPrime.Y - TopMiddle.Y);
                newLocationRectangle.Y += (pPrime.Y - TopMiddle.Y);
                mInitialScalePoint.X = pPrime.X;
                mInitialScalePoint.Y = pPrime.Y;
            }
            break;

        case HANDLE_TYPE.TOP_RIGHT:
            if (newLocationRectangle.Height - (pPrime.Y - TopRight.Y) > 0)
            {
                newLocationRectangle.Height -= (pPrime.Y - TopRight.Y);
                newLocationRectangle.Y += (pPrime.Y - TopRight.Y);
                mInitialScalePoint.X = pPrime.X;
                mInitialScalePoint.Y = pPrime.Y;
            }
            if (newLocationRectangle.Width + pPrime.X - TopRight.X > 0)
            {
                newLocationRectangle.Width += pPrime.X - TopRight.X;
                mInitialScalePoint.X = pPrime.X;
                mInitialScalePoint.Y = pPrime.Y;
            }
            break;

        case HANDLE_TYPE.RIGHT_MIDDLE:

            if (newLocationRectangle.Width + pPrime.X - RightMiddle.X > 0)
            {
                newLocationRectangle.Width += pPrime.X - RightMiddle.X;
            }
            mInitialScalePoint.X = pPrime.X;
            mInitialScalePoint.Y = pPrime.Y;
            break;

        case HANDLE_TYPE.BOTTOM_RIGHT:
            if (newLocationRectangle.Height + pPrime.Y - BottomRight.Y > 0)
            {
                newLocationRectangle.Height += pPrime.Y - BottomRight.Y;
                mInitialScalePoint.X = pPrime.X;
                mInitialScalePoint.Y = pPrime.Y;
            }
            if (newLocationRectangle.Width + pPrime.X - BottomRight.X > 0)
            {
                newLocationRectangle.Width += pPrime.X - BottomRight.X;
                mInitialScalePoint.X = pPrime.X;
                mInitialScalePoint.Y = pPrime.Y;
            }

            break;

        case HANDLE_TYPE.BOTTOM_MIDDLE:
            if (newLocationRectangle.Height + pPrime.Y - BottomMiddle.Y > 0)
            {
                newLocationRectangle.Height += pPrime.Y - BottomMiddle.Y;
                mInitialScalePoint.X = pPrime.X;
                mInitialScalePoint.Y = pPrime.Y;
            }
            break;

        case HANDLE_TYPE.BOTTOM_LEFT:
            if (newLocationRectangle.Height + pPrime.Y - BottomLeft.Y > 0)
            {
                newLocationRectangle.Height += pPrime.Y - BottomLeft.Y;
                mInitialScalePoint.X = pPrime.X;
                mInitialScalePoint.Y = pPrime.Y;
            }
            if (newLocationRectangle.Width - (pPrime.X - BottomLeft.X) > 0)
            {
                newLocationRectangle.Width -= (pPrime.X - BottomLeft.X);
                newLocationRectangle.X += (pPrime.X - BottomLeft.X);
                mInitialScalePoint.X = pPrime.X;
                mInitialScalePoint.Y = pPrime.Y;
            }
            break;

        case HANDLE_TYPE.LEFT_MIDDLE:
            if (newLocationRectangle.Width - (pPrime.X - BottomLeft.X) > 0)
            {
                newLocationRectangle.Width -= (pPrime.X - BottomLeft.X);
                newLocationRectangle.X += (pPrime.X - BottomLeft.X);
                mInitialScalePoint.X = pPrime.X;
                mInitialScalePoint.Y = pPrime.Y;
            }
            break;
    }

    if (((Control.ModifierKeys & Keys.Shift) != Keys.Shift) && this is ImageDesignElement)
    {

        int hightChange = newLocationRectangle.Height - rectangleOldHeight;
        int widthChange = newLocationRectangle.Width - rectangleOldWidth;

        newLocationRectangle.Width = rectangleOldWidth;//reset to the old Width before the call of this method
        newLocationRectangle.Width += (int)(((double)hightChange) * widthRatioHeight); //use the difference is width to adjust Width

        if (mSelectedHandleForScaling == HANDLE_TYPE.TOP_LEFT || mSelectedHandleForScaling == HANDLE_TYPE.BOTTOM_LEFT)
        {
            newLocationRectangle.X = rectangleOldX + (rectangleOldWidth - newLocationRectangle.Width);
        }

    }

    if (this is ImageDesignElement)
    {
        mLayoutRectangle = newLocationRectangle;
        //Console.WriteLine("* mLayoutRectangle.Width =" + mLayoutRectangle.Width + " mLayoutRectangle.Height =" + mLayoutRectangle.Height);

    }
    else
    {
        mLayoutRectangle = newLocationRectangle;
    }
}

Now, here we have a mLayoutRectangle a rectangle object in which some corners can be dragged with new position tmpX and tmpY through mouse. mAngle is the angle in degree rotated.

The problem is when angle is between 0 and 80 it works fine but when the angle is greater than this its behavior is changed totally. I don't get what I am doing wrong here.

I have used the following rectangle datatype: Rectangle Structure from MSDN. I want to set the left, right, top and bottom values for this rectangle. Can i somehow extend it, so that i can set values for left, right, top and bottom???

The problem is well explained here: but i have to do that in window form not WPF. Exact explanation of the problem

like image 230
TH Afridi Avatar asked Dec 06 '22 19:12

TH Afridi


1 Answers

1: Add a blank Windows Form

2: Add Mouse handlers for Paint, MouseDown, MouseUp, MouseMove

3: Add AnchorPoint.cs:

public enum AnchorPoint
{
    TopLeft,
    TopRight,
    BottomLeft,
    BottomRight,
    Rotation,
    Center
}

4: add MatrixExtension.cs

public static class MatrixExtension
{
    public static PointF TransformPoint(this Matrix @this, PointF point)
    {
        var points = new[] { point };

        @this.TransformPoints(points);

        return points[0];
    }
}

5: Edit Form1.cs:

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        private bool _drag;

        private SizeF _dragSize;
        private PointF _dragStart;
        private PointF _dragStartOffset;
        private RectangleF _dragRect;
        private AnchorPoint _dragAnchor;
        private Single _dragRot;

        private RectangleF _rect;
        private PointF _rectPos;
        private Single _rectRotation;

        public Form1()
        {
            InitializeComponent();

            //Center location of our item
            _rectPos = new PointF(200, 200);

            //The rectangle dimensions in _rectPos space
            _rect = new RectangleF(-40, -30, 80, 60);

            _rectRotation = 0;
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            var gc = e.Graphics;

            // Move Graphics handler to Rectangle's space
            var mat = new Matrix();
            mat.Translate(_rectPos.X, _rectPos.Y);
            mat.Rotate(_rectRotation);
            gc.Transform = mat;

            // All out gizmo rectangles are defined in Rectangle Space
            var rectTopLeft = new RectangleF(_rect.Left - 5f, _rect.Top - 5f, 10f, 10f);
            var rectTopRight = new RectangleF(_rect.Left + _rect.Width - 5f, _rect.Top - 5f, 10f, 10f);
            var rectBottomLeft = new RectangleF(_rect.Left - 5f, _rect.Top + _rect.Height - 5f, 10f, 10f);
            var rectBottomRight = new RectangleF(_rect.Left + _rect.Width - 5f, _rect.Top + _rect.Height - 5f, 10f, 10f);
            var rectRotate = new RectangleF(-5, _rect.Top + -30, 10f, 10f);
            var rectCenter = new RectangleF(-5, -5, 10f, 10f);

            var backBrush = new SolidBrush(Color.CadetBlue);
            var cornerBrush = new SolidBrush(Color.OrangeRed);

            // Looks rotated because we've transformed the graphics context
            gc.FillRectangle(backBrush, _rect);
            gc.FillRectangle(cornerBrush, rectTopLeft);
            gc.FillRectangle(cornerBrush, rectTopRight);
            gc.FillRectangle(cornerBrush, rectBottomLeft);
            gc.FillRectangle(cornerBrush, rectBottomRight);
            gc.FillRectangle(cornerBrush, rectRotate);
            gc.FillRectangle(cornerBrush, rectCenter);

            // Reset Graphics state
            gc.ResetTransform();
        }

        private void Form1_MouseDown(object sender, MouseEventArgs e)
        {
            // Compute a Screen to Rectangle transform 

            var mat = new Matrix();
            mat.Translate(_rectPos.X, _rectPos.Y);
            mat.Rotate(_rectRotation);
            mat.Invert();

            // Mouse point in Rectangle's space. 
            var point = mat.TransformPoint(new PointF(e.X, e.Y));

            var rect = _rect;
            var rectTopLeft = new RectangleF(_rect.Left - 5f, _rect.Top - 5f, 10f, 10f);
            var rectTopRight = new RectangleF(_rect.Left + _rect.Width - 5f, _rect.Top - 5f, 10f, 10f);
            var rectBottomLeft = new RectangleF(_rect.Left - 5f, _rect.Top + _rect.Height - 5f, 10f, 10f);
            var rectBottomRight = new RectangleF(_rect.Left + _rect.Width - 5f, _rect.Top + _rect.Height - 5f, 10f, 10f);
            var rectRotate = new RectangleF(-5, _rect.Top + -30, 10f, 10f);

            if (!_drag)
            {
                //We're in Rectangle space now, so its simple box-point intersection test
                if (rectTopLeft.Contains(point))
                {
                    _drag = true;
                    _dragStart = new PointF(point.X, point.Y);
                    _dragAnchor = AnchorPoint.TopLeft;
                    _dragRect = new RectangleF(_rect.Left, _rect.Top, _rect.Width, _rect.Height);
                }
                else if (rectTopRight.Contains(point))
                {
                    _drag = true;
                    _dragStart = new PointF(point.X, point.Y);
                    _dragAnchor = AnchorPoint.TopRight;
                    _dragRect = new RectangleF(_rect.Left, _rect.Top, _rect.Width, _rect.Height);
                }
                else if (rectBottomLeft.Contains(point))
                {
                    _drag = true;
                    _dragStart = new PointF(point.X, point.Y);
                    _dragAnchor = AnchorPoint.BottomLeft;
                    _dragRect = new RectangleF(_rect.Left, _rect.Top, _rect.Width, _rect.Height);
                }
                else if (rectBottomRight.Contains(point))
                {
                    _drag = true;
                    _dragStart = new PointF(point.X, point.Y);
                    _dragAnchor = AnchorPoint.BottomRight;
                    _dragRect = new RectangleF(_rect.Left, _rect.Top, _rect.Width, _rect.Height);
                }
                else if (rectRotate.Contains(point))
                {
                    _drag = true;
                    _dragStart = new PointF(point.X, point.Y);
                    _dragAnchor = AnchorPoint.Rotation;
                    _dragRect = new RectangleF(_rect.Left, _rect.Top, _rect.Width, _rect.Height);
                    _dragRot = _rectRotation;
                }
                else if (rect.Contains(point))
                {
                    _drag = true;
                    _dragStart = new PointF(point.X, point.Y);
                    _dragAnchor = AnchorPoint.Center;
                    _dragRect = new RectangleF(_rect.Left, _rect.Top, _rect.Width, _rect.Height);
                    _dragStartOffset = new PointF(e.X - _rectPos.X, e.Y - _rectPos.Y);
                }
            }
        }

        private void Form1_MouseUp(object sender, MouseEventArgs e)
        {
            _drag = false;    
        }

        private void Form1_MouseMove(object sender, MouseEventArgs e)
        {
            if (_drag)
            {
                var mat = new Matrix();

                mat.Translate(_rectPos.X, _rectPos.Y);
                mat.Rotate(_rectRotation);
                mat.Invert();

                var point = mat.TransformPoint(new PointF(e.X, e.Y));

                SizeF deltaSize;
                PointF clamped;

                switch (_dragAnchor)
                {
                    case AnchorPoint.TopLeft:

                        clamped = new PointF(Math.Min(0, point.X), Math.Min(0, point.Y));
                        deltaSize = new SizeF(clamped.X - _dragStart.X, clamped.Y - _dragStart.Y);
                        _rect = new RectangleF(
                            _dragRect.Left + deltaSize.Width,
                            _dragRect.Top + deltaSize.Height,
                            _dragRect.Width - deltaSize.Width,
                            _dragRect.Height - deltaSize.Height);
                        break;

                    case AnchorPoint.TopRight:
                        clamped = new PointF(Math.Max(0, point.X), Math.Min(0, point.Y));
                        deltaSize = new SizeF(clamped.X - _dragStart.X, clamped.Y - _dragStart.Y);
                        _rect = new RectangleF(
                            _dragRect.Left,
                            _dragRect.Top + deltaSize.Height,
                            _dragRect.Width + deltaSize.Width,
                            _dragRect.Height - deltaSize.Height);
                        break;

                    case AnchorPoint.BottomLeft:
                        clamped = new PointF(Math.Min(0, point.X), Math.Max(0, point.Y));
                        deltaSize = new SizeF(clamped.X - _dragStart.X, clamped.Y - _dragStart.Y);
                        _rect = new RectangleF(
                            _dragRect.Left + deltaSize.Width,
                            _dragRect.Top,
                            _dragRect.Width - deltaSize.Width,
                            _dragRect.Height + deltaSize.Height);
                        break;

                    case AnchorPoint.BottomRight:
                        clamped = new PointF(Math.Max(0, point.X), Math.Max(0, point.Y));
                        deltaSize = new SizeF(clamped.X - _dragStart.X, clamped.Y - _dragStart.Y);
                        _rect = new RectangleF(
                            _dragRect.Left,
                            _dragRect.Top,
                            _dragRect.Width + deltaSize.Width,
                            _dragRect.Height + deltaSize.Height);
                        break;

                    case AnchorPoint.Rotation:
                        var vecX = point.X;
                        var vecY = -point.Y;

                        var len = Math.Sqrt(vecX*vecX + vecY*vecY);

                        var normX = vecX / len;
                        var normY = vecY / len;

                        //In rectangles's space, 
                        //compute dot product between, 
                        //Up and mouse-position vector
                        var dotProduct = (0*normX + 1*normY);
                        var angle = Math.Acos(dotProduct);

                        if (point.X < 0)
                            angle = -angle;

                        // Add (delta-radians) to rotation as degrees
                        _rectRotation += (float) ((180/Math.PI)*angle);

                        break;

                    case AnchorPoint.Center:
                        //move this in screen-space
                        _rectPos = new PointF(e.X - _dragStartOffset.X, e.Y - _dragStartOffset.Y);
                        break;
                }

                Invalidate();
            }
        }
    }
}

Explanation

The trick is to move your mouse points into the rectangle's space. This is done by making a matrix with the rectangles point of origin, its rotation and inverting it:

var mat = new Matrix();
mat.Translate(_rectPos.X, _rectPos.Y);
mat.Rotate(_rectRotation);
mat.Invert();

then you multiply your screen-space points with the inverted matrix to get you a point in the rectangle's space.

var point = mat.TransformPoint(new PointF(e.X, e.Y));

From there is very simple to do all your operations. i.e. all intersections are point-box tests, and all transformations and scaling are done on an axis-aligned box.

Example

enter image description here

Todo

  • separate the code and make reusable
  • when you stop dragging, reset the _rectPos to the center of the rectangle

Enjoy. :)

like image 183
Meirion Hughes Avatar answered Dec 08 '22 07:12

Meirion Hughes