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.
X11 repose sur un modèle hiérarchique de zones rectangulaires, appelées "Window" :
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.
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);
où 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);
où 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 */
#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 :
he_display
mémorise les paramètres de l'écran sur lequel on affiche
(local ou distant, nombre de plans, définition, etc) : on doit le
donner à presque toutes les fonctions de Xlib.
he_gc
est un GC (Graphical Context), qui mémorise tous les
attributs de dessin (couleur, mais aussi épaisseur, pointillé, etc ;
en tout il y en a 23). Il évite d'avoir à passer tous ces paramètres
aux fonctions de dessin de Xlib.
he_black
est l'indice de la couleur noire, he_white
est
l'indice de la couleur blanche (cf section
« 7.3. Dessins en couleur »). Le noir et le blanc sont les seules
couleurs qui existent au départ.
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.
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);
où 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 */
#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.
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.
HeSetCanvasEventProc (canvas, canvas_event_proc);
le prototype de la callback est
void canvas_event_proc (He_node* hn, He_event *hev);
où 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 :
EnterNotify
: la souris rentre dans le Canvas
LeaveNotify
: la souris sort du Canvas
KeyPress
: une touche est enfoncée
KeyRelease
: une touche est relachée
ButtonPress
: un bouton de la souris est enfoncé
ButtonRelease
: un bouton de la souris est relaché
MotionNotify
: la souris a bougé
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 */
#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 :
hev->win
) si
c'est pour rajouter des dessins. Il faut prévoir de mémoriser ces
dessins pour que la RepaintProc puisse éventuellement tout
redessiner.
HeSetCanvasResizeProc (canvas, canvas_resize_proc);
Le prototype de la callback est
void canvas_resize_proc (He_node *hn, int width, int height);
où 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 */
#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 :
1+width+1
et la
hauteur totale est 1+height+1
. Voir HeSetBorder()
plus loin dans le tutorial pour changer la largeur du bord
extérieur.
/* 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);
}
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
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 */
#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);
}
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);
où 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);
où 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);
où 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);
où 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 */
#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 ...
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);
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)
où 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)
où 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 */
#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);
}
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.