La GDIDate de publication : 30 avril 2008
Par
Jessee Edouard (Accueil)
Ce tutoriel présente quelques concepts avancés de la GDI.
I. Les polices de caractères
I-A. Attributs d'un DC
I-B. Récupérer des informations sur le périphérique
I-C. Les polices de caractères
II. Les images bitmaps
II-A. Introduction
II-B. Charger une image puis l'afficher
II-C. Afficher une image par copie de bits
II-C-1. L'astuce
II-C-2. Les fonctions
II-C-3. Exemple
II-D. Les masques bitmaps
III. Les rectangles et les régions
III-A. Les rectangles
III-A-1. Définition
III-A-2. Initialisation
III-A-3. Copie et comparaison
III-A-4. Modifier un rectangle
III-A-5. Opérations ensemblistes
III-A-5-a. Intersection
III-A-5-b. Union
III-A-5-c. Soustraction
III-A-5-d. Appartenance d'un point à un rectangle
III-A-6. Dessiner
III-B. Les régions
IV. La transparence
IV-A. Bitmaps avec un fond transparent
IV-B. Créer des fenêtres non rectangulaires
IV-C. Régler la transparence d'une fenêtre
V. Remerciements
I. Les polices de caractères
I-A. Attributs d'un DC
Les attributs d'un DC définissent la manière dont comment les objets (c'est-à-dire les
dessins) seront dessinés sur ce périphérique. Le crayon, la brosse et la police de caractères
utilisés en sont des exemples mais ne sont pas les seuls. Parmi tant d'autres, ce qui nous
intéressera ici, avec les polices bien sûr, est le Mapping Mode.
Le Mapping Mode définit les caractéristiques planimétriques du DC. Un mode est caractérisé
par une unité de longueur (l'unité logique), l'orientation des axes x et y et le
point Origine. Dans le mode par défaut, à savoir MM_TEXT, l'unité logique est le pixel et
les axes sont orientés vers la droite pour l'axe des x et vers le bas pour l'axe des y. On peut
changer de mode avec la fonction SetMapMode :
int SetMapMode(HDC hdc, int fnMapMode);
|
Le tableau suivant liste les différents modes existants.
| Mode |
Unité logique |
Sens des x croissants |
Sens des y croissants |
| MM_TEXT |
1 px |
Vers la droite |
Vers le bas |
| MM_LOMETRIC |
0.1 mm |
Vers la droite |
Vers le haut |
| MM_HIMETRIC |
0.01 mm |
Vers la droite |
Vers le haut |
| MM_LOENGLISH |
0.01 '' |
Vers la droite |
Vers le haut |
| MM_HIENGLISH |
0.001 '' |
Vers la droite |
Vers le haut |
| MM_TWIPS |
1 / 1440 '' |
Vers la droite |
Vers le haut |
| MM_ISOTROPIC |
A définir |
A définir |
A définir |
| MM_ANISOTROPIC |
A définir |
A définir |
A définir |
L'utilisation de cette fonction est un peu délicate. Cela vient notamment du fait que la fenêtre
et le repère (qui intervient dans les fonctions de dessin ...) n'utilisent pas nécessairement le
même mode. En effet, sauf si on a spécifié MM_ISOTROPIC ou MM_ANISOTROPIC,
SetMapMode change uniquement le mode du repère et non celui de la fenêtre. Par défaut
(et quel que soit le mode utilisé), le point origine de la fenêtre (Window Origin)
est le coin supérieur gauche de la zone dessinable (c'est-à-dire la zone cliente ou la fenêtre toute entière même
selon ce que vous permet votre DC ...) de la fenêtre.
L'origine du repère (Viewport Origin) est, par défaut, confondu avec l'origine de la fenêtre
mais on peut bien sûr la déplacer. Les fonctions permettant de connaître / déplacer l'origine de la fenêtre ou
l'origine de du repère sont :
BOOL GetWindowOrgEx(HDC hdc, LPPOINT lpPoint);
BOOL GetViewportOrgEx(HDC hdc, LPPOINT lpPoint);
BOOL SetWindowOrgEx(HDC hdc, int x, int y, LPPOINT lpOldOrg);
BOOL SetViewportOrgEx(HDC hdc, int x, int y, LPPOINT lpOldOrg);
|
Toutes les distances sont exprimées en unités logiques de la fenêtre (je rappelle que le mode
initial est toujours MM_TEXT). En général, lorsqu'on veut choisir une nouvelle origine,
on modifie celle du repère plutôt que celle de la fenêtre, mais ce n'est évidemment pas une règle.
L'extrait de code suivant montre un exemple d'utilisation du mode MM_LOMETRIC pour dessiner un cercle
de 1 cm de rayon au centre de la fenêtre (plus précisément de la zone cliente).
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HINSTANCE hInstance;
static HDC hDC;
static POINT center;
PAINTSTRUCT ps;
switch(message)
{
case WM_CREATE:
hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
hDC = GetDC(hwnd);
SetMapMode(hDC, MM_LOMETRIC);
break;
case WM_SIZE:
center.x = LOWORD(lParam) / 2;
center.y = HIWORD(lParam) / 2;
SetViewportOrgEx(hDC, center.x, center.y, NULL);
break;
case WM_PAINT:
BeginPaint(hwnd, &ps);
Ellipse(hDC, -100, 100, 100, -100);
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
ReleaseDC(hwnd, hDC);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0L;
}
|
Les modes MM_ISOTROPIC et MM_ANISOTROPIC permettent au programmeur de définir
un mode personnalisé. Dans le mode MM_ISOTROPIC, l'unité de longueur suivant l'axe des
x doit égal à l'unité de longueur suivant l'axe des y (comme tel est le cas dans les autres
modes) quel que soit le point de vue (fenêtre ou repère) alors que dans le mode
MM_ANISOTROPIC, on est libre de personnaliser à volonté. Les fonctions suivantes s'utilisent
avec ces deux modes pour définir les unités de longueur et du coup l'orientation des axes
(il suffit d'utiliser les nombres négatifs pour inverser l'orientation d'un axe) :
BOOL SetWindowExtEx(HDC hdc, int nXExtent, int nYExtent, LPSIZE lpOldExtent);
BOOL SetViewportExtEx(HDC hdc, int nXExtent, int nYExtent, LPSIZE lpOldExtent);
|
I-B. Récupérer des informations sur le périphérique
La fonction GetDeviceCaps permet d'obtenir des informations (telles que la version du
pilote, le type de périphérique, etc.) sur un périphérique graphique.
int GetDeviceCaps(HDC hdc, int nIndex);
|
| Paramètre (nIndex) |
Description |
| HORZRES |
Résolution horizontale (en pixels) |
| VERTRES |
Résolution verticale (en pixels) |
| LOGPIXELSX |
Résolution horizontale (en pixels / pouce) |
| LOGPIXELSY |
Résolution verticale (en pixels / pouce) |
Pour récupérer la résolution de l'écran (en pixels), ou pourra également utiliser la fonction :
int GetSystemMetrics(int nIndex);
|
En passant en paramètre SM_CXSCREEN pour obtenir la résolution horizontale et SM_CYSCREEN pour
obtenir la résolution verticale.
I-C. Les polices de caractères
Une police de caractères est un jeu de caractères et de symboles partageant un design
commun. Par abus du langage, un élément d'une police est également appelé une police. Le
paramètre qui différencie toute police de caractères donnée d'une autre est son nom (face
name). Après avoir choisi une police, on peut généralement, si on le souhaite, ajuster d'autres
paramètres comme sa taille ou son style par exemple. La structure LOGFONT permet de
décrire une police de caractères.
typedef struct tagLOGFONT {
LONG lfHeight;
LONG lfWidth;
LONG lfEscapement;
LONG lfOrientation;
LONG lfWeight;
BYTE lfItalic;
BYTE lfUnderline;
BYTE lfStrikeOut;
BYTE lfCharSet;
BYTE lfOutPrecision;
BYTE lfClipPrecision;
BYTE lfQuality;
BYTE lfPitchAndFamily;
TCHAR lfFaceName[LF_FACESIZE];
} LOGFONT;
|
Le membre lfHeight spécifie la taille, en unités logiques, de la police de caractères. Or
généralement, on souhaite plutôt la spécifier en points. Dans ce cas, il faut juste faire un petit
calcul. En mode MM_TEXT par exemple, on a la formule :
nPixels = - (nPoints * GetDeviceCaps(hDC, LOGPIXELSY)) / 72
|
En effet 1 point = 1 / 72 pouce. Et comme l'axe des y est orienté vers le bas,
GetDeviceCaps(hDC, LOGPIXELSY) retourne un nombre négatif, ce qui explique la
présence du facteur -1. En fait ce -1 c'est seulement pour les matheux parce qu'en fait, il est
tout à fait légitime de passer une valeur négative à lfHeight (auquel cas la valeur absolue sera prise en compte).
Le membre lfWidth spécifie la largeur moyenne des caractères, généralement définie comme
étant la largeur du caractère 'x'. Il peut être tout simplement laissé à zéro. Le membre
lfWeight spécifie l'épaisseur des caractères, il doit être compris entre 0 et 1000. On a
également les constantes FW_DONTCARE (0), FW_THIN (100), FW_NORMAL (400),
FW_BOLD (700), FW_HEAVY (900), etc.
Voici un exemple de création et d'utilisation d'une police de caractères
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HDC hDC;
PAINTSTRUCT ps;
static HFONT hFont;
switch(message)
{
case WM_CREATE:
{
LOGFONT font;
hDC = GetDC(hwnd);
ZeroMemory(&font, sizeof(font));
strcpy(font.lfFaceName, "Monotype Corsiva");
font.lfHeight = - (24 * GetDeviceCaps(hDC, LOGPIXELSY)) / 72;
font.lfUnderline = (BYTE)TRUE;
hFont = CreateFontIndirect(&font);
SelectObject(hDC, hFont);
SetTextColor(hDC, RGB(255, 0, 0));
break;
}
case WM_PAINT:
{
BeginPaint(hwnd, &ps);
TextOut(hDC, 30, 30, "Hello world !", 13);
EndPaint(hwnd, &ps);
break;
}
case WM_DESTROY:
{
ReleaseDC(hwnd, hDC);
DeleteObject(hFont);
PostQuitMessage(0);
break;
}
default:
{
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
return 0L;
}
|
Et pour calculer la largeur et la hauteur d'une chaîne de caractères
dans la police courante, on a la fonction :
BOOL GetTextEntentPoint32(HDC hdc, LPCTSTR lpString, int cbString, LPSIZE lpSize);
|
II. Les images bitmaps
II-A. Introduction
Une image bitmap (carte de bits) est une image constituée à partir d'un tableau de n lignes et
m colonnes, chaque cellule du tableau contenant une information spécifiant la couleur du
point correspondant. Un fichier bitmap (.bmp) est un fichier qui stocke une telle
image, en la faisant précéder d'un en-tête indiquant des informations qu'il faut savoir
concernant l'image (dimensions, nombre de bits de couleur, etc.) et le fichier lui-même (mot
magique, offset à partir duquel commencent les bits de l'image, etc.). Si on connaît donc le
format exact des fichiers bitmaps, on peut charger les bits constituant l'image pour en faire
ensuite ce qu'on veut, comme afficher l'image par exemple. Mais la GDI possède des
fonctions permettant de manipuler les images bitmaps sans avoir à connaître leur format.
Dans ce tutoriel, ce sont ces fonctions que nous allons utiliser.
II-B. Charger une image puis l'afficher
Pour charger une image bitmap nous avons le choix entre LoadBitmap qui sait seulement
charger une image depuis une ressource et LoadImage qui sait également charger une image
depuis un fichier. Dans tous les cas, on obtient toujours en retour un handle d'un bitmap
(HBITMAP).
HANDLE LoadImage( HINSTANCE hInstance, LPCTSTR lpszName, UINT uType,
int cxDesired, int cyDesired, UINT fuLoad );
|
hInstance spécifie évidemment le handle du module contenant la ressource à charger et si on
passe une valeur différente de NULL, le paramètre lpszName doit spécifier le nom de l'image
que l'on veut charger. Si hInstance vaut NULL et que fuLoad vaut LR_LOADFROMFILE,
alors lpszName doit spécifier le nom du fichier à charger. Pour une image bitmap (uType =
IMAGE_BITMAP), il est inutile de spécifier les paramètres cxDesired (largeur de l'image)
et cyDesired (hauteur de l'image) puisque ces informations peuvent être lues dans le fichier.
On peut donc tout simplement mettre 0.
Pour afficher l'image, le plus simple est d'utiliser la fonction DrawState.
BOOL DrawState( HDC hdc, HBRUSH hBrush, DRAWSTATEPROC lpDstProc,
LPARAM lData, WPARAM wData, int x, int y, int cx, int cy,
UINT fuFlags );
|
Dans le cas d'une image bitmap (fuFlags = DST_BITMAP), le paramètre lData doit spécifier
le handle du bitmap qu'on veut afficher. Les paramètres cx et cy permettent de spécifier
respectivement la largeur et la hauteur de l'image mais bien entendu, DrawState sait récupérer
ces informations sans qu'on ait à les lui fournir nous-mêmes. On peut donc ici aussi passer
tout simplement 0.
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HDC hDC;
PAINTSTRUCT ps;
static HBITMAP hBitmap;
switch(message)
{
case WM_CREATE:
{
hDC = GetDC(hwnd);
hBitmap = LoadImage(NULL, "c:\\image.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
break;
}
case WM_PAINT:
{
BeginPaint(hwnd, &ps);
DrawState(hDC, NULL, NULL, (LPARAM)hBitmap, 0, 20, 20, 0, 0, DST_BITMAP);
EndPaint(hwnd, &ps);
break;
}
case WM_DESTROY:
{
ReleaseDC(hwnd, hDC);
DeleteObject(hBitmap);
PostQuitMessage(0);
break;
}
default:
{
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
return 0L;
}
|
II-C. Afficher une image par copie de bits
II-C-1. L'astuce
Il s'agit d'une technique simple, élégante et puissante pour afficher une image bitmap. Elle
consiste à créer un DC en mémoire dont la seule contrainte est d'avoir les mêmes
caractéristiques que le DC de destination, de sélectionner l'image sur ce DC (le DC en
mémoire), et de copier tous les bits du DC en mémoire vers le DC de destination à l'aide de la
fonction BitBlt (Bit-Block Transfert).
II-C-2. Les fonctions
La fonction :
HDC CreateCompatibleDC(HDC hdc);
|
permet de créer un DC en mémoire possédant les mêmes caractéristiques que le DC spécifié
en argument. Sa surface sera initialement restreinte à un tout petit bitmap monochrome de 1
px de largeur et de hauteur. Donc avant de dessiner sur un DC en mémoire, il faut lui octroyer
une surface assez grande pour contenir tout notre dessin en sélectionnant une image bitmap.
Une fois qu'on a sélectionné un bitmap, grâce à la fonction SelectObject, le DC devient aussi
grand que l'image sélectionnée. On peut désormais dessiner tout ce qu'on veut à l'intérieur de
cette surface, comme avec n'importe quel DC. Il suffit ensuite de copier l'image pixel par
pixel, du DC en mémoire vers un autre DC (associé à une fenêtre par exemple), à l'aide de la
fonction BitBlt (voir aussi : StretchBlt).
BOOL BitBlt( HDC hdcDest, int xDest, int yDest, int nWidth, int nHeight,
HDC hdcSource, int xSource, int ySource, DWORD dwOperation );
|
Dans le cas d'une opération de copie, la valeur du paramètre dwOperation doit être égale à
SRCCOPY. Les paramètres nWidth et nHeight spécifient respectivement la largeur et la
hauteur de l'image à copier. La fonction GetObject permet de récupérer des informations sur
un objet graphique dont le handle est passé en argument.
int GetObject(HGDIOBJ hgdiobj, int cbObjectInfo, LPVOID lpObjectInfo);
|
Si hgdiobj est un handle d'un crayon (HPEN), lpObjectInfo doit pointer sur une structure de
type LOGPEN. Si c'est un handle d'une police (HFONT), lpObjectInfo doit pointer sur une
structure de type LOGFONT. Et ainsi de suite. Dans tous les cas, cbObjectInfo doit indiquer
la taille de l'objet pointé par lpObjectInfo. Pour un bitmap, cet objet doit être une structure de
type BITMAP. En ce qui nous concerne, seuls les membres bmWidth et bmHeight de cette
structure, qui spécifient respectivement la largeur et la hauteur de l'image, nous intéressent.
Et enfin, il ne faut pas oublier de détruire le DC en mémoire avec DeleteDC lorsqu'on n'en a
plus besoin.
II-C-3. Exemple
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HDC hDC, hdcMem;
PAINTSTRUCT ps;
static HBITMAP hBitmap;
static BITMAP bitmap;
switch(message)
{
case WM_CREATE:
{
hBitmap = LoadImage(NULL, "c:\\image.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
GetObject(hBitmap, sizeof(bitmap), &bitmap);
hDC = GetDC(hwnd);
hdcMem = CreateCompatibleDC(hDC);
SelectObject(hdcMem, hBitmap);
break;
}
case WM_PAINT:
{
BeginPaint(hwnd, &ps);
BitBlt(hDC, 20, 20, bitmap.bmWidth, bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
break;
}
case WM_DESTROY:
{
ReleaseDC(hwnd, hDC);
DeleteDC(hdcMem);
DeleteObject(hBitmap);
PostQuitMessage(0);
break;
}
default:
{
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
return 0L;
}
|
II-D. Les masques bitmaps
Un masque est un objet qui permet de cacher certaines parties d'un dessin. Par exemple,
supposons qu'on ait d'un côté un dessin sur papier et de l'autre côté un carton de même
dimension que le papier, comportant un trou au milieu. Si on place le carton au dessus du
papier, on ne voit plus que la partie du dessin que nous laisse voir le trou. Le papier représente
l'image et le carton le masque. En masquant l'image avec le masque, on obtient une nouvelle
image (le résultat du masquage).
La technique de masquage consiste à réaliser des opérations bit à bit avec les bits des couleurs
de l'image à masquer et ceux du masque. En RGB, le code de la couleur noir est 0 partout et
celui de la couleur blanc des 1 partout. Ces couleurs sont donc très pratiques pour contrôler
les parties d'une image qu'on veut cacher ou afficher. La fonction BitBlt nous sera ici encore
d'une grande utilité. En spécifiant SRCAND dans le paramètre dwOperation par exemple,
cette fonction permet de mélanger les couleurs de la source et de la destination à l'aide de
l'opérateur ET. Avec SRCPAINT les couleurs sont mélangées en utilisant l'opérateur OU.
SRCCOPY, comme nous l'avons déjà vu un peu plus haut, permet de copier la source en
écrasant la destination. On a aussi NOTSRCCOPY qui permet de copier la source avec tous
les bits inversés. Et il y en a encore d'autres. Par exemple, pour afficher une image à
l'intérieur d'une ellipse, voici comment on fait :
- On charge l'image qu'on veut afficher, on crée un DC en mémoire compatible avec le
DC de destination puis on sélectionne l'image sur ce DC. Maintenant, l'image
n'attend donc plus qu'à être masquée.
- On crée un bitmap vierge compatible avec le DC cible (celui de la fenêtre) de même type et de même dimension que l'image à masquer
(CreateCompatibleBitmap) ainsi qu'un nouveau DC en mémoire. On sélectionne
bien sûr le bitmap sur ce DC, ensuite on dessine un rectangle noir puis une ellipse en
blanc. Le masque est alors prêt.
- Il ne nous reste plus qu'à masquer l'image avec l'opération ET. On peut maintenant
afficher l'image résultante.
Si on affiche l'image, on aura un rectangle noir et une ellipse affichant une partie de l'image
qu'on a masquée. Or le plus souvent, on ne veut pas avoir ce rectangle noir (ni un rectangle
blanc ...). Ce qu'on veut, c'est une ellipse affichant une partie de l'image masquée et le fond de
la fenêtre, et rien de plus. Pour avoir le résultat voulu, on affichera donc l'image à l'aide de la
méthode suivante :
- On inverse les bits du masque de façon à avoir un rectangle blanc et une ellipse en noir.
- On copie l'image ainsi obtenue vers le DC de la fenêtre en appliquant l'opération ET.
On a donc maintenant une ellipse en noir dessinée sur le fond de la fenêtre. Tout ce
qui était en blanc a disparu grâce au ET.
- On copie enfin l'image masquée (le rectangle noir avec une ellipse affichant une partie
de l'image originale) vers le DC de la fenêtre à l'aide de l'opération OU.
Voici un exemple de code :
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HDC hDC, hdcImage, hdcMasque;
static HBITMAP hImage, hMasque;
static BITMAP bmImage;
PAINTSTRUCT ps;
switch(message)
{
case WM_CREATE:
{
hDC = GetDC(hwnd);
hdcImage = CreateCompatibleDC(hDC);
hImage = LoadImage(NULL, "c:\\image.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
SelectObject(hdcImage, hImage);
GetObject(hImage, sizeof(bmImage), &bmImage);
hdcMasque = CreateCompatibleDC(hDC);
hMasque = CreateCompatibleBitmap(hDC, bmImage.bmWidth, bmImage.bmHeight);
SelectObject(hdcMasque, hMasque);
SelectObject(hdcMasque, GetStockObject(BLACK_BRUSH));
Rectangle(hdcMasque, 0, 0, bmImage.bmWidth - 1, bmImage.bmHeight - 1);
SelectObject(hdcMasque, GetStockObject(WHITE_BRUSH));
Ellipse(hdcMasque, 0, 0, bmImage.bmWidth - 1, bmImage.bmHeight - 1);
BitBlt(hdcImage, 0, 0, bmImage.bmWidth, bmImage.bmHeight, hdcMasque, 0, 0, SRCAND);
BitBlt(hdcMasque, 0, 0, bmImage.bmWidth, bmImage.bmHeight, hdcMasque, 0, 0, NOTSRCCOPY);
break;
}
case WM_PAINT:
{
BeginPaint(hwnd, &ps);
BitBlt(hDC, 0, 0, bmImage.bmWidth, bmImage.bmHeight, hdcMasque, 0, 0, SRCAND);
BitBlt(hDC, 0, 0, bmImage.bmWidth, bmImage.bmHeight, hdcImage, 0, 0, SRCPAINT);
EndPaint(hwnd, &ps);
break;
}
case WM_DESTROY:
{
DeleteObject(hMasque);
DeleteObject(hImage);
DeleteDC(hdcMasque);
DeleteDC(hdcImage);
ReleaseDC(hwnd, hDC);
PostQuitMessage(0);
break;
}
default:
{
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
return 0L;
}
|
III. Les rectangles et les régions
III-A. Les rectangles
III-A-1. Définition
Un rectangle est tout simplement un objet représenté par une structure de type RECT définie
comme suit :
typedef struct _RECT {
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT;
|
Les rectangles sont utilisés dans de nombreuses fonctions comme AdjustWindowRect,
InvalidateRect ou encore GetClientRect. Mais ce qu'il faut également connaître, c'est
qu'on peut faire des opérations (intersection, union, etc.) sur les rectangles. Les paragraphes
suivants discutent des fonctions permettant de manipuler les rectangles.
III-A-2. Initialisation
La fonction :
BOOL SetRect(LPRECT lpRect, int left, int top, int right, int bottom);
|
Permet de remplir les champs d'un rectangle par les valeurs passées en arguments. Pour mettre
tous les champs à zéro, on pourra également utiliser la fonction :
BOOL SetRectEmpty(LPRECT lpRect);
|
La fonction IsRectEmpty permet de connaître si un rectangle est vide.
III-A-3. Copie et comparaison
La fonction :
BOOL CopyRect(LPRECT lprcDst, CONST RECT * lprcSrc);
|
Permet de copier les valeurs des champs du rectangle pointé par lprcSrc vers le rectangle
pointé par lprcDst.
Pour comparer deux rectangles, on a la fonction :
BOOL EqualRect(CONST RECT * lprc1, CONST RECT * lprc2);
|
Qui retourne VRAI si les deux rectangles sont identiques.
III-A-4. Modifier un rectangle
Les fonctions OffsetRect et InflateRect permettent respectivement de déplacer et de
redimensionner un rectangle.
BOOL OffsetRect(LPRECT lpRect, int dx, int dy);
BOOL InflateRect(LPRECT lpRect, int dx, int dy);
|
III-A-5. Opérations ensemblistes
III-A-5-a. Intersection
L'intersection de deux rectangles est le rectangle qui contient tous les points communs entre
les deux rectangles. Si les deux rectangles n'ont aucun point commun, leur intersection sera
vide. La fonction :
BOOL IntersectRect(LPRECT lprcDst, CONST RECT * lprcSrc1, RECT * lprcSrc2);
|
Permet de calculer l'intersection de deux rectangles.
III-A-5-b. Union
L'union de deux rectangles est le plus petit rectangle capable de les contenir.
BOOL UnionRect(LPRECT lprcDst, CONST RECT * lprcSrc1, RECT * lprcSrc2);
|
III-A-5-c. Soustraction
La soustraction d'un ensemble B d'un ensemble A donne un ensemble A - B (ou A\B) formé
de tous les points de A qui n'appartiennent pas à B. Si A et B sont des rectangles, la
soustraction n'est définie que si A - B est aussi un rectangle. Sinon le résultat sera A.
BOOL SubtractRect(LPRECT lprcDst, CONST RECT * lprcSrc1, RECT * lprcSrc2);
|
III-A-5-d. Appartenance d'un point à un rectangle
La fonction :
BOOL PtInRect(CONST RECT * lpRect, POINT pt);
|
Permet de connaître si le point pt appartient au rectangle pointé par lpRect. Attention ! L'intérieur
d'un rectangle est défini comme étant tout ce qui est à l'intérieur ou sur son périmètre, excluant les points
se trouvant sur les côtés droit et bas. C'est aussi pour cela que les fonctions telles que Rectangle ou
FillRect n'incluent pas ces côtés.
III-A-6. Dessiner
On peut dessiner un rectangle à l'aide de la fonction Rectangle mais on a également les
fonctions FillRect et FrameRect qui acceptent un pointeur vers un rectangle en argument.
int FillRect(HDC hDC, CONST RECT * lpRect, HBRUSH hBrush);
int FrameRect(HDC hDC, CONST RECT * lpRect, HBRUSH hBrush);
|
Ces fonctions permettent de dessiner un rectangle en utilisant la brosse spécifiée en argument.
FillRect dessine un rectangle plein alors que FrameRect rectangle dessine le périmètre
uniquement avec une épaisseur de 1 unité logique. Remarquez bien que FrameRect utilise une
brosse et non un crayon pour dessiner le rectangle.
III-B. Les régions
Une région est un objet (qui peut être un rectangle, une ellipse, etc.) que l'on peut dessiner
mais aussi et surtout utiliser à d'autres fins. Une région peut être créée à l'aide d'une variété
de fonctions, qui retournent toutes un handle d'une région (HRGN), dont voici les plus utilisées :
HRGN CreateRectRgn(int left, int top, int right, int bottom);
HRGN CreateEllipticRgn(int left, int top, int right, int bottom);
HRGN CreateRoundRectRgn(int left, int top, int right, int bottom, int nWidthEllipse, int nHeightEllipse);
HRGN CreatePolygonRgn(CONST POINT * lppt, int cPoints, int fnPolyFillMode);
HRGN CreateRectRgnIndirect(CONST RECT * lpRect);
HRGN CreateEllipticRgnIndirect(CONST RECT * lpRect);
|
Après avoir créé des régions, on peut les combiner à l'aide de la fonction :
int CombineRgn(HRGN hrgnDest, HRGN hrgnSrc1, HRGN hrgnSrc2, int fnCombineMode);
|
Le paramètre fnCombineMode permet de spécifier la manière dont comment on veut
combiner les régions. On a le choix entre RGN_COPY (dans ce cas le paramètre hrgnSrc2
est ignoré), RGN_AND, RGN_OR, RGN_DIFF et RGN_XOR.
Et enfin on peut dessiner une région (usuellement avec FillRgn), déplacer une région (OffsetRgn),
restreindre la partie dessinable d'un DC à une région (SelectObject), restreindre la partie
visible d'une fenêtre à une région (SetWindowRgn), etc.
BOOL OffsetRgn(HRGN hRgn, int dx, int dy);
int FillRgn(HDC hDC, HRGN hRgn, HBRUSH hBrush);
int SetWindowRgn(HWND hWnd, HRGN hRgn, BOOL bRedraw);
|
Dans SetWindowRgn, le paramètre bRedraw doit être mis à TRUE pour redessiner
immédiatement la fenêtre après avoir appliqué la nouvelle région.
Lorsqu'on n'en a plus besoin, il ne faut pas oublier de détruire la région avec DeleteObject.
IV. La transparence
IV-A. Bitmaps avec un fond transparent
Les images bitmaps en elles-même ne gèrent pas la transparence. De plus, une image bitmap est toujours rectangulaire.
Le rectangle qui contient toute l'image est appelé le fond, la couleur de ce rectangle est donc évidemment ce
qu'on appelle la couleur de fond de l'image. Si cette couleur est utilisée pour et uniquement pour le fond,
alors on peut imaginer une multitude de techniques permettant d'afficher l'image sans le fond, comme si le fond de
l'image était réellement transparent. Depuis Windows 98 et 2000, Microsoft a introduit la fonction TransparentBlt
qui fait déjà tout le boulot cependant, son implémenation sous Win9x contient, hélas, une fuite de mémoire ce qui fait que,
finalement, cette fonction ne devrait être utilisée que sous Windows 2000 et plus récents. Pour l'utiliser, il faut se lier
avec msimg32.lib.
BOOL TransparentBlt( HDC hdcDest, int xDest, int yDest, int nWidthDest, int nHeightDest,
HDC hdcSrc, int xSrc, int ySrc, int nWidthSc, int nHeightSrc,
UINT crTransparent );
|
Où crTransparent spécifie bien sûr la couleur de transparence (dans notre cas, la couleur de fond).
IV-B. Créer des fenêtres non rectangulaires
Comme nous l'avons déjà vu plus haut la fonction SetWindowRgn permet de limiter la partie visible d'une fenêtre à une région
donnée ce qui permet déjà de créer des fenêtres non rectangulaires. Mais parfois, on veut faire encore plus compliqué, comme par exemple
créer une fenêtre ajustée exactement au contour d'une image (en excluant le fond). Cela est possible grâce à la fonction
SetLayeredWindowAttributes, disponible qu'à partir de Windows 2000. Pour l'utiliser, il faut définir la macro _WIN32_WINNT à 0x0500
avant d'inclure windows.h. Cette macro permet d'inclure ou d'exclure certaines définitions et/ou déclarations en fonction de la configuration requise par
l'application. 0x0500 signifie que l'application requiert la version 5.0 de Window NT (c'est-à-dire Windows 2000). Donc vous l'avez maintenant compris, la macro
_WIN32_WINNT permet d'inclure certaines définitions et/ou déclarations de fonctionnalités spécifiques à Windows NT (0x0500 pour Windows 2000,
0x0501 pour XP, 0x0600 pour Vista, etc.) et d'exclure les définitions et/ou déclarations incompatibles ou non supportées par la version spécifiée. Mais il y en a
également d'autres comme _WIN32_WINDOWS qui permet de choisir la version de Windows 3.x ou 9x et WINVER qui permet de contrôler les définitions et/ou
déclarations communes à toutes les versions de Windows. Bien entendu, si une application utilise des fonctionnalités spécifiques à Windows NT, elle ne pourra pas
fonctionner sous Windows 3.x ou 9x et vice versa.
BOOL SetLayeredWindowAttributes(HWND hWnd, COLORREF crKey, BYTE bAlpha, DWORD dwFlags);
|
Le paramètre crKey permet de spécifier la couleur de transparence. Toute partie de la fenêtre peinte avec cette couleur deviendra transparente. Lorsque ce paramètre
est utilisé, il faut l'indiquer dans dwFlags en spécifiant la valeur LWA_COLORKEY. Si le paramètre bAlpha est utilisé, LWA_ALPHA doit être également spécifié.
A noter que les deux paramètres (crKey et bAlpha) sont indépendants (on peut donc utiliser l'un sans l'autre).
Cette fonction ne peut être utilisée que sur des fenêtres possédant le style étendu WS_EX_LAYERED. Typiquement, la fenêtre ne comporte ni bordure ni barre de titre
(juste le style WS_POPUP). Conséquence immédiate, il faut soi-même gérer les messages de la souris pour pouvoir contrôler la fenêtre (déplacer, réduire, fermer, etc.).
Les menus contextuels (et plus généralement les menus) seront abordés dans le prochain tutoriel.
IV-C. Régler la transparence d'une fenêtre
Il peut également arriver que l'on désire avoir une fenêtre ni complètement invisible (transparente) ni complètement opaque. C'est là qu'intervient le troisième paramètre de
la fonction SetLayeredWindowAttributes, sachant que 0 permet d'avoir une fenêtre invisible et 255 une fenêtre opaque.
V. Remerciements
Un grand merci à vicenzo pour ses conseils et son soutien.
 
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 oeuvre intellectuelle protégée par les droits d'auteurs. Copyright ©
2008 Melem. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
Cette page est déposée à la
SACD.
|