INTRODUCCION A LA PROGRAMACION EN X WINDOW

Artículo 12: PROGRAMACION CON IMLIB.

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



Una necesidad básica en cualquier aplicación es cargar (o grabar) ficheros gráficos en formatos GIF, PNG, JPEG o similares, así como convertirlos a pixmaps con la profundidad de color adecuada y usarlos en nuestros programas. El paquete Imlib y su conjunto de librerías nativas nos permite esto y mucho más bajo X Lib.


Hemos comentado en anteriores capítulos el problema típico que se nos presentaba a la hora de cargar imágenes en nuestro programa: en principio nosotros debemos ser los encargados de convertir el gráfico almacenado en disco a la misma profundidad de color (8 bpp, 16 bpp, 24 bpp, etc.) que el display del usuario, ya que las funciones de copia de pixmaps a ventanas (o entre pixmaps) no realizan conversión del formato interno de la imagen y para el proceso de copia ambos pixmaps deben estar definidos en los mismos términos de profundidad o depth. Esto implica saber realizar la transformación de cualquier imagen a cualquier profundidad de color, pues en principio en usuario de nuestro programa puede tener el desktop en 256, 65536 o hasta en 16 millones de colores.

Ese problema no es el único que se nos presentaba, ya que tampoco es fácil realizar la carga y descompresión de ficheros de tipo JPEG, GIF o PNG, ya que implica conocer su formato interno, realizar librerías de carga y descompresión LZW, fractal, inflate, etc. Todo esto es solucionable utilizando las librerías libjpeg, libgif, libungif, libpng o libtiff de Linux (así como otras disponibles), pero con la particularidad de disponer de un juego de funciones por cada una de ellas, y de que debemos distinguir en nuestro código el tipo de imagen a cargar para llamar a la función de carga y descompresión de la librería adecuada.

Para solucionar esto disponemos de la librería Imlib, la cual da acceso estas librerías a partir de una sola función de carga y que además, como veremos, dispone de funciones de conversión automática a la profundidad de color del display actual e incluso funciones de ampliación y reducción de imágenes, con lo que es posible utilizar iconos de cualquier tamaño, hacer thumbnails de imágenes, aumentar o reducir los pixmaps al tamaño deseado, etc. La versión de Imlib de la que dispone el autor es la 1.9.2, pero todo lo que expliquemos hoy será seguramente aplicable a otras versiones.


OBTENIENDO E INSTALANDO IMLIB

Los fuentes de Imlib (para la versión 1.9.2) se pueden obtener en ftp.gnome.org:

ftp://ftp.gnome.org/pub/imlib/TAR/imlib-1.9.2.tar.gz

Los paquetes RPMs ya compilados (así como el .src.rpm de los fuentes) para sistemas Redhat, Suse o similares están disponibles en el mismo ftp site:

ftp://ftp.gnome.org/pub/imlib/RPM/imlib-1.9.2-1.i386.rpm
ftp://ftp.gnome.org/pub/imlib/RPM/imlib-devel-1.9.2-1.i386.rpm
ftp://ftp.gnome.org/pub/imlib/RPM/imlib-cfgeditor-1.9.2-1.i386.rpm
ftp://ftp.gnome.org/pub/imlib/SRPM/imlib-1.9.2-1.src.rpm

Dado que Imlib permite acceso a los diferentes formatos gráficos más habituales para usarlos en nuestros programas, necesitaremos descargar e instalar (si no las tenemos ya) todas las librerías gráficas que dan control sobre los diferentes formatos para acceder nativamente a ellos (JPEG, GIF, PNG, TIFF, etc.). Las fuentes de las librerías (al menos en las versiones que tiene instalada el autor, y que puede cambiar con las diferentes actualizaciones que se van haciendo a las mismas) están disponibles en ftp.gnome.org bajo los siguientes nombres:

giflib-3.0-2.tar.gz
libungif-3.0.tar.gz
ibgr-2.0.13.tar.gz
ibgr-scripts.tar.gz
ibpng-1.0.1.tar.gz
zlib-1.1.2.tar.gz
tiff-v3.4-tar.gz
jpegsrc.v6b.tar.gz
gtk+-1.1.12.tar.gz
glib-1.1.3.tar.gz
ImageMagick-4.0.5.tar.gz

Si en lugar de obtener los fuentes deseamos adquirir las librerías ya compiladas y listas para instalar, se deben descargar los siguientes paquetes:

giflib-3.0-2.i386.rpm
libungif-3.0-3.i386.rpm
libungif-devel-3.0-3.i386.rpm
libungif-progs-3.0-3.i386.rpm
ImageMagick-4.0.5-4.i386.rpm
ImageMagick-devel-4.0.5-4.i386.rpm
libgr-2.0.13-10.i386.rpm
libgr-devel-2.0.13-10.i386.rpm
libgr-progs-2.0.13-10.i386.rpm
libjpeg-6b-5.i386.rpm
libjpeg-devel-6b-5.i386.rpm
libpng-1.0.1-1.i386.rpm
libpng-devel-1.0.1-1.i386.rpm
libtiff-3.4-4.i386.rpm
libtiff-devel-3.4-4.i386.rpm
zlib-1.1.2-1.i386.rpm
zlib-devel-1.1.2-1.i386.rpm
glib-1.1.3-1.i386.rpm
glib-devel-1.1.3-1.i386.rpm
gtk+-1.1.12-1.i386.rpm
gtk+-devel-1.1.12-1.i386.rpm

Las versiones de gtk+, gtk+-devel y glib pueden haber variado mucho dado su continuo desarrollo, al igual que con el resto de paquetes, por lo que se recomienda obtener siempre las ultimas versiones de los paquetes mencionados.

Los usuarios de Debian pueden perfectamente utilizar alien sobre los paquetes rpm y obtener así los .deb, compilar los ficheros .tar.gz, o simplemente ir a su ftp habitual de paquetes debian (por ejemplo, ftp.debian.org) desde donde obtener las librerías para su distribución Linux. Los que dispongan de rpm en su sistema simplemente deben ejecutar rpm -i *.rpm para realizar la instalación de todos estos paquetes. También es posible que ya dispongamos de todas estas librerías en nuestros sistemas (e incluso en el mismo CD de la distribución que usemos) de modo que podremos ponernos directamente a estudiar su uso práctico a nivel de programación.


QUÉ ES IMLIB

Como se comenta en la documentación adjunta, Imlib es una librería general de carga y renderizado de imágenes creada con el fin de permitir la carga, manipulación y conversión de imágenes, incluyendo la transformación interna del tipo de imagen a un pixmap (en general a un drawable) con el formato adecuado para su impresión en pantalla (ya sea en otro pixmap o en una ventana) con las funciones vistas en capítulos anteriores. Incluye además otras funciones básicas de manipulación que comentaremos en su momento (como escalado de imágenes, rotación o flipping, por ejemplo).


VENTAJAS DE IMLIB

La gran ventaja de Imlib es que su uso es independiente del tipo de display que el usuario esté utilizando, ya que para realizar la carga, Imlib trabajará internamente en modo de 24 bpp (8 bits para RED, 8 para GREEN y 8 para BLUE), de forma que nunca haya pérdida de color de la imagen en su almacenamiento. Además incorpora un canal AlphaChannel que podrá ser utilizado por hardware por aquellas tarjetas que lo utilicen para transparencias (o en futuro por la propia librería). Cuando el programador desee puede renderizar (volcar) dicha imagen (por ejemplo, durante la carga del programa) en un drawable (pixmap, ventana) para su posterior utilización con el tipo de display activo por defecto, generando así drawables listos para ser utilizados o volcados sobre el display. Esto implica que Imlib realizará por nosotros la conversión gráfica de 24 bpp a la profundidad actual del display, de una manera transparente y rápida para el programador, realizando el dithering o mapeado de colores de forma que también funcione en modos de 8 bpp (256 colores). Además implementa un correcto uso de transparencias e internamente es capaz de cachear las imágenes que carga. Comentando más ampliamentes sus funciones principales:

La ventaja que tiene para nosotros aprender a utilizar Imlib es que además de disponer de funciones Xlib para su uso incluye también funciones (con el mismo nombre pero con la cadena gdk_ delante) para su uso con GDK, que es parte de Gtk y que puede ser utilizado para acelerar y facilitar la creación de programas con interfaces gráficos al estilo gtk (ver gnome). Esto implica que podremos utilizar esta librería tanto para programas a bajo nivel (con Xlib) o de alto nivel (gtk/gdk) con el mismo set de funciones.

FUNCIONAMIENTO DE IMLIB

Imlib funciona de la siguiente manera: Lo primero, se debe inicializar la librería pasandole el display a utilizar (y es que Imlib tiene soporte para múltiples displays, inicializándola una vez por display a utilizar), mediante la función Imlib_init(display). Tras la inicialización deberemos cargar el fichero gráfico a utilizar en una estructura del tipo ImlibImage:


ImlibImage *imagen;
ImlibData *id;
imagen=Imlib_load_image(id, "fichero.ext" );

Imlib reconocerá el formato gráfico a carga por medio de la extensión atribuida al fichero gráfico, e introducirá los datos gráficos en las estructuras especificadas, cuyos campos podrán informarnos de las características de la misma:


anchura=imagen->rgb_width;
altura=imagen->rgb_height;
(etc...)

Lo siguiente es, simplemente, renderizar o aplicar el bitmap (2 funciones según nuestras necesidades) sobre un Drawable dado (por ejemplo, una ventana o un elemento de tipo pixmap):


Imlib_apply_image(id, imagen, ventana);
La función Imlib_render_image(id, imagen, anchura, altura) funciona mapeando el bitmap (internamente a 24bpp) sobre el pixmap especificado y con la anchura y altura deseada, mientras que Imlib_apply_image( id, imagen, ventana) realiza la misma función pero sobre una ventana y con la anchura y altura de la misma.

Las ventajas son muy claras. Gracias a Imlib podemos cargar todos nuestros gráficos en pixmaps durante la inicialización del programa para su posterior uso, y desde ficheros de formatos conocidos que podremos crear con cualquier herramienta de dibujo.

Veamos un ejemplo de uso de la librería Imlib basándonos en nuestro programa de ejemplo del mes (listado 1), poniendo en práctica todo lo que hemos visto en esta introducción a Imlib. Este programa carga el fichero gráfico que se le pase como parámetro (de cualquier formato) y lo visualiza en pantalla en la ventana que él mismo crea (como puede apreciarse en la figura 1, donde se visualizan varios ficheros GIF y JPG). Si mediante el uso del ratón modificamos el tamaño de la pantalla, la imagen se reducirá o ampliará automáticamente hasta ocupar la totalidad del área cliente de la misma. El programa finalizará al pulsar una tecla o mediante un ctrl+c en la xterm donde fue lanzado.


Figura 1


Los programas en Imlib se compilan como mínimo con las librerías libX11, libXext, libjpeg, libpng, libtiff, libz, libgif, libm e libImlib. Esto deja la orden de compilación de un programa cualquiera como:


gcc -o programa programa.c -I/usr/X11R6/include -I/usr/local/include
    -L/usr/X11/lib -L/usr/local/lib -lX11 -lXext -ljpeg -lpng
    -ltiff -lz -lgif -lm -lImlib

Listado 1:

/*-------------------------------------------------------------- Ejemplo.c Muestra como cargar, convertir y mapear imagenes mediante Imlib. Compilacion: gcc ejemplo.c -o ejemplo -I/usr/X11R6/include -I/usr/local/include -L/usr/X11/lib -L/usr/local/lib -lX11 -lXext -ljpeg -lpng -ltiff -lz -lgif -lm -lImlib (todo en una sola linea). ---------------------------------------------------------------*/ #include <X11/Xlib.h> #include <X11/Xutil.h> #include <X11/extensions/shape.h> #include <Imlib.h> int main(int argc, char **argv) { Display *display; ImlibData *id; XSetWindowAttributes attr; Window ventana; ImlibImage *imagen; int anchura,altura; XEvent evento; char done=0; /* Comprobamos el nº de parametros del programa: */ if (argc<=1) { printf("Visualizador de ficheros de imagenes:\n"); printf("Uso:\n %s fichero_de_imagen\n",argv[0]); exit(1); } /* Connectar al servidor X (display por defecto) */ display=XOpenDisplay(NULL); /* Inicializar Imlib para su uso con dicho display */ id=Imlib_init(display); /* Cargar la imagen especificada por el argumento argv[1] del programa. No es necesario indicarle a Imlib_load_image() en tipo de imagen pues lo averigua automaticamente a partir de la extension del fichero, lo cual permite carga de ficheros gif, jpeg, tiff, etc... */ imagen=Imlib_load_image(id, argv[1]); /* Extraer los datos de anchura y altura de la imagen original */ anchura=imagen->rgb_width; altura=imagen->rgb_height; /* Crear la ventana principal de nuestro programa (como en las anteriores entregas del curso. Ademas pedimos que se nos notifiquen los eventos de modificacion de la ventana (StructureNotifyMask) */ ventana=XCreateWindow(display, DefaultRootWindow(display), 0, 0, anchura, altura, 0, id->x.depth, InputOutput, id->x.visual, 0, &attr); XSelectInput(display, ventana, StructureNotifyMask | KeyPressMask ); /* Aplicamos la imagen en la ventana principal usando la anchura y altura de la ventana creada, y tras ello la visualizamos (XMapWindow()) */ Imlib_apply_image(id, imagen, ventana); XMapWindow(display, ventana); XSync(display, False); /* bucle de eventos (para las modificaciones de tamaño de ventana) */ while( !done ) { /* recoger el siguiente evento */ XNextEvent(display, &evento); /* si el evento es de tipo ConfigureNotify (resize) -> */ if (evento.type == ConfigureNotify) { /* entonces dibujamos de nuevo la imagen con el nuevo tamaño: */ Imlib_apply_image(id,imagen,ventana); XSync(display,False); } /* si se ha pulsado una tecla, salir */ else if( evento.type == KeyPress ) done = 1; } }


IMAGENES EN IMLIB

Como ya se ha comentado, internamente Imlib trata las imágenes como bloques gráficos de 24 bits por pixel en formato RGB (el orden lógico en memoria para las componentes de color de los pixels es, pues, Rojo, Verde, Azul). Su almacenamiento interno es en un bloque de tipo unsigned char donde cada tripleta de valores constituye las componentes RGB de el pixel en cuestión. Su organización interna es como en la videomemoria: el byte 0 de este bloque coincide con el pixel superior izquierdo de la imagen (0,0), y estando almacenada la imagen en forma de scanlines, es decir, como lineas horizontales lineales de datos.

Imagen -> [byte r, byte g, byte b], [byte r, byte g, byte b], ...
                pixel(0,0)        ,       pixel(1,0)   ,  ...

Además de este bloque de datos existe otro paralelo y separado del mismo que coincide con el bloque anterior pixel a pixel, llamado Alpha Channel (para transparencias y futuras ampliaciones de la librería). Otro dato asociado a las imágenes internamente es un valor RGB que es considerado como transparente, es decir, que a la hora de dibujar el pixmap se considera como hueco de la imagen y no sobreescriría el fondo donde se renderiza la misma. Además la imagen posee una serie de pixels en los bordes que por defecto se ponen a 0. Los bordes son pixels que son tratados de forma diferente al escalar la imagen cuando se renderiza en un drawable.

Finalmente, las imagenes en Imlib incluyen también look-up-tables que se utilizarán para generar las características de contraste, brillo y Gamma de la imagen. La estructura de datos para las imágenes en Imlib es, como veremos en la próxima entrega, ImlibImage. El acceso a los datos gráficos RGB de la imagen está determinado pues por 2 punteros que son campos de dicha estructura:


 ImlibImage *imagen;
 imagen->rgb_data;      -> Puntero a la imagen gráfica.
 imagen->alpha_data;  -> Puntero al canal Alpha (normalmente NULL).

Esto quiere decir que es posible también por nuestra parte acceder a los contenidos gráficos de la imagen (ver código del PutPixel para 24bpp en anteriores capítulos, utilizándolo para acceder a Imagen->rgb_data[pixel];). Tras realizar cualquier cambio en la imagen se deberá llamar a Imlib_changed_image() para indicarle que la imagen ha sido modificada y que se debe realizar un cacheo de la nueva imagen.


TRANSPARENCIA (SHAPE COLOR)

En Imlib también disponemos del uso de transparencia para las imágenes. Las transparencias son zonas de un color determinado que se desea que a la hora de ser trazada la imagen no sobre escriben el fondo con dicho color, es decir, son zonas huecas en la imagen a través de las cuales se debe ver el fondo sobre el que se dibuja (como por ejemplo el escudo de la Universidad de Valencia en la figura 3). Habitualmente este color de transparencia suele ser el color 0 (negro, (0,0,0)) pero con Imlib disponemos de la posibilidad de indicar cual es el valor RGB de la máscara de transparencia de una determinada imagen, especificando el shape color, que no es ni más ni menos que los valores R, G y B de los pixels que deben ser considerados como transparentes. Algunos formatos gráficos (GIF, PNG, TIFF...) usan determinados colores para las transparencias, colores que Imlib será capaz de leer y de utilizar sin que sea necesaria intervención por nuestra parte (para informarle de cuales son estos colores). Esto nos permite simplemente dedicarnos a obtener buenos gráficos con nuestro programa de dibujo habitual para posteriormente cargarlos con toda su información (incluída transparencia) en nuestros programas. Esta transparencia puede hacer que Imlib genere un pixmap máscara para el renderizado.

Si no deseamos disponer de ningún color que represente transparencia, simplemente habremos de poner las 3 componentes de Shape Color a (-1,-1,-1) (no transparencia). Para ello utilizaremos las funciones de lectura y especificación del ShapeColor:

Para obtener las componentes RGB del color de transparencia se procedería como sigue:


ImlibData *id;
ImlibImage *imagen;
ImlibColor color;
int r,g,b;

Imlib_get_image_shape(id, imagen, &color);
r=color.r;
g=color.g;
b=color.b;

Si deseamos cambiar ese color a cualquier otro (o a no transparencia) puede hacerse mediante:


color.r=-1;
color.g=-1;
color.b=-1;
Imlib_set_image_shape(id, imagen, &color);


BRILLO, GAMMA Y CONTRASTE

Las tablas internas de mapas de colores de Imlib (3 tablas de 256 elementos de tipo unsigned char) se calculan para posteriormente hacer ajustes de brillo, gamma y contraste de la imagen completa o de cada canal RGB por separado. Estos valores pueden obtenerse de la siguiente forma (para todos los canales simultáneamente):


ImlibData *id;
ImlibImage *imagen;
ImlibColorModifier mod;
double gamma, brightness, contrast;

Imlib_get_image_modifier(id, imagen ,&mod);

/* dividimos por 256 para obtener valores entre 0.0 y 1.0 */
gamma = (double) mod.gamma / 256;
brightness = (double) mod.brightness / 256;
contrast = (double) mod.contrast / 256;

Para modificar los valores de brillo, contraste y gamma se dispone de la función opuesta:


/* se escriben en mod los valores de 0 (0.0) a 256 (1.0) */
Imlib_set_image_modifier(id, imagen, &mod);

Como puede verse, operaciones como incremento o disminución de brillo o cambio del gamma (para monitores oscuros o claros) son en Imlib operaciones básicas y sencillas accediendo a los 3 campos de la estructura ImlibColorModified, que van desde 0 (valor de 0) hasta 256 (valor de 1.0). Tras eso las tablas son recalculadas para permitir otro posterior reajuste de las características de la imagen, debiendo re-renderizar de nuevo el pixmap para que los cambios surtan efecto al usuario.

Si se desea trabajar única y directamente sobre uno de los canales R, G o B se dispone de las siguientes funciones (reciben como parámetro un array de 256 unsigned char donde Imlib colocará los valores para las tablas, o desde donde los tomará):


Imlib_set_image_red_curve(),
Imlib_set_image_green_curve(),
Imlib_set_image_blue_curve(),
Imlib_get_image_red_curve(),
Imlib_get_image_green_curve(),
Imlib_get_image_blue_curve(),


EN RESUMEN

En la próxima entrega se dará una referencia de todas las funciones y declaraciones de estructuras de Imlib, dotando al lector ya de toda la información necesaria para programar aplicaciones X Window que requieran de gráficos en pantalla, pudiendo estos ser creados en cualquiera de los programas de dibujo existentes. Mediante lo visto hasta ahora pueden desarrollarse multitud de aplicaciones para X que contribuirán aún más al desarrollo y popularización de este entorno de ventanas bajo los Sistemas Operativos de Open Source.

Santiago Romero


Volver a la tabla de contenidos.