Bitmaps, Pixmaps, etc...


Table des matières

Introduction

Nous avons déjà parlé dans les premiers chapitres de ce cours de Pixmaps. Il s'agit en effet de "l'autre" drawable (avec les objets de type Window) dans lequel on peut utiliser les fonctions de dessin de la Xlib comme XDrawLine(display, drawable, gc, x1, y1, x2, y2) .

Un pixmap est un buffer mémoire qui peut être assimilé à une fenêtre graphique virtuelle. On peut dessiner dedans, copier son contenu dans une fenêtre, dans un autre pixmap, effacer son contenu, etc...

Les pixmaps ont plusieurs utilités :

Mettre un bitmap en fond d'une fenêtre

Rappel : un bitmap est en noir et blanc, un pixmap peut comporter de nombreuses couleurs.

Pour créer un bitmap, le plus simple est d'utiliser le programme bitmap. Ce programme permet de dessiner à l'aide d'outil très simples des motifs ou des curseurs et de les sauvegarder dans un fichier.

Le programme bitmap

Par la suite on pourra soit lire directement le fichier à l'aide de la fonction XReadBitmapFile() soit l'inclure directement dans le code avec un #include (déconseillé). Dans tous les cas, pour les manipuler il faudra stocker leur contenu dans une variable de type Pixmap.


La Xlib ne fournit pas de fonctions permettant de lire des motifs comportant plus de deux couleurs. Pour celà il faudra recourir à des librairies spécialisées comme la librairie Xpm ou la librairie ImageMagick

Premier exemple : le bitmap est lu dans un fichier.

Note : source disponible : bitmaps.c

Pour les locaux, tous les exemples (sources + binaires Solaris) dans file:/u/I3S/buffa/www/Internet/cours/X11_Motif/exemples



#include <stdio.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>

Display *dpy;
int screen;
Window root;
Visual *visual;
int depth;
int fg, bg;

main()
{
Window win;
XSetWindowAttributes xswa;
XEvent report;

GC gc;
XGCValues gcvalues;

Pixmap motif;
Pixmap motif_8bits;
int val;
int motif_width, motif_height,x_hot, y_hot;

if((dpy=XOpenDisplay(NULL))==NULL) {
fprintf(stderr,"fenetre de base: ne peut pas se connecter ");
fprintf(stderr,"au serveur %s\n",XDisplayName(NULL));
exit(-1);
}

/* Initialisation des variables standards */
screen = DefaultScreen(dpy);
root = DefaultRootWindow(dpy);
visual = DefaultVisual(dpy, screen);
depth = DefaultDepth(dpy,screen);
fg = BlackPixel(dpy, screen);
bg = WhitePixel(dpy, screen);

/* Lecture du motif de 1 bit de profondeur (bitmap) */
val = XReadBitmapFile(dpy, root,
"motif.xbm", /* Nom du fichier */
&motif_width, &motif_height, /* largeur et hauteur */
&motif, /* le pixmap */
&x_hot, &y_hot); /* le "hot spot */
switch(val) {
case BitmapFileInvalid:
fprintf(stderr, "le fichier motif.xbm n'est pas un bitmap valide\n");
break;
case BitmapOpenFailed:
fprintf(stderr, "le fichier motif.xbm n'a pu être lu\n");
break;
case BitmapNoMemory:
fprintf(stderr, "Pas assez de mémoire pour lire le fichier motif.xbm\n");
break;
case BitmapSuccess:
fprintf(stderr, "Fichier motif.xbm lu avec succes. Taille = %d %d, hotspot = %d %d\n",
motif_width, motif_height, x_hot, y_hot);
break;
}

/* Creation d'un second pixmap de la meme profondeur que le fenetre */
motif_8bits = XCreatePixmap(dpy,
root,
motif_width, motif_height,
depth);

  /* La fonction suivante a besoin d'un GC */
  gcvalues.foreground =  fg;
  gcvalues.background =  bg;

  gc =  XCreateGC(dpy,  RootWindow(dpy, screen),
                  (GCForeground | GCBackground), &gcvalues);
  

  /* On copie le bitmap dans le pixmap_8 bits */
  val = XCopyPlane(dpy, motif, motif_8bits, gc, 0, 0, motif_width,
                   motif_height, 0, 0, 1);

  /* attributs de la fenetre. */
  /* Le motif en fond. INCOMPATIBLE avec background_pixel !!!*/
  xswa.background_pixmap = motif_8bits;

  /* Les événements */
  xswa.event_mask = ExposureMask|ButtonPressMask|ButtonReleaseMask|
    PointerMotionMask|KeyPressMask;

  /* Couleur de la bordure */
  xswa.border_pixel     = fg;

  win = XCreateWindow(dpy, root,
                          100, 100, 500, 500, 3,
                          depth,
                          InputOutput, 
                          visual,
                          CWEventMask|CWBorderPixel|CWBackPixmap,
                          &xswa);
  XMapWindow(dpy,win);
  
  /* On libere les pixmaps car maintenant ils sont dans la memoire du
     serveur X11 */
  XFreePixmap(dpy, motif);
  XFreePixmap(dpy, motif_8bits);

  while(1) {
    XNextEvent(dpy, &report);
    ...
  }
}


Ce qu'il faut retenir de ce petit exemple :

Deuxième exemple : le bitmap est inclu dans le source.

#include <stdio.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "motif.xbm"

...

main()
{
 ...
  if((dpy=XOpenDisplay(NULL))==NULL) {
    fprintf(stderr,"fenetre de base: ne peut pas se connecter ");
    fprintf(stderr,"au serveur %s\n",XDisplayName(NULL));
    exit(-1);
  }

  /* Initialisation des variables standards */
  screen = DefaultScreen(dpy);
  ...

  motif = XCreateBitmapFromData(dpy, root, 
                                motif_bits, motif_width, motif_height);

  /* Creation d'un second pixmap de la meme profondeur que le fenetre */
  motif_8bits = XCreatePixmap(dpy, 
                              root, 
                              motif_width, motif_height, 
                              depth);
  ...

En gras, les modifications à apporter à l'exemple précédent.

Quelques remarques cependant :

Utiliser un bitmap comme motif de remplissage pour les fonctions de dessin

Une fois le pixmap alloué et initialisé (s'assurer au'il a la même profondeur que la fenêtre, comme dans les deux exemples précédents), il est très facile de l'utiliser comme motif pour dessiner. Il suffit pour celà de créer un GC avec les attributs suivants positionnés :

... ou de modifier un GC existant à l'aide des fonctions XSetFillStyle() et XSetTile()

Exemple d'utilisation d'un motif pour dessiner :

#include <stdio.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>

...

main()
{
  ...
  /* Creation d'un second pixmap de la meme profondeur que le fenetre */
  motif_8bits = XCreatePixmap(dpy, 
                              root, 
                              motif_width, motif_height, 
                              depth);
  gcvalues.foreground =  fg;
  gcvalues.background =  bg;

  gc =  XCreateGC(dpy,  RootWindow(dpy, screen),
                  (GCForeground | GCBackground), &gcvalues);  

  /* On copie le bitmap dans le pixmap_8 bits */
  val = XCopyPlane(dpy, motif, motif_8bits, gc, 0, 0, motif_width,
             motif_height, 0, 0, 1);
  ... 
  /* creation de la fenêtre, etc... */
  ...
  XSetFillStyle(dpy, gc, FillTiled);
  XSetTile(dpy, gc, motif_8bits);

  XMapWindow(dpy,win);

  XFreePixmap(dpy, motif);
  XFreePixmap(dpy, motif_8bits);

  while(1) {
    XNextEvent(dpy, &report);
    switch (report.type) {
    case Expose:
      /* traitement des évènements de type Expose */
      break;
    case ButtonPress:
      /*Quand on clique on dessine un rectangle rempli avec le motif */
      XFillRectangle(dpy, win, gc, report.xbutton.x, 
                     report.xbutton.y, 100, 100);
      break;
    case KeyPress:
      XCloseDisplay(dpy);
      exit(0);
      /* traitement des évènements de type KeyPress */
      break;
    case ConfigureNotify:
      /* traitement des évènements de type ConfigureNotify */
      break;
    }
  }
}

Utilisation d'un pixmap pour faire du double buffering

Pas grand chose à expliquer car la méthode est très simple : on dessine dans un pixmap (et non pas dans la fenêtre), puis on copie d'un coup le pixmap dans la fenêtre.

Le petit exemple ci-dessous montre un exemple d'utilisation d'un Pixmap en tant que double buffer. Il permet en outre de comparer la même animation (cent cercles concentriques qui avancent en diagonale à partir de la position cliquée) avec et sans double buffer.

Le programme d'exemple

#include <stdio.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "motif.xbm"

Display *dpy;
int screen;
Window root;
Visual *visual;
int depth;
int fg, bg;

main()
{
  Window win;
  int width = 500, height = 500;
  XSetWindowAttributes xswa;
  XEvent report;

  GC gc;
  XGCValues gcvalues;

  Pixmap db;
  int i,j;

  if((dpy=XOpenDisplay(NULL))==NULL) {
    fprintf(stderr,"fenetre de base: ne peut pas se connecter ");
    fprintf(stderr,"au serveur %s\n",XDisplayName(NULL));
    exit(-1);
  }

  /* Initialisation des variables standards */
  screen = DefaultScreen(dpy);
  root = DefaultRootWindow(dpy);
  visual = DefaultVisual(dpy, screen);
  depth = DefaultDepth(dpy,screen);
  fg = BlackPixel(dpy, screen);
  bg = WhitePixel(dpy, screen);

  /* Creation du double buffer */
  db = XCreatePixmap(dpy, root, width, height, depth);
  
  gcvalues.foreground =  fg;
  gcvalues.background =  bg;

  gc =  XCreateGC(dpy,  RootWindow(dpy, screen),
                  (GCForeground | GCBackground), &gcvalues);
  


  /* attributs de la fenetre. */
  /* Le motif en fond */
  xswa.background_pixel = bg;

  /* Les événements */
  xswa.event_mask = ExposureMask|ButtonPressMask|ButtonReleaseMask|
    PointerMotionMask|KeyPressMask;

  /* Couleur de la bordure et du background */
  xswa.background_pixel = bg;
  xswa.border_pixel     = fg;

  win = XCreateWindow(dpy, root,
                          100, 100, 500, 500, 3,
                          depth,
                          InputOutput, 
                          visual,
                          CWEventMask|CWBorderPixel|CWBackPixel,
                          &xswa);
  
  XMapWindow(dpy,win);

  while(1) {
    XNextEvent(dpy, &report);
    switch (report.type) {
    case Expose:
      /* traitement des évènements de type Expose */
      break;
    case ButtonPress: 
      switch(report.xbutton.button) {
      case Button1: 
        /* Animation avec double buffer */
        for(j=0; j < 100; j++) {
          XSetForeground(dpy, gc, bg);
          XFillRectangle(dpy, db, gc, 0, 0, width, height);
          XSetForeground(dpy, gc, fg);

          for(i = 0; i < 400; i+=4)
            XDrawArc(dpy, db, gc, report.xbutton.x+j, report.xbutton.y+j, 
                     i,i, 0, 23040);

          XCopyArea(dpy, db, win, gc, 0, 0, width, height, 0, 0); 
          /* on force le rafraichissement de l'écran */
          XFlush(dpy);
        }
        
        break;
      case Button2: 
        /* Animation sans double buffer */
        for(j=0; j < 100; j++) {
          XClearWindow(dpy, win);

          for(i = 0; i < 400; i+=4)
            XDrawArc(dpy, win, gc, report.xbutton.x+j, report.xbutton.y+j, 
                     i,i, 0, 23040);
        }
        break;
      }
      break;
    case KeyPress:
      XCloseDisplay(dpy);
      exit(0);
      /* traitement des évènements de type KeyPress */
      break;
    case ConfigureNotify:
      /* traitement des évènements de type ConfigureNotify */
      break;
    }
  }
}


Etudions le code du double buffer :

Dans le cas normal, on efface la fenêtre, et on dessine directement dans la fenêtre. Le dessin étant très long, on a presque le temps de voir les cercles se dessiner. L'effet est désastreux.

Optimisation des Expose

Si on dessine directement dans un Pixmap celà permet de gérer facilement le rafraichissement de la fenêtre. En effet, si cette dernière est recouverte par une autre fenetre puis découverte, il est très facile de la rafraichir puisqu'on dispose d'une copie de son contenu dans le pixmap.

Méthode "tout en un" :

Si l'on n'est pas à la recherche de performances ou si l'application n'est pas très gourmande en ressources, le plus simple est de recopier intégralement le contenu du pixmap dans la fenêtre, comme nous l'avons fait pour dans l'exemple du double buffer.

  ...
   while(1) {
    XNextEvent(dpy, &report);
    switch (report.type) {
    case Expose:
      /* On efface tous les Expose de la pile */
      while(XCheckTypedEvent(dpy, Expose, &report));
      XCopyArea(dpy, db, win, gc, 0, 0, width, height, 0, 0); 
      /* on force le rafraichissement de l'écran */
      XFlush(dpy);

      break;
    case ButtonPress: 
   ...

Puisque on recopie l'intégralité du pixmap dans la fenêtre, il faut purger les événements Expose qui traînent encore dans la pile. Inutile de redessiner plusieurs fois la même chose!

Rappelons que plusieurs événements de type Expose peuvent être générés dans le cas d'une modification de la taille de la fenêtre ou d'un recouvrement partiel multiple, un événement Expose est généré pour chaque partie rectangulaire à redessiner.

Pour supprimer des événements d'un type donné de la pile des événements on utilise la fonction XCheckTypedEvent()

Méthode "normale" :

On traite les Expose un à un en ne redessinant que les parties rectangulaires correspondantes.

  ...
   while(1) {
    XNextEvent(dpy, &report);
    switch (report.type) {
    case Expose:
      XCopyArea(dpy, db, win, gc, 
                report.xexpose.x, report.xexpose.y,          
                report.xexpose.width, report.xexpose.height,
                report.xexpose.x, report.xexpose.y);
      break;
    case ButtonPress: 
   ...

C'est effectivement plus simple, mais n'oublions pas que dans ce cas on va passer plusieurs fois dans le case dans le cas d'exposition multiple.