Antes de pasar a codificar gráficos en X-Lib se hacen necesarias algunas definiciones básicas relativas al mundo de los gráficos en general, estando algunas particularizadas al nombre que un determinado concepto recibe dentro de Xlib o del sistema X Window. Posteriormente veremos el sistema de gráficos de X Window y las primitivas que Xlib nos ofrece, pero siempre teniendo en cuenta que para escribir aplicaciones gráficas de alta velocidad se hará necesario el uso de librerías externas (como la conocida XwinAllegro) o de las extensiones MitSHM y Xfree86-DGA (buffers de memoria virtual para los gráficos y acceso directo al frame buffer de vídeo, respectivamente), aunque para aplicaciones que no requieran excesiva velocidad todo lo que veremos es más que suficiente para poder crear gran variedad de aplicaciones para X Window.
Los pixels (abreviatura del inglés picture element, PEL) son las unidades mínimas para los gráficos en dos dimensiones, una serie de pequeños puntos con un color asociado cuya conjunción define las diferentes imágenes.
Esos pixels se agrupan en la pantalla para formar imágenes, pero esta agrupación está limitada por la resolución de la pantalla, ya que en cada modo de vídeo (de los diferentes modos disponibles por la tarjeta) hay una cantidad determinada de pixels a lo largo y ancho de la pantalla. Las resoluciones se especifican en el formato NºPixelsAnchura x NºPixelsAltura. Así, un modo de vídeo de 800x600 píxeles de resolución indica que pueden activarse (cada uno de ellos con su color asociado) 800 píxeles en cada línea horizontal de pantalla, con un total de 600 líneas.
Cuando es necesario hacer referencia a una posición concreta de la pantalla (un pixel de la misma) se utilizan sus coordenadas de pantalla para describir su situación. Las coordenadas de pantalla son especificadas mediante un par de números en el formato (posición_x, posición_y). De tal forma, el píxel (400,300) podría considerarse situado aproximadamente en el centro de una pantalla de resolución 800x600, de la misma forma que el (0,0) es la esquina superior izquierda y el (799,599) la esquina inferior derecha (ver figura 1).
Existen diferentes modos de vídeo, es decir, la tarjeta es capaz de programar el sistema para diferentes resoluciones y número de colores, y gracias a esto el programador puede elegir entre todos ellos el más apropiado para su programa. Así, es posible que nuestro programa trabaje en 320x200 a 256 colores, 640x480 a 65.535 colores, o incluso a 1024x768 con 32 millones de colores. Algunas veces es tarea del programador seleccionar el modo de vídeo adecuado para el programa (aplicaciones fullscreen o pantalla completa), mientras que otras es necesario detectar cuál es el modo de vídeo actual y trabajar en este modo (como hace cualquiera de los programas de X Window o Windows que trabajan en modo ventana o modo windowed). Por suerte, en la mayoría de los casos las librerías nos permitirán trabajar con cualquiera de estos modos de vídeo de una manera transparente al programador.
En un PC, a partir de una determinada posición de memoria (0xA0000 en muchos casos) se encuentra la denominada memoria de vídeo (videomemoria o VRAM). La memoria de vídeo es memoria integrada en la tarjeta de vídeo, y cuyos contenidos son leídos por la tarjeta para dibujar en pantalla la imagen que es visualizada en cada momento.
Entre 50 y 90 veces por segundo (según el monitor y modo de vídeo) la tarjeta gráfica lee de esta memoria los valores que contiene y los envía al monitor (gracias al conversor DAC) en forma de haz electrones mediante el tubo de rayos catódicos que hay dentro de la misma. Explicado de una manera sencilla: en nuestro monitor haz un cañón de electrones (TRC) que bombardea la pantalla empezando desde la esquina superior izquierda hasta la esquina inferior derecha, moviendose horizontalmente y bajando a la siguiente línea al llegar al límite derecho de la pantalla. Estos electrones activan los diferentes pixels del monitor de forma que el usuario ve físicamente la imagen. El motivo de que éste se haga tantas veces por segundo es que la excitación de las partículas de la pantalla desaparece con el tiempo, de modo que es obligatorio refrescarlas, y de ahi la frecuencia de 50 a 90 veces por segundo (este proceso se llama retrazado o refresco de la pantalla).
Esto quiere decir que los datos en la videomemoria son leídos muchas veces por segundo y enviados en forma de colores al monitor, lo cual da una posibilidad muy rápida y sencilla de trazar puntos: simplemente escribiendo en la videomemoria (en el PC habitualmente a partir de 0xA0000) en la posición correcta y en el formato correcto (según el modo de vídeo) el valor adecuado, habremos cambiado automáticamente el valor de un pixel de la pantalla. Esta técnica se utilizaba en MSDOS al ser la posibilidad más rápida de que se disponía, pero en el caso de sistemas como Windows, X Window o similares esto es imposible (o difícil) ya que la memoria suele tener algun mecanismo de protección, o bien no es permitido por el propio Sistema Operativo, o, en el peor de los casos, no estamos ante un PC (recordemos que X Window corre en gran variedad de plataformas) y lo que estamos haciendo no tiene ningún sentido físico para la máquina. Para evitar esto se utilizan las funciones incluidas en el propio Sistema Operativo (o Sistema Distribuido, en este caso), que son las apropiadas para cada máquina, de forma que nuestro programa correrá perfectamente en un PC, un Alpha, o cualquier otra plataforma que soporte X Window.
Cada vez que se especifica un color se hace como una tripleta (R,G,B) que define cuánta cantidad de rojo (valor de R), verde (G) y azúl (B) forman dicho color. Estos tres valores son llamados componentes RGB del color. Así, un rojo medianamente intenso se podría definir como (128,0,0) (128 de rojo y nada de las otras 2 componentes). De esta manera (mezclando estas tres componentes con las diferentes intensidades 0-255) se pueden obtener hasta 256*256*256 = 16.7 millones de colores diferentes.
Visto de una manera muy sencilla, si pudiesemos ir a la videomemoria y almacenar allí (en alguna posición de la misma) tres bytes de valores 128, 0 y 0 aparecería en pantalla un pixel de color rojo, cuyas coordenadas dependerian del lugar de la videomemoria (cuyo tamaño varía entre 64Kb y 8Mb según la tarjeta) donde hubiésemos escrito los tres valores (modos TrueColor RGB).
En los modos sin paleta, los colores se definen de la forma explicada hasta ahora, y en el supuesto de que se deseara referirse a un determinado pixel, se hace siempre mediante las tres componentes de color RGB, y éstas son almacenadas en videomemoria (ya sea por nosotros o por las rutinas del S.O. a que llamemos para el trazado de gráficos) mediante las 3 componentes de color. Esto hace que para definir un color sean necesarios 3 bytes y que el tamaño de la videomemoria tenga que ser bastante grande. Por ejemplo, para una resolución de 1024x768 tenemos en total 1024*768=924.672 pixels, y si necesitamos 3 bytes (R,G,B) para definir cada color, entonces hará falta una videomemoria de 924.672*3 = 2.774.016 bytes para poder soportar ese modo gráfico (¡casi 3 MB de videoram!). Para ahorrar memoria gráfica (y por tanto obtener un aumento de velocidad en muchas ocasiones) se utilizan modos con paleta.
En los modos con paleta (paletized o modos pseudocolor) cada color se define con un único byte (en el rango 0-255) y no con tres como en el caso de los modos sin paleta de 24bpp. ¿Cómo puede hacerse esto? En estos modos, cuando decimos que un pixel tiene el valor cero (y así lo escribimos en videomemoria), ese valor es utilizado durante el retrazado de pantalla no cómo el color en sí mismo, sino como un índice a una tabla de 256 colores (conocida como paleta o mapa de colores) de donde se obtendrán los 3 valores RGB a reprensentar en pantalla.
Explicado de una manera esquemática, en la tarjeta existe una tabla de 256 elementos similar en formato a la siguiente:
paleta[256][3] = { 0, 0, 0, 0, 10, 63, 2, 23, 2 ... 63, 63, 63 };A leer la tarjeta un cero de la videomemoria, va a esta tabla y obtiene los valores RGB asociados al color cero, representándolos en pantalla. En este caso R=paleta[0][0], G=paleta[0][1], y B=paleta[0][2]. Si se hubiese especificado como color el 128, por ejemplo, la tarjeta hubiese obtenido los valores RGB correspondientes a la entrada 128 (paleta[128][0], [1] y [2]) de la paleta de colores, y de igual forma para el resto de colores. De esta manera sólo necesitamos un byte para definir un color, aunque debido a esto reducimos el número de colores a 256 como máximo (desde el color 0 al color 255).
Por supuesto, en esos 256 colores hay verdes, azules, rojos, etc. predefinidos. Esto es lo que se llama la paleta por defecto, y consiste en que en esta tabla en principio no es necesario escribir nada porque viene con 256 colores ya predefinidos. Si dichos colores no son los necesarios para nuestra aplicación (ya sea porque necesitamos muchos colores de un matiz, porque vamos a cargarlos desde un fichero gráfico, etc.), es posible cambiarlos (uno a uno o incluso el bloque completo de colores) a los valores deseados. Por defecto el valor cero es el color negro (0,0,0), pero simplemente cambiando las tres primeras entradas de la paleta de colores (mediante funciones del S.O.) podemos hacer que el valor cero represente a otro color RGB especificado.
La única limitación al definir las componentes RGB de los diferentes colores es que el valor máximo de cada componente de color es de 63 (un (63,63,63) sería blanco puro), lo cual nos deja 63*63*63 posibilidades=262.144 colores diferentes, pero sólo 256 de ellos (los elegidos en la paleta) en pantalla. Gracias a la utilización de la paleta y la utilización de un sólo byte por pixel (8 bits por pixel u 8 bpp), ahora sólo son necesarios 1024*768*1=924.672 bytes para la resolución 1024x768 a 256 colores. En uno de los ejemplos de este artículo se muestra un programa que visualiza la paleta de colores activa. Como veremos, en Xlib la paleta de colores se denomina colormap (mapa de colores).
NOTA: La limitación a 63 del valor máximo de las componentes de color se aplica a tarjetas con un DAC de 6 bit (0-63), pero eso será transparente para nosotros usando las rutinas de Xlib, como veremos en los próximos apartados.
Profundidad de color: el número de bits por pixel (bpp) que utiliza un determinado modo de vídeo se conoce como profundidad de color de ese modo (depth), y puede variar en principio entre 8 bpp (Pseudocolor, 1 byte -> 256 colores), 15 bpp (32768 colores), 16 bpp (Hicolour, 65536 colores), 24 bpp ó 32 bpp (TrueColor, 16.7 millones de colores).
Pixmaps: Los pixmaps son (vistos de una manera sencilla) memoria (por ejemplo, un array) donde se almacenan gráficos de igual forma que si fuese la videomemoria, con la salvedad de que no aparecen en la pantalla, sino que se quedan ahi, listos para ser copiados a la videomemoria (a la pantalla) cuando sea necesario.
Un bitmap es un caso particular de pixmap donde cada color está representado por 1 bit (en los modos de vídeo antiguos de 2 colores, donde sólo hay 1 ó 0 representando si había color o no), pero la palabra se ha extendido adquiriendo prácticamente el mismo significado que pixmap. Hoy en día un bitmap/pixmap se puede definir como un array que contiene los valores de pixels que habría que escribir en videomemoria para obtener la imagen a la que representa.
Ejemplo: letra 'A' (.=pixel blanco, #=pixel negro): ........ .######. .##..##. .######. .##..##. .##..##. .##..##. ........En memoria:
char letra[9*9]={ 0,0,0,0,0,0,0,1,1,1, ... , 0};Hay varios de estos bitmaps en /usr/include/X11/bitmaps, y se pueden convertir a ascii mediante el comando bmtoa <nombre_de_bitmap>.
Los pixmaps se usan normalmente como imágenes fijas, fuentes de texto, cursores, fondos de ventana, patrones de rellenado, pantallas virtuales donde escribir para volcarlo a la pantalla real cuando sea necesario, etc. Además sobre los pixmaps es posible utilizar diferentes primitivas de dibujo proporcionadas por Xlib (trazado pixels, líneas, etc.), y transferir su contenido a otro pixmap o ventana (ya que la ventana misma es un pixmap).
Los Contextos gráficos (GC) son recursos utilizados para especificar atributos para las primitivas de dibujo de X, como colores, anchura de línea, patrones de rellenado, etc. Estos elementos serán comentados con mucho más detalle en el próximo número.
A la hora de programar nuestro programa en Xlib nosotros no sabemos si el usuario final estará usando un modo de 256 colores (con paleta) o un modo HiColour, TrueColor (sin paleta, modos de 15, 16, 24 o 32 bpp), de modo que Xlib nos proporciona funciones de forma que mediante su uso nuestro programa funcione en todo tipo de displays.
El problema está, por ejemplo, a la hora de realizar las operaciones gráficas. Si nosotros queremos especificar como color de fondo de nuestra ventana el negro, esto se haría (en teoría) de forma diferente si estamos en un modo de 256 colores (siendo el negro el color 0, por ejemplo) o en un modo sin paleta (sería el color (0,0,0)). Para ello se utiliza una estructura de color y una serie de funciones que nos devolverán el valor del color, siendo ellas las encargadas de buscar en la paleta de colores el color más parecido al que buscamos, para realizar nuestro dibujo en ese color (recordemos que sólo hay 256 colores en los modos con paleta y puede ser que el pedido no esté disponible entre ellos), o de devolver las componentes RGB del color.
Por supuesto, existe un mapa de colores por defecto (al igual que ocurría en el caso de la tarjeta gráfica) definido por el servidor en el momento del arranque del mismo. En este mapa de colores, aparte de la disponibilidad o no del color necesitado, si necesitarámos el color blanco éste bien podría ser el 1, el 34, el 255... (cuál es, no lo sabemos a priori).
Es posible obtener el identificador de este mapa de colores (o paleta) mediante las siguientes funciones:
Colormap XDefaultColormap( Display *display, int pantalla ); Colormap DefaultColormap( display, pantalla );Este identificador (Colormap ID) se utilizará para algunas funciones gráficas, incluida la modificación del mismo, aunque esto último no será necesario ya que el mapa de colores por defecto suele ser suficiente para la mayoría de aplicaciones básicas.
typedef struct { unsigned long pixel; /* pixel value */ unsigned short red, green, blue; /* rgb values */ char flags; /* DoRed, DoGreen, DoBlue */ char pad; } XColor;Los valores red, green y blue son en este caso valores en el rango 0 a 65535 (blanco=(65535,65535,65535)), pero con la misma filosofía que lo visto hasta ahora en el rango 0-255. Estos valores tan elevados permiten representar colores independientemente del número de bits utilizados en el hardware de visualización (es el servidor quien escala estos valores a los valores apropiados para el display, ya sea al rango 0-63, a 0-255 o a otro que pudiese utilizarse en el futuro o en máquinas más potentes...).
Por otra parte, en el campo pixel se devolverá el valor de pixel en las llamadas a las rutinas apropiadas, mientras que pad es un campo de uso del sistema. El campo flags se usa para determinadas aplicaciones con mapa de colores descompuesto (OR inclusivo).
A la hora de localizar un color en el mapa de colores activo se utiliza la función XAllocColor() con los parámetros apropiados:
Status XAllocColor( Display *display, Colormap mapa, XColor *color );Argumentos: El display es aquel obtenido mediante XOpenDisplay(). Mapa es el mapa de colores donde localizar el color (normalmente el ID del Colormap por defecto), y color es un puntero a una estructura XColor correctamente rellenada con los campos red, green y blue conteniendo los valores del color RGB a buscar. Esta función nos devolverá el color resultante (el color disponible más parecido al demandado) en el campo pixel de la estructura XColor utilizada, además de modificar los campos red, green y blue de esta con las componentes de color exactas del color encontrado.
Ejemplo de búsqueda del color blanco:
Display *display; Window window; Colormap mapa; XColor color; unsigned long blanco; (...más código...) display=XOpenDisplay(NULL); mapa=DefaultColorMap(display, DefaultScreen(display)); color.red = 65535; color.blue = 65535; color.green = 65535; XAllocColor( display, mapa, &color ); blanco = color.pixel;Tras esto en la variable blanco dispondríamos de la entrada/valor/celdilla de dicho color, y podríamos utilizarla en las funciones gráficas apropiadas. Como el trozo de código visto arriba suele ser bastante utilizado si tratamos con muchos colores, vamos a crearnos una función que haga todo el trabajo desde una sola llamada:
unsigned long Color( Display *display, unsigned short red, unsigned short green, unsigned short green, Colormap mapa ) { XColor color; color.red=red; color.blue=blue; color.green=green; XAllocColor( display, mapa, &color ); return( color.pixel ); }
! $XConsortium: rgb.txt,v 10.41 94/02/20 18:39:36 rws Exp $ 255 250 250 snow 248 248 255 ghost white 248 248 255 GhostWhite (etc...) 144 238 144 LightGreenEn este fichero están disponibles las definiciones de los tripletes RGB de colores más usuales, y Xlib dispone de una función mediante la cual podemos localizar un color en el mapa de colores especificado dando como parámetro una cadena con el nombre del color deseado:
Status XAllocNamedColor(display, colormap, color_name, screen_def, exact_def);Parámetros: Display *display; -> Display.
De nuevo podemos utilizar esta función dentro de otra que haga más sencillo la utilización de colores en el programa principal:
unsigned long ColorPorNombre( Display *display, char *Nombre, Colormap mapa ) { XColor color, temp; XAllocNamedColor( display, mapa, Nombre, &color, &temp ); return( color.pixel ); }Si vamos a trabajar en nuestra aplicación con el mapa de colores por defecto (muy recomendable a menos que se trate de un juego o programa de dibujo o visualización que requiera de toda la paleta de colores con valores específicos), podemos ahorrarnos también el parámetro mapa:
unsigned long ColorPorNombre( Display *display, char *Nombre ) { XColor color, temp; XAllocNamedColor( display, DefaultColormap(display,DefaultScreen(display)), Nombre, &color, &temp ); return( color.pixel ); }Mediante esta nueva función, las siguientes operaciones son ya perfectamente posibles en nuestro programa:
blanco = ColorPorNombre( display, "snow" ); azul = ColorPorNombre( display, "blue" );Y mediante estas nuevas variables y funciones ya es posible indicarle a las diferentes funciones en qué color queremos realizar cada operación. Hasta ahora la única función que hemo visto que precise colores es la de creación de la ventana del programa, de modo que lo siguiente que veremos será un programa de ejemplo que cree la ventana con un color que no sea negro o blanco (macros WhitePixel y BlackPixel), sino que utilice ColorPorNombre() para localizar los colores deseados. En la próxima entrega, cuando veamos las diferentes primitivas gráficas, usaremos estas funciones y estructuras para dotar a las figuras del color que queramos otorgarle.
Respecto a los mapas de color, si nuestra aplicación utiliza muchos colores distribuidos de una manera concreta (por ejemplo, si usa imágenes pregrabadas a cargar desde fichero con unos colores y paleta prefijados), es posible crear un mapa de color propio mediante la función XCreateColorMap(). Cómo sólo puede haber un mapa de color activo, éste será el asociado a la ventana en primer plano en cada momento.
gcc -o ejemplo1.exe ejemplo1.c -L/usr/X11R6/lib -lX11Sin duda uno de los aspectos fundamentales de este ejemplo es que (como veremos en la próxima entrega) muestra la manera de obtener y modificar los atributos gráficos (GC) de un objeto para modificar diferentes parámetros (como su color asociado). En este caso lo hacemos para imprimir la cadena en amarillo (tras obtener el valor de dicho color con ColorPorNombre()):
/* estructura utilizada para cambiar valores */ XGCValues nuevo; /* 1º creamos un GC para texto amarillo */ gc = XCreateGC( display, window, 0, NULL ); nuevo.foreground=amarillo; /* cambiamos el valor del GC */ XChangeGC( display, gc, GCForeground, &nuevo); /* Ahora seleccionamos la fuente con el GC deseado */ XSetFont( display, gc, font );Todo esto será comentado con más detalle en nuestra próxima entrega del curso. Aparte de esto, el color de la ventana podría haber sido cambiado tras su creación mediante XSetWindowBackground() y XSetWindowBorder(), cuyos parámetros son el display, la ventana y un color.
LISTADO 1: LOCALIZACION DE COLORES: /* Ejemplo1.c -> Ventana de fondo azul y letras amarillas */ #include <X11/Xlib.h> #include <X11/Xutil.h> #include <strings.h> unsigned long ColorPorNombre( Display *, char * ); /*--- Funcion principal main() --------------------------------*/ main() { Display *display; Window window; Font font; GC gc; XEvent evento; int pantalla; unsigned long azul, amarillo; char cadena[]="Esto deberia aparecer en amarillo."; XGCValues nuevo; display = XOpenDisplay( NULL ); font = XLoadFont( display, "8x16" ); pantalla = DefaultScreen( display ); azul = ColorPorNombre( display, "blue"); amarillo = ColorPorNombre( display, "yellow" ); window = XCreateSimpleWindow( display, DefaultRootWindow( display ), 350, 0, 500, 400, 1, amarillo, azul ); XSelectInput( display, window, ButtonPressMask | KeyPressMask ); XMapWindow( display, window ); gc = XCreateGC( display, window, 0, NULL ); nuevo.foreground=amarillo; XChangeGC( display, gc, GCForeground, &:nuevo); XSetFont( display, gc, font ); while( 1 ) { XNextEvent( display, &:evento ); switch( evento.type ) { case ButtonPress: XDrawString( display, window, gc, evento.xbutton.x, evento.xbutton.y, "X", 1); XDrawString( display, window, gc, 10, 150, cadena, strlen(cadena)); break; case KeyPress: exit(0); } } } /*--- Rutina para localizar colores por nombre ----------------*/ unsigned long ColorPorNombre( Display *display, char *Nombre ) { XColor color, temp; XAllocNamedColor( display, DefaultColormap(display,DefaultScreen(display)), Nombre, &:color, &:temp ); return( color.pixel ); }El otro ejemplo (listado 2) muestra el mapa de colores completo en subventanas de la ventana principal. Para poder ver bien este mapa de colores habremos de tener configurado el servidor X Window en un modo de 256 colores (sino sólo veremos 64 celdillas), y arrancarlo mediante la orden "startx -- -bpp 8". Este listado se ha obtenido de uno de los tutoriales de Xlib en Internet, y muestra cómo obtener el número de colores del mapa por defecto así como crear ventanas hijas.
LISTADO 2: CAMBIO DE ATRIBUTOS DE COLOR: /* Ejemplo2 -> Mapa de colores */ #include <X11/Xlib.h> #include <X11/Xutil.h> #include <stdio.h> #define ANCHO 8 #define ALTO 20 void main() { Display *display; Window madre, hija; Colormap mapa; XColor borde, fondo, temp; int numColores, f; XSetWindowAttributes atributos; display = XOpenDisplay( NULL ); mapa = DefaultColormap( display, DefaultScreen( display ) ); numColores = XDisplayCells( display, DefaultScreen( display ) ); printf( "\nHay %d colores en el DefaultColormap.", numColores ); XAllocNamedColor( display, mapa, "yellow", &borde, &temp ); XAllocNamedColor( display, mapa, "blue", &fondo, &temp ); madre = XCreateSimpleWindow( display, DefaultRootWindow( display ), 0, 0, (ANCHO*numColores) + 10, ALTO + 10, 10, borde.pixel, fondo.pixel ); atributos.override_redirect = True; XChangeWindowAttributes( display, madre, CWOverrideRedirect, &atributos ); XMapWindow( display, madre ); XFlush( display ); for ( f=0; f<numColores; f++) { hija = XCreateSimpleWindow( display, madre, (f*ANCHO)+5, 5, ANCHO, ALTO, 0, f, f ); XMapWindow( display, hija ); } XFlush(display); getchar(); }
Pulse aquí para bajarse los ejemplos y listados del artículo (6 Kb).
Figura 1: "El color en Xlib."
Santiago Romero