This is my first CodeBank entry, I hope it's useful.
Here is the code for an ImageMap control that inherits from Panel. You can assign an image to be displayed to the BackgroundImage property, and an image with a color map to the ColorMap property. When you have done this, on top of the normal MouseMove and MouseClick events, the control will also raise the ImageMouseMove and ImageClicked events that return the color of the color map at the location indicated by the mouse as an additional parameter.
The ImageMap control also supports zooming by scrolling the mouse wheel. The properties LinearZoom (a fixed percentage instead of the default increasing percentage) and ZoomSpeed control the zooming. It also has a property called ZoomFactor to set it directly.
Finally the image can be dragged in any direction to scroll to another part of the image.
I may try to develop it further to support placing tokens on the map, so it can be used for a board game or something like that.
Code:
Public Class ImageMap
Inherits Panel
Private _BaseImage As Image
Private _ColorMap As Bitmap
Private _ZoomFactor As Double = 1
Private _LinearZoom As Boolean = False
Private _ZoomSpeed As Integer = 20
Private _MinZoomFactor As Double
Private _TopLeft As New Point(0, 0)
Private _CurrPos As New Point(0, 0)
Private _Dragging As Boolean = False
Private _DragStart As Point
Private _TopLeftDrag As Point
Public Sub New()
Me.DoubleBuffered = True
End Sub
Public Sub New(ByVal BackgroundImage As Image, ByVal ColorMap As Bitmap)
Me.DoubleBuffered = True
Me.BackgroundImage = BackgroundImage
Me.ColorMap = ColorMap
End Sub
Public Shadows Property BackgroundImage As Image
Get
Return _BaseImage
End Get
Set(ByVal value As Image)
_BaseImage = value
SetMinZoomFactor()
Me.Invalidate()
End Set
End Property
Public Property ColorMap As Bitmap
Get
Return _ColorMap
End Get
Set(ByVal value As Bitmap)
_ColorMap = value
End Set
End Property
Public Property ZoomFactor As Double
Get
Return _ZoomFactor
End Get
Set(ByVal value As Double)
value = Math.Round(value, 2)
If value < _MinZoomFactor Then value = _MinZoomFactor
_TopLeft.X = CInt(Math.Round(_TopLeft.X + _CurrPos.X / _ZoomFactor - _CurrPos.X / value))
_TopLeft.Y = CInt(Math.Round(_TopLeft.Y + _CurrPos.Y / _ZoomFactor - _CurrPos.Y / value))
ValidateTopLeft()
If _ZoomFactor <> value Then
_ZoomFactor = value
Me.Invalidate()
End If
End Set
End Property
Public Property LinearZoom As Boolean
Get
Return _LinearZoom
End Get
Set(ByVal value As Boolean)
_LinearZoom = value
End Set
End Property
Public Property ZoomSpeed As Integer
Get
Return _ZoomSpeed
End Get
Set(ByVal value As Integer)
_ZoomSpeed = value
End Set
End Property
Public Event ImageClicked(ByVal sender As Object, ByVal e As MouseEventArgs, ByVal ReturnedColor As Color)
Protected Overrides Sub OnMouseClick(ByVal e As System.Windows.Forms.MouseEventArgs)
MyBase.OnMouseClick(e)
Dim Pnt As Point = FindRealPoint(New Point(e.X, e.Y))
If Not _ColorMap Is Nothing AndAlso Pnt.X < _ColorMap.Width AndAlso Pnt.Y < _ColorMap.Height Then
RaiseEvent ImageClicked(Me, e, _ColorMap.GetPixel(Pnt.X, Pnt.Y))
Else
RaiseEvent ImageClicked(Me, e, Nothing)
End If
End Sub
Public Event ImageMouseMove(ByVal sender As Object, ByVal e As MouseEventArgs, ByVal ReturnedColor As Color)
Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)
If _Dragging Then
_TopLeft.X = _TopLeftDrag.X - CInt((e.X - _DragStart.X) / _ZoomFactor)
_TopLeft.Y = _TopLeftDrag.Y - CInt((e.Y - _DragStart.Y) / _ZoomFactor)
ValidateTopLeft()
Me.Invalidate()
Else
_CurrPos.X = e.X
_CurrPos.Y = e.Y
MyBase.OnMouseMove(e)
Dim Pnt As Point = FindRealPoint(New Point(e.X, e.Y))
If Not _ColorMap Is Nothing AndAlso Pnt.X > -1 AndAlso Pnt.X < _ColorMap.Width AndAlso Pnt.Y > -1 AndAlso Pnt.Y < _ColorMap.Height Then
RaiseEvent ImageMouseMove(Me, e, _ColorMap.GetPixel(Pnt.X, Pnt.Y))
Else
RaiseEvent ImageMouseMove(Me, e, Nothing)
End If
End If
End Sub
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
Dim dstRect, srcRect As Rectangle
srcRect = New Rectangle(_TopLeft.X, _TopLeft.Y, CInt(Me.Width / _ZoomFactor), CInt(Me.Height / _ZoomFactor))
dstRect = New Rectangle(New Point(0, 0), Me.Size)
e.Graphics.DrawImage(_BaseImage, dstRect, srcRect, GraphicsUnit.Pixel)
End Sub
Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
MyBase.OnMouseDown(e)
_Dragging = True
_DragStart = New Point(e.X, e.Y)
_TopLeftDrag = _TopLeft
End Sub
Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Forms.MouseEventArgs)
MyBase.OnMouseUp(e)
_Dragging = False
_DragStart = Nothing
_TopLeftDrag = Nothing
End Sub
Protected Overrides Sub OnMouseWheel(ByVal e As System.Windows.Forms.MouseEventArgs)
MyBase.OnMouseWheel(e)
Select Case _LinearZoom
Case True
ZoomFactor = _ZoomFactor + (e.Delta / 120 * _ZoomSpeed / 100)
Case False
ZoomFactor = _ZoomFactor + (e.Delta / 120 * Math.Floor(_ZoomFactor + 1) * _ZoomSpeed / 400)
End Select
End Sub
Protected Overrides Sub OnResize(ByVal e As System.EventArgs)
MyBase.OnResize(e)
SetMinZoomFactor()
ValidateTopLeft()
Me.Invalidate()
End Sub
Protected Overrides Sub OnMouseEnter(ByVal e As System.EventArgs)
MyBase.Select()
MyBase.OnMouseEnter(e)
End Sub
Private Function FindRealPoint(ByVal Pnt As Point) As Point
Dim Pnt2 As New Point
Pnt2.X = CInt(_TopLeft.X + Pnt.X / _ZoomFactor)
Pnt2.Y = CInt(_TopLeft.Y + Pnt.Y / _ZoomFactor)
Return Pnt2
End Function
Private Function IsOnScreen(ByVal Pnt As Point) As Boolean
If _ZoomFactor > 1 Then
Dim OnScreen As New Rectangle(_TopLeft, New Size(CInt(Me.Width / _ZoomFactor), CInt(Me.Height / _ZoomFactor)))
Return OnScreen.Contains(Pnt)
Else
Return True
End If
End Function
Private Sub SetMinZoomFactor()
_MinZoomFactor = Math.Round(Math.Min(Me.Width / _BaseImage.Width, Me.Height / _BaseImage.Height), 2)
If _ZoomFactor < _MinZoomFactor Then ZoomFactor = _MinZoomFactor
End Sub
Private Sub ValidateTopLeft()
If Me.Width / _ZoomFactor >= _BaseImage.Width Then
_TopLeft.X = Math.Max(_TopLeft.X, CInt(_BaseImage.Width - Me.Width / _ZoomFactor))
_TopLeft.X = Math.Min(_TopLeft.X, 0)
Else
_TopLeft.X = Math.Max(_TopLeft.X, 0)
_TopLeft.X = Math.Min(_TopLeft.X, CInt(_BaseImage.Width - Me.Width / _ZoomFactor))
End If
If Me.Height / _ZoomFactor > _BaseImage.Height Then
_TopLeft.Y = Math.Max(_TopLeft.Y, CInt(_BaseImage.Height - Me.Height / _ZoomFactor))
_TopLeft.Y = Math.Min(_TopLeft.Y, 0)
Else
_TopLeft.Y = Math.Max(_TopLeft.Y, 0)
_TopLeft.Y = Math.Min(_TopLeft.Y, CInt(_BaseImage.Height - Me.Height / _ZoomFactor))
End If
End Sub
End Class
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
Dim dstRect, srcRect As Rectangle
srcRect = New Rectangle(_TopLeft.X, _TopLeft.Y, CInt(Me.Width / _ZoomFactor), CInt(Me.Height / _ZoomFactor))
dstRect = New Rectangle(New Point(0, 0), Me.Size)
If Me.BackgroundImage IsNot Nothing Then 'add this
e.Graphics.DrawImage(_BaseImage, dstRect, srcRect, GraphicsUnit.Pixel)
End If 'and this
End Sub
and
Code:
Protected Overrides Sub OnResize(ByVal e As System.EventArgs)
MyBase.OnResize(e)
If Me.BackgroundImage IsNot Nothing Then 'add this
SetMinZoomFactor()
ValidateTopLeft()
Me.Invalidate()
End If 'add this
End Sub
and
Code:
Public Property ZoomFactor As Double
Get
Return _ZoomFactor
End Get
Set(ByVal value As Double)
value = Math.Round(value, 2)
If value < _MinZoomFactor Then value = _MinZoomFactor
_TopLeft.X = CInt(Math.Round(_TopLeft.X + _CurrPos.X / _ZoomFactor - _CurrPos.X / value))
_TopLeft.Y = CInt(Math.Round(_TopLeft.Y + _CurrPos.Y / _ZoomFactor - _CurrPos.Y / value))
If Me.BackgroundImage IsNot Nothing Then 'add this
ValidateTopLeft()
End If 'add this
If _ZoomFactor <> value Then
_ZoomFactor = value
Me.Invalidate()
End If
End Set
End Property
Good code, but for the people like me that doesn't actually know what a color map is, you can think of it as a color picker, but obviously this is more powerful.
Last edited by BlindSniper; Sep 17th, 2011 at 01:21 PM.
Good additions, thank you!
I built the control to fit the needs of my project, and tested it with the correct implementation. I did not consider that anyone might forget to fill the BackgroundImage property.
And I thought my description of the events (specifically 'the control will also raise the ImageMouseMove and ImageClicked events that return the color of the color map at the location indicated by the mouse') would be enough explanation, but I guess it is not. I don't know what a color picker is, but I'm sure it will make things clearer for people that do.
I attached a new version of the ImageMap control. I now realize what you meant by a color picker, and that's not really it. What this control does is show you an image, and when you move over it, or click it, it raises an event that has as an attribute the color of the pixel at the same location but in ANOTHER image. This way, you can determine in which area of the original image the mouse pointer was when the event was raised. Useful if you want to create an interactive map of a country for example.
The new version now supports placing images or text in your background image by using the AddImageToken and AddTextToken methods. If anyone needs more info on how to use it, just post here.