Retour au sommaire

  IMPLEMENTATION SOUS VB 6  

 

 

    AFFICHAGE SOMMAIRE CONCLUSION    

 

Ce chapitre décrit les différents problèmes d'implémentation de =FOOT= sous Visual Basic 6, ainsi que les diverses solutions qui ont été trouvées pour y remédier.

 

  1) L'API Win32:  

Pour manipuler des images de manière souple et efficace, nous avons besoin de deux choses essentielles:

  1. Etre en mesure de lire et d'écrire des pixels individuellement.

  2. Pouvoir y accéder le plus rapidement possible.

Les capacités intrinsèques de Visual Basic ne permettent pas de répondre à la deuxième condition de manière satisfaisante. Pour ce faire, nous devons utiliser les fonctions GDI de l'API "Win32" qui dispose heureusement de tous les outils nécessaires.

 

 

  2) CreateDIBSection():  

CreateDIBSection() est une fonction très puissante de la GDI. Elle nous permet de créer une image de n'importe quelle taille avec la profondeur de couleurs de notre choix (dans la limite des capacités du système bien entendu). Mais son principal avantage réside dans le fait qu'elle nous fournit dans la foulée un pointeur direct sur les données de l'image.

Voici sa déclaration dans VB:
 

Public_Declare_Function CreateDIBSection_Lib_"gdi32"  (_ByVal_hdc_As_Long, handle sur le contexte de périphérique de l'image (cette valeur est nécessaire à la plupart des fonctions GDI)
  pBitmapInfo_As_BITMAPINFO, structure contenant les infos décrivant l'image à créer
  ByVal_un_As_Long, type de l'image (doit valoir DIB_RGB_COLORS pour les images en "vraies couleurs"
  lplpVoid_As_Long, pointeur sur les données (pixels) de l'image
  ByVal_handle_As_Long, = 0&
  ByVal_dw_As_Long = 0&
  )_As_Long renvoie un handle si la fonction a réussie permettant de valider l'image, 0& en cas d'echec.

 

Prenez garde si vous utilisez la visionneuse d'API. En effet cette dernière déclare lplpVoid par valeur. Dans ces conditions CreateDIBSection() ne peut pas nous retourner la valeur du pointeur sur les pixels de l'image par le biais de cette variable. Il faut donc supprimer ByVal ou le remplacer par ByRef afin que le passage se fasse bien par référence.

 

L'adresse de départ d'une image Windows pointe sur le premier pixel du bas à gauche de l'image jusqu'au pixel du haut à droite. Ceci peut être gênant si on y prête pas attention car l'origine des coordonnées d'une fenêtre Windows (dans laquelle peut être affichée l'image) est située en haut à gauche. Il faut donc, si l'on ne veut pas afficher l'image à l'envers, effectuer l'opération suivante pour corriger les coordonnées verticales: Y ' = hauteur de l'image - Y

 

Voilà, nous disposons à présent de l'outil idéal pour accéder aux pixels de notre image. Mais si vous avez déjà programmé avec Visual Basic, vous avez certainement remarqué un problème, car il y en a un, et de taille: Visual Basic ne gère pas les pointeurs!

Du moins, pas officiellement...

 

 

  3) Gestion des pointeurs dans VB:  

Certes VB ne gère pas les pointeurs nativement tels qu'on peut le concevoir dans un langage comme le C ou le C++, mais il existe trois  fonctions qui vont nous permettre de le faire malgré tout:

Voici la déclaration des deux dernières dans VB:
 

Public_Declare_Function VarPtrArray_Lib_"msvbvm50.dll" Alias_"VarPtr"  (_Ptr()_As_Any tableau sur lequel on veut récupérer l'adresse
  )_As_Long renvoie l'adresse du tableau passé en argument

 

Public_Declare_Sub CopyMemory_Lib_"kernel32" Alias_"RtlMoveMemory"  (_Destination_As_Any, adresse de destination à laquelle doivent être copiées les données
  Source_As_Any, adresse source à partir de laquelle il faut copier les données
  ByVal_Length_As_Long nombre d'octets à copier

 

Grâce à ces trois fonctions nous allons pouvoir créer un tableau Visual Basic qui, au lieu de contenir ses propres données, pointera sur les pixels de l'image créée à l'adresse retournée par CreateDIBSection(). Pour cela nous avons encore besoin d'une dernière chose: Deux structures qui vont nous permettre de construire nous même un tableau tel que Visual Basic le concevrait en interne:

 

Private_Type_SAFEARRAYBOUND  (_cElement As Long, capacité (en octets) du tableau
End Type lLBound_As_Long, index de départ du tableau

 

Private_Type_SAFEARRAY2D  (_cDims_As_Integer, nombre de dimension(s) du tableau
  fFeatures_As_Integer, non utilisé
  cbElements_As_Long, doit valoir 1
  cLocks_As_Long, non utilisé
  pvData_As_Long, les données du tableaux. Doit contenir l'adresse sur les pixels retournée par CreateDIBSection()
End Type Bounds(0)_As_SAFEARRAYBOUND (voir SAFEARRAYBOUND plus haut)

 

Il faut bien faire attention à libérer le pointeur ainsi créé après chaque utilisation. En effet, ce dernier n'étant pas permanent, son utilisation ultérieure ferait planter VB.

 

Un exposé plus complet sur l'utilisation de CreateDIBSection() et des pointeurs dans Visual Basic est disponible à cette adresse: http://www.vbaccelerator.com/codelib/gfx/dibsect.htm.

La syntaxe des deux structures précédentes est d'ailleurs directement inspirée de celle décrite sur ce site, à la différence qu'ici nous créons un tableau à une seule dimension pour des raisons d'optimisation:

Ainsi, au lieu d'avoir: pixel [ x, y ] nous aurons: pixel [ x + (y * largeur_image) ]

Dans certain cas, lorsque l'image devra être dessinée ligne par ligne, la quantité (y * largeur_image) n'aura besoin d'être calculée qu'une seule fois par ligne.

Ou bien, lorsque l'image devra être dessinée en totalité, une seule variable suffira à parcourir l'ensemble des pixels: pixel [ index ] avec  0 <= index < largeur_image * hauteur_image

 

Nous disposons à présent de tous les éléments nécessaires à l'élaboration de notre image. Il manque cependant  encore un dernier ingrédient: Le format de pixel.

 

 

  4) 32 Bits vs 24 bits:  

Les cartes graphiques actuelles pouvant allégrement afficher plus de 16 millions de couleurs (16777216 pour exact), il serait dommage de s'en priver. Pour cela nous avons le choix entre deux formats pour coder les pixels: 24 bits ou 32 bits. C'est ce dernier Format que =FOOT= utilise car il présente plusieurs avantages que voici:

 

 

Voici deux codes comparatifs en VB qui remplissent une image avec la couleur orange: 

Remplissage de couleur dans une image 24 bits

Dim limiteImage as Long

limiteImage = (hauteurImage * largeurImage * 3) - 1___' limite de l'image * 3 octects

'pPixel pointeur 8 bits de type Byte

For i = 0 To limiteImage Step 3

pPixel(i) = 255___' 'codage du rouge

pPixel(i + 1) = 128___' 'codage du vert

pPixel(i + 2) = 0___' 'codage du bleu

Next

 

Remplissage de couleur dans une image 32 bits

Dim limiteImage as Long

limiteImage = (hauteurImage * largeurImage) - 1___' limite de l'image

Dim couleur as Long

couleur = RGB(255, 128, 0)___' couleur orange

'pPixel pointeur 32 bits de type Long

For i = 0 To limiteImage

pPixel(i) = couleur

Next

On constate avec ces deux exemples qu'il faut 3 fois plus d'opérations pour écrire un pixel dans une image 24 bits que dans une image 32 bits. Cependant cette différence tend à se réduire lors de la lecture des pixels car les composantes RVB de la couleur d'un pixel 24 bits sont directement accessibles contrairement à ceux d'une image 32 bits qu'il faut décomposer (voir plus loin).

 

Cette assertion n'est pas très audacieuse puisque 32 bits correspondent effectivement à 4 octets. Mais ce point est très important car Windows ne sait pas créer des images alignées autrement que sur 4 octets (pour des raisons d'optimisation liées à l'architecture des ordinateurs).

Cela signifie que lorsque nous créons une image 24 bits dont la largeur n'est pas un multiple de 4, le système va automatiquement ajouter des octets pour l'aligner sur 32 bits ce qui peut compliquer davantage la programmation lorsque l'on veut accéder individuellement aux pixels.

 

Mais les images 32 bits ne sont pas non plus la panacée. Elles ont aussi des inconvénients. Le plus évident est qu'elles consomment davantage de mémoire du fait de leurs 8 bits supplémentaires. Cependant lorsque cet espace est utilisé pour stocker un masque, ce n'est plus vraiment du gaspillage.

Comme je l'ai précisé plus haut, on ne peut accéder directement aux composantes de la couleur du pixel qu'en décortiquant la valeurs 32 bits en 3 ou 4 octets (selon si l'on veut utiliser le masque ou non).

L'API "Win32" nous propose à cet effet 3 macros: GetRValue(), GetGValue(), GetBValue()

Voici leur déclaration en langage C:

#define GetRValue(rgb)   ((BYTE) (rgb))___'Extrait la composante rouge

#define GetGValue(rgb)   ((BYTE) (((WORD) (rgb)) >> 8))___'Extrait la composante verte

#define GetBValue(rgb)   ((BYTE) ((rgb) >> 16)) ___'Extrait la composante bleue

GetAValue() n'existe pas mais elle pourrait s'écrire ainsi:

#define GetAValue(rgb) ((BYTE) ((rgb) >> 24))___'Extrait la composante alpha (masque)

 

Comme nous pouvons le constater, ces macros font appel au décalage de bits pour extraire les 8 bits de chaque composante. Or cela nous amène à parler de la dernière difficulté de cet article: Visual Basic ne gèrent pas le décalage de bits! 

 

Il semblerait cependant que Microsoft ait corrigé ce problème dans sa version .NET.

 

Comment faire alors dans ces conditions pour bénéficier des avantages du 32 bits si nous ne pouvons pas accéder aux composantes de la couleur des pixels?

Heureusement la solution est beaucoup plus simple qu'il n'y paraît:

Au lieu de déclarer un seul pointeur 32 bits sur l'image, il faut en déclarer un second sur la même image mais de type 8 bits. Ainsi, chaque fois qu'il sera nécessaire de lire ou décrire une composante de la couleur d'un pixel; on utilisera le pointeur 8 bits.

Voici un exemple de code qui illustre cette astuce: 

...

Dim index_32 as long

Dim index_8 as long

'pPixel_32:  pointeur 32 bits de type Long

'pPixel_8:  pointeur 8 bits de type Byte

...

'Au cours du traitement, nous voulons affecter une couleur particulière au pixel situé à l'index index_32 lorsque le masque est différent de 0

For index_32 = 0 To limiteImage

If ( (pPixel_32(index_32) And  &HFF000000) <> 0 ) then

' Pour cela nous déduisons la valeur de index_8 en lui affectant celle de index_32 multipliée par 4 (32 / 8)

index_8 = index_32 * 4

pPixel_8(index_8) = R

pPixel_8(index_8 + 1) = V

pPixel_8(index_8 + 2) = B

Else

pPixel_32(index_32) = couleur

End If

Next

...

 

 

Voilà, c'est la fin de cette partie. Vous pourrez trouver l'application de cet article dans les modules ZImage.cls et Affichage.bas du code source de = FOOT =.

 

 

 

    AFFICHAGE SOMMAIRE CONCLUSION