INTRODUCCION A LA PROGRAMACION EN X WINDOW

Artículo 13: PROGRAMACION CON IMLIB (II).

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



En esta entrega veremos todas las estructuras y funciones Imlib necesarias para dotar a nuestros programas del colorido y funcionalidad de cualquier programa X existente, proporcionándonos acceso a los formatos gráficos más utilizados en el desarrollo de aplicaciones.


Tras la pequeña introducción de la semana pasada al sistema interno de Imlib y a las funciones básicas de manejo de imágenes nos introduciremos hoy en la descripción completa del juego de funciones y estructuras implicadas en el manejo de imágenes por parte del programador que usa Imlib en conjunción con Xlib.


ESTRUCTURAS BÁSICAS EN IMLIB

En el anterior capítulo de esta serie conocimos ya las estructuras ImlibImage e ImlibData y algunos de sus campos internos, aunque no todos. Sus declaraciones son como siguen (no están completas pero muestran los campos más interesantes):


typedef struct _ImlibImage
{
   int rgb_width,rgb_height;
   unsigned char *rgb_data;
   unsigned char *alpha_data;
   char *filename;
/* the below information is private */
    int width, height;
    ImlibColor shape_color;
    ImlibBorder border;
    Pixmap pixmap;
    Pixmap shape_mask;
    char cache;
    ImlibColorModifier mod, rmod, gmod, bmod;
    unsigned char rmap[256], gmap[256], bmap[256];      
} ImlibImage;

ImlibImage es la estructura que contiene el puntero a la imagen en sí misma. Los campos rgb_width y rgb_height indican la anchura y altura en pixels del bitmap apuntado por rgb_data, puntero a unsigned char que referencia un bloque gráfico en formato 24bpp formado como una sucesión de scanlines horizontales con la información gráfica (RGB, 8 bits para cada componente de color). Opcionalmente disponemos del campo alpha_data que actualmente no es usado por la librería y debe mantenerse como NULL (hasta alguna futura versión de la librería donde sea totalmente implementado su uso). Todos estos campos pueden modificarse siempre y cuando luego se llame a la función Imlib_apply_modifiers_to_rgb() ya que no hay que olvidar que las imágenes son cacheadas por Imlib, de forma que para evitar errores en una posible obtención de imagen desde la caché Imlib siempre tiene que estar correctamente informada de cuándo cambian estos valores. Como indica el comentario en el código, la información bajo el mismo es privada a la estructura y no debe ser modificada (solo mediante el uso de las funciones Imlib adecuadas).


typedef struct _ImlibData
{
   struct _xdata
   {
      Display *disp;
      int screen;
      Window root;
      Visual *visual;
      int depth;
     Colormap root_cmap; 
   } x;
  /*  otros campos, como cache, num_colors, etc. */
} ImlibData;
Esta estructura proporciona a Imlib información sobre el display actual sobre el que actúa, la ventana raíz, la profundidad de color del entorno o los datos del Visual del mismo. Esta estructura sólo es visible por la versión Xlib de Imlib, y no por la versión gdk (diferente tratamiento e implementación).

A continuación veamos como Imlib reconoce los colores:


typedef struct _ImlibColor
{
   int r,g,b;
} ImlibColor;

Los elementos r, g y b de ImlibColor son enteros en el rango 0-255, con la excepción del valor -1 para indicar no color, y por tanto poder especificar que no se desea transparencia (shape-color) cuando estemos indicándole a Imlib algo sobre esta propiedad. Otras características relacionadas con el color son (como se vio el mes pasado) el gamma, brillo, y contraste de la imagen, en formato 0-255, con lo cual para pasar un valor normalizado (de 0.0 a 1.0) al formato que usa Imlib simplemente debe multiplicarse el valor por 256 y hacerle un typecast a int antes de ponerlo en dichas estructuras:


typedef struct _ImlibColorModifier
{
   int gamma;
   int brightness;
   int contrast;
} ImlibColorModifier;

Otros tipos de datos definidos en /usr/include/Imlib_types.h son:


typedef struct _ImlibBorder
{
   int left,right;
   int top,bottom;
} ImlibBorder;

Esta estructura define los valores de pixels del borde para cada lado (por defecto todos 0).

Como veremos en las funciones de grabación de imágenes en disco, la siguiente estructura también es utilizada (por la función Imlib_save_image()):


typedef struct _ImlibSaveInfo
{
   int quality;
   int scaling;
   int xjustification;
   int yjustification;
   int page_size;
   char color;
} ImlibSaveInfo;
Todas estas estructuras y las funciones que veremos a continuación se declaran mediante la inclusión del fichero Imlib.h en nuestro programa. Así pues, un #include <Imlib.h> debe estar presente en la cabecera de nuestro programa principal.


BIBLIOTECA DE FUNCIONES DE IMLIB

ImlibData *Imlib_init( Display *disp);
Esta función inicializa Imlib, y debe ser llamada en el programa antes de llamar a cualquier otra función Imlib y despues de hacer inicializado el display con XOpenDisplay(). Llamándola una vez por display es posible utilizar Imlib en múltiples displays.

La inicialización de Imlib implica que ésta leerá las propiedades de nuestro desktop, las extensiones disponibles, tomará el visual actual, la profundidad de color del mismo, aplicará un colormap por defecto, inicializar el caché interno y leerá del fichero imrc los valores por defecto que el usuario quiere para el uso de la librería (nº de colores para 8 bpp, cacheo on/off, etc.). Como resultado final se nos devolverá una estructura de tipo ImlibData que ha de ser pasada a todas las funciones gráficas de Imlib.

ImlibData *Imlib_init_with_params(Display *disp, ImlibInitParams *p);
Esta función también inicializa imlib, pero permitiendo especificar ciertos parámetros y pudiendo así contradecir valores elegidos por el usuario en su fichero imrc.


/* para indicar que flags queremos cambiar */
#define PARAMS_VISUALID 1<<0
#define PARAMS_PALETTEFILE 1<<1
#define PARAMS_SHAREDMEM 1<<2
#define PARAMS_SHAREDPIXMAPS 1<<3
#define PARAMS_PALETTEOVERRIDE 1<<4
#define PARAMS_REMAP 1<<5
#define PARAMS_FASTRENDER 1<<6
#define PARAMS_HIQUALITY 1<<7
#define PARAMS_DITHER 1<<8
#define PARAMS_IMAGECACHESIZE 1<<9
#define PARAMS_PIXMAPCACHESIZE 1<<10

typedef struct _ImlibInitParams
{
   int  flags;
   int  visualid;              /*  id del visual a usar por Imlib */
   char palettefile;         /*  fichero de paleta */
   char sharedmem;     /* 0/1 si se desea o no MitShm */
   char sharedpixmaps;
   char paletteoverride;
   char remap;            /* 0=mapeado de paleta rápido, 1=bueno*/
   char fastrender;
   char hiquality;
   char dither;
   int  imagecachesize;
   int  pixmapcachesize;
} ImlibInitParams;

Como siempre en X, usaremos el operador | para indicar en flags todos los datos que deseemos modificar al tiempo que introducimos en los campos de la estructura los valores a que deseamos hacerlo.

int Imlib_get_render_type(ImlibData *id);
Devuelve el modo actual de rendering de Imlib, entre RT_PLAIN_PALETTE, RT_PLAIN_PALETTE_FAST, RT_DITHER_PALETTE, RT_DITHER_PALETTE_FAST, RT_PLAIN_TRUECOL, RT_DITHER_TRUECOL. Los primeros 4 son para modos de 8 bits mientras que los restantes para los modos de más alta profundidad de color (RT_PLAIN_TRUECOL es uno de los más rápidos, y RT_DITHER_TRUECOL hace dithering en modos de 15 y 16 bpp para obtener aún más calidad de imagen pero con una ligera pérdida de velocidad).

void Imlib_set_render_type(ImlibData *id, int rend_type);
Permite especificar (mediante las constantes vistas en la anterior función) el modo de rendering.

int Imlib_load_colors(ImlibData *id, char *file);
Permite cargar una paleta de colores desde el fichero file. Es recomendable llamar a Imlib_free_colors() antes de llamar a esta función, posteriormente cargar la paleta y por último re-renderizar la imagen.

ImlibImage *Imlib_load_image(ImlibData *id, char *file);
Una de las funciones pilares de Imlib. Esta función carga el fichero gráfico file y devuelve un puntero a una estructura de tipo imagen (ImlibImage). Para realizar la carga simplemente buscará en la cabecera del archivo gráfico la firma del formato del archivo o consultará la extensión del mismo. Si no consiguiese realizar su identificación (cosa poco probable pues soporta gran variedad de formatos), esta función llamaría a alguna de las utilidades normalmente incluidas en X como último recurso para convertir y cargar la misma en tiempo real (usando convert de Imagemagick o NETPBM para ello). Ante la imposibilidad final de cargar la imagen, la función devolvería NULL a la función llamadora. Imlib puede cargar ficheros de tipo JPEG, GIF, PPM, PGM, XPM, PNG, TIFF y EIM. Además, de los GIF, PNG, XPM, TIFF y EIM, puede extraer información relativa a las transparencias en la misma.

int Imlib_best_color_match(ImlibData *id, int *r, int *g, int *b);
Esta función nos puede convertir una componente RGB (0-255) en el color más aproximado en el display (muy útil para convertir internamente a pixels con el mismo formato que el display actual o para efectos de luces). Esto puede utilizarse para buscar el color más parecido a uno dado en la paleta de colores actual para modos 8bpp, por ejemplo (y crearse tablas lookup para luces, flares, etc).

int Imlib_render(ImlibData *id, ImlibImage *image, int width, int height);
Esta función renderizará/volcará la imagen en un pixmap de anchura y altura width y height, así como un pixmaps de máscara si la imagen contiene transparencias. La función devuelve 1 en caso de éxito o 0 si ha habido algún error.

Pixmap Imlib_copy_image(ImlibData *id, ImlibImage *image);
Pixmap Imlib_copy_mask(ImlibData *id, ImlibImage *image);
Pixmap Imlib_move_image(ImlibData *id, ImlibImage *image);
Pixmap Imlib_move_mask(ImlibData *id, ImlibImage *image);

Estas 4 funciones devuelven una copia del pixmap de la imagen (por ejemplo tras llamar a Imlib_render()) o de la máscara del mismo. En caso de error (o si no existe transparencia) se devolverá 0 (NULL). La distinción entre Imlib_copy e Imlib_move radica en que con copy posteriormente podremos modificar dicho pixmap mientras que con move no (ya que lo movemos literalmente y lo eliminamos de la estructura ImlibImage, poniendo a NULL su campo).

void Imlib_destroy_image(ImlibData *id, ImlibImage *image);
void Imlib_kill_image(ImlibData *id, ImlibImage *image);

La primera función destruye la estructura de tipo ImlibImage (pasando a la caché hasta que su antigüedad haga que Imlib la elimine de la misma). La segunda la elimina directamente sin hacerla pasar por la caché.

void Imlib_free_colors(ImlibData *id);
Libera el colormap que esté usando Imlib actualmente.

void Imlib_free_pixmap(ImlibData *id, Pixmap pixmap);
Libera el pixmap indicado (entrando éste en la caché). Debe usarse con todos los pixmaps generados por Imlib. Con cada liberación se libera también el pixmap de la máscara (que no hay que liberar manualmente, sino que es borrado por Imlib cuando salga de la caché).

void Imlib_get_image_border(ImlibData *id, ImlibImage *image, ImlibBorder *border);
void Imlib_set_image_border(ImlibData *id, ImlibImage *image, ImlibBorder *border);

Permiten leer y modificar la información sobre los pixels de los bordes de una imagen.

void Imlib_get_image_shape(ImlibData *id, ImlibImage *image, ImlibColor *color);
void Imlib_set_image_shape(ImlibData *id, ImlibImage *image, ImlibColor *color);

Estas 2 funciones permiten operar sobre el color de transparencia (ShapeColor) de una imagen (como se vio en la introducción a Imlib).

int Imlib_save_image(ImlibData *id, ImlibImage *im, char *file, ImlibSaveInfo *info);
Graba una imagen en cualquier tipo de fichero (según la extensión en el nombre del fichero). Si el puntero a la información ImlibSaveInfo es NULL se grabará con los parámetros por defecto, mientras que si los especificamos nosotros mismos será posible elegir el porcentaje de compresión en los JPEG, valores de transparencias, el formato de folio en grabación Postscript (ej: page_size entre PAGE_SIZE_EXECUTIVE, PAGE_SIZE_LETTER, PAGE_SIZE_LEGAL, PAGE_SIZE_A4, PAGE_SIZE_A3, PAGE_SIZE_A5, o PAGE_SIZE_FOLIO), etc, etc.

int Imlib_save_image_to_ppm(ImlibData *id, ImlibImage *image, char *file);
Grabación de ficheros PPM (devuelve 1 en caso de éxito y 0 ante un error).

int Imlib_load_file_to_pixmap(ImlibData *id, char *filename, Pixmap *pmap, Pixmap *mask);
Carga el fichero de imagen y genera el pixmap de imagen y de máscara en los pixmaps especificados, liberando la imagen tras la carga. Devuelve 1 en caso de éxito y 0 en caso de error.

void Imlib_get_image_modifier(ImlibData *id, ImlibImage *im, ImlibColorModifier *mod);
void Imlib_set_image_modifier(ImlibData *id, ImlibImage *im, ImlibColorModifier *mod);
void Imlib_get_image_red_modifier(ImlibData *id, ImlibImage *im, ImlibColorModifier *mod);
void Imlib_set_image_red_modifier(ImlibData *id, ImlibImage *im, ImlibColorModifier *mod);

(y lo mismo para green y blue):

Permite modificar (como se vio en la introducción a Imlib) el brillo, contraste y gamma de todos los canales o de un único canal de color (rojo, verde o azul). Para que los cambios surtan efecto hay que volver a renderizar la imagen.

void Imlib_apply_modifiers_to_rgb(ImlibData *id, ImlibImage *im);
Esta llamada le indica a Imlib que tome los mapas RGB modificados con todas las funciones anteriores y que modifique la imagen gráfica RGB original para que los cambios se hagan efectivos, notificándoselo además a la caché para posteriores referencias (hace falta también renderizar de nuevo la imagen).

void Imlib_changed_image(ImlibData *id, ImlibImage *im);
Indica a Imlib que hemos modificado los pixels de la imagen (accediendo a imagen->rgb_data) manualmente, y que queremos informarle de ello antes de renderizar el pixmap (es importante a efectos de caché indicarle a Imlib que los contenidos de la imagen han cambiado).

void Imlib_apply_image(ImlibData *id, ImlibImage *im, Window p);
Renderiza una imagen con la anchura y altura de la ventana que se le especifica. Utiliza automáticamente la máscara del pixmap apropiado si existe transparencia en la imagen, y libera ambos pixmaps internos a la estructura tras dibujarlos en la ventana que se le indica.

void Imlib_paste_image(ImlibData *id, ImlibImage *im, Window p, int x, int y, int w, int h);
Pega el pixmap de la imagen en las coordenadas (x,y) del Drawable o Window especificado, y utiliza también automáticamente la máscara del pixmap para el dibujado (liberando ambos al igual que la función anterior).

void Imlib_paste_image_border(ImlibData *id, ImlibImage *im, Window p, int x, int y, int w, int h);
Realiza lo mismo que la función anterior pero imprimiendo sólo los bordes de la imagen.

void Imlib_flip_image_horizontal(ImlibData *id, ImlibImage *im);
void Imlib_flip_image_vertical(ImlibData *id, ImlibImage *im);

Realiza un espejo (mirror) de la imagen, horizontal o verticalmente. Para ello modifica los valores RGB de la imagen y tendremos que re-renderizar la misma para ver el efecto.


transformaciones


void Imlib_rotate_image(ImlibData *id, ImlibImage *im, int d);
Rota la imagen usando el parámetro d como indicador del ángulo y grados de la rotación (aunque en la versión 1.9.2 es ignorado). El significado es:

 -1 -> rotación 90º en el orden de las agujas del reloj.
 +1 -> rotación 90º en el orden contrario de las agujas del reloj.
 -2 -> rotación 180º en el orden de las agujas del reloj.
 +2 -> rotación 180º en el orden contrario de las agujas del reloj.
 (etc.)

ImlibImage *Imlib_create_image_from_data(ImlibData *id, unsigned char *data, unsigned char *alpha, int w, int h);
Toma el puntero data a una imagen de 24bpp en formato RGB y crea una estructura tipo ImlibImage de tamaño w*h para su posterior uso con Imlib. El contenido del puntero pasado es copiado dentro de la estructura de imagen, por lo que podemos liberar la memoria apuntada por el mismo tras llamar a esta función. Devuelve NULL en caso de error o el puntero a la estructura en caso de éxito.

ImlibImage *Imlib_clone_image(ImlibData *id, ImlibImage *im);
ImlibImage *Imlib_clone_scaled_image(ImlibData *id, ImlibImage *im, int w, int h);

La primera función crea un duplicado de la imagen que se le pasa, devolviendo NULL en caso de error o un puntero a la nueva imagen. La segunda realiza la misma función pero escalándo la imagen automáticamente a un tamaño deseado para la nueva imagen.

Visual *Imlib_get_visual(ImlibData *id);
Colormap Imlib_get_colormap(ImlibData *id);

Devuelven el Visual y Colormap que está usando Imlib.

ImlibImage *Imlib_create_image_from_xpm_data(ImlibData *id, char **data);
Crea una imagen imlib a partir de un XPM cargado en el programa (como #include "fichero.xpm").

int Imlib_data_to_pixmap(ImlibData *id, char **data, Pixmap *pmap, Pixmap *mask);
Esta función crea un pixmap (y una máscara si esta es necesariam o NULL si no lo es) a partir de los datos de un fichero XPM incluido en el programa. Como resultado se crea el Pixmap pmap y se retorna 1 o se devuelve 0 en caso de error.

ImlibImage *Imlib_create_image_from_drawable(ImlibData *id, Drawable win, Pixmap mask, int x, int y, int w, int h);
Permite crear una imagen Imlib a partir de un drawable de Xwindow (ya sea una ventana o pixmap). La imagen capturada es la encuadrada entre (x,y) y (x+w,y+h).

void Imlib_crop_image(ImlibData *id, ImlibImage *im, int x, int y, int w, int h);
ImlibImage *Imlib_crop_and_clone_image(ImlibData *id, ImlibImage *im, int x, int y, int w, int h);

Estas funciones extraen una sección entre (x,y) y (x+w,y+h) de la imagen original. La segunda, además, hace una copia de dicha sección y la devuelve (o NULL en caso de error).

A título de muestra de todas estas funciones, en el listado 1 tenemos un programa extraído de la documentación de la librería que carga una imagen que se le pasa como parámetro utilizando pixmaps (figura 1), para así mostrar cómo se trabaja con Pixmaps (que es la principal utilidad de Imlib para el programador). Siguiendo los pasos del ejemplo podremos cargar, renderizar y volcar todos los gráficos de nuestros programas.



LISTADO 1

USO DE PIXMAPS EN IMLIB /*---------------------------------------------------------------- Ejemplo1.c -> Implementación de un visualizador de imágenes usando funciones Imlib que trabajan con pixmaps (ejemplo del uso de pixmaps). 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 ------------------------------------------------------------------*/ #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; Pixmap pixmap, mask; int anchura, altura; XEvent evento; char done = 0; if (argc<=1) { printf("Usage:\n %s image_file\n",argv[0]); exit(1); } display = XOpenDisplay(NULL); id = Imlib_init(display); imagen=Imlib_load_image(id,argv[1]); anchura = imagen->rgb_width; altura = imagen->rgb_height; ventana = XCreateWindow(display, DefaultRootWindow(display), 0, 0, anchura, altura, 0, id->x.depth, InputOutput, id->x.visual, 0, &attr); XSelectInput(display,ventana,StructureNotifyMask | KeyPressMask ); /* renderizar la imagen en un pixmap de anchura*altura */ Imlib_render(id, imagen, anchura, altura ); /* extraer el pixmap y la mascara de la Imagen */ pixmap = Imlib_move_image( id,imagen ); mask = Imlib_move_mask( id, imagen ); XSetWindowBackgroundPixmap(display, ventana, pixmap ); /* si hay máscara, combinarla con el pixmap con XLib */ if (mask) XShapeCombineMask(display, ventana, ShapeBounding, 0, 0, mask, ShapeSet); XMapWindow(display, ventana); XSync(display, False); while( !done ) { XNextEvent(display, &evento); if (evento.type==ConfigureNotify) { anchura = evento.xconfigure.width; altura = evento.xconfigure.height; /* Renderizar la imagen con el nuevo tamaño */ Imlib_render(id, imagen, anchura, altura); Imlib_free_pixmap(id, pixmap); /* de nuevo, obtener el pixmap y la mascara */ pixmap = Imlib_move_image(id, imagen); mask = Imlib_move_mask(id, imagen); XSetWindowBackgroundPixmap(display, ventana, pixmap); if (mask) XShapeCombineMask(display, ventana, ShapeBounding, 0, 0, mask, ShapeSet); XClearWindow(display,ventana); XSync(display,False); } else if ( evento.type == KeyPress ) done = 1; } }


EN RESUMEN

Con todas estas funciones ya estamos en disposición de poder crear cualquier programa que use gráficos bajo X Window, utilizando para ello la potencia de Xlib, la velocidad de MitSharedMemory o DGA y la sencillez y funcionalidad de Imlib. Esta última, además, se integra en Gtk, con lo que su aprovechamiento será máximo para desarrollar programas que utilicen este avanzado conjunto de widgets.

Santiago Romero


Volver a la tabla de contenidos.