TEMA 2.7
OTRAS OPERACIONES CON GRÁFICOS





2.7.1 - DEFINIR MARCOS DE VISUALIZACIÓN.
INSTRUCCIÓN VIEW

Un marco de visualización es un mecanismo que tiene QBasic para poder dibujar solamente en una parte de la pantalla sin miedo a meternos en otras partes ya dibujadas y estropear el trabajo ya hecho con instrucciones anteriores. Al definir un marco de visualización se define un rectángulo de manera que QBasic solo podrá dibujar dentro de él. El resto de la pantalla se queda como está y no podrá ser alterado hasta que se defina otro marco de visualización. Esta técnica no es muy utilizada y solo nos va a ser útil en casos muy determinados, pero ya que en la ayuda de QBasic se hace referencia constantemente a esto, vamos a verlo.

Para definir el marco de visualización utilizaremos la siguiente instrucción, similar a LINE.

VIEW (h1, v1)-(h2,v2)

Así definimos un recuadro dentro del cual podremos dibujar sin miedo a salirnos e invadir otras partes de la pantalla.
Vamos con un ejemplo:

SCREEN 12
VIEW (100, 100)-(400, 300)
LINE(0, 0)-(500, 400), 14

Haciendo esto definimos un recuadro imaginario que abarca parte del centro de la pantalla. Después dibujamos una línea que va desde la esquina superior izquierda de nuestro recuadro (Las coordenadas ahora empiezan desde ahí) hasta donde llegue según las dimensiones que les hemos dado, pero en todo caso sólo se dibujará el trozo de línea que cae dentro de nuestro marco de visualización. El resto de la pantalla queda tal cual está.

Si nos liamos con el nuevo origen de coordenadas y queremos seguir usando el normal con el origen en la esquina superior izquierda de la pantalla no hay más que poner la palabra clave SCREEN delante de las coordenadas del marco de visualización, por ejemplo:

VIEW SCREEN(100, 100)-(400, 300)

Así dejamos como estaba el origen de coordenadas, pero siempre se dibujarán solamente los trozos de figuras que entren en el marco de visualización.

Al definir un marco de visualización, las figuras que hubiera ya dibujadas en esa zona se respetan, es como si el marco de visualización fuera transparente. En algunos casos nos puede interesar rellenarlo directamente de un color. Para hacerlo solo hay que poner el valor de color a continuación de las coordenadas, por ejemplo para que se rellene de azul sería:

VIEW (100, 100)-(400, 300), 1

También podemos aprovechar para dibujar un borde alrededor del marco de visualización. Habría que poner a continuación el color que queremos para el borde, por ejemplo:

VIEW (100, 100)-(400, 300), 1, 14

Así dibujaríamos el interior azul oscuro y un borde amarillo alrededor. Es importante entender que este borde va fuera del marco de visualización, y por lo tanto tampoco podrá ser alterado con las instrucciones de dibujo.

Si no queremos relleno, para conservar lo que ya haya dibujado, pero sí queremos el borde, también se puede hacer. Habrá que respetar la posición del color de relleno, pero sin poner ningún número, aunque parezca extraño se pueden poner dos comas juntas:

VIEW (100, 100)-(400, 300), , 14

Para usar la pantalla completa como marco de visualización es muy sencillo, simplemente:

VIEW

En esta instrucción no podemos usar la palabra clave STEP para indicar coordenadas relativas.





2.7.2 - DEFINIR UN SISTEMA DE COORDENADAS PERSONALIZADO

Hasta ahora, para definir las posiciones donde si dibujaban las figuras usando las instrucciones de dibujo hemos usado un sistema de coordenadas en el que la coordenada superior derecha de la pantalla (o del marco de visualización actual) era la (0, 0) y la de la esquina inferior izquierda dependía del modo de pantalla (podía ser (639, 479) para SCREEN 12 o (319, 199) para SCREEN 13).

Este sistema es extremadamente cómodo porque cada unidad representa a un pixel de la pantalla, pero en algunos casos nos puede interesar definirnos otro sistema de coordenadas donde cada unidad represente a más o manos de un pixel.

Supongamos que queremos que cada unidad represente a 64 pixeles en horizontal y a 48 en vertical. Sería algo como dividir la pantalla en una cuadrícula de 10 por 10 cuadros. Si estamos en SCREEN 12 habría que hacer...

WINDOW (0, 0)-(10, 10)

De esta forma la coordenada INFERIOR IZQUIERDA de nuestra pantalla pasa a llamarse (0,0) y la SUPERIOR DERECHA pasa a ser la (10,10) Es importarse darse cuenta que se ha cambiado superior por inferior, pero no izquierda por derecha.

Hecho esto, si ejecutamos la instrucción...

PSET (5, 5)

...se dibujará un punto nada más y nada menos que en el centro de la pantalla, que tiene las coordenadas (5,5) con nuestro nuevo sistema de coordenadas.

Si nos liamos con el cambio de arriba por abajo es mejor usar la palabra clave SCREEN a continuación de WINDOW. De esta forma la primera coordenada corresponde a la esquina superior derecha y la segunda a la inferior izquierda, como hemos hecho hasta ahora. Sería, por ejemplo...

WINDOW SCREEN (0, 0)-(10, 10)

Para volver a dejar el sistema de coordenadas original de nuestro modo de pantalla bastaría con usar la instrucción WINDOW sin nada más.

WINDOW

Esto de los sistemas de coordenadas personalizados casi nunca nos será de utilidad, ya que es más cómodo trabajar directamente con el original de cada modo de pantalla que va relacionado directamente con el número de píxeles. De todas formas nos va a servir para entender mejor el concepto de TWIP que es la unidad que utiliza Visual Basic por defecto para trabajar con las coordenadas de los gráficos. De todas formas allí también es sencillo pasar a trabajar con píxeles directamente y olvidarse también de los TWIPS que no son más que un sistema de coordenadas personalizado definido por el propio Visual Basic.





2.7.3 - COPIAR Y PEGAR.
INSTRUCCIONES GET Y PUT

En todos los programas de dibujo hay procedimientos para copiar un fragmento de imagen y pegarlo por la pantalla todas las veces que queramos. Nuestro lenguaje de programación no va a ser menos y nos permite, con algunas limitaciones, capturar con una instrucción un área rectangular de nuestra pantalla gráfica y después pegarla una o más veces por la pantalla usando otra instrucción.

En Windows, por ejemplo, para todas las operaciones de copiar y pegar se utiliza una zona especial de la memoria llamada portapapeles que es controlada por el sistema operativo y nosotros únicamente nos tenemos que limitar a meter y sacar de allí todo lo que queramos. En nuestros programas de QBasic no hay portapapeles ni nada que se le parezca, y por lo tanto somos nosotros los encargados de "construir uno". Para hacerlo tendremos que declarar un vector de tipo entero de tamaño suficiente para almacenar temporalmente los fragmentos de imágenes que queremos copiar y pegar.

La instrucción que usaremos para "copiar" un trozo de pantalla es esta...

GET (h1, v1)-(h2, v2), nombreVector

Esta instrucción tiene el mismo nombre que la utilizada para sacar información de los ficheros de registros de acceso directo o aleatorio, pero al usarla con esta sintaxis, ya QBasic se da cuenta que es para trabajar con gráficos.
Como se puede ver, tiene una sintaxis muy parecida a la instrucción LINE. Lo que hace es coger el trozo de pantalla delimitado por el recuadro que va desde la coordenada (h1, v1) a (h2, v2) y almacenarlo en el vector que le pasemos, que deberemos haber declarado previamente y disponer de tamaño suficiente. El contenido de la pantalla no sufrirá ningún cambio.

Esto parece muy sencillo, pero hay varias cosas que hay que tener en cuenta. Lo más importante es que el tamaño del vector debe ser suficiente. Para calcularlo exactamente se puede usar la fórmula que viene en la ayuda en pantalla de QBasic en el tema "Matrices para imágenes de pantalla y compatibilidad". Recordemos que en la ayuda de QBasic llaman matrices a todos los arrays, ya sean de una, dos o más dimensiones. En nuestro caso lo que nos interesa es un vector de enteros con una sola dimensión.

Como esta fórmula está muy liada podemos hacernos una idea aproximada del tamaño del vector sabiendo que si en el modo de pantalla 13 hay 256 colores, cada pixel ocupa un byte. Bastará con calcular el área del cuadro que vamos a copiar (altura por anchura) y añadirle siempre unos 256 bytes más. En el modo 12, como sólo hay 16 colores, cada pixel ocupa en memoria 4 bits, que es un nibble o medio byte y por lo tanto el tamaño requerido para el vector será la mitad que en el caso anterior, siempre añadiremos algo más. Con un ejemplo que haremos más adelante esto quedará más claro.

Ya hemos visto como "copiar" la imagen a "nuestro portapapeles". Ahora vamos a ver como "pegar" en la pantalla lo que acabamos de copiar. Habrá que utilizar otra instrucción distinta, que es esta...

PUT (h, v), nombreVector, operacionPegado

Esta instrucción , que también se llama como la de escribir en ficheros pero que nunca se va a confundir, lo que hace es dibujar en la pantalla, a partir de la posición (h, v) hacia la derecha y hacia abajo, el fragmento de imagen que hayamos almacenado previamente en el vector con una instrucción GET.

Lo de operaciónPegado se refiere a una palabra clave que habrá que poner al final para decirle a QBasic como tiene que dibujar los puntos que componen nuestra imagen. Si esta palabra es AND, OR o XOR, los puntos de la imagen que estamos pegandose combinarán con lo que ya hubiera dibujado en esa parte de la pantalla según estas operaciones lógicas. Si la operaciónPegado es PSET los puntos se dibujarán de forma normal borrando en todo caso lo que hubiera detrás. Si usamos PRESET los puntos se dibujarán borrando lo que hubiera detrás, pero en este caso en vídeo inverso. Lo normal es utilizar siempre PSET. Si no lo ponemos, QBasic creerá que estamos queriendo utilizar XOR, cosa que nos puede confundir.

Vamos con un ejemplo de como conseguir que un trozo de imagen que dibujemos en la pantalla en modo gráfico, usando por ejemplo la orden DRAW, se pueda copiar por toda la pantalla sin tener que repetir la correspondiente orden DRAW en cada caso.

Como copiar partes de la pantalla a otra posición usando las instrucciones GET y PUT
'declaramos un vector
DIM portapapeles(1 TO 400) AS INTEGER
' activamos la pantalla gráfica y dibujamos algo
SCREEN 12
CIRCLE (10, 10), 8, 14
DRAW "c9bm4,10e6d3r6d6l6d3h6"
'capturamos lo que haya dentro de un recuadro de 20x20 pixels
GET (0, 0)-STEP(19, 19), portapapeles
'pegamos lo capturado tantas veces como queramos con PUT
FOR h = 0 TO 620 STEP 20
   FOR v = 0 TO 460 STEP 20
      PUT (h, v), portapapeles, PSET
   NEXT
NEXT
'y ya está

Como se puede ver, el ejemplo se explica por sí solo, pero de todas formas vamos a verlo con un poco más de detalle y vamos a comentar algunas cosillas.

El resultado de ejecutar este programa sería que dibujamos en la esquina superior izquierda de la pantalla una circunferencia amarilla y dentro una flecha azul. Esto lo copiamos con la orden GET y lo almacenamos en un vector llamado portapapeles que hemos declarado previamente con un tamaño de 400, suficiente, ya que la imagen que vamos a almacenar tiene un tamaño de 20x20puntos y cada punto ocupa en este modo de pantalla 4 bits. Después con PUT pegamos la imagen desde dentro de un bucle para que rellene toda la pantalla. Usamos la operación PSET para que no se combine con el dibujo original que sigue estando en la esquina superior izquierda.

En este caso justo después de GET hemos usado PUT. Pero esto no tiene porqué ser así. Entre medio podemos borrar la pantalla, dibujar otras cosas o hacer lo que queramos, siempre que no modifiquemos los valores almacenados en nuestro vector. Incluso se podría cambiar de modo de pantalla y después volver al original. Hay que tener en cuenta que no podemos copiar algo en un modo de pantalla y después pegarlo en otro distinto que tenga una resolución horizontal distinta.

También es importante darse cuenta que al dibujar con las instrucciones de dibujo que hemos visto anteriormente nos podíamos "salir" de la pantalla sin mayores consecuencias, pero al usar las instrucciones GET y PUT nunca nos podemos salir fuera, ya que se produciría un error de tiempo de ejecución y el programa se detendría.

Para finalizar con estas dos instrucciones, decir que su principal limitación es que el máximo tamaño de los vectores que podemos declarar es de 64 KB (usando subíndices desde -32768 hasta 32767) y por lo tanto queda limitado el tamaño máximo de las imágenes que queremos copiar. De todas formas este tamaño máximo seguramente no lo podamos tampoco alcanzar porque las demás variables y el propio QBasic van gastando más memoria y nos dará un error de memoria agotada si intentamos declarar vectores muy grandes.

Estas instrucciones ya dejan de existir en Visual Basic. Allí, en un entorno totalmente gráfico hay una instrucción que nos permitirá hacer eso con mucha mayor flexibilidad e incluso leyendo la información directamente desde archivos de imágenes normales. En QBasic estas instrucciones tienen utilidad en casos muy sencillos de repetir patrones parecidos al que hemos visto en el ejemplo.





2.7.4 - AVERIGUAR EL COLOR DE UN PIXEL.
INSTRUCCIÓN POINT

Para terminar con los gráficos vamos a ver una cosa muy sencilla, pero extremadamente útil. Se trata de una función que nos va a devolver el color que tiene un punto de la pantalla gráfica.

Esto puede parecer algo que no sirve para mucho, pero como ahora veremos nos va a ser de mucha utilidad para resolver una gran cantidad de problemas relacionados con los gráficos de una forma bastante sencilla, aunque poco eficiente.

Vamos a lo que vamos, y vamos a ver la sintaxis de esta función:

POINT (h,v)

Así de sencillo. Usamos la función dentro de una expresión o a la derecha del operador de asignación y le pasamos como parámetros las coordenadas del punto y nos devuelve el valor de color correspondiente. Una cosa importante es que no podemos usar la palabra clave STEP para indicar coordenadas relativas, por lo tanto algo como...

punto = POINT STEP (10, 10)

...estaría mal y daría un error detectado incluso por el editor al intentar dar un salto de línea.

Si especificamos una coordenada que esté fuera de la pantalla, no pasa nada, simplemente se devuelve el valor -1.

La instrucción POINT nos devuelve el valor del atributo de color del punto que especifiquemos, independientemente de que el color que se representa en la pantalla haya sido alterado por alguna instrucción PALETTE. Esto nos permite por ejemplo convertir en negro varios colores para que coincidan con el fondo, dibujar figuras con estos colores "invisibles" y después detectar su presencia con la instrucción POINT en alguno de los métodos que se describen a continuación, especialmente el de mapas de durezas utilizado ampliamente para la programación de videojuegos.

Esta función es bastante útil en la pantalla de gráficos. Una aplicación interesante puede ser la implementación de una especie de "pantógrafo virtual" con el que iremos copiando punto por punto un área de la pantalla a otra posición con la posibilidad de cambiar el tamaño o los colores de la muestras obtenidas. Esto se hace metiendo dentro de dos bucles FOR anidados la instrucción POINT y una instrucción PSET para dibujar los puntos del mismo color en otra parte de la pantalla. Con este ejemplo vamos a copiar un trozo de la pantalla a otra posición 200 pixeles más a la derecha y con el doble de tamaño.

FOR h = 0 TO 49
	FOR v = 0 TO 49
		c=POINT(h, v)
		PSET(h * 2) + 200, v * 2), c
	NEXT
NEXT

Este método es muy lento, pero para áreas pequeñas nos da grandes posibilidades cambiando todos o alguno de los los colores, no dibujando alguno o haciendo otras operaciones matemáticas. También en este ejemplo aparecerían puntos separados en la nueva ubicación. Podemos arreglar esto usando instrucciones LINE, circunferencias, rectas que vengan de un punto, botones dibujados con varias LINE, etc. Los resultados pueden ser bastante vistosos. Estos son algunos ejemplos de los resultados que se pueden obtener usando esta técnica.

Ejemplos de lo que se puede conseguir trasladando puntos de colores con la instrucción PAINT

Otra aplicación ampliamente utilizada de la instrucción POINT en los videojuegos es lo que se conoce como detección anticipada de color o mapas de durezas. Esto sirve para que por ejemplo en un juego de laberintos no podamos meter a nuestro personaje a través de las paredes. Si no lo hiciéramos así tendríamos que desarrollar una sofisticada estructura de datos con matrices o algún otro algoritmo que puede que en proyectos pequeños no merezca la pena. De esta forma sólo hay que saber que si el fondo por el que se va a mover nuestro personaje es siempre negro, por ejemplo, a todas las partes de la pantalla que sean de otro color no va a poder entrar. Esto se evita comprobando el color de la nueva posición antes de desplazarnos a ella.

Los mapas de durezas son una representación de la pantalla a menor resolución que el original y de un color que ha sido convertido por la instrucción PALETTE en invisible al coincidir con el fondo. Se usan normalmente en videojuegos sencillos de plataformas para saber exactamente por que parte de los escenarios se puede mover el personaje, donde están las plataformas, etc... trasladando las coordenadas reales de la pantalla a las del mapa de dureza para comprobar los colores sin interferir con los detalles del escenario original.

Dos ejemplos videojuegos, uno con detección anticipada de colores y otro con mapa de durezas

Se pueden ver ejemplos de utilización de estas técnicas de programación y de algunas otras parecidas en la sección de videojuegos terminados de esta página web.

Otra aplicación menos útil de la función POINT es que nos devuelva la posición actual del cursor de gráficos. Como una función sólo nos puede devolver un valor, le tenemos que decir lo que queremos que nos devuelva, con estos números.

POINT(0) Devuelve el valor X (horizontal) de la coordenada actual en la pantalla o marco de visualización activo (Medida en píxeles).

POINT(1) Devuelve el valor Y (vertical) de la coordenada actual en la pantalla o marco de visualización activo (Medida en píxeles).

POINT(2) Devuelve el valor X (horizontal) de la coordenada lógica actual de nuestro sistema de coordenadas personalizado si lo tenemos definido.

POINT(3) Devuelve el valor Y (vertical) de la coordenada lógica actual de nuestro sistema de coordenadas personalizado si lo tenemos definido.

Si no tenemos definido un sistema de coordenadas personalizado (lo más normal), POINT(0) devolverá lo mismo que POINT(2) y POINT(1) lo mismo que POINT(3) ya que siempre estamos contando en pixeles.





2.7.5 - ESCRIBIR TEXTO EN MODO GRÁFICO

En la pantalla gráfica también tenemos la posibilidad de escribir textos usando la instrucción PRINT de la misma forma que hacíamos en la pantalla normal en modo texto, pero nos vamos a encontrar con una serie de inconvenientes que seguramente nos van a hacer de desistir en la mayoría de los casos. Vamos a ver cuales son estos inconvenientes y la forma de evitarlos en la medida de lo posible.

Las pantalla de texto a la que estamos acostumbrada es extremadamente rápida, pero en la pantalla de gráficos todo es más lento. Puede ocurrir incluso que veamos aparecer nuestro texto línea por línea como en las películas si nuestro ordenador tiene en este momento una carga de trabajo más alta de lo normal por culpa de otros programas que esté ejecutando Windows en ese momento.

En las instrucciones de dibujo ya nos hemos acostumbrado a dibujar nuestras figuras exactamente en la posición de pixel que queramos, pero con la instrucción PRINT nos tenemos que acomodar a la línea y columna más próxima al sitio que queramos usando instrucciones LOCATE y puede que esto no siempre nos venga bien para encajar las letras en el sitio justo. Tendremos que adecuar nuestros gráficos al texto y no al contrario.

En la pantalla en modo SCREEN 12 tenemos 80 columnas y 30 líneas para escribir. Los caracteres se dibujan con un tamaño algo más pequeño al del modo texto y con una definición aceptable.

Een modo SCREEN 13 tenemos 40 columnas y 25 líneas. Los textos escritos aquí tendrán un aspecto parecido al del teletexto, con unos caracteres de doble ancho. Como este modo de pantalla es de baja resolución los caracteres aparecerán muy pixelados.

Si llegamos a escribir en las líneas más bajas de la pantalla sin usar un punto y coma al final de las instrucciones PRINT, se producirá el desplazamiento automático arrastrando hacia arriba el texto y también nuestros gráficos.

Como color para las letras podemos usar cualquiera de los que nos permite nuestro modo de pantalla (16 o 256), pero el fondo va a ser siempre y obligatoriamente del color 0 (negro). No se permite el uso de colores intermitentes.
A diferencia de lo que pudiéramos pensar, las letras no se dibujan transparentes sobre lo que ya haya dibujado en la pantalla, sino que siempre van a ir sobre un recuadro negro. Este negro lo podemos cambiar redefiniendo el color 0 con una instrucción PALETTE, pero de todas formas esto es un grave inconveniente si queremos escribir sobre un fondo ya dibujado.

Estos inconvenientes hacen que normalmente los rótulos que aparecen en nuestras pantallas en modo gráfico no se escriban utilizando instrucciones PRINT. Los lenguajes de programación más avanzados incluyen bibliotecas de fuentes y las correspondientes instrucciones de dibujo para escribir utilizándolas, así como funciones que nos permiten averiguar el ancho de los textos y otras cosas. Por norma general estas tipografías no incluyen las letras acentuadas ni la eñe ni el símbolo del Euro.

En QBasic vamos a tener que ser nosotros mismos los que nos construyamos nuestras instrucciones personalizadas para escribir en modo gráfico. En todo caso nos tendremos que molestar en dibujar todos y cada uno de los símbolos que vamos a querer escribir (Letras mayúsculas y minúsculas, números, signos de puntuación y matemáticos, caracteres especiales, etc...) Hay dos posibilidades:

La definición de los puntos o trazos que componen cada carácter quedará almacenada en un vector para ser leída cada vez que sea necesario. Para llenar el vector se harán asignaciones directas de literales o bien se obtendrá la información de un fichero secuencial que leeremos al principio del programa. En la sección de esta web dedicada a programas gráficos y videojuegos se pueden ver ejemplos de utilización de tipografías de ambos tipos.













CuRSo De iNTRoDuCCióN a La PRoGRaMaCióN CoN QBaSiC
© 2004 Juan M. González