Accueil
Rechercher:
sur developpez.com sur les forums
Forums | Tutoriels | F.A.Q's | Participez | Hébergement | Contacts
Accueil Conception Java DotNET Visual Basic  C  C++ Delphi MS-Office SQL & SGBD Oracle  4D  Business Intelligence
Club Emploi Blogs   TV   Dév. Web PHP XML Python Autres 2D-3D-Jeux Sécurité Windows Linux PC Mac
FORUM WIN FORUM DEV WIN FORUM .NET F.A.Qs TUTORIELS LIVRES WINDOWS

La GDI

Date de publication : 30 avril 2008

Par Jessee Edouard (Accueil)
 

Ce tutoriel présente quelques concepts avancés de la GDI.

               Version hors-ligne

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:
        /* Centrer le repère */
        center.x = LOWORD(lParam) / 2;
        center.y = HIWORD(lParam) / 2;
        SetViewportOrgEx(hDC, center.x, center.y, NULL);
        break;
        
    case WM_PAINT:
        BeginPaint(hwnd, &ps);
        /* Dans le mode MM_LOMETRIC, l'unité est le dixième de mm donc 100 correspond à 1 cm */
        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);
            
            /* Création et sélection de la police */
            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);
            
            /* Couleur du texte */
            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:
        {
            /* Chargement de l'image */
            hBitmap = LoadImage(NULL, "c:\\image.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
            GetObject(hBitmap, sizeof(bitmap), &bitmap);
            
            /* DC de la fenêtre */
            hDC = GetDC(hwnd);
            
            /* DC en mémoire */
            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 :

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

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

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

  1. On inverse les bits du masque de façon à avoir un rectangle blanc et une ellipse en noir.

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

  3. 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);
            
            /* On prépare l'image */
            hdcImage = CreateCompatibleDC(hDC);
            hImage = LoadImage(NULL, "c:\\image.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
            
            SelectObject(hdcImage, hImage);
            
            GetObject(hImage, sizeof(bmImage), &bmImage);
            
            /* On prépare le masque */
            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);
            
            /* On masque l'image */
            BitBlt(hdcImage, 0, 0, bmImage.bmWidth, bmImage.bmHeight, hdcMasque, 0, 0, SRCAND);
            
            /* Rectangle blanc + ellipse noire */
            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.



               Version hors-ligne

Valid XHTML 1.1!Valid CSS!

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.

Responsables bénévoles de la rubrique Windows : Aymeric Morilleau et Louis-Guillaume Morand - Contacter par EMail :
Vos questions techniques : forum d'entraide Windows - Publiez vos articles, tutoriels et cours
et rejoignez-nous dans l'équipe de rédaction du club d'entraide des développeurs francophones
Nous contacter - Copyright © 2000-2008 www.developpez.com - Legal informations.