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.