INTRODUCCION A LA PROGRAMACION EN X WINDOW

Artículo 10: Mit Shared Memory Extension..

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



Gracias a la extensión de X Window - Mit Shared Memory (XSHM), es posible crear programas gráficos de alta velocidad como juegos y aplicaciones gráficas que así lo requieran, haciendo uso de memoria compartida entre el sistema y entre el servidor X, que posteriormente se volcará directamente sobre la videomemoria.

Como ya se comentó en el primer capítulo de nuestra serie, X Window dispone de una serie de extensiones que lo han ido adaptando siempre a las últimas necesidades. Para suplir la necesidad de disposición de gráficos de alta velocidad aparecieron las extensiones Mit Shared Memory (Memoria compartida) y DGA (Direct Graphics Access, Acceso directo a memoria gráfica). Hoy nos centraremos en la primera de ellas mediante la descripción de las diferentes funciones que posee.

  CUADRO 1: Algunas extensiones de XFree86.

    number of extensions:    19
    BIG-REQUESTS
    DOUBLE-BUFFER
    DPMS
    LBX
    MIT-SCREEN-SAVER
    MIT-SHM
    MIT-SUNDRY-NONSTANDARD
    RECORD
    SECURITY
    SHAPE
    SYNC
    XC-APPGROUP
    XC-MISC
    XFree86-DGA
    XFree86-Misc
    XFree86-VidModeExtension
    XInputExtension
    XKEYBOARD
    XTEST

 FIN CUADRO

La finalidad del XSHM (abreviatura de Mit Shared Extensions a partir de este momento), es algo similar a lo que los programadores que usan Linear Frame Buffer y pantallas virtuales están acostumbrados a hacer y que proporciona gran velocidad al trazado de gráficos: hablamos de poder dibujar en buffers de memoria para copiar todo el buffer de una sola pasada directamente a la videomemoria, y de forma transparente al programador.


INICIALIZACIÓN DE XSHM

Antes de poder utilizar cualquier función de XSHM es necesario consultar al servidor X para ver si dispone de dicha extensión disponible en el sistema. Si recordamos de nuestra primera entrega, el programa xdpyinfo (ver cuadro 1) podía proveernos de dicha información, aunque una manera mucho más sencilla de hacerlo utilizando la función XShmQueryExtension(), que devuelve 1 en caso de estar disponible la extensión requerida:


Display *display;
int ignorar;

if( XQueryExtension(display, "MIT-SHM", &ignorar, &ignorar, &ignorar) ) 
{
   /* todo bien, extensión disponible */
}
else
{
  /* error, extensión no disponible */
}

Para utilizar esta función y cualquier otra de XSHM será necesario incluir el fichero de cabecera que contiene las declaraciones de las funciones y estructuras necesarias:


#include <X11/extensions/XShm.h>
#include <sys/ipc.h>
#include <sys/shm.h>

Además es necesario incluir una nueva librería a la hora de compilar:


gcc -o programa programa.c -lX11 -L/usr/X11R6/lib -lXext

Si la extensión está disponible, podemos averiguar su versión con la función XshmQueryVersion():


Bool XShmQueryVersion( Display *display, int *major, int *minor, Bool *shared );

Esta función devuelve True en caso de éxito y False en caso de error, y nos informa sobre la versión de la extensión de XSHM (utilizando las variables major y minor en formato major.minor). Además nos indica si están disponibles los pixmaps compartidos (shared pixmaps) en la extensión (utilizando la variable shared que es de tipo Bool). Así pues el código de detección de la extensión y su versión quedaría de la siguiente forma:


Display *display;
int ignorar, major, minor;
Bool pixmaps_disponibles;

/* Check for the XShm extension */
if( XQueryExtension(display, "MIT-SHM", &ignorar, &ignorar, &ignorar) ) 
{
  if(XShmQueryVersion(display, &major, &minor, &pixmaps_disponibles) == True) 
  {
    /* version = major.minor */
  }
  else Error();
}

El trabajo que se realiza cuando se usa XSHM es similar al trabajo que se realizaba con las estructuras de tipo XImage. En lugar de crear una XImage con las funciones vistas hasta ahora, lo que haremos será crear una estructura de ese tipo utilizando las funciones Xshm:


XImage *XShmCreateImage( Display *display,    Visual *visual,   unsigned int depth, int format, char *data, XShmSegmentInfo *shminfo, unsigned int width, unsigned int height );

Esta función crea una XImage (equivalente a la que crea XCreateImage() pero utilizando XShm), en el display especificado, con el visual especificado, de una profundidad de color depth (tipicamente la misma que en display donde la copiaremos posteriormente), y con una anchura y altura determinadas por width y heigth. El formato que usaremos será el de Zpixmap (ver articulo anterior para la descripción de XcreateImage()), y el puntero a los datos char *data puede ser NULL si no nos importa el contenido inicial de dicha imagen. El único parámetro extra que requiere esta función respecto a XCreateImage() es shminfo, que es un puntero a una estructura del tipo XShmSegmentInfo, y que contiene información que necesitaremos en otras funciones Xshm.


typedef struct {
    ShmSeg shmseg;	/* resource id */
    int shmid;		/* kernel id */
    char *shmaddr;	/* address in client */
    Bool readOnly;	/* how the server should attach it */
} XShmSegmentInfo;

Veamos un ejemplo de creación de imagen:


Display *display;
Screen *pantalla;
XImage *imagen;
XShmSegmentInfo shminfo;
int anchura, altura;

imagen = XShmCreateImage(display, DefaultVisualOfScreen(pantalla),
         DefaultDepthOfScreen(pantalla), ZPixmap, NULL, &shminfo,
         anchura, altura );
if(imagen == NULL) /* error */

Tras crear la estructura XImage es necesario realizar una petición de memoria compartida utilizando la información shminfo obtenida en la creación de la estructura, utilizando para ello la función shmget():


bpp = imagen->bytes_per_line;
altura=imagen->height;
shminfo.shmid = shmget(IPC_PRIVATE, bpp*altura, IPC_CREAT | 0777);
if( shminfo.shmid < 0) /* error */

Con esta llamada hemos preparado la estructura para el uso de memoria compartida del tamaño suficiente para albergar una imagen completa de Anchura*Altura. El cálculo realizado es simplemente la multiplicación del número de líneas horizontales de la imagen por el tamaño que ocupa cada una de estas líneas o scanlines en bytes, lo cual nos da una determinada cantidad de memoria que obtenemos para nuestra XImage con shmget.


 Tras esto debemos asignar (attach) nuestra XImage al servidor X, utilizando shmat():


shminfo.shmaddr = imagen->data = (char *) shmat( shminfo.shmid, 0, 0);
if(shminfo.shmaddr == (char *) -1) /* error */;

Finalmente, establecemos la memoria compartida de lectura y escritura (para hacerla de lectura ponemos el campo readOnly de shminfo como False) y lo asignamos al display que se esté utilizando:


shminfo.readOnly = False;
XShmAttach(display, &shminfo);

El prototipo de esta última función es el siguiente:


Status XShmAttach( Display*display, XshmSegmentInfo *shminfo );

Con esto obtenemos el buffer de memoria compartida ligado al servidor X11 y más concretamente al display actual.

Cabe decir también que si los Shared Pixmaps están disponibles (función XShmQueryVersion()), es posible también crear pixmaps en lugar de Ximages y ligarlos de igual forma a memoria compartida:


Pixmap XShmCreatePixmap(  Display *display, Drawable drawable, char *data, XShmSegmentInfo * shminfo, unsigned int width, unsigned int height, unsigned int depth );

Los parámetros son los mismos que en XCreatePixmap() excepto la nueva estructura de información shminfo.


UTILIZANDO XSHM

Una vez tenemos la superficie XImage creada, ya podemos utilizarla para el trazado de gráficos. Una primera manera de hacerlo es utilizar las funciones asociadas a XImage, como:


unsigned long XGetPixel( XImage *image, int x, int y );
Status XPutPixel( XImage *image, int x, int y, unsigned long pixel );
Status XAddPixel( XImage *image, unsigned long value );

Las 2 primeras funciones toman y escriben un pixel en la XImage especificada con los valores pasados como parámetros, mientras que la tercera añade un valor constante (value) a todos los pixels de la imagen (ideal para efectos de luz y similares).

Una segunda posibilidad que puede ser realmente rápida si se conoce la manera de trabajar en diferentes profundidades de color es acceder directamente al puntero char *data de la XImage. Este puntero apunta exclusivamente al buffer de memoria que conforma la imagen, de forma que si sabemos interpretar la profundidad de color de pantalla y datos como la anchura y altura del pixmap es posible trazar pixels y cualquier tipo de figura e imagen accediendo a este buffer de datos/pixels. En particular, y a título de ejemplo, si estuviesemos trabajando en un modo de 256 colores donde cada byte representa un pixel, si data fuese un buffer de 300x300 pixels, al acceder (lectura o escritura) a imagen->data[0] estaríamos accediendo al pixel (0,0) de la imagen (ver artículos de programación gráfica sobre modos de direccionamiento de videomemoria lineales). Como cada byte representa un pixel con valores relativos a la paleta, podemos trazar colores fácilmente escribiendo en dicho buffer los colores deseados.

Si se conoce el modo de funcionamiento de otros modos de vídeo (15bpp, 16bpp, 24bpp, etc.) es posible crearse una biblioteca de funciones de trazado de pixels, bitmaps, líneas y similares que trabajen sobre este buffer, obteniendo velocidades de trazado realmente altas.

Se haga como se haga, gracias a Xshm es posible crear cada fotograma de animación de nuestro programa/juego en un buffer XImage para posteriormente volcarlo a pantalla, evitando efectos de parpadeos, flickering y snow, y con velocidades comparables a la de juegos profesionales (por ejemplo, al compilar el emulador de máquina recreativa XMame (ver figura 1) con soporte para XShm se obtiene un gran incremento en el número de fotogramas por segundo o frames). Una porción del código de este gran emulador de máquina podemos verlo en el cuadro 2, donde pueden verse algunos de los pasos que siguen sus programadores para inicializar Xshm.


LISTADO 2: Codigo de inicialización de X - Mame.

/*--- Nota: código extraido de los fuentes de Xmame 
          © Hans de Goede y Juan Antonio Martinez    ---*/

#ifdef USE_MITSHM   
/* following routine traps missused MIT-SHM if not available */
int test_mit_shm(Display *display, XErrorEvent *error) 
{
   char msg[256];
   unsigned char ret=error->error_code;
   XGetErrorText(display,ret,msg,256);
   /* if MIT-SHM request failed, note and continue */
   if (ret == BadAccess ) { mit_shm_avail=0; return 0; }
   /* else unspected error code: notify and exit */
   fprintf(stderr_file,"Unspected X Error %d: %s\n",ret,msg );
   exit(1);
}

/*--- ... en una función posterior viene la detección: ---*/

   if (mit_shm_avail) /* look for available Mitshm extensions */
   {
      mit_shm_avail = 0;
      /* get XExtensions to be if mit shared memory is available */
      result = XQueryExtension(display,"MIT-SHM",&i,&j,&k);
      if ( ! result)
      fprintf(stderr_file,"X-Server Doesn't support MIT-SHM extension\n");
      else
      mit_shm_avail = 1;
  }

/*--- ... y posteriormente viene la inicialización: ---*/

  if ( mit_shm_avail ) 
  {    /* Create a MITSHM image. */
     XSetErrorHandler( test_mit_shm );
    image = XShmCreateImage (display, myvisual.visual, myvisual.depth,  ZPixmap,  NULL, &shm_info, 	xsize, 	ysize);

   if (!image) 
   {
        fprintf (stderr_file,"OSD ERROR: failed in XShmCreateImage().\n");
        mit_shm_avail = 0;
        goto mit_shm_failed;
   }
    shm_info.shmid = shmget ( IPC_PRIVATE,  image->bytes_per_line * image->height,  (IPC_CREAT | 0777) );
   if ( shm_info.shmid < 0) {
       fprintf (stderr_file,"OSD ERROR: failed to create MITSHM block.\n");
       x11_close_display(); /* this will clean up for us */
       return OSD_NOT_OK;
    }

    /* And allocate the bitmap buffer. */
   /* new pen color code force double buffering in every cases */
    image->data = shm_info.shmaddr = (char *) shmat ( shm_info.shmid,0 ,0);
    scaled_buffer_ptr = (unsigned char*)image->data;
    if (!scaled_buffer_ptr) 
    {
        fprintf (stderr_file,"OSD ERROR: failed to allocate MITSHM bitmap buffer.\n");
        x11_close_display(); /* this will clean up for us */
       return OSD_NOT_OK; 
    }

    shm_info.readOnly = FALSE;
    /* Attach the MITSHM block. this will cause an exception if */
    /* MIT-SHM is not available. so trap it and process         */

    fprintf(stderr_file,"MIT-SHM Extension Available. trying to use...");

    if(!XShmAttach (display, &shm_info)) 
    {
       fprintf (stderr_file,"OSD ERROR: failed to attach MITSHM block.\n");
       x11_close_display(); /* this will clean up for us */
       return OSD_NOT_OK; 
    }
    XSync(display,False); /* be sure to get request processed */
    sleep(2); /* enought time to notify error if any */


/*--- Por ultimo tenemos el cierre de MitSHM ---*/
void x11_close_display (void)
{
#ifdef USE_MITSHM
   if (mit_shm_avail)
  {
      if (mit_shm_attached)    XShmDetach (display, &shm_info);
      if (scaled_buffer_ptr)   shmdt  (scaled_buffer_ptr);
      if (shm_info.shmid >= 0) shmctl (shm_info.shmid, IPC_RMID, 0);
      scaled_buffer_ptr = NULL;
   }
#endif

 /*--- etc etc ... (resto de codigo de Xmame)---*/
}


VOLCANDO LA MIT SHARED MEMORY A UN DRAWABLE

El principal interés de disponer de los gráficos en los buffers de memoria compartidos es la posibilidad de volcarlos a cualquier drawable, ya sea un pixmap, una ventana, o similar. La función a utilizar es de nombre y estructura similar a XputImage() (ver figura 2), pero adaptada al uso de las Xshm:


Status XShmPutImage( Display *display, Drawable drawable, GC gc, XImage *image, int src_x, int src_y, int dst_x, int dst_y, unsigned int src_width, unsigned int src_height, Bool send_event );

La función vuelca el contenido del XImage image sobre el drawable y display especificado, desde las coordenadas (src_x,src_y) de la imagen origen hasta (src_x+src_width,src_y+src_height) (un rectángulo), copiándolo en el recuadro definido por (dst_x,dst_y) y (dst_x+src_width,dst_y+src_height); es decir, vuelca un recuadro gráfico desde image al drawable.

Gracias a esta función es posible trabajar en el buffer de memoria compartida y posteriormente volcar la imagen completa a la ventana o pixmap que se esté utilizando con una orden como la siguiente:


XShmPutImage(display, window, gc, imagen, 0, 0, 0, 0, imagen->width, imagen->height, False);

La única diferencia de XShmPutImage() con XPutImage() está en el parámetro final denominado send_event, de tipo booleano, y que en el ejemplo de volcado anterior hemos puesto a False. Hay que comprender que XShmPutImage() trabaja realizando el volcado en background (mientras el programa sigue su ejecución, esto es lo que hace los programas increiblemente rápidos), de forma que tal vez sea necesario que se nos informe de cuándo ha finalizado la copia (tal vez para no volver a realizar otra copia hasta que la anterior acabe). El parámetro send_event especifica si deseamos recibir un evento cuando XShmPutImage() finalice la copia (si lo ponemos a True). Si no necesitamos conocer la finalización de la copia (muchas veces puede no ser necesario) podemos especificar False.

En el caso de haber especificado True para recibir el evento, necesitaremos calcular el tipo de evento de finalización, ya que no es un evento constante, y que se calcula mediante XShmGetEventBase() y una nueva estructura (XShmCompletionEvent), las cuales nos determinarán el evento que informa de la finalización del volcado:


XShmCompletionEvent CompletionType;

CompletionType = XShmGetEventBase(display) + ShmCompletion;

La estructura XShmCompletionEvent está definida como sigue:


typedef struct {
    int	type;		    /* of event */
    unsigned long serial;   /* # of last request processed by server */
    Bool send_event;	    /* true if this came frome a SendEvent request */
    Display *display;	    /* Display the event was read from */
    Drawable drawable;	    /* drawable of request */
    int major_code;	    /* ShmReqCode */
    int minor_code;	    /* X_ShmPutImage */
    ShmSeg shmseg;	    /* the ShmSeg used in the request */
    unsigned long offset;   /* the offset into ShmSeg used in the request */
} XShmCompletionEvent;


COPIANDO A LA MIT SHARED MEMORY

Aparte de poder copiar la imagen contenida en XImage en el drawable, es posible realizar el proceso inverso, es decir, tomar una porción (rectangular) de imagen del drawable especificado y volcarlo en la posición deseada de la estructura Ximage:


Status XShmGetImage( Display *display, Drawable drawable, XImage *image, int x, int y, unsigned long plane_mask );

La máscara de plano indica qué plano deseamos copiar. Para copiarlos todos, simplemente habría que especificar todos los bits a 1, es decir, el valor 0xFFFFFFFF, con lo que la copia sería total.


 Ejemplo: XShmGetImage(display, window, imagen, 0, 0, 0xFFFFFFFF );


LIBERACIÓN DE MEMORIA

Antes de salir del programa deberemos liberar toda la memoria y estructuras utilizadas durante el programa, y las creadas con XShm no son una excepción. Para ello se debe liberar la ligadura de la memoria al servidor X con XshmDetach():


Status XShmDetach( Display *display,  XShmSegmentInfo * shminfo );

La línea de código que cumpliría dicha función sería la siguiente:


XShmDetach(display, &shminfo);

Tras desligar la memoria compartida del servidor X debemos destruir la estructura de tipo Ximage:


XDestroyImage(image);

Y por último liberar la memoria asignada mediante el uso de shmdt():


shmdt(shminfo.shmaddr);

Con esto hemos obtenido funcionalidades equivalentes a las de los programas profesionales en apenas un par de llamadas a funciones del Mit Shared Extension, obteniendo velocidad a cambio de sencillez de utilización.


ESTRUCTURA XIMAGE

Ya que el Xshm se apoya en la estructura XImage vamos a hacer una breve descripción de sus diferentes campos. La declaración de la estructura en los ficheros de cabecera de X11 es como sigue:

/*
 * Data structure for "image" data, used by image manipulation routines.
 */
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;
    XPointer obdata;		/* hook for the object routines to hang on */
    struct funcs {		/* image manipulation routines */
	struct _XImage *(*create_image)(
		struct _XDisplay* /* display */,
		Visual*		/* visual */,
		unsigned int	/* depth */,
		int		/* format */,
		int		/* offset */,
		char*		/* data */,
		unsigned int	/* width */,
		unsigned int	/* height */,
		int		/* bitmap_pad */,
		int		/* bytes_per_line */);
	int (*destroy_image)        (struct _XImage *);
	unsigned long (*get_pixel)  (struct _XImage *, int, int);
	int (*put_pixel)            (struct _XImage *, int, int, unsigned long);
	struct _XImage *(*sub_image)(struct _XImage *, int, int, unsigned int, unsigned int);
	int (*add_pixel)            (struct _XImage *, long);
	} f;
} XImage;

Los parámetros width y height especifican la anchura y altura de la imagen contenida, respectivamente. Una imagen generalmente estará compuesta de un número determinado de scanlines (filas horizontales de pixels), que conforma la altura de dicha imagen. La cantidad de pixels en cada scanline definen la anchura de la imagen.

El parámetro format especifica el formato del pixmap (ZPixmap en nuestro caso). Asociado a éste, el campo xoffset define cuantos pixels hay que ignorar al principio de cada scanline (donde empieza la imagen realmente). En el caso de los ZPixmap (el tipo que utilizamos nosotros) será 0 (no hay pixels a descartar).

Uno de los campos más importantes es char *data, que es el puntero a los datos gráficos que conforman la imagen, los pixels en sí mismos, organizados en scanlines horizontales. En este puntero hemos de poner la dirección de la imagen (si ha sido creada con XCreateImage()). En el caso de ser una estructura proveniente de XGetImage() , es esta misma función la encargada de pedir memoria para el buffer de datos.

Existen otros parámetros que proporcionan información sobre la Imagen, como depth, que indica el número de bits por pixel (profundidad de color) que define cada pixel (equivalente al campo bits_per_pixel para ZPixmaps), o bytes_per_line, que nos dice el nº de bytes que ocupa cada scanline (lo cual nos permite calcular el total de memoria necesaria para albergar la imagen).

Por último, red_mask, green_mask y blue_mask especifican las máscaras de bits que indican dentro de cada palabra (pixel) qué bits están asignados a las componentes rojos, verdes y azules de color. Gracias a esto es posible conocer la organización interna de los pixels y trazar pixels directamente en data.


Ejemplo de salida de xdpyinfo para masks:
 red, green, blue masks:    0xf800, 0x7e0, 0x1f

Para poder trabajar directamente con el buffer de la imagen es necesario utilizar estas 3 máscaras para componer los pixels de la manera correcta, así como tener almacenados los gráficos del programa en todos los formatos distintos. En principio se podría también disponer de una rutina de trazado de gráficos por cada modo de vídeo, pero en la práctica esto no se hace así, sino que se suele guardar el set de gráficos del programa en un sólo formato, o en 2 (por ejemplo, en modo con paleta de 256 colores y en 24bpp), y al entrar en el programa se detecta el tipo de display y se convierten los datos gráficos durante la carga a la profundidad de color adecuada. Para ello se debe disponer de rutinas del tipo Convert8bppTo15bpp(), ConvertXbpptoYbpp(), etc. Con estas rutinas se convierte "on the fly" los gráficos en las rutinas de carga e inicialización de los programas, y para ello es necesario el uso de las 3 máscaras, ya que en principio podemos encontrarnos gran variedad de modos de vídeo:

> Modos de 256 colores -> Disponen de 256 colores como índices a una paleta de tripletes RGB.

> Modos de 15 bpp -> Utilizan 2 bytes para almacenar un pixel, y lo hacen en el siguiente formato: 1 bit (el bit más significativo) es ignorado, y a continuación vienen 5 bits para cada componente de color (R,G,B). El formato quedaría pues de la forma 1:5:5:5 (5 para r, 5 para g y 5 para b):


   Bit 15        Bit 0
Pixel=xrrrrrgggggbbbbb  

Si queremos trazar el color (11,12,23) tenemos que pasar estos 2 números a un word utilizando desplazamientos para posicionarlos en los lugares adecuados, estando cada componente de color además en el rango 0-32 (5 bits).

> Modos de 16 bpp -> Estos modos son similares a los de 15bpp (1:5:5:5), pero con la diferencia de que se utilizan todos los bits, pasando a ser 5 bits para el rojo (los más significativos), 6 para el verde y 5 para el azul (5:6:5)


   Bit 15        Bit 0
Pixel=rrrrrggggggbbbbb  

> Modos de 24bpp -> Utiliza un byte para cada componente de color (8:8:8), lo cual define cada color con 3 bytes. Suelen usarse 4 bytes por la mayor compatibilidad y velocidad de transferencia del dword.


   Bit 23                Bit 0
Pixel=rrrrrrrrggggggggbbbbbbbb  

> Modos de 32bpp -> Utiliza también un byte para cada componente de color (8:8:8), pero se usa un Dword para almacenar la información en memoria, ya que es más eficiente que la escritura de 3 bytes. Además el byte extra (el más significativo del Dword) puede aprovecharse como canal alpha en las tarjetas que lo soporten (transparencias por hardware).


   Bit 31        Bit 0
Pixel=00000000rrrrrrrrggggggggbbbbbbbb  

Para terminar de complicar más la cosa, en algunos sistemas de vídeo la información se almacena en el orden BGR en lugar del habitual RGB, pero gracias a las máscaras de colores red_mask, green_mask y blue_mask es posible diseñar rutinas genéricas de trazado de pixels y de conversión entre diferentes profundidades de color fácilmente y con muy poco código (ver artículos nº 13 y 14 de Programación Actual, Curso de Programación Gráfica). A título de ejemplo, una manera de convertir un color de 8bpp a 32 bpp sería con un código similar al siguiente:


/* Ejemplo de codigo de conversion */
long Convert8To32Bpp( char color, char *Paleta )
{
  long resultado;
  unsigned char r, g, b;

  /* extraemos las componentes de color */
  r = Paleta[color][0]; 
  g = Paleta[color][1];
  b = Paleta[color][2];

  /* situamos cada componente en su lugar */
  resultado = (long) ((r<<16) | (g<<8) | (b));
  return( resultado );
}

Con una rutina como esta sería posible convertir una imagen completa de 256 colores a 32 bpp convirtiendo cada pixel:


void ConvertImage8To32Bpp( char *Paleta, char *Img8, long *Img32, int anchura, int altura )
{
  long color, x, y;

  for( y=0; y<altura; y++ )
  {
   for(x=0; x<anchura; x++)
   {
    color = Convert8To32Bpp( Img8[x][y], Paleta );
    Img32[x][y]= color;
   }
  }
}

Gracias a esta rutina y a las rutinas que cubren el resto de posibilidades es posible disponer de un sólo juego de gráficos almacenado en disco, detectar el modo de vídeo que el usuario está usando, convertir todas las imágenes a ese formato, y trabajar con gráficos de alta velocidad sin necesidad de duplicar el espacio de los gráficos. Para más información se recomienda consultar el curso de programación gráfica publicado en esta misma revista desde la nº 1 a la 14 ambas inclusive.


EN RESUMEN

En nuestra próxima entrega continuaremos viendo características avanzadas de X Window, como la extensión DGA para acceso directo a memoria de vídeo, algo similar a la propiedad ::lock() de las Surfaces en DirectX, y que nos permitirá máximas velocidades para el trabajo con gráficos.

Santiago Romero


Volver a la tabla de contenidos.