Capítulo 15. Modo texto y modo gráfico. 1. Introducción. 2. El modo texto. 2.1. Modos de salida por pantalla. 2.2. Control de posiciones de pantalla. 2.3. Colores. 2.4. Control de ventanas de pantalla. 2.5. Entrada y salida con colores. 3. El modo gráfico. 3.1. Introducción al modo gráfico. 3.2. Inicialización y cierre del modo gráfico. 3.3. Comprobación de error gráfico. 3.4. Manipulación del modo gráfico. 3.5. Control de ventanas y posiciones. 3.6. Manipulación de los colores. 3.7. Visualización de pixels y figuras. 3.8. Entrada y salida en modo gráfico. 1 1. Introducción. El C++ permite trabajar en modo texto y en modo gráfico. En modo texto la pantalla se divide en una serie de posiciones y cada carácter a visualizar ocupará una de esas posiciones. Se permite acceder individualmente a una posición. En modo gráfico la pantalla se divide en una serie de pixels, que son mucho más pequeños que las posiciones en modo texto. Un carácter ocupará un conjunto de pixels. Se permite acceder de forma individual a un pixel. 2. El modo texto. 2.1. Modos de salida por pantalla. Para visualizar datos por la pantalla, el C permite usar dos métodos, directamente a la RAM de la tarjeta gráfica o a través de llamadas a la BIOS del sistema. Existe la variable global directvideo, predefinida en conio.h, que por defecto vale 1, indicando que se escriba directamente a la RAM gráfica. Si se establece a 0, las salidas por pantalla se hacen a través de llamadas a la BIOS. Con el valor 0 se asegura la compatibilidad con mayor diversidad de sistemas. Para usar el valor 1, se debe estar seguro que existe compatibilidad 100 % con sistemas PC-IBM. Por otra parte, la tarjeta gráfica tiene una memoria ROM que contiene la forma (los pixels que deben iluminarse) de cada carácter ASCII, para trabajar en modo texto. La tarjeta recibe del procesador el código ASCII del carácter, consulta en la ROM su forma y visualiza los pixels correspondientes. La tarjeta gráfica tiene además una memoria RAM donde mantiene la información mostrada en pantalla, para redibujarla unas 50/60 veces por segundo (50/60 Hz). En esta memoria RAM existe una celda para cada posición de pantalla, por ejemplo un array de 80x25 posiciones. Cada posición ocupa 2 bytes. En el primer byte se almacena el código ASCII del carácter y en el segundo los atributos, es decir el color de texto, de fondo e si la intermitencia está activada o no. 2.2. Control de posiciones de pantalla. Se puede trabajar en 6 modos de texto distintos, utilizando 6 constantes numéricas predefinidas (en conio.h) para ello: BW40 C40 BW80 C80 MONO C4350 40 columnas y 25 filas en blanco y negro. 40 columnas y 25 filas en color. 80 columnas y 25 filas en blanco y negro. 80 columnas y 25 filas en color (valor usado por defecto). 80 columnas y 25 filas en monocromo. En adaptador EGA: 80 columnas y 43 filas en color. En adaptador VGA: 80 columnas y 50 filas en color. Para especificar un modo de trabajo se usa la función textmode(modo). La constante LASTMODE, cuyo valor es –1, se usa para restablecer el modo 2 de texto existente antes de cambiarlo la última vez, de la forma textmode(LASTMODE). Ej. textmode(C4350); La función gotoxy(columna, fila) coloca el cursor en la columna y fila especificados. Las funciones wherex() y wherey() devuelven la columna y la fila respectivamente donde está colocado el cursor. Se suelen utilizar para colocar el cursor en una determinada posición respecto a la posición actual. Ej. gotoxy(wherex(), wherey()+1); //Coloca cursor en la columna actual y en la fila //siguiente a la actual. 2.3. Colores. En el modo texto cada posición de la pantalla tiene 3 atributos que son: el color de texto o primer plano (foreground), el color de fondo (background) y si la intermitencia (blink) está activa o no. Para establecer un color para el texto se utiliza la función textcolor(color). El parámetro color tendrá un valor entre 0 y 15, al cual se le puede añadir el valor 128 para que sea intermitente. Los colores están definidos como constantes numéricas en conio.h (BLACK=0, BLUE=1, BLINK=128, etc.). Los colores de 8 a 15 son los mismos del 0 a 7 pero más claros (alta luminosidad, por ejemplo la constante RED pasa a ser LIGHTRED). Para establecer el color de fondo se usa textbackground(color), donde el parámetro color estará entre 0 y 7, que serán los mismos colores del 0 al 7 que se usan para el texto (RED, BLUE, etc.). Los colores de 8 a 15, o BLINK, no se pueden usar para el fondo. Existen las funciones lowvideo(), highvideo() y normvideo(), que afectan sólo al texto, no al fondo. La primera quita la alta luminosidad al color de texto establecido (resta 8), si es que estaba en alta luminosidad. La segunda aplica alta luminosidad al color (suma 8). La tercera restablece los colores al comenzar la ejecución del programa (generalmente texto a LIGHTGRAY y fondo a BLACK). La función textattr() permite establecer un color de texto y otro de fondo en una sóla llamada, en lugar de llamar una vez a textcolor() y otra vez a textbackground(), pero el resultado es el mismo. Como ya se ha comentado, cada posición de pantalla se almacena en 2 bytes: en el primero se guarda el código ASCII del carácter mostrado; en el segundo se guardan los atributos de color de texto, de fondo y la intermitencia. La forma de almacenar esos atributos en esos 8 bits es la siguiente: 3 7 6 5 4 blink back back back 3 text 2 text 1 text 0 text Los bits del 0 al 3 guardan el color de texto, de 0 a 15 (16 colores posibles). Los bits del 4, 5 y 6 guardan el color de fondo, de 0 a 7 (8 colores posibles). El bit 7 guarda el estado de intermitencia. Ej. textattr(5); //5 =0 000 0101 texto=5 fondo=0 Inter=0 textattr(37); //37 =0 010 0101 texto=5 fondo=2 Inter=0 textattr(165);//165=1 010 0101 texto=5 fondo=2 Inter=1 Para no tener que pasar a binario pueden usarse las constantes predefinidas, multiplicando el color de fondo por 16 y sumando la constante BLINK (128) para activar intermitencia. Ej. textattr(BLINK + 16 * RED + BLUE); //Texto azul, fondo //rojo e intermitencia activada. textattr(16*GREEN + RED); //Texto rojo, fondo verde e //intermitencia desactivada. Al establecer nuevos colores de fondo o de texto, los caracteres ya mostrados en pantalla no se ven alterados. Esos colores se utilizan en los caracteres que se van a mostrar después de usar textcolor() y textbackground(). Los colores no son tenidos en cuenta por todas las funciones de salida por pantalla, sólo por aquellas que escriben directamente en pantalla, como cprintf(), cscanf(), cputs(), etc., no a través del stream (buffer de un fichero) asociado al fichero de salida estándar (stdout, que es la pantalla), como las funiones printf(), scanf(), puts(), etc. Las funciones clrscr() y clreol() sí tienen en cuenta los colores. Los colores que están establecidos en un momento dado pueden ser almacenados, para poder restablecerlos más adelante. Esto puede realizarse con la función gettextinfo(), la cual guarda en una variable de tipo estructura (tipo estructura text_info predefinido en conio.h) no sólo los atributos del texto, sino más información. El campo de esa estructura donde se guardan los atributos de colores se llama attribute. Ej. struct text_info ti; //Estructura necesaria para //guardar la información. gettextinfo(&ti); //Guarda la información. La //estructura se pasa por referencia. 4 textattr(ti.attribute); //Restablece los colores //usando el campo ti.attribute. 2.4. Control de ventanas de pantalla. Existen funciones que afectan a trozos, rectángulos o ventanas de la pantalla. La función clrscr() borra la pantalla completa. La función clreol() borra el trozo de la línea desde donde esté colocado el cursor hasta el final, colocando de nuevo el cursor donde estaba antes de borrar la línea. Las funciones insline() y delline() inserta y elimina respectivamente la línea completa de pantalla donde esté el cursor. Este es colocado después donde estaba previamente. La función window(Col_Izda, Fila_Arriba, Col_Dcha, Fila_Abajo) permite definir una ventana dentro de la pantalla, es decir un rectángulo. Una vez definido, las funciones que afectan a la pantalla sólo se aplican sobre ese rectángulo. Por ejemplo, clrscr() sólo borra ese rectángulo, no la pantalla completa. Un gotoxy(1,1) se coloca en la esquina superior izquierda del rectángulo definido. Para volver a usar la pantalla completa se activa un rectángulo que ocupe toda la pantalla, en función del modo que se esté usando. Por ejemplo, window(1,1,80,25) para 80 columnas y 25 filas, o window(1,1,80,50) para 80 columnas y 50 filas. Para conocer el número de filas y columnas del modo texto que se está usando se usará gettextinfo(), que lo dejará grabado en la estructura de tipo text_info, como se ha visto anteriormente. Las funciones gettext(), puttext() y movetext() permiten almacenar el texto mostrado en una zona de pantalla, con gettext(Col_Izda, Fila_Arriba, Col_Dcha, Fila_Abajo, buffer), y mostrarlo después, con puttext(Col_Izda, Fila_Arriba, Col_Dcha, Fila_Abajo, buffer), ya sea en la misma posición en pantalla o en una diferente. Cada posición de pantalla ocupa 2 bytes, en el primer byte se almacena el carácter y en el segundo sus atributos. Por tanto, para usar estas funciones debe declararse un array de tipo char con el doble de tamaño que el número de caracteres que se va almacenar. Ej. char buffer[200]; //Caben 100 caracteres de pantalla. gettext(3,5,27,8,buffer); //25 columnas (de 3 a 27) y 4 filas (de 5 a 8). //25x4 = 100 posiciones de pantalla ... puttext(5,7,29,10,buffer); //Muestra lo guardado en otra zona de pantalla. Una vez almacenada una ventana de pantalla con gettext() en un array como buffer, podrán modificarse las posiciones impares de dicho array, con lo cual se modificarán los atributos de los caracteres almacenados. De esta 5 forma, el posterior puttext() mostrará la ventana con colores diferentes que los que tenía antes del gettext(). La funcion movetext(Col_Izda, Fila_Arriba, Col_Dcha, Fila_Abajo, Col_Izda_Destino, Fila_Arriba_Destino) mueve el rectángulo de pantalla delimitado por Col_Izda, Fila_Arriba, Col_Dcha y Fila_Abajo a otra zona de pantalla, cuya esquina superior izquierda queda indicada por Col_Izda_Destino y Fila_Arriba_Destino. 2.5. Entrada y salida con colores. La función cprintf() es un printf() con pequeñas diferencias. La primera es que envía la salida directamente a pantalla, sin pasar por el stream o buffer del fichero de salida estándar (stdout, que es la pantalla), como sí hace printf(). Esto implica que cprintf() realiza la salida a pantalla teniendo en cuenta los colores activos, mientras que printf() no. Además cprintf() atiende a la ventana activa, definida con la función window(), es decir se ajusta a dicha ventana, lo cual no ocurre con la función printf(). Por otra parte, el carácter ‘\n’, que para printf() se traduce como un CR+LF, para cprintf() sólo es LF (ASCII 10). Para hacer un salto de línea con cprintf() debe escribirse “\r\n...”, o sea CR+LF (ASCII 13 y 10). El cscanf() es como scanf() pero lee directamente de la consola, en lugar de pasar por el stream o buffer del fichero de entrada estándar (stdin, que es el teclado), el teclado. Las funciones getch() y getche() también leen directamente de la consola. Esto significa que utilizan los colores que estén activados, algo que no hacen las funciones scanf() y getchar(), ya que leen a través de stdin. Por tanto, con cscanf(), getch() y getche() no tiene sentido utilizar después fflush(stdin). Las teclas que provocan fin de almacenamiento en la variable de memoria son las mismas en cscanf() que en scanf(), por ejemplo pulsar un espacio en blanco cuando el tipo es %s, una letra cuando el tipo es %d, pulsar 3 dígitos con el tipo %2d, pulsar 5 caracteres con el tipo %5s, etc. Es decir el almacenamiento finaliza cuando se pulsa una tecla que no cumple con el tipo especificado. Pero mientras scanf() permite que se siga tecleando hasta pulsar ENTER, dejando en el buffer de teclado los datos no almacenados en la variable, la función cscanf() no permite que se siga tecleando, sino finaliza su ejecución y el programa sigue con la sentencia siguiente, aunque no se haya pulsado ENTER. Obviamente, cscanf() no deja ningún carácter en el buffer, porque no lo usa, pero el carácter que finaliza tanto el almacenamiento como el tecleo o entrada puede ser recogido con un getch() que se coloque después del cscanf(). Esto significa que si se usa un getch() después del cscanf() para que produzca una pausa en la ejecución del programa, no se realizará dicha pausa, ya que ese getch() recoge el carácter (si es ENTER recoge un 13) que produjo el fin del cscanf(). Por tanto, para realizar dicha pausa deberán ponerse dos getch() después de un cscanf(). Podría escribirse por tanto: 6 scanf(...); fflush(stdin); cscanf(...); getch(); //Este getch() no hace pausa. La función cscanf() almacena correctamente el carácter fin de cadena para el tipo %s. Pero sí debe tenerse en cuenta un detalle, el tipo %[^\n] provoca que el espacio en blanco no finalice la entrada, sino que se almacene correctamente en la cadena, pero con ese tipo no finaliza la entrada al pulsar ENTER, sino que se guarda en la cadena el carácter 13. Ello se debe a que para el cscanf() el \n es el ASCII 10, no 13. Como el carácter \r es el 13, se debe usar el tipo %[^\r]. Por otro lado, tanto scanf() como cscanf() no finalizan nunca si la primera tecla pulsada es ENTER con un tipo que no sea %[^\n] (o %[^\r] para cscanf()), por ejemplo con %s, %d, etc. Simplemente con scanf() se ejecuta en pantalla un CR+LF, y con cscanf() no se ejecuta nada en pantalla. 3. El modo gráfico. 3.1. Introducción al modo gráfico. Hasta ahora se ha estado trabajando en modo texto, en el cual cada carácter visualizado ocupa una posición de la pantalla. El número de columnas puede ser 40 u 80, mientras que el número de filas puede ser 25, 43 o 50, en función del modo establecido con la función textmode(). En modo texto se permite acceso independiente a cada posición de consola, a la que se le puede establecer color de fondo, de texto e intermitencia, así como el carácter que se muestra en dicha posición. Se pueden usar hasta 16 colores diferentes de texto y 8 de fondo. Pero C++ también permite trabajar en modo gráfico, en el que se puede acceder de forma individual a cada pixel de la pantalla. El número máximo de pixels depende de la tarjeta gráfica utilizada. Además se pueden usar muchos más de 16 colores, siempre en función del adaptador gráfico. Al trabajar en modo gráfico, las funciones de modo texto relacionadas con la pantalla no funcionarán correctamente, por ejemplo: gotoxy(), clrscr(), clreol(), textcolor(), textbackground(), printf(), etc. 3.2. Inicialización del modo gráfico. Para trabajar en modo gráfico debe activarse la opción “Graphics Library”, que se encuentra dentro del menú en el apartado “Options” – “Linker” – “Libraries”. Al activar esta opción, el proceso de enlace (link) busca automáticamente el fichero .BGI de librería gráfica. Además debe incluirse el archivo de cabecera graphics.h, con la correspondiente sentencia include: #include <graphics.h> Aparte de esas dos acciones, para abandonar el modo texto y comenzar a funcionar en modo gráfico, nuestro programa debe llamar a la función initgraph(). Esta función activa el hardware para trabajar en modo gráfico, cargando de disco a memoria el controlador (driver) gráfico del fichero .BGI 7 correspondiente. Al ejecutarse esta función, se borra la pantalla completa. La sintaxis de esta función es: void initgraph(int *controlador,int *modo,char *ruta); En el parámetro ruta debe indicarse la carpeta (una cadena de caracteres entre comillas dobles) donde se encuentran los ficheros .BGI. Si estos ficheros están en la carpeta actual de trabajo, se puede dejar en blanco, escribiendo sólo las comillas. Debe tenerse en cuenta que al especificar una ruta, la barra invertida debe escribirse dos veces (\\), porque si se escribe una sola vez es considerada como una secuencia de escape (como \n, \a, \t, etc.). En el parámetro controlador se especifica la dirección de memoria de una variable entera (es un puntero). El valor de esta variable indicará el driver a utilizar. Para asignar dicho valor se pueden usar 11 constantes numéricas predefinidas: DETECT CGA MCGA EGA EGA64 EGAMONO 0 1 2 3 4 5 IBM8514 HERCMONO ATT400 VGA PC3270 6 7 8 9 10 Se suele usar la opción DETECT, con la que el programa autodetecta el tipo de adaptador gráfico del sistema. En el parámetro modo se especifica la dirección de una variable entera, cuyo valor indicará el modo gráfico a utilizar, es decir el número de pixels y de colores. Si el controlador se ha establecido a DETECT, el modo se establece automáticamente al máximo permitido por el adaptador detectado. En este caso, modo puede ser simplemente la dirección de una variable entera en la que no se necesita haber guardado previamente ningún valor. En esa variable quedará grabado el valor entero correspondiente a ese modo máximo después de la llamada a initgraph(). Si no se usa DETECT en controlador, se debe especificar un modo. Para ello pueden usarse unas constantes numéricas predefinidas. Por ejemplo, cuando el controlador especificado es VGA, las constantes para el modo pueden ser: VGALO VGAMED VGAHI 0 1 2 640x200 640x350 640x480 Para utilizar un fichero .BGI suministrado por el fabricante de una tarjeta gráfica podrá usarse la función installuserdriver(). Cuando ya no se necesita trabajar en modo gráfico y se quiere volver a modo texto, se utilizará la función closegraph(), cuya sintaxis es: void closegraph(void); 8 Esta función libera la memoria dinámica ocupada por el controlador gráfico, las fuentes y todo lo relacionado con el sistema gráfico. Ej. #include <graphics.h> ... void main(void) { int controlador = DETECT; int modo;//No es necesario darle un valor, ya que ... //se ha usado DETECT en controlador. initgraph(&controlador, &modo, “C:\\BC\\BGI”); ... closegraph(); } Ej. #include <graphics.h> ... void main(void) { int controlador = VGA; int modo = VGAHI; ... initgraph(&controlador, &modo, “C:\\BC\\BGI”); ... closegraph(); //A partir de esta línea se trabaja en modo texto. ... } Para conocer el adaptador gráfico de nuestro sistema y el modo gráfico de mayor resolución para ese adaptador, puede utilizarse la función detectgraph(). Su sintaxis es: void detectgraph(int *controlador, int *modo) Ej. #include <graphics.h> ... void main(void) { int controlador, modo; detectgraph(&controlador, &modo); //En controlador y modo queda grabado el número de //controlador y máximo modo gráfico del sistema. ... 9 } 3.3. Comprobación de error gráfico. Cada vez que se llama a una función que trabaja en modo gráfico, puede producirse algún tipo de error. La función graphresult() permite comprobar dicha situación. La sintaxis es la siguiente: int graphresult(void); El valor entero que devuelve indicará el tipo de error producido. Este valor puede ser pasado a la función grapherrormsg() para visualizar una cadena que explica el error. La sintaxis de esta función es: char *grapherrormsg(int CodigoError); Ej. #include <graphics.h> ... void main(void) { int controlador = DETECT, modo; int CodigoError; initgraph(&controlador, &modo, “C:\\BC\\BGI”); //Para comprobar si initgraph() ha dado error: CodigoError=graphresult(); if (CodigoError != grOk) //grOk es una cte. { //predefinida que vale 0. printf(“Error:%s”,grapherrormsg(CodigoError)); //Visualiza una explicación del error. getch(); exit(1); } ...//Si no hay error, el programa continúa por aquí } 3.4. Manipulación del modo gráfico. Una vez que se ha inicializado el sistema gráfico con un controlador y un modo gráfico por haber llamando a la función initgraph(), existen otras funciones que permiten conocer el modo que se está usando, así como modificar dicho modo, sin necesidad de cerrar con closegraph() y volver a inicializar los gráficos. La función getdrivername() devuelve el nombre del controlador usado. La sintaxis es: char * getdrivername(void); La función getgraphmode() devuelve un valor entero que se corresponde con el modo gráfico que se está usando. La sintaxis es: 10 int getgraphmode(void); La función setgraphmode() permite establecer un nuevo modo gráfico a utilizar. La sintaxis es: void setgraphmode(int modo); La función getmaxmode() devuelve el máximo modo gráfico, como número entero, para el controlador usado. La sintaxis es: int getmaxmode(void); La función getmoderange() permite conocer el rango de modos válidos que se pueden especificar para un controlador especificado. La sintaxis es: void getmoderange(int controlador,int *ModMen,int *ModMay); La función getmodename() devuelve el nombre que corresponde al modo gráfico cuyo valor numérico se le pase como parámetro. La sintaxis es: char * getmodename(int modo); La función restorecrtmode() permite regresar al modo texto, sin cerrar el modo gráfico. Para volver de nuevo al modo gráfico se realizará una llamada a la función setgraphmode(), pasándole como parámetro el valor devuelto por getgraphmode(). La sintaxis es: void restorecrtmode(void); Ej. #include <graphics.h> ... void main(void) { int controlador = DETECT, modo; int CodigoError; initgraph(&controlador, &modo, “C:\\BC\\BGI”); CodigoError=graphresult(); if (CodigoError != grOk) { printf(“Error:%s”,grapherrormsg(CodigoError); getch(); exit(1); } ... restorecrtmode(); //Vuelve a modo texto. ... setgraphmode(getgraphmode()); //Vuelve a gráficos, //con el modo usado antes de restorecrtmode() ... 11 } La función graphdefaults() restaura los valores gráficos por defecto: activa pantalla completa, coloca cursor en columna 0 y fila 0, color de fondo y texto, la paleta de colores, patrón y color de relleno, la fuente y la justificación de caracteres. Su sintaxis es: void graphdefaults(void); 3.5. Control de ventanas y posiciones. El C proporciona una serie de funciones que permiten el control de la consola a nivel de pixel. Debe tenerse en cuenta que la primera columna de pixels es la 0, al igual que ocurre con la primera fila. Por tanto, esto es una diferencia con el modo texto, en el que la primera columna y fila es la 1. La función que permite borrar la pantalla completa en modo gráfico es cleardevice(), colocando después el cursor en la posición 0,0. Borrar realmente significa rellenar con el color de fondo establecido. Su sintaxis es: void cleardevice(void); La función setviewport() permite definir un rectángulo (ventana) de la pantalla, para que ciertas funciones se restrinjan a él. Una vez definido un rectángulo, la esquina 0,0 será la esquina superior izquierda del mismo, no de la pantalla completa. El cursor será colocado en dicha esquina del rectángulo. La sintaxis es: void setviewport(int CI,int FS,int CD,int FI,int trunca); Los parámetros CI, FS, CD y FI son la columna izquierda, fila superior, columna derecha y fila inferior respectivamente, que delimitan la ventana. El parámetro trunca puede valer 0 para indicar que al visualizar un texto, se muestre completo aunque no quepa en la ventana definida. Si trunca toma un valor distinto de 0, el texto que no quepa en la ventana no se visualizará, es decir que se corta en los límites de la ventana. La función getviewsettings() permite conocer las características de la ventana activada, es decir los límites y el valor de trunca. La función clearviewport() borrará la ventana definida con la función anterior, colocando el cursor en la posición 0,0 de dicha ventana. La sintaxis es: void clearviewport(void); La función moveto() coloca el cursor en una posición de pantalla. Su sintaxis es: void moveto(int x, int y); //x es columna, y es fila. 12 La función moverel() desplaza el cursor desde la posición actual el número de columnas y filas indicadas. Su sintaxis es: void moverel(int x, int y); Las funciones getmaxx() y getmaxy() devuelven la columna y fila máxima respectivamente con el sistema gráfico que se esté usando. Esta columna y fila son relativas a la pantalla completa, no a la ventana activa. La sintaxis es: int getmaxx(void); int getmaxy(void); Por ejemplo, en una pantalla de 640x480, getmaxx() devuelve 639 y getmaxy() devuelve 479. Las funciones getx() y gety() devuelven la columna y la fila respectivamente de la posición donde se está situado en ese momento. Esta columna y fila son relativas a la ventana activa, no a la pantalla completa. La sintaxis es: int getx(void); int gety(void); 3.6. Manipulación de los colores. Para cambiar los colores de los pixels y del fondo en modo gráfico deben usarse funciones diferentes a las usadas en modo texto. Los colores funcionan a través de paletas. Una paleta es un array unidimensional de colores, que son los que se pueden usar en un momento dado. Para poder usar un color que no está en la paleta, primero debe guardarse dicho color en la paleta, para posteriormente ser utilizado. En el modo EGA/VGA, la paleta contiene 16 colores, es decir el array tiene 16 elementos. Cada color tiene asociado un número entero entre 0 y 63. El color de un pixel se especifica con un número de 0 a 15, que será la posición de la paleta, entre los 16 elementos del array, donde está almacenado el número entero que corresponde al color. El color que se almacene en la posición 0 de la paleta será siempre utilizado como color de fondo. La función getpalette() permite conocer la información actual de la paleta, cargándola en una estructura cuyo tipo está predefinido. Este tipo se llama struct palettetype y su formato es: struct palettetype{ unsigned char size; //Nºcolores en la paleta. char colors[ ]; //Array de la paleta. El tamaño }; //depende del modo gráfico. La sintaxis de la función getpalette() es: 13 void getpalette(struct palettetype *paleta); Como puede verse, el parámetro paleta es un puntero a estructura del tipo palettetype. Ej. struct palettetype paleta; //No es puntero, es struct. ... getpalette(&paleta); //Se pasa un puntero a paleta. //En paleta.size queda el número de colores de la //paleta. En paleta.colors[i] quedan los colores que //se están usando (i de 0 a paleta.size – 1). La función getpalettesize() devuelve el número de elementos del array de la paleta. Su sintaxis es: int getpalettesize(void); La función setpalette() permite cambiar un color de la paleta, es decir modifica el valor de un elemento del array de la paleta. Su sintaxis es: void setpalette(int elemento, int color); Ej. setpalette(0, 62); //En el elemento 0 de la paleta //guarda el color 62. Por tanto, //ahora el color de fondo será ese. Al cambiar el color guardado en un elemento del array de la paleta, todos los pixels que tuvieran el color de ese elemento (antes del cambio) se actualizan en pantalla inmediatamente al nuevo color. Por ejemplo, al cambiar el elemento 0 de la paleta, automáticamente se actualiza el nuevo color de fondo en pantalla. La función setallpalette() permite cambiar todos o algunos de los colores de la paleta. Automáticamente se actualizan los pixels mostrados en pantalla a los nuevos colores, por ejemplo si el color blanco se cambia a rojo, todo lo que hubiera en pantalla en blanco se cambia a rojo después de ejecutar setallpalette(). La sintaxis de esta función es: void setallpalette(struct palettetype *paleta); Ej. struct palettetype paleta; ... for (i = 0; i < 16; i++) //Se guardan en el array los paleta.colors[i] = i + 20; //colores del 20 al 35. 14 setallpalette(&paleta); Cuando no se desea modificar un color de la paleta, debe guardarse el valor –1 en el elemento de la estructura paleta, antes de ejecutar setallpalette(). Por ejemplo, paleta.colors[5] = -1, setallpalette() no modificará el elemento 5 del array de la paleta. La función getcolor() devuelve el color de dibujo que está activado en ese momento. Su sintaxis es: int getcolor(void); La función getmaxcolor() devuelve el valor de color más alto que se puede usar en el modo gráfico que se esté usando. Su sintaxis es: int getmaxcolor(void); La función setcolor() permite establecer un nuevo color de dibujo. Su sintaxis es: void setcolor(int colordibujo); El parámetro colordibujo puede valer desde 0 hasta el valor que devuelva getmaxcolor(). El cambio se actualiza inmediatamente en pantalla, por ejemplo si el color de dibujo es blanco y se cambia a verde, todos los pixels de color blanco en la pantalla se mostrarán en verde. La función getbkcolor() devuelve el color de fondo que está activado en ese momento. Su sintaxis es: int getbkcolor(void); La función setbkcolor() permite establecer un nuevo color de fondo, el cual actualiza inmediatamente la pantalla. Esta función modifica la entrada 0 de la paleta usada. Su sintaxis es: void setbkcolor(int colorfondo); La función setrgbpalette() permite la selección de 16 colores de un total de 256 colores con un controlador VGA. Sólo 16 colores pueden utilizarse en un momento determinado. Es decir, si se cambia alguno de esos 16 colores en la paleta, los pixels de pantalla que tuvieran el color que se ha cambiado, automáticamente se actualizan al nuevo color. Su sintaxis es: void setrgbpalette(int NColor,int rojo,int verde,int azul); En el parámetro NColor se indica el número de color al que se le va a dar una mezcla de rojo, verde y azul. Es decir, NColor no es la posición de la paleta. Los parámetros rojo, verde y azul indican los componentes de color a guardar en el color indicado en NColor. Por ejemplo, si el color 63 es el blanco 15 y se ejecuta setrgbpalette(63, 20, 20, 20), ahora el 63 será un tono de gris. Si el color 63 está almacenado en la posición 15 de la paleta, para usar ese tono de gris deberá ejecutarse setcolor(15). Todo lo que hubiera en pantalla en color blanco (63) se cambiará a gris. Ej. struct palettetype paleta; getpalette(&pal); setrgbpalette(pal.colors[15],32,32,32); //El color de la posición 15 de la paleta será gris. setcolor(15); //Activa el nuevo color gris. Los parámetros rojo, verde y azul son de tipo int, es decir que ocupan 2 bytes. Sólo se usa el byte menos significativo, y dentro de ese byte sólo se cargan en la paleta los 6 bits más significativos, por tanto el rango de valores para cada componente de color será de 0 a 63 (con 6 bits se pueden codificar 64 valores distintos). 3.7. Visualización de pixels y figuras. La función putpixel() pinta un píxel con un color. Su sintaxis es: void putpixel(int x, int y, int color); Los parámetros x e y son la columna y fila respectivamente del píxel que se desea pintar, mientras que color es el que se utiliza para pintarlo. La función getpixel() devuelve el color de un píxel. Su sintaxis es: int getpixel(int x,int y); Los parámetros x e y son la columna y fila respectivamente del píxel cuyo color desea conocerse. Ej. putpixel(200, 300, 15); //Pinta de color 15. color = getpixel(200,300); //En color queda un 15. La función getimage() permite grabar en memoria una imagen mostrada en pantalla. Su sintaxis es: void getimage(int CI,int FS, int CD, int FI, void *imagen); Los parámetros CI y CD indican la columna izquierda y derecha respectivamente del tramo de pantalla a grabar. Los parámetros FS y FI indican la fila superior e inferior del tramo de pantalla a grabar. El parámetro imagen debe ser un puntero a una zona de memoria con el tamaño suficiente para almacenar la imagen seleccionada. Para conocer el tamaño en bytes que ocupará una imagen puede utilizarse la función imagesize(), cuya sintaxis es: 16 int imagesize(int CI,int FS,int CD, int FI); Los parámetros CI, CD, FS e FI tienen el mismo significado que en el caso anterior, es decir indican los límites de la imagen. Para volver a mostrar en pantalla una imagen grabada en memoria se utiliza la función putimage(). Su sintaxis es: void putimage(int CI, int FS, void *imagen, int opc); Como puede verse, aquí sólo se especifica la esquina superior izquierda (CI y FS) donde comienza a mostrarse la imagen. El parámetro imagen será el puntero usado en getimage(). El parámetro opc permite cambiar el color de la imagen original cuando se va a mostrar de nuevo con putimage(). Cuando opc es 0, no cambia el color de la imagen original. Ej. void *p; //puntero sin especificar a qué tipo apunta. int nbytes; nbytes = imagesize(10,10,40,50); p = malloc(nbytes); getimage(10, 10, 40, 50, p); putimage(300, 300, p, 0); La función line() dibuja una línea entre dos puntos dados con el color, grosor y estilo de línea establecidos. Se le pasa como parámetros la columna y fila del punto inicial (CI y FI) y del punto final (CF y FF). Su sintaxis es: void line(int CI, int FI, int CF, int FF); La función lineto() dibuja una línea partiendo desde la posición donde esté el cursor hasta un punto cuya columna (CF) y fila (FF) se pase como parámetros. Su sintaxis es: void lineto(int CF, int FF); La función linerel() dibuja una línea partiendo desde la posición donde esté el cursor hasta un punto final. Este punto final se especifica pasando 2 parámetros a la función que son la distancia relativa desde la posición del cursor en columnas (DC) y en filas (DF). Su sintaxis es: void linerel(int DC, int DF); Por ejemplo, si el cursor está en la posición 120,200 y se ejecuta linerel(40,70), se pinta una línea desde el punto 120,200 hasta el punto 160,270, donde 160=120+40 y 270=200+70. La función rectangle() permite dibujar un rectángulo, pasándole como parámetro la columna y fila de la esquina superior izquierda (CI y FS) y de la 17 esquina inferior derecha (CD y FI). Se usará el color, grosor y estilo de línea activos. Su sintaxis es: void rectangle(int CI, int FS, int CD, int FI); La función bar() permite dibujar una barra o rectángulo sin borde, especificando las coordenadas de las esquinas superior izquierda (CI y FS) e inferior derecha (CD y FI). Se rellena con el patrón y color de relleno activos. Si se desea pintar con borde, se puede usar la función bar3d() con profundidad 0. La sintaxis de la función bar() es: void bar(int CI, int FS, int CD, int FI); La función bar3d() permite dibujar una barra en 3 dimensiones, especificando las coordenadas de las esquinas superior izquierda (CI y FS) e inferior derecha (CD y FI), así como la profundidad y si se desea pintar la cara superior. Se rellena con el patrón y color de relleno activos. La línea del borde se pinta con el estilo de línea activo. Como referencia se puede decir que una profundidad razonable es el 25% de la anchura de la barra, es decir (CD-CI)/4. Su sintaxis es: void bar3d(int CI,int FS,int CD,int FI,int Prof, int Cara); El parámetro Cara se usa para pintar la línea correspondiente a la cara superior, cuando vale 1, o para no pintarla, cuando vale 0. Esto permite apilar una barras con otras, pintando sólo la cara superior de la barra más alta. La función arc() permite dibujar un arco. Su sintaxis es: void arc(int x, int y, int AnguloI,int AnguloF, int radio); Los parámetros x e y indican la columna y fila respectivamente del centro del arco. Los parámetros AnguloI y AnguloF indican los ángulos de inicio y final del arco, expresados en grados (de 0º a 360º en sentido contrario a las agujas del reloj). Finalmente el parámetro radio especifica el radio del arco. La función pieslice() permite dibujar un arco y a continuación lo rellena del color y patrón de relleno activos. Su sintaxis es idéntica a la función arc(): void pieslice(int x,int y,int AnguI,int AnguF, int radio); La función circle() permite dibujar un círculo, especificando las coordenadas del centro y el radio. Su sintaxis es: void circle(int x, int y, int radio); Cuando los círculos no se muestran perfectamente redondos, debe usarse la función setaspectratio(). 18 La función ellipse() permite dibujar un arco elíptico, especificando los mismos parámetros que en la función arc(), excepto que se deben indicar dos radios, el radio del ancho de la elipse y el radio de alto. Su sintaxis es: void ellipse(int x,int y,int AI,int AF,int radX,int radY); La función sector() permite dibujar un arco elíptico y después lo rellena con el patrón y color de relleno activos. Se especifican los mismos parámetros que en la función ellipse(). Por tanto, su sintaxis es: void sector(int x,int y,int AI,int AF,int radX,int radY); La función fillellipse() permite dibujar una elipse y rellenarla con el patrón y color de relleno activos. Se le deben pasar las coordenadas del centro y los radios de ancho y de alto de la elipse. Su sintaxis es: void fillellipse(int x,int y, int radioX,int radioY); La función drawpoly() permite dibujar un polígono cualquiera, es decir una serie de líneas unidas, especificando una serie de parejas de puntos, es decir una serie de coordenadas x,y. Cada pareja x,y indica donde finaliza una línea y comienza la siguiente. Su sintaxis es: void drawpoly(int Npuntos, int *poligono); El parámetro Npuntos indica el número de vértices del polígono, es decir el número de parejas de puntos, que será uno más que el número de líneas que forman el polígono. El parámetro poligono es un puntero a la serie de parejas de puntos, es decir a la serie de coordenadas x,y. Para dibujar un polígono cerrado, la última pareja de puntos debe coincidir con la primera pareja y el parámetro Npuntos será uno más que el número de vértices del polígono. Ej. Pintar un exágono (6 líneas y 6 vértices). int poligono[]={300,200, //Vértice superior 320,220, 320,240, 300,260, //Hay 7 parejas de números. 280,240, 280,220, 300,200};//Coincide con vert.superior. drawpoly(7, poligono); //El parámetro poligono es un puntero,ya que es array. La función fillpoly() es idéntica a drawpoly(), excepto que después de pintar el polígono, lo rellena con el patrón y color de relleno activos. La función floodfill() permite rellenar, con el patrón y color de relleno activos, un área cerrada de la pantalla. Su sintaxis es: 19 void floodfill(int x,int y, int color); Los parámetros x e y indican un punto cualquiera del área cerrada, rellenándose desde ese punto, en todas direcciones, hasta encontrar el color indicado en el parámetro color. Si este color no se encuentra, se rellena la pantalla completa. Si el punto x,y está fuera del área cerrada, se rellena el resto de la pantalla. La función setfillstyle() permite especificar un patrón y color de relleno, que afectará a todas las funciones que dibujen relleno. Su sintaxis es: void setfillstyle(int patron, int color); El parámetro patron debe ser un valor de 0 a 11, donde el 0 significa sin relleno (se usa para rellenar con el color de fondo activo). Los valores de 1 a 11 son distintos patrones predefinidos. Si se le pasa un valor erróneo, no se altera el patrón activo. El parámetro color es cualquier posición de la paleta. La función getfillsettings() nos permite conocer el patrón y color de relleno activos. Su sintaxis es: void getfillsettings(struct fillsettingstype *pfill); El único parámetro es un puntero a una estructura de tipo predefinido, struct fillsettingstype, cuyo formato sólo incluye 2 enteros, que coinciden con los 2 parámetros de la función setfillstyle(): struct fillsettingstype { int pattern; int color; }; Ej. struct fillsettingstype relleno; getfillsettings(&relleno); //Se pasa un puntero, la //dirección de la estructura relleno. //En relleno.patron queda el valor del patrón activo. //En relleno.color queda el color de relleno activo. La función setlinestyle() permite especificar el estilo y grosor de línea, afectando a todas las funciones que dibujen líneas. Su sintaxis es: void setlinestyle(int estilo, unsigned int EstiloUsuario, int grosor); Como puede verse, tiene 3 parámetros. El parámetro estilo puede valer de 0 a 3, siendo 0 una línea normal. El parámetro grosor puede tomar sólo dos valores, 1 o 3. El parámetro EstiloUsuario se utiliza cuando estilo vale 4, con el que se permite al usuario definir su propio estilo de línea. 20 La función getlinesettings() permite conocer el estilo y ancho de línea activos. Su sintaxis es: void getlinesettings(struct linesettingstype *plinea); El único parámetro es un puntero a una estructura de tipo predefinido, struct linesettingstype, que consta de 3 campos, los cuales coinciden con los 3 parámetros de la función setlinestyle(): struct linesettingstype { int estilo; unsigned int EstiloUsuario; int grosor; }; 3.8. Entrada y salida en modo gráfico. La función gettextsettings() nos permite conocer las características de visualización de texto que están activas. Estas características son: fuente, dirección de visualización, tamaño de carácter, justificación horizontal y justificación vertical. Su sintaxis es: void gettextsettings(struct textsettingstype *ptext); El único parámetro es un puntero a una estructura de tipo predefinido, struct textsettingstype. Esta estructura tiene 5 campos de tipo int donde se almacenan las 5 características de texto mencionadas previamente. El formato es: struct int int int int int textsettingstype { font; direction; charsize; horiz; vert; } ; El campo font puede tomar un valor de 0 a 4, el 0 indica la fuente normal y del 1 al 4 son fuentes trazadas (“stroked”). El campo direction puede valer 0 para visualizar en dirección horizontal de izquierda a derecha, o 1 para visualizar en dirección vertical de abajo a arriba, o sea girando 90º el texto horizontal (el extremo derecho pasa arriba y el izquierdo pasa abajo). El campo charsize puede tomar un valor de 0 a 10. Para la fuente normal (valor 0 de fuente), un charsize de 0 o 1 indica 8x8 pixels, el 2 es 16x16, el 3 es 24x24, hasta el 10 que es 80x80 pixels. Para las fuentes trazadas (valor de fuente entre 1 y 4), un charsize de 1 a 10 indica el factor de aumento, siendo el valor 4 el factor de aumento por defecto. Cuando charsize vale 0, las fuentes trazadas se visualizan con el tamaño por defecto (factor de aumento 4) o con un tamaño definido por el usuario (ver función setusercharsize()). El campo horiz puede valer 0, 1 o 2, para justificar horizontalmente a la izquierda, en el centro o a la derecha respectivamente. El campo vert puede valer 0, 1 o 2, para justificar 21 verticalmente abajo, al centro o arriba respectivamente. Estas justificaciones se aplican respecto a la posición del cursor, por ejemplo si la justificación horizontal se establece a 1 (en el centro) y el cursor está en el píxel de la columna 100, el texto se visualiza de forma que quede centrado en la columna 100, por lo que se visualiza desde la posición 100 hacia la izquierda la mitad del texto y hacia la derecha la otra mitad. Ej. struct textsettingstype infotexto; //No es puntero. gettextsettings(&infotexto); //Se pasa la dirección //de la estructura, es decir un puntero. //En infotexto.font se graba la fuente activa. //En infotexto.vert se graba la justificación vertical La función settextstyle() permite activar o establecer las 3 primeras características de texto citadas previamente, es decir la fuente, la dirección y el tamaño, que son pasadas como parámetros. Su sintaxis es: void settextstyle(int fuente,int direccion,int tama); La función setusercharsize() permite definir un factor de aumento para las fuentes trazadas (“stroked”, fuente activa entre 1 y 4). Este factor estará activo si el tamaño de fuente (parámetro tama) se ha establecido a 0 con la función settextstyle(). La sintaxis es: void setusercharsize(int multx, int divx, int multy, int divy); Los 2 primeros parámetros, multx y divx, indican el aumento a lo ancho respecto al tamaño por defecto (un charsize de 4). Por ejemplo, si multx vale 2 y divx vale 1, el ancho se multiplica con 2/1 (es decir, por 2), por lo que la fuente será el doble de ancha que el ancho por defecto. Si multx vale 1 y divx vale 3 (1/3), la fuente será 3 veces menos ancha que el ancho por defecto. En cuanto al alto de la fuente se aplica un tratamiento similar. Por ejemplo, si multy vale 4 y divy 2 (4/2=2), la fuente será el doble de alta que el alto por defecto. Para conocer cuántos pixels va a ocupar un texto al visualizarlo en pantalla, pueden usarse las funciones textheight() y textwidth(), que nos devuelven el número de pixels de alto y ancho respectivamente. Para calcular dicho número de pixels, estas funciones tienen en cuenta la fuente y el factor de multiplicación activos. La sintaxis es: int textheight(char *cad); //ALTO int textwidth (char *cad); //ANCHO Como se ve, el parámetro para ambas funciones es un puntero a char, es decir el nombre de la cadena donde está el texto que se quiere visualizar. Ej. int alto, ancho; 22 char cadena=”LISTADO DE ARTICULOS”; alto = textheight(“LISTADO DE CLIENTES”); ancho = textwidth (cadena); Para una fuente y factor de multiplicación activos, todos los caracteres ocupan el mismo número de pixels de alto, mientras que el ancho depende del carácter. Por ejemplo, los caracteres “Ñ” y “_” ocupan lo mismo de alto y los caracteres “i” y “O” no ocupan lo mismo de ancho. La función settextjustify() permite establecer la justificación horizontal y vertical, pasándole para ello 2 parámetros enteros. La sintaxis es: void settextjustify(int JHoriz, int JVert); Los valores posibles para los parámetros JHoriz y JVert son 0, 1 y 2, para justificar horizontalmente a la izquierda, en el centro o a la derecha, y verticalmente abajo, al centro o arriba respectivamente. Las justificaciones por defecto son la horizontal a la izquierda (JHoriz a 0) y la vertical arriba (JVert a 2). La función outtext() permite visualizar una cadena (texto) en modo gráfico, a partir de la posición donde esté el cursor, con las características de texto que estén activas. Cuando se usa justificación horizontal a la izquierda y dirección de visualización horizontal (es decir, los valores por defecto), la posición del cursor avanza automáticamente, en otros casos permanece inalterada, el cursor no se coloca al final de lo visualizado, por lo que se podrían visualizarse unas cadenas sobre otras. Su sintaxis es: void outtext(char *cad); El parámetro cad es la cadena que se desea visualizar. Ej. char cadena[]=”TECLEE “; moveto(100,100); outtext(cadena); //Se le puede pasar un array de char. outtext(“NOMBRE:”); //Se le pasa un mensaje. La función outtextxy() permite visualizar una cadena de texto en una posición de pantalla, pasándole como parámetro la columna, la fila y la cadena. Su sintaxis es: void outtextxy(int col, int fila, char *cad); Para visualizar datos que no son cadenas, como números enteros o reales, pueden convertirse a cadenas utilizando la función sprintf(), que tiene la misma sintaxis que la función printf(), pero en lugar de enviar la información a la pantalla la graba en una cadena. Una vez que se tiene la información en la cadena, podrá visualizarse con outtext() o outtextxy(). La sintaxis de sprintf() es: 23 int sprintf(char *cad, char *control, argumentos); Los dos últimos parámetros (char *control, argumentos) son los mismos que usa la función printf(). En el parámetro cad se graba lo que el printf visualizaría en pantalla. El valor entero que devuelve es el número de caracteres que graba en la cadena. Ej. char cad[80]; int edad = 20; float sueldo = 1200.00; sprintf(cad,”Edad: %2d Sueldo: %7.2f”, edad, sueldo); //En cad queda: “Edad: 20 Sueldo: 1200.00” outtext(cad); La entrada de datos en modo gráfico deberá implementarse controlando cada carácter recogido con getch() y mostrándolo en el formato deseado. 24 EJERCICIOS - CAPITULO 15: Realizar los siguientes algoritmos en pseudocódigo y en C: 1. Programa que dibuja una línea girando. 2. Función para realizar entrada de datos en modo gráfico, es decir como la función scanf() pero con caracteres gráficos. 3. Función que visualiza texto con sombra en modo gráfico. 25