VB.Net - Chercher un sprite dans une image (StreamBitmap)

Contenu du snippet

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

Compatibilité : 1.0

A voir également

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.