I need some help...

Imports System.Drawing.Drawing2D
Imports System.Drawing.Imaging

Public Class MapCanvas
    Inherits UserControl

    Public Sub New()
        InitializeComponent()
        InitializeControl()
    End Sub

#Region " Variables And Properties "

    Private actionAdded As Boolean = False
    Private undoRedoManager As New UndoRedoManager()
    Private _tiles As New List(Of MapTile)

    Private PicCanvas As MapCanvas
    Private undoStack As New Stack(Of MapAction)
    Private redoStack As New Stack(Of MapAction)

    Private ctrlClickTile As MapTile = Nothing

    Private Const DragThreshold As Integer = 4
    Private isCtrlClick As Boolean = False
    Private mouseDownLocation As Point

    Public Event TileSizeChanged(selectedTile As MapTile)

    Public Event TileSelected(selectedTile As MapTile)

    Private contextMenuTile As MapTile = Nothing
    Private animationTimer As System.Windows.Forms.Timer

    Public Event ZoomLevelChanged(zoomLevel As Single)

    Private selectedTile As MapTile = Nothing

    Private dragging As Boolean = False
    Private resizing As Boolean = False

    Private initialTileState As MapTile
    Private initialTilePosition As Point
    Private initialTileSize As Size
    Private initialMousePosition As Point

    Private isSelecting As Boolean = False

    Private selectionStartPoint As Point
    Private selectionEndPoint As Point
    Private selectionRectangle As Rectangle

    Private selectedTiles As New List(Of MapTile)

    Private initialSelectedTilesBounds As Rectangle

    Private initialSelectedTilesPositionsAndSizes As New List(Of Tuple(Of MapTile, Point, Size))

    Private isPanning As Boolean = False

    Private panStartPoint As Point
    Private scrollStartPoint As Point
    Private hasPanned As Boolean = False

    Private _gridSize As Integer = 32

    Private snappingEnabled As Boolean = True

    Private _showGrid As Boolean = True

    Private _mapWidth As Integer = 15000

    Private _mapHeight As Integer = 10000

    Private _zoomLevel As Single = 1.0F

    Public Event CanvasModified()

    Private _showPathingOverlay As Boolean = False

    Private _currentlySelectedTile As MapTile

    Public Property CurrentlySelectedTile As MapTile
        Get
            Return _currentlySelectedTile
        End Get
        Set(value As MapTile)
            _currentlySelectedTile = value
        End Set
    End Property
    Public Property ShowPathingOverlay As Boolean
        Get
            Return _showPathingOverlay
        End Get
        Set(value As Boolean)
            _showPathingOverlay = value
            Invalidate() ' Invalidate the canvas to redraw when the property changes
        End Set
    End Property

    Public Property GridSize As Integer
        Get
            Return _gridSize
        End Get
        Set(value As Integer)
            If value > 0 Then
                _gridSize = value
                Invalidate()
            End If
        End Set
    End Property

    Public Property ShowGrid As Boolean
        Get
            Return _showGrid
        End Get
        Set(value As Boolean)
            _showGrid = value
            Invalidate()
        End Set
    End Property

    Public ReadOnly Property Tiles As List(Of MapTile)
        Get
            Return _tiles
        End Get
    End Property

    Public Property ZoomLevel As Single
        Get
            Return _zoomLevel
        End Get
        Set(value As Single)
            If value <= 0 Then
                value = 0.01F
            End If

            If _zoomLevel <> value Then
                Dim oldZoomLevel As Single = _zoomLevel
                _zoomLevel = value
                AdjustScrollPosition(oldZoomLevel, Nothing)
                UpdateScrollBars()
                Invalidate()
                RaiseEvent ZoomLevelChanged(_zoomLevel)
            End If
        End Set
    End Property

    Public ReadOnly Property MapWidth As Integer
        Get
            Return _mapWidth
        End Get
    End Property

    Public ReadOnly Property MapHeight As Integer
        Get
            Return _mapHeight
        End Get
    End Property

    Public ReadOnly Property MapSize As Size
        Get
            Return New Size(_mapWidth, _mapHeight)
        End Get
    End Property

#End Region

#Region " Initialization and Setup "

    Private Sub InitializeControl()
        DoubleBuffered = True

        SetStyle(ControlStyles.AllPaintingInWmPaint Or
             ControlStyles.OptimizedDoubleBuffer Or
             ControlStyles.ResizeRedraw Or
             ControlStyles.UserPaint Or
             ControlStyles.Selectable, True)

        UpdateStyles()

        AutoScroll = True

        TabStop = True

        ContextMenuStrip = Nothing

        animationTimer = New System.Windows.Forms.Timer With {
            .Interval = 50
        }
        AddHandler animationTimer.Tick, AddressOf AnimationTimer_Tick
        animationTimer.Start()
    End Sub

#End Region

#Region " Drawing Methods "
    Protected Overrides Sub OnPaint(e As PaintEventArgs)
        MyBase.OnPaint(e)

        Dim g As Graphics = e.Graphics

        g.Clear(Color.White)

        g.InterpolationMode = InterpolationMode.NearestNeighbor
        g.PixelOffsetMode = PixelOffsetMode.Half
        g.SmoothingMode = SmoothingMode.None

        Dim state As GraphicsState = g.Save()

        g.TranslateTransform(AutoScrollPosition.X, AutoScrollPosition.Y)
        g.ScaleTransform(ZoomLevel, ZoomLevel)

        g.PixelOffsetMode = PixelOffsetMode.None

        DrawGrid(g)
        DrawTiles(g)

        g.Restore(state)

        If isSelecting Then
            DrawSelectionRectangle(g)
        End If

        If selectedTiles.Count > 0 Then
            If selectedTiles.Count = 1 Then
                DrawResizeHandles(g, selectedTiles(0))
            Else
                DrawGroupResizeHandles(g)
            End If
        End If

        ' Draw the overlay on pathable tiles if ShowPathingOverlay is True
        If ShowPathingOverlay Then
            For Each tile In _tiles
                If tile.IsPathable Then
                    Dim overlayRect As New Rectangle(tile.Position, tile.Size)
                    Using overlayBrush As New SolidBrush(Color.FromArgb(100, 255, 192, 203)) ' Light pink with transparency
                        g.FillRectangle(overlayBrush, overlayRect)
                    End Using
                End If
            Next
        End If
    End Sub

    Private Sub DrawGrid(g As Graphics)
        If Not _showGrid Then Exit Sub

        Using pen As New Pen(Color.LightGray)
            pen.Width = 1 / ZoomLevel

            Dim visibleRect As Rectangle = GetVisibleCanvasRectangle()

            Dim startX As Integer = visibleRect.Left - (visibleRect.Left Mod _gridSize)
            Dim startY As Integer = visibleRect.Top - (visibleRect.Top Mod _gridSize)

            For x As Integer = startX To visibleRect.Right Step _gridSize
                g.DrawLine(pen, x, visibleRect.Top, x, visibleRect.Bottom)
            Next

            For y As Integer = startY To visibleRect.Bottom Step _gridSize
                g.DrawLine(pen, visibleRect.Left, y, visibleRect.Right, y)
            Next
        End Using

        DrawGridBorder(g)
    End Sub

    Private Sub DrawGridBorder(g As Graphics)
        Dim gridRect As New Rectangle(0, 0, _mapWidth, _mapHeight)

        Using pen As New Pen(Color.Black)
            pen.Width = 4 / ZoomLevel
            g.DrawRectangle(pen, gridRect)
        End Using
    End Sub

    Private Sub DrawTiles(g As Graphics)
        For Each tile In _tiles
            Dim tileImage As Image = tile.GetCurrentFrame()

            Dim tileRect As New Rectangle(Math.Round(tile.Position.X),
                                      Math.Round(tile.Position.Y),
                                      Math.Round(tile.Size.Width),
                                      Math.Round(tile.Size.Height))

            g.DrawImage(tileImage, tileRect)

            If tile.Selected Then
                Using pen As New Pen(Color.Blue)
                    pen.DashStyle = DashStyle.Dash
                    g.DrawRectangle(pen, tileRect)
                End Using
            End If
        Next
    End Sub

    Private Sub DrawSelectionRectangle(g As Graphics)
        Using selectionPen As New Pen(Color.Red)
            selectionPen.DashStyle = Drawing2D.DashStyle.Dash
            Dim rect As Rectangle = GetSelectionRectangle()
            Dim screenRect As Rectangle = CanvasToClientRect(rect)
            g.DrawRectangle(selectionPen, screenRect)
        End Using
    End Sub

    Private Sub DrawResizeHandles(g As Graphics, tile As MapTile)
        Dim handleSize As Integer = 6
        Dim tileRect As New Rectangle(tile.Position, tile.Size)
        Dim screenRect As Rectangle = CanvasToClientRect(tileRect)

        Using brush As New SolidBrush(Color.White)
            Using pen As New Pen(Color.Black)
                DrawHandle(g, brush, pen, screenRect.Left - (handleSize \ 2), screenRect.Top - (handleSize \ 2), handleSize)
                DrawHandle(g, brush, pen, screenRect.Right - (handleSize \ 2), screenRect.Top - (handleSize \ 2), handleSize)
                DrawHandle(g, brush, pen, screenRect.Left - (handleSize \ 2), screenRect.Bottom - (handleSize \ 2), handleSize)
                DrawHandle(g, brush, pen, screenRect.Right - (handleSize \ 2), screenRect.Bottom - (handleSize \ 2), handleSize)
            End Using
        End Using
    End Sub

    Private Sub DrawGroupResizeHandles(g As Graphics)
        Dim handleSize As Integer = 6
        Dim bounds = GetSelectedTilesBounds()
        Dim screenRect As Rectangle = CanvasToClientRect(bounds)

        Using brush As New SolidBrush(Color.White)
            Using pen As New Pen(Color.Black)
                DrawHandle(g, brush, pen, screenRect.Left - (handleSize \ 2), screenRect.Top - (handleSize \ 2), handleSize)
                DrawHandle(g, brush, pen, screenRect.Right - (handleSize \ 2), screenRect.Top - (handleSize \ 2), handleSize)
                DrawHandle(g, brush, pen, screenRect.Left - (handleSize \ 2), screenRect.Bottom - (handleSize \ 2), handleSize)
                DrawHandle(g, brush, pen, screenRect.Right - (handleSize \ 2), screenRect.Bottom - (handleSize \ 2), handleSize)
            End Using
        End Using
    End Sub

    Private Sub DrawHandle(g As Graphics, brush As Brush, pen As Pen, x As Integer, y As Integer, size As Integer)
        g.FillRectangle(brush, x, y, size, size)
        g.DrawRectangle(pen, x, y, size, size)
    End Sub

#End Region

#Region " Mouse Event Handlers "

    Private Sub HandleLeftMouseDown(e As MouseEventArgs, currentPoint As Point, clickedTile As MapTile)
        If clickedTile Is Nothing Then
            StartSelection(currentPoint)
            Return
        End If

        If Not clickedTile.IsLocked Then
            Dim direction As MapTile.ResizeDirections = GetResizeDirection(clickedTile, currentPoint)
            If direction <> MapTile.ResizeDirections.None Then
                initialTileSize = clickedTile.Size
                initialTilePosition = clickedTile.Position
                StartSingleTileResizing(clickedTile, currentPoint, direction)
            Else
                initialTilePosition = clickedTile.Position
                If selectedTiles IsNot Nothing AndAlso selectedTiles.Contains(clickedTile) Then
                    For Each tile In selectedTiles
                        tile.InitialPosition = tile.Position
                    Next
                    initialMousePosition = currentPoint
                    dragging = True
                    Refresh()
                Else
                    DeselectAllTiles()
                    selectedTile = clickedTile
                    selectedTile.Selected = True
                    selectedTiles.Clear()
                    selectedTiles.Add(selectedTile)

                    selectedTile.InitialPosition = selectedTile.Position
                    initialMousePosition = currentPoint
                    dragging = True
                    Refresh()
                End If
                If PicCanvas IsNot Nothing Then
                    PicCanvas.CurrentlySelectedTile = clickedTile ' Ensure the selected tile is set
                End If
            End If
            actionAdded = False
        Else
            StartSelection(currentPoint)
        End If
    End Sub

    Private Sub HandleRightMouseUp(e As MouseEventArgs)
        Dim currentPoint As Point = ClientToCanvas(e.Location)
        contextMenuTile = GetTileAtPoint(currentPoint, includeLockedTiles:=True)

        If contextMenuTile IsNot Nothing Then
            ' If the clicked tile is not already selected, select it
            If Not contextMenuTile.Selected Then
                DeselectAllTiles()
                contextMenuTile.Selected = True
                selectedTiles.Clear()
                selectedTiles.Add(contextMenuTile)
                selectedTile = contextMenuTile

                RaiseEvent TileSelected(selectedTile)
            End If

            ' Show the context menu at the mouse position
            CTMMenu.Show(Me, e.Location)
        Else
            ' If no tile was clicked, deselect all tiles
            DeselectAllTiles()
        End If

        Refresh()
    End Sub

    Private Sub HandleDragging(currentPoint As Point)
        Dim deltaX As Integer = currentPoint.X - initialMousePosition.X
        Dim deltaY As Integer = currentPoint.Y - initialMousePosition.Y

        For Each tile In selectedTiles
            Dim initialPos As Point = tile.InitialPosition
            Dim newPos As New Point(initialPos.X + deltaX, initialPos.Y + deltaY)

            If snappingEnabled AndAlso Not IsShiftKeyPressed Then
                newPos.X = CInt(Math.Round(newPos.X / _gridSize)) * _gridSize
                newPos.Y = CInt(Math.Round(newPos.Y / _gridSize)) * _gridSize
            End If

            newPos.X = Math.Max(0, Math.Min(newPos.X, _mapWidth - tile.Size.Width))
            newPos.Y = Math.Max(0, Math.Min(newPos.Y, _mapHeight - tile.Size.Height))

            tile.Position = newPos
        Next

        Refresh()
        RaiseEvent CanvasModified()
    End Sub

    Private Sub HandleResizing(currentPoint As Point)
        If selectedTiles.Count = 1 Then
            ResizeSingleTile(currentPoint)
        Else
            ResizeGroupTiles(currentPoint)
        End If
    End Sub

    Private Sub UpdateCursor(currentPoint As Point)
        Dim canvasPoint As Point = ClientToCanvas(currentPoint)

        Dim cursorSet As Boolean = False

        If selectedTiles.Count > 1 Then
            Dim groupDirection As MapTile.ResizeDirections = GetGroupResizeDirection(canvasPoint)
            If groupDirection <> MapTile.ResizeDirections.None Then
                Cursor = GetCursorForDirection(groupDirection)
                cursorSet = True
            End If
        End If

        If Not cursorSet Then
            For Each tile In _tiles
                Dim direction As MapTile.ResizeDirections = GetResizeDirection(tile, canvasPoint)
                If direction <> MapTile.ResizeDirections.None Then
                    Cursor = GetCursorForDirection(direction)
                    cursorSet = True
                    Exit For
                End If
            Next
        End If

        If Not cursorSet Then
            Cursor = Cursors.Default
        End If
    End Sub

    Protected Overrides Sub OnMouseDown(e As MouseEventArgs)
        MyBase.OnMouseDown(e)

        If e.Button = MouseButtons.Left Then
            Dim currentPoint As Point = ClientToCanvas(e.Location)
            Dim clickedTile As MapTile = GetTileAtPoint(currentPoint)

            If IsCtrlKeyPressed Then
                isCtrlClick = True
                mouseDownLocation = e.Location
                ctrlClickTile = clickedTile

                If clickedTile IsNot Nothing Then
                    If selectedTiles.Contains(clickedTile) Then
                        clickedTile.Selected = False
                        Dim unused = selectedTiles.Remove(clickedTile)
                    Else
                        clickedTile.Selected = True
                        selectedTiles.Add(clickedTile)
                    End If

                    Refresh()
                End If
            Else
                ctrlClickTile = Nothing
                HandleLeftMouseDown(e, currentPoint, clickedTile)
                isCtrlClick = False
            End If
        ElseIf e.Button = MouseButtons.Right Then
            Dim currentPoint As Point = e.Location
            isPanning = True
            panStartPoint = currentPoint
            scrollStartPoint = New Point(AutoScrollPosition.X, AutoScrollPosition.Y)
            hasPanned = False
            Cursor = Cursors.Hand
        End If
    End Sub

    Protected Overrides Sub OnMouseMove(e As MouseEventArgs)
        MyBase.OnMouseMove(e)

        Dim currentPoint As Point = e.Location

        If resizing Then
            Dim canvasPoint As Point = ClientToCanvas(currentPoint)
            HandleResizing(canvasPoint)
        ElseIf dragging AndAlso selectedTiles.Count > 0 Then
            Dim canvasPoint As Point = ClientToCanvas(currentPoint)
            HandleDragging(canvasPoint)
        ElseIf isSelecting Then
            Dim canvasPoint As Point = ClientToCanvas(currentPoint)
            selectionEndPoint = canvasPoint
            Refresh()
        ElseIf isPanning AndAlso e.Button = MouseButtons.Right Then
            HandlePanning(currentPoint)
        ElseIf isCtrlClick Then
            If Math.Abs(e.Location.X - mouseDownLocation.X) > DragThreshold OrElse Math.Abs(e.Location.Y - mouseDownLocation.Y) > DragThreshold Then
                If ctrlClickTile IsNot Nothing AndAlso Not selectedTiles.Contains(ctrlClickTile) Then
                    ctrlClickTile.Selected = True
                    selectedTiles.Add(ctrlClickTile)
                End If

                DuplicateSelectedTiles(ClientToCanvas(e.Location))
                dragging = True
                isCtrlClick = False
                ctrlClickTile = Nothing
            End If
        Else
            UpdateCursor(currentPoint)
        End If
    End Sub

    Protected Overrides Sub OnMouseUp(e As MouseEventArgs)
        If e.Button <> MouseButtons.Right Then
            MyBase.OnMouseUp(e)
        End If
        If e.Button = MouseButtons.Right Then
            HandleRightMouseUp(e)
        End If

        If isSelecting Then
            isSelecting = False
            selectionRectangle = GetSelectionRectangle()
            SelectTilesInRectangle(selectionRectangle)
            Refresh()
        End If

        If dragging Then
            If selectedTile IsNot Nothing AndAlso selectedTile.Position <> initialTilePosition AndAlso Not actionAdded Then
                undoRedoManager.AddAction(New MoveTileAction(selectedTile, Me, initialTilePosition, selectedTile.Position))
                actionAdded = True
            End If
            dragging = False
        End If

        If resizing Then
            If selectedTile IsNot Nothing AndAlso selectedTile.Size <> initialTileSize AndAlso Not actionAdded Then
                undoRedoManager.AddAction(New ResizeTileAction(selectedTile, Me, initialTileSize, selectedTile.Size))
                actionAdded = True
            End If
            resizing = False
        End If

        If e.Button = MouseButtons.Left Then
            If isCtrlClick Then
                isCtrlClick = False
                ctrlClickTile = Nothing
            End If
        End If

        If e.Button = MouseButtons.Right Then
            If isPanning Then
                isPanning = False
                Cursor = Cursors.Default
                If hasPanned Then
                    hasPanned = False
                Else
                    HandleRightMouseUp(e)
                End If
            Else
                HandleRightMouseUp(e)
            End If
        End If

        ClearInitialState()
    End Sub

    Private Sub ClearInitialState()
        initialTilePosition = Point.Empty
        initialTileSize = Size.Empty
        selectedTile = Nothing
        actionAdded = False
    End Sub

    Protected Overrides Sub OnCreateControl()
        MyBase.OnCreateControl()
        AllowDrop = True
    End Sub

    Protected Overrides Sub OnDragEnter(drgevent As DragEventArgs)
        MyBase.OnDragEnter(drgevent)
        If drgevent.Data.GetDataPresent(GetType(Bitmap)) Then
            drgevent.Effect = DragDropEffects.Copy
        ElseIf drgevent.Data.GetDataPresent(DataFormats.FileDrop) Then
            drgevent.Effect = DragDropEffects.Copy
        Else
            drgevent.Effect = DragDropEffects.None
        End If
    End Sub

    Protected Overrides Sub OnDragDrop(drgevent As DragEventArgs)
        MyBase.OnDragDrop(drgevent)
        If drgevent.Data.GetDataPresent(GetType(Bitmap)) Then
            Dim image As Image = CType(drgevent.Data.GetData(GetType(Bitmap)), Image)
            Dim imageFilePath As String = Nothing
            If drgevent.Data.GetDataPresent("ImageFilePath") Then
                imageFilePath = CType(drgevent.Data.GetData("ImageFilePath"), String)
            End If
            HandleImageDrop(image, imageFilePath, drgevent)
        ElseIf drgevent.Data.GetDataPresent(DataFormats.FileDrop) Then
            Dim files() As String = CType(drgevent.Data.GetData(DataFormats.FileDrop), String())
            For Each file In files
                If IO.File.Exists(file) Then
                    Try
                        Dim image As Image = Image.FromFile(file)
                        HandleImageDrop(image, file, drgevent)
                    Catch ex As Exception
                    End Try
                End If
            Next
        End If
    End Sub

    Protected Overrides Sub OnMouseEnter(e As EventArgs)
        MyBase.OnMouseEnter(e)
        If Not Focused Then
            Dim unused = Focus()
        End If
    End Sub

    Protected Overrides Sub OnMouseWheel(e As MouseEventArgs)
        Dim unused = Focus()

        Dim mousePosition As Point = e.Location

        If e.Delta > 0 Then
            ZoomIn(mousePosition)
        ElseIf e.Delta < 0 Then
            ZoomOut(mousePosition)
        End If
    End Sub

#End Region

#Region " Helper Methods "

    Private Sub HandleImageDrop(image As Image, imageFilePath As String, drgevent As DragEventArgs)
        Dim isSpriteSheet As Boolean = False
        If drgevent.Data.GetDataPresent("IsSpriteSheet") Then
            isSpriteSheet = CBool(drgevent.Data.GetData("IsSpriteSheet"))
        End If

        Dim frameWidth As Integer = 0
        Dim frameHeight As Integer = 0
        If drgevent.Data.GetDataPresent("FrameWidth") Then
            frameWidth = CInt(drgevent.Data.GetData("FrameWidth"))
        End If
        If drgevent.Data.GetDataPresent("FrameHeight") Then
            frameHeight = CInt(drgevent.Data.GetData("FrameHeight"))
        End If

        Dim spriteSheetImage = If(Not String.IsNullOrEmpty(imageFilePath) AndAlso IO.File.Exists(imageFilePath), Image.FromFile(imageFilePath), image)

        If isSpriteSheet Then
            If frameWidth = 0 Then frameWidth = 32
            If frameHeight = 0 Then frameHeight = 32

            Dim framesInRow As Integer = spriteSheetImage.Width \ frameWidth
            Dim numberOfRows As Integer = spriteSheetImage.Height \ frameHeight

            Dim rowToExtract As Integer = 0

            Dim animationFrames As List(Of Image) = ExtractAnimationFrames(spriteSheetImage, frameWidth, frameHeight, rowToExtract)

            spriteSheetImage.Dispose()

            Dim scaledFrameWidth As Integer = frameWidth * 2
            Dim scaledFrameHeight As Integer = frameHeight * 2

            For i As Integer = 0 To animationFrames.Count - 1
                Dim resizedFrame As New Bitmap(scaledFrameWidth, scaledFrameHeight)
                Using g As Graphics = Graphics.FromImage(resizedFrame)
                    g.InterpolationMode = InterpolationMode.NearestNeighbor
                    g.PixelOffsetMode = PixelOffsetMode.HighQuality
                    g.SmoothingMode = SmoothingMode.None
                    g.DrawImage(animationFrames(i), New Rectangle(0, 0, scaledFrameWidth, scaledFrameHeight))
                End Using
                animationFrames(i).Dispose()
                animationFrames(i) = resizedFrame
            Next

            Dim frameSize As New Size(scaledFrameWidth, scaledFrameHeight)

            Dim point As Point = PointToClient(New Point(drgevent.X, drgevent.Y))
            point = ClientToCanvas(point)

            If snappingEnabled AndAlso Not IsShiftKeyPressed Then
                point.X = CInt(Math.Round(point.X / _gridSize)) * _gridSize
                point.Y = CInt(Math.Round(point.Y / _gridSize)) * _gridSize
            End If

            point.X = Math.Max(0, Math.Min(point.X, _mapWidth - frameSize.Width))
            point.Y = Math.Max(0, Math.Min(point.Y, _mapHeight - frameSize.Height))

            Dim newTile As New MapTile With {
    .AnimationFrames = animationFrames,
    .IsAnimated = True,
    .AnimationInterval = 200,
    .ImageFilePath = imageFilePath,
    .Position = point,
    .Size = frameSize,
    .OriginalSize = frameSize,
    .FrameWidth = frameWidth,
    .FrameHeight = frameHeight
}

            AddTile(newTile)
        Else
            Dim imageClone As Image = CType(image.Clone(), Image)

            Dim originalSize As Size = imageClone.Size

            Dim scaledWidth As Double = originalSize.Width * 0.5
            Dim scaledHeight As Double = originalSize.Height * 0.5

            Dim newWidth As Integer = CInt(Math.Ceiling(scaledWidth / _gridSize)) * _gridSize
            Dim newHeight As Integer = CInt(Math.Ceiling(scaledHeight / _gridSize)) * _gridSize

            newWidth = Math.Min(newWidth, _mapWidth)
            newHeight = Math.Min(newHeight, _mapHeight)

            Dim resizedImage As New Bitmap(newWidth, newHeight, PixelFormat.Format32bppArgb)
            Using g As Graphics = Graphics.FromImage(resizedImage)
                g.InterpolationMode = InterpolationMode.NearestNeighbor
                g.PixelOffsetMode = PixelOffsetMode.Half
                g.SmoothingMode = SmoothingMode.None

                g.Clear(Color.Transparent)
                g.DrawImage(imageClone, New Rectangle(0, 0, newWidth, newHeight))
            End Using

            imageClone.Dispose()

            Dim point As Point = PointToClient(New Point(drgevent.X, drgevent.Y))
            point = ClientToCanvas(point)

            If snappingEnabled AndAlso Not IsShiftKeyPressed Then
                point.X = CInt(point.X / _gridSize) * _gridSize
                point.Y = CInt(point.Y / _gridSize) * _gridSize
            End If

            point.X = Math.Max(0, Math.Min(point.X, _mapWidth - newWidth))
            point.Y = Math.Max(0, Math.Min(point.Y, _mapHeight - newHeight))

            Dim newTile As New MapTile With {
    .Image = resizedImage,
    .ImageFilePath = imageFilePath,
    .Position = point,
    .Size = New Size(newWidth, newHeight),
    .OriginalSize = image.Size
}
            AddTile(newTile)
        End If
    End Sub

    Private Function ExtractBossAnimationFrames(spriteSheet As Image, bossIndex As Integer) As List(Of Image)
        Dim frames As New List(Of Image)

        Dim frameWidth As Integer = 96
        Dim frameHeight As Integer = 96

        Dim framesPerBoss As Integer = 3

        Dim row As Integer = bossIndex
        Dim yOffset As Integer = row * frameHeight

        For col As Integer = 0 To framesPerBoss - 1
            Dim frameRect As New Rectangle(col * frameWidth, yOffset, frameWidth, frameHeight)

            Dim frameImage As New Bitmap(frameWidth, frameHeight)
            Using g As Graphics = Graphics.FromImage(frameImage)
                g.DrawImage(spriteSheet, New Rectangle(0, 0, frameWidth, frameHeight), frameRect, GraphicsUnit.Pixel)
            End Using

            frames.Add(frameImage)
        Next

        Return frames
    End Function

    Private Sub HandlePanning(currentMousePosition As Point)
        hasPanned = True

        Dim deltaX As Integer = currentMousePosition.X - panStartPoint.X
        Dim deltaY As Integer = currentMousePosition.Y - panStartPoint.Y

        Dim newScrollPosition As New Point(scrollStartPoint.X + deltaX, scrollStartPoint.Y + deltaY)

        Dim scaledMapWidth As Integer = _mapWidth * ZoomLevel
        Dim scaledMapHeight As Integer = _mapHeight * ZoomLevel

        Dim maxScrollX As Integer = Math.Max(0, scaledMapWidth - ClientSize.Width)
        Dim maxScrollY As Integer = Math.Max(0, scaledMapHeight - ClientSize.Height)

        Dim offsetX As Integer = 0
        Dim offsetY As Integer = 0

        If scaledMapWidth < ClientSize.Width Then
            offsetX = (ClientSize.Width - scaledMapWidth) \ 2
        End If
        If scaledMapHeight < ClientSize.Height Then
            offsetY = (ClientSize.Height - scaledMapHeight) \ 2
        End If

        Dim minScrollX As Integer = -offsetX
        Dim minScrollY As Integer = -offsetY
        Dim maxScrollXWithOffset As Integer = maxScrollX + offsetX
        Dim maxScrollYWithOffset = maxScrollY + offsetY

        newScrollPosition.X = Math.Max(-maxScrollXWithOffset, Math.Min(-offsetX, newScrollPosition.X))
        newScrollPosition.Y = Math.Max(-maxScrollYWithOffset, Math.Min(-offsetY, newScrollPosition.Y))

        AutoScrollPosition = New Point(-newScrollPosition.X, -newScrollPosition.Y)

        Refresh()
    End Sub

    Public Function ClientToCanvas(point As Point) As Point
        Return New Point((point.X - AutoScrollPosition.X) / ZoomLevel, (point.Y - AutoScrollPosition.Y) / ZoomLevel)
    End Function

    Private Function CanvasToClientRect(rect As Rectangle) As Rectangle
        Dim x As Integer = (rect.X * ZoomLevel) + AutoScrollPosition.X
        Dim y As Integer = (rect.Y * ZoomLevel) + AutoScrollPosition.Y
        Dim width As Integer = rect.Width * ZoomLevel
        Dim height As Integer = rect.Height * ZoomLevel
        Return New Rectangle(x, y, width, height)
    End Function

    Private Sub UpdateScrollBars()
        Dim scaledWidth As Integer = _mapWidth * ZoomLevel
        Dim scaledHeight As Integer = _mapHeight * ZoomLevel

        AutoScrollMinSize = New Size(scaledWidth, scaledHeight)

        Dim offsetX As Integer = 0
        Dim offsetY As Integer = 0

        If scaledWidth < ClientSize.Width Then
            offsetX = (ClientSize.Width - scaledWidth) \ 2
        End If
        If scaledHeight < ClientSize.Height Then
            offsetY = (ClientSize.Height - scaledHeight) \ 2
        End If

        Dim maxScrollX As Integer = Math.Max(0, scaledWidth - ClientSize.Width)
        Dim maxScrollY As Integer = Math.Max(0, scaledHeight - ClientSize.Height)

        Dim minScrollX As Integer = -offsetX
        Dim minScrollY As Integer = -offsetY
        Dim maxScrollXWithOffset As Integer = maxScrollX + offsetX
        Dim maxScrollYWithOffset As Integer = maxScrollY + offsetY

        Dim scrollX As Integer = AutoScrollPosition.X
        Dim scrollY As Integer = AutoScrollPosition.Y

        scrollX = Math.Max(-maxScrollXWithOffset, Math.Min(-offsetX, scrollX))
        scrollY = Math.Max(-maxScrollYWithOffset, Math.Min(-offsetY, scrollY))

        AutoScrollPosition = New Point(scrollX, scrollY)
    End Sub

    Private Sub AdjustScrollPosition(oldZoomLevel As Single, ByVal focusPoint As Point?)
        Dim focus As Point = If(focusPoint, New Point(ClientSize.Width / 2, ClientSize.Height / 2))

        Dim scrollPos As Point = AutoScrollPosition
        Dim offsetX As Integer = 0
        Dim offsetY As Integer = 0

        Dim scaledMapWidth As Integer = _mapWidth * ZoomLevel
        Dim scaledMapHeight As Integer = _mapHeight * ZoomLevel

        If scaledMapWidth < ClientSize.Width Then
            offsetX = (ClientSize.Width - scaledMapWidth) \ 2
        End If
        If scaledMapHeight < ClientSize.Height Then
            offsetY = (ClientSize.Height - scaledMapHeight) \ 2
        End If

        Dim focusX As Single = (-scrollPos.X + focus.X - offsetX) / oldZoomLevel
        Dim focusY As Single = (-scrollPos.Y + focus.Y - offsetY) / oldZoomLevel

        UpdateScrollBars()

        offsetX = 0
        offsetY = 0
        scaledMapWidth = CInt(_mapWidth * ZoomLevel)
        scaledMapHeight = CInt(_mapHeight * ZoomLevel)

        If scaledMapWidth < ClientSize.Width Then
            offsetX = (ClientSize.Width - scaledMapWidth) \ 2
        End If
        If scaledMapHeight < ClientSize.Height Then
            offsetY = (ClientSize.Height - scaledMapHeight) \ 2
        End If

        Dim newScrollX As Integer = -(focusX * ZoomLevel) + focus.X - offsetX
        Dim newScrollY As Integer = -(focusY * ZoomLevel) + focus.Y - offsetY

        Dim maxScrollX As Integer = Math.Max(0, scaledMapWidth - ClientSize.Width)
        Dim maxScrollY As Integer = Math.Max(0, scaledMapHeight - ClientSize.Height)

        Dim minScrollX As Integer = -offsetX
        Dim minScrollY As Integer = -offsetY
        Dim maxScrollXWithOffset As Integer = maxScrollX + offsetX
        Dim maxScrollYWithOffset As Integer = maxScrollY + offsetY

        newScrollX = Math.Max(-maxScrollXWithOffset, Math.Min(-offsetX, newScrollX))
        newScrollY = Math.Max(-maxScrollYWithOffset, Math.Min(-offsetY, newScrollY))

        AutoScrollPosition = New Point(-newScrollX, -newScrollY)
    End Sub

    Private Sub BringTileToFront(tile As MapTile)
        If _tiles.Remove(tile) Then
            _tiles.Add(tile)
            Invalidate()
        End If
    End Sub

    Private Sub SendTileToBack(tile As MapTile)
        If _tiles.Remove(tile) Then
            Dim insertIndex As Integer = _tiles.Count - 1
            For i As Integer = _tiles.Count - 1 To 0 Step -1
                If _tiles(i).IsLocked Then
                    insertIndex = i
                    Exit For
                End If
            Next
            _tiles.Insert(insertIndex, tile)
            AddAction(New MoveTileAction(tile, Me, tile.Position, tile.Position))
            Invalidate()
        End If
    End Sub

    Private Sub DeleteSelectedTiles()
        If selectedTiles.Count > 0 Then
            For Each tile In selectedTiles
                ' Add the remove action to the undo stack
                undoRedoManager.AddAction(New RemoveTileAction(tile, Me))
                ' Remove the tile from the canvas
                Dim unused = RemoveTileInternal(tile)
            Next

            ' Clear the selected tiles list
            selectedTiles.Clear()
            selectedTile = Nothing

            ' Refresh the canvas
            Invalidate()
            RaiseEvent CanvasModified()
        End If
    End Sub

    Private Function GetTileAtPoint(point As Point, Optional includeLockedTiles As Boolean = False) As MapTile
        For i As Integer = _tiles.Count - 1 To 0 Step -1
            Dim tile = _tiles(i)

            If tile.IsLocked AndAlso Not includeLockedTiles Then
                Continue For
            End If

            Dim rect As New Rectangle(tile.Position, tile.Size)
            If rect.Contains(point) Then
                Return tile
            Else
                If IsPointOverTileHandles(tile, point) Then
                    Return tile
                End If
            End If
        Next
        Return Nothing
    End Function

    Private Function IsPointOverTileHandles(tile As MapTile, point As Point) As Boolean
        Dim handleSize As Integer = 6
        Dim adjustedHandleSize As Single = handleSize / ZoomLevel

        Dim rect = New Rectangle(tile.Position, tile.Size)

        Dim topLeftHandle As New RectangleF(rect.Left - (adjustedHandleSize / 2), rect.Top - (adjustedHandleSize / 2), adjustedHandleSize, adjustedHandleSize)
        Dim topRightHandle As New RectangleF(rect.Right - (adjustedHandleSize / 2), rect.Top - (adjustedHandleSize / 2), adjustedHandleSize, adjustedHandleSize)
        Dim bottomLeftHandle As New RectangleF(rect.Left - (adjustedHandleSize / 2), rect.Bottom - (adjustedHandleSize / 2), adjustedHandleSize, adjustedHandleSize)
        Dim bottomRightHandle As New RectangleF(rect.Right - (adjustedHandleSize / 2), rect.Bottom - (adjustedHandleSize / 2), adjustedHandleSize, adjustedHandleSize)

        Return topLeftHandle.Contains(point) OrElse topRightHandle.Contains(point) OrElse bottomLeftHandle.Contains(point) OrElse bottomRightHandle.Contains(point)
    End Function

    Private Function GetResizeDirection(tile As MapTile, canvasPoint As Point) As MapTile.ResizeDirections
        Dim handleSize As Integer = 6

        Dim adjustedHandleSize As Single = handleSize / ZoomLevel

        Dim rect = New Rectangle(tile.Position, tile.Size)
        Dim direction As MapTile.ResizeDirections = MapTile.ResizeDirections.None

        Dim topLeftHandle As New RectangleF(rect.Left - (adjustedHandleSize / 2), rect.Top - (adjustedHandleSize / 2), adjustedHandleSize, adjustedHandleSize)
        Dim topRightHandle As New RectangleF(rect.Right - (adjustedHandleSize / 2), rect.Top - (adjustedHandleSize / 2), adjustedHandleSize, adjustedHandleSize)
        Dim bottomLeftHandle As New RectangleF(rect.Left - (adjustedHandleSize / 2), rect.Bottom - (adjustedHandleSize / 2), adjustedHandleSize, adjustedHandleSize)
        Dim bottomRightHandle As New RectangleF(rect.Right - (adjustedHandleSize / 2), rect.Bottom - (adjustedHandleSize / 2), adjustedHandleSize, adjustedHandleSize)

        If topLeftHandle.Contains(canvasPoint) Then
            direction = MapTile.ResizeDirections.Left Or MapTile.ResizeDirections.Top
        ElseIf topRightHandle.Contains(canvasPoint) Then
            direction = MapTile.ResizeDirections.Right Or MapTile.ResizeDirections.Top
        ElseIf bottomLeftHandle.Contains(canvasPoint) Then
            direction = MapTile.ResizeDirections.Left Or MapTile.ResizeDirections.Bottom
        ElseIf bottomRightHandle.Contains(canvasPoint) Then
            direction = MapTile.ResizeDirections.Right Or MapTile.ResizeDirections.Bottom
        End If

        Return direction
    End Function

    Private Function GetGroupResizeDirection(canvasPoint As Point) As MapTile.ResizeDirections
        If selectedTiles.Count <= 1 Then Return MapTile.ResizeDirections.None

        Dim handleSize As Integer = 6

        Dim adjustedHandleSize As Single = handleSize / ZoomLevel

        Dim bounds = GetSelectedTilesBounds()

        Dim direction As MapTile.ResizeDirections = MapTile.ResizeDirections.None

        Dim topLeftHandle As New RectangleF(bounds.Left - (adjustedHandleSize / 2), bounds.Top - (adjustedHandleSize / 2), adjustedHandleSize, adjustedHandleSize)
        Dim topRightHandle As New RectangleF(bounds.Right - (adjustedHandleSize / 2), bounds.Top - (adjustedHandleSize / 2), adjustedHandleSize, adjustedHandleSize)
        Dim bottomLeftHandle As New RectangleF(bounds.Left - (adjustedHandleSize / 2), bounds.Bottom - (adjustedHandleSize / 2), adjustedHandleSize, adjustedHandleSize)
        Dim bottomRightHandle As New RectangleF(bounds.Right - (adjustedHandleSize / 2), bounds.Bottom - (adjustedHandleSize / 2), adjustedHandleSize, adjustedHandleSize)

        If topLeftHandle.Contains(canvasPoint) Then
            direction = MapTile.ResizeDirections.Left Or MapTile.ResizeDirections.Top
        ElseIf topRightHandle.Contains(canvasPoint) Then
            direction = MapTile.ResizeDirections.Right Or MapTile.ResizeDirections.Top
        ElseIf bottomLeftHandle.Contains(canvasPoint) Then
            direction = MapTile.ResizeDirections.Left Or MapTile.ResizeDirections.Bottom
        ElseIf bottomRightHandle.Contains(canvasPoint) Then
            direction = MapTile.ResizeDirections.Right Or MapTile.ResizeDirections.Bottom
        End If

        Return direction
    End Function

    Private Function GetSelectedTilesBounds() As Rectangle
        If selectedTiles.Count = 0 Then Return Rectangle.Empty

        Dim minX As Integer = Integer.MaxValue
        Dim minY As Integer = Integer.MaxValue
        Dim maxX As Integer = Integer.MinValue
        Dim maxY As Integer = Integer.MinValue

        For Each tile In selectedTiles
            minX = Math.Min(minX, tile.Position.X)
            minY = Math.Min(minY, tile.Position.Y)
            maxX = Math.Max(maxX, tile.Position.X + tile.Size.Width)
            maxY = Math.Max(maxY, tile.Position.Y + tile.Size.Height)
        Next

        Return New Rectangle(minX, minY, maxX - minX, maxY - minY)
    End Function

    Private Function GetCursorForDirection(direction As MapTile.ResizeDirections) As Cursor
        Select Case direction
            Case MapTile.ResizeDirections.Left Or MapTile.ResizeDirections.Top,
                 MapTile.ResizeDirections.Right Or MapTile.ResizeDirections.Bottom
                Return Cursors.SizeNWSE
            Case MapTile.ResizeDirections.Right Or MapTile.ResizeDirections.Top,
                 MapTile.ResizeDirections.Left Or MapTile.ResizeDirections.Bottom
                Return Cursors.SizeNESW
            Case MapTile.ResizeDirections.Left, MapTile.ResizeDirections.Right
                Return Cursors.SizeWE
            Case MapTile.ResizeDirections.Top, MapTile.ResizeDirections.Bottom
                Return Cursors.SizeNS
            Case Else
                Return Cursors.Default
        End Select
    End Function

    Private Function GetSelectionRectangle() As Rectangle
        Dim x As Integer = Math.Min(selectionStartPoint.X, selectionEndPoint.X)
        Dim y As Integer = Math.Min(selectionStartPoint.Y, selectionEndPoint.Y)
        Dim width As Integer = Math.Abs(selectionStartPoint.X - selectionEndPoint.X)
        Dim height As Integer = Math.Abs(selectionStartPoint.Y - selectionEndPoint.Y)
        Return New Rectangle(x, y, width, height)
    End Function

    Private Sub SelectTilesInRectangle(rect As Rectangle)
        selectedTiles.Clear()
        For Each tile In _tiles
            If tile.IsLocked Then
                tile.Selected = False
                Continue For
            End If
            If rect.IntersectsWith(New Rectangle(tile.Position, tile.Size)) Then
                tile.Selected = True
                selectedTiles.Add(tile)
            Else
                tile.Selected = False
            End If
        Next
    End Sub

    Private Sub DeselectAllTiles()
        For Each tile In _tiles
            tile.Selected = False
            tile.CurrentResizeDirection = MapTile.ResizeDirections.None
        Next
        selectedTiles.Clear()
    End Sub

    Public Sub SetMapSize(width As Integer, height As Integer)
        _mapWidth = width
        _mapHeight = height

        For Each tile In _tiles
            Dim newX As Integer = Math.Max(0, Math.Min(tile.Position.X, _mapWidth - tile.Size.Width))
            Dim newY As Integer = Math.Max(0, Math.Min(tile.Position.Y, _mapHeight - tile.Size.Height))
            tile.Position = New Point(newX, newY)

            If tile.Position.X + tile.Size.Width > _mapWidth Then
                tile.Size = New Size(_mapWidth - tile.Position.X, tile.Size.Height)
            End If
            If tile.Position.Y + tile.Size.Height > _mapHeight Then
                tile.Size = New Size(tile.Size.Width, _mapHeight - tile.Position.Y)
            End If

            If tile.Size.Width < 1 Then tile.Size = New Size(1, tile.Size.Height)
            If tile.Size.Height < 1 Then tile.Size = New Size(tile.Size.Width, 1)
        Next

        UpdateScrollBars()
        Invalidate()
    End Sub

    Public Sub SnapAllTilesToGrid()
        For Each tile In _tiles
            tile.Position = New Point(CInt(tile.Position.X / _gridSize) * _gridSize,
                                      CInt(tile.Position.Y / _gridSize) * _gridSize)
            tile.Size = New Size(CInt(tile.Size.Width / _gridSize) * _gridSize,
                                 CInt(tile.Size.Height / _gridSize) * _gridSize)
        Next
        Invalidate()
    End Sub

    Private ReadOnly Property IsShiftKeyPressed As Boolean
        Get
            Return (ModifierKeys And Keys.Shift) = Keys.Shift
        End Get
    End Property

    Private ReadOnly Property IsCtrlKeyPressed As Boolean
        Get
            Return (Control.ModifierKeys And Keys.Control) = Keys.Control
        End Get
    End Property

    Private Function GetVisibleCanvasRectangle() As Rectangle
        Dim left As Integer = -AutoScrollPosition.X / ZoomLevel
        Dim top As Integer = -AutoScrollPosition.Y / ZoomLevel
        Dim width As Integer = ClientSize.Width / ZoomLevel
        Dim height As Integer = ClientSize.Height / ZoomLevel
        Return New Rectangle(left, top, width, height)
    End Function

#End Region

#Region " Tile Manipulation Methods "

    Private Sub RotateSelectedTiles(angle As Single)
        For Each tile In selectedTiles
            RotateTile(tile, angle)
        Next
    End Sub

    Private Sub RotateTile(tile As MapTile, angle As Integer)
        Dim rotateFlipType As RotateFlipType
        Select Case angle Mod 360
            Case 90
                rotateFlipType = RotateFlipType.Rotate90FlipNone
            Case 180
                rotateFlipType = RotateFlipType.Rotate180FlipNone
            Case 270
                rotateFlipType = RotateFlipType.Rotate270FlipNone
            Case Else
                Return
        End Select

        If tile.IsAnimated AndAlso tile.AnimationFrames IsNot Nothing Then
            For i As Integer = 0 To tile.AnimationFrames.Count - 1
                tile.AnimationFrames(i).RotateFlip(rotateFlipType)
            Next
            If angle Mod 180 <> 0 Then
                tile.Size = New Size(tile.Size.Height, tile.Size.Width)
            End If
        ElseIf tile.Image IsNot Nothing Then
            tile.Image.RotateFlip(rotateFlipType)
            If angle Mod 180 <> 0 Then
                tile.Size = New Size(tile.Size.Height, tile.Size.Width)
            End If
        End If

        tile.RotationAngle = (tile.RotationAngle + angle) Mod 360

        Dim oldCenter As New Point(tile.Position.X + (tile.Size.Width \ 2), tile.Position.Y + (tile.Size.Height \ 2))
        tile.Position = New Point(oldCenter.X - (tile.Size.Width \ 2), oldCenter.Y - (tile.Size.Height \ 2))

        If snappingEnabled AndAlso Not IsShiftKeyPressed Then
            tile.Position = New Point(CInt(Math.Round(tile.Position.X / _gridSize)) * _gridSize,
                                  CInt(Math.Round(tile.Position.Y / _gridSize)) * _gridSize)
        End If
    End Sub

    Private Function RotateImage(img As Image, angle As Single) As Image
        ArgumentNullException.ThrowIfNull(img)

        Dim rotatedBmp As New Bitmap(img.Height, img.Width)
        rotatedBmp.SetResolution(img.HorizontalResolution, img.VerticalResolution)

        Using g As Graphics = Graphics.FromImage(rotatedBmp)
            g.TranslateTransform(rotatedBmp.Width / 2, rotatedBmp.Height / 2)
            g.RotateTransform(angle)
            g.TranslateTransform(-img.Width / 2, -img.Height / 2)
            g.DrawImage(img, New Point(0, 0))
        End Using

        Return rotatedBmp
    End Function

    Private Sub HandleTileClick(e As MouseEventArgs, currentPoint As Point, clickedTile As MapTile)
        If clickedTile IsNot Nothing AndAlso Not clickedTile.IsLocked Then
            If selectedTiles.Contains(clickedTile) Then
                For Each tile In selectedTiles
                    tile.InitialPosition = tile.Position
                Next
                initialMousePosition = currentPoint
                dragging = True
                Refresh()
            Else
                DeselectAllTiles()
                selectedTile = clickedTile
                selectedTile.Selected = True
                selectedTiles.Clear()
                selectedTiles.Add(selectedTile)

                selectedTile.InitialPosition = selectedTile.Position
                initialMousePosition = currentPoint
                dragging = True
                Refresh()
            End If
        Else
            StartSelection(currentPoint)
        End If
    End Sub

    Private Sub DuplicateSelectedTiles(currentPoint As Point)
        Dim duplicates As New List(Of MapTile)

        For Each tile In selectedTiles
            Dim duplicateTile As MapTile = tile.Clone()
            _tiles.Add(duplicateTile)
            duplicates.Add(duplicateTile)
        Next

        For Each tile In _tiles
            tile.Selected = False
        Next

        selectedTiles = duplicates
        For Each tile In selectedTiles
            tile.Selected = True
            tile.InitialPosition = tile.Position
        Next

        initialMousePosition = currentPoint

        dragging = True
        Refresh()
        RaiseEvent CanvasModified()

        For Each tile In duplicates
            undoRedoManager.AddAction(New AddTileAction(tile, Me))
        Next
    End Sub

    Private Sub StartSelection(currentPoint As Point)
        isSelecting = True
        selectionStartPoint = currentPoint
        selectionEndPoint = currentPoint
        DeselectAllTiles()
        Refresh()
    End Sub

    Private Sub StartGroupResizing(currentPoint As Point, groupDirection As MapTile.ResizeDirections)
        resizing = True
        initialMousePosition = currentPoint

        initialSelectedTilesBounds = GetSelectedTilesBounds()
        initialSelectedTilesPositionsAndSizes = New List(Of Tuple(Of MapTile, Point, Size))
        For Each tile In selectedTiles
            initialSelectedTilesPositionsAndSizes.Add(Tuple.Create(tile, tile.Position, tile.Size))
        Next

        For Each tile In selectedTiles
            tile.CurrentResizeDirection = groupDirection
        Next

        Refresh()
    End Sub

    Private Sub StartSingleTileResizing(clickedTile As MapTile, currentPoint As Point, direction As MapTile.ResizeDirections)
        DeselectAllTiles()
        selectedTile = clickedTile
        selectedTile.Selected = True
        selectedTiles.Clear()
        selectedTiles.Add(selectedTile)

        resizing = True
        selectedTile.IsResizing = True
        selectedTile.CurrentResizeDirection = direction

        initialTilePosition = selectedTile.Position
        initialTileSize = selectedTile.Size
        initialMousePosition = currentPoint
        Refresh()
    End Sub

    Private Sub DuplicateTile(clickedTile As MapTile, currentPoint As Point)
        Dim duplicateTile As MapTile = clickedTile.Clone()
        _tiles.Add(duplicateTile)
        DeselectAllTiles()
        selectedTile = duplicateTile
        selectedTile.Selected = True
        selectedTiles.Clear()
        selectedTiles.Add(selectedTile)

        initialTilePosition = duplicateTile.Position
        duplicateTile.InitialPosition = duplicateTile.Position
        initialMousePosition = currentPoint

        dragging = True
        Refresh()
    End Sub

    Private Sub ResizeSingleTile(currentPoint As Point)
        Dim deltaX As Integer = currentPoint.X - initialMousePosition.X
        Dim deltaY As Integer = currentPoint.Y - initialMousePosition.Y

        Dim newSize As Size = initialTileSize
        Dim newPosition As Point = initialTilePosition

        If selectedTile.CurrentResizeDirection.HasFlag(MapTile.ResizeDirections.Right) Then
            newSize.Width = initialTileSize.Width + deltaX
        End If
        If selectedTile.CurrentResizeDirection.HasFlag(MapTile.ResizeDirections.Bottom) Then
            newSize.Height = initialTileSize.Height + deltaY
        End If
        If selectedTile.CurrentResizeDirection.HasFlag(MapTile.ResizeDirections.Left) Then
            newSize.Width = initialTileSize.Width - deltaX
            newPosition.X = initialTilePosition.X + deltaX
        End If
        If selectedTile.CurrentResizeDirection.HasFlag(MapTile.ResizeDirections.Top) Then
            newSize.Height = initialTileSize.Height - deltaY
            newPosition.Y = initialTilePosition.Y + deltaY
        End If

        If newSize.Width <= 1 Then newSize.Width = 1
        If newSize.Height <= 1 Then newSize.Height = 1

        If snappingEnabled AndAlso Not IsShiftKeyPressed Then
            newSize.Width = CInt(Math.Ceiling(newSize.Width / _gridSize)) * _gridSize
            newSize.Height = CInt(Math.Ceiling(newSize.Height / _gridSize)) * _gridSize
            newPosition.X = CInt(Math.Round(newPosition.X / _gridSize)) * _gridSize
            newPosition.Y = CInt(Math.Round(newPosition.Y / _gridSize)) * _gridSize
        End If

        If newPosition.X < 0 Then
            newSize.Width += newPosition.X
            newPosition.X = 0
        End If
        If newPosition.Y < 0 Then
            newSize.Height += newPosition.Y
            newPosition.Y = 0
        End If
        If newPosition.X + newSize.Width > _mapWidth Then
            newSize.Width = _mapWidth - newPosition.X
        End If
        If newPosition.Y + newSize.Height > _mapHeight Then
            newSize.Height = _mapHeight - newPosition.Y
        End If

        If newSize.Width <= 1 Then newSize.Width = 1
        If newSize.Height <= 1 Then newSize.Height = 1

        selectedTile.Size = newSize
        selectedTile.Position = newPosition

        RaiseEvent TileSizeChanged(selectedTile)

        Refresh()
        RaiseEvent CanvasModified()
    End Sub

    Private Sub ResizeGroupTiles(currentPoint As Point)
        Dim deltaX As Integer = currentPoint.X - initialMousePosition.X
        Dim deltaY As Integer = currentPoint.Y - initialMousePosition.Y

        Dim newBounds As Rectangle = initialSelectedTilesBounds

        Dim resizeDirection = selectedTiles(0).CurrentResizeDirection

        If resizeDirection.HasFlag(MapTile.ResizeDirections.Right) Then
            newBounds.Width = initialSelectedTilesBounds.Width + deltaX
        End If
        If resizeDirection.HasFlag(MapTile.ResizeDirections.Bottom) Then
            newBounds.Height = initialSelectedTilesBounds.Height + deltaY
        End If
        If resizeDirection.HasFlag(MapTile.ResizeDirections.Left) Then
            newBounds.Width = initialSelectedTilesBounds.Width - deltaX
            newBounds.X = initialSelectedTilesBounds.X + deltaX
        End If
        If resizeDirection.HasFlag(MapTile.ResizeDirections.Top) Then
            newBounds.Height = initialSelectedTilesBounds.Height - deltaY
            newBounds.Y = initialSelectedTilesBounds.Y + deltaY
        End If

        If newBounds.Width <= 1 Then newBounds.Width = 1
        If newBounds.Height <= 1 Then newBounds.Height = 1

        If newBounds.X < 0 Then
            newBounds.Width += newBounds.X
            newBounds.X = 0
        End If
        If newBounds.Y < 0 Then
            newBounds.Height += newBounds.Y
            newBounds.Y = 0
        End If
        If newBounds.X + newBounds.Width > _mapWidth Then
            newBounds.Width = _mapWidth - newBounds.X
        End If
        If newBounds.Y + newBounds.Height > _mapHeight Then
            newBounds.Height = _mapHeight - newBounds.Y
        End If

        If newBounds.Width <= 1 Then newBounds.Width = 1
        If newBounds.Height <= 1 Then newBounds.Height = 1

        Dim scaleX As Double = newBounds.Width / initialSelectedTilesBounds.Width
        Dim scaleY As Double = newBounds.Height / initialSelectedTilesBounds.Height

        For Each item In initialSelectedTilesPositionsAndSizes
            Dim tile = item.Item1
            Dim initialPos = item.Item2
            Dim initialSize = item.Item3

            Dim relX As Double = (initialPos.X - initialSelectedTilesBounds.X) / initialSelectedTilesBounds.Width
            Dim relY As Double = (initialPos.Y - initialSelectedTilesBounds.Y) / initialSelectedTilesBounds.Height

            Dim newPosX As Double = newBounds.X + (relX * newBounds.Width)
            Dim newPosY As Double = newBounds.Y + (relY * newBounds.Height)

            Dim newWidth As Double = initialSize.Width * scaleX
            Dim newHeight As Double = initialSize.Height * scaleY

            If newWidth <= 1 Then newWidth = 1
            If newHeight <= 1 Then newHeight = 1

            If snappingEnabled AndAlso Not IsShiftKeyPressed Then
                newPosX = CInt(Math.Round(newPosX / _gridSize)) * _gridSize
                newPosY = CInt(Math.Round(newPosY / _gridSize)) * _gridSize
                newWidth = CInt(Math.Ceiling(newWidth / _gridSize)) * _gridSize
                newHeight = CInt(Math.Ceiling(newHeight / _gridSize)) * _gridSize
            End If

            newPosX = Math.Max(0, Math.Min(newPosX, _mapWidth - newWidth))
            newPosY = Math.Max(0, Math.Min(newPosY, _mapHeight - newHeight))
            newWidth = Math.Min(newWidth, _mapWidth - newPosX)
            newHeight = Math.Min(newHeight, _mapHeight - newPosY)

            tile.Position = New Point(newPosX, newPosY)
            tile.Size = New Size(newWidth, newHeight)
        Next

        Refresh()
        RaiseEvent CanvasModified()
    End Sub

    Private Sub HandleDragDrop(drgevent As DragEventArgs)
        Dim image As Image = CType(drgevent.Data.GetData("ImageFormat"), Image)
        Dim imageFilePath As String = CType(drgevent.Data.GetData("ImageFilePath"), String)

        Dim originalSize As Size = image.Size

        Dim newWidth As Integer = CInt(originalSize.Width / _gridSize) * _gridSize
        Dim newHeight As Integer = CInt(originalSize.Height / _gridSize) * _gridSize

        If newWidth < _gridSize Then newWidth = _gridSize
        If newHeight < _gridSize Then newHeight = _gridSize

        Dim resizedImage As New Bitmap(image, New Size(newWidth, newHeight))

        Dim point As Point = PointToClient(New Point(drgevent.X, drgevent.Y))
        point = ClientToCanvas(point)

        If snappingEnabled AndAlso Not IsShiftKeyPressed Then
            point.X = CInt(point.X / _gridSize) * _gridSize
            point.Y = CInt(point.Y / _gridSize) * _gridSize
        End If

        Dim newTile As New MapTile With {
            .Image = resizedImage,
            .ImageFilePath = imageFilePath,
            .Position = point,
            .Size = resizedImage.Size
        }
        AddTile(newTile)
    End Sub

    Public Sub ClearTiles()
        _tiles.Clear()
        Invalidate()
    End Sub
    Private Sub MnuPathing_Click(sender As Object, e As EventArgs) Handles MnuPathing.Click
        ' Uncheck the menu item
        MnuPathing.Checked = False

        ' Get the selected tile
        Dim selectedTile As MapTile = PicCanvas.CurrentlySelectedTile

        ' Ensure selectedTile is not Nothing
        If selectedTile Is Nothing Then
            MessageBox.Show("No tile is currently selected.")
            Return
        End If

        ' Set the tile to pathable
        selectedTile.IsPathable = True

        ' Invalidate the canvas to redraw the overlay
        PicCanvas.Invalidate()
    End Sub

#End Region

End Class
Imports System.Drawing.Drawing2D
Imports System.Drawing.Imaging
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Threading

Public Class FrmWorld
    Private dragBoxFromMouseDown As Rectangle
    Private currentFileName As String = ""
    Private isModified As Boolean = False
    Private recentSearches As New List(Of String)()
    Private Const MaxSearches As Integer = 10
    Private isUpdatingComboBox As Boolean = False
    Private searchCancellationTokenSource As CancellationTokenSource
    Private Const MaxImagesToDisplay As Integer = 500
    Private ReadOnly imageExtensions() As String = {".jpg", ".jpeg", ".png", ".bmp", ".gif"}
    Private undoStack As New Stack(Of MapAction)
    Private redoStack As New Stack(Of MapAction)

    Public Sub New()
        InitializeComponent()
    End Sub
#Region " Initiate "

    Private Sub FrmWorld_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' Other initialization code...
        PopulateTreeView(GetImagesDirectory())
        PicCanvas.AllowDrop = True

        AddHandler CboDimensions.SelectedIndexChanged, AddressOf CboDimensions_SelectedIndexChanged

        CboDimensions.SelectedItem = "32 x 32"

        Dim defaultMapSize As String = "15000 x 10000"

        If Not CboMapSize.Items.Contains(defaultMapSize) Then
            Dim unused = CboMapSize.Items.Add(defaultMapSize)
        End If

        CboMapSize.SelectedItem = defaultMapSize
        LoadTreeViewState()
        LoadRecentSearches()

        ' Add debugging statement to verify initialization
    End Sub
#End Region

#Region " Populate TreeView and FlowPanel "

    Private Async Function LoadImagesAsync(directoryInfo As DirectoryInfo) As Task
        Try
            FLPImages.Controls.Clear()

            Dim imageExtensions() As String = {".jpg", ".jpeg", ".png", ".bmp", ".gif"}
            Dim imageFiles As New List(Of FileInfo)()

            For Each extension As String In imageExtensions
                imageFiles.AddRange(directoryInfo.GetFiles($"*{extension}", SearchOption.TopDirectoryOnly))
            Next

            Dim isInBossesFolder As Boolean = IsInBossesDirectory(directoryInfo)
            Dim isInPeopleFolder As Boolean = IsInPeopleDirectory(directoryInfo)

            For Each imageFile As FileInfo In imageFiles
                Try
                    Dim spriteSheetImage As Image = Await Task.Run(Function() Image.FromFile(imageFile.FullName))

                    Dim frameWidth As Integer
                    Dim frameHeight As Integer

                    If isInBossesFolder Then
                        frameWidth = 96
                        frameHeight = 96

                        Dim firstFrame As Image = ExtractFirstFrame(spriteSheetImage, frameWidth, frameHeight, frameIndex:=1)

                        spriteSheetImage.Dispose()

                        Dim displayName As String = imageFile.Name

                        Invoke(Sub()
                                   Dim imageItem As New ImageItem() With {
                               .Filename = displayName,
                               .FilePath = imageFile.FullName,
                               .Width = frameWidth + 10,
                               .Height = frameHeight + 30,
                               .Image = firstFrame,
                               .IsSpriteSheet = True,
                               .FrameWidth = frameWidth,
                               .FrameHeight = frameHeight
                           }

                                   AddHandler imageItem.ImageItemMouseDownEvent, AddressOf ImageItem_MouseDown
                                   AddHandler imageItem.ImageItemMouseMoveEvent, AddressOf ImageItem_MouseMove
                                   AddHandler imageItem.ImageItemMouseUpEvent, AddressOf ImageItem_MouseUp

                                   FLPImages.Controls.Add(imageItem)
                               End Sub)
                    ElseIf isInPeopleFolder Then
                        frameWidth = 32
                        frameHeight = 32

                        Dim firstFrame As Image = ExtractFirstFrame(spriteSheetImage, frameWidth, frameHeight)

                        spriteSheetImage.Dispose()

                        Dim displayName As String = imageFile.Name

                        Invoke(Sub()
                                   Dim imageItem As New ImageItem() With {
                               .Filename = displayName,
                               .FilePath = imageFile.FullName,
                               .Width = 100,
                               .Height = 120,
                               .Image = firstFrame,
                               .IsSpriteSheet = True,
                               .FrameWidth = frameWidth,
                               .FrameHeight = frameHeight
                           }

                                   AddHandler imageItem.ImageItemMouseDownEvent, AddressOf ImageItem_MouseDown
                                   AddHandler imageItem.ImageItemMouseMoveEvent, AddressOf ImageItem_MouseMove
                                   AddHandler imageItem.ImageItemMouseUpEvent, AddressOf ImageItem_MouseUp

                                   FLPImages.Controls.Add(imageItem)
                               End Sub)
                    Else
                        frameWidth = spriteSheetImage.Width
                        frameHeight = spriteSheetImage.Height

                        Dim firstFrame As Image = New Bitmap(spriteSheetImage)

                        spriteSheetImage.Dispose()

                        Dim displayName As String = imageFile.Name

                        Invoke(Sub()
                                   Dim imageItem As New ImageItem() With {
                               .Filename = displayName,
                               .FilePath = imageFile.FullName,
                               .Width = 100,
                               .Height = 120,
                               .Image = firstFrame,
                               .IsSpriteSheet = False,
                               .FrameWidth = frameWidth,
                               .FrameHeight = frameHeight
                           }

                                   AddHandler imageItem.ImageItemMouseDownEvent, AddressOf ImageItem_MouseDown
                                   AddHandler imageItem.ImageItemMouseMoveEvent, AddressOf ImageItem_MouseMove
                                   AddHandler imageItem.ImageItemMouseUpEvent, AddressOf ImageItem_MouseUp

                                   FLPImages.Controls.Add(imageItem)
                               End Sub)
                    End If
                Catch ex As Exception
                    Dim unused1 = MessageBox.Show("Error processing image " & imageFile.Name & ": " & ex.Message)
                End Try
            Next
        Catch ex As Exception
            Dim unused = MessageBox.Show("Error loading images: " & ex.Message)
        End Try
    End Function

    Private Function IsInBossesDirectory(directory As DirectoryInfo) As Boolean
        Dim bossesDirectoryPath As String = Path.Combine(GetImagesDirectory(), "People", "Boss")
        bossesDirectoryPath = Path.GetFullPath(bossesDirectoryPath).TrimEnd(Path.DirectorySeparatorChar).ToLowerInvariant()
        Dim currentDirectoryPath As String = directory.FullName.TrimEnd(Path.DirectorySeparatorChar).ToLowerInvariant()
        Return String.Equals(currentDirectoryPath, bossesDirectoryPath, StringComparison.OrdinalIgnoreCase)
    End Function

    Private Function IsInPeopleDirectory(directory As DirectoryInfo) As Boolean
        Dim peopleDirectoryPath As String = Path.Combine(GetImagesDirectory(), "People")
        peopleDirectoryPath = Path.GetFullPath(peopleDirectoryPath).TrimEnd(Path.DirectorySeparatorChar).ToLowerInvariant()
        Dim currentDirectoryPath As String = directory.FullName.TrimEnd(Path.DirectorySeparatorChar).ToLowerInvariant()
        Return currentDirectoryPath.StartsWith(peopleDirectoryPath, StringComparison.OrdinalIgnoreCase)
    End Function

    Private Function ExtractFirstFrame(spriteSheet As Image, frameWidth As Integer, frameHeight As Integer, Optional frameIndex As Integer = 0) As Image
        Dim framesInRow As Integer = spriteSheet.Width \ frameWidth

        If frameIndex < 0 OrElse frameIndex >= framesInRow Then
            frameIndex = 0
        End If

        Dim sourceRect As New Rectangle(frameIndex * frameWidth, 0, frameWidth, frameHeight)

        Dim firstFrame As New Bitmap(frameWidth, frameHeight, PixelFormat.Format32bppArgb)
        Using g As Graphics = Graphics.FromImage(firstFrame)
            g.DrawImage(spriteSheet, New Rectangle(0, 0, frameWidth, frameHeight), sourceRect, GraphicsUnit.Pixel)
        End Using

        Return firstFrame
    End Function

    Private Function ExtractFrameAt(spriteSheet As Image, frameWidth As Integer, frameHeight As Integer, frameIndex As Integer) As Image
        Dim framesInRow As Integer = spriteSheet.Width \ frameWidth

        If frameIndex < 0 OrElse frameIndex >= framesInRow Then
            frameIndex = 0
        End If

        Dim sourceRect As New Rectangle(frameIndex * frameWidth, 0, frameWidth, frameHeight)

        Dim frameImage As New Bitmap(frameWidth, frameHeight)
        Using g As Graphics = Graphics.FromImage(frameImage)
            g.DrawImage(spriteSheet, New Rectangle(0, 0, frameWidth, frameHeight), sourceRect, GraphicsUnit.Pixel)
        End Using

        Return frameImage
    End Function

    Private Sub PopulateTreeView(rootDirectory As String)
        TrvFiles.Nodes.Clear()
        Dim rootDirectoryInfo As New DirectoryInfo(rootDirectory)
        Dim rootNode As New TreeNode(rootDirectoryInfo.Name) With {.Tag = rootDirectoryInfo}
        Dim unused = TrvFiles.Nodes.Add(rootNode)
        AddDirectories(rootDirectoryInfo, rootNode)
    End Sub

    Private Sub AddDirectories(directoryInfo As DirectoryInfo, node As TreeNode)
        For Each directory As DirectoryInfo In directoryInfo.GetDirectories()
            Dim directoryNode As New TreeNode(directory.Name) With {.Tag = directory}
            Dim unused = node.Nodes.Add(directoryNode)
            AddDirectories(directory, directoryNode)
        Next
    End Sub

    Private Async Sub TrvFiles_AfterSelect(sender As Object, e As TreeViewEventArgs) Handles TrvFiles.AfterSelect
        FLPImages.Controls.Clear()
        Dim selectedNode As TreeNode = e.Node
        If TypeOf selectedNode.Tag Is DirectoryInfo Then
            Dim directoryInfo As DirectoryInfo = CType(selectedNode.Tag, DirectoryInfo)
            Await LoadImagesAsync(directoryInfo)
        End If
    End Sub

#End Region

#Region " Mouse Events for Dragging Images "

    Private Sub ImageItem_MouseDown(sender As Object, e As MouseEventArgs)
        If e.Button = MouseButtons.Left Then
            Dim dragSize As Size = SystemInformation.DragSize
            dragBoxFromMouseDown = New Rectangle(New Point(e.X - (dragSize.Width \ 2),
                                                       e.Y - (dragSize.Height \ 2)),
                                             dragSize)
        Else
            dragBoxFromMouseDown = Rectangle.Empty
        End If
    End Sub

    Private Sub ImageItem_MouseMove(sender As Object, e As MouseEventArgs)
        If (e.Button And MouseButtons.Left) = MouseButtons.Left Then
            If dragBoxFromMouseDown <> Rectangle.Empty AndAlso Not dragBoxFromMouseDown.Contains(e.Location) Then
                Dim imageItem As ImageItem = CType(sender, ImageItem)
                Dim image As Image = imageItem.Image

                If image IsNot Nothing AndAlso image.Width > 0 AndAlso image.Height > 0 Then
                    Try
                        Dim cursor As Cursor = CreateCursorFromImage(image)
                        Cursor.Current = cursor

                        Dim dataObj As New DataObject()
                        dataObj.SetData(GetType(Bitmap), image)
                        Dim imageFilePath As String = imageItem.FilePath
                        dataObj.SetData("ImageFilePath", imageFilePath)
                        dataObj.SetData("IsSpriteSheet", imageItem.IsSpriteSheet)

                        dataObj.SetData("FrameWidth", imageItem.FrameWidth)
                        dataObj.SetData("FrameHeight", imageItem.FrameHeight)

                        Dim unused2 = imageItem.DoDragDrop(dataObj, DragDropEffects.Copy)
                        Cursor.Current = Cursors.Default

                        cursor.Dispose()
                    Catch ex As Exception
                        Dim unused1 = MessageBox.Show("Error creating cursor: " & ex.Message)
                        Return
                    End Try
                Else
                    Dim unused = MessageBox.Show("Error: The image is invalid or has been disposed.")
                    Return
                End If
                dragBoxFromMouseDown = Rectangle.Empty
            End If
        End If
    End Sub

    Private Sub ImageItem_MouseUp(sender As Object, e As MouseEventArgs)
        dragBoxFromMouseDown = Rectangle.Empty
    End Sub

    Private Function CreateCursorFromImage(image As Image) As Cursor
        Try
            If image Is Nothing Then
                Throw New ArgumentNullException(NameOf(image), "Image cannot be null.")
            End If

            If image.Width = 0 OrElse image.Height = 0 Then
                Throw New ArgumentException("Image dimensions are invalid.")
            End If

            Dim cursorSize As Integer = 32
            Using bitmap As New Bitmap(cursorSize, cursorSize, PixelFormat.Format32bppArgb)
                Using g As Graphics = Graphics.FromImage(bitmap)
                    g.InterpolationMode = InterpolationMode.HighQualityBicubic
                    g.DrawImage(image, New Rectangle(0, 0, cursorSize, cursorSize))
                End Using

                Dim iconHandle As IntPtr = bitmap.GetHicon()

                Dim cursor As New Cursor(iconHandle)

                Dim unused1 = DestroyIcon(iconHandle)

                Return cursor
            End Using
        Catch ex As Exception
            Dim unused = MessageBox.Show("Error creating cursor: " & ex.Message)
            Return Cursors.Default
        End Try
    End Function

    <DllImport("user32.dll", CharSet:=CharSet.Auto)>
    Private Shared Function DestroyIcon(ByVal hIcon As IntPtr) As Boolean
    End Function

    Private Sub CanvasModified_Handler() Handles PicCanvas.CanvasModified
        isModified = True
    End Sub

#End Region

#Region " ToolStrip "

    Private Sub CboDimensions_SelectedIndexChanged(sender As Object, e As EventArgs)
        Dim selectedText As String = CboDimensions.SelectedItem.ToString()
        Dim dimensions() As String = selectedText.Split("x"c)
        If dimensions.Length = 2 Then
            Dim size As Integer
            If Integer.TryParse(dimensions(0).Trim(), size) Then
                PicCanvas.GridSize = size
                PicCanvas.SnapAllTilesToGrid()
            End If
        End If
    End Sub

    Private Sub CboMapSize_SelectedIndexChanged(sender As Object, e As EventArgs) Handles CboMapSize.SelectedIndexChanged
        Dim selectedText As String = CboMapSize.SelectedItem.ToString()
        Dim dimensions() As String = selectedText.Split("x"c)
        If dimensions.Length = 2 Then
            Dim width As Integer
            Dim height As Integer
            If Integer.TryParse(dimensions(0).Trim(), width) AndAlso Integer.TryParse(dimensions(1).Trim(), height) Then
                PicCanvas.SetMapSize(width, height)
            End If
        End If
    End Sub

    Private Sub MnuNew_Click(sender As Object, e As EventArgs) Handles MnuNew.Click
        If isModified Then
            Dim result = MessageBox.Show("Do you want to save changes?", "Save Changes", MessageBoxButtons.YesNoCancel)
            If result = DialogResult.Yes Then
                MnuSave_Click(sender, e)
            ElseIf result = DialogResult.Cancel Then
                Return
            End If
        End If

        PicCanvas.ClearTiles()
        currentFileName = ""
        isModified = False
    End Sub

    Private Sub MnuExit_Click(sender As Object, e As EventArgs) Handles MnuExit.Click
        If isModified Then
            Dim result = MessageBox.Show("Do you want to save changes?", "Save Changes", MessageBoxButtons.YesNoCancel)
            If result = DialogResult.Yes Then
                MnuSave_Click(sender, e)
            ElseIf result = DialogResult.Cancel Then
                Return
            End If
        End If
        Close()
    End Sub

#End Region

    Private Sub TrvFiles_NodeMouseClick(sender As Object, e As TreeNodeMouseClickEventArgs) Handles TrvFiles.NodeMouseClick
        If e.Button = MouseButtons.Right Then
            TrvFiles.SelectedNode = e.Node
        End If
    End Sub

    Private Sub MnuOpenInExplorer_Click(sender As Object, e As EventArgs) Handles MnuOpenInExplorer.Click
        Dim selectedNode As TreeNode = TrvFiles.SelectedNode
        If selectedNode IsNot Nothing AndAlso TypeOf selectedNode.Tag Is DirectoryInfo Then
            Dim directoryInfo As DirectoryInfo = CType(selectedNode.Tag, DirectoryInfo)
            If directoryInfo.Exists Then
                Try
                    Dim unused3 = Process.Start("explorer.exe", directoryInfo.FullName)
                Catch ex As Exception
                    Dim unused2 = MessageBox.Show("Failed to open folder: " & ex.Message)
                End Try
            Else
                Dim unused1 = MessageBox.Show("Directory does not exist.")
            End If
        Else
            Dim unused = MessageBox.Show("No directory selected.")
        End If
    End Sub

    Private Sub PicCanvas_MouseMove(sender As Object, e As MouseEventArgs) Handles PicCanvas.MouseMove
        Dim canvasPoint As Point = PicCanvas.ClientToCanvas(e.Location)
        STBMouseCoords.Text = $"X: {canvasPoint.X}, Y: {canvasPoint.Y}"
    End Sub

    Private Sub PicCanvas_TileSelected(selectedTile As MapTile) Handles PicCanvas.TileSelected
        PicCanvas.CurrentlySelectedTile = selectedTile
        STBSize.Text = If(selectedTile IsNot Nothing,
    $"Original Size {selectedTile.OriginalSize.Width}, {selectedTile.OriginalSize.Height}: Current Size {selectedTile.Size.Width}, {selectedTile.Size.Height}",
    "")
        UpdateSTBSize(selectedTile)
    End Sub

    Private Sub PicCanvas_TileSizeChanged(selectedTile As MapTile) Handles PicCanvas.TileSizeChanged
        UpdateSTBSize(selectedTile)
        AddAction(New MoveTileAction(selectedTile, PicCanvas, selectedTile.Position, selectedTile.Position))
    End Sub

    Private Sub UpdateSTBSize(selectedTile As MapTile)
        STBSize.Text = If(selectedTile IsNot Nothing,
            $"Original Size {selectedTile.OriginalSize.Width}, {selectedTile.OriginalSize.Height}: Current Size {selectedTile.Size.Width}, {selectedTile.Size.Height}",
            "")
    End Sub

    Public Function GetImagesDirectory() As String
        Dim startupPath As String = Application.StartupPath
        Dim dirInfo As New DirectoryInfo(startupPath)

        While dirInfo IsNot Nothing AndAlso dirInfo.Name <> "Bizarria New"
            dirInfo = dirInfo.Parent
        End While

        If dirInfo Is Nothing Then
            Throw New DirectoryNotFoundException("Could not find the 'Bizarria New' directory in the path hierarchy.")
        End If

        Dim imagesDir As String = Path.Combine(dirInfo.FullName, "Data", "Decor")

        If Not Directory.Exists(imagesDir) Then
            Throw New DirectoryNotFoundException($"The images directory does not exist: {imagesDir}")
        End If

        Return imagesDir
    End Function
    Private Sub ChkShowPathing_CheckedChanged(sender As Object, e As EventArgs) Handles ChkShowPathing.CheckedChanged
        PicCanvas.ShowPathingOverlay = ChkShowPathing.Checked
    End Sub
End Class


System.NullReferenceException
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=Bizarria
  StackTrace:
   at Bizarria.MapCanvas.MnuPathing_Click(Object sender, EventArgs e) in D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\MapCanvas\MapCanvas.vb:line 1561



Here are all the mention of picCanvas throughout my project...

Code	File	Line	Column
        ' PicCanvas	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.Designer.vb	331	11
    Friend WithEvents PicCanvas As MapCanvas	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.Designer.vb	452	23
        PicCanvas = New MapCanvas()	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.Designer.vb	65	9
        PicCanvas.AutoScroll = True	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.Designer.vb	333	9
        PicCanvas.AutoScrollMinSize = New Size(10000, 10000)	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.Designer.vb	334	9
        PicCanvas.CurrentlySelectedTile = Nothing	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.Designer.vb	335	9
        PicCanvas.Dock = DockStyle.Fill	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.Designer.vb	336	9
        PicCanvas.GridSize = 16	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.Designer.vb	337	9
        PicCanvas.Location = New Point(0, 0)	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.Designer.vb	338	9
        PicCanvas.Name = "PicCanvas"	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.Designer.vb	339	9
        PicCanvas.ShowGrid = True	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.Designer.vb	340	9
        PicCanvas.Size = New Size(1391, 678)	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.Designer.vb	341	9
        PicCanvas.TabIndex = 0	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.Designer.vb	342	9
        PicCanvas.ZoomLevel = 1F	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.Designer.vb	343	9
        SplitMain.Panel1.Controls.Add(PicCanvas)	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.Designer.vb	400	39
        AddAction(New MoveTileAction(selectedTile, PicCanvas, selectedTile.Position, selectedTile.Position))	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.vb	429	52
        Dim canvasPoint As Point = PicCanvas.ClientToCanvas(e.Location)	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.vb	415	36
        PicCanvas.AllowDrop = True	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.vb	28	9
        PicCanvas.ClearTiles()	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.vb	371	9
        PicCanvas.CurrentlySelectedTile = selectedTile	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.vb	420	9
                PicCanvas.GridSize = size	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.vb	343	17
                PicCanvas.SetMapSize(width, height)	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.vb	356	17
        PicCanvas.ShowPathingOverlay = ChkShowPathing.Checked	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.vb	459	9
                PicCanvas.SnapAllTilesToGrid()	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.vb	344	17
    Private Sub CanvasModified_Handler() Handles PicCanvas.CanvasModified	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.vb	329	50
    Private Sub PicCanvas_MouseMove(sender As Object, e As MouseEventArgs) Handles PicCanvas.MouseMove	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.vb	414	17
    Private Sub PicCanvas_TileSelected(selectedTile As MapTile) Handles PicCanvas.TileSelected	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.vb	419	17
    Private Sub PicCanvas_TileSizeChanged(selectedTile As MapTile) Handles PicCanvas.TileSizeChanged	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\frmWorld.vb	427	17
        .GridSize = PicCanvas.GridSize,	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\Part_SaveLoad.vb	9	21
        .MapSize = PicCanvas.MapSize,	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\Part_SaveLoad.vb	10	20
        For Each tile As MapTile In PicCanvas.Tiles	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\Part_SaveLoad.vb	14	37
                        PicCanvas.AddTile(mapTile)	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\Part_SaveLoad.vb	123	25
                        PicCanvas.AddTile(mapTile)	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\Part_SaveLoad.vb	158	25
                        PicCanvas.AddTile(mapTile)	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\Part_SaveLoad.vb	200	25
                        PicCanvas.AddTile(mapTile)	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\Part_SaveLoad.vb	238	25
            PicCanvas.ClearTiles()	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\Part_SaveLoad.vb	66	13
            PicCanvas.GridSize = canvasData.GridSize	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\Part_SaveLoad.vb	68	13
            PicCanvas.SetMapSize(canvasData.MapSize.Width, canvasData.MapSize.Height)	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\Part_SaveLoad.vb	69	13
        PicCanvas.Redo()	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\Part_UndoRedo.vb	8	9
        PicCanvas.Undo()	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\Part_UndoRedo.vb	4	9
        PicCanvas.ZoomLevel = zoomLevel	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\Part_Zoom.vb	28	9
        PicCanvas.ZoomToFit()	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\Part_Zoom.vb	32	9
    Private Sub PicCanvas_ZoomLevelChanged(zoomLevel As Single) Handles PicCanvas.ZoomLevelChanged	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\FrmWorld\Part_Zoom.vb	3	17
        Dim selectedTile As MapTile = PicCanvas.CurrentlySelectedTile	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\MapCanvas\MapCanvas.vb	1561	39
                If PicCanvas IsNot Nothing Then	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\MapCanvas\MapCanvas.vb	379	20
                    PicCanvas.CurrentlySelectedTile = clickedTile ' Ensure the selected tile is set	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\MapCanvas\MapCanvas.vb	380	21
        PicCanvas.Invalidate()	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\MapCanvas\MapCanvas.vb	1573	9
    Private PicCanvas As MapCanvas	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\MapCanvas\MapCanvas.vb	18	13
                AddAction(New MoveTileAction(tile, PicCanvas, oldPosition, tile.Position))	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\MapCanvas\Part_CTMMenu.vb	151	52
                AddAction(New RemoveTileAction(tile, PicCanvas))	D:\01 - Grags\Visual Studio\Bizarria New\Bizarria\MapCanvas\Part_CTMMenu.vb	169	54