VB.Net How to rotate a rectangle by mouse click and drag

1.1k views Asked by At

I'm using WinForms with a PictureBox and a custom rectangle drawn on it (face detection). Now I have code that draws the rectangle on the specified coordinates and allows resizing and moving of the rectangle. However, I need to know how to rotate the rectangle by using the mouse. I know how to transform it, but not how to do it by using the mouse. Can someone please assist with this?

Here's the code for the custom rectangle:

Imports System
Imports System.Drawing
Imports System.Windows.Forms

Public Class UserRect
    Private Enum PosSizableRect
        UpMiddle
        LeftMiddle
        LeftBottom
        LeftUp
        RightUp
        RightMiddle
        RightBottom
        BottomMiddle
        None
    End Enum

    Private mPictureBox As PictureBox

    Public rect As Rectangle

    Public allowDeformingDuringMovement As Boolean

    Private mIsClick As Boolean

    Private mMove As Boolean

    Private oldX As Integer

    Private oldY As Integer

    Private sizeNodeRect As Integer = 5

    Private mBmp As Bitmap

    Private nodeSelected As UserRect.PosSizableRect = UserRect.PosSizableRect.None

    Private angle As Integer = 30

    Public Sub New(r As Rectangle)
        Me.rect = r
        Me.mIsClick = False
    End Sub

    Public Sub Draw(g As Graphics)
        g.DrawRectangle(New Pen(Color.Red), Me.rect)
        For Each p As UserRect.PosSizableRect In [Enum].GetValues(GetType(UserRect.PosSizableRect))
            g.DrawRectangle(New Pen(Color.Red), Me.GetRect(p))
        Next
    End Sub

    Public Sub SetBitmapFile(filename As String)
        Me.mBmp = New Bitmap(filename)
    End Sub

    Public Sub SetBitmap(bmp As Bitmap)
        Me.mBmp = bmp
    End Sub

    Public Sub SetPictureBox(p As PictureBox)
        Me.mPictureBox = p
        AddHandler Me.mPictureBox.MouseDown, AddressOf Me.mPictureBox_MouseDown
        AddHandler Me.mPictureBox.MouseUp, AddressOf Me.mPictureBox_MouseUp
        AddHandler Me.mPictureBox.MouseMove, AddressOf Me.mPictureBox_MouseMove
        AddHandler Me.mPictureBox.Paint, AddressOf Me.mPictureBox_Paint
    End Sub

    Private Sub mPictureBox_Paint(sender As Object, e As PaintEventArgs)
        Try
            Me.Draw(e.Graphics)
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        End Try
    End Sub

    Private Sub mPictureBox_MouseDown(sender As Object, e As MouseEventArgs)
        Me.mIsClick = True
        Me.nodeSelected = UserRect.PosSizableRect.None
        Me.nodeSelected = Me.GetNodeSelectable(e.Location)
        If Me.rect.Contains(New Point(e.X, e.Y)) Then
            Me.mMove = True
        End If
        Me.oldX = e.X
        Me.oldY = e.Y
    End Sub

    Private Sub mPictureBox_MouseUp(sender As Object, e As MouseEventArgs)
        Me.mIsClick = False
        Me.mMove = False
    End Sub

    Private Sub mPictureBox_MouseMove(sender As Object, e As MouseEventArgs)
        Me.ChangeCursor(e.Location)
        If Not Me.mIsClick Then
            Return
        End If
        Dim rectangle As Rectangle = Me.rect
        Select Case Me.nodeSelected
            Case UserRect.PosSizableRect.UpMiddle
                Me.rect.Y = Me.rect.Y + (e.Y - Me.oldY)
                Me.rect.Height = Me.rect.Height - (e.Y - Me.oldY)
            Case UserRect.PosSizableRect.LeftMiddle
                Me.rect.X = Me.rect.X + (e.X - Me.oldX)
                Me.rect.Width = Me.rect.Width - (e.X - Me.oldX)
            Case UserRect.PosSizableRect.LeftBottom
                Me.rect.Width = Me.rect.Width - (e.X - Me.oldX)
                Me.rect.X = Me.rect.X + (e.X - Me.oldX)
                Me.rect.Height = Me.rect.Height + (e.Y - Me.oldY)
            Case UserRect.PosSizableRect.LeftUp
                Me.rect.X = Me.rect.X + (e.X - Me.oldX)
                Me.rect.Width = Me.rect.Width - (e.X - Me.oldX)
                Me.rect.Y = Me.rect.Y + (e.Y - Me.oldY)
                Me.rect.Height = Me.rect.Height - (e.Y - Me.oldY)
            Case UserRect.PosSizableRect.RightUp
                Me.rect.Width = Me.rect.Width + (e.X - Me.oldX)
                Me.rect.Y = Me.rect.Y + (e.Y - Me.oldY)
                Me.rect.Height = Me.rect.Height - (e.Y - Me.oldY)
            Case UserRect.PosSizableRect.RightMiddle
                Me.rect.Width = Me.rect.Width + (e.X - Me.oldX)
            Case UserRect.PosSizableRect.RightBottom
                Me.rect.Width = Me.rect.Width + (e.X - Me.oldX)
                Me.rect.Height = Me.rect.Height + (e.Y - Me.oldY)
            Case UserRect.PosSizableRect.BottomMiddle
                Me.rect.Height = Me.rect.Height + (e.Y - Me.oldY)
            Case Else
                If Me.mMove Then
                    Me.rect.X = Me.rect.X + e.X - Me.oldX
                    Me.rect.Y = Me.rect.Y + e.Y - Me.oldY
                End If
        End Select
        Me.oldX = e.X
        Me.oldY = e.Y
        If Me.rect.Width < 5 OrElse Me.rect.Height < 5 Then
            Me.rect = rectangle
        End If
        Me.TestIfRectInsideArea()
        Me.mPictureBox.Invalidate()
    End Sub

    Private Sub TestIfRectInsideArea()
        If Me.rect.X < 0 Then
            Me.rect.X = 0
        End If
        If Me.rect.Y < 0 Then
            Me.rect.Y = 0
        End If
        If Me.rect.Width <= 0 Then
            Me.rect.Width = 1
        End If
        If Me.rect.Height <= 0 Then
            Me.rect.Height = 1
        End If
        If Me.rect.X + Me.rect.Width > Me.mPictureBox.Width Then
            Me.rect.Width = Me.mPictureBox.Width - Me.rect.X - 1
            If Not Me.allowDeformingDuringMovement Then
                Me.mIsClick = False
            End If
        End If
        If Me.rect.Y + Me.rect.Height > Me.mPictureBox.Height Then
            Me.rect.Height = Me.mPictureBox.Height - Me.rect.Y - 1
            If Not Me.allowDeformingDuringMovement Then
                Me.mIsClick = False
            End If
        End If
    End Sub

    Private Function CreateRectSizableNode(x As Integer, y As Integer) As Rectangle
        Return New Rectangle(x - Me.sizeNodeRect / 2, y - Me.sizeNodeRect / 2, Me.sizeNodeRect, Me.sizeNodeRect)
    End Function

    Private Function GetRect(p As UserRect.PosSizableRect) As Rectangle
        Select Case p
            Case UserRect.PosSizableRect.UpMiddle
                Return Me.CreateRectSizableNode(Me.rect.X + Me.rect.Width / 2, Me.rect.Y)
            Case UserRect.PosSizableRect.LeftMiddle
                Return Me.CreateRectSizableNode(Me.rect.X, Me.rect.Y + Me.rect.Height / 2)
            Case UserRect.PosSizableRect.LeftBottom
                Return Me.CreateRectSizableNode(Me.rect.X, Me.rect.Y + Me.rect.Height)
            Case UserRect.PosSizableRect.LeftUp
                Return Me.CreateRectSizableNode(Me.rect.X, Me.rect.Y)
            Case UserRect.PosSizableRect.RightUp
                Return Me.CreateRectSizableNode(Me.rect.X + Me.rect.Width, Me.rect.Y)
            Case UserRect.PosSizableRect.RightMiddle
                Return Me.CreateRectSizableNode(Me.rect.X + Me.rect.Width, Me.rect.Y + Me.rect.Height / 2)
            Case UserRect.PosSizableRect.RightBottom
                Return Me.CreateRectSizableNode(Me.rect.X + Me.rect.Width, Me.rect.Y + Me.rect.Height)
            Case UserRect.PosSizableRect.BottomMiddle
                Return Me.CreateRectSizableNode(Me.rect.X + Me.rect.Width / 2, Me.rect.Y + Me.rect.Height)
            Case Else
                Return Nothing
        End Select
    End Function

    Private Function GetNodeSelectable(p As Point) As UserRect.PosSizableRect
        For Each posSizableRect As UserRect.PosSizableRect In [Enum].GetValues(GetType(UserRect.PosSizableRect))
            If Me.GetRect(posSizableRect).Contains(p) Then
                Return posSizableRect
            End If
        Next
        Return UserRect.PosSizableRect.None
    End Function

    Private Sub ChangeCursor(p As Point)
        Me.mPictureBox.Cursor = Me.GetCursor(Me.GetNodeSelectable(p))
    End Sub

    Private Function GetCursor(p As UserRect.PosSizableRect) As Cursor
        Select Case p
            Case UserRect.PosSizableRect.UpMiddle
                Return Cursors.SizeNS
            Case UserRect.PosSizableRect.LeftMiddle
                Return Cursors.SizeWE
            Case UserRect.PosSizableRect.LeftBottom
                Return Cursors.SizeNESW
            Case UserRect.PosSizableRect.LeftUp
                Return Cursors.SizeNWSE
            Case UserRect.PosSizableRect.RightUp
                Return Cursors.SizeNESW
            Case UserRect.PosSizableRect.RightMiddle
                Return Cursors.SizeWE
            Case UserRect.PosSizableRect.RightBottom
                Return Cursors.SizeNWSE
            Case UserRect.PosSizableRect.BottomMiddle
                Return Cursors.SizeNS
            Case Else
                Return Cursors.[Default]
        End Select
    End Function
End Class
1

There are 1 answers

2
S.Serpooshan On

Here is the scenario: When user push mouse down at first time, check if for example CTRL key is also pressed ModifierKeys = Keys.Control then we should start the rotation (the CTRL key is to distinguish between move and rotation). for this we should know the center of rotation and also the amount of rotation (rotation angle). we can assume the center of rectangle (face) is our center of rotation. to find the rotation angle, we should do some calculation:

Dim a1, a2, delta as Double
a1 = Math.Atan2(yA-yO, xA-xO)
a2 = Math.Atan2(yB-yO, xB-xO)
delta = a2 - a1 'this is the angle AOB (in radians) to be applied for rotation to all 4 points 

enter image description here