Index   Table   Précédent   Suivant

7. Dessins en Xlib

Dans ce chapitre, nous présentons le widget Canvas, qui permet de faire des dessins en Xlib et de réagir au clavier et à la souris.

Vous n'avez pas besoin de connaître déjà Xlib pour aborder ce chapitre : les notions nécessaires sont présentées au fur et à mesure.

Index   Table   Précédent   Suivant

7.1. Courte présentation de Xlib

Le système X-Window (ou X11) est le standard d'affichage graphique du monde Unix ; c'est lui qui permet entre autre l'affichage distant, avec un modèle client-serveur et un protocole réseau.

X11 repose sur un modèle hiérarchique de zones rectangulaires, appelées "Window" :

  1. Chaque Window peut être inclus dans un autre Window (son propriétaire) et peut inclure d'autre Window (ses fils) ; des Window qui ont le même propriétaire sont des frères (sibling).
  2. L'écran lui-même est un Window (le Root Window) qui contient tous les Window.
  3. Un Window peut être devant ou derrière un Window frère ; le Window qui est devant masque tout ou partie de celui qui est derrière.
  4. Tout dessin fait dans un Window est "coupé" automatiquement, c'est à dire que seule la partie du dessin à l'intérieur du Window est tracé.
  5. Un Window peut être apparent ("mappé") ou caché ; les ordres de dessins faits dans un Window non mappé sont ignorés.
  6. Chaque évènement (clavier, souris, etc) est adressé à un Window en particulier.
  7. Un Window ne mémorise en principe pas son contenu : chaque fois qu'il doit être réaffiché, il reçoit un évènement "Expose", lui demandant de redessiner tout son contenu.
On voit donc que le système X11 est relativement "bas niveau" mais qu'il est spécialement pensé pour écrire par dessus des Window-Manager et des Toolkits (tel que Helium).

La librairie standard de X11 est Xlib ; elle comporte des centaines de fonctions et des dizaines de types, pour gérer les Window, les évènements, le clavier et la souris, l'affichage de dessins, de texte, d'images et de couleurs, etc. Helium en exploite un certain nombre pour le dessin automatique des widgets.

Index   Table   Précédent   Suivant

7.2. Le widget Canvas

Le widget Canvas que nous présentons dans cette section est un widget dans lequel vous pouvez faire vos propres dessins en Xlib, et où vous pouvez réagir à un certain nombre d'évènements souris et clavier.

En fait, le widget Canvas est tout simplement un Window de Xlib, avec des évènements filtrés par Helium : toutes les fonctions de dessin de Xlib sont utilisables (avec naturellement le coupage automatique), et la gestion des évènements est très simplifiée (mais on a accès aux "vrais" évènements X11 si on le désire).

Pour créer un Canvas on fait

    He_node *canvas;
    canvas = HeCreateCanvas (frame);
frame est le Frame qui hébergera le Canvas. Par défaut, le Canvas est placé en 0,0 du Frame, et son aspect est un rectangle blanc, bordé d'un fin trait noir. Pour dessiner dedans, on attache une callback RepaintProc :
    HeSetCanvasRepaintProc (canvas, canvas_repaint_proc);
Le prototype de la callback est
    void canvas_repaint_proc (He_node *hn, Window win);
hn est le Canvas et win est son Window X11. Chaque fois que le Canvas doit être réaffiché, Helium appelle la RepaintProc en lui passant le Canvas hn et le Window win dans lequel dessiner. N'importe quelle fonction de dessin de Xlib peut être appelée dans la RepaintProc, comme dans l'exemple suivant :

examples/canvas/dessins.c
    /* examples/canvas/dessins.c */
    
    #include <helium.h>
    
    He_node *princ, *canvas;
    
    void canvas_repaint_proc (He_node *hn, Window win)
    {
        printf("Appel de canvas_repaint_proc\n");
        XSetForeground (he_display, he_gc, he_black);
        XDrawLine (he_display, win, he_gc, 
            10, 10, 290, 290);
        XDrawRectangle (he_display, win, he_gc, 
            10, 10, 280, 280);
        XDrawArc (he_display, win, he_gc, 
            10, 10, 280, 280, 0, 360*64);
    }
    
    int main (int argc, char *argv[])
    {
        HeInit (&argc, &argv);
    
        princ = HeCreateFrame();
        HeSetFrameLabel (princ, "Dessins");
    
        canvas = HeCreateCanvas (princ);
        HeSetCanvasRepaintProc (canvas, canvas_repaint_proc);
    
        HeSetWidth (canvas, 300); HeSetHeight (canvas, 300);
        HeFit (princ);
    
        return HeMainLoop (princ);
    }

Toutes les fonctions de dessin de Xlib (XDraw*, XFill*, XSet*) sont documentées avec la commande Unix man ; les plus courantes sont expliquées dans la section « 7.4. Fonctions de dessins de Xlib ».

Les variables he_display, he_gc, he_black et he_white sont des variables globales de Helium :

win est l'identificateur du Window dans lequel on va dessiner (ici, le Window du Canvas). On le passe donc aux fonctions de dessin.

Les coordonnées dans le Canvas varient entre 0,0 (coin en haut à gauche) et width-1,height-1. Le repère est orienté vers le bas, en "coordonnées souris". Le Canvas fait le coupage des dessins qui sont faits ; autrement dit, aucun dessin ne peut dépasser le Canvas, et on n'a pas besoin de faire ce genre de test.

Index   Table   Précédent   Suivant

7.3. Dessins en couleur

Dans cette section, on explique comment faire des dessins en couleur dans un Canvas avec Xlib. Dans l'exemple précédent dessins.c, la RepaintProc commence par ces lignes :
    XSetForeground (he_display, he_gc, he_black);
    XDrawLine (he_display, win, he_gc, 10, 10, 290, 290);
On demande à Xlib de fixer la couleur de dessin à he_black ; tous les dessins qui suivent sont faits dans cette couleur, jusqu'au prochain appel à XSetForeground.

La variable de couleur transmise à XSetForeground est un index de couleur, codée sur un int. Pour obtenir un index pour une couleur R,G,B ou une couleur H,S,V on appelle

    int HeAllocRgb (int R, int G, int B, int defaut);
    int HeAllocHsv (int H, int S, int V, int defaut);
defaut est l'index de la couleur par défaut si l'appel échoue. L'action de ces fonctions dépend du mode d'affichage du serveur X11.

En mode PseudoColor (jusqu'à 8 plans), le nombre de couleurs simultanées est limité (256 pour 8 plans), et chaque allocation "consomme" une case de couleur ; lorsque plus aucune case n'est disponible, les appels à HeAlloc... échouent. (Remarque : les couleurs utilisées sont rendues au système à la terminaison du programme).

En mode TrueColor (15, 16, 24 ou 32 plans), l'index est une simple combinaison calculée entre les bits r, g et b ; l'appel ne peut échouer.

En général on alloue donc les couleur nécessaires au début du programme et on les mémorise dans des variables globales ou un tableau.

Dans l'exemple suivant, on alloue 4 couleurs et on dessine un carré.

examples/canvas/couleur.c
    /* examples/canvas/couleur.c */
    
    #include <helium.h>
    
    He_node *princ, *canvas;
    int rouge, vert, bleu, gris;
    
    void init_couleurs ()
    {
        rouge = HeAllocRgb (255, 0, 0, he_black);
        vert  = HeAllocRgb (0, 255, 0, he_black);
        bleu  = HeAllocRgb (0, 0, 255, he_black);
        gris  = HeAllocRgb (150, 150, 150, he_black);
    }
    
    void dessin_ligne (Window win, int x1, int y1, int x2, int y2, int coul)
    {
        XSetForeground (he_display, he_gc, coul);
        XDrawLine (he_display, win, he_gc, x1, y1, x2, y2);
    }
    
    void canvas_repaint_proc (He_node *hn, Window win)
    {
        dessin_ligne (win, 50, 50, 250, 50, rouge);
        dessin_ligne (win, 250, 50, 250, 250, vert);
        dessin_ligne (win, 250, 250, 50, 250, bleu);
        dessin_ligne (win, 50, 250, 50, 50, gris);
    }
    
    int main (int argc, char *argv[])
    {
        HeInit (&argc, &argv);
    
        princ = HeCreateFrame();
        HeSetFrameLabel (princ, "carré en couleur");
    
        canvas = HeCreateCanvas (princ);
        HeSetCanvasRepaintProc (canvas, canvas_repaint_proc);
        init_couleurs ();
    
        HeSetWidth (canvas, 300); HeSetHeight (canvas, 300);
        HeFit (princ);
    
        return HeMainLoop (princ);
    }

Remarque : l'appel à HeAllocHsv est plus coûteux que HeAllocRgb, car il passe par une conversion de HSV en RGB avant d'appeler HeAllocRgb. Dans la suite on parlera des fonctions fournies par Helium pour convertir des couleurs.

Index   Table   Précédent   Suivant

7.4. Fonctions de dessins de Xlib

Toutes les fonctions de dessin de Xlib reçoivent toujours les mêmes trois premiers paramètres, qui sont : he_display, win, he_gc (le Display, qui mémorise les caractéristiques de l'écran sur lequel afficher ; le Window dans lequel dessiner ; les attributs de dessin courants, qui évitent d'avoir à passer un trop grand nombre de paramètres aux fonctions de dessin de Xlib).

Les coordonnées que l'on donnent sont toujours par rapport au coin en haut à gauche du Canvas (0,0), avec le repère vers le bas (c'est à dire en coordonnées souris). Enfin on rappelle que le coupage des dessins est automatique dans le Window.

Pour dessiner un point x1,y1 on fait

    XDrawPoint (he_display, win, he_gc, x1, y1);
Pour dessiner une ligne d'un point x1,y1 à un point x2,y2 on fait
    XDrawLine (he_display, win, he_gc, x1, y1, x2, y2);
Pour dessiner un rectangle de coin en haut à gauche x1,y1 et de coin en bas à droite x2,y2 on fait
    XDrawRectangle (he_display, win, he_gc, x1, y1, x2-x1, y2-y1);
En effet, les deux derniers paramètres sont la largeur et la hauteur du rectangle. On peut aussi tracer un rectangle plein avec XFillRectangle. Attention, les deux derniers paramètres ne sont pas tout à fait les mêmes, il faut faire :
    XFillRectangle (he_display, win, he_gc, 
                    x1, y1, x2-x1+1, y2-y1+1);
Pour tracer un cercle ou une ellipse, on donne les coordonnées des coins du rectangle qui contient le cercle ou l'ellipse (la boîte englobante), soit x1,y1 le coin en haut à gauche et x2,y2 le coin en bas à droite :
    XDrawArc (he_display, win, he_gc, 
              x1, y1, x2-x1, y2-y1, 0, 360*64);
L'avant dernier paramètre correspond à l'angle de départ du tracé, en 64èmes de degrés, par rapport à l'horizontale de droite, dans le sens inverse des aiguilles d'une montre. Le dernier paramètre est l'angle de tracé par rapport à l'angle de départ (et pas par rapport à l'horizontale), toujours en 64èmes de degrés. On peut donc tracer un morceau d'arc au lieu de tracer une ellipse pleine. On peut aussi tracer un cercle plein, une ellipse pleine, ou un camembert plein, avec :
    XFillArc (he_display, win, he_gc, 
              x1, y1, x2-x1+1, y2-y1+1, 0, 360*64);
Attention, comme pour XFillRectangle, il faut rajouter 1 à la hauteur et la largeur de la boîte englobante.

On peut aussi afficher du texte dans un Canvas : les fonctions sont décrites dans la section « 7.11. Afficher du texte ».

Les autres fonctions de dessin de Xlib sont documentées dans les pages de man ; si j'en oublie, prévenez-moi ! Les voici :

XDrawArcs, XDrawLines, XDrawPoints, XDrawRectangles, XDrawSegments, XFillArcs, XFillPolygon, XFillRectangles, XSetArcMode, XSetLineAttributes, XSetDashes, XSetFillRule, XSetFillStyle, XSetClipMask, XSetClipRectangles, XSetRegion, XUnionRectWithRegion, XSetClipOrigin, XSetTile, XSetStipple, XQueryBestSize, XQueryBestTile, XQueryBestStipple, XSetFunction, XSetPlaneMask, XCopyPlane, XCopyArea, XCreateGC, XChangeGC, XGetGCValues.

Index   Table   Précédent   Suivant

7.5. Évènements dans un Canvas

Pour recevoir des évènements dans un Canvas, il suffit d'attacher une callback EventProc avec
    HeSetCanvasEventProc (canvas, canvas_event_proc);
le prototype de la callback est
    void canvas_event_proc (He_node* hn, He_event *hev);
hn est le Canvas et hev contient les caractéristiques de l'évènement. Cette callback est appelée par Helium chaque fois que l'un des évènements X11 suivants arrive au Canvas :

Le type He_event est défini dans include/types.h :
    typedef struct He_event_st {
        int     type,           /* Type d'évènement = xev->type */
                sx, sy,         /* Coords souris / window */
                sb;             /* Bouton souris filtré : 0,1,2,3 */
        Time    time;           /* Temps en milli secondes */
        Window  win;            /* Le Window X11 du widget */
        XEvent *xev;            /* Evènement X11 complet */
        char    str[256];       /* Buffer lu au clavier */
        int     len;            /* Nombre de char dans str */
        KeySym  sym;            /* Symbole de la touche pressée */
    } He_event;
Dans l'exemple suivant on affiche tous les évènements :

examples/canvas/event.c
    /* examples/canvas/event.c */
    
    #include <helium.h>
    
    He_node *princ, *canvas;
    
    void canvas_repaint_proc (He_node *hn, Window win)
    {
        printf ("canvas_repaint_proc\n");
    }
    
    void canvas_event_proc (He_node *hn, He_event *hev)
    {
        printf ("canvas_event_proc ");
    
        switch (hev->type) {
            case EnterNotify :
                printf ("EnterNotify   %d,%d %d\n", hev->sx, hev->sy, hev->sb);
                break;
            case LeaveNotify :
                printf ("LeaveNotify   %d,%d %d\n", hev->sx, hev->sy, hev->sb);
                break;
            case ButtonPress :
                printf ("ButtonPress   %d,%d %d\n", hev->sx, hev->sy, hev->sb);
                break;
            case ButtonRelease :
                printf ("ButtonRelease %d,%d %d\n", hev->sx, hev->sy, hev->sb);
                break;
            case MotionNotify :
                printf ("MotionNotify  %d,%d %d\n", hev->sx, hev->sy, hev->sb);
                break;
            case KeyPress :
                printf ("KeyPress: \"%s\" keysym = XK_%s len = %d\n",
                    hev->str, XKeysymToString(hev->sym), hev->len);
                break;
            case KeyRelease :
                printf ("KeyRelease\n");
                break;
        }
    }
    
    int main (int argc, char *argv[])
    {
        HeInit (&argc, &argv);
    
        princ = HeCreateFrame();
        HeSetFrameLabel (princ, "Évènements du canvas");
    
        canvas = HeCreateCanvas (princ);
        HeSetCanvasRepaintProc (canvas, canvas_repaint_proc);
        HeSetCanvasEventProc (canvas, canvas_event_proc);
    
        HeSetWidth (canvas, 300); HeSetHeight (canvas, 300);
        HeFit (princ);
    
        return HeMainLoop (princ);
    }

Remarques :

Index   Table   Précédent   Suivant

7.6. Canvas redimensionné

A la création du Canvas, et chaque fois que la taille du Canvas change, la callback ResizeProc du Canvas est automatiquement appelée (si définie), juste avant que la RepaintProc ne soit appelée à son tour. Pour attacher une callback ResizeProc au Canvas on fait
    HeSetCanvasResizeProc (canvas, canvas_resize_proc);
Le prototype de la callback est
    void canvas_resize_proc (He_node *hn, int width, int height);
hn est le Canvas, width et height sont les nouvelles dimensions.

Un bon endroit où changer la taille d'un Canvas est la ResizeProc d'un Frame. On a alors le schéma suivant : l'utilisateur redimensionne une fenêtre à la souris, ce qui déclenche l'appel de la ResizeProc du Frame. Dans cette callback on change la taille du Canvas, ce qui déclenche l'appel de la ResizeProc puis de la RepaintProc du Canvas.

Dans l'exemple suivant on crée une fenêtre avec deux Panels et un Canvas. Dans le Panel 1 on place un bouton Quit, et dans le Panel 2 on place un Message ; entre les deux Panel on place le Canvas, qui doit occuper toute la place disponible. Lorsqu'on redimensionne la fenêtre, les Panels et le Canvas sont mis à la nouvelle largeur ; le Panel 2 est abaissé, et la hauteur du Canvas est ajustée pour occuper tout l'espace disponible. Depuis la ResizeProc du Canvas on affiche chaque fois la nouvelle taille du Canvas dans le Message.

examples/canvas/taille.c
    /* examples/canvas/taille.c */
    
    #include <helium.h>
    
    He_node *princ, *panel1, *panel2, *canvas, *mess1;
    
    void princ_resize_proc (He_node *hn, int width, int height)
    {
        /* ajuste largeurs panels */
        HeSetWidth (panel1, width);
        HeSetWidth (panel2, width);
    
        /* met le panel2 en bas */
        HeJustify (panel2, NULL, HE_BOTTOM);
        
        /* le canvas prend toute la place disponible */
        HeExpand (canvas, panel2, HE_BOTTOM);
        HeExpand (canvas, NULL, HE_RIGHT);
    }
    
    void canvas_resize_proc (He_node *hn, int width, int height)
    {
        char bla[100];
        sprintf (bla, "Canvas %d x %d", width, height);
        HeSetMessageLabel (mess1, bla);
    }
    
    void butt_proc (He_node *hn)
    {
        HeQuit(0);
    }
    
    int main (int argc, char *argv[])
    {
        HeInit (&argc, &argv);
    
        princ = HeCreateFrame();
        HeSetFrameLabel (princ, "Canvas redimensionnée");
        HeSetFrameResizeProc (princ, princ_resize_proc);
    
        panel1 = HeCreatePanel (princ);
        HeCreateButtonP (panel1, "Quit", butt_proc, NULL);
        HeFit(panel1);
    
        canvas = HeCreateCanvas (princ);
        HeJustify (canvas, panel1, HE_TOP);
        HeSetWidth (canvas, 300);
        HeSetHeight (canvas, 300);
        HeSetCanvasResizeProc (canvas, canvas_resize_proc);
    
        panel2 = HeCreatePanel (princ);
        HeJustify (panel2, canvas, HE_TOP);
    
        mess1 = HeCreateMessageP (panel2, NULL, FALSE);
        HeFit(panel2);
    
        HeFit (princ);
    
        return HeMainLoop (princ);
    }

Remarques :

La ResizeProc du Canvas est un bon endroit pour recalculer par exemple les coordonnées de points de contrôle, stockées dans un vecteur, pour mettre un dessin à la nouvelle échelle du Canvas. Dans l'exemple suivant, on fait un dessin à la souris et on le mémorise ; on le remet à l'échelle chaque fois qu'on redimensionne la fenêtre ; lorsqu'un bouton est pressé on réinitialise le dessin, puis on dessine en tirant la souris avec un bouton enfoncé.

examples/canvas/echelle.c
    /* examples/canvas/echelle.c */
    
    #include <helium.h>
    
    He_node *princ, *canvas;
    int old_width = 1, old_height = 1;
    
    #define SMAX 1000
    int Sx[SMAX], Sy[SMAX], Sn = 0;
    
    void dessin_segment (Window win, int i)
    {
        if (i == 0)
             XDrawPoint (he_display, win, he_gc,
                Sx[0], Sy[0]);
        else XDrawLine (he_display, win, he_gc,
                Sx[i-1], Sy[i-1], Sx[i], Sy[i]);
    }
    
    void princ_resize_proc (He_node *hn, int width, int height)
    {
        HeExpand (canvas, NULL, HE_BOTTOM_RIGHT);
    }
    
    void canvas_resize_proc (He_node *hn, int width, int height)
    {
        int i;
        for (i = 0; i < Sn; i++) {
            Sx[i] = Sx[i] * width / old_width;
            Sy[i] = Sy[i] * height / old_height;
        }
        old_width = width; old_height = height;
    }
    
    void canvas_repaint_proc (He_node *hn, Window win)
    {
        int i;
        XSetForeground (he_display, he_gc, he_black);
        for (i = 0; i < Sn; i++)
            dessin_segment (win, i);
    }
    
    void canvas_event_proc (He_node *hn, He_event *hev)
    {
        switch (hev->type) {
            case ButtonPress :
                HeDrawBg (hn, he_white);
                Sn = 0;
                Sx[Sn] = hev->sx; Sy[Sn] = hev->sy; 
                dessin_segment (hev->win, Sn++);
                break;
            case MotionNotify :
                if (hev->sb > 0 && Sn < SMAX) {
                    Sx[Sn] = hev->sx; Sy[Sn] = hev->sy;
                    dessin_segment (hev->win, Sn++);
                }
                break;
        }
    }
    
    int main (int argc, char *argv[])
    {
        HeInit (&argc, &argv);
    
        princ = HeCreateFrame();
        HeSetFrameLabel (princ, "Mise à l'échelle");
        HeSetFrameResizeProc (princ, princ_resize_proc);
    
        canvas = HeCreateCanvas (princ);
        HeSetWidth (canvas, 300);
        HeSetHeight (canvas, 300);
        HeSetCanvasResizeProc  (canvas, canvas_resize_proc);
        HeSetCanvasRepaintProc (canvas, canvas_repaint_proc);
        HeSetCanvasEventProc   (canvas, canvas_event_proc);
    
        HeFit (princ);
    
        return HeMainLoop (princ);
    }
    

Index   Table   Précédent   Suivant

7.7. Double buffer d'affichage

Le Canvas possède un double buffer d'affichage intégré, qui permet d'éviter des clignotements lors de l'affichage. Par défaut il est désactivé. Pour l'activer, utiliser
    void HeSetCanvasDBuf (He_node *hn, int val);
avec val = TRUE (ou FALSE pour le désactiver). Pour savoir si un Canvas est actuellement en mode double buffer, consulter
    int HeGetCanvasDBuf (He_node *hn);
Il n'y a strictement rien d'autre à faire, Helium se charge complètement de la gestion des doubles buffers. Une petite contrainte cependant : il ne faut faire des dessins que dans la RepaintProc, et il faut utiliser le second paramètre win de la RepaintProc pour dessiner (c'est soit le Window natif en mode non bufférisé, soit le back buffer en mode bufférisé).

Comme exemple, voir demo/grille.c

Index   Table   Précédent   Suivant

7.8. Provoquer un réaffichage

On peut appeler directement la fonction qui fait office de RepaintProc pour tout redessiner à un moment donné ; il suffit de lui fournir le Canvas et son Window.

Soit canvas un Canvas et canvas_repaint_proc sa RepaintProc. Si on veut faire l'appel depuis l'EventProc du Canvas, on écrira

    canvas_repaint_proc (canvas, hev->win);
si on veut faire l'appel depuis la callback d'un bouton, on écrira
    canvas_repaint_proc (canvas, HeGetWindow(canvas));
Attention : il faut être conscient que l'exécution de la RepaintProc peut durer un certain temps, pendant lequel l'affichage peut être bloqué, et certains boutons peuvent êtres "gelés" en position enfoncée par exemple ; cela peut aussi faire clignoter l'affichage en cas d'appels multiples. La solution est simple : il suffit de remplacer l'appel direct de la RepaintProc par :
    HePostRepaint (canvas);
qui provoque un appel (à peine) différé de la RepaintProc par Helium. (En fait, HePostRepaint envoie simplement un évènement Expose au Window du Canvas ; cet évènement est traité une fois qu'on est sorti de la callback d'où on a fait l'appel).

Remarque 1 : lorsque le Canvas reçoit plusieurs Expose très rapprochés, Helium le détecte et ne commande l'appel de la RepaintProc que sur le dernier Expose, pour gagner en fluidité ; donc si vous appelez plusieurs fois à la suite HePostRepaint, un seul RepaintProc sera effectué (lire les commentaires au dessus de "case Expose :" dans gui/event.c).

Remarque 2 : lorsque la RepaintProc est appelée suite à un Expose normal, le fond est initialisé à blanc, et donc vous pouvez dessiner de suite sans effacer le fond. Ce n'est pas le cas lorsque le Expose est dû à un HePostRepaint : le fond n'étant pas initialisé à blanc, il peut s'avérer nécessaire de l'effacer avant de faire vos dessins. Pour cela il suffit d'appeler

    HeDrawBg (canvas, he_white);
qui vide et met en blanc le Canvas. On peut appeler HeDrawBg au début de la RepaintProc ou au moment de l'appel de HePostRepaint.

Remarque 3 : si le double buffer d'affichage est activé, il ne faut jamais appeler directement la RepaintProc car on ne connaît pas le back buffer ; il faut obligatoirement utiliser HePostRepaint.

L'exemple suivant récapitule ce qui est dit dans cette section. Agrandissez la fenêtre pour ralentir l'affichage, et observez le redessin des boutons ; cliquez plusieurs fois d'affilée très vite sur un bouton ou dans le Canvas, et comptez le nombre de réaffichages effectifs.

examples/canvas/reaffi.c
    /* examples/canvas/reaffi.c */
    
    #include <helium.h>
    
    He_node *princ, *panel, *canvas;
    
    void dessin_point (Window win, int x, int y, int coul)
    {
        XSetForeground (he_display, he_gc, coul);
        XDrawPoint (he_display, win, he_gc, x, y);
    }
    
    void canvas_repaint_proc (He_node *hn, Window win)
    {
        int x, y, w = HeGetWidth(hn), h = HeGetHeight(hn);
        
        printf ("Début RepaintProc\n");
        
        /* Dessin du fond */
        HeDrawBg (hn, he_white);
        
        /* Dessin volontairement lent */
        for (y = 0; y < h; y++)
        for (x = 0; x < w; x++)
            if ((w/2-x)*(w/2-x)+(h/2-y)*(h/2-y) < w*h/4)
                dessin_point (win, x, y, he_black);
        
        printf ("Fin RepaintProc\n");
    }
    
    void canvas_event_proc (He_node *hn, He_event *hev)
    {
        switch (hev->type) {
            case ButtonPress :
                if (hev->sb == 1) {
                    printf ("Debut appel direct\n");
                    canvas_repaint_proc (canvas, hev->win);
                    printf ("Fin appel direct\n");
                } else if (hev->sb == 2) {
                    printf ("Debut appel différé\n");
                    HePostRepaint (canvas);
                    printf ("Fin appel différé\n");
                }
                break;
        }
    }
    
    void butt1_proc (He_node *hn)
    {
        printf ("Debut appel direct\n");
        canvas_repaint_proc (canvas, HeGetWindow(canvas));
        printf ("Fin appel direct\n");
    }
    
    void butt2_proc (He_node *hn)
    {
        printf ("Debut appel différé\n");
        HePostRepaint (canvas);
        printf ("Fin appel différé\n");
    }
    
    void princ_resize_proc (He_node *hn, int width, int height)
    {
        HeExpand (canvas, NULL, HE_BOTTOM_RIGHT);
    }
    
    int main (int argc, char *argv[])
    {
        HeInit (&argc, &argv);
    
        princ = HeCreateFrame();
        HeSetFrameLabel (princ, "Provoquer un réaffichage");
        HeSetFrameResizeProc (princ, princ_resize_proc);
    
        panel = HeCreatePanel (princ);
        HeSetPanelLayout (panel, HE_VERTICAL);
        
        HeCreateButtonP (panel, "Appel direct", butt1_proc, NULL);
        HeCreateButtonP (panel, "Appel différé", butt2_proc, NULL);
        HeCreateMessageP (panel, "Bouton souris 1 : appel direct", FALSE);
        HeCreateMessageP (panel, "Bouton souris 2 : appel différé", FALSE);
        HeFit(panel);
    
        canvas = HeCreateCanvas (princ);
        HeSetY (canvas, HeGetHeight(panel) + 2);
        HeSetWidth (canvas, 500);
        HeSetHeight (canvas, 500);
        HeSetCanvasRepaintProc (canvas, canvas_repaint_proc);
        HeSetCanvasEventProc (canvas, canvas_event_proc);
    
        HeFit (princ);
    
        return HeMainLoop (princ);
    }

Index   Table   Précédent   Suivant

7.9. Les XImages

Lorsqu'on veut afficher une image dans un Canvas, on peut faire un affichage point par point ; le problème est que cette méthode est extrêmement lente. On présente dans cette section comment réaliser un affichage instantané.

Une XImage est une "mémoire image", qui peut être plaquée à l'écran. C'est une structure de donnée spéciale de Xlib, comprenant un tableau des couleurs des points, codées sous leur forme finale, c'est-à-dire sous la forme de la mémoire graphique.

En premier lieu on déclare une XImage rectangulaire de taille width*height ; les coordonnées dans xi varient entre 0,0 (coin en haut à gauche) et width-1,height-1.

    XImage *xi;
    xi = HeCreateXi (width, height);
Cette création contient une allocation de mémoire qui peut échouer ; il faut donc tester si xi != NULL avant de continuer.

On peut initialiser la couleur de fond de xi par

    HeSetXiBg (xi, R, G, B);
R,G,B sont des unsigned char. On fixe ensuite la couleur de chaque pixel de coordonnées x,y dans xi avec
    HeSetXiPixel (xi, offset, R, G, B);
offset est y*width+x. Attention, x,y ne doivent jamais être en dehors de [0..width-1,0..height-1] sous peine de plantage.

Si les valeurs des couleurs sont stockées dans des tableaux tabR, tabG, tabB de unsigned char et de taille exacte [width*height], alors on peut appeler

    HeRGBtoXi (xi, tabR, tabG, tabB);
Cet appel est équivalent à
    int x, y, t;
    for (y = 0; y < height; y++)
    for (x = 0; x < width; x++) {
        t = y * width + x;
        HeSetXiPixel (xi, t, tabR[t], tabG[t], tabB[t]);
    }
mais il est encore 50% plus rapide.

Une fois que xi est achevée, on peut l'afficher à l'écran. Cette étape est instantanée. Il suffit d'appeler

    HePutXi (win, gc, xi, x, y);
win est le Window du Canvas, et x,y sont les coordonnées dans le Canvas du coin en haut à gauche de xi. On peut donc ainsi afficher xi plusieurs fois et en plusieurs endroits du Canvas.

On peut aussi afficher une partie rectangulaire de xi avec

    HePutSubXi (win, gc, xi, x, y, sub_x, sub_y, sub_w, sub_h);
x,y sont les coordonnées dans le Canvas du coin en haut à gauche de xi, et sub_x, sub_y, sub_w, sub_h délimitent dans xi la partie rectangulaire de xi à afficher.

On appelle typiquement HePutXi ou HePutSubXi dans la RepaintProc d'un Canvas.

Lorsqu'on n'a plus besoin de xi, il faut la détruire. De même, si on veut changer la taille de xi, il faut détruire xi puis la recréer avec la nouvelle taille. On appelle donc

    HeDestroyXi (xi);
Dans l'exemple suivant, on ouvre une fenêtre avec un Canvas, on crée une XImage dans init_xi (sans initialiser le fond) puis on l'affiche en damier dans la RepaintProc. On ne détruit pas la XImage dans cet exemple. Pour tester la rapidité de l'affichage, bouger la fenêtre ou faire glisser une autre fenêtre devant.

examples/canvas/xi.c
    /* examples/canvas/xi.c */
    
    #include <helium.h>
    
    #define XMAX 180
    #define YMAX 220
    
    He_node *princ, *canvas;
    XImage *xi;
    
    void init_xi ()
    {
        int x, y;
    
        /* Création */
        xi = HeCreateXi (XMAX, YMAX);
        if (xi == NULL) return;
    
        /* Calcul */
        for (y = 0; y < YMAX; y++)
        for (x = 0; x < XMAX; x++) {
            Uchar R = x, G = 255-x, B = y;
            HeSetXiPixel (xi, y*XMAX+x, R, G, B);
        }
    }
    
    void canvas_repaint_proc (He_node *hn, Window win)
    {
        int i, j;
    
        /* Affichage multiple instantané */
        for (i = 0; i <= HeGetWidth (hn)/XMAX; i++)
        for (j = 0; j <= HeGetHeight(hn)/YMAX; j++)
            HePutXi (win, he_gc, xi, i*XMAX, j*YMAX);
    }
    
    int main (int argc, char *argv[])
    {
        HeInit (&argc, &argv);
    
        princ = HeCreateFrame();
        HeSetFrameLabel (princ, "XImage");
    
        canvas = HeCreateCanvas (princ);
        HeSetCanvasRepaintProc (canvas, canvas_repaint_proc);
        HeSetWidth (canvas, 500); HeSetHeight (canvas, 500);
        init_xi();
    
        HeFit (princ);
    
        return HeMainLoop (princ);
    }

Remarque : dans Xlib, tous les mécanismes bas-niveau sont prévus pour manipuler les XImages ; mais l'étape de calcul d'une XImage est laissée au soin de l'utilisateur, et l'algorithme est relativement compliqué ; il suffit de regarder gui/xi.c pour s'en convaincre ...

Index   Table   Précédent   Suivant

7.10. Conversion de couleurs

Quelques fonctions de conversion de couleurs sont fournies dans Helium.

Dans le système RGB (Red, Green, Blue), chaque valeur est codée entre 0 et HE_COLOR_MAXRGB = 255. Dans le système HSV (Hue, Saturation, Value), H est entre 0 et HE_COLOR_MAXH = 360, S et V sont entre 0 et HE_COLOR_MAXSV = 1000 (voir gui/color.h). Les fonctions de conversion entre les deux systèmes sont :

    void HeHsvToRgb (int h, int s, int v, int *r, int *g, int *b);
    void HeRgbToHsv (int r, int g, int b, int *h, int *s, int *v);
Le type XColor est un type de couleur fourni par Xlib dans Xlib.h :
    typedef struct {
        unsigned long pixel;
        unsigned short red, green, blue;
        char flags;  /* do_red, do_green, do_blue */
        char pad;
    } XColor;
Les valeurs red, green, blue sont codées sur 3*16 bits. On a les fonctions de conversion suivantes :
    void HeRgbToXColor (int r, int g, int b, XColor *x);
    void HeHsvToXColor (int h, int s, int v, XColor *x);
    void HeXColorToRgb (XColor *x, int *r, int *g, int *b);
    void HeXColorToHsv (XColor *x, int *h, int *s, int *v);
Dans la philosophie X11, les couleurs peuvent être décrites par un string, soit par leur nom ("blue", "yellow2", etc) soit par leur code hexadécimal ("#37a", "#e0b4b0", etc). Les fonctions de Helium pour interpréter ces noms sont :
    int HeParseXColor (char *name, XColor *x);
    int HeParseRgb (char *name, int *r, int *g, int *b);
    int HeParseHsv (char *name, int *h, int *s, int *v);
Index   Table   Précédent   Suivant

7.11. Afficher du texte

Xlib fournit des fonctions très efficaces pour afficher du texte dans de multiples polices de caractères. On peut se servir de toutes les fonctions de dessin de Xlib dans un Canvas.

Par exemple, XLoadQueryFont charge une fonte (c'est-à-dire une police de caractères) à partir de son nom ; XSetFont fixe la fonte courante ; XDrawString affiche un string sans effacer le fond ; XDrawImageString affiche un string en effaçant le fond ; XDrawText affiche plusieurs strings avec plusieurs fontes.

Helium fournit quelques fonctions qui simplifient l'usage des fonctions de Xlib, spécialement au niveau du positionnement du string par rapport à un point de coordonnées x,y.

Les variables globales he_normal_font et he_bold_font sont les fontes normale et grasse utilisées pour le dessin des widgets. On rappelle que la variable globale he_gc sert à mémoriser les attributs du tracé. La fonction

    HeDrawString (win, he_gc, he_normal_font, x, y, ligne)
affiche le char *ligne dans le Window win du Canvas, dans la fonte normale d'Helium. Le point x,y est le coin en haut à gauche du string. On peut aussi afficher un sous-string avec
    HeDrawSubString (win, he_gc, he_normal_font, 
                     x, y, ligne, pos, len)
pos est le numéro du premier caractère de ligne à afficher, et len est le nombre de caractères à afficher. Si on veut afficher un string centré en hauteur et largeur par rapport à une zone rectangulaire, on dispose de la fonction
    HeDrawStringCenterRect (win, he_gc, he_normal_font,
        xb, yb, xm, ym, ligne)
xb,yb est le coin en haut à gauche et xm,ym est la largeur et la hauteur de la zone. La fonction
    HeDimString (he_normal_font, ligne, &x, &y)
demande les dimensions en pixels d'un string dans une fonte, et les stocke dans les entiers x,y. Enfin la fonction
    HeDrawStringPos (win, he_gc, he_normal_font, x, y, pos, ligne)
affiche une ligne de texte justifiée en hauteur et en largeur selon pos, par rapport au point de coordonnées x,y. Le paramètre pos peut prendre l'une des 9 valeurs :
    HE_TOP_LEFT, HE_TOP_MIDDLE, HE_TOP_RIGHT 
    HE_BASE_LEFT, HE_BASE_MIDDLE, HE_BASE_RIGHT 
    HE_BOTTOM_LEFT, HE_BOTTOM_MIDDLE, HE_BOTTOM_RIGHT. 
Dans l'exemple suivant, on illustre les différentes possibilités de justification avec HeDrawStringPos par rapport à un point donné :

examples/canvas/drawstring.c
    /* examples/canvas/drawstring.c */
    
    #include <helium.h>
    
    He_node *princ, *canvas;
    int rouge;
    
    void init_couleurs ()
    {
        rouge = HeAllocRgb (255, 0, 0, he_black);
    }
    
    void dessin_ligne (Window win, int x1, int y1, int x2, int y2, int c)
    {
        XSetForeground (he_display, he_gc, c);
        XDrawLine (he_display, win, he_gc, x1, y1, x2, y2);
    }
    
    void dessin_string (Window win, int x, int y, int pos, char *ligne)
    {
        dessin_ligne (win, x-20,y,x+20,y, rouge);
        dessin_ligne (win, x,y-20,x,y+20, rouge);
        XSetForeground (he_display, he_gc, he_black);
        HeDrawStringPos (win, he_gc, he_normal_font,
            x, y, pos, ligne);
    }
    
    void canvas_repaint (He_node *hn, Window win)
    {
        dessin_string (win, 50,   50, HE_TOP_LEFT,      "TopLeft");
        dessin_string (win, 300,  50, HE_TOP_MIDDLE,    "TopMiddle");
        dessin_string (win, 550,  50, HE_TOP_RIGHT,     "TopRight");
        dessin_string (win, 50,  150, HE_BASE_LEFT,     "BaseLeft");
        dessin_string (win, 300, 150, HE_BASE_MIDDLE,   "BaseMiddle");
        dessin_string (win, 550, 150, HE_BASE_RIGHT,    "BaseRight");
        dessin_string (win, 50,  250, HE_BOTTOM_LEFT,   "BottomLeft");
        dessin_string (win, 300, 250, HE_BOTTOM_MIDDLE, "BottomMiddle");
        dessin_string (win, 550, 250, HE_BOTTOM_RIGHT,  "BottomRight");
    }
    
    int main (int argc, char *argv[])
    {
        HeInit (&argc, &argv);
    
        princ = HeCreateFrame();
        HeSetFrameLabel (princ, "HeDrawStringPos");
    
        canvas = HeCreateCanvas (princ);
        HeSetCanvasRepaintProc (canvas, canvas_repaint);
        init_couleurs ();
    
        HeSetWidth (canvas, 600); HeSetHeight (canvas, 300);
        HeFit (princ);
    
        return HeMainLoop (princ);
    }

Index   Table   Précédent   Suivant

7.12. En savoir plus sur Xlib

Les livres officiels pour apprendre à programmer Xlib sont : "Xlib Programming Manual" (Volume One) et "Xlib Reference Manual" (Volume Two), chez O'Reilly.

Le système XWindow est maintenu par le XConsortium, regroupant les grands groupes informatiques du monde Unix.

Actuellement, les plus gros développements sont faits par le groupe XFree86, auteur d'une implémentation libre de Xlib.


Index   Table   Début   Suivant