Office : Utilisation de la classe clGdiplus

Thierry GASPERMENT

Dans cet article nous allons parcourir les principales fonctionnalités de clGdiplus.

clGdiplus est un module de classe VBA pour faciliter l'utilisation de la librairie graphique gdiplus.dll (GDI+).

Consultez la page dédiée à clGdiplus ; vous y trouverez la référence des méthodes et propriétés, ainsi que de nombreux tutoriels et exemples.

- Titre : Office : Utilisation de la classe clGdiplus

- Auteur : Thierry GASPERMENT

- Parution : 15 mai 2008

- Mise à jour : 15 avril 2018

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2011 Thierry GASPERMENT. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.

Sommaire

- I. Introduction à GDI+

- II. Exemples d'utilisations

- III. Installation et paramétrage

- IV. Système de coordonnées

- V. Traitement d'images

- V-A. Chargement d'un fichier

- V-B. Lecture d'informations

- V-C. Sauvegarde de l'image

- V-D. Modification de l'image

- V-D-1. Transformations

- V-D-2. Couleurs

- V-D-3. Dessin

- V-D-3-a. Précision : Long ou Single

- V-D-3-b. Dessin de formes

- V-D-3-c. Dessin de texte

- VI. Charger une image

- VII. Créer une image vierge

- VIII. Image dans un formulaire ou UserForm

- VIII-A. Repaint

- VIII-B. RepaintFast

- VIII-C. RepaintNoFormRepaint

- IX. Interaction avec la souris

- IX-A. Préparation du formulaire

- IX-B. Chargement de l'image avec GDI+

- IX-C. Capture de la sélection à la souris

- IX-C-1. Sur souris appuyée

- IX-C-2. Sur souris déplacée

- IX-C-3. Sur souris relâchée

- IX-D. Recadrage de l'image

- X. Barres de défilement

- X-A. Préparation du formulaire

- X-B. Fonctionnement des barres de défilement de clGdiplus

- X-C. Une image recouvrant tout le contrôle

- X-C-1. Initialisation des images et barres de défilement

- X-C-2. Faire défiler l'image

- X-D. Plusieurs images sur un même contrôle

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 !

Montage avec plusieurs images et du texte.

Horloge analogique interactive.

Carte de France interactive.

Affichage d'images avec barres de défilement.

Formulaire avec une forme personnalisée.

Et des jeux !

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 :

Matrice identité

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 :

Inverse la transparence

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.

Appliquer 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.

Charge le contenu du contrôle Image0

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.

Création d'une image en définissant chaque pixel

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).

Charge une image à partir d'un champ pièce-jointe

' 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.

Chargement d'une image 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 » :

Chargement d'une image du menu (anciens menus)

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).

Pour Access

Private Sub Form_Load()
…
End Sub

Pour les autres applications

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.

Transparence pour Access

' 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 :

Pseudo-transparence pour les UserForm

' 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.

Déclarations

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 :

Chargement de l'image

' 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.

1. Sur souris appuyée, nous sauvegardons les coordonnées dans gX1 et gY1.

2. Sur souris déplacée, nous sauvegardons les coordonnées dans gX2 et gY2 et dessinons le rectangle sur l'image.

3. 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

' 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

' 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 :

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

' 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.

Recadrage

' 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.

Déclarations

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é :