Está en la página 1de 39

Las primitivas son dibujos bsicos, ya sean lneas, crculos, rectngulos, etc.

Todas las funciones que vayamos a crear, hacen uso de la rutina de poner pixeles PutPixel(), por lo que ser esta la rutina que deber estar mejor optimizada.

Crculos Razn de aspecto y resoluciones cuadradas Lneas Rectngulos Ejemplos

Crculos Ahora vamos a empezar con una de las rutinas de uso mas frecuente, la de poner crculos en la pantalla. Para poder realizar esto usaremos las ecuaciones de la circunferencia en coordenadas polares que son: Estas ecuaciones sern las que ocuparemos para calcular cada punto (x,y) del crculo, donde el r ser obviamente el radio de crculo y q ser el angulo que forma el radio con la parte positiva del eje x. En forma grfica sera as:

El ngulo deber estar en radianes ya que las funciones de seno y coseno que incluye C , trabajan son los ngulos en radianes. La frmula para transformar grados a radianes es la siguiente: Entonces para dibujar el crculo de un radio determinado, solamente tenemos que hacer un ciclo desde 0 hasta 360, pero con incrementos pequeos, calcular cada punto con las ecuaciones en coordenadas polares e ir dibujando cada punto. El ciclo en vez de ir de 0 a 360 ira de 0 a 6.28 (360*3.14/180=6.28) ya que el ngulo debe estar en radianes. Como dijimos el ciclo de 0 a 6.28 debe hacerse con incrementos pequeos, no contando de uno en uno, ya que para un circulo de radio muy grande, podran aparecer huecos entre un punto y el siguiente, por lo tanto tenemos que usar

un incremento fraccionario. El valor 0.005 produce buenos resultados. Dibujar el crculo punto a punto es una tarea un poco lento, debido a que se debe calcular en cada punto el seno y el coseno del angulo, y estas funcionas son muy lentas. Para solucionar esto se deben crear tablas predefinidas o precalculadas, pero esto lo podrs ver en la seccin correspondiente. Ahora veremos como nos quedara una rutina en lenguaje C para dibujar un crculo en pantalla: void Circle(int cx, int cy, int radio, unsigned char color) { float angulo=0; int x, y; do { x = cx + radio * cos(angulo); y = cy + radio * sin(angulo); if((x>=0) && (y>=0) && (x<320) && (y<200)) PutPixel(x, y , color); angulo+=0.005; } while(angulo<6.28); } Ntese que al calcular las coordenadas x e y, estamos sumndoles las coordenadas cx e cy. Esto se hace para trasladar el centro del crculo a cualquier punto que queramos. De esta forma, para dibujar un crculo solamente tenemos que especificar las coordenadas del centro, el radio y el color del crculo. La condicin que se encuentre antes del PutPixel nos sirve para verificar que las coordenadas que se calculan no se salgan de los bordes de la pantalla. Esta verificacin hace que la rutina vaya un poco ms lenta. Acurdense que las funciones cos(), sin() se encuentran en math.h Volver Razn de aspecto y resoluciones cuadradas Algo que se puede observar, es que nuestros crculos no son exactamente circulares, es decir, estn un poco alargados. Esto se debe a que no estamos utilizando una resolucin cuadrada. Una resolucin cuadrada es aquella en la que la resolucin (o nmero de pixeles) en X es 4/3 de la resolucin en Y, ya que esto corresponde con las medidas fsicas de la pantalla y ocasiona que los pixeles sean cuadrados. Ejemplos de resoluciones cuadradas son 640x480, o 320x240 y 800x600. Pero nuestra resolucin de 320x200 no produce pixeles cuadrados, sino rectngulos que son un poco alargados en el sentido del eje Y.

Si queremos dibujar un crculo perfecto en el modo 320x200, tendramos que dividir el radio de la coordenada Y por un factor de 1.2 ya que:

Podemos modificar nuestra funcin para colocar un crculo ara que dibuje crculos bien proporcionados, solo hay que cambiar la lnea que dice: y = cy + radio * sin(angulo); por una que diga: y = cy + radio * sin(angulo) / 1.2; De esta forma podemos dibujar crculos ms perfectos, aunque sea un poco ms lento, ya que se hace una divisin extra por cada punto. Volver Lneas Muchos pensaran que dibujar lneas es mucho ms fcil que hacer un crculo, pero estn muy equivocados. Las lneas son ms difciles de dibujar que los crculos, y aun mas difcil es dibujarlas rpido. La razn principal es que para los crculos ya tenamos las funciones de seno y coseno precalculadas, y el resto es simplemente un ciclo. Para las lneas es diferente. Primero tenemos que calcular la pendiente de la recta, ver si es positiva o negativa, ver si es mayor o menor que 1, y hasta que tengamos toda esa informacin podremos dibujar una lnea. Si las lneas son horizontales o verticales no hay ningn problema. Para dibujar una lnea horizontal la funcin sera as: void LineH(int x, int y, int ancho, unsigned char color) { int i; for(i=x; i<=x+ancho; i++) PutPixel(i, y, color); } Y para hacer la lnea vertical: void LineV(int x, int y, int alto, unsigned char color) {

int i; for(i=y; i<=y+alto; i++) PutPixel(x, i, color); } Ahora veremos como dibujar un lnea si esta tiene cualquier pendiente, es decir esta inclinada hacia algn lado. Para solucionar esto podemos utilizar la ecuacin de la recta que es: y = pendiente * x pero nosotros veremos un algoritmo que es muchsimo ms eficaz y se conoce con el nombre de ALGORITMO DE BRESENHAM. Este algoritmo se basa en que cuando vamos a colocar un punto, solo hay tres posibles soluciones, (x+1, y) o (x+1, y-1) o (x+1, y+1). El objetivo es trazar una lnea entre los puntos (x, y) e (x1, y1). Veamos un ejemplo, si x2 e y2 sin mayores que x1 e y1 entonces calculamos: longitud_x = abs(x2 - x1); longitud_y = abs(y2 y1); (Nota: la funcin abs() se refiere al valor absoluto de un nmero) entonces tenemos que longitud_x es mayor o igual a longitud_y . As mediante otras dos variables que representen el punto actual a poner, inicializaremos los valores x1 para x e y1 para y. Como hemos dicho, este algoritmo se basa en ir tomando decisiones, por eso tambin tendremos que tener otra variable que nos indique la decisin tomada para saber hacia que posicion nos corresponde ir, as sabremos cuando tendremos que sumas uno a la variable Punto_actual_y: Decision = (longitud_y + longitud_x) longitud_x; Punto_actual_x = x1; Punto_actual_y = y1; Y lo que tendremos que hacer ahora es tan solo ir repitiendo desde x1 a x2 el ciclo. Deberemos poder adaptar el algoritmo a todos los casos, ya que se puede dar que x1 e y1 sean mayores que x2 e y2, de manera que restaremos en uno en cada paso en vez de sumar. Como esta es muy poro enredado, veremos como nos quedara la funcion realizada en lenguaje C. Pero antes haremos una funcin que nos ser muy util en la rutina para colocar una lnea, y es la siguiente: int sgn(int x) { if(x>0) return(1); if(x<0) return(-1); return (0); } la cual nos devuelve simplemente el signo de un nmero o bien cero.

void Line(int x1, int y1, int x2, int y2, unsigned char color) { int i, s, u, v, dx1, dx2, dy1, dy2, m, n; u=x2-x1; v=y2-y1; dx1=sgn(u); dy1=sgn(v); dx2=sgn(u); dy2=0; m=abs(u); n=abs(v); if(!(m>n)) { dx2=0; dy2=sgn(v); m=abs(v); n=abs(u); } s=m>>2; // s=m/2; for(i=0; i<=m; i++) { PutPixel(x1, y1, color); s=s+n; if (!(s<m)) { s=s-m; x1=x1+dx1; y1=y1+dy1; } else { x1=x1+dx2; y1=y1+dy2; } } }

Volver Rectngulos Dibujar un rectngulo es lo ms simple que hay, simplemente se trazan dos lneas horizontales y dos verticales. Veamos entonces como nos quedara el cdigo:

void Box(int x1, int y1, int x2, int y2, unsigned char color) { LineV(x2, y1, y2-y1, color); LineV(x1, y1, y2-y1, color); LineH(x1, y1, x2-x1, color); LineH(x1, y2, x2-x1, color); } Tambin se pueden crear cuadros rellenos de algn color, esto tambin es simple, veamos como nos queda: void FillBox(int x, int y, int ancho, int alto, unsigned char color) { int px, py; for(px=x; px<=x+ancho; px++) for(py=y; py<=y+alto; py++) PutPixel(px, py, color); }

Volver Ejemplos: A continuacin encontrars varios ejemplos usando las rutinas que vimos: Librera grfica: vgalib.h

Dibujo de lneas horizontales y verticales. Dibujo de un circulo. Lneas que rotan. Movimiento de un cuadrado. Dibuja una figura geomtrica denominada Toro mediante varios crculos. Dibuja todas la primitivas vistas en forma aleatoria.

Hardware Modo 13h Retrazado Vertical Primitivas La Paleta Pantallas Virtuales Sprites Tablas Prefedifinidas Efectos Texto Formatos grficos

Ahora veremos como funciona la paleta de colores, para poder cambiar nosotros mismos los colores y as poder personalizarnos un programa, o por ejemplo cuando cargamos una imagen, que tambin es necesario poner la paleta de esa imagen. Vamos empezar viendo como funciona lo de la paleta de colores, que control un chip llamado DAC (Digital Analog Converter), para as conseguir hacer efectos de paleta, pero estos lo encontraras en la seccin de efectos (hacer link).

Perifricos Optimizaciones Herramientas Cursos Links Foro

Como ya sabemos en el modo 13h disponemos de una paleta de 8 bits, o sea, 256 colores. Cada color se consigue a basa de mezclar tres componentes para ir consiguiendo as diferentes tonalidades. As tenemos que un color est formado por tres colores bsicos, el rojo, verde y azul. Estos tres colores pueden tener un mximo de 64 tonalidades, desde el 0 (ms oscuro) hasta el 63 (ms claro). Esto es as porque el sistema que se utiliza es el denominado RGB (Red, Green, Blue), que es el que el monitor utiliza para representar el color de un pixel, ya que este posee tres haces de luz, una haz rojo, otro verde y uno azul, los cuales apuntan a una sola posicin que es lo que conocemos como el haz de electrones, y mezclndose se consigue crear el color deseado. Si queremos conseguir un color gris bastar con poner los valores RGB a la misma intensidad cada uno. Veamos un ejemplo, si queremos que el color 0 sea negro entonces utilizamos los valores (0, 0, 0), o si queremos que sea blanco colocamos los valores (63, 63, 63). Y si lo que queremos es que sea de color rojo intenso ponemos (63, 0, 0) y as se puede jugar con los valores. A continuacin les muestro los valores de algunos colores comunes:

Con unos clculos podemos ver que el numero total de tonalidades disponibles es de 264. 144 (64^3) diferentes, pero solo podremos representar simultneamente 256, ya que la profundidad es de 8 bits y lo mximo que podemos representar en un byte es el valor 255 (0-255). Pero como podemos cambiar los colores, bueno como dije antes el chip encargado de esta tarea es el DAC o Digital Analog Converter, cuya misin es la de transformas las seales digitales del computador a seales analgicas que utiliza el monitor. Para acceder a este chip y olvidarnos de la BIOS la cual dispone de funciones para trabajar con la paleta, nosotros usaremos tres puertos, a travs de los cuales podremos leer y escribir las diferentes componentes de los colores. Estos registros son el 03C7h, el 03C8h y el 03C9h. En el siguiente cuadro podrs encontrar la descripcin detallada de cada uno:

Como vemos, para leer el valor actual de un color, lo que tendramos que hacer es, primero, enviar al puerto 3C7h, el nmero de color del que queremos obtener los 3 valores, y luego, obtener y leer tres valores del puerto 3C9h, que correspondern a los valores RGB del color nmero x que indicamos en el puerto 3C7h. Y si lo que queremos es cambiar los valores RGB de un determinado color, lo que haremos ser primero enviar al puerto 3C8h el nmero del color a cambiar, y despus enviar los valores RGB al puerto 3C9h. A continuacin veremos las rutinas correspondientes en C, para cambiar y leer los valores RGB de un color determinado. Cambiar los valores RGB de un color:

void SetColor(unsigned char color, char r, char g, char b) { outportb(0x3C8, color); outportb(0x3C9, r); outportb(0x3C9, g); outportb(0x3C9, b); } Y para leer los valores RGB de un color, se hara as:

void GetColor(unsigned char color, char *r, char *g, char *b) { outportb(0x3C7, color); *r=inportb(0x3C9); *g=inportb(0x3C9); *b=inportb(0x3C9); } Ya sabemos cambiar los colores, ahora veremos como seran las rutinas para cambiar o obtener una paleta. Antes de esto debemos crear una estructura que nos permita guardar las paleta, esto simplemente se puede hacer utilizando un arreglo de tamao 768 (256*3) y se podra declarar de la siguiente manera: char paleta[768]; o incluso se podra crear un tipo de dato llamado t_paleta, para as hacer mas fcil las declaraciones de paletas en los programas que realicemos, esto sera as: typedef char t_paleta[768]; de esta forma, cuando queramos crear una variable que guarde la paleta, en este caso llamada pal, simplemente la declararamos as: t_paleta pal; La rutina modificar la paleta sera as:

void SetPal(t_paleta pal) { int i; for(i=0; i<256; i++) SetColor(i, pal[i*3], pal[(i*3)+1], pal[(i*3)+2]); } Lo que se hace aqu es recorrer los 256 colores, y por cada uno ir cambiando las intensidades RGB correspondientes. Y por ltimo la rutina para obtener una paleta:

void GetPal(t_paleta pal) { int i; for(i=0; i<256; i++) GetColor(i, &pal[i*3], &pal[(i*3)+1], &pal[(i*3)+2]); } En esta rutina se hace simplemente lo contrario a la rutina SetPal. Nuevamente recorremos los 256 colores, y por cada uno vamos obteniendo las intensidades RGB de cada color. Cuando llamamos a la rutina GetColor debemos pasarle los valores RGB por referencia, es decir la direccin, para que de esta forma salgan modificados. Lo que hara en realidad la rutina GetPal sera obtener la paleta activa que se encuentra en el sistema. Ejemplos Librera grfica: vgalib.h

Creacin de distntos colores, modificando las intensidades RGB Cambio de los colores de la paleta

Pantallas Virtuales
Hardware Modo 13h Retrazado Vertical Primitivas La Paleta Pantallas Virtuales Sprites Tablas Prefedifinidas Efectos Texto Formatos grficos Perifricos Optimizaciones Herramientas Cursos Links Foro

Qu son y para qu sirven? Cmo se hace una pantalla virtual? Cmo dibujar en la pantalla virtual? Cmo copiamos una pantalla virtual a la VGA? Ejemplo

Qu son y para qu sirven? Alguna vez habrn visto alguna demo, o por lo menos un juego comercial y se habrn dado cuenta que se hacen muchas animaciones y efectos, y no se ve ni el ms mnimo parpadeo en la pantalla. Si por ejemplo nosotros realizamos una animacin simple, como puede ser el movimiento de un cuadrado por la pantalla, notaremos que el movimiento no es suave, sino que la figura parpadea constantemente y a veces puede pasar que no se vea

alguna parte de la figura. En la seccin ejemplos, que se encuentra al final de esta pagina, podrn darse cuenta de estos efectos que suceden, y hacen que la realizacin de una animacin no sea profesional. Todo este parpadeo es producido por los constantes cambios que se hace en la pantalla en el momento en que se est dibujando. En la seccin retrazado vertical, vimos que el usar esta tcnica sincronizaba un poco nuestro programa con la pantalla, pero supongamos que tenemos que dibujar un pantalla con un fondo un poco complejo y encima de este fondo tenemos que dibujar varias figuras que se movern en la pantalla. El tiempo de retrazado vertical no seria suficiente para dibujar toda la pantalla, as que la tcnica de retrazado vertical en este caso no nos servira. Piensen por ejemplo que no sera nada excelente que el usuario viera como dibujamos el fondo y luego las figuras una por una y despus borramos la pantalla para volver a dibujar el fondo. Esto sera horrible, lo ideal sera que todo se dibujara tan rpidamente que diera la impresin que el fondo nunca se borra y que las figuras realmente estn en movimiento. Pero la CPU y en especial la memoria de video, no son tan rpidos como para lograr esto, por lo que tenemos que hacer un pequeo truco. Que pasara si en lugar de dibujar el fondo y las figuras en la pantalla, las dibujamos en otro lugar y luego simplemente copiamos todo a la pantalla real, una vez que ha sido todo dibujado. Y mientras el usuario admira la escena, nosotros estamos dibujando el siguiente cuadro en nuestra pantalla virtual, pero el usuario no ver nada hasta que copiemos toda las escena a la pantalla real. Copiar una pantalla de un lugar a otro, en el modo13h significa mover 64.000 bytes de una zona de la memoria a otra. Entonces una pantalla virtual es simplemente una zona de memoria, en el caso del modo 13h de 64.000 bytes, que funciona como la memoria real de la VGA. Nosotros podemos dibujar puntos, lneas, crculos o lo que sea en nuestra pantalla virtual, la nica diferencia es que nada de lo que dibujemos en la pantalla virtual es visible. Nosotros podemos dibujar una escena que tarde 1 segundo en dibujarse y luego simplemente copiamos los 64 Kb de la pantalla virtual a la memoria de la VGA, lo cual tarda solo un instante. Para el usuario que esta viendo el programa, la pantalla se dibuj en ese instante, aunque como ya sabemos esto no fue as. Adems mientras el usuario esta viendo la pantalla, nosotros podemos estar dibujando en otras pantalla virtuales sin modificar lo que realmente se esta viendo. Volver

Cmo se hace una pantalla virtual? La pantalla virtual, como ya sabemos, debe contener 64.000 bytes. Para necesitamos reservar una zona de memoria con 64 Kb. Alguien podra pensar que para esto simplemente podramos usar un arreglo declarado de la siguiente forma: unsigned char pvirtual[64000]; Pero lamentablemente, el lenguaje C y muchos otros lenguajes de alto nivel, utilizan 64 Kb para las variables del programa, por lo que si hacemos lo anterior, nos quedaramos sin memoria para otras variables. As que tenemos solo una posibilidad, y esta es usar punteros para acceder al heap (montn). Esto en C lo haramos as, primero definimos un puntero que apunte a datos de tipo byte: unsigned char *pvirtual; Luego deberamos reservar la memoria necesaria, utilizando las funciones que nos proporciona el lenguaje C, podramos ocupar la funcion malloc o la callos, la diferencia de esta ltima con la funcin malloc, es que llena con ceros el bloque reservado, lo cual es bueno en nuestro caso, ya que nuestra pantalla virtual quedara automticamente limpia, es decir toda llena con el color 0, que por defecto es el negro. Entonces podramos reservar memoria de dos formas: pvirtual=(unsigned char *)malloc(64000); o pvirtual=(unsigned char *)calloc(64000, sizeof(unsigned char)); De esta forma tendramos un puntero, apuntando al primer elemento del bloque de 64 Kb. Pero hay un problema, debemos verificar si realmente se nos reserv la memoria requerida para esto podemos ver si la variable pvirtual es igual a NULL, si es as significa que no hay memoria suficiente, esta memoria se refiere a la memoria convencional que posee libre nuestro PC. Recuerden que la memoria convencional es de 640 Kb solamente. Para verificar si se reserv o no memoria, podemos hacer lo siguiente: if(pvirtual==NULL) { printf(\nNo hay memoria suficiente!\n); exit(1); } Pero antes que termine nuestro programa, debemos liberar la memoria utilizada por la pantalla virtual, y esto lo haremos simplemente utilizando la funcion free: free(pvirtual); Ya sabemos crearnos una pantalla virtual, pero seria conveniente tener una funcion que nos hiciera todo este trabajo, as que crearemos un nuevo tipo de dato para la pantalla virtual,

esto lo haremos as: typedef unsigned char *t_pvirtual; As, si necesitamos una pantalla virtual simplemente la declararemos de esta forma: t_pvirtual pv; La funcion que nos cree la pantalla virtual, es decir la que nos reserve la memoria y nos indique adems si pudo lograrlo, se podra hacer as: unsigned char *SetPVirtual(unsigned char **pv) { *pv=(unsigned char *)calloc(64000, sizeof(unsigned char)); return (*pv); } Ojo con esta funcion, recibe como parmetro un puntero a un puntero, esto es as para poder ocupar el puntero fuera de la funcion y modificarlo. Despus de reservar la memoria, la funciona devuelve el mismo puntero, esto nos servir para verificar si se pudo reservar la memoria. En caso que no se logra la reserva, la funcion devolver NULL, ya que *pv tendr ese valor. Un ejemplo simple de la utilizacin de esta funcin podra ser el siguiente: // Declaramos una variable de tipo pantalla virtual t_pvirtual pv; //Llamamos a la funcin con el parmetro requerido if(!SetPVirtual(&pv)) { printf(\nNo hay memoria suficiente\n); exit(1); } Ojo con el ejemplo, le pasamos la direccin del puntero &pv, ya que la funcin nos pide un puntero a un puntero. Si la funcion devuelve NULL, significar que no se pudo reservar memoria y por lo tanto ese NULL equivale a que la funcion devolvi un cero, luego la negamos con ! y por lo tanto la condicin del if tendr el valor 1, lo que significa que es verdadero (recuenden que en C, 0=falso y 1=verdadero) y por lo tanto se ejecutar el cdigo correspondiente al if. La funcion inversa, es decir la que elimina el bloque de memoria reservada es el siguiente:

void FreePVirtual(unsigned char **pv) { free(*pv); *pv=NULL; } Esta funcin es muy simple, lo que se hace es liberar el bloque de memoria, reservado anteriormente con malloc o calloc, y luego se hace que el puntero no apunte a nada, es decir se le asigna el valor NULL. Volver Cmo dibujar en la pantalla virtual? Si ya tenemos reservada la memoria para un puntero llamado por ejemplo pv, dibujar en pv es anlogo a como lo hicimos con el puntero DIR_VGA, la nica diferencia es que ahora lo que hagamos o mejor dicho dibujemos, no se ver en la pantalla. Por ejemplo podramos hacer lo siguiente con la variable pv: for(x=0; x<320; x++) for(y=0; y<200; y++) pv[y*320+x]=random(256); En este caso lo que se hace es simplemente llenar los 64.000 bytes de nuestra pantalla virtual con pixeles al azar. Ahora que ya sabemos trabajar muy bien con una pantalla virtual tendremos que modificar todas las rutinas que hemos hecho hasta el momento. Para esto simplemente se le agregar un parmetro a cada funcin, donde este parmetro ser un puntero que nos indicar donde dibujar. Voy hacer solo el ejemplo para la funcion PutPixel, las dems modificaciones las podrs ver al final de esta pagina en el archivo vgalib.h void PutPixel(int x, int y, unsigned char color, unsigned char *where) { where[(y<<8)+(y<<6)+x]=color; } Si se dan cuenta es super simple, ahora cuando hagan un programa solo deben especificar donde quieren dibujar. Si quisieran dibujar en la pantalla real, simplemente le pasan como ultimo parmetro el puntero DIR_VGA. Volver

Cmo copiamos una pantalla virtual a la VGA? Esto tambin es simple. Para esto podemos usar una funcin que se encuentra en mem.h y es la funcin memmove. Lo que hace esta funcion es mover un bloque de memoria de un lugar a otro. Sus parmetros son: un puntero destino, otro fuente, y el numero de bytes que se quieren mover. El nmero de bytes mximo que se pueden mover es de 64.000 bytes, lo que justamente corresponde a lo que nosotros necesitamos. Podemos crear una funcion que reciba dos punteros uno destino y otro fuente: void FlipTo(unsigned char *source, unsigned char *where) { memmove(where, source, 64000); } Por ejemplo si quisiramos mover el contenido de la pantalla virtual pv al memoria de la VGA, haramos lo siguiente: FlipTo(pv, DIR_VGA); Como esta funcin ser muy utilizada podemos crear otras similar, pero solo con un parmetro: void Flip(unsigned char *source) { memmove(DIR_VGA, source, 64000); } Como ven, ya sabemos todo lo necesario para trabajar con pantallas virtuales. Pero las funciones Flip y FlipTo (que tcnicamente son iguales) pueden resultar un poco lentas. La solucin a esto es crear estas funciones en ensamblador, pero todo lo que se refiere a optimizaciones lo encontraras en la seccin correspondiente. La funcin de volcado es una de las que debera estar mejor optimizada para lograr una velocidad aceptable en nuestros programas. Volver Ejemplo Librera grfica: vgalib.h Muestra un cuadro en movimiento usando tres tcnicas diferentes: 1) Se borra y se vuelve a dibujar el cuadro. 2) Se inserta el procedimiento WaitRetrace() antes de mover el cuadro. 3) Se borra y se dibuja el bloque en una pantalla virtual, luego llama al procedimiento WaitRetrace() y luego se copia la

pantalla virtual a la VGA. Cuando ejecuten el programa vern la diferencia, y ah se darn cuenta de la gran utilidad de las pantallas virtuales. De aqu en adelante las pantallas virtuales se usarn mucho, especialmente en lo que se refiere a animaciones. Ver ejemplo

Bajar todo (26 Kb) Sprites


Hardware Modo 13h Retrazado Vertical Primitivas La Paleta Pantallas Virtuales Sprites Tablas Prefedifinidas Efectos Texto Formatos grficos Perifricos Optimizaciones Herramientas Cursos Links Foro

Qu son y para qu sirven los sprites? Creando un sprite Creando nuestro propio formato de sprite Dibujando sprites en la pantalla Sprites transparentes Animaciones Ejemplo

Qu son y para qu sirven los sprites? Un sprite es simplemente una figura o imagen de dimensiones no muy grandes, que puede desplazarse por la pantalla, es decir que posee movimiento. Hay que tener claro que tambien existen los bitmaps, que tambien son como los sprites, pero no poseen movimiento, es decir es una agrupacin de pixeles que forma una figura, y por lo tanto es esttica. Un sprite est formado por varios frames o cuadros, lo cuales forman una animacin, por ejemplo nuestro protagonista de un juego de plataforma, o un juego de naves, etc. As tenemos que un frame o cuadro es simplemente uno de los dibujos que se muestran en la pantalla. Los sprites pueden tener cualquier tamao, y normalmente tendrn asociadas unas variables, las cuales por ejemplo pueden representar su posicin en la pantalla, el ancho y el alto del sprite, etc. Algunas veces pueden tener factores como la velocidad, el estado del sprite. Por ejemplo en un juego un sprite tendr varias variables, como por ejemplo una para saber si colision o choco con otro sprite, etc. Los sprites siempre son rectangulares, aunque al verlos en un programa no lo parezcan, esto se debe a tcnicas que se utilizan una de ellas es el uso de mascaras. La principal funcin de los sprites es la de las animaciones, ya sea en una presentacin, juego, etc. Los sprites son muy usados en los juegos 2-D, ya sean de plataformas, aventuras grficas, etc. Aunque hay excepciones donde los sprites pueden ser utilizados en juegos 3-D, un ejemplo de ello es el conocidisimo Wolfenstein 3D, de la

empresa ID Software, aunque no lo crean este juego utilizaba sprites, a diferencia de los ultimos juegos tridimensionales como el Quake, Duke Nukem, etc. Ejemplo de juegos que han utilizado sprites son el Street Fighter, Mortal Kombat, Killer Instinct, y clsicos como The Prince of Persia, la saga Keen Commander, etc. Volver Creando un sprite Antes de crear un sprite, tendremos que crearnos una estructura que almacene todas las variables necesarias y los frames que formarn el sprite. Esto en C se puede hacer as:
typedef struct { int x, y; // Posicin del sprite en la pantalla unsigned char ancho, alto; // Ancho y alto del sprite unsigned char num_frames; // Nmero de frames que tiene el sprite unsigned char *frame[MAX_FRAMES]; // Cada uno de los frames que forma el sprite } t_sprite;

Hemos creado un tipo de dato llamado t_sprite, que contiene las variables bsicos para trabajar con un sprite. Dentro de la estructura definimos a los frames de esta forma:
unsigned char *frame[MAX_FRAMES];

lo cual es un arreglo de punteros, la gracia de hacer esto que podemos crear un sprite de cualquier tamao. MAX_FRAMES es una constante que puede ser definida as:
#define MAX_FRAMES 10

Para crear cada frame deberemos reservar la memoria necesaria. Si de dan cuenta, hay solo un inconveniente. Imaginemos que tenemos un sprite de solo 3 frames, y nosotros definimos ese arreglo de tamao 10, por lo que se desperdiciaran 7 posiciones. Esto se puede solucionar usando listas enlazadas, pero para no complicarnos lo haremos as no mas. Ya tenemos una estructura para guardar nuestros sprites, pero ahora cmo almaceno los sprites en el disco duro para poder cargarlos despus desde mi programa? Para esto hay varias posibilidades todas basadas en el uso de archivos, una es tener archivos independientes, es decir un archivo para cada frame. Pero esto puede resultar incomodo si tenemos un sprite por ejemplo de 50 frames, tendramos que tener 50 archivos. En todo caso nunca tendremos tantos. Para tener archivos independientes podramos usar los tpicos archivos *.CEL del Autodesk Animator o bien crearnos un tipo de archivo a nuestra medida y esto ser lo que haremos nosotros. Otra forma sera

guardar todos los frames en un mismo archivo de un formato propio, o en formatos tradicionales como el GIF, PCX, BMP. De estos tres el ms recomendable es el PCX, ya que es mas fcil de trabajar y comprime las imgenes. Volver Creando nuestro propio formato de sprite Realizar esto no es complicado, simplemente hay que conocer el manejo de archivos en C. Lo que haremos ser crearnos un archivo que tendr la extensin SPR, y tendr la siguientes estructura: CABECERA: 0 byte: ancho del sprite 1 byte: alto del sprite IMAGEN: resto del archivo desde el byte 2. Los 2 primeros bytes contendrn el ancho y alto de nuestro sprites y el resto del archivo ser la informacin de cada pixel que forma el frame. As el tamao de nuestro archivo se podr calcular de la siguiente forma:
Tamao = ancho * alto + 2;

La pregunta ahora es como creamos nuestro archivo, esto se puede hacer de varias formas, las ms simple es tener nuestro sprite en un formato tradicional como el PCX, entonces crearnos un programita que transforme el archivo PCX en un archivo con nuestro formato. En la seccin herramientas podrs encontrar un programa muy simple, que realice hace un tiempo, y hace esto. Su funcion es leer un archivo PCX, luego lo muestra en pantalla, luego guarda el ancho y el alto de la imagen y lee cada pixel y lo va guardando en un archivo. El programa tambin guarda la paleta. Esta ltima es muy importante para conocer la informacin de los colores que usa nuestro sprite. Veamos como nos quedara la funcion que lee un simple frame desde un archivo:
int LoadSpr(char *filename, unsigned char **frame) { enum{ARCHIVO_NO_ENCONTRADO=0, NO_HAY_MEMORIA=-1, MUY_GRANDE=-2, OK=1}; int ancho, alto; FILE *f; if((f=fopen(filename, "rb"))==NULL) return ARCHIVO_NO_ENCONTRADO; // Leemos el ancho y alto del frame ancho=fgetc(f);

alto=fgetc(f); if((ancho>255) || (alto>255)) { fclose(f); return MUY_GRANDE; } // Reservamos memoria para la imagen, el ancho y el alto if((*frame=(unsigned char *)malloc((ancho*alto)+2))==NULL) { fclose(f); return NO_HAY_MEMORIA; } // Copiamos el ancho y el alto memcpy(*frame, &ancho, 1); memcpy(*frame+1, &alto, 1); // Leemos la imagen fread(*frame+2, sizeof(char), (ancho*alto), f); fclose(f); return OK; }

Lo que hace esta funcion es recibir como parmetro el nombre del archivo y un puntero a un puntero a un dato de tipo byte. Luego abrimos el archivo y vemos si existe o no. Si no existe devolvemos un error. En caso que exista leemos la cabecera para conocer las dimensiones de nuestro frame si es mas grande que 55 devolvemos un error. Si el tamao del frame es menor o igual a 255, reservamos la memoria necesaria, as nuestro puntero contendr en sus dos primeros bytes el ancho y el alto, del byte 2 en adelante estar la informacin de la imagen. Luego leemos los datos de la imagen del archivo y listo. Como vemos ya podemos leer un archivo con nuestro formato y guardar los datos en memoria. Al final de nuestro programa deberemos liberar la memoria reservada, para hacer esto simplemente llamamos a la funcion free con el puntero al frame como parmetro. Ahora veremos como crearnos una funcin que abra varios archivos de tipo spr, pero con la condicin que todas tengan el mismo nombre y todos se encuentren enumerados. Veamos:
int LoadSprite(char *filename, t_sprite *sprite, unsigned char num_frames) { unsigned char i, num;

int r; char *nombre, *numero; // Guardamos total de frames sprite->num_frames=num_frames; // Inicializamos a cero las coordenadas del sprite sprite->x=sprite->y=0; // Cargaremos cada uno de los frames que forma el sprite for(i=0, num=1; i<num_frames; i++, num++) { // Creamos el nombre del archivo strcpy(nombre, filename); itoa(num, numero, 10); strcat(nombre, numero); strcat(nombre, ".spr"); // Lo cargamos r=LoadSpr(nombre, &(sprite->frame[i])); // Si hubo algun error salimos del for if(r!=1) break; } // Si hubo algun error al cargar algun un frame, liberamos la memoria reservada hasta // ese momento if(r!=1) { if (i!=0) // Si se cargo en memoria al menos un frame... { for(num=0; num<i; num++) free(sprite->frame[num]); } return r; // Retornamos el error producido } // Guardamos el ancho y el alto del sprite memcpy(&(sprite->ancho), &(sprite->frame[0][0]), 1); memcpy(&(sprite->alto), &(sprite->frame[0][1]), 1); return 1; // Todo OK! }

Espero que hayan entendido la funcin, aunque no es de lo mejor, lo ideal sera usar listas enlazadas. Tal vez ms adelante en otra seccin incluya como hacer esto ltimo. La secuencia de archivos para un sprite debera tener los nombres de esta forma, por ejemplo si los archivos son de un auto y tenemos 3, los archivos deberan llamarse: auto1.spr, auto2.spr y auto3.spr. As al llamar a la funcion LoadSprite lo haramos de la siguiente forma:
r=LoadSpr(auto, &sprite, 3);

suponiendo que sprite se defini as:


t_sprite sprite;

donde r, ser una variable de tipo entero que guardar el valor que regresa la funcion y 3 corresponde al numero de frames

que conforma el sprite. Ojo que la funcion puede devolver los siguientes valores:
0: Archivo no encontrado 1: OK! -1: Memoria insuficiente -2: Tamao de la imagen muy grande

Pero esto no es todo debemos crearnos una funcion que elimine todos los frames de la memoria:
void FreeSprite(t_sprite *sprite, unsigned char num_frames) { unsigned char i; sprite->x=sprite->y=0; sprite->num_frames=0; sprite->ancho=sprite->alto=0; for(i=0; i<num_frames; i++) free(sprite->frame[i]); }

De esta forma si por ejemplo queremos eliminar los frames del auto lo haramos as:
FreeSprite(&sprite, 3);

Volver Dibujando sprites en la pantalla Ya sabemos como dejar un sprite en memoria, pero ahora cmo lo colocamos en la pantalla? Para hacer esto podemos tomar el sprite y leer fila a fila la imagen y visualizarlas. Lo ideal sera que esta funcion que pone un sprite en la pantalla estuviera hecha en ensamblador. Pero por ahora nosotros veremos solo un funcion en C:
void PutSprite(int x, int y, unsigned char *frame, unsigned char *where) { unsigned char ancho=frame[0], alto=frame[1]; int col, fil, inc=2; for(fil=0; fil<alto; fil++) for(col=0; col<ancho; col++) where[((fil+y)<<8)+ ((fil+y)<<6)+col+x]=frame[inc++]; }

Si se dan cuenta, esta funcin har que nuestro sprite aparezca como un rectngulo, lo cual no resulta muy bonito, a continuacin veremos como solucionar esto. Volver

Sprites transparentes Un sprite transparente es un sprite que tiene asociado un color especfico como fondo, por ejemplo el color 0 (negro). As cuando queramos dibujar un sprite, simplemente tendremos que preguntar si el color que vamos a dibujar es el color de la mascara, si es as no lo dibujaremos y pasaremos al otro. Cuando encontremos un color distinto de 0 dibujaremos dicho color. Casi siempre se utiliza el color 0 como mscara, ya que al realizar la funcin en ensamblador para colocar un sprite, es mucho ms fcil verificar si una variable tiene el valor 0. Entonces nuestra rutina para colocar sprites transparentes se hara as:
void PutSprite(int x, int y, unsigned char *frame, unsigned char *where) { unsigned char ancho=frame[0], alto=frame[1], color; int col, fil, inc=2; for(fil=0; fil<alto; fil++) for(col=0; col<ancho; col++) { color=frame[inc++]; if(color!=0) where[((fil+y)<<8)+ ((fil+y)<<6)+col+x]=color; } }

De ahora en adelante nosotros utilizaremos esta funcin, ya que sera muy feo que nuestro sprite se viera rectangular. Volver Animaciones Realizar una animacin de un sprite no es nada difcil. Imaginemos que tenemos un sprite que comprende varios frames y queremos animarla y desplazarla sobre un fondo esttico. Para hacer esto tenemos tres formas de hacerlo. No son las nicas ni las mejores, pero si son las ms comunas y sencillas, veamos: Mtodo 1:Utiliza harta memoria 1) Crear e inicializar dos pantallas virtuales (pv1 y pv2). 2) Dibujar el fondo en pv2. 3) Copiar pv2 a pv1. 4) Dibujar sprites en pv1. 5) Esperar el retrazado vertical. 6) Copiar pv1 a la VGA. 7) Si es necesario, modificar la posicin de los sprites y de los

frames. 8) Repetir desde el paso 3. Ventajas: El mas fcil de implementar. Es el mejor mtodo cuando se trata de muchos sprites. Reduce al mnimo o a nada el parpadeo. Desventajas: - Necesita mucha memoria, es decir dos pantallas virtuales ms los datos de los sprites, lo cual puede ser mas de 130 Kb. - No es el ms rpido cuando son pocos sprites. Mtodo 2: No necesita mucha memoria 1) Dibujar el fondo en la VGA o copiarlo de alguna pantalla virtual. 2) Almacenar la parte del fondo donde se va dibujar el sprite. 3) Dibujar el sprite. 4) Modificar posicion del sprite 5) Reponer la parte del fondo que se obtuvo en el paso 2. 6) Repetir desde el paso 2. Ventajas: - No necesita ninguna pantalla virtual. - Funciona para pocos y pequeos sprites. Desventajas: - Dibujar en la VGA es ms lento que dibujar en una pantalla virtual. - Puede causar mucho parpadeo con sprites grandes. Mtodo 3: Un poco de memoria 1) Crear e inicializar una pantalla virtual (pv). 2) Dibujar el fondo en pv. 3) Copiar pv a la VGA. 4) Dibujar sprite en la VGA. 5) Modificar posicion del sprite 6) Copiar la porcin de fondo de pv a la VGA. 7) Repetir desde el paso 4. Ventajas: - No necesita tanta memoria, solamente una pantalla virtual. - Es ms rpido cuando se trata de pocos sprites. - Hay menos parpadeo que en el mtodo 2. Desventajas: - Es un poco ms complejo que lo otros mtodos. - Con muchos sprites puede haber parpadeos. Estos tres son los mtodos ms comunes, pero pueden haber ms. Lo que deben hacer ustedes es adaptar el mtodo que ms se ajuste a su programa, haciendo un balance entre la memoria, velocidad y calidad. Incluso pueden combinar dos o ms mtodos, por ejemplo, en el mtodo 3, en lugar de dibujar el fondo y los sprites en la VGA, pueden dibujarlos en otra

pantalla virtual y copiar todo despus a la VGA como en el mtodo 1. Eso reduce mucho el parpadeo e incremente la velocidad, pero consume ms memoria. Ya sabemos como crear una animacin, pero se nos queda algo importante en el tintero, que pasa si cargo un sprite, cmo se cuales son sus colores? Bueno muy simple, conociendo su paleta de colores, para esto debemos tener en un archivo la paleta de colores correspondiente al sprite que visualizaremos. Si recuerdan una paleta tiene las valores RGB de los 256 colores que pueden estar mostrndose en la pantalla simultneamente en la pantalla. Entonces lo que haremos ser leer desde un archivo la paleta de colores, luego almacenarla en un arreglo t_paleta y colocarla en el sistema con SetPal(). Para esto crearemos una funcion que nos lea la informacin de una paleta desde un archivo, es muy simple. Veamos:
int LoadPal(char *filename, t_paleta pal) { enum{ARCHIVO_NO_ENCONTRADO=0, ARCHIVO_NO_VALIDO=-1, OK=1}; FILE *f; if((f=fopen(filename, "rb"))==NULL) return ARCHIVO_NO_ENCONTRADO; if(filesize(f)!=768) { fclose(f); return ARCHIVO_NO_VALIDO; } fread(pal, sizeof(char), 768, f); fclose(f); return OK; }

Esta funcin recibe como parmetros el nombre de la paleta, que generalmente tiene la extensin pal, tambien recibe una variable de tipo t_paleta para guardar los datos ledos. Se abre el archivo y se verifica que el archivo tenga el tamao 768 (recuerden: 256*3=768), y al final se lee la paleta. La funcion filesize la podrs encontrar en el archivo vgalib.h Ahora veremos como haramos una animacin utilizando el mtodo 1. Lo primero que se har, ser cargar todos los sprites en memoria y cargar la paleta correspondiente. Por ejemplo imaginemos que tenemos la toda la secuencia de frames de un mono caminando. Supongamos que el sprite est formado por 5 frames. Entonces si queremos mostrar la animacin ser hara de esta forma:

t_sprite mono; // Variable que guarda los datos del sprite t_pvirtual pv1, pv2; // Pantallas virtuales t_paleta pal; // Para guardar la paleta del sprite int num=0; // Nmero del frame a mostrar SetPVirtual(&pv1); virtuales SetPVirtual(&pv2); // Reservamos memoria para las pantallas

LoadSprite(mono, &mono, 5); // Cargamos el sprite LoadPal(mono.pal, pal); // Cargamos la paleta mono.x=0; // Inicializamos variables de posicion del sprite mono.y=100; SetMode(GRAFICO); // Inicializamos el modo 13h (320x200) SetPal(pal); // Colocamos nuestra paleta do { FlipTo(pv1, pv2); // Copiamos pv2 a pv1 // Dibujamos el sprite PutSprite(mono.x, mono.y, mono.frame[num], DIR_VGA); // Cambiamos el frame del sprite num++; if(num>2) num=0; mono.x+=2; // Modificamos posicion // Verificamos si se sale de la pantalla if(mono.x+mono.ancho>320) mono.x=0; WaitRetrace(); // Copiamos pv1 a la VGA Flip(pv1); } while(!kbhit()); SetMode(TEXTO); // Volvemos al modo texto

FreePVirtual(&pv1); // Liberamos memoria de las pantallas virtuales FreePVirtual(&pv2); FreeSprite(&mono, 5); // Liberamos la memoria del sprite

Como ven no es difcil crear animaciones. Tengan en cuenta que en el ejemplo no verificamos ningn error, como por ejemplo si no hay memoria o si no existe algn archivo. Volver Ejemplo Ahora podrn encontrar un ejemplo similar al anterior, pero ms completo y con los archivos de los sprites y paleta. Ver Librera grfica: vgalib.h Tablas Prefefinidas

Hardware Modo 13h Retrazado Vertical Primitivas La Paleta Pantallas Virtuales Sprites Tablas Prefedifinidas Efectos Texto Formatos grficos Perifricos Optimizaciones Herramientas Cursos Links Foro

Qu es un tabla predefinida? Creando una tabla precalculada Ejemplo

Qu es un tabla predefinida? Las tablas predefinidas o lookup table, es algo muy simple de entender, y que har que en nuestros demos o juegos los distintos clculos y operaciones que tengamos que realizar no nos quiten tiempo y hagan ms rpidos nuestros programas. Cuando en nuestras aplicaciones necesitamos realizar numerosos clculos matemticos complejos de una forma frecuente, esto puede hacer que el resultado de nuestro demo o juego se ponga muy lento, debido al tiempo que se tardan en realizar estos clculos. La idea para crear una tabla es sencilla. Y si primero realizamos las operaciones y los resultados los guardamos en una tabla indexada? Luego tendremos solo que tomar los valores de la tabla, cuando nos haga falta para alguna aplicacin, y extraer los resultados de esta, previamente calculados. Esto de las tablas precalculadas es vital en la programacin 3D, o tambin para hacer fractales, etc. Volver Creando una tabla precalculada Sabemos que el lenguaje C trae varias funciones trigonomtricas, en las que se incluyen la del seno y coseno. Lamentablemente, estas funciones hacen muchas comprobaciones de rangos y algunos clculos extra, que hacen que su uso sea algo muy lento. Adems de que el parmetro que reciben debe ser un ngulo en radianes. Por lo que si quisiramos usar grados, tendramos que hacer antes una conversin, y lo que significa ms clculos y ms tiempo que esperar. Lo que tenemos que hacer entonces, es generar nuestra propia tabla de valores de las funciones, almacenarla en un arreglo y simplemente usar este arreglo en lugar de las funciones sin() y cos(). Veamos un ejemplo de esto:
#define PI 3.141516 typedef float *t_tabla;

t_tabla seno, coseno; void GeneraTablas(void) { int ang; seno=(float )malloc(360*sizeof(float)); coseno=(float *)malloc(360*sizeof(float)); for(ang=0; ang<360; ang++) { seno[ang]=sin(ang*PI/180); coseno[ang]=cos(ang*PI/180); } }

Primero definimos una constante para el valor de Pi. Luego un tipo de datos llamado t_tabla, que es un puntero a datos de tipo float, para almacenar los nmeros reales que corresponden a los senos y/o cosenos de ngulos enteros de 0 a 360 grados. Luego creamos dos variable del tipo t_tabla una para los senos y otra para los cosenos. Despus en el procedimiento GeneraTablas(), reservamos el espacio en memoria necesario para nuestras tablas con la funcion malloc(). Si intentamos acceder a las tablas antes de reservar el espacio en memoria podramos hacer que se trabara la mquina. Luego calculamos los valores de las tablas. En este caso no importa usar las funciones sin y cos, ya que solamente calculamos las tablas una vez en el programa, de preferencia al principio. Las funciones sin y cos usan un argumento en radianes, por lo tanto hay que convertir primero los grados a radianes. Una vez que hemos calculado las tablas, podemos usarlas en cualquier parte del programa, pero hay recordar que las variables seno y coseno son punteros. Otra cosa a recordar es que las tablas son arreglos, no funciones, por lo tanto tenemos que especificar el parmetro como si fuera un ndice, es decir , entre [ ]. Por ejemplo:
seno[45] devuelve el seno de 45 grados. coseno[20] devuelve el coseno de 20 grados. seno[30.5] devuelve un error de compilacin. Coseno[-20] devuelve un error de fuera de rango.

Antes de que termine un programa que usa tablas, debemos liberar la memoria que hemos reservado, para que el DOS la pueda utilizar en otros programas. Esto la hacemos simplemente usando la funcin free, de la siguientes forma:
free(seno); free(coseno);

Obviamente, despus de liberar la memoria de una tabla, ya no la podremos utilizar. Al dibujar un crculo debemos hacer un ciclo de 0 a 360 grados, pero no contando de uno en uno, sino que hay que utilizar incrementos fraccionarios. El problema es que nuestras tablas de senos y cosenos son arreglos, y C no permite que los arreglos tengan ndices fraccionarios, por lo que tenemos que hacer un arreglo un poco ms grande. Por ejemplo imaginemos que queremos dibujar un crculo con incrementos de 0.25. Para hacer esto en C podramos hacer lo siguiente:
float *tabla; tabla=(float )malloc(1440*sizeof(float));

Notar que: 1440 = 360 / 0.25 = 360 * 4 Esto significa que para calcular el seno o coseno de un ngulo especfico, tendremos que usar:
seno[angulo*4] coseno[angulo*4]

Por ejemplo:
seno[80] = seno de 20 grados seno[720] = seno de 180 grados coseno[1] = coseno de 0.25 grados

Pero lo que nosotros vamos hacer es un ciclo desde 0 hasta 1440, por lo tanto no necesitamos hacer ningn clculo extra. Volver Ejemplo Ahora podrs ver un ejemplo que hace uso de tablas precalculadas para dibujar circunferencias en la pantalla. Primero se dibujan varias circunferencias concntricas sin utilizar tablas predefinidas, y luego utilizndolas. Librera grfica: vgalib.h Ver ejemplo Texto en modo grfico

Hardware Modo 13h Retrazado Vertical Primitivas La Paleta Pantallas Virtuales Sprites Tablas Prefedifinidas Efectos Texto Formatos grficos Perifricos Optimizaciones Herramientas Cursos Links Foro

Hasta el momento para colocar texto en la pantalla solo utilizbamos las funciones que nos proporcionaba nuestro compilador. Ahora veremos como colocar texto en modo grfico. Podemos colocar texto normal, en negrita, sombreado, cursiva, etc., sin el uso de la BIOS, ni funciones del compilador. El mtodo que veremos se basa en la lectura de los caracteres que hay almacenados en la memoria ROM, pero como veremos ms adelante, tambin los podremos cargar de un archivo utilizando fuentes de 8x16 (los archivos *.fnt que ocupan 4096 bytes=8*16*256). Y como veremos y hemos dicho antes, a estas les podremos aplicar diferentes efectos. La tabla de caracteres se encuentra el la posicin F000:FA6A, aqu es donde comienza la descripcin de los 256 caracteres. Cada uno esta representado mediante 8 bytes. Cada byte es el patrn o mascara de una fila. Por ejemplo, si tenemos la letra 'A', sus 8 bytes seran 56, 108, 198, 198, 254, 198,198, y 0, como se muestra en la figura:

Para descomponer los diferentes bits de un byte, es decir para saber si va un pixel o no, lo que haremos ser utilizar la operacin lgica >>. Lo que hace esta operacin es rotar un bit n posiciones a la derecha (lo cual corresponde a una divisin), as si queremos saber que valor hay en el bit nmero 3 de menor peso del nmero 53 (empezando por el de ms a la derecha), haremos la siguiente operacin:
53 AND (256 SHR 3)

Esto nos dar un 1 o por el contrario, un cero, depende de si dicho bit esta encendido o apagado. Ahora crearemos nuestra primera rutina, la cual colocar solo un carcter en la pantalla. Pero para esto necesitamos un puntero a la tabla de caracteres de la ROM:
unsigned char *Font8x8=(unsigned char *) 0xF000FA6E;

De esta forma utilizando el puntero Font8x8, podremos acceder a cualquiera de los 256 caracteres disponibles.

Para poder representar estos bytes adecuadamente en la pantalla, tendremos que irnos a la posicin correspondiente de las 256 e ir descomponiendo todos los bits de cada byte, e ir dibujando slo los que den 1, a medida que cambiemos de byte, aumentaremos la posicion Y en uno, ya que es el que corresponde a una lnea ms abajo en pantalla. Veamos como queda:

void WriteChar8x8(int x, int y, unsigned char car, unsigned char color, unsigned char *where) { register int linea, pos; for(linea=0; linea<8; linea++) for(pos=0; pos<8; pos++) if(Font8x8[(car<<3)+linea] & (256>>pos)) where[((y+linea)<<8)+((y+linea)<<6)+x+pos]=color; }

Para dibujar una cadena entera, lo que haremos ser, antes de entrar en el bucle encargado de dibujarnos el carcter obtener el tamao de la cadena. En C eso lo podemos hacer mediante la funcin strlen(). Como sabemos el tamao de la cadena, slo tendremos que ir dibujando carcter a carcter, teniendo en cuenta que tendremos que aumentar en 8 la X cada vez que dibujemos un carcter.

void Write8x8(int x, int y, unsigned char *cadena, unsigned char color, unsigned char *where) { register int i, linea, pos; int longitud=strlen(cadena); for(i=0; i<longitud; i++) for(linea=0; linea<8; linea++) for(pos=0; pos<8; pos++) if(Font8x8[(cadena[i]<<3)+linea] & (256>>pos)) where[((y+linea)<<8)+((y+linea)<<6)+x+pos+ (i<<3)]=color; }

Si queremos hacerlo con cursiva, lo nico que tendremos que hacer es ir desplazando X en n-1 posiciones cada vez, de

manera que cuando Y sea igual 8, n sea igual 1, o sea, a X le sumaremos 0 y as quedar como estaba, vamos a ver esto con ms detalle, la siguiente funcin coloca texto en cursiva:

void WriteCur8x8(int x, int y, unsigned char *cadena, unsigned char color, unsigned char *where) { register int i, linea, pos; int longitud=strlen(cadena); x+=6; for(i=0; i<longitud; i++) for(linea=0; linea<8; linea++) for(pos=0; pos<8; pos++) if(Font8x8[(cadena[i]<<3)+linea] & (256>>pos)) where[((y+linea)<<8)+((y+linea)<<6)+x+pos+(i<<3)linea]=color; }

Si lo que queremos es dibujar con sombra, la funcin necesitar dos valores; el color del texto y el color de la sombra. Ahora lo nico que deberemos hacer es dibujar con la funcin encargada de escribir un texto que vimos antes, Write8x8(). Primero colocaremos la sombra, pero desplazando X e Y una posicion y luego el texto con el color correspondiente.

void WriteShadow8x8(int x, int y, unsigned char *cadena, unsigned char color, unsigned char c_shadow, unsigned char *where) { Write8x8(x+1, y+1, cadena, c_shadow, where); Write8x8(x, y, cadena, color, where); }

Tambin podramos crear texto en negrita, esto es muy simple. Veamos:

void WriteNeg8x8(int x, int y, unsigned char *cadena, unsigned char color, unsigned char *where) { Write8x8(x, y, cadena, color, where); Write8x8(x+1, y, cadena, color, where);

Normalmente el tamao de 8x8 se nos hace demasiado pequeo para los modos SVGA que se usan hoy en da (como mnimo 640x480). Existen otro tipo diferente de letras, estas son el doble de altas que las anteriores. Estos tipos de fuentes pueden ser cargadas desde unos archivos de extensin *.fnt. Estos archivos contienen varios tipos diferentes de fuentes que podrn ser retocadas con alguno de los muchos diferentes tipos de programas que hay para esto, as que se hacen ideales para utilizar en juegos, demos, etc. Estos archivos no son nada ms que caracteres de tamao 8x16 almacenados de la misma forma que los de 8x8, pero en vez de estar en memoria, estos se encuentran en un archivo. Los primeros 16 bytes de estos archivos correspondern al carcter 0 de la tabla ASCII, y as recorriendo todas sus posiciones hasta llegar al byte nmero 4096 (256*16). La forma de trabajar con estas, no difiere en nada con las anteriores (8x8), pero ahora deberemos de tener en cuenta que son fuentes de 16 pixeles de alto en vez de 8. Para trabajar con estos archivos crearemos un nuevo tipo de dato:
typedef unsigned char t_font[4096];

De esta forma cuando queramos cargar alguna fuente en especial, simplemente crearemos una variable del tipo t_font. Una rutina que cargue estos archivos puede ser la siguiente:

int LoadFont8x16(char *filename, t_font font) { enum{ARCHIVO_NO_ENCONTRADO=0, OK=1}; FILE *f; if((f=fopen(filename,"rb"))==NULL) return ARCHIVO_NO_ENCONTRADO; fread(font, sizeof(char), 4096, f); fclose(f); return OK; }

La funcin para escribir cadenas de texto, pero utilizando caracteres de 8x16 es anloga a la que se hizo para las de 8x8. Ojo que la funcin tambien necesita como parmetro una

variable de tipo t_font. Veamos entonces como nos quedara esta funcin:

void Write8x16(int x, int y, unsigned char *cadena, unsigned char color, t_font font, unsigned char *where) { register int i, linea, pos; int longitud=strlen(cadena); for(i=0; i<longitud; i++) for(linea=0; linea<16; linea++) for(pos=0; pos<8; pos++) if(font[(cadena[i]<<4)+linea] & (256>>pos)) where[((y+linea)<<8)+((y+linea)<<6)+x+pos+ (i<<3)]=color; }

Las dems funciones para trabajar con fuentes de 8x16, al igual que todas las rutinas que ya vimos, las podrn encontrar en una librera llamada write.h Ejemplos Librera grfica: vgalib.h Librera texto: write.h

Colocando texto en pantalla de tamao 8x8 y 8x16, con diferentes estilos Un scroll de texto

Formatos Grficos
Hardware Modo 13h Retrazado Vertical Primitivas La Paleta Pantallas Virtuales Sprites Tablas Prefedifinidas Efectos Texto Formatos grficos Perifricos

Muchas veces en lugar de dibujar en la pantalla utilizando instrucciones de lneas, crculos etc., simplemente queremos cargar una imagen desde un archivo. Existen muchos formatos de imgenes, entre los que destacan los siguientes: BMP, GIF, PCX, TIF, TGA, JPG y muchos otros. Ahora veremos como cargar imgenes en distintos formatos desde un archivo. De esta forma, por ejemplo podremos cargar un fondo para una demo, juego, etc.

Optimizaciones Herramientas Cursos Links Foro

Formato crudo Formato PIC Formato CEL Formato PCX

Optimizaciones A continuacin podrs encontrar algunas rutinas bsicas para la programacin grfica en modo 13h optimizadas en ensamblador.

Hardware Modo 13h Retrazado Vertical Primitivas La Paleta Pantallas Virtuales Sprites Tablas Prefedifinidas Efectos Texto Formatos grficos Perifricos Optimizaciones Herramientas Cursos Links Foro

Cambia a un nuevo modo de video

void SetMode(char mode) { asm xor ah, ah asm mov al, mode asm int 10h }

Borra una pantalla virtual de un color determinado


void Cls(BYTE color, BYTE *where) { asm les di, [where] asm xor di, di asm mov al, [color] asm mov ah, al asm mov dx, ax asm db 0x66 asm shl ax, 16 asm mov cx, 16000 asm mov ax, dx asm db 0xF3, 0x66, 0xAB }

Coloca un pixel en una pantalla virtual


void PutPixel(int x, int y, BYTE color, BYTE *where) { asm les di, [where]

asm mov ax, y asm mov di, ax asm shl ax, 8 asm shl di, 6 asm add di, ax asm add di, x asm mov al, color asm mov es:[di], al }

Devuelve el color de un pixel de una posicin determinada de la pantalla


BYTE GetPixel(int x, int y, BYTE *where) { asm les di, [where] asm mov ax, y asm mov di, ax asm shl ax, 8 asm shl di, 6 asm add di, ax asm add di, x asm mov al, es:[di] return _AL; }

Mueve el contenido de una pantalla virtual (64 Kb) a la VGA


void Flip(BYTE *source) { asm push ds asm mov ax, 0xA000 asm mov es, ax asm lds si, [source] asm xor di, di asm mov cx, 16000 asm db 0xF3, 0x66, 0xA5 // rep movsd asm pop ds }

Mueve el contenido de una pantalla virtual a otra


void FlipTo(BYTE *source, BYTE *where) { asm push ds asm les di, [where] asm lds si, [source]

asm xor di, di asm mov cx, 16000 asm db 0xF3, 0x66, 0xA5 // rep movsd asm pop ds }

Cambia las intensidades RGB de un color determinado


void SetColor(BYTE color, char red, char green, char blue) { asm mov dx, 3C8h asm mov al, color asm out dx, al asm inc dx // dx=3C9h asm mov al, red asm out dx, al asm mov al, green asm out dx, al asm mov al, blue asm out dx, al }

Obtiene las intensidades RGB de un color determinado

void GetColor(BYTE color, char *red, char *green, char *blue) { char r, g, b; asm mov dx, 3C7h asm mov al, color asm out dx, al asm inc dx asm inc dx asm in al, dx asm mov r, al asm in al, dx asm mov g, al asm in al, dx asm mov b, al *red=r; *green=g; *blue=b; }

// dx=3C9h

Cambia la paleta actual por una pasada por parmetro

void SetPal(t_paleta paleta) { asm push ds asm lds si, paleta asm mov dx, 3C8h asm xor al, al asm out dx, al asm inc dx asm mov cx, 768 asm rep outsb asm pop ds }

Obtiene la paleta actual


void GetPal(t_paleta paleta) { asm les di, paleta asm mov dx, 3C7h asm xor al, al asm out dx, al asm add dx, 2 asm mov cx, 768 asm rep insb }

Coloca un sprite en una pantalla virtual. El tamao mximo es de 255x255 En las dos primeras posiciones debe estar el ancho y alto del sprite respectivamente.

void PutSprite(int x, int y, BYTE *sprite, BYTE *where) { asm push ds // Salva los registros de segmento asm push es asm les di, [where] asm lds si, [sprite] asm mov di, y asm mov ax, di asm shl di, 8 asm shl ax, 6 asm add di, ax asm add di, x asm xor dx, dx // es:di direccin de pantalla virtual // ds:si direccin del sprite // Calcula el offset lo cual corresponde // al numero de bytes para llegar al sprite // desde el comienzo de la pantalla

// dx = 0

asm xor bx, bx asm cld asm lodsw guarda en AX asm mov dl, al asm mov bl, ah asm mov cx, bx bucle1: asm mov bx, cx asm mov cx, dx bucle2: asm lodsb asm or al, al asm jz incdi asm stosb asm jmp seguir incdi: asm inc di seguir: asm dec cx asm jnz bucle2 asm sub di, dx asm add di, 320 asm mov cx, bx asm dec cx asm jnz bucle1 asm pop es asm pop ds }

// bx = 0 // Incrementa los desplazamientos // Recupera el primer word de DS:SI y lo // dl = ancho // bl = alto // Al registro de cuenta la altura // Guardamos en bx el registro de cuenta // Al registro de cuenta la anchura // Recupera un byte del sprite // Para ver si es cero // si cero = parte transparente del sprite // Si no es cero lo visualizamos // y volver a empezar... // Nos saltamos este byte // Bucle de la anchura // Nos situamos en la lnea siguiente // Recuperamos contador de altura // Bucle de altura // Recuperamos registros de segmento

Espera a que termine el retrazado vertical de la pantalla


void WaitRetrace(void) { asm mov dx, 03DAh espera1: asm in al, dx asm test al, 08h asm jnz espera1 espera2: asm in al, dx asm test al, 08h asm jz espera2 }

Ahora continuacin podrs encontrar la librera grfica VGALIB.H actualizada con las rutinas que vimos optimizadas en ensamblador.

Antes de compilar la librera, es posible que tengan que activar el cdigo para 80286, esto lo encuentran en los compiladores Borland en: Options | Compiler | Advanced Code Generation | Instruction Set

También podría gustarte