Esto no quiere decir que cada vez que realicemos una operación de dibujado tengamos que crear y rellenar un GC. Aunque esto es perfectamente posible, lo que se suele hacer en lugar de enviar un GC con cada petición de dibujado es enviar los atributos deseados al servidor, y enviar peticiones de dibujado utilizando dichos atributos, lo cual reduce mucho el overhead (sobrecarga) del sistema o la red. Como veremos, los GC's pueden crearse (lo hacen en el servidor) mediante XCreateGC, así como modificarse, de forma que podremos optar por crear varios GCs para diferentes primitivas de dibujo, o bien crear un sólo GC (es lo mínimo para utilizar funciones de dibujo) e ir cambiando sus valores antes de dibujar cada primitiva (función XChangeGC o funciones individuales). Esto es así porque cada vez que llamemos a una función que dibuje algo (en una ventana, en memoria, etc) será necesario pasarle entre sus parámetros el GC que le indica al servidor cómo debe realizar el trazado.
Como ya hemos comentado, los GC se crean y almancenan en el servidor, de modo que cuando uno de ellos es creado, se nos da un número identificador (identificador del GC) que nos permitirá decirle al servidor cual de todos los GC definidos allí queremos usar.
A).- Obtener un GC a un recurso o crear un GC nuevo.
Esto se puede hacer principalmente de 3 formas. Por una parte se puede crear un nuevo GC con lo cual el servidor nos devolverá su ID para que lo usemos al referirnos a él. Esta es la opción más comun, aunque también se puede obtener el ID del GC por defecto creado en el servidor X, o utilizar XCopyGC para obtener una copia de un GC ya creado.
B).- Modificar los valores del GC para definir los atributos que deberá usar X Window cuando llamemos a la función gráfica adecuada.
C).- Llamar a la función gráfica deseada pasándole el GC creado u obtenido.
GC XCreateGC( Display *display, Drawable drawable, unsigned long valuemask, XGCValues *values );Esta función hace que el servidor cree un nuevo Contexto Gráfico y que nos devuelva un valor entero que lo identifique, de forma que lo utilizaremos en las funciones de dibujo para que el servidor sepa cuál de toda la lista de GCs que posee es el que deseamos utilizar para el trazado de la primitiva gráfica pedida. Mediante esta función no sólo se puede crear el GC sino que también pueden establecerse algunos de los valores de este atributo mediante el uso de valuemask y values, mediante los cuales le indicaremos qué atributos deseamos establecer y con qué valores. Si no nos interesa darle valores iniciales a este GC, se puede especificar 0 como valuemask y NULL como values de forma que el GC será creado con los valores por defecto del servidor (ver tabla 1).
TABLA 1: Campo Valor por defecto ------------------------------------------------------------- plane_mask 1 foreground 0 background 1 line_width 0 function GXcopy line_style LineSolid cap_style CapButt join_style JoinMiter fill_style FillSolid fill_rule EvenOddRule arc_mode ArcPieSlice tile pixmap relleno con fondo stipple pixmap de 1s ts_x_origin 0 ts_y_origin 0 subwindow_mode ClipByChildren graphics_exposures True clip_x_origin 0 clip_x_origin 0 clip_mask NoneEl modo de actuación del servidor se basa en trazar la función gráfica pedido utilizando los atributos del contexto gráfico, lo cual afectará a determinados pixels del lugar donde se desea dibujar. Sobre estos pixels se aplicará la máscara de planos y la función lógica deseada (AND, XOR, o combinaciones de ellas como se podrá ver más adelante) entre los valores calculados y los existentes en el lugar destino.
Los elementos que define el contexto gráfico son los siguientes:
Colores: De primer plano (foreground) y de fondo (background). El primero indicará el color de la mayoría de operaciones gráficas (pixels, líneas, polígonos), mientras que el segundo se utilizará para el fondo de dichas primitivas (líneas discontinuas, fondos de los rellenados, etc.). Texto: Fuente de texto a utilizar para operaciones de texto (font).
Líneas: Anchura (line_width), forma de los extremos (cap_style) y de las uniones (join_style), así como, obviamente, el estilo de la propia línea (line_style).
Rellenados: Estilo de rellenado (fill_style), que puede variar entre sólido (usando foreground como color de rellenado), tile (pixmap que se usa como patrón de relleno), stipple (se usa un patrón de relleno con pixels transparentes que no se dibujan) o stipple opaco, donde los pixels transparentes se dibujan con el color de fondo. Puede definir además el modo de rellenado de polígonos (fill_rule) o de arcos (arc_mode).
Recorte: Mediante clip_ask puede limitarse el trazado a una determinada área del drawable (recortando lo que quede fuera de ella, como ocurriría si parte de una imagen se saliera fuera de la pantalla, pero definiendo lo mismo en cualquier rectángulo del drawable).
Función: Permite especificar una función lógica (function) a aplicar entre los pixels que dibujamos y los ya existentes, para poder hacer XOR, AND, OR, etc., lo cual puede dar mucho juego a nuestras operaciones lógicas.
Nuestro próximo objetivo es saber cómo debemos indicarle a XCreateGC() o funciones similares qué valores deseamos para cada una de estas carácterísticas (es decir, como cambiar el tipo de rellenado, cambiar el color de fondo o primer plano, etc.). Esto se conseguirá fácilmente mediante los 2 últimos parámetros de la función, cuyo significado exacto analizaremos a continuación.
En cualquier momento (por ejemplo, antes de salir del programa) podemos liberar los GCs previamente creados mediante la función XfreeGC():
XFreeGC(Display *display, GC gc);
Para simplificar la modificación de la variable máscara existen unos #define en los ficheros de cabecera que facilitan enormemente la tarea de especificación de atributos, pues es más sencillo acordarse de estas constantes que del bit asociado a cada una de ellas:
#define GCFunction (1L<:<:0) #define GCPlaneMask (1L<:<:1) #define GCForeground (1L<:<:2) #define GCBackground (1L<:<:3) #define GCLineWidth (1L<:<:4) #define GCLineStyle (1L<:<:5) #define GCCapStyle (1L<:<:6) #define GCJoinStyle (1L<:<:7) #define GCFillStyle (1L<:<:8) #define GCFillRule (1L<:<:9) #define GCTile (1L<:<:10) #define GCStipple (1L<:<:11) #define GCTileStipXOrigin (1L<:<:12) #define GCTileStipYOrigin (1L<:<:13) #define GCFont (1L<:<:14) #define GCSubwindowMode (1L<:<:15) #define GCGraphicsExposures (1L<:<:16) #define GCClipXOrigin (1L<:<:17) #define GCClipYOrigin (1L<:<:18) #define GCClipMask (1L<:<:19) #define GCDashOffset (1L<:<:20) #define GCDashList (1L<:<:21) #define GCArcMode (1L<:<:22)En la tabla 2 podemos ver la estructura XGCValues completa, y en la tabla 3 la lista de funciones para el parámetro function de dicha estructura.
TABLA 2: typedef struct { int function; /* operación lógica a realizar */ unsigned long plane_mask; /* máscara del plano */ unsigned long foreground; /* color de 1er plano */ unsigned long background; /* color de fondo */ int line_width; /* ancho de la línea */ int line_style; /* Estilo de línea */ int cap_style; int join_style; int fill_style; /* Estilo de rellenado */ int fill_rule; int arc_mode; /* Tipo de arco */ Pixmap tile; /* pixmap en operaciones tile */ Pixmap stipple; /* pixmap en operaciones stipple */ int ts_x_origin; /* desplazamiento en ambas */ int ts_y_origin; Font font; /* fuente de texto por defecto */ int subwindow_mode; Bool graphic_exposures; /* Indica si debe haber EXPOSE */ int clip_x_origin; /* origen del clipping */ int clip_y_origin; Pixmap clip_mask; /* máscara de recorte */ int dash_offset; char dashes; } XGCValues; TABLA 3: ---------------------------------------------------- Function Name Value Operation ---------------------------------------------------- GXclear 0x0 0 GXand 0x1 src AND dst GXandReverse 0x2 src AND NOT dst GXcopy 0x3 src GXandInverted 0x4 (NOT src) AND dst GXnoop 0x5 dst GXxor 0x6 src XOR dst GXor 0x7 src OR dst GXnor 0x8 (NOT src) AND (NOT dst) GXequiv 0x9 (NOT src) XOR dst GXinvert 0xa NOT dst GXorReverse 0xb src OR (NOT dst) GXcopyInverted 0xc NOT src GXorInverted 0xd (NOT src) OR dst GXnand 0xe (NOT src) OR (NOT dst) GXset 0xf 1Ejemplo:
Display *display; Window window; GC gc; XGCValues valores; (... codigo de inicializacion, etc ...) valores.foreground = WhitePixel(display, pantalla); valores.line_width = 10; gc = XCreateGC( display, window, GCForeground | GCLineWidth, &:valores );Como puede verse en este ejemplo, hemos creado un GC (XCreateGC) preparado para dibujar en el drawable window (la ventana principal del programa), donde queremos que el color de primer plano sea el blanco y la anchura de línea de 10 unidades. El resto de valores deberá ser los que pone por defecto el servidor. Lo que se ha hecho ha sido crear una estructura de tipo XGCValues donde hemos modificado los 2 campos deseados, y luego mediante la máscara de (GCForeground | GCLineWidth) se han puesto los 2 bits correspondientes a 1 de forma que el servidor ha tomado los valores de color de primer plano y de ancho de línea de XGCValues, y el resto de los valores por defecto. Si ahora utilizamos una función de dibujar líneas con este GC, estaremos escribiendo en la ventana líneas de color blanco y de anchura 10. Este es el significado y funcionamiento de los GCs: la especificación del formato de las primitivas de dibujo.
XChangeGC( Display *display, GC gc, unsigned long mascara, XCGValues *valores );Como puede verse en el siguiente ejemplo, su uso es muy similar al de la función XcreateGC():
valores.foreground = BlackPixel(display, pantalla); valores.line_width = 5; gc = XChangeGC( display, gc, GCForeground | GCLineWidth, &:valores );Aparte de disponer de XChangeGC() también pueden utilizarse una serie de funciones individuales que permiten cambiar un sólo atributo sin necesidad de utilizar XChangeGC ni de crear y modificar estructuras como las anteriores. Ejemplos de este tipo de funciones son las siguientes:
XSetForeground( Display *display, GC gc, unsigned long pixel ); XSetBackground( Display *display, GC gc, unsigned long pixel );Estas 2 funciones permiten modificar el color de primer plano y de fondo del GC que se le pasa como parámetro. El color es especificado mediante un unsigned long que suele ser resultado de operaciones de búsqueda de color como las vistas en nuestra primera entrega de gráficos en X Window. Estos colores (primer plano y fondo) afectarán al trazado de figuras, líneas (continuas y discontinuas), rellenados (stipples), texto, etc.
En cuanto el GC tiene el valor que se quiere para la operación ya puede utilizarse la función gráfica deseada:
XDrawLine( display, window, gc, 10, 20, 100, 110 );Esto dibujaría una línea desde (10,20) hasta (100,110) en la ventana apuntada por window con los atributos (color, ancho de línea, etc.) especificados en el contexto gráfico gc.
XCopyGC(Display *display, GC src, unsigned long valuemask, GC dest);Con esta función se copian determinados atributos del GC SRC en DEST, y de nuevo gracias a valuemask podemos copiar todos los atributos o bien sólo algunos de ellos, dejando el resto inalterados.
XDrawPoint( Display *display, Drawable drawable, GC gc, int x, int y )Dibuja un punto del color especificado en GC.foreground en las coordenadas (x,y) del drawable indicado (por ejemplo una ventana). Existe una función similar llamada XDrawPoints() que permite dibujar n puntos simultáneamente:
XDrawPoints( Display *display, Drawable drawable, GC gc, XPoint *puntos, int numpuntos, int modo );A esta función se le pasa (aparte del GC, display y drawable), un array de puntos contenidos en una estructura de tipo XPoint, el número de puntos a dibujar, y el modo de dibujado, que puede variar entre CoordModeOrigin (coordenadas absolutas tomando (0,0) como la esquina de la ventana, mientras que con el modo CoordModePrevious el nuevo origen de coordenadas es la posición del punto anterior (coordenadas relativas). La estructura XPoint tiene la siguiente definición:
typedef struct { short x, y; } XPoint;
Las partes principales de este ejemplo son:
gc = XCreateGC( display, window, 0, NULL );Crea un GC con sus valores por defecto. Este GC será utilizado más adelante.
fread( &:paleta, 1, 768, fp ); fread( buffer, 1, 64000, fp );Tras la apertura del fichero, leemos en memoria la paleta gráfica (colormap) y el cuerpo de la imagen (los valores de los pixels dentro de la paleta, como se comentó en la primera entrega de gráficos en X Window).
for(y=0; y<:200; y++) { for(x=0; x<:320; x++) { pix=Color( display, paleta[(buffer[y*320+x]*3)]*(65536/64), paleta[(buffer[y*320+x]*3)+1]*(65536/64), paleta[(buffer[y*320+x]*3)+2]*(65536/64), DefaultColormap( display, pantalla )); XSetForeground( display, gc, pix); XDrawPoint( display, window, gc, x, y); } }Con esto dibujamos los 64.000 puntos de la imagen (es una imagen de 320x200 pixels) para lo cual cogemos cada color, buscamos el color más parecido disponible en el ColorMap por defecto (función Color()), ponemos como color de dibujado del GC el color encontrado, y dibujamos el pixel en la posición deseada mediante XDrawPoint(). Gracias al bucle esto se hace para los 64.000 pixels con lo que obtenemos la imagen completa en la ventana.
Dicho de otro modo, creamos una ventana con el mismo tamaño en pixels que la imagen, de modo que cada pixel del fichero se corresponderá con un pixel concreto de la ventana. Lo que hacemos es leer cada pixel y preparar el GC para que al dibujarlo éste tenga el color que le corresponde. Como la imagen es de 256 colores, tiene una paleta que define las componentes RGB de cada uno de estos colores, y es por eso que no utilizamos el valor leído del fichero directamente como un pixel, sino que éste representa una referencia a la tabla de 256 componentes RGB que es la paleta. Esta paleta se almacena en el fichero (como comentamos en las imágenes de 256 colores en nuestra primera entrega de gráficos) en formato de 0 a 63, mientras que nuestra función de búsqueda de colores (y X Window) esperan valores de 0 a 65536, de modo que lo que hacemos es multiplicar por 65536/64 para escalarlas antes de llamar a la función Color. Tras esto dibujamos el pixel con el color que nos devuelve esta función y pasamos al siguiente pixel.
Santiago Romero