I. Introduction à GDI+▲
GDI et GDI+ sont des librairies graphiques Windows.
Ce sont des fonctions que l'on peut appeler depuis nos programmes en ayant pris soin auparavant de les déclarer : on les connaît également sous le nom API (Application Programming Interface).
La librairie GDI est intégrée à toutes les versions de Windows, elle contient des fonctions de dessins basiques et ne sait gérer que le format Bitmap (BMP).
Pour pallier le manque de fonctionnalités de la librairie GDI, Microsoft a développé une autre librairie : GDI+ (ou GdiPlus).
Cette seconde librairie (fournie à partir de Windows XP) est beaucoup plus puissante et reconnaît en autre les formats Jpeg/Png/Gif et les données Exif intégrées aux images Jpeg, notamment par les appareils photos numériques.
Le module de classe clGdiPlus que nous utiliserons dans cet article encapsule ces API pour faciliter leur utilisation.
II. Exemples d'utilisations▲
Grâce aux fonctions de Gdi+ nous allons pouvoir faire du traitement d'images (redimensionnement, rotation, filtres…).
Il est également possible de dessiner des formes géométriques (lignes, ellipses, rectangles…) ou du texte.
Les images générées par Gdi+ peuvent être affichées dans des contrôles image (sur formulaire Access ou UserForm) ou sauvegardées dans un fichier.
Et enfin la création de régions va nous permettre d'ajouter de l'interactivité en déterminant par exemple où l'utilisateur clique sur l'image.
Il y a de nombreuses possibilités, y compris de programmer un jeu !
Cet article ne couvre pas toutes les méthodes et propriétés de clGdiplus.
Il fait un tour d'horizon des fonctionnalités les plus importantes et permet de démarrer ses premiers développements avec Gdi+.
III. Installation et paramétrage▲
Il est nécessaire de posséder la librairie GDI+ de Microsoft.
Cette librairie est incorporée à Windows à partir de la version XP.
Sinon besoin la librairie est en téléchargement :
Lien vers la libraire en téléchargement sur Microsoft.com
Le fichier gdiplus.dll doit dans ce cas être placé dans le répertoire du fichier Office en développement.
Puis le module de classe suivant doit être importé dans le projet VBA : Télécharger le module clGdiplus
Ce module contient toutes les déclarations des API Gdi+ ainsi que de nombreuses fonctions avancées plus pratiques que les fonctions de base.
Si vous ne l'avez pas déjà fait, créez un fichier Access, Word, Excel ou Powerpoint.
Vous pouvez ensuite accéder au projet VBA par le raccourci ALT + F11.
Pour importer le module de classe clGdiplus.cls, allez dans le menu Fichier => Importer un fichier….
Ensuite il faut mettre à jour une constante de compilation dans le module clGdiplus importé.
Recherchez la ligne :
#Const
AppName
=
"
A
"
Et remplacez la valeur A par l'initiale de l'application hôte :
- A pour Access ;
- W pour Word ;
- E pour Excel ;
- P pour Powerpoint.
Pour l'article je crée un classeur Excel ; je modifie donc la constante dans le module clGdiPlus pour lui donner la valeur E.
#Const
AppName
=
"
E
"
L'article s'applique également aux autres applications Office, remplacez la valeur en fonction de l'application que vous utilisée.
Sauf pour Access, la référence à Microsoft Forms 2.0 Object Library est requise.
Vous pouvez ajouter cette référence dans le menu Outils => Références….
Sinon si vous ne la trouvez pas, cette référence sera ajoutée automatiquement lors de la création d'un UserForm.
Vérifier enfin que tout est correct en compilant : menu Débogage => Compiler….
Vous êtes maintenant prêt à utiliser Gdi+.
IV. Système de coordonnées▲
Il est important de savoir que l'origine pour Gdi+ est le coin en haut à gauche de l'image.
Un pixel en position (0,0) est donc en haut à gauche.
Le pixel en bas à droite a pour coordonnées (largeur de l'image -1, hauteur de l'image -1), soit (oGdi.ImageWidth -1, oGdi.ImageHeight - 1).
V. Traitement d'images▲
VBA ne sait pas nativement modifier les fichiers image.
Avec Gdi+, cela va être simple.
Créez un nouveau module : menu Insertion => Module.
Si ce n'est pas déjà fait, ajoutez en tout début de module l'instruction Open Explicit.
Cette instruction qui rend nécessaire la déclaration de toutes les variables utilisées permet d'éviter de nombreuses erreurs de saisie.
Dans le menu Outils => Options, onglet Editeur : cochez Déclaration des variables obligatoire pour que cette instruction soit automatiquement ajoutée dans chaque nouveau module.
Créez une procédure nommée par exemple TestImage :
Option
Explicit
Sub
TestImage
(
)
Dim
oGdi As
clGdiplus
Set
oGdi =
New
clGdiplus
End
Sub
Dans cette procédure on commence par déclarer l'objet oGdi de type clGdiplus.
Mais il ne suffit pas de déclarer l'objet, il faut ensuite l'instancier avec Set et New.
Évitez de déclarer l'objet avec le mot-clé New (Dim oGdi As New clGdiplus).
En effet en déclarant l'objet de cette manière, VBA va vérifier à chaque appel de l'objet s'il a bien été créé et cela ralenti l'exécution.
Préférez la déclaration et l'instanciation de l'objet en deux temps.
V-A. Chargement d'un fichier▲
Choisissez maintenant un fichier image.
Gdi+ supporte notamment les formats BMP, GIF, JPEG, PNG, TIFF et EMF.
Pour l'exemple je choisis une photo JPEG que je copie dans le répertoire de mon fichier Office.
Pour charger l'image du PC, on utilise la fonction LoadFile.
Dim
oGdi As
clGdiplus
Set
oGdi =
New
clGdiplus
'
Chargement
de
l'image
If
oGdi.
LoadFile
(
oGdi.
ApplicationPath
&
"
P1010940.jpg
"
) Then
'
Chargement
réussi
Else
'
Erreur
de
chargement
MsgBox
"
Erreur
au
chargement
de
l'image
"
End
If
Mon image étant dans le même dossier que mon fichier Excel, j'utilise la fonction ApplicationPath qui est dépendante de l'application utilisée.
Pour mon fichier Excel j'obtiens ThisWorkbook.Path (+ un antislash final).
Dans une application Access par exemple, la valeur serait celle de CurrentProject.Path (+ un antislash final).
Ainsi on peut utiliser des chemins relatifs sans avoir besoin de modifier le code en fonction de l'application ciblée.
Notez qu'il est bien sûr également possible d'utiliser un chemin absolu du type C:\Documents and Settings\monuser\Mes documents\monimage.jpg.
Ou même un chemin réseau du type \\serveur.fr\images\monimage.jpg.
La fonction de chargement, comme beaucoup de fonctions de clGdiplus, retourne Vrai si elle s'est correctement exécutée.
Il convient de vérifier ce retour pour continuer l'exécution ou afficher un message d'erreur par exemple.
Notez que la fonction LoadFile accepte deux autres paramètres :
- pThumbNail pour charger la miniature EXIF ;
- pIcon pour charger l'icône associée au fichier.
Nous verrons l'utilisation de ces deux paramètres dans le chapitre suivant.
V-B. Lecture d'informations▲
Une fois l'image chargée, on peut commencer par lire ses informations.
Les informations les plus courantes sont :
- le format de l'image ;
- la largeur de l'image ;
- la hauteur de l'image.
Ces informations sont respectivement accessibles par les propriétés ImageFormat (ou ImageFormatText), ImageWidth et ImageHeight.
Dim
oGdi As
clGdiplus
Set
oGdi =
New
clGdiplus
'
Chargement
de
l'image
If
oGdi.
LoadFile
(
oGdi.
ApplicationPath
&
"
P1010940.jpg
"
) Then
'
Chargement
réussi
Debug.
Print
"
Format
:
"
, oGdi.
ImageFormatText
Debug.
Print
"
Taille
:
"
, oGdi.
ImageWidth
&
"
x
"
&
oGdi.
ImageHeight
Else
'
Erreur
de
chargement
MsgBox
"
Erreur
au
chargement
de
l'image
"
End
If
Pour afficher des informations lors du déroulement du code, on utilise Debug.Print pour écrire dans la fenêtre d'exécution.
Si elle n'est pas affichée, vous la trouverez dans le menu Affichage => Fenêtre Exécution.
Le code précédent affiche par exemple :
Format : JPEG
Taille : 3264 x 2448
Le format de l'image est JPEG, ce qui est logique lors d'un chargement de fichier jpg.
Quant à la taille elle est donnée en pixels.
Utilisons maintenant les paramètres supplémentaires de la fonction LoadFile.
Avec pThumbnail pour chargement de la miniature EXIF :
[.
.
.
]
'
Chargement
de
l'image
If
oGdi.
LoadFile
(
oGdi.
ApplicationPath
&
"
P1010940.jpg
"
, True
) Then
[.
.
.
]
Format : JPEG
Taille : 160 x 120
On a donc chargé une image JPEG de faible taille.
Cette option est très pratique pour rapidement charger et afficher des miniatures de photos.
Les appareils photos utilisent cette miniature EXIF, ainsi que l'explorateur Windows.
Si l'image ne contient pas de miniature EXIF, alors l'image complète est chargée.
Ou avec pIcon pour chargement de l'icône associée :
[.
.
.
]
'
Chargement
de
l'image
If
oGdi.
LoadFile
(
oGdi.
ApplicationPath
&
"
P1010940.jpg
"
, , GdipLargeIcon) Then
[.
.
.
]
Format : MemoryBMP
Taille : 32 x 32
On a ici chargé une image de 32 pixels (GdipSmallIcon donnerait une image de 16 pixels).
Notez que ce n'est pas une vrai icone, c'est un format BMP (il y a une conversion de format par clGdiplus lors de la recherche de l'icône associée au fichier).
Pour une image JPEG (ou TIFF), on peut également accéder aux données EXIF.
Consultez le tutoriel dédié à la gestion des données EXIF.
V-C. Sauvegarde de l'image▲
Pour pouvoir observer les modifications appliquées à l'image, nous allons ajouter une sauvegarde à l'aide la fonction SaveFile.
Sub
TestImage
(
)
Dim
oGdi As
clGdiplus
Set
oGdi =
New
clGdiplus
'
Chargement
de
l'image
If
oGdi.
LoadFile
(
oGdi.
ApplicationPath
&
"
P1010940.jpg
"
) Then
'
Chargement
réussi
Debug.
Print
"
Format
:
"
, oGdi.
ImageFormatText
Debug.
Print
"
Taille
:
"
, oGdi.
ImageWidth
&
"
x
"
&
oGdi.
ImageHeight
'
Sauvegarde
de
l'image
If
oGdi.
SaveFile
(
oGdi.
ApplicationPath
&
"
tuto.jpg
"
) Then
Debug.
Print
"
Sauvegarde
OK
"
Else
Debug.
Print
"
Sauvegarde
KO
"
End
If
Else
'
Erreur
de
chargement
MsgBox
"
Erreur
au
chargement
de
l'image
"
End
If
End
Sub
Ici on sauvegarde une image au format JPEG.
Les différents formats de sauvegarde possibles sont : BMP, EMF, JPEG, PNG, GIF et TIFF.
Si le paramètre pFormat n'est pas précisé (comme c'est le cas ici), le format est déterminé en fonction de l'extension du fichier cible.
Le format BMP est volumineux mais n'altère pas l'image.
Le format JPEG est peu volumineux mais altère l'image.
Le format PNG est un bon compromis pour des images ayant des zones unies (donc généralement pas pour des photographies) et supporte la transparence.
Évitez le format GIF qui ne donne pas de bons résultats avec Gdi+.
Notez la possibilité pour le format JPEG de préciser une qualité de compression entre 0 et 100, à l'aide du troisième paramètre pQuality de la fonction SaveFile.
Par défaut ce paramètre de qualité est à 75.
Ici le paramètre n'est pas précisé, mais aucune compression n'a eu lieu.
L'image n'ayant pas été modifiée, elle n'a pas été altérée.
En regardant de plus près les données dans le fichier (avec un éditeur hexa par exemple) on s'aperçoit qu'il est identique à l'original, à quelques données d'en-tête près.
Maintenant on va modifier l'image.
V-D. Modification de l'image▲
Les modifications que l'on peut appliquer à l'image sont :
- les transformations (redimensionnement, recadrage, rotation…) ;
- la modification des couleurs ;
- le dessin de formes (rectangle, ellipse…) ou de textes.
Pour illustrer ce chapitre, nous utilisons comme image source le logo de la FAQ Access :
Cette image au format GIF a un fond transparent.
L'enregistrement au format GIF n'étant pas très bien géré par Gdi+ (perte de couleurs), nous allons la sauvegarder dans un autre format.
Si on charge l'image et qu'on la sauvegarde ensuite au format JPEG, on perd la transparence.
Pour obtenir de la transparence, il faut le préciser avec la propriété HasTransparency et sauvegarder dans un format qui gère la transparence (PNG par exemple).
Sub
TestImage
(
)
Dim
oGdi As
clGdiplus
Set
oGdi =
New
clGdiplus
'
Chargement
de
l'image
If
oGdi.
LoadFile
(
oGdi.
ApplicationPath
&
"
faqaccess.gif
"
) Then
'
Active
la
transparence
oGdi.
HasTransparency
=
True
'
Sauvegarde
de
l'image
au
format
png
If
oGdi.
SaveFile
(
oGdi.
ApplicationPath
&
"
tuto.png
"
) Then
Debug.
Print
"
Sauvegarde
OK
"
Else
Debug.
Print
"
Sauvegarde
KO
"
End
If
Else
'
Erreur
de
chargement
MsgBox
"
Erreur
au
chargement
de
l'image
"
End
If
End
Sub
On obtient bien la transparence avec le format PNG.
V-D-1. Transformations▲
Toutes les transformations de ce chapitre créent une nouvelle image.
Si vous travaillez sur un fichier JPEG contenant des données EXIF, celles-ci ne seront recopiées dans la nouvelle image, sauf si vous passez en paramètre pKeepExifData la valeur True.
Si vous modifiez un fichier JPEG, celui-ci sera à nouveau compressé et l'image s'en trouvera altérée (sauf utilisation de SaveTransformFile).
Ces codes de transformation de l'image seront à écrire juste avant l'instruction de sauvegarde (fonction SaveFile).
Le redimensionnement :
Il est possible de modifier la taille de l'image avec les fonctions Resize ou ScaleI.
Resize défini une nouvelle taille en pixels :
oGdi.
Resize
60
Le code précédent redimensionne l'image à 60 pixels en largeur.
La hauteur n'étant pas précisée, elle sera calculée en fonction de la nouvelle largeur pour conserver les proportions de l'image originale.
ScaleI défini un facteur de redimensionnement :
oGdi.
ScaleI
0
.
5
, 0
.
5
Le code précédent redimensionne l'image à 50% de sa taille sur les deux dimensions.
Le recadrage :
La fonction Crop permet de recadrer l'image :
oGdi.
Crop
50
, 40
, 80
, 70
Le code précédent extrait une portion de l'image originale selon les coordonnées précisées.
La rotation :
La fonction Rotate permet de tourner l'image d'un angle quelconque :
oGdi.
Rotate
30
Le code précédent tourne l'image d'un angle de 30° (vers la droite car positif).
L'image résultante est un rectangle contenant l'image tournée.
Il y a donc des zones non remplies par l'image après rotation.
Ces zones sont noires et transparentes.
La transparence est obtenue car on a précisé la propriété HasTransparency = True et sauvegardé dans un format qui gère la transparence (PNG).
Sont également disponibles des transformations standards pour les rotations multiples de 90° et les retournements (flip) de l'image.
Utilisez pour cela la fonction RotateFlip :
oGdi.
RotateFlip
Rotate90FlipY
Le code précédent tourne l'image d'un angle de 90° (vers la droite) et retourne l'image sur l'axe vertical.
Il reste une dernière subtilité pour les images au format JPEG (la plupart des photographies de nos appareils), la transformation sans perte.
En effet si vous souhaitez tourner une photo de 90° par exemple et que vous sauvegardez ensuite l'image, celle-ci est altérée par une compression.
La fonction SaveTransformFile permet de transformer une image et la sauvegarder sans perte.
Les transformations sont toutefois limitées aux rotations multiples de 90° et aux retournements (flip).
De plus la largeur et la hauteur de l'image doivent toutes deux être des multiples de 16 pixels.
oGdi.
SaveTransformFile
(
oGdi.
ApplicationPath
&
"
tuto.jpg
"
, EncoderValueTransformRotate90)
Le code précédent tourne l'image de 90° et la sauvegarde sans altérer les données de l'image.
C'est-à-dire que l'image sera tournée mais il n'y aura pas nouvelle compression JPEG.
V-D-2. Couleurs▲
La fonction ReplaceColor remplace une couleur par une autre dans l'image.
oGdi.
ReplaceColor
RGB
(
164
, 192
, 239
), vbGreen
Le code précédent remplace la couleur de composantes RGB (164, 192, 239) par du vert.
J'ai lu les composantes de la couleur avec un logiciel tiers (msPaint).
On peut également lire la couleur d'un pixel à partir de ses coordonnées (fonction GetPixel).
oGdi.
ReplaceColor
oGdi.
GetPixel
(
50
, 100
), vbGreen
Les troisièmes et quatrièmes paramètres définissent les transparences des couleurs.
On peut donc remplacer par une couleur transparente :
oGdi.
ReplaceColor
oGdi.
GetPixel
(
50
, 100
), vbGreen
, , 0
Notez également les paramètres pX1, pY1, pX2, pY2 qui définissent un rectangle qui limite la zone à traiter.
La fonction ApplyColorMatrix applique une matrice de couleur à l'image.
Cette matrice est divisée en 4 paramètres : pRed, pGreen, pBlue et pAlpha.
Chacun de ces paramètres est un tableau de 5 valeurs comprises entre 0 et 1.
Les 4 premières valeurs sont les multiplicateurs respectivement des composantes Rouge, Vert, Bleu et Alpha (transparence) du pixel.
La 5e valeur est une constante à ajouter (1 étant la valeur maximum).
Pour l'exemple, voici un appel à cette fonction … qui ne fait aucune modification :
oGdi.
ApplyColorMatrix
Array
(
1
, 0
, 0
, 0
, 0
), _
Array
(
0
, 1
, 0
, 0
, 0
), _
Array
(
0
, 0
, 1
, 0
, 0
), _
Array
(
0
, 0
, 0
, 1
, 0
)
En effet :
- le paramètre pRed est Array(1, 0, 0, 0, 0), c'est-à-dire qu'on multiplie la composante rouge par 1 ;
- le paramètre pGreen est Array(0, 1, 0, 0, 0), c'est-à-dire qu'on multiplie la composante verte par 1 ;
- le paramètre pBlue est Array(0, 0, 1, 0, 0), c'est-à-dire qu'on multiplie la composante bleue par 1 ;
- le paramètre pAlpha est Array(0, 0, 0, 1, 0), c'est-à-dire qu'on multiplie la composante alpha par 1 ;
Un autre filtre :
oGdi.
ApplyColorMatrix
Array
(
1
, 0
, 0
, 0
, 0
), _
Array
(
0
, 1
, 0
, 0
, 0
), _
Array
(
0
, 0
, 1
, 0
, 0
), _
Array
(
0
, 0
, 0
, -
1
, 1
)
Ici on multiplie la composante alpha par -1 et on lui ajoute 1.
Donc pour une couleur transparente, la composante alpha est 0 et est transformée en 1.
Pour une couleur opaque, la composante alpha est 1 et est transformée en 0.
Ce qui était transparent devient opaque et vice-versa.
Et comme on peut combiner les couleurs entre elles, on peut écrire un filtre noir et blanc.
oGdi.
ApplyColorMatrix
Array
(
0
.
222
, 0
.
707
, 0
.
071
, 0
, 0
), _
Array
(
0
.
222
, 0
.
707
, 0
.
071
, 0
, 0
), _
Array
(
0
.
222
, 0
.
707
, 0
.
071
, 0
, 0
), _
Array
(
0
, 0
, 0
, 1
, 0
)
Les nuances de gris sont obtenues en multipliant chacune des composantes de manière à ce qu'il y ait autant de rouge, de vert et de bleu dans chaque pixel.
En effet : 0.222 + 0.707 + 0.071 = 1
On aurait pu utiliser des multiplicateurs 0.333 par exemple, mais les valeurs ci-dessus donnent de meilleurs résultats.
Si les modifications de couleurs souhaitées ne sont pas possibles avec une matrice, vous pouvez utiliser les fonctions SavePixels et LoadPixels.
SavePixels permet de récupérer les couleurs des pixels dans un tableau.
Ce tableau peut alors être modifié puis réinjecté dans l'image avec la fonction LoadPixel.
'
Tableau
pour
recevoir
les
pixels
Dim
lPixels
(
) As
Byte
'
Compteurs
Dim
lCptX As
Long
, lCptY As
Long
'
Lecture
des
pixels
de
l'image
lPixels =
oGdi.
SavePixels
'
Boucle
sur
les
pixels
For
lCptX =
LBound
(
lPixels
(
), 2
) To
UBound
(
lPixels
(
), 2
)
For
lCptY =
LBound
(
lPixels
(
), 3
) To
UBound
(
lPixels
(
), 3
)
'
Composante
bleue
=
lPixels(1,
lCptX,
lCptY)
'
Composante
verte
=
lPixels(2,
lCptX,
lCptY)
'
Composante
rouge
=
lPixels(3,
lCptX,
lCptY)
'
Composante
alpha
=
lPixels(4,
lCptX,
lCptY)
If
lCptX >
lCptY Then
lPixels
(
3
, lCptX, lCptY) =
255
Next
Next
'
Réinjecte
les
pixels
dans
l'image
oGdi.
LoadPixels
lPixels
lPixels est un tableau à 4 dimensions retourné par la fonction SavePixels.
Quel que soit le format de l'image, il y a toujours 3 composantes de couleur (rouge, vert et bleu) et une composante alpha. Si l'image ne gère pas la transparence alors cette composante est ignorée.
Les valeurs vont de 0 à 255.
Il suffit alors de parcourir le tableau comme dans l'exemple ci-dessus et remplacer les valeurs de chaque composante.
Dans l'exemple on remplace la composante Rouge par 255 (c'est-à-dire le plus de rouge possible) pour les pixels dont X est supérieur à Y.
On conserve intact les autres composantes de la couleur.
Une fois les pixels modifiés dans le tableau, on le réinjecte dans l'image avec LoadPixels.
On obtient une image passée dans un filtre rouge sur la moitié en haut à droite :
Si possible, utilisez la fonction ApplyColorMatrix qui est plus rapide.
V-D-3. Dessin▲
Dans ce chapitre nous allons dessiner sur l'image : des rectangles, des ellipses, des polygones, du texte…
Les noms des fonctions de dessin de clGdiplus commencent par Draw, par exemple Drawline, DrawRectangle…
V-D-3-a. Précision : Long ou Single▲
Dans le module de classe clGdiplus, vous trouverez une constante de compilation #Const UseSingle
Cette constante permet de définir la précision des coordonnées utilisées : Long (entier) ou Single (virgule flottante).
Donnez à cette constante la valeur True pour utiliser des coordonnées Single ou False pour des valeurs Long.
C'est-à-dire qu'en précision Long, les coordonnées seront des entiers, alors qu'en précision Single les cordonnées pourront être des nombres avec décimales.
Cependant, l'unité de base d'une image reste le pixel que vous voyez à l'écran.
Il n'est pas possible de dessiner réellement à une position de 10.3 pixels par exemple ; l'utilisation de coordonnées avec décimales induit un mélange des pixels pour faire croire à une précision supérieure.
En précision Single, le dessin est un peu plus lent mais plus précis.
Pour obtenir de bons résultats avec des Single, définissez la propriété SmoothingMode à GdipSmoothingAntialias ou GdipSmoothingHighQuality.
Sinon par défaut l'effet de lissage n'est pas présent.
Sur l'image ci-dessous nous pouvons voir comment Gdi+ procède pour dessiner un carré décalé de un dixième de pixel à chaque image.
À chaque image il y a un mélange entre le noir, le rouge, et le fond blanc.
À la dixième image, le carré est complément décalé de un pixel.
Si on affiche un tel déplacement à l'écran, l'œil ne verra pas de saccades car les positions intermédiaires nous trompent et nous font penser que le carré est déplacé d'un dixième de pixel à chaque image.
Avec des coordonnées entières, le déplacement est beaucoup moins fluide.
Dans le cas de l'affichage d'un défilement à l'écran, il faut évidemment trouver un juste milieu entre fluidité et rapidité.
V-D-3-b. Dessin de formes▲
Commençons par dessiner tout simplement une ligne :
oGdi.
DrawLine
10
, 10
, 150
, 100
, vbRed
, 10
Ce code dessine une ligne rouge de 10 pixels de large entre les points de coordonnées (10,10) et (150,100).
Le dessin de lignes n'est pas lissé par défaut. Pour appliquer un lissage, définissez la propriété SmoothingMode.
oGdi.
SmoothingMode
=
GdipSmoothingAntialias
oGdi.
DrawLine
10
, 10
, 150
, 100
, vbRed
, 10
'
,
GdipDashDASH
Pour chaque dessin à base de lignes, vous pouvez définir plusieurs paramètres à l'appel des fonctions :
- pDash pour lignes pointillées ;
- pAlpha pour des lignes transparentes.
oGdi.
SmoothingMode
=
GdipSmoothingAntialias
oGdi.
DrawLine
10
, 10
, 150
, 100
, vbRed
, 10
, GdipDashDASH
Et voici une ligne pointillée avec une transparence de 150 (sur un maximum de 255).
Et toujours pour chaque dessin à base de lignes, vous pouvez définir plusieurs propriétés (à définir une fois pour toutes les fonctions), notamment :
- LineStart pour le type de départ de ligne (rond, carré, flèche…) ;
- LineEnd pour le type de fin de ligne ;
- LineJoin pour le type de jointure entre chaque ligne.
Par exemple pour une fin de ligne en flèche pleine de taille 3 (c'est-à-dire 3 fois l'épaisseur du trait) :
oGdi.
LineEnd
=
LineCapArrowAnchor
oGdi.
LineArrowLength
=
3
oGdi.
LineArrowFill
=
True
oGdi.
DrawLine
10
, 10
, 150
, 100
, vbRed
, 10
LineJoin est inutile dans le cas d'un dessin d'une unique ligne.
Il sera utile pour dessiner des rectangles, des polygones…
Le dessin d'un rectangle est tout aussi facile :
Par exemple pour un rectangle rouge dessiner avec un trait rouge de 10 pixels :
oGdi.
DrawRectangle
10
, 10
, 150
, 100
, , vbRed
, 10
Il est ensuite facile de colorer l'intérieur du rectangle, de jaune par exemple :
oGdi.
DrawRectangle
10
, 10
, 150
, 100
, vbYellow
, vbRed
, 10
Vous vous en doutez, il est possible de rendre transparent le rectangle :
oGdi.
DrawRectangle
10
, 10
, 150
, 100
, vbYellow
, vbRed
, 10
, , 150
Pour dessiner le cadre opaque et l'intérieur transparent, il faut dessiner en deux temps :
- d'abord l'intérieur en précisant une épaisseur de trait à zéro ;
- ensuite le cadre en ne précisant pas de couleur de fond.
Et pour un dessin de rectangle avec des coins arrondis :
oGdi.
DrawRoundRectangle
10
, 10
, 150
, 100
, 30
, 30
, vbYellow
, vbRed
, 10
, , 150
Les paramètres à 30 définissent le rayon des arrondis.
Poursuivons avec le dessin d'une ellipse :
Ce code dessine une ellipse qui s'inscrit dans un rectangle de coordonnées 10, 10, 150, 100.
oGdi.
DrawEllipse
10
, 10
, 150
, 100
, TypeEllipseRectangle, vbRed
, vbBlue
, 5
, GdipDashDASH, , , 150
Et le code suivant dessine une ellipse de centre 100, 50 et de rayons 60 et 40.
oGdi.
DrawEllipse
100
, 50
, 60
, 40
, TypeEllipseCenter, vbRed
, vbBlue
, 5
, GdipDashDASH, , , 150
Un peu plus complexe avec le dessin d'une courbe de Bézier :
Une courbe de Bézier nécessite au minimum quatre points : le point de départ, deux points de contrôle et un point d'arrivée.
La courbe ne passe pas par les points de contrôle.
oGdi.
DrawCurve
Array
(
20
, 60
, 50
, 10
, 80
, 100
, 150
, 60
), , vbBlue
, 5
Sur cette image, la courbe de Bézier est en bleu.
J'ai ajouté les points en rouge et les lignes joignant ces points en noir.
Notez comme la courbe s'applique à être tangente au segment entre les points.
Il est également possible de dessiner une courbe cardinale :
Cette courbe passe par tous les points définis.
oGdi.
DrawCardinal
Array
(
20
, 60
, 50
, 10
, 80
, 100
, 150
, 60
), 0
.
7
, , vbBlue
, 5
J'ai ici aussi ajouté les points en rouge et les lignes joignant ces points en noir.
Le deuxième paramètre est la tension de la courbe, par défaut égale à 0,5.
Plus la tension est grande, plus la courbe sera arrondie.
Une tension à zéro dessine des segments droits joignant les points.
Pour dessiner un polygone :
oGdi.
DrawPolygon
Array
(
20
, 60
, 50
, 10
, 80
, 100
, 150
, 60
), , vbBlue
, 5
C'est ici que la propriété LineJoin est utile.
Avec par exemple oGdi.LineJoin = LineJoinRound, on obtient des angles arrondis.
V-D-3-c. Dessin de texte▲
Afficher des informations sur l'image peut être utile, la méthode DrawText est faite pour cela.
Le code suivant dessine du texte avec une police de caractères Arial (la police est optionnelle) de taille 20.
Le texte est ici positionné centré en position 100, 50.
Pour modifier l'alignement, modifier les paramètres pAlignHoriz et pAlignVert.
oGdi.
DrawText
"
Essais
gdi+
"
, 20
, "
Arial
"
, 100
, 50
Pour mieux voir le texte vous pouvez ajouter un fond de couleur :
oGdi.
DrawText
"
Essais
gdi+
"
, 20
, "
Arial
"
, 100
, 50
, , , , , , , vbRed
, 150
Si vous précisez les deux autres coordonnées, le texte est alors positionné dans le rectangle défini.
oGdi.
DrawText
"
Essais
gdi+
"
, 20
, "
Arial
"
, 50
, 50
, 150
, 100
, , , , , , vbRed
, 150
J'ai ajouté ici le rectangle de positionnement en noir qui ne s'affiche pas automatiquement.
Notez bien que le surlignage du texte ne se fait que sur la zone écrite et pas sur tout le rectangle.
Le texte étant trop grand, il y a un retour à la ligne automatique.
Si on agrandit le texte (taille 40 par exemple), il ne tient plus dans le rectangle :
Utilisez les propriétés MinTextSize et/ou MaxTextSize pour définir une taille minimale ou maximale du texte.
oGdi.
MinTextSize
=
5
oGdi.
DrawText
"
Essais
gdi+
"
, 40
, "
Arial
"
, 50
, 50
, 150
, 100
, , , , , vbRed
, 150
Le texte est dimensionné pour tenir dans le rectangle.
La propriété LastTextSize contient alors la taille utilisée pour dessiner le texte.
Notez également les propriétés LastTextRight et LastTextBottom qui contiennent la dernière position droite et base du texte et sont utiles pour dessiner du texte à la suite d'un autre.
La méthode DrawText a également des paramètres pour écrire en gras, italique, souligné ou barré.
VI. Charger une image▲
Nous avons vu précédemment la méthode LoadFile pour charger une image à partir d'un fichier stocké sur le PC.
Il est également possible de charger d'autres images.
LoadControl charge l'image contenue dans un contrôle.
oGdi.
LoadControl
Me.
Image0
Cette méthode peut être utile pour charger une image (qui peut être invisible) intégrée dans un formulaire au lieu de la stocker dans un fichier externe.
LoadPixels charge un tableau de couleurs ARGB.
Dim
oGdi As
clGdiplus
Dim
lPixels
(
) As
Byte
Dim
lX As
Long
, lY As
Long
'
Objet
Gdi+
Set
oGdi =
New
clGdiplus
'
Tableau
de
pixels
'
4
composantes
Bleu,
Vert,
Rouge,
Alpha
'
20
de
largeur,
20
de
hauteur
ReDim
lPixels
(
1
To
4
, 1
To
20
, 1
To
20
)
'
Par
défaut
les
pixels
sont
noirs
transparents
'
Parcours
des
pixels
For
lX =
1
To
20
For
lY =
1
To
20
'
Si
pixel
sur
la
diagonale
haut-gauche
=>
bas-droite
If
lX =
lY Then
'
Point
Bleu
avec
Alpha
=
255
pour
opaque
lPixels
(
1
, lX, lY) =
255
'
Bleu
lPixels
(
2
, lX, lY) =
0
'
Vert
lPixels
(
3
, lX, lY) =
0
'
Rouge
lPixels
(
4
, lX, lY) =
255
'
Alpha
End
If
Next
Next
'
Charge
les
pixels
dans
une
image
oGdi.
LoadPixels
lPixels
Le code précédent génère une image avec une ligne bleue en diagonale :
LoadArray charge l'image à partir d'un tableau binaire (tableau de bytes) ou d'une pièce-jointe.
Par tableau binaire on entend un tableau de bytes qui contient l'image telle que stockée dans un fichier, au format JPEG, PNG…
Cette fonction est notamment utile pour charger une image stockée dans une colonne de type pièce-jointe (Access >= 2007).
'
Timages
est
le
nom
de
la
table
'
data
est
le
champ
pièce-jointe
'
id
est
un
champ
texte
identifiant
de
l'image
oGdi.
LoadArray
DLookup
(
"
data.filedata
"
, "
Timages
"
, "
id=
"
"
btnGIF
"
"
"
), True
LoadPictureMask charge un bitmap (et un mask) Gdi32.
Cette méthode est particulièrement utile pour charger des images des menus ou du ruban.
Dim
oGdi As
clGdiplus
Dim
oCmdBars As
Office.
CommandBars
Dim
oImg As
Object
'
Objet
barres
de
commande
Set
oCmdBars =
Application.
CommandBars
'
Objet
Gdi+
Set
oGdi =
New
clGdiplus
'
Récupère
l'image
du
bouton
de
ruban
"ReviewDeleteComment"
Set
oImg =
oCmdBars.
GetImageMso
(
"
ReviewDeleteComment
"
, 32
, 32
)
'
Si
image
OK
If
Not
oImg Is
Nothing
Then
'
Charge
l'image
dans
clGdiplus
oGdi.
LoadPictureMask
oImg.
handle
End
If
Avec le code précédent, on a chargé l'image du bouton du ruban « ReviewDeleteComment » :
Dim
oGdi As
clGdiplus
Dim
oCmdBars As
Office.
CommandBarButton
Dim
oImg As
Object
'
Objet
barres
de
commande
Set
oCmdBars =
Application.
CommandBars
(
"
Worksheet
Menu
Bar
"
).
Controls
(
"
Fichier
"
).
Controls
(
"
&Nouveau...
"
)
'
Objet
Gdi+
Set
oGdi =
New
clGdiplus
'
Si
image
OK
If
Not
oCmdBars Is
Nothing
Then
'
Charge
l'image
dans
clGdiplus
oGdi.
LoadPictureMask
oCmdBars.
Picture
, oCmdBars.
Mask
End
If
Avec le code précédent, on a chargé l'image du bouton « Nouveau… » du menu « Fichier » du menu d'Excel :
LoadBitmap charge un bitmap Gdi+ (peut être utile pour cloner une image d'une instance de clGdiplus vers une autre par exemple).
VII. Créer une image vierge▲
On a vu comment charger une image.
Il est souvent utile de créer une image vierge de dimensions précises.
Les méthodes CreateBitmap* sont faite pour cela.
Ces méthodes créent une image noire et transparente.
CreateBitmap crée une image avec définition de la largeur, de la hauteur, et de la résolution (96 par défaut).
Pour créer une image de taille 300x200 et 72 dpi (=ppp = points par pouces).
oGdi.
CreateBitmap
300
, 200
, 72
La résolution peut être modifiée ultérieurement avec les propriétés DpiX et DpiY.
Cette résolution défini l'affichage sur une imprimante, sur une page internet, ou même dans Word.
CreateBitmapForControl est très utile et permet de créer une image dont la taille et la résolution sont optimales pour un affichage sur un contrôle.
Une image créée avec cette méthode aura comme taille la taille exacte de l'intérieur du contrôle, et aura un format identique à celui de l'écran ce qui accélère l'affichage.
CreateBitmapForImg crée une image dont le format est identique à celui d'une autre image.
Là encore cela permet d'accélérer le dessin.
VIII. Image dans un formulaire ou UserForm▲
Jusqu'ici nous avons chargé puis sauvegardé une image dans un fichier.
Il est possible d'afficher l'image dans un contrôle.
Créez un formulaire sous Access, ou un UserForm (« Insertion » => « UserForm » dans le menu de l'éditeur VBA) pour les autres applications.
Placez-y un contrôle image nommé Image0.
Nous allons écrire du code dans l'événement de chargement du formulaire Access (Form_Load) ou du UserForm (UserForm_Initialize).
Private
Sub
Form_Load
(
)
…
End
Sub
Private
Sub
UserForm_Initialize
(
)
…
End
Sub
Débutons avec un simple chargement de fichier que nous injectons dans le contrôle :
Dim
oGdi As
clGdiplus
Set
oGdi =
New
clGdiplus
'
Chargement
de
l'image
If
oGdi.
LoadFile
(
oGdi.
ApplicationPath
&
"
faqaccess.gif
"
) Then
'
Injecte
l'image
dans
le
contrôle
oGdi.
Repaint
Me.
Image0
Else
'
Erreur
de
chargement
MsgBox
"
Erreur
au
chargement
de
l'image
"
End
If
On obtient le même résultat sur toutes les applications :
L'image est positionnée et redimensionnée selon les propriétés du contrôle : ici le contrôle est en mode « découpage » et « centré ».
On note par contre que bien que l'image soit transparente, le fond est restitué en noir à l'écran.
La solution pour corriger cela est différente selon les applications.
Pour Access :
Il faut injecter une image EMF (Métafichier amélioré) à l'aide du paramètre pUseEMF.
On prendra soin d'activer la transparence au préalable.
'
Active
la
transparence
oGdi.
HasTransparency
=
True
'
Injecte
l'image
dans
le
contrôle
oGdi.
Repaint
Me.
Image0
, , , Me.
BackColor
Pour les autres applications avec un UserForm :
Il n'est pas possible d'avoir de la réelle transparence mais il est possible de définir la couleur de fond (par exemple identique à la couleur de fond du UserForm) avec le paramètre pBackgroundColor :
'
Injecte
l'image
dans
le
contrôle
oGdi.
Repaint
Me.
Image0
, , , Me.
BackColor
Il y a en fait trois méthodes de dessin d'une image sur un contrôle :
- Repaint
- RepaintFast
- RepaintNoFormRepaint
VIII-A. Repaint▲
Cette méthode que nous avons utilisée précédemment injecte l'image dans le contrôle.
Le dessin de l'image est ensuite effectué par l'application Office.
Cette méthode n'est pas très rapide et peut provoquer des clignotements de l'image (plus ou moins visibles selon la configuration).
VIII-B. RepaintFast▲
Comme son nom l'indique, cette méthode est la plus rapide.
Elle se contente de dessiner l'image sur l'écran, à l'emplacement actuel du contrôle ciblé.
Le dessin est alors temporaire ; si vous affichez une autre fenêtre et que vous revenez sur votre formulaire, les modifications apportées à l'image avec RepaintFast ne seront plus là.
On utilisera cette méthode par exemple pour une animation ou pour dessiner sur un événement « sur souris déplacée ».
VIII-C. RepaintNoFormRepaint▲
Cette méthode injecte l'image dans le contrôle mais ne laisse pas l'application Office la redessiner.
Le dessin est ensuite effectué sur l'écran comme avec RepaintFast.
Il n'y a donc pas d'effet de clignotement de l'image, tout en ayant un dessin permanent.
On utilisera cette méthode pour finaliser un dessin par exemple à la fin d'une animation ou sur un événement « sur souris relâchée ».
IX. Interaction avec la souris▲
Vous trouverez un autre exemple d'interaction dans cet article.
Pour ce chapitre nous allons utiliser cette image :
Elle va être chargée puis affichée dans le contrôle image.
Ensuite on va permettre la sélection d'un rectangle à la souris et recadrer l'image selon cette sélection.
IX-A. Préparation du formulaire▲
Préparez un formulaire Access (ou un UserForm) contenant une image nommée image0 et un bouton nommé btnRecadre.
Nous allons devoir agir sur l'image dans différentes procédures ; l'objet oGdi (de type clGdiplus) sera donc créé en entête du module du formulaire pour pouvoir être accessible dans toutes les procédures du formulaire.
Il nous sera utile de conserver également les coordonnées du rectangle de sélection ; on ajoute donc quatre variables gX1, gY1 (coordonnées du premier point) et gX2, gY2 (coordonnées du second point).
Ces variables contiendront des valeurs en pixels, nous les déclarons donc en entiers longs.
Option
Explicit
'
Objet
clGdiplus
Private
oGdi As
clGdiplus
'
Coordonnées
de
sélection
Private
gX1 As
Long
, gY1 As
Long
Private
gX2 As
Long
, gY2 As
Long
IX-B. Chargement de l'image avec GDI+▲
L'image est chargée dans l'événement « sur chargement » du formulaire Access :
'
Sur
chargement
du
formulaire
Private
Sub
Form_Load
(
)
Set
oGdi =
New
clGdiplus
'
Chargement
de
l'image
If
oGdi.
LoadFile
(
oGdi.
ApplicationPath
&
"
image3.gif
"
) Then
'
Conserve
l'image
initiale
oGdi.
ImageKeep
'
Injecte
l'image
dans
le
contrôle
oGdi.
Repaint
Me.
Image0
Else
'
Erreur
de
chargement
MsgBox
"
Erreur
au
chargement
de
l'image
"
End
If
End
Sub
Pour un UserForm, utilisez la procédure UserForm_Initialize.
L'image s'affichage alors dans le contrôle, par défaut non redimensionnée et centrée (mode découpage et alignement au centre).
Un appel à la méthode ImageKeep a été inséré juste après le chargement de l'image.
Cela nous sera utile par la suite pour rappeler cette image de la mémoire sans recharger le fichier.
IX-C. Capture de la sélection à la souris▲
Nous utilisons tout simplement les événements souris du contrôle image.
- Sur souris appuyée, nous sauvegardons les coordonnées dans gX1 et gY1.
- Sur souris déplacée, nous sauvegardons les coordonnées dans gX2 et gY2 et dessinons le rectangle sur l'image.
- Sur souris relâchée, nous sauvegardons les coordonnées dans gX2 et gY2.
IX-C-1. Sur souris appuyée▲
On teste si le bouton gauche est appuyé et on sauvegarde les coordonnées.
Office nous renvoi des coordonnées en twips ou en points par rapport au contrôle ; nous les convertissons en pixels sur l'image grâce aux méthodes CtrlToImgX et CtrlToImgY.
'
Sur
souris
appuyée
Private
Sub
Image0_MouseDown
(
Button As
Integer
, Shift As
Integer
, X As
Single
, Y As
Single
)
If
Button =
vbKeyLButton Then
gX1 =
oGdi.
CtrlToImgX
(
X, Me.
Image0
)
gY1 =
oGdi.
CtrlToImgY
(
Y, Me.
Image0
)
End
If
End
Sub
Pour un UserForm, la déclaration de la procédure est différente :
Private
Sub
Image0_MouseDown
(
ByVal
Button As
Integer
, ByVal
Shift As
Integer
, ByVal
X As
Single
, ByVal
Y As
Single
)
IX-C-2. Sur souris déplacée▲
On teste si le bouton gauche est appuyé et on sauvegarde les coordonnées (converties en pixels sur l'image) dans gX2 et gY2.
Ensuite on dessine un rectangle pointillé sur l'image à l'aide des coordonnées sauvegardées.
'
Sur
souris
déplacée
Private
Sub
Image0_MouseMove
(
Button As
Integer
, Shift As
Integer
, X As
Single
, Y As
Single
)
If
Button =
vbKeyLButton Then
gX2 =
oGdi.
CtrlToImgX
(
X, Me.
Image0
)
gY2 =
oGdi.
CtrlToImgY
(
Y, Me.
Image0
)
Render
oGdi.
RepaintFast
Me.
Image0
End
If
End
Sub
Pour un UserForm, la déclaration de la procédure est différente :
Private
Sub
Image0_MouseMove
(
ByVal
Button As
Integer
, ByVal
Shift As
Integer
, ByVal
X As
Single
, ByVal
Y As
Single
)
Après avoir sauvegardé les coordonnées converties en pixels, on effectue le rendu (image + rectangle) à l'aide la procédure Render.
L'image est ensuite redessinée sur le contrôle avec la méthode RepaintFast.
Ce dessin est temporaire mais durant un déplacement souris c'est la meilleure méthode.
Voici la procédure de rendu :
'
Dessine
l'image
avec
le
rectangle
de
sélection
Private
Sub
Render
(
)
'
Rétabli
l'image
initiale
oGdi.
ImageReset
'
Dessine
le
rectangle
de
sélection
oGdi.
DrawRectangle
gX1, gY1, gX2, gY2, , vbRed
, 1
, GdipDashDash
End
Sub
On commence par rappeler l'image originale qu'on a sauvegardée précédemment en mémoire (avec la méthode ImageKeep juste après le chargement de l'image).
Sur cette image, on dessine le rectangle correspondant aux coordonnées de sélection.
Ce rectangle est rouge (vbRed), de taille 1 et pointillé (GdipDashDash).
IX-C-3. Sur souris relâchée▲
On teste si le bouton gauche est relâché et on sauvegarde les coordonnées (converties en pixels sur l'image) dans gX2 et gY2.
Ensuite on dessine un rectangle pointillé sur l'image à l'aide des coordonnées sauvegardées.
Ce sont les mêmes opérations que sur souris déplacée, sauf qu'on va ici utiliser RepaintNoFormRepaint pour dessiner l'image en l'injectant dans le contrôle de manière permanente.
'
Sur
souris
relachée
Private
Sub
Image0_MouseUp
(
Button As
Integer
, Shift As
Integer
, X As
Single
, Y As
Single
)
If
Button =
vbKeyLButton Then
gX2 =
oGdi.
CtrlToImgX
(
X, Me.
Image0
)
gY2 =
oGdi.
CtrlToImgY
(
Y, Me.
Image0
)
Render
oGdi.
RepaintNoFormRepaint Me.
Image0
End
If
End
Sub
Pour un UserForm, la déclaration de la procédure est différente :
Private
Sub
Image0_MouseUp
(
ByVal
Button As
Integer
, ByVal
Shift As
Integer
, ByVal
X As
Single
, ByVal
Y As
Single
)
IX-D. Recadrage de l'image▲
Pour un recadrage, il suffit d'utiliser la méthode Crop sur l'image originale avec en paramètres les coordonnées sauvegardées.
On effectue cette opération sur clic sur le bouton btnRecadre.
'
Clic
sur
bouton
de
recadrage
Private
Sub
btnRecadre_Click
(
)
'
Rétabli
l'image
initiale
oGdi.
ImageReset
'
Recadre
l'image
oGdi.
Crop
gX1, gY1, gX2, gY2
'
Dessine
l'image
recadrée
oGdi.
Repaint
Me.
Image0
End
Sub
Un clic sur le bouton « Recadre » remplace l'image originale par l'image recadrée.
X. Barres de défilement▲
Dans ce chapitre nous souhaitons afficher une grande image dans un contrôle, et utiliser les barres de défilement pour afficher les parties invisibles.
Les barres de défilement utilisées sont intégrées à clGdiplus.
Elles sont en fait dessinées sur le contrôle image, ce qui nous permet d'utiliser un contrôle image de taille raisonnable et de gérer éventuellement plusieurs ensembles de barres de défilement sur un même contrôle.
X-A. Préparation du formulaire▲
Préparez un formulaire Access (ou un UserForm) contenant une image nommée image0.
Nous allons devoir agir sur l'image dans différentes procédures ; l'objet oGdi (de type clGdiplus) sera donc créé en entête du module du formulaire pour pouvoir être accessible dans toutes les procédures du formulaire.
L'utilisation des barres de défilement implique de réagir à des événements ; l'objet oGdi est donc déclaré avec le mot-clé WithEvents.
Option
Explicit
'
Objet
clGdiplus
Private
WithEvents oGdi As
clGdiplus
X-B. Fonctionnement des barres de défilement de clGdiplus▲
Les barres de défilement vont par paire : une horizontale et une verticale.
Elles sont attachées à un objet clGdiplus, et il est possible de gérer plusieurs barres de défilements par objet.
Sur cette image on voit le contrôle image avec un fond blanc.
On a dessiné dessus deux images ayant chacune ses barres de défilement.
On a donc une image principale (celle dessinée dans le contrôle) et deux images secondaires (qui sont dessinées sur l'image principale).
Les barres de défilement sont attachées à l'image principale.
Le texte en bleu sur fond jaune est écrit une seule fois lors de l'initialisation de l'image.
Lors du défilement d'une image, seule cette image et ses barres de défilement sont redessinées.
Le reste de l'image est inchangé.
Mais nous allons commencer en douceur avec une seule image recouvrant tout le contrôle :
X-C. Une image recouvrant tout le contrôle▲
X-C-1. Initialisation des images et barres de défilement▲
Dans la procédure d'initialisation (UserForm_Initialize pour un UserForm, Sur ouverture ou Sur chargement pour un formulaire Access), il faut d'abord créer l'image principale et charger l'image secondaire.
Bien qu'on n'ait qu'une seule image, on en utilise deux :
- l'image principale est le reflet de ce qu'on verra à l'écran ;
- l'image secondaire est l'image complète que l'on dessine sur l'image principale.
Ensuite on crée un ensemble de barres de défilement.
'
Sur
chargement
du
formulaire
Private
Sub
UserForm_Initialize
(
)
'
Crée
l'objet
clGdiplus
Set
oGdi =
New
clGdiplus
'
Crée
l'image
principale
de
la
taille
du
contrôle
Image
If
oGdi.
CreateBitmapForControl
(
Me.
Image1
) Then
'
Remplit
l'image
de
blanc
oGdi.
Clear
vbWhite
'
Crée
une
image
secondaire
With
oGdi.
ImgNew
(
"
photo
"
)
'
Charge
le
fichier
image
dans
l'image
secondaire
.
LoadFile
oGdi.
ApplicationPath
&
"
P1010940.jpg
"
'
Crée
un
ensemble
de
barres
de
défilement
oGdi.
BarNew
'
Définit
la
barre
horizontale
oGdi.
BarScaleX
.
ImageWidth
'
Définit
la
barre
verticale
oGdi.
BarScaleY
.
ImageHeight
'
Définit
le
contrôle
source
d'événement
oGdi.
BarObject
=
Me.
Image1
End
With
End
If
'
Dessine
l'image
secondaire
et
les
barres
PaintImage
'
Dessine
l'image
sur
le
contrôle
oGdi.
RepaintNoFormRepaint
Me.
Image1
End
Sub
'
Dessine
l'image
secondaire
et
les
barres
Private
Sub
PaintImage
(
)
'
Dessine
l'image
en
mode
découpage
et
alignée
en
haut
à
gauche
'
La
position
de
dessin
est
fonction
des
positions
de
la
barre
de
défilement
oGdi.
DrawImg
"
photo
"
, oGdi.
BarStartX
, oGdi.
BarStartY
, , , , GdipSizeModeClip, GdipAlignTopLeft
'
Dessine
les
barres
oGdi.
BarDraw
End
Sub
'
Fermeture
du
formulaire
Private
Sub
Form_Close
(
)
'
Libère
l'objet
oGdi
Set
oGdi =
Nothing
End
Sub
Dans le code précédent on a trois procédures :
- UserForm_Initialize (ou Form_Load ou Form_Open pour Access) qui s'exécute à l'ouverture du formulaire ;
- PaintImage qui dessine l'image et les barres.
- UserForm_Terminate (ou Form_Unload ou Form_Close pour Access) qui s'exécute à la fermeture du formulaire pour libérer l'objet oGdi.
Il est important, voir indispensable, de libérer l'objet oGdi explicitement à la fermeture du formulaire.
Le fait d'avoir dans l'objet oGdi une référence à un contrôle du formulaire contenant cet objet oGdi (référence circulaire) risque de bloquer l'application lors de sa fermeture.
À l'ouverture du formulaire, on crée l'image principale avec CreateBitmapForControl.
Cette image est de la taille du contrôle image.
Puis on remplit l'image de blanc.
Ensuite on crée une image secondaire, c'est le rôle de la fonction ImgNew : l'image est nommée « photo ».
Cette image est chargée à partir d'un fichier à l'aide de la fonction LoadFile.
Les barres de défilement sont initialisées dans la suite avec les méthodes Bar* :
- BarNew initialise un nouvel ensemble de barres ;
- BarScaleX et BarScaleY définissent ensuite la taille totale du contenu à faire défiler : ici la largeur et la hauteur de l'image ;
- BarObject définit le contrôle qui recevra les événements de défilement.
Ces quatre méthodes sont obligatoires ; c'est le strict minimum pour gérer des barres de défilement.
Enfin on effectue le rendu avec la procédure PaintImage et on dessine l'image principale dans le contrôle avec RepaintNoFormRepaint.
La procédure de rendu PaintImage dessine l'image secondaire et les barres sur l'image principale.
L'image « photo » est d'abord dessinée à une position définie par les propriétés BarStartX et BarStartY.
On la dessine en mode découpage (GdipSizeModeClip) et alignée en haut à gauche (GdipAlignTopLeft).
L'image suivante illustre commence s'effectue ce rendu :
On termine enfin le rendu avec la méthode BardDraw qui dessine les deux barres horizontale et verticale.
X-C-2. Faire défiler l'image▲
Pour que les barres de défilement soient opérationnelles, il faut gérer un événement important : BarOnRefreshNeeded.
Pour créer la procédure gérant cet événement sélectionnez oGdi en haut à gauche de la barre de code, puis BarOnRefreshNeeded à droite.
Notez que l'objet oGdi est disponible dans la liste déroulante car il a été déclaré avec le mot-clé WithEvents.
'
Sur
demande
de
rafraichissement
des
barres
de
défilement
Private
Sub
oGdi_BarOnRefreshNeeded
(
BarName As
String
, MouseUp As
Boolean
)
'
Redesine
l'image
et
les
barres
à
nouvelle
position
PaintImage
'
Test
si
souris
relâchée
If
MouseUp Then
'
Dessin
permanent
si
souris
relâchée
oGdi.
RepaintNoFormRepaint
Me.
Image1
Else
'
Dessin
temporaire
si
souris
appuyée
oGdi.
RepaintFast
Me.
Image1
End
If
End
Sub
À chaque déplacement des barres de défilement à l'aide de la souris, cet événement est déclenché.
Il suffit alors d'exécuter la procédure de rendu PaintImage, puis de dessiner l'image principale sur le contrôle.
Pour plus de fluidité, on effectue un dessin temporaire avec RepaintFast si la souris est appuyée.
Lorsque la souris est relâchée, on effectue un dessin permanent.
Tout est maintenant opérationnel, on peut faire défiler l'image en cliquant sur les barres de défilement.
X-D. Plusieurs images sur un même contrôle▲
Commençons par placer la première image à un endroit précis de l'image principale.
Pour cela on définit la position des barres de défilement lors de l'appel de BarScaleX et BarScaleY :
- BarScaleX définit les positions gauche et droite ;
- BarScaleY définit les positions hautes et basses.
On reprend le code du chapitre précédent et on modifie ainsi :
'
Définit
la
barre
horizontale
oGdi.
BarScaleX
.
ImageWidth
, , 50
, 200
'
Définit
la
barre
verticale
oGdi.
BarScaleY
.
ImageHeight
, , 100
, 250
Ici on place notre image et ses barres dans un rectangle de coordonnées (50,100) et (200,250).
Ensuite il faut modifier la procédure de rendu PaintImage pour dessiner l'image au bon endroit, en ajoutant la position des barre de défilement BarLeft et BarTop :
'
Dessine
l'image
secondaire
et
les
barres
Private
Sub
PaintImage
(
)
'
Dessine
l'image
en
mode
découpage
et
alignée
en
haut
à
gauche
'
La
position
de
dessin
est
fonction
des
positions
de
la
barre
de
défilement
'
On
ajoute
la
position
des
barres
de
défilement
pour
afficher
l'image
au
bon
endroit
oGdi.
DrawImg
"
photo
"
, oGdi.
BarLeft
+
oGdi.
BarStartX
, oGdi.
BarTop
+
oGdi.
BarStartY
, , , , GdipSizeModeClip, GdipAlignTopLeft
'
Dessine
les
barres
oGdi.
BarDraw
End
Sub
Mais il y a un petit problème : l'image « déborde » :
Il faut en effet limiter le dessin pour qu'il ne dépasse pas.
La méthode DrawClipRectangle nous permet de définir sur l'image principale un rectangle dans lequel sera limité le dessin : les zones extérieures à ce rectangle ne seront pas dessinées.
Puis on annule cette limitation après avoir dessiner l'image secondaire avec la méthode DawClipRegion.
Pour faire la finition, on ajoute le dessin d'un rectangle qui encadre l'image et ses barres.
'
Dessine
l'image
secondaire
et
les
barres
Private
Sub
PaintImage
(
)
'
Limite
le
dessin
à
la
zone
souhaitée
oGdi.
DrawClipRectangle
oGdi.
BarLeft
, oGdi.
BarTop
, oGdi.
BarRight
, oGdi.
BarBottom
'
Dessine
l'image
en
mode
découpage
et
alignée
en
haut
à
gauche
'
La
position
de
dessin
est
fonction
des
positions
de
la
barre
de
défilement
'
On
ajoute
la
position
des
barres
de
défilement
pour
afficher
l'image
au
bon
endroit
oGdi.
DrawImg
"
photo
"
, oGdi.
BarLeft
+
oGdi.
BarStartX
, oGdi.
BarTop
+
oGdi.
BarStartY
, , , , GdipSizeModeClip, GdipAlignTopLeft
'
Retire
la
limite
de
dessin
oGdi.
DrawClipRegion
=
"
"
'
Dessine
les
barres
oGdi.
BarDraw
'
Dessine
en
rectangle
qui
encadre
l'image
et
ses
barres
oGdi.
DrawRectangle
oGdi.
BarLeft
, oGdi.
BarTop
, oGdi.
BarRight
, oGdi.
BarBottom
End
Sub
Et voici le résultat :
Nous souhaitons maintenant ajouter une deuxième image.
Dans le code d'initialisation, il faut créer deux images secondaires et deux ensembles de barres de défilement :
'
Sur
chargement
du
formulaire
Private
Sub
UserForm_Initialize
(
)
'
Crée
l'objet
clGdiplus
Set
oGdi =
New
clGdiplus
'
Crée
l'image
principale
de
la
taille
du
contrôle
Image
If
oGdi.
CreateBitmapForControl
(
Me.
Image1
) Then
'
Remplit
l'image
de
blanc
oGdi.
Clear
vbWhite
'
Ecrit
un
texte
en
haut
oGdi.
DrawText
"
Tutoriel
barres
de
défilement
"
, 20
, , 0
, 0
, oGdi.
ImageWidth
-
1
, 100
, , , vbBlue
, , vbYellow
, 150
'
Crée
une
image
secondaire
With
oGdi.
ImgNew
(
"
photo1
"
)
'
Charge
le
fichier
image
dans
l'image
secondaire
.
LoadFile
oGdi.
ApplicationPath
&
"
P1010940.jpg
"
'
Crée
un
ensemble
de
barres
de
défilement
oGdi.
BarNew
.
ImgName
'
Définit
la
barre
horizontale
oGdi.
BarScaleX
.
ImageWidth
, , 50
, 200
, .
ImgName
'
Définit
la
barre
verticale
oGdi.
BarScaleY
.
ImageHeight
, , 100
, 250
, .
ImgName
'
Dessine
l'image
secondaire
et
les
barres
PaintImage .
ImgName
End
With
'
Crée
une
image
secondaire
With
oGdi.
ImgNew
(
"
photo2
"
)
'
Charge
le
fichier
image
dans
l'image
secondaire
.
LoadFile
oGdi.
ApplicationPath
&
"
P1020363.jpg
"
'
Crée
un
ensemble
de
barres
de
défilement
oGdi.
BarNew
.
ImgName
'
Définit
la
barre
horizontale
oGdi.
BarScaleX
.
ImageWidth
, , 250
, 400
, .
ImgName
'
Définit
la
barre
verticale
oGdi.
BarScaleY
.
ImageHeight
, , 100
, 250
, .
ImgName
'
Dessine
l'image
secondaire
et
les
barres
PaintImage .
ImgName
End
With
'
Définit
le
contrôle
source
d'événement
(un
seul
contrôle
pour
les
deux
images)
oGdi.
BarObject
=
Me.
Image1
End
If
'
Dessine
l'image
sur
le
contrôle
oGdi.
RepaintNoFormRepaint
Me.
Image1
End
Sub
Nous ajoutons donc deux images secondaires « photo1 » et « photo2 ».
Pour chacune de ces images, on ajoute un ensemble de barres de défilement.
Dans les méthodes BarNew, BarScaleX et BarScaleY, il faut préciser un nom de barre.
Pour faciliter la suite on utilise le nom de chaque image (.ImgName) pour nommer chaque barre « photo1 » et « photo2 ».
Ensuite on dessine chacune des deux barres en exécutant la procédure de rendu PaintImage avec en paramètre le nom de l'image (nous allons ajouter ce paramètre ensuite dans la procédure).
Par contre BarObject n'est défini qu'une seule fois : il n'y a qu'un contrôle pour les deux images.
Notez qu'on en a profité pour ajouter un appel à DrawText afin d'écrire du texte en haut de l'image principale.
La procédure de rendu doit également être mise à jour pour dessiner une des deux images en ajoutant un paramètre pImage.
'
Dessine
l'image
secondaire
et
les
barres
Private
Sub
PaintImage
(
pImage As
String
)
'
Limite
le
dessin
à
la
zone
souhaitée
oGdi.
DrawClipRectangle
oGdi.
BarLeft
(
pImage), oGdi.
BarTop
(
pImage), oGdi.
BarRight
(
pImage), oGdi.
BarBottom
(
pImage)
'
Dessine
l'image
en
mode
découpage
et
alignée
en
haut
à
gauche
'
La
position
de
dessin
est
fonction
des
positions
de
la
barre
de
défilement
'
On
ajoute
la
position
des
barres
de
défilement
pour
afficher
l'image
au
bon
endroit
oGdi.
DrawImg
pImage, oGdi.
BarLeft
(
pImage) +
oGdi.
BarStartX
(
pImage), oGdi.
BarTop
(
pImage) +
oGdi.
BarStartY
(
pImage), , , , GdipSizeModeClip, GdipAlignTopLeft
'
Retire
la
limite
de
dessin
oGdi.
DrawClipRegion
=
"
"
'
Dessine
les
barres
oGdi.
BarDraw
pImage
'
Dessine
en
rectangle
qui
encadre
l'image
et
ses
barres
oGdi.
DrawRectangle
oGdi.
BarLeft
(
pImage), oGdi.
BarTop
(
pImage), oGdi.
BarRight
(
pImage), oGdi.
BarBottom
(
pImage)
End
Sub
DrawImg dessinera l'image passée en paramètre.
Les méthodes Bar* doivent toutes préciser le nom de la barre de défilement concernée.
Comme on a nommé nos barres comme nos images c'est facile.
Pour terminer on met à jour l'événement BarOnRefreshNeeded.
Celui-ci possède un paramètre BarName qui est le nom de la barre de défilement déplacée.
Il suffit donc de passer ce nom en paramètre de la procédure de rendu :
'
Sur
demande
de
rafraichissement
des
barres
de
défilement
Private
Sub
oGdi_BarOnRefreshNeeded
(
BarName As
String
, MouseUp As
Boolean
)
'
Redesine
l'image
et
les
barres
à
nouvelle
position
PaintImage BarName
'
Test
si
souris
relâchée
If
MouseUp Then
'
Dessin
permanent
si
souris
relâchée
oGdi.
RepaintNoFormRepaint
Me.
Image1
Else
'
Dessin
temporaire
si
souris
appuyée
oGdi.
RepaintFast
Me.
Image1
End
If
End
Sub
C'est enfin terminé :