Pierre Ficheux (pficheux@com1.fr)
Novembre 1999
Les exemples des programmation sont disponibles en téléchargement sur http://www.com1.fr/~pficheux/articles/lmf/xlib-images/xlib-images_exemples.tar.gz
Dans le cas de la Xlib, la valeur de chaque composante est codée sur un octet soit de 0 à 255. On a par exemple:
Les contraintes matérielles ont donc conduit les concepteurs de X à définir différents type des codage plus restrictifs mais aussi moins consommateurs de mémoire vidéo.
Le DISPLAY X est caractérisé entre-autres par le nombre de bits utilisés pour coder un pixel. Dans le cas précédent, chaque pixel occupe 3 octets soit 24 bits. On parlera d'un DISPLAY à 24 plans. On peut connaitre le nombre de plans du DISPLAY courant en utilisant la macro Xlib DisplayPlanes(display). Pour simplifier le problème, on distinguera 3 types de DISPLAY:
Au début des années 1990, il existait également de nombreux DISPLAY 1 plan, capables seulement d'afficher des pixels noirs ou blancs.
Le nombre de couleurs décrit ci-dessus correspond au nombre de couleurs affichables simultanément sur l'écran. Nous verrons plus loin qu'il est possible d'avoir une palette de 16M de couleurs même si l'on ne peut en afficher que 256 à la fois.
Les caractéristiques d'affichage de votre DISPLAY sont accessible à l'aide de la commande xdpyinfo qui pourra entre-autres donner le type d'information suivante:
screen #0: dimensions: 1152x900 pixels (390x305 millimeters) resolution: 75x75 dots per inch depths (1): 16 root window id: 0x25 depth of root window: 16 planes
Ce type de codage dit PseudoColor est encore largement utilisé aujourd'hui dans le cas de cartes vidéos disposant de faible capacité mémoire (1 Mo). Il est cependant très contraignant pour plusieurs raisons:
Dans le schéma ci-dessus les composantes R/V/B utilisent respectivement 5, 6 et 5 bits. Cette information est disponible par la commande xpdyinfo au niveau des paramètres de masques:
red, green, blue masks: 0xf800, 0x7e0, 0x1f
On dispose donc de 65536 couleurs (2 ^16).
Dans le cas général d'une utilisation classique (pas de traitement d'image), un DISPLAY 16 bits est un bon compromis.
xinit -- -bpp 16
Dans le cas de DISPLAY TrueColor, vous pouvez également calculer directement la valeur du pixel à partir du RVB et des caractéristiques du DISPLAY. Ceci permet de ne pas effectuer d'accès au serveur X (pas d'appel à XAllocColor) et donc d'optimiser le temps de traitement (voir l'exemple de manipulation d'image au paragraphe 5.2).
L'allocation de couleur sera effectuée par la fonction:
XAllocColor (Display *display, Colormap cmap, XColor *color)
Le type XColor permet de spécifier les composantes RGB de la couleur à allouer. En cas de succès, la variable color contiendra la valeur du pixel.
typedef struct { unsigned long pixel; unsigned short red, green, blue; char flags; /* do_red, do_green, do_blue */ char pad; } XColor;
Il existe un certain nombre de couleurs définies par des noms et listées dans le fichier /usr/X11R6/lib/X11/rgb.txt:
211 211 211 light grey 211 211 211 LightGrey 211 211 211 light gray 211 211 211 LightGray 25 25 112 midnight blue 25 25 112 MidnightBlue 0 0 128 navy 0 0 128 navy blue 0 0 128 NavyBlueCe fichier permet de manipuler plus facilement les noms de couleurs courantes en utilisant la fonction:
XAllocNamedColor (Display *display, Colormap cmap, char *color_name, XColor *color, XColor *exact)
Le dernier paramètre retournera la valeur exacte de la couleur spécifiée par color_name alors que color retournera la valeur approchée supportée par le screen courant.
La valeur du pixel est ensuite utilisable dans un GC (contexte graphique) utilisé pour tous les types de tracés (voir article Xlib LM10), par exemple:
XSetForeground (display, gc, color.pixel)
On définit un tableau contenant 8 noms de couleurs et un tableau de 8 variables de type XColor:
static char *color_names[] = { "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white" }; static XColor x_colors[8];On alloue les couleurs définies par leurs noms sur la Colormap par défaut:
cmap = DefaultColormap(display, screen); /* Allocation des couleurs */ for (i = 0; i != 8; i++) { if (!XAllocNamedColor (display, cmap, color_names[i], &x_colors[i], &exact)) { fprintf(stderr, "cant't alloc color %s\n", color_names[i]); exit (1); } }Lors de la réception de l'évènement Expose, on affiche la fenêtre en la remplissant de carrés multicolores:
void expose () { int x, y, color = 0; for (x = 0 ; x < WIDTH ; x += 10) { for (y = 0 ; y < HEIGHT ; y += 10) { XSetForeground (display, gc, x_colors[color].pixel); XFillRectangle (display, win, gc, x, y, 10, 10); color = (++color > 7 ? 0 : color); } } }Le résultat à l'affichage est le suivant:
Pour manipuler les images, nous allons utiliser un type particulier appelé XImage. Un des champs du type XImage sera un pointeur vers les données réelles de l'image (la définition des pixels). La création d'une image sera réalisée par la fonction:
XImage *XCreateImage(display, visual, depth, format, off- set, data, width, height, bitmap_pad, bytes_per_line) Display *display; Visual *visual; unsigned int depth; int format; int offset; char *data; unsigned int width; unsigned int height; int bitmap_pad; int bytes_per_line;La zone mémoire pointée par data devra contenir l'image codée au format du DISPLAY courant (8, 16 ou 24 bits). Le remplissage de cette mémoire sera effectué par la fonction:
XPutPixel(ximage, x, y, pixel) XImage *ximage; int x; int y; unsigned long pixel;Vous remarquerez que cette fonction n'utilise pas de paramètre display pour la bonne raison que la zone de donnée de l'XImage est définie dans la mémoire du calculateur et non dans celle du serveur X.
Lorsque l'on voudra afficher l'image dans un Drawable, on devra utiliser la fonction:
XPutImage(display, d, gc, image, src_x, src_y, dest_x, dest_y, width, height) Display *display; Drawable d; GC gc; XImage *image; int src_x, src_y; int dest_x, dest_y; unsigned int width, height;
En-tête (magic number = 'P6') largeur_image hauteur_image 255 description du pixel 1 en RVB description du pixel 2 en RVB etc...Par exemple:
P6 359 352 255 U&(54"!º}'· ...Le principe du programe est le suivant:
Dans le cas d'un DISPLAY 8 bits, on limitera le nombre d'appels à XAllocColor (très gourmant en ressources X) en gèrant un cache très simple qui stockera les valeurs RVB allouées au fur et à mesure de la lecture du fichier. Dans le cas des autre configurations, on optimisera le code en calculant directement la valeur du pixel.
On commence par déterminer la Colormap par défaut et le nombre de plans:
default_cmap = DefaultColormap (display, screen); nplanes = DisplayPlanes (display, screen);
Si l'on utilise un codage TrueColor on doit calculer le nombre de bits associés à chaque composante ainsi que la position de la composante dans la définition du pixel:
if (nplanes > 8) { /* On utilise dans ce cas la colormap par défaut */ current_cmap = default_cmap; /* Calcul des decalages */ r = red_mask = the_visual->red_mask; g = green_mask = the_visual->green_mask; b = blue_mask = the_visual->blue_mask; red_bits = 0; green_bits = 0; blue_bits = 0; red_shift = 0; green_shift = 0; blue_shift = 0; while (!(r & 1)) { r >>= 1; red_shift++; } while (r & 1) { r >>= 1; red_bits++; } ... }
Si on est en 8 bits, on alloue une nouvelle Colormap en dupliquant celle par défaut:
else current_cmap = XCopyColormapAndFree (display, default_cmap);
On effectue ensuite la lecture du fichier ppm et on alloue la mémoire nécessaire au stockage de l'image:
fscanf (fp, "%c%c\n", &magic[0], &magic[1]); if (magic[1] != '6') { fprintf (stderr, "%s: %s is not a PPM file.\n", av[0], av[1]); fclose (fp); exit (1); } fscanf (fp, "%d %d\n", &image_width, &image_height); fscanf (fp, "%d\n", &nb_colors); if (!(image_data = (char*) calloc (1, sizeof(unsigned int) * image_width * image_height))) { perror ("calloc image_data:"); exit (1); }On alloue ensuite l'XImage. Notez la compilation conditionnelle suivant le type de processeur little endian (x86 ou équivalents) ou big endian (SPARC, 68k etc...).
ximage = XCreateImage (display, DefaultVisual (display, screen), nplanes, ZPixmap, 0, image_data, image_width, image_height, 8 * sizeof(unsigned int), 0) ; #ifdef __i386__ ximage->byte_order = LSBFirst; #else ximage->byte_order = MSBFirst; #endifOn lit ensuite les triplés RVB à partir du fichier. Dans le cas d'un codage TrueColor, on calcule la valeur du pixel directement à partir des composantes, des masques et des décalages (pas d'allocation).
for (y = 0 ; y < image_height ; y++) { for (x = 0 ; x < image_width ; x++) { fscanf (fp, "%c%c%c", &red, &green, &blue); if (nplanes > 8) { red >>= (8 - red_bits); green >>= (8 - green_bits); blue >>= (8 - blue_bits); pixel = ((red << red_shift) & red_mask) | ((green << green_shift) & green_mask) | ((blue << blue_shift) & blue_mask); }Dans le cas du 8 bits, on doit tout d'abord vérifier que la couleur n'est pas déja dans le cache:
/* 8 planes */ else { XColor color; red <<= 8; green <<= 8; blue <<= 8; for (i = 0 ; i != last_color ; i++) { if (color_cache[i].red == red && color_cache[i].green == green && color_cache[i].blue == blue) break; }Si elle n'y est pas, on doit allouer la couleur et la stocker dans le cache. Si on dépasse le nombre de couleurs dans la Colormap (256) on sort...
if (i == last_color) { color.red = red; color.green = green; color.blue = blue; if (!XAllocColor (display, current_cmap, &color)) { fprintf (stderr, "Can't allocate %d %d %d\n", red, green, blue); exit (1); } else { color_cache[i].red = red; color_cache[i].green = green; color_cache[i].blue = blue; color_cache[i].pixel = pixel = color.pixel; } last_color++; if (last_color > 256) { fprintf (stderr, "Too many colors...\n"); exit (1); } }Si elle y est, il suffit de récupérer la valeur du pixel:
else pixel = color_cache[i].pixel;Dans tous les cas, on écrit le pixel calculé dans l'image:
XPutPixel (ximage, x, y, pixel);Lorsque l'image est prête, on crée la fenêtre d'affichage, puis on affiche l'image dans l'évènement Expose:
XPutImage (display, win, gc, ximage, 0, 0, 0, 0, image_width, image_height);On pourrait améliorer ce programme en lui ajoutant une fonction de réduction de couleurs (dithering) dans le cas du mode 8 bits. Un algorithme de dithering est disponible dans le package NetPbm.