Texel |
|
Les bases de la programmation Windows Chapitre III
Utilisation de la GDI GDI (Graphics Device Interface) est une interface de périphérique graphique. C'est lui qui permet l'affichage de tout ce qui est graphique à l'écran et sur imprimante (ou traceur). Il possède de nombreuses fonctions: affichage de pixels, textes, bitmaps …Il peut même être utilisé avec des librairies graphiques tel que DirectX. Nous ne parlerons ici que du tracé sur écran, même si bon nombre des fonctions présentées peuvent être utiles pour d'autres périphériques. Je vous conseil d'avoir à porté de main le code source d'exemple de ce chapitre pour bien comprendre ce qui va suivre. Cliquez ici
I. Le contexte de périphérique Lorsque vous dessinez sur l'écran ou que vous lancez une impression, vous utilisez un périphérique matériel (carte graphique, imprimante…). Dans vos programmes, vous devez demander un handle de contexte de périphérique (Device context en anglais) qui vous autorise à utiliser ce périphérique. Ce contexte de périphérique contient des attributs qui définissent par exemple la couleur du texte courante. Des fonctions permettent de changer les attributs définis dans le contexte de périphérique. Vous n'avez donc pas à redéfinir à chaque fois les attributs du texte ou du tracé que vous affichez (puisqu'ils sont sauvegardés jusqu'à la fin de votre application).
Pour obtenir un handle de contexte de périphérique, il existe deux méthodes. 1) Première méthode La première méthode est utilisée lors de la réception du message WM_PAINT. Ce message était traité par notre précédente application par la fonction DefWindowProc. Ce message est envoyé par Windows à notre application lorsque la fenêtre doit être redessinée (Ex: Après un chevauchement de fenêtre ou un agrandissement). Dans notre programme précédent, DefWindowProc gérait ce message en redessinant simplement la fenêtre vide. Si l'on veut afficher quelque chose dans notre fenêtre, il faut profiter de ce rafraîchissement pour appeler à chaques mise à jours les fonctions d'affichage de texte et d'image. Mais il faut pour cela obtenir un HDC.
Vous devez commencer par déclarer un contexte de périphérique, de type HDC (H pour handle et DC pour Device Context), au début de votre procédure de fenêtre ou juste avant d'appeler BeginPaint.
Déclarez ensuite une structure "d'information de dessin" de type PAINTSTRUCT. Les champs de cette structure sont remplis par défaut par Windows, mais vous pouvez modifier ses trois premiers champs.
Le champ rcPaint est le plus important. C'est une structure de type RECT contenant les coordonnées des sommets d'un rectangle. Ses champs sont left, top, right, et bottom. Ce rectangle contient les limites de la zone à redessiner, appelée zone invalide, dans la zone client (Windows envoi le message WM_PAINT à chaque fois qu'une zone est invalide, jusqu'à ce que la fonction ValideRect ou les fonctions BeginPaint et EndPaint la valide). Si vous ne touchez pas au champ rcPaint Windows le calcul.
Tout le code permettant d'afficher tous les objets graphiques de votre fenêtre doit être placé entre BeginPaint et EndPaint pour pouvoir être redessiner par Windows. Lorsque c'est DefWindowProc qui gère le message WM_PAINT, il utilise aussi BeginPaint et EndPaint (en interne). Après avoir obtenu un handle de contexte de périphérique avec BeginPaint, il ne faut pas oublier de le libérer immédiatement après avec EndPaint avant de quitter la procédure de fenêtre. Ces deux fonctions prennent en argument l'handle de la fenêtre et une structure PAINTSTRUCT. EndPaint retourne un BOOL pour indiquer une réussite ou un échec. Presque n'importe où dans votre programme (dans WinMain, procédure de fenêtre ou vos propres fonctions), vous pouvez utiliser la fonction InvalidateRect et une structure RECT pour redessiner la partie de la zone cliente que vous souhaitez (vous invalidez alors une zone). Le message WM_PAINT sera alors appelé et BeginPaint rafraîchira l'intérieur du rectangle.
Si vous envoyer la valeur NULL en deuxième argument à la fonction, c'est toute la fenêtre qui sera rafraîchie. 2) Deuxième méthode Elle utilise deux fonctions:
Ces fonctions peuvent être appelées presque n'importe où dans votre programme (après que votre fenêtre est été crée biensûr) et n'ont pas besoin d'être utilisées avec un message WM_PAINT. Mais dans ce cas ATTENTION: si vous avez écrit un texte ou affiché une image au début de votre programme et si la fenêtre est redessinée par Windows (Ex: à cause d'un chevauchement), les objets graphiques seront effacés. Il vous faut donc trouver un moyen de redessiner vos graphismes en rappelant ces fonctions (en les mettant par exemple dans une boucle qui rappel les fonctions en permanance. cf fin du chapitre 2 ou tutorial sur les compteurs).
GetDC et ReleaseDC reçoivent en argument l'handle d'une fenêtre. La seconde fonction nécessite aussi l'handle de contexte de périphérique. D'autres variantes comme GetWindowDC accède à l'intégralité de votre fenêtre, même les barres de titres, contrairement à GetDC qui n'accède qu'à la zone cliente. CreateDC est aussi utilisable pour dessiner sur tout l'écran (même en dehors de votre fenêtre). II. L'affichage de texte. 1) La fonction TextOut Cette fonction est la plus utiliser pour afficher du texte. Elle s'insère après GetDC ou BeginPaint, comme toutes les fonctions GDI. La voici:
Toutes les fonctions GDI ont besoin de l'handle du contexte de périphérique.
Les coordonnées du texte correspondent par défaut au coin supérieur gauche du premier caractère.
Pour le paramètre lpString, vous pouvez mettre la chaîne de caractère entre guillemets ou passer en argument un tableau.
Pour la longueur de la chaîne, utilisez tout simplement la fonction C: strlen. 2) La fonction SetTextAlign Vous pouvez parfois avoir besoin de placer votre texte sur l'extrémité droite de l'écran. Précédez alors TextOut par la fonction SetTextAlign qui définit, grâce à son deuxième argument, à quel emplacement du texte correspondent les coordonnées x et y de TextOut.
Les drapeaux TA_RIGHT | TA_TOP indique que les coordonnées x et y correspondent au coin supérieur droit de la chaîne de caractère.
3) Les couleurs SetTextColor change la couleur de votre texte.
COLORREF est un entier long non signé de 32 bits qui sert à contenir les valeurs de 0 à 255 des trois couleurs primaires (rouge, bleu, vert) qui forment chaques couleurs. Il est plus simple d'envoyer la macro RGB pour le deuxième argument de SetTextColor. Cette macro à trois arguments pour le rouge, le vert, le bleu: RGB(nombre de 0 à 255, idem, idem). Exemple: SetTextColor(hdc,RGB(0,255,0)) affichera le texte en rouge très saturé.
SetBkColor change la couleur de fond située derrière le texte.
SetBkMode permet d'avoir une couleur de fond transparente grâce au drapeau TRANSPARENT. Mais le drapeau OPAQUE est aussi disponible.
III. L'affichage de pixels. Pour afficher un pixel à l'écran c'est encore plus simple que d'afficher du texte. Ici aussi une fonction suffit:
Rien de bien nouveau à signaler. Noter tout de même que la fonction retourne la couleur utilisée par Windows pour afficher le pixel. En effet, la couleur souhaitée n'est pas toujours disponible et Windows choisit alors la couleur la plus proche. Si vous n'avez pas besoin de cette information, utilisez donc la fonction SetPixelV. Elle a exactement les mêmes paramètres mais ne retourne rien. Elle est de ce fait un peut plus rapide à l'exécution.
Il est possible que vous ayez besoin de connaître la couleur d'un pixel sur une coordonnée. Utilisez la fonction GetPixel:
Il existe bien sûr des fonctions pour tracer des figures toutes faites (Rectangle, RoundRect, Ellipse, Arc, Pie, Chord, Polyline …). LineTo trace une ligne de la position courante (modifiable par MoveToEx) jusqu'aux coordonnées passées à la fonction. IV. afficher une image bitmap Pour afficher une image bitmap, c'est un peut plus compliquer que pour afficher un pixel. Voici les différentes étapes: _ Obtenir un handle de bitmap. _ Obtenir un handle de contexte de périphérique pour l'écran. _ Obtenir un autre handle de contexte de périphérique de mémoire. Il servira à avoir une surface d'écran "virtuelle" en mémoire qui ne sera pas affichée. _ Charger le bitmap dans le contexte de périphérique de mémoire. _ Transférer l'image du contexte de périphérique de mémoire au contexte de périphérique de l'écran (l'image apparaît). _ Libérer les handles de contexte de périphérique et de bitmap. Commencez d'abord par déclarer un handle de bitmap de type HBITMAP. Ensuite, pour l'obtenir, il existe deux fonctions possibles. La plus simple utilise LoadBitmap qui est du même genre que LoadCursor avec les mêmes paramètres. Mais LoadImage est plus intéressante:
Cette fonction retourne un handle de type HANDLE alors que l'handle de notre bitmap est de type HBITMAP. Il faudra donc faire un forçage de type (en mettant HBITMAP entre parenthèse juste avant la fonction).
Le premier argument doit être fixé à NULL pour charger un ficher bitmap externe à l'application. Si vous chargez votre image depuis un fichier ressource inclus dans l'exécutable, utilisez l'handle de votre application. L'image à afficher n'est pas forcement un bitmap, ce peut être un curseur de souris ou un icône. Envoyez la constante IMAGE_BITMAP au troisième argument dans notre situation. Fixer la largeur et la hauteur du bitmap à zero pour avoir la taille d'origine du bitmap. Pour le dernier paramètre, il existe plusieurs drapeaux dont: LR_CREATEDIBSECTION : Ce drapeau permet d'obtenir l'image avec ses propres propriétés plutôt que d'utiliser des propriétés définit par le périphérique. LR_LOADFROMFILE : Ce drapeau est à utiliser lorsque vous charger l'image depuis un fichier externe à votre exécutable. Maintenant, il faut créer un contexte de périphérique pour l'écran comme vous avez appris à le faire précédemment (cf un peu plus haut). Puis, pour le contexte de périphérique de mémoire, il faut utiliser la fonction CreateCompatibleDC:
Comme son nom l'indique, le contexte de périphérique créé est compatible avec un autre contexte de périphérique qui n'est autre que celui passer en argument à la fonction. Si l'argument est NULL vous créer un contexte de périphérique compatible avec l'écran (et c'est ce que l'on veut). Maintenant que vous avez une surface mémoire vide, il faut charger le bitmap dans cette surface par la fonction SelectObject:
Cette fonction n'est pas utilisée uniquement pour afficher des bitmaps, mais pour nous HGDIOBJ est un handle de type HBITMAP. Pas besoin de forçage de type ici. Pour afficher le bitmap à sur l'écran on utilise la fonction BitBlt ou l'une de ses variantes. Ces fonctions demandent en argument la largeur de l'image bitmap source. Donc avant de parlé de ces fonctions, nous allons nous intéresser à la fonction GetObject qui obtient des informations sur l'image et les copie dans une structure de type…BITMAP.
Pour le deuxième argument, il faut utiliser la fonction sizeof sur la structure BITMAP. Voici justement la structure:
On ne va pas ici donner des explications sur les différents champs de la structure. Ce qui nous intéresse, c'est la largeur et la hauteur du bitmap. Maintenant, on va transférer l'image de la surface mémoire du contexte de périphérique (qui est la source) vers la surface de l'écran (qui est la destination) pour qu'on puis enfin la voir. Pour cela, on utilise la fonction BitBlt:
La structure BITMAP est utilisée pour le quatrième et le cinquième argument de la fonction. Pour le dernier argument il y a de nombreux drapeaux pour par exemple inverser les couleurs (cf. win32.hlp). Le plus simple est SRCCOPY qui effectue un transfert direct entre les surfaces. Notez que l'image source peut provenir de la capture d'une partie de votre propre écran. Les fonctions LoadImage, SelectObject, GetObject sont alors inutiles et CreateCompatibleDC est remplacer par GetDC ou GetWindowDC. BitBlt ne peut pas modifier la taille d'une image avant de l'afficher, contrairement à StretchBlt:
Comme vous le voyez, la fonction reçoit les dimensions d'origine et les dimensions souhaitées. Si le signe d'une de ces dimensions est négatives, l'image sera retournée horizontalement ou verticalement. N'oubliez pas de libérer les contextes de périphériques mémoire avec la fonction DeleteDC après son utilisation (et non pas ReleaseDC puisqu'on à pas d'handle de fenêtre pour celui-ci).
De même, supprimez l'image bitmap avec DeleteObject avant de quitter le programme:
Conclusion: Vous remarquerez rapidement lors de vos tests que vous ne pouvez pas afficher d'objet à l'extrémité d'une fenêtre si une partie de l'objet est en dehors de la fenêtre. Ce n'est pas un bogue. C'est normal. Il s'agit d'un phénomène appelé clipping. Pour régler le problème, il suffit de découper l'objet pour n'afficher de celui-ci que la partie située dans la fenêtre. Vous pouvez télécharger le code source d'un programme utilisant tous ce que nous avons vu ici. Modifiez le et essayez d'autres fonctions décrites dans les fichiers d'aides. Vous connaissez assez de chose maintenant sur l'API WIN32 pour envisager de commencer à programmer avec DirectX ou OpenGL. Néanmoins le prochain chapitre sur les ressources risque de vous être très utile dans l'avenir. |