Bonjour à tous.
L'objectif de ce code est similaire à mon autre projet que j'avais titré :
"Chercher un morceau d'image dans une image"; voyons les différences
avec ce snippet...
- Le code est 100% VB.NET (aucun pInvoke).
- Le niveau du code est beaucoup plus accessible et compréhensible.
- Les coordonnées traitées sont d'une précision "atomique" ^^
- La rapidité du traitement à été optimisé (pas de gestion du "color block")
- La méthode de recherche est différente (un point trouvé lance un scan ? to X)
Voici la class StreamBitmap.
Performante, elle permet d'effectuer le strict nécessaire en mémoire.
Option Explicit On
Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices
Class StreamBitmap
Public _Bitmap As Bitmap
Public Width As Integer
Public Height As Integer
Public _BitmapData As BitmapData
Public _PointeurZero As IntPtr
Public _Bytes() As Byte
Public _PixelSize As Integer
Public _BlockSize As Integer
'Utilisé pour la fonction SearchSprite()
Private SearchResult As SearchSprite = Nothing
Private BlockResult As List(Of Rectangle) = Nothing
Sub New(ByVal Image As String)
Me._Bitmap = New Bitmap(Image)
Me._PixelSize = Bitmap.GetPixelFormatSize(Me._Bitmap.PixelFormat)
'32 bits = ARGB = index 3 2 1 0 = BGRA
'24 bits = RGB = index 2 1 0 = BGR
Me._BlockSize = Me._PixelSize / 8
Me.Width = Me._Bitmap.Size.Width
Me.Height = Me._Bitmap.Size.Height
Me._BitmapData = Me._Bitmap.LockBits(New Rectangle(0, 0, Me.Width, Me.Height), ImageLockMode.ReadOnly, Me._Bitmap.PixelFormat)
Me._PointeurZero = Me._BitmapData.Scan0
ReDim Me._Bytes((Math.Abs(Me._BitmapData.Stride) * Me.Height) - 1)
Marshal.Copy(Me._PointeurZero, Me._Bytes, 0, Me._Bytes.Length)
End Sub
Sub BlockResultToList(ByRef AnyList As List(Of Rectangle))
AnyList.Clear()
For Each BlockFound As Rectangle In Me.BlockResult
AnyList.Add(BlockFound)
Next
End Sub
Overloads Function PointToBlock(ByVal PointPosition As Point) As Integer
Dim x As Integer = PointPosition.X
Dim y As Integer = PointPosition.Y
Dim xData As Integer = x * Me._BlockSize
Dim yData As Integer = y * Me._BlockSize
Dim StrideHeight As Integer = (yData * Me._BitmapData.Stride) / Me._BlockSize
Dim Position As Integer = StrideHeight + xData
Return Position
End Function
Overloads Function PointToBlock(ByVal PointX As Integer, ByVal PointY As Integer) As Integer
Dim x As Integer = PointX
Dim y As Integer = PointY
Dim xData As Integer = x * Me._BlockSize
Dim yData As Integer = y * Me._BlockSize
Dim StrideHeight As Integer = (yData * Me._BitmapData.Stride) / Me._BlockSize
Dim Position As Integer = StrideHeight + xData
Return Position
End Function
Function BlockToPoint(ByVal BlockPosition As Integer) As Point
Dim x As Integer = 0
Dim y As Integer = 0
x = BlockPosition Mod Me._BitmapData.Stride
x /= Me._BlockSize
y = BlockPosition / Me._BitmapData.Stride
Return New Point(x, y)
End Function
Function GetHitAll() As Integer
Return (Me.Width * Me.Height) * Me._BlockSize
End Function
Function SearchSprite(ByVal Sprite As StreamBitmap, ByRef ScanPosition As Integer, ByRef CurrentHeightIndex As Integer) As Integer
If SearchResult Is Nothing Then
SearchResult = New SearchSprite
Me.BlockResult = New List(Of Rectangle)
End If
With SearchResult
If .IsLoaded = False Then .Load(Sprite)
ScanPosition = Array.IndexOf(Of Byte)(Me._Bytes, .SpriteBitmap._Bytes(.SpriteHeightBlockAlign), ScanPosition)
If ScanPosition = -1 Then
GoTo GT_NOTFOUND
Else
For y As Integer = CurrentHeightIndex To .SpriteBlockHeight
CurrentHeightIndex = y
.SpriteHeightBlockAlign = .SpriteBlockWidth * y
For x As Integer = 0 To .SpriteBlockWidth
If .BlockHits = .SpriteBlockWidth Then
.HitPointStart = Me.BlockToPoint(ScanPosition)
.HitPointStop = Me.BlockToPoint(ScanPosition + x)
.HitToRectangle = New Rectangle(.HitPointStart.X, .HitPointStart.Y, Math.Abs(.HitPointStart.X - .HitPointStop.X), (.HitPointStart.Y - .HitPointStop.Y) + 1)
Me.BlockResult.Add(.HitToRectangle)
.BlockHitSum += .BlockHits
.BlockHits = 0
ScanPosition += x
Exit For
End If
If Me._Bytes(ScanPosition + x) = .SpriteBitmap._Bytes(x + .SpriteHeightBlockAlign) Then
.BlockHits += 1
Else
.BlockHits = 0
ScanPosition += x
Return .BlockHitSum
End If
Next
.BlockNext = .SpriteBlockWidth * (CurrentHeightIndex + .ByteZero)
If .BlockHitSum = .BlockNext Then
Debug.Print("Nouvelle ligne trouvé à " & .HitPointStart.X & "*" & .HitPointStart.Y & " - Block Hits:" & .BlockHitSum & "/" & .BlockHitFull)
If .BlockHitSum = .BlockHitFull Then
Debug.Print("Sprite trouvé à 100% !. En théorie, on sort de la fonction...")
End If
End If
Next
End If
'FOUND !
Debug.Print("Sortie naturelle de la recherche, féliciation !")
Return .BlockHitSum
End With
GT_NOTFOUND:
Debug.Print("NOT FOUND !")
ScanPosition = -1
Return -1
End Function
Sub Dispose()
'Si SearchSprite est utilisé
If Me.SearchResult IsNot Nothing Then
Me.BlockResult.Clear()
Me.BlockResult = Nothing
Me.SearchResult.Dispose()
Me.SearchResult = Nothing
End If
'StreamBitmap
Me._Bitmap.UnlockBits(Me._BitmapData)
Me._Bytes = Nothing
Me._PointeurZero = 0
Me._BitmapData = Nothing
Me.Height = Nothing
Me.Width = Nothing
Me._BlockSize = 0
Me._PixelSize = 0
Me._Bitmap.Dispose()
MyClass.Finalize()
End Sub
End Class
' Class partielle pour la fonction StreamBitmap.SearchSprite()
Partial Class SearchSprite
Public IsLoaded As Boolean
Public ByteZero As Integer
Public SpriteBitmap As StreamBitmap
Public SpriteBlockWidth As Integer
Public SpriteBlockHeight As Integer
Public BlockHits As Integer
Public BlockNext As Integer
Public BlockHitSum As Integer
Public BlockHitFull As Integer
Public SpriteHeightBlockAlign As Integer
Public HitPointStart As Point
Public HitPointStop As Point
Public HitToRectangle As Rectangle
Sub New()
IsLoaded = False
ByteZero = 1
SpriteBitmap = Nothing
SpriteBlockWidth = 0
SpriteBlockHeight = 0
BlockHits = 0
BlockNext = 0
BlockHitSum = 0
BlockHitFull = 0
SpriteHeightBlockAlign = 0
HitPointStart = Nothing
HitPointStop = Nothing
HitToRectangle = Nothing
End Sub
Sub Load(ByVal Sprite As StreamBitmap)
IsLoaded = True
ByteZero = 1
SpriteBitmap = Sprite
SpriteBlockWidth = SpriteBitmap.Width * SpriteBitmap._BlockSize
SpriteBlockHeight = SpriteBitmap.Height - ByteZero
BlockHits = 0
BlockNext = 0
BlockHitSum = 0
BlockHitFull = (SpriteBitmap.Width * SpriteBitmap.Height) * SpriteBitmap._BlockSize
SpriteHeightBlockAlign = 0
End Sub
Sub Dispose()
If Me.SpriteBitmap IsNot Nothing Then
Me.IsLoaded = False
End If
MyClass.Finalize()
End Sub
End Class
Voici un exemple d'utilisation, sur un bouton ou autres objets.
Affichages d'informations locales et un call pour un traitement "quelconque".
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
'Respectez bien les normes logiques !
'Le sprite doit être une copie parfaite de la source (même format).
Dim iSource As New StreamBitmap("Image.png")
Dim iSprite As New StreamBitmap("Sprite.png")
Dim cxID As Integer = 0
Dim cyID As Integer = 0
Dim HitFound As Integer = 0
Dim HitAllSprite As Integer = iSprite.GetHitAll
'Toutes les lignes trouvées, mise à jour ByRef au moment de sortir
Dim Resultats As New List(Of Rectangle)
Do
HitFound = iSource.SearchSprite(iSprite, cxID, cyID)
If HitFound = HitAllSprite Then
'Ajoute les résultats
Call iSource.BlockResultToList(Resultats)
'Mes opérations
Call MesTraitementsAuBonMoment(Resultats, iSource, iSprite)
'Affichage (petit résumé)
Dim msg As String
msg = "Taille image source: " & iSource.Width & "*" & iSource.Height & ControlChars.NewLine
msg &= "Profondeur image source: " & iSource._PixelSize & " PPP" & ControlChars.NewLine
msg &= "Block par pixel de l'image source: " & iSource._BlockSize & " BITS" & ControlChars.NewLine
msg &= "Taille du Data de l'image source: " & iSource._Bytes.Length & ControlChars.NewLine
msg &= ControlChars.NewLine
msg &= "Taille image sprite: " & iSprite.Width & "*" & iSprite.Height & ControlChars.NewLine
msg &= "Profondeur image sprite: " & iSprite._PixelSize & " PPP" & ControlChars.NewLine
msg &= "Block par pixel de l'image sprite: " & iSprite._BlockSize & " BITS" & ControlChars.NewLine
msg &= "Taille du Data de l'image sprite: " & iSprite._Bytes.Length & ControlChars.NewLine
msg &= ControlChars.NewLine
msg &= "Position du premier point trouvé en Pixel: " & Resultats(0).X & "*" & Resultats(0).Y & ControlChars.NewLine
msg &= "Position du premier point trouvé en Data: " & iSource.PointToBlock(Resultats(0).X, Resultats(0).Y).ToString & ControlChars.NewLine
msg &= "Largeur scanné de la premiere ligne (0): " & Resultats(0).Width & ControlChars.NewLine
msg &= "Hauteur scanné de la premiere ligne (0): " & Resultats(0).Height & ControlChars.NewLine
msg &= "Nombre de hauteur scanné: " & Resultats.Count & ControlChars.NewLine
MessageBox.Show(msg, "Sprite trouvé !")
Exit Do
End If
Loop Until cxID = -1
iSprite.Dispose()
iSource.Dispose()
End Sub
Private Sub MesTraitementsAuBonMoment(ByVal Resultats As List(Of Rectangle), ByVal ptrSource As StreamBitmap, ByVal ptrSprite As StreamBitmap)
' RESULTATS = 100%
' HitFound = HitAllSprite
'
'Je vais dessiner un rectangle complet de la zone trouvé sur la source
Dim retRect As Rectangle = Nothing
'Je peux trouver les points du centre de cette source
Dim retPointCentre As Point = Nothing
'Je peux aussi avoir le pointeur Data correspondant aux points central
Dim retDataCentre As Integer = 0
'J'utilise un rectangle du resultat pour le cibler sur la source
retRect = New Rectangle(Resultats.Item(0).X, _
Resultats.Item(0).Y, _
Resultats.Item(0).Width,
Resultats.Count)
'Je peux trouver le point central de ce rectangle source
retPointCentre = New Point(retRect.X + Math.Abs(Resultats.Item(0).Width / 2), _
retRect.Y + Math.Abs(Resultats.Count / 2))
'Je peux aussi faire des opérations avec le pointeur DATA
'retDataCentre = ptrSource.PointToBlock(New Point(retPointCentre.X, retPointCentre.Y))
retDataCentre = ptrSource.PointToBlock(retPointCentre.X, retPointCentre.Y)
Debug.Print("")
Debug.Print("MesTraitementsAuBonMoment".ToUpper)
Debug.Print("")
Debug.Print("Rectangle sprite dans la source:")
Debug.Write(retRect)
Debug.Print("")
Debug.Print("Centre de l'image sprite dans la source:")
Debug.Write(retPointCentre)
Debug.Print("")
Debug.Print("Position data du centre de l'image sprite dans la source:")
Debug.Write(retDataCentre)
Debug.Print("")
End Sub
Vous n'êtes pas encore membre ?
inscrivez-vous, c'est gratuit et ça prend moins d'une minute !
Les membres obtiennent plus de réponses que les utilisateurs anonymes.
Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.
Le fait d'être membre vous permet d'avoir des options supplémentaires.