INTRODUCCION A LA PROGRAMACION EN X WINDOW

Artículo 8: GRAFICOS EN XLIB (III).

Autor: (c) Santiago Romero.
Revista: Programación Actual (Prensa Técnica) nº 26, Mayo-1999


En la pasada entrega aprendimos la forma de modificar los atributos o propiedades asociadas a los Contextos Gráficos, preparándo al servidor X antes de utilizar las primitivas gráficas en sí. Este mes veremos las principales funciones gráficas de la API de X Lib.

Una vez conocemos todos los posibles atributos del GC y sabemos crearlos, copiarlos y modificarlos, podemos pasar ya a ver las funciones gráficas básicas de que disponemos, que van desde el trazado de pixels, líneas, polilíneas, segmentos, arcos, rectángulos, polígonos y similares hasta el mismo uso de las imágenes como pixmaps, images, bitmaps y similares, todo ello con funciones proporcionadas por Xlib.


Pixels


XDrawPoint(Display *display, drawable drawable,
	GC gc, int x, int y);

Como ya vimos el mes pasado, esta función dibuja un pixel en (x,y) del display y drawable especificado y con los atributos gráficos (sobre todo el color GC.foreground) deseados.


XDrawPoints(Display *display, drawable drawable,
	GC gc, XPoint *points, int npoints, int mode);

Dibuja npoints puntos con el atributo GC, en el modo especificado (entre absoluto y relativo). Los puntos se pasan a través de la estructura XPoints de campos unsigned short x e y.

Cuando se utilizan estas funciones se realiza una llamada al servidor X, que es quien las ejecuta. Esto implica transferencia de datos entre el cliente y el servidor, de tal modo que en caso de estar en máquinas diferentes (es decir, un cliente que accede a un servidor X en red, en contraposición al caso aislado de un usuario con Xwindow instalado en su propio sistema), estarán siendo enviadas estas peticiones por red (todos los parámetros, así como la llamada al servidor X). Debido a esto no podemos tratar imágenes completas a base de muchas llamadas a estas funciones para no reducir el rendimiento de la red. La solución de este tipo de problemas se aborda con el uso de XImage, que, como veremos, hace que la imagen y las peticiones residan en el ordenador cliente y no en el servidor, con lo que el tratamiento se realiza localmente sin necesidad de acceso a la red que lo conecte con el servidor nada más que para el volcado en el pixmap de la ventana del programa.


Lineas

En Xlib también disponemos de las clásicas funciones para el trazado de líneas, polilíneas y segmentos, con los siguientes prototipos:


XDrawLine( Display *display, Drawable drawable, GC gc,
	int x1, int y1, int x2, int y2);
Esta función dibuja una línea entre las coordenadas (x1,y1) y (x2,y2), usando los atributos especificados en GC. Las coordenadas están referidas a la esquina superior izquierda (0,0) de la ventana (coordenadas relativas) en lugar de referirse a la propia pantalla. Esto hace más sencillo el trabajo de los programas con sus ventanas, ya que no tienen que preocuparse por la posición de las mismas a la hora de trazar gráficos en las mismas.


XDrawLines( Display *display, Drawable drawable,
	GC gc, XPoint *points, int n, int mode );

Esta función traza la polilínea especificada por los puntos contenidos en el array de puntos points.


XDrawSegments( Display *display, Drawable drawable,
	GC gc, XSegment *segments, int n );

Dibuja los n segmentos pasados en el array de estructuras XSegment, cuyos campos son los siguientes:


typedef structure 
{
	short x1, y1, x2, y2;
} XSegment;

A la hora de trazar las líneas (con cualquiera de las tres funciones comentadas), es muy importante haber asignado bien los parámetros de GC para una correcta representación con el color, anchura y estilo deseados. Para ello hay que hacer un uso adecuado de los parámetros foreground, background, line_style, line_width, cap_style y join_style del GC.

Los atributos foreground y background pueden establecerse bien mediante XChangeGC() o mediante XSetForeGround y XSetBackGround, mientras que para modificar el resto de atributos de la línea se hace uso de la función XSetLineAttributes(), con la siguiente definición:


XSetLineAttributes( Display *display, GC gc,
	unsigned int line_width, int line_style,
	int cap_style, int join_style );
donde los argumentos de la función son los siguientes:

line_width: ancho de la línea en pixels (0 por defecto, es la línea más fina).

line_style: Estilo de línea, a elegir entre los siguientes valores.
-> LineSolid: Línea sólida.
-> LineOnOffDash: Líneas discontinuas que usan 1er plano y fondo.
-> LineDoubleDash: Líneas discontinuas que usan 1er plano y fondo.

cap_style: Estilo de terminación de la línea, a elegir entre:
-> CapNotLast -> Termina la línea sin llegar al último pixel.
-> CapRound -> Sobrepasa el punto final y termina redondeándose.
-> CapButt -> Termina la línea sin redonde y llegando al punto final.
-> CapProyecting -> Soprepasa el punto final pero sin redondearse.

join_style: Estilo de unión de líneas con XdrawLine:
-> JoinMiter: Esquinas cuadradas.
-> JoinRound: Esquinas redondeadas.
-> JoinBevel: Esquina de forma triangular.
Veamos un ejemplo de trazado de líneas que dibujaría 2 líneas de colores azul y rojo:


unsigned long color1, color2;
GC gc;

..apertura del display, etc..

color1 = ColorPorNombre( display, "blue" );
color2 = ColorPorNombre( display, "red"   );
gc = XCreateGC( display, window, 0, NULL );

XSetForeground( display, gc, color1 );
XDrawLine( display, window, gc, 50, 100, 250, 100 );
XSetForeground( display, gc, color2 );
XDrawLine( display, window, gc, 50, 200, 250, 200 );

Otra posibilidad en el uso de las líneas es definir patrones, lo cual quiere decir que si queremos que las líneas estén dibujadas a partir de patrones de, por ejemplo, 10 pixels, donde los 2 primeros sean de color de primer plano, los 6 siguientes del color de fondo, y los 2 últimos de nuevo del color de primer plano (por ejemplo), esto puede hacerse mediante la función XsetDashes():


XSetDashes( Display *display, GC gc, int offset,
	char lista[], int num_trozos )

Ejemplo:


char patron[] = { 10, 2, 6, 2 };
(...etc...)
XSetDashes( display, gc, 0, patron, 3 );

Para más información sobre esta función puede consultar la página man correspondiente.


Rectángulos


XDrawRectangle( Display *display,
	Drawable drawable, GC gc,
	int x, int y, unsigned int width,
	unsigned int height );

Dibuja un rectángulo cuya esquina superior izquierda está situado en las coordenadas (x,y) relativas a la ventana, y con una anchura width y altura height relativas a dichas coordenadas de inicio, utilizando para su dibujado el GC especificado.


XDrawRectangles( Display *display,
	Drawable drawable, GC gc,
	XRectangle rectangles[], int numrect);

Dibuja numrect rectángulos especificados en el array de tipo Xrectangles:


typedef struct 
{
	short x, y;
	unsigned short width, height;
} XRectangle;

Otras dos funciones relacionadas son:


XFillRectangle ( Display *display,
	Drawable drawable, GC gc,
	int x, int y, unsigned int width,
	unsigned int height);

XFillRectangles( Display *display,
	Drawable drawable, GC gc,
	XRectangle rectangles[], int numrect);

Estas funciones hacen lo mismo que las dos primeras (dibujar rectángulos) pero los trazan rellenos del color de primer plano (cajas o boxes).


Arcos

Para el trazado de arcos disponemos de las siguientes funciones:


XDrawArc( Display *display, Drawable drawable,
	GC gc, int x, int y, unsigned int width,
	unsigned int height, int angle1, angle2);
Dibuja un arco cuyo valor angle1 se refiere al comienzo del arco en grados multiplicados por 64, y cuyo final es especificado por angle2 de forma relativa al comienzo del arco.


XDrawArcs( Display *display, Drawable drawable, GC gc, XArc arcs[], int numarcs);
Al igual que en otras primitivas gráficas, esta función permite dibujar más de un arco.


typedef struct
{
short x, y;
unsigned short width, height;
short angle1, angle2;
} XArc
De nuevo al igual que en el caso de los rectángulos, existen funciones para dibujar arcos rellenos:


XFillArc( Display *display, Drawable drawable,
	GC gc, int x, int y,
	unsigned int width, unsigned int height,
	int angle1, angle2 );

XFillArcs( Display *display, Drawable drawable,
	GC gc, XArc arcs[], int numarcs);
Por último, mediante la función XSetArcMode() es posible especificar los modos de rellenado de los arcos:


XSetArcMode( Display *display, GC gc, int mode );
El parámetro mode puede valer las constantes ArcPieSlice y ArcChord.


Relleno de polígonos

Para el relleno de polígonos en Xlib disponemos de las siguientes funciones:


XFillPolygon( Display *display, Drawable drawable,
	GC gc, XPoint points[],
	int numpoints, int style, int mode );
Dibuja un polígono relleno de n puntos (cuyos valores pueden obtenerse en el array de estructuras XPoint que se le pasa como parámetro), con un determinado tipo de rellenado (style). Estos puntos pueden tomarse como coordenadas absolutas o relativas, según el valor del parámetro modo, como se vio en la anterior entrega para XdrawPoints().

Los posibles valores para el estilo o modo de dibujado son:

Nonconvex: Polígono no convexo (cóncavo) y no tiene líneas que se intersecten.
Convex: Polígono convexo.
Complex: El polígono tiene líneas que se intersectan.

Otra función relacionada es:


XSetFillRule( Display *display, GC gc, int fillrule )
El parámetro fillrule permite indicar el modo de relleno cuando haya líneas que se intersecten, pudiendo tomar los valores EvenOddRule (si las áreas se superponen un número impar de veces no se dibuja la intersección) o WindingRule (las áreas de intersección siempre son rellenadas).


Otras Funciones

Otras funciones de menos importancia por ahora pero que conviene conocer (al menos su nombre, para poder ir a la página man correspondiente en caso de duda) son las siguientes:

XSetState() -> Modificación de varios parámetros para gráficos.
XSetFunction() -> Modificación de la función lógica con que trazar en pantalla. Gracias a esta función es posible hacer un AND, OR o XOR de un pixel con otro en pantalla, copiar directamente un gráfico, etc, lo cual nos permitiría trazar bitmaps con zonas transparentes, así como otras operaciones básicas.
XSetPlaneMask() -> Cambio de la máscara de planos.
XSetClipOrigin() -> Permite modificar la (x,y) del origen de clipping. El clipping o recorte es el proceso que realiza el servidor cuando trazamos gráficos que se salen de la ventana. Para evitar escribir en zonas que no son de nuestra aplicación (el escritorio u otras ventanas), el servidor recorta todos los gráficos (por defecto) que salgan del área cliente de nuestra ventana. Con esta función y las 2 siguientes podemos definir otro origen de recorte de gráficos para preservar determinadas zonas de nuestra ventana si ese fuera nuestro deseo.
XSetClipMask() -> Máscara del clipping.
XSetClipRectangle() -> Máscara del clipping.
XSetTSOrigin() -> Origen de tiles y stipples.
XSetTile() -> Seleccionar tile.
XSetStipple() -> Seleccion stipple.
Todas ellas son funciones de conveniencia, es decir, se utiliza para cambiar un valor del GC especificado sin necesidad de modificar el resto de valores (como hace, por ejemplo, XSetBackGround() para gc.background).


Bitmaps, Pixmaps e Imagenes

Como ya sabemos, los pixels se agrupan formando pixmaps, que puede concebirse como un área cuadrada de puntos de color que dan lugar a una imagen. Un caso especial de pixmaps son los bitmaps, donde los pixels sólo pueden tomar valores de 0 ó 1 (negro/blanco), aunque el término actualmente se acuña con el mismo significado que pixmap (ya que los bitmaps quedan atrás, en los tiempos de los modos de vídeo de 2 colores).

pixmaps

Otros casos especiales de pixmap son los tiles y stipples. Ambos son pixmaps pequeños que se utilizan para rellenar áreas. El tile tiene la misma profundidad de color que el drawable en que se usará (y son opacos) mientras que el stipple tiene una profundidad de color de 1, pudiendo utilizarse para rellenados con color transparente.

Los pixmaps se suelen utilizar para fondos y bordes de las ventanas, como tiles, para double buffering y como pantallas virtuales para disponer de una copia de la ventana en memoria y poder restaurar cualquier parte de la misma en caso de haber un solapamiento. Los bitmaps además suelen usarse como gráficos de cursores, fuentes, stipples y clipping masks (máscaras para el clipping).

Un caso más general de imágen es la estructura XImage, utilizada por las siguientes funciones gráficas:


XImage *XGetImage(Display *display,
	drawable drawable, int x, int y,
	unsigned int width, unsigned int height,
	unsigned long plane_mask, int format);
Esta función lee del drawable especificado la imagen contenida entre (x,y) y (x+width,y+height) a la estructura XImage que devuelve. Si el argumento format es XYPixmap, la imagen contendrá sólo los planos de bits especificados en el parámetro plane_mask. Si format es ZPixmap, devolverá como 0 los bits no especificados en plane_mask. Los campos de la estructura XImage son los siguientes:


typedef struct _XImage {
int width, height; /* size of image */
int xoffset; /* number of pixels offset in X direction */
int format;  /* XYBitmap, XYPixmap, ZPixmap */
char *data; /* pointer to image data */
int byte_order; /* data byte order, LSBFirst, MSBFirst */
int bitmap_unit; /* quant. of scanline 8, 16, 32 */
int bitmap_bit_order; /* LSBFirst, MSBFirst */
int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */
int depth; /* depth of image */
int bytes_per_line; /* accelarator to next line */
int bits_per_pixel; /* bits per pixel (ZPixmap) */
unsigned long red_mask; /* bits in z arrangment */
unsigned long green_mask;
unsigned long blue_mask;
/* (etc... no relevantes, funciones, etc.) */
}
Otras funciones de interés son:


XPutImage(Display *display,
	drawable drawable, GC gc,
	XImage *image,
	int src_x, int src_y,
	int dest_x, int dest_y,
	unsigned int width,
	unsigned int height);
Esta función copia el área cuadrada desde (src_x, src_y) en Ximage a (dest_x,dest_y) en drawable, con la anchura y altura especificadas en width y height.


XCopyArea(Display *display, drawable src,
	drawable dest, GC gc, int src_x, int src_y,
	unsigned int width, unsigned int height,
	int dest_x, int dest_y);
XCopyArea copia un área cuadrada desde el drawable src al drawable dest. Ambos han de tener la misma profundidad de color.

La principal ventaja de trabajar con estructuras XImage consiste en que éstas se almacenan localmente en el cliente, en lugar de almacenarse en el servidor como las estructuras de Pixmaps, Bitmaps, Tiles o Stipples. Esto hace que el trabajo de borrado, copiado y dibujado en estas estructuras no tenga que pasar por la red local y por tanto es prácticamente instantáneo (el lector sólo debe pensar que para una ventana de 640x480 pixels en formato pixmap habrían de transferirse una gran cantidad de datos por red hasta el servidor). Al ser un trabajo local, nos encontramos con la desventaja de que se ha de trabajar con los datos con la misma profundidad de color de que disponga el servidor. Esto implica que tenemos que saber escribir los gráficos en el formato correcto dentro de las estructuras para posteriormente realizar el volcado a la ventana (para realizar un volcado de XImage a Pixmap es imprescindible que ambas estructuras estén a la misma profundidad de color, así como los gráficos definidos dentro de ellas).


Un ejemplo con pixmaps

En el ejemplo de este mes vamos a crear una ventana con un pixmap de fondo. El pixmap lo convertiremos a partir de un bitmap mediante la función XCreatePixmapFromBitmapData() (consultar páginas man) y lo tomaremos de /usr/include/X11/bitmaps, donde tenemos varios de ellos disponibles. El bitmap está en formato 0-1, pero mediante la función comentada se puede convertir a formato pixmap convirtiendo los 0 en colores de fondo y los 1 en colores de primer plano (es decir, lo convertimos en un pixmap, donde no se usan bits (0-1) para representar cada pixel sino bytes). Tras esto utilizaremos dicho pixmap como fondo de la ventana, como puede verse en el listado 1.



 LISTADO 1: Ventana con pixmap de fondo:


/* Ejemplo1.c -> Creacion de una ventana con un pixmap de fondo. */
/* gcc -o ejemplo1 ejemplo1.c -lX11 -L/usr/X11R6/lib             */

#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/bitmaps/xlogo64>

unsigned long ColorPorNombre( Display *, char *);

void main(int  argc, char **argv)
{
  Display *display;
  Window ventana;
  int pantalla;
  int profundidad, clase;
  int x, y, ancho, alto, 
colorfondo, colorfore;
  Visual *visual;
  Pixmap pixmapfondo;
	/* atributos de las ventanas */
  XSetWindowAttributes atrib;      
  unsigned long mascara;

  display= XOpenDisplay(NULL);
  pantalla=DefaultScreen(display);

  x=y=200;
  ancho=alto=300;
  profundidad=CopyFromParent;
  visual=DefaultVisual(display, pantalla);
  clase=InputOutput;
  colorfondo = ColorPorNombre(display, "red");
  colorfore  = ColorPorNombre(display, "green");

  pixmapfondo= XcreatePixmapFromBitmapData
		(display,RootWindow(display, pantalla),
		xlogo64_bits, xlogo64_width, xlogo64_height,
		colorfore, colorfondo,
     DefaultDepth(display, pantalla));

	/* Asociamos el pixmap al fondo de la ventana.
		Este atributo de la ventana lo estableceremos con
		XCreateWindow() indicandole que queremos que la
     ventana tenga un pixmap de fondo. */
	
	atrib.background_pixmap=pixmapfondo;

  /* Máscara sirve para indicarle a CreateWindow
		qué parametros de la ventana queremos especificar
		desde XSetWindowAttributes (atrib) */
  mascara= CWBackPixmap;
  ventana= XCreateWindow(display, 
		RootWindow(display, pantalla),
     x , y, ancho, alto, 2, profundidad, clase, visual,
		mascara, &atrib);
  XMapWindow(display, ventana);
  XFlush(display);
  getchar();
  XCloseDisplay(display);
}

unsigned long ColorPorNombre( Display *display, char *Nombre )
{
  XColor color, temp;
  XAllocNamedColor( display,
     DefaultColormap(display,DefaultScreen(display)),
     Nombre, &color, &temp );
  return( color.pixel );
}

El prototipo de la función utilizada para la conversión es:


Pixmap XCreatePixmapFromBitmapData( Display *display, Drawable drawable,
  char *data, int width, int height, long fg, long bg, int depth);
El bitmap utilizado está definido de la siguiente forma (bits a uno y a cero):


#define xlogo64_width 64
#define xlogo64_height 64
static unsigned char xlogo64_bits[] = {
   0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xfe, 0xff, 0x01, 0x00,
   0x00, 0x00, 0x00, 0xf8, 0xfc, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x7c,
   0xf8, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x3e, 0xf8, 0xff, 0x07, 0x00,
   (etc etc etc...)
   0x00, 0xc0, 0xff, 0x3f, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x7f,
   0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff};
Este bitmap está definido usando los 8 bits de cada char como pixels individuales activados (=1) o desactivados (=0), información que describe el bitmap.


En resumen

Hasta hoy hemos aprendido a crear ventanas, gestionar los eventos X, y trazar primitivas gráficas simples en pantalla. En nuestro siguiente capítulo abordaremos el trazado de texto, un tema verdaderamente importante pues gracias a él ya podrán crearse gran variedad de programas con los conocimientos adquiridos hasta ahora (todos los programas de texto podrían adaptarse a X Window), así y el tratamiento de los eventos de exposición (Expose), la verdadera clave de los programas gráficos para el refresco del contenido de la ventana ante solapamientos y similares. Ese será nuestro último capítulo sobre la API básica de gráficos en Xlib.

Pulse aquí para bajarse los ejemplos y listados del artículo (7 Kb).

Figura 1: "Ejemplo de pixmap."

Santiago Romero


Volver a la tabla de contenidos.