vb.net how to create a region or path from a bitmap or image

Gib
10-22-2006, 09:42 PM
Hello Gurus, 1st time posting here.

I came up with an algorithm for my tactical space combat game that fits a picture box to its non-transparent pixels (creates a region from the gif the picturebox contains) and it works great, but its slow since it iterates through all the pixels in the picbox and forms unions of regions to yield the final region. The code can be found here:

http://vb-helper.com/howto_net_shape_pic.html

My starship combat game needs to be able to handle mouse clicks on the ships to set them as the active ship or to target an enemy ship in a particular fire arc(fore, aft, port, starboard). This is why I went with a picture box since its easy to code with as it has almost all the properties and methods and events I need for a non-flashy board game on the pc.

I was wondering if there was a way to get the outline of a gif image(bitmap object or image object) in vb.net and use that as a series of points to construct a polygon and then in turn use the polygon to contruct a path and then finally a region from that path. I have spent alot of hours Googleing to no avail. There must be some way of tracing an outline of an image to yield the points and then do the rest. Anybody have code for this or have a link or ideas on how? The drawing classes and gdi are powerful but so far I've played around with different types and methods on the classes and have come up short.

Then I can create the region for the ships gifs and use mouse clicks to test region.isvisible to return what ship was clicked on and it will be faster I assume. Otherwise I need to make 6 copies of my images region - 1 per 60 degree rotation since im using a hex map. I suppose I could do this during the loading screen and keep them in memory to use them when a ship is rotated and then the rotation would be instant(comparatively)

Sorry for the long winded post. Any help would be appreciated. Let me know if you need anything clarified.

Thanks!

Dave Giblin
"Gib"

Gib
10-23-2006, 12:15 AM
You can tell I am tired because my code I have IS the solution. I've been chasing this thing all day lol.

All I need to do is to copy my function for generating a region and create a new function and modify it.

IE: Perform a bitmap scan of the gif by using the getpixel function of GDI as I am doing it but go to the next line of pixels down once the color of the pixel read is different from the transparent color and add that point(as a new point(x,y) to an array of points. This will find the left side of the bitmap outline. Once at the bottom of the picture then start reading bottom up right to left to find the right side of the bitmap outline. Then I have an array of points that make up a system.drawing.drawing.graphicspath. Then I just make a region from that path and viola, old fashioned bitblt with a mask (sort of). Since I can then use isvisible to hit test for mouse clicks in a region, I have my objective of being able to mouse click on a picture!

Also check out
http://www.codeproject.com/cs/miscctrl/Controls_arbitrary_shape.asp

This guy did the same thing and after comming across it tonight I had to hit my forhead(doh!) with my palm as I realized that's what my code was doing, I was just getting too many pixels and thus the process took much longer.

I'll code it in the morning and post back here in case anybody else ever needs it.

Gib

Gib
10-23-2006, 02:17 PM
:mad:

Well I didn't think about something until now, the technique of "scanning" a bitmap for its pixels that are on the outside edge around it to yield a region that is an outline of a bitmap will not work so I might as well use my original and go with my alternative idea.

While doing it I realized you would have to scan the 4 faces of the "box" that hold the image in the bitmap. In doing do, I realized that complex shapes could not be done.

The ONLY way is to iterate through every pixel in a bitmap to find the region that's just the actual image. This is becasue, and unless someone can show otherwise, the gdi drawing classes dont have a way to create a region nor a path or polygon trace of the outline of a bitmap.

If anyone has any ideas please let me know!


Gib

Cags
10-24-2006, 02:38 AM
Is this (http://www.bobpowell.net/region_from_bitmap.htm) the kind of thing your after?

Gib
10-24-2006, 10:53 PM
Is this (http://www.bobpowell.net/region_from_bitmap.htm) the kind of thing your after?

That's what im doing right now yes, and it works. The problem is, it's using getpixel on every pixel in the picturebox and its slow. Then I rotate my picture using my rotate sub I then call my GetRegion which gets a new region for the rotated image and sets the picture box region. This works fine but has a noticeable lag in rotating.

What I was trying to do now was pre define 6 roation regions and the rotated bitmap image and store them. That way the the loading is on the front end when the game loads for each differnt ship image. My problem now is that when I do the first rotation and set my ship's image property to the stored rotated image and also set the ships region property to the stored region for that rotation it doesnt seem to fully set the region of my pictureox - the previous region is mixed into the new region of sorts.

Here's the core subs/functions - let me know if you need more to figure out

The code is messy right now because I was just trying various things out, hope you can understand the rest. If you want I could post some more but this is the core stuff - i left out the classes and structures and whatnot

Private Function GetRegion(ByVal bm As Bitmap, ByVal bg_color As Color) As Region
Dim new_region As New Region
new_region.MakeEmpty()

Dim rect As New Rectangle
Dim in_image As Boolean = False
Dim X As Integer

For Y As Integer = 0 To bm.Height - 1
X = 0
Do While (X < bm.Width)
If Not in_image Then
If Not bm.GetPixel(X, Y).Equals(bg_color) Then
in_image = True
rect.X = X
rect.Y = Y
rect.Height = 1
End If
ElseIf bm.GetPixel(X, Y).Equals(bg_color) Then
in_image = False
rect.Width = (X - rect.X)
new_region.Union(rect)
End If
X = (X + 1)
Loop

' Add the final piece if necessary.
If in_image Then
in_image = False
rect.Width = (bm.Width - rect.X)
new_region.Union(rect)
End If
Next Y

Return new_region
End Function


Public Function RotateShip(ByVal Angle As Integer, ByVal m_SourceBm As Bitmap) 'ByVal Index As Integer,
Try
'Dim MyImage As String = Application.StartupPath & "\Ships\" & Ship(Index).Name & ".gif"
'Dim m_SourceBm As New Bitmap(Application.StartupPath & "\Ships\" & Ship(Index).Name & ".gif")
Dim m_SourceWid As Integer = m_SourceBm.Width
Dim m_SourceHgt As Integer = m_SourceBm.Height
Dim m_SourceCx As Single = m_SourceWid / 2
Dim m_SourceCy As Single = m_SourceHgt / 2
Dim m_SourceCorners As PointF()
Dim m_DestWid As Integer
Dim m_DestHgt As Integer
Dim m_DestCx As Single
Dim m_DestCy As Single
Dim m_DestBm As Bitmap
Dim m_DestBackColor As Color

m_SourceCorners = New PointF() _
{New PointF(0, 0), _
New PointF(m_SourceWid, 0), _
New PointF(0, m_SourceHgt), _
New PointF(m_SourceWid, m_SourceHgt)}
m_DestWid = m_SourceBm.Width
m_DestHgt = m_SourceBm.Height
m_DestCx = m_DestWid / 2
m_DestCy = m_DestHgt / 2
m_DestBm = New Bitmap(m_DestWid, m_DestHgt)

' Translate the corners to center the bounding box at the origin.
Dim i As Long

For i = 0 To 3
m_SourceCorners(i).X -= m_SourceCx
m_SourceCorners(i).Y -= m_SourceCy
Next i

'Save picDest's background color.
m_DestBackColor = Color.Transparent 'pbShip.BackColor
Dim theta As Single = Angle * PI / 180

'Make a local copy of the image's corners.
Dim corners() As PointF = m_SourceCorners

'Drop the last corner lest we confuse DrawImage which expects an array of three corners.
ReDim Preserve corners(2)

'Rotate.
Dim sin_theta As Single = Math.Sin(theta)
Dim cos_theta As Single = Math.Cos(theta)
Dim X As Single
Dim Y As Single

For i = 0 To 2
X = corners(i).X
Y = corners(i).Y
corners(i).X = X * cos_theta + Y * sin_theta
corners(i).Y = -X * sin_theta + Y * cos_theta
Next i

'Translate to center the results in the destination image.
For i = 0 To 2
corners(i).X += m_DestCx
corners(i).Y += m_DestCy
Next i

'Create an output Bitmap and Graphics object.
Dim gr_out As Graphics = Graphics.FromImage(m_DestBm)

'Draw the result onto the output Bitmap.
gr_out.Clear(m_DestBackColor)
gr_out.DrawImage(m_SourceBm, corners)

'Display the result.
' pbShip.Image = m_DestBm
Return m_DestBm
Catch ex As Exception
Return m_SourceBm
End Try
End Function


Private Sub frmStarshipCombat_KeyPress _
(ByVal sender As Object, _
ByVal e As System.Windows.Forms.KeyPressEventArgs) _
Handles Me.KeyPress
Try
pbShip.Visible = False
pbShip.BringToFront()
If Game.Phase = "Movement" Then
Select Case Game.Ship
Case 0, 1, 2
If Game.Player = 1 Then
If Ship(Game.Ship).MoveLeft > 0 Then
Select Case UCase(e.KeyChar)
Case "W"
'Move the ship forward 1 hex in the direction it's facing
Select Case Ship(Game.Ship).Facing
Case 1
pbShip.Top -= HexYIncr
Case 2
pbShip.Top -= (0.5 * HexYIncr)
pbShip.Left += HexXIncr
Case 3
pbShip.Top += (0.5 * HexYIncr)
pbShip.Left += HexXIncr
Case 4
pbShip.Top += HexYIncr
Case 5
pbShip.Top += (0.5 * HexYIncr)
pbShip.Left -= HexXIncr
Case 6
pbShip.Top -= (0.5 * HexYIncr)
pbShip.Left -= HexXIncr
End Select
Case "A"
'Rotate the ship 60 degrees counter-clockwise
Ship(Game.Ship).Facing -= 1
If Ship(Game.Ship).Facing < 0 Then
Ship(Game.Ship).Facing = 5
End If
pbShip.Image = Ship(Game.Ship).Image(Ship(Game.Ship).Facing)
pbShip.Region = Ship(Game.Ship).Region(Ship(Game.Ship).Facing)

'RotateShip((Ship(Game.Ship).Facing - 1) * -60, Game.Ship)
'Dim ShipImage As New Bitmap(pbShip.Image)
'pbShip.Region = BitmapToRegion(ShipImage, ShipImage.GetPixel(0, 0))
Case "D"
'Rotate the ship 60 degrees clockwise
Ship(Game.Ship).Facing += 1
If Ship(Game.Ship).Facing > 5 Then
Ship(Game.Ship).Facing = 0
End If
pbShip.Image = Ship(Game.Ship).Image(Ship(Game.Ship).Facing)
pbShip.Region = Ship(Game.Ship).Region(Ship(Game.Ship).Facing)

'RotateShip((Ship(Game.Ship).Facing - 1) * -60, Game.Ship)
'Dim ShipImage As New Bitmap(pbShip.Image)
'pbShip.Region = BitmapToRegion(ShipImage, ShipImage.GetPixel(0, 0))
End Select
pbShip.Invalidate()
End If
End If
Case Else
If Game.Player = 1 Then

End If
End Select
End If

pbShip.Visible = True
Catch ex As Exception
MsgBox(ex.Message & vbCrLf & vbCrLf & "frmStarshipCombat.KeyPress" & vbCrLf & Ship(Game.Ship).Facing)
End Try
End Sub

Gib
10-24-2006, 10:55 PM
Public Sub GetRotatedImageAndRegion(ByVal bitmap As Bitmap)
'test code for now, need to put in a 0-5 loop to clean up
Try
'Facing 0
Dim image0 As New Bitmap("E:\StarshipCombat\StarshipCombat\bin\Debug\Ships\Klingon D7.gif")
image0 = bitmap
pb0.BackColor = Color.Transparent
pb0.SizeMode = PictureBoxSizeMode.AutoSize
pb0.Top = 100
pb0.Left = 100
pb0.Visible = True
pb0.Image = image0
'picHexGrid.Controls.Add(pb0)
pb0.Region = BitmapToRegion(image0, image0.GetPixel(0, 0))
pb0.Image = RotateShip(0, image0)
Ship(Game.Ship).Image(0) = pb0.Image
Dim ShipImage0 As New Bitmap(pb0.Image)
pb0.Region = BitmapToRegion(ShipImage0, ShipImage0.GetPixel(0, 0))
Ship(Game.Ship).Region(0) = pb0.Region

'Facing 1
Dim image1 As New Bitmap("E:\StarshipCombat\StarshipCombat\bin\Debug\Ships\Klingon D7.gif")
image1 = bitmap
pb1.BackColor = Color.Transparent
pb1.SizeMode = PictureBoxSizeMode.AutoSize
pb1.Top = 100
pb1.Left = 200
pb1.Visible = True
pb1.Image = image1
'picHexGrid.Controls.Add(pb1)
pb1.Region = BitmapToRegion(image1, image1.GetPixel(0, 0))
pb1.Image = RotateShip(-120, image1)
Ship(Game.Ship).Image(1) = pb1.Image
Dim ShipImage1 As New Bitmap(pb1.Image)
pb1.Region = BitmapToRegion(ShipImage1, ShipImage1.GetPixel(0, 0))
Ship(Game.Ship).Region(1) = pb1.Region

'Facing 2
Dim image2 As New Bitmap("E:\StarshipCombat\StarshipCombat\bin\Debug\Ships\Klingon D7.gif")
image2 = bitmap
pb2.BackColor = Color.Transparent
pb2.SizeMode = PictureBoxSizeMode.AutoSize
pb2.Top = 100
pb2.Left = 200
pb2.Visible = True
pb2.Image = image2
'picHexGrid.Controls.Add(pb2)
pb2.Region = BitmapToRegion(image2, image2.GetPixel(0, 0))
pb2.Image = RotateShip(-120, image2)
Ship(Game.Ship).Image(2) = pb2.Image
Dim ShipImage2 As New Bitmap(pb2.Image)
pb2.Region = BitmapToRegion(ShipImage2, ShipImage2.GetPixel(0, 0))
Ship(Game.Ship).Region(2) = pb2.Region

'Facing 3
Dim image3 As New Bitmap("E:\StarshipCombat\StarshipCombat\bin\Debug\Ships\Klingon D7.gif")
image3 = bitmap
pb3.BackColor = Color.Transparent
pb3.SizeMode = PictureBoxSizeMode.AutoSize
pb3.Top = 100
pb3.Left = 200
pb3.Visible = True
pb3.Image = image3
'picHexGrid.Controls.Add(pb3)
pb3.Region = BitmapToRegion(image3, image3.GetPixel(0, 0))
pb3.Image = RotateShip(-120, image3)
Ship(Game.Ship).Image(3) = pb3.Image
Dim ShipImage3 As New Bitmap(pb3.Image)
pb3.Region = BitmapToRegion(ShipImage3, ShipImage3.GetPixel(0, 0))
Ship(Game.Ship).Region(3) = pb3.Region

'Facing 4
Dim image4 As New Bitmap("E:\StarshipCombat\StarshipCombat\bin\Debug\Ships\Klingon D7.gif")
image4 = bitmap
pb4.BackColor = Color.Transparent
pb4.SizeMode = PictureBoxSizeMode.AutoSize
pb4.Top = 100
pb4.Left = 200
pb4.Visible = True
pb4.Image = image4
'picHexGrid.Controls.Add(pb4)
pb4.Region = BitmapToRegion(image4, image4.GetPixel(0, 0))
pb4.Image = RotateShip(-120, image4)
Ship(Game.Ship).Image(4) = pb4.Image
Dim ShipImage4 As New Bitmap(pb4.Image)
pb4.Region = BitmapToRegion(ShipImage4, ShipImage4.GetPixel(0, 0))
Ship(Game.Ship).Region(4) = pb4.Region

'Facing 5
Dim image5 As New Bitmap("E:\StarshipCombat\StarshipCombat\bin\Debug\Ships\Klingon D7.gif")
image5 = bitmap
pb5.BackColor = Color.Transparent
pb5.SizeMode = PictureBoxSizeMode.AutoSize
pb5.Top = 100
pb5.Left = 200
pb5.Visible = True
pb5.Image = image5
'picHexGrid.Controls.Add(pb5)
pb5.Region = BitmapToRegion(image5, image5.GetPixel(0, 0))
pb5.Image = RotateShip(-120, image5)
Ship(Game.Ship).Image(5) = pb5.Image
Dim ShipImage5 As New Bitmap(pb5.Image)
pb5.Region = BitmapToRegion(ShipImage5, ShipImage5.GetPixel(0, 0))
Ship(Game.Ship).Region(5) = pb5.Region


'Dim image(5) As Bitmap
'Dim i As Integer

'For i = 0 To 5
' Dim pb As New PictureBox
' Dim newimage As Bitmap = bitmap '("E:\StarshipCombat\StarshipCombat\bin\Debug\Ships\Klingon D7.gif")
' pb.BackColor = Color.Transparent
' pb.SizeMode = PictureBoxSizeMode.AutoSize
' pb.Top = 100
' pb.Left = 100
' pb.Visible = True
' pb.Image = newimage
' picHexGrid.Controls.Add(pb)
' pb.Region = BitmapToRegion(newimage, newimage.GetPixel(0, 0))
' pb.Image = RotateShip((i * -60), newimage)
' Dim ShipImage As New Bitmap(pb.Image)
' pb.Region = BitmapToRegion(ShipImage, ShipImage.GetPixel(0, 0))
' Ship(Game.Ship).Region(i) = pb.Region
' pb.Dispose()
'Next

'image(i) = RotateShip(-60, newimage)
'Ship(Game.Ship).Image(i) = image(i)
'Dim pbship As New PictureBox
'Ship(Game.Ship).Region(i) = pbship.Region
'Ship(Game.Ship).Region(i) = BitmapToRegion(image(i), image(i).GetPixel(0, 0))

Catch ex As Exception
MsgBox(ex.Message & vbCrLf & vbCrLf & "GetRotatedImageAndRegion")
End Try
End Sub

Gib
10-24-2006, 11:06 PM
You're probably wondering why all this code to make a picturebox fit to its non-transparent pixels and be able to rotate images.

I could do all this with gdi drawing classes and just draw the transparent gif rotated on the game screen. BUT, and this is a huge BUTow do I, in GDI, tell if that bitmap has been clicked or moused over by the user? I would still have to create a region in order to detect region.isvisible(e.x, e.y) correct?

That's why I had to go with a picture box since It supports the needed mouse click detection - so the controls are intuitive. IE, click a ship to select it, click anywhere else on the hex grid(picturebox) to deselect or while selected, click on your target(and fire arcs get drawn and you select weapons in the fire arc to fire on the enemy vessel).

If there's no way to detect what gif was clicked on then I have to come up with something very ugly to enagle selecting ships/targets. There has to be a way...anyone know??

Thanks in advance,
Gib

Gib
10-29-2006, 03:34 PM
I finally got this son of a beep to work. Found out you have to PictureBox.Region.Dispose() before setting the region. Oh, you CAN set the region without doing this and will work fine but since my game rotates ships to hexside facings, Im holding 6 variables for each different ship gif in memory. In my test code I was getting it almost to work but when I set the region from 1 to 2 to 3... to 5 it worked but when I set it from 5 to 0 then I got a strange parmamter is not valid error. I stated screwing around with other methods contained in the region class and found dispose(). Now everything is well in the universe ;)

If anybody is making games and wants to know all about rotating a image in a picture box and how to make that picturebox TRUELY transparent(by setting its' region to its' non-transparent pixels) let me know!

I cant believe MS left out direct bitmap access to convert bitmaps to paths and then regions in GDI+ and also true transparent picboxes!!! :chuckle:

Gib

EZ Archive Ads Plugin for vBulletin Copyright 2006 Computer Help Forum