Introduction
This tip will provide you with the code on how to accurately retain the location of a point of interest (area) on an image while zooming in(+) or out(-). Think of Google Maps to get an idea of where the tip is going.
Background
I am writing an application which uses images extensively and one of the requirements I have was to allow "notes" to be placed on the image as an overlay. The notes cannot be saved onto the image because the images may not be altered in any way. I started off by capturing and saving various pieces of data to a MySQL database and then read it back into a DataGridView
for use later in the application.
The information I recorded included: Mouse.X, Mouse.Y
, Zoom Factor, Horizontal and Vertical Scroll
position of the Panel
control in which my Picturebox
control was placed to allow for panning. I also recorded the width and height of the image at the time the note was placed over the image. I would then draw triangle markers over the areas of interest from the information in the DataGridView
for each note.
To maintain accuracy during the process of adding or viewing a note, I had to lock the zoom factor at 100%. This meant that the user was locked into a preset zoom factor and had no means of zooming in or out of the image while having accurate plotting of the notes when the zoom factor was at anything other than 100%.
I started to look around for more information on how to accurately plot a predetermined area of an image while zooming in or out - very much the same you get to see when using Google Maps and place markers on areas of interest on the globe. Sadly, I came up empty handed.
The following code will demonstrate how easy it is to accurately plot a specific area of an image while zooming in or out. Please keep in mind that the code, as it is, is not optimized for smooth plotting of the markers over the image. Suggestions from the community will be greatly appreciated to rectify this little problem.
Using the Code
Unfortunately, the code and the control I use for Zooming and Panning, and adding Notes to images are part of a much larger application and it will take a lot of hours to strip it out of the main application and put some source code up for you to look at.
However, I will provide you with code in order for you to achieve exactly the same result in your applications.
When I first started researching this, I received one reply to a question I posted regarding this topic.
A reply was posted which suggested I look at the Right
and Bottom
properties of the Picturebox
, calculate the change and then apply the result on the marker to relocate them accordingly. Well needless to say, that did not work at all.
Being unfamiliar to the behaviour of a Picturebox
and what happens when it becomes bigger or smaller according to either a larger image being loaded or it being zoom'ed. I noted anything and everything that could change when an image is zoomed in/out. I even recorded how the position of a certain location changed across the screen to try and find some trace of over all consistency to changes.
I tested image aspect ration - always remained 1.3333.
I tested the aspect ration of the Picturebox
control. That was 2.7 for Width
and 2.6 for Height
.
I then tested the distance, a specific location on an image, it changes by every time the image was Zoom'ed in / out. This is where things started to become clearer, however there were significant changes when the image was either Zoom'ed greater than the original Zoom factor and then when the Zoom factor was smaller than the original Zoom factor.
In the end, I determined that a specific point on an image can only be accurately recalculated based on the New Zoom Factor (+ or -). This means that you can accurately plot the location of a specific point on an image no matter what the current Zoom factor is, as long as you have the original Zoom factor at which you recorded / plotted the location on the image.
Here is the code to provide you with the calculation for accurately plotting the location of an area on an image based on the current Zoom factor.
Note: I set .Visible=False
on the UserControl
that I use as the "marker" of the Note on the image everytime the Zoom factor changes. This prevents the markers (Notes) from jumping around while calculations are conducted. There is probably a better way to do it but for now this has to do.
Some clarification to understand the code better:
-
tbImageZoom
= TrackBar
dgvNotes
= DataGridView
- Original Zoom Factor =
dgvNotes.Rows(i).Cells(8).Value
pbImage
= Picturebox
Zoom(ZoomFactor)
= Function
Private Sub tbImageZoom_Scroll(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles tbImageZoom.Scroll, tbImageZoom.ValueChanged
If IsNothing(pbImage.Image) Then
tbImageZoom.Value = 0
Return
End If
If tbImageZoom.Value > tbImageZoom.Maximum Then Return
Dim ZoomFactor As Double
For Each Pin As Control In pbImage.Controls
If TypeOf Pin Is UserControl Then
Pin.Visible = False
End If
Next
pbImage.Refresh()
ZoomFactor = tbImageZoom.Value / 100
Zoom(ZoomFactor)
lblZoomFactor.Text = tbImageZoom.Value & "%"
tbImageZoom.Update()
For Each ctrl As Control In pbImage.Controls
If TypeOf (ctrl) Is UserControl Then
For i = 0 To dgvNotes.Rows.Count - 1
If dgvNotes.Rows(i).Cells(0).Value = ctrl.Tag.ToString Then
If Val(dgvNotes.Rows(i).Cells(8).Value) < Val(tbImageZoom.Value) Then
Dim zoomRation As Double = Val(tbImageZoom.Value) / Val(dgvNotes.Rows(i).Cells(8).Value)
ctrl.Location = New Point(CInt(Val(dgvNotes.Rows(i).Cells(4).Value) * _
zoomRation) - 14, CInt(Val(dgvNotes.Rows(i).Cells(5).Value) * zoomRation) - 24)
ElseIf Val(dgvNotes.Rows(i).Cells(8).Value) > Val(tbImageZoom.Value) Then
Dim zoomRation As Double = Val(dgvNotes.Rows(i).Cells(8).Value) / Val(tbImageZoom.Value)
ctrl.Location = New Point(CInt(Val(dgvNotes.Rows(i).Cells(4).Value) / _
zoomRation) - 14, CInt(Val(dgvNotes.Rows(i).Cells(5).Value) / zoomRation) - 24)
Else
ctrl.Location = New Point(Val(dgvNotes.Rows(i).Cells(4).Value) - 14, _
Val(dgvNotes.Rows(i).Cells(5).Value) - 24)
End If
End If
Next i
End If
ctrl.Visible = True
Next ctrl
End Sub
You will notice that if the Current Zoom Factor is great than the Original Zoom Factor we Multiply (+). When the Current Zoom Factor is smaller than the Original Zoom Factor, we Divide (-).
Zoom Function - for the purpose of clarity
Private Sub Zoom(ByVal Factor As Double)
Try
Dim sourceBitmap As New Bitmap(currentImage)
myScreen = Screen.AllScreens(0)
If Factor < 0.01 Then Return
Dim destinationBitmap As New Bitmap(CInt(sourceBitmap.Width * Factor), _
CInt(sourceBitmap.Height * Factor))
Dim destinationGraphic As Graphics = Graphics.FromImage(destinationBitmap)
destinationGraphic.DrawImage(sourceBitmap, 0, 0, destinationBitmap.Width + 1, _
destinationBitmap.Height + 1)
screenImageWidth = destinationBitmap.Width
screenImageHeight = destinationBitmap.Height
pbImage.Image = destinationBitmap
destinationGraphic.Dispose()
GC.Collect()
Catch ex As Exception
End Try
End Sub
Here are a few images to demonstrate the accurate calculations to replotting on an image.
Fig 1.0 - Note is added at 101% zoom factor
Fig 1.1 - Shows another marker (Note) being placed on the image
Fig 1.3 - Shows the image Zoom'ed in to 50% while the markers (Notes) with their position over the original location on the image.
Fig 1.4 - Zoom factor at 74% and markers retain their accurate plot over the image
Finally Fig 1.5 shows markers accurately plotted at a zoom factor of 104%. Even with the smallest offset, the plot is 100% accurate.
Points of Interest
In this project, I learned to keep bashing my head, the wall will give in at some point.
History
- 23rd September, 2014: Initial version