Visual Basic 2010 (Windows) Guide
Fractals: Zoom Feature

The RubberBand Class

One of the most natural ways to enable the user to select a portion of the image to zoom in on is to use a reversible selection rectangle rather like you might find in a graphics application. This is sometimes called a rubberband selection tool.

The bulk of the programming logic can be placed in a separate class. That allows you to export and reuse the class in another application.

Add a class to your project called RubberBand.vb.

The code for this class is as follows,

Class Rubberband

   Public Enum RubberBandState
      Inactive
      Starting
      Moving
   End Enum

   Private StartPoint As Point
   Private EndPoint As Point
   Private CurrentState As RubberBandState = RubberBandState.Inactive
   Private Surface As Control

   Public Sub New(ByVal ControlSurface As Control)
      Surface = ControlSurface
   End Sub

   Public ReadOnly Property SelectedRectangle() As Rectangle
      Get
         Dim selectedRect As New Rectangle()
         selectedRect.X = If(StartPoint.X < EndPoint.X, StartPoint.X, EndPoint.X)
         selectedRect.Y = If(StartPoint.Y < EndPoint.Y, StartPoint.Y, EndPoint.Y)
         selectedRect.Width = Math.Abs(EndPoint.X - StartPoint.X)
         selectedRect.Height = Math.Abs(EndPoint.Y - StartPoint.Y)
         Return selectedRect
      End Get
   End Property

   Public Sub Start(ByVal x As Integer, ByVal y As Integer)
      StartPoint.X = x
      StartPoint.Y = y
      EndPoint.X = x
      EndPoint.Y = y
      KeepInView(StartPoint)
      CurrentState = RubberBandState.Starting
   End Sub

   Public Sub [Stop]()
      DrawFrame()
      CurrentState = RubberBandState.Inactive
   End Sub

   Private Sub KeepInView(ByRef origin As Point)
      If origin.X < 0 Then
         origin.X = 0
      End If
      If origin.X > Surface.ClientSize.Width Then
         origin.X = Surface.ClientSize.Width - 1
      End If
      If origin.Y < 0 Then
         origin.Y = 0
      End If
      If origin.Y > Surface.ClientSize.Height Then
         origin.Y = Surface.ClientSize.Height - 1
      End If
   End Sub

   Public Sub Move(ByVal x As Integer, ByVal y As Integer)
      Dim newPoint As New Point(x, y)
      KeepInView(newPoint)
      Select Case CurrentState
         Case RubberBandState.Inactive
         Exit Select
      Case RubberBandState.Starting
         EndPoint = newPoint
         DrawFrame()
         CurrentState = RubberBandState.Moving
         Exit Select
      Case RubberBandState.Moving
         DrawFrame()
         EndPoint = newPoint
         DrawFrame()
         Exit Select
      End Select
   End Sub

   Private Sub DrawFrame()
      Dim exactStart As Point = Surface.PointToScreen(StartPoint)
      Dim exactEnd As Point = Surface.PointToScreen(EndPoint)
      Dim rectSize As New Size(exactEnd.X - exactStart.X, exactEnd.Y - exactStart.Y)
      Dim drawRect As New Rectangle(Surface.PointToScreen(StartPoint), rectSize)
      ControlPaint.DrawReversibleFrame(drawRect, Color.Black, FrameStyle.Dashed)
   End Sub

End Class

Using The RubberBand Class

We have a few things to do to implement the zoom feature.

Start by adding the following global variables to the form's code window.

Dim IsDrawing As Boolean = False
Dim IsRubberBand As Boolean = False
Dim SelectedArea As Rubberband

Create a Form_Load event for the form and add the following lines of code.

SelectedArea = New Rubberband(picMandel)
CreateMandelbrotImage()

This creates an instance of the RubberBand class and plots the Mandelbrot image when the form is first loaded.

Adapt the CreateMandelbrotImage() procedure so that the first and last lines of the procedure are,

IsDrawing = true

and

IsDrawing = false

The MouseDown event for the picture box should read as follows,

Private Sub picMandel_MouseDown(sender As Object, e As MouseEventArgs)
   If Not IsDrawing Then
      SelectedArea.Start(e.X, e.Y)
      IsRubberBand = True
   End If
End Sub

The MouseMove event should be adapted as follows,

Private Sub picMandel_MouseMove(sender As Object, e As MouseEventArgs)
   If Not IsDrawing Then
      Dim a As String = System.Convert.ToString((e.X * unitsPerPixel) + minA)
      Dim b As String = System.Convert.ToString(((bmpMandel.Height - e.Y) * unitsPerPixel) + minB)
      Dim coords As String = "(" + a + ", " + b + ")"
      toolStripStatusLabel1.Text = coords
      If IsRubberBand Then
         SelectedArea.Move(e.X, e.Y)
      End If
   End If
End Sub

Before we write the MouseUp event which will end the selection, we need to think about how we adjust the rectangular shape that the user selects into a square. To do this we make adjust the rectangle into a square based on the longest side of the selection. We use the centre of the selected area to determine the positioning of the square.

Private Sub CreateNewView()
   Dim newRect As New Rectangle()
   Dim centrePoint As New Point(SelectedArea.SelectedRectangle.X + (SelectedArea.SelectedRectangle.Width / 2), SelectedArea.SelectedRectangle.Y + (SelectedArea.SelectedRectangle.Height / 2))
   'make into square the length of longest side
   If SelectedArea.SelectedRectangle.Width > SelectedArea.SelectedRectangle.Height Then
      newRect.Width = SelectedArea.SelectedRectangle.Width
      newRect.Height = newRect.Width
   Else
      newRect.Height = SelectedArea.SelectedRectangle.Width
      newRect.Width = newRect.Height
   End If
   'shift so that the square is centred on the centre of the rectangle
   Dim newOrigin As New Point()
   newOrigin.X = centrePoint.X - (newRect.Width / 2)
   newOrigin.Y = centrePoint.Y - (newRect.Height / 2)
   newRect.Location = newOrigin
   'convert into plot values
   minA = minA + (newRect.X * unitsPerPixel)
   maxA = minA + (newRect.Width * unitsPerPixel)
   maxB = maxB - (newRect.Y * unitsPerPixel)
   minB = maxB - (newRect.Height * unitsPerPixel)
End Sub

Finally, add a MouseUp event for the picture box.

Private Sub picMandel_MouseUp(sender As Object, e As MouseEventArgs)
   If IsRubberBand Then
      If Not IsDrawing Then
         SelectedArea.[Stop]()
         IsRubberBand = False
         CreateNewView()
         CreateMandelbrotImage()
      End If
   End If
End Sub

You need to do some testing now. You should find that you can zoom in quite nicely on different sections of the image.

Improvements

The image you see does depend in some part on the number of iterations allowed in the application. Creating a simple dialog box to allow the user (and you) to change the maximum number of iterations would be quite useful. Be careful though, the more iterations allowed, the longer it takes to create the image. You can, however, get some interesting results by varying this number.