Miguel Ángel Ballesteros bio photo

Miguel Ángel Ballesteros

Maker, using software to bring great ideas to life. Manager, empowering and developing people to achieve meaningful goals. Father, devoted to family. Lifelong learner, with a passion for generative AI.

Email LinkedIn Github
RSS Feed

256.000 colores en tu VGA (ES)

Overview

This was my first publication in a professional magazine: RPP. RPP (Revista Profesional para Programadores) was a state of the art magazine those days in Spain, I bought them every month and read articles from important people like Ricardo Devis Botella.

At that time, I had an expensive PC with a display VGA card… and a lot of time to search the gopher, and usenet of the early Internet (before the WWW existed). I found some articles of Michael Abrash that showed the internals of the VGA…

Using that new knowledge, some of my abundant time, and the TV principles (Red + Green + Blue = any color) I managed to show 256.000 colors in a VGA (that only could show 256!)

Cover and first article page

256.000 colores en tu VGA (article body)

Cuando salió la targeta VGA al mercado, me costaba creer que, tarde o temprano, aquellos 256 colores se quedarían cortos. Han pasado varios años desde entonces. Las técnicas de ray-tracing, mapeado de texturas, etc. se han desarrollado en tal grado, que ya se necesitan capacidades gráficas en color real.

Actualmente la mayoría de los ordenadores PC disponen de targetas gráficas SuperVGA. Estas targetas, sucesoras de la VGA, aportan mayor resolución pero no mayor número de colores. El color es caro, y una targeta que supere la barrera impuesta de los 256 colores es algo que no está al alcance del usuario medio.

Las aplicaciones que requieren un gran número de colores deben optar por dos alternativas: o reducir el espacio de colores de 16 millones a 256 (ver RPP Nº 8: Presentación de colores en los modos VGA) , o aplicar complejos algoritmos de selección óptima de colores. El primero es el más rápido, por lo que tiene aplicación en tiempo real, pero a veces la reducción es demasiado fuerte. El segundo, con aplicación en imágenes estáticas, requiere mucho tiempo de cálculo y, cuando la imagen tiene demasiados colores, no se consiguen resultados todo lo buenos que sería deseable.

Con esta perspectiva, siempre es bienvenido cualquier metodo que solucione la papeleta, aunque este diste de ser perfecto. Como siempre, hay que dar para ganar. Nosotros ganaremos muchísimos colores; solo falta ver si lo que hay que dar compensa.

…si, ¿pero cómo?

Todo color en pantalla se puede descomponer en tres componentes primarias: un poco de rojo, otro poco de azul y algo de verde. Cambiando las proporciones de estos tres colores primarios conseguimos formar todo el espectro de colores.

Cuando escogemos la paleta de la VGA, esta nos permite asignar a cada color, de 0 a 255, tres números de 0-63 cada uno que indican qué‚ cantidad de cada color primario tendrá dicho color. Esto quiere decir que podemos seleccionar un color de entre 64x64x64 = 256.144 colores disponibles. En algunas targetas, se puede seleccionar los colores primarios entre 0-255, por lo que la elección llega hasta los 256^3 = 16.777.216 colores. Esta seleccion nos da la primera llave a los colores, pero hace falta algo mas.

El ojo humano tiene una cierta inercia visual. Esto quiere decir que retiene la impresión de una imagen durante un cierto tiempo. Este hecho hace posible la televisión, y como caso clásico la animación o dibujos animados. En estos, cada fotograma se presenta durante un tiempo lo suficientemente breve como para que la sensacion de movimiento sea perfecta. Esto quiere decir que no podemos apreciar planos intermedios y que añadirlos seria superfluo, pues el efecto ya se ha logrado. La inercia visual nos la la segunda llave a los colores. Solo falta abrir la puerta.

Si somos capaces de hacer llegar tres imagenes en el tiempo en el que el ojo percibe una, y cada una de ellas esta formada por diferentes tonos de un color primario, el ojo no vera los tres colores, sino el color resultante de superponerlos. Esta es la tecnica. Solo resta encontrar el metodo adecuado para llevarla a cabo, y es aquí donde empiezan las dificultades.

Al maximo de colores

De lo dicho se desprende que debemos ser capaces de crear 3 planos de color para cada imagen. Los formatos graficos que soportan color real, como por ejemplo TGA, ya tienen la imagen descompuesta en los tres planos primarios, cada uno de ellos con 256 tonos posibles. Por tanto, debemos centrarnos en como lograr dibujar cada uno de los planos.

Lo ideal sería que la VGA dispusiera de 256x3 colores a seleccionar. Esto nos permitiría asignar los 256 tonos de rojo a los 256 primeros colores, los 256 tonos de verde a los 256 colores siguientes, y los £ltimos para el azul.

Desgraciadamente, solo disponemos de 256 y además solo podemos seleccionar 64 tonos. Con estas limitaciones asignaremos los diferentes tonos de cada color primario en bloques de 64 colores. As¡, por ejemplo, el plano primario rojo lo pintaremos con los colores de 0 a 63, que han sido asignados con todos los de este color.

Esto nos obliga a tomar los 6 bits mas significativos dela imagen a color real para adecuarlo a nuestros 64 tonos.

Maxima velocidad

Presentar los tres planos primarios a la velocidad necesaria para que se perciban como una no es nada sencillo. No basta con copiar una y otra vez las imágenes a la memoria de video. Esto ser¡a realmente lento.

Para conseguir altas velocidades hay que recurrir a la programacion a nivel de registros de la VGA. No es que sea dificil, pero es un tema desconocidísimo para el gran público.

El objetivo de este artículo no es explorar la programación avanzada de la VGA, por lo que solo explicar‚ de ella lo que sea estrictamente necesario y de forma muy simplificada. Además, me restringiré‚ al caso de la VGA.

Lo primero que hay que saber es que cuando programamos en el modo 13h (modo típico a 320x200 y 256 colores) estamos desaprovechando 3/4 partes de la memoria de la VGA.

Aunque resulte sorprendente, el modo 13h es lo que se conoce como un modo encadenado que, de un modo un tanto peculiar, reduce cuatro bloques de 64k a uno solo, que es el que vemos en pantalla.

Si desencadenamos la memoria tendremos a nuestra disposicion unas maravillosas 256k de memoria de video (esto es lo primero que hace el misterioso Modo X).

Otra de las fantásticas posibilidades que ofrece la programacion a nivel de registros es la de seleccionar el bloque, dentro de las 256k, que queremos ver por pantalla. Esta seleccion es casi instantanea, por lo que nos va a permitir lograr las altas velocidades que buscábamos.

En efecto, si situamos los planos rojo, verde y azul en el primer, segundo y tercer bloque de 64k y seleccionamos cíclicamente los bloques, conseguiremos presentarlos con la frecuencia suficiente para que se produzca el efecto de superposicion de colores.

Problemas, soluciones y mejoras

La teoría es bastante simple pero, como es habitual, implementarla no lo es. Imaginemos que tenemos ya los tres planos primarios en la memoria de video.

El primer problema con el que nos encontramos es que si hacemos el cambio de bloque cuando la imagen est a mitad, veremos un salto nada agradable de la imagen. Para evitar esto lo que debemos hacer es esperar a que termine de trazarse la imagen, para no dejarla a mitad, y comience el retrazo vertical.

La funcion WaitRetrace() se encarga de esta tarea.

El segundo problema surge al solucionar el primero. Esperar al retrazo vertical supone que se nos limita la frecuencia de presentacion de imágenes a la frecuencia con la que se refresque la imagen en pantalla.

El modo 13h hace operar a la VGA a 25Mhz, lo que conlleva una presentación de mas de 50 y menos de 70 imagenes por segundo. El ojo no distingue mas de 28 planos por segundo, por lo que para una unica imagen es mas que de sobra. Sin embargo, si presentamos tres planos de color por cada imagen, parece obvio que se necesita poder presentar unos 90 planos por segundo (30 por cada plano primario) de forma que viesemos unas 30 imagenes completas.

Esto se puede solucionar aumentando la frecuencia de trabajo de la VGA. Poniendola a funcionar a 28Mhz el numero de planos por segundo aumenta sensiblemente. Desgraciadamente algunos monitores se desestabilizan a estas frecuencias y hay que rebajar el número de planos que se presentan.

A pesar de todas estas mejoras que le hacemos, no podemos presentar los planos lo suficientemente deprisa. El ojo, que es muy sensible a los cambios de color, percibe el cambio brusco de un color primario a otro, y obtenemos una imagen a saltos muy molesta.

El problema surge debido a que estamos pasando de una superficie grande de, por ejemplo, color rojo a otra superficie grande de, por ejemplo, color verde.

La solución a este problema es mezclar los planos. Cada plano tendrá puntos de los tres colores, pero dispuestos de tal forma que la superposición de los tres planos nos de la misma imagen. Con este simple truco conseguimos reducir las superficies de color.

El lector avispado habrá observado que hay muchas maneras de mezclar los planos. En efecto. Lo ideal sería mezclar los planos de forma que la maxima superficie continua de un color primario se quedara reducida a un único pixel. Esto es, en un plano, cada color primario debería estar rodeado solo por pixels de los otros dos colores. En la práctica esto es imposible.

La aproximación más simple es la de presentar secuencias rojo-verde-azul. El inconveniente es que así siempre quedan lineas continuas de colores primarios que el ojo percibe, aunque muchísimo menos que cuando eran superficies grandes. Como los pixels son cuadrados, la máxima continuidad se da con lineas horizontales y verticales.

Para reducir la continuidad del color, optaremos por lineas diagonales. En ésta, los pixels solo se tocan en una esquina y la continuidad de color es menor. La aproximación de lineas diagonales hace que estas sean visibles y enturbian la imagen ligeramente, aunque los resultados son bastante buenos.

Reto desde aquí a que alguien encuentre una mezcla mas adecuada que de mejores resultados. Me consta que hay alguna disposición que reduce la continuidad a unos pocos pixels, tal vez dos o tres. Pasar de una linea con cerca de cien puntos contiguos a solo unos pocos debe mejorar en gran medida los resultados.

La programación a nivel de registros de la VGA nos permite una mejora adicional. Otra de las particularidades del modo 13h es que sus pixels son aproximadamente cuadrados. Esto es conocido por todos. Lo que la mayoría no sabe es que dichos puntos son puntos dobles. Cuando la VGA pinta un pixel en pantalla realmente está pintando dos, uno encima de otro. El resultado es un pixel cuadrado. Podemos decirle a la VGA que no doble los pixels, con lo que la pantalla pasar a tener unas dimensiones de 320x400. Veremos as¡ el contenido de los dos primeros bloques de 64k pero las imágenes aparecerán achatadas, pues los pixels son ahora rectangulares. Como los pixels han reducido a la mitad su area, la continuidad de color también disminuye. Para que las imágenes no aparezcan achatadas, se dibuja dos veces cada linea de la imagen. Ganamos así calidad a costa de memoria. Como solo disponemos de 256k de memoria, para tener tres planos con esta resolución de pixel rectangular, debemos reducir el tamaño de la imagen a 240x180 puntos (que en pantalla serán 240x360 y que multiplicado por 3 planos es menor de 256k).

Ejemplo de programa

El listado 1 muestra un ejemplo de programa que emplea esta técnica. El programa comienza estableciendo un modo gráfico de 240x180x256k. Es de pequeño tamaño por que trabaja con resolución vertical doble y emplea pixels doblados (que consumen mucha memoria pues la mitad están repetidos).

Continúa poniendo a funcionar un ciclo de animación libre. Esto quiere decir que se muestran las tres pantallas c¡clicamente, pero podemos seguir con el programa (recomiendo a los curiosos que den una ojeada a estas funciones, pues son muy interesantes).

Para ver una muestra de colores dibujamos en pantalla una gama amplia de colores, y entramos en un bucle donde se nos permite modificar la frecuencia con la que se llama a la animación libre. Jugad un poco para ver el efecto.

Para salir del bucle basta con pulsar espacio. Una vez fuera del bucle se desactiva la animación libre y comienza una animación con espera de tecla. Los resultados son mejores en este tipo de animación y es la más adecuada para presentar imágenes estáticas.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
*****************************************************************************
 Listado 1
*****************************************************************************
/*--------------------------------------------------------------------------
   VGA256K1.c - Demostracion de la gama de colores
  --------------------------------------------------------------------------
   OBJETIVO: Ofrecer una muestra de las posibilidades de la libreria.
  --------------------------------------------------------------------------
   En este programa se visualizan un total de 30*30*30=27.000 colores en
   pantalla. Los colores han sido seleccionados de forma que se cubre todo
   el espacio de colores de 64x64x64, quedando sin mostrar tonalidades
   intermedias.
  --------------------------------------------------------------------------*/
#include <conio.h>
#include "vga256k.h"

/*--------------------------------------------------------------------------
   main() - Funcion principal
  --------------------------------------------------------------------------*/
main()
{
	/*--- Variables empleadas ---*/
	unsigned r, g, b, Tecla, Frec;

	/*--- Establecemos el modo grafico 240x180x256k ---*/
	SetHigh240x180x256k();

	/*--- Activamos la animacion ---*/
	ActivarAnimacion2();

	/*--- Pintamos una muestra de 30 tonos por cada color primario.
	      Empleamos PutPixelHigh256k() para que nos doble los puntos ---*/
	for(b=2;b<32;++b){
	for(g=2;g<32;++g){
	for(r=0;r<32;++r){
	 PutPixelHigh256k( (r%8)*30+b-2, (int)(r/8)*30+g-2, r*2,b*2,g*2);
	}}}

	/*--- Permitimos aumentar o disminuir la frecuencia de refresco ---*/
	Frec=176;
	while(1){
		Tecla=getch();
		if(Tecla==' ') break;
		if(Tecla=='+'){ ++Frec; CambiarPIT(Frec); }
		if(Tecla=='-'){ --Frec; CambiarPIT(Frec); }
	}

	/*--- Desactivamos la animacion ---*/
	DesactivarAnimacion();

	/*--- Otra forma: Animar hasta que se pulse una tecla: la 's' ---*/
	AnimarHastaTecla('s');

       /*--- Esperamos tecla. Restablecemos el modo de texto ---*/
	SetVideoMode(3);

}
*****************************************************************************

Aplicaciones

La aplicación principal es la obvia: presentar imágenes a color casi real.

La capacidad de simultanear tantos colores permite presentar varias imágenes por pantalla con independencia de las paletas de cada una. En general, el procedimiento es útil es todos aquellos programas donde se realiza un uso intensivo de la paleta de colores, o la velocidad de representación impide que esta se pueda cambiar.

Esta técnica llevada a una SuperVGA puede llevar a resultados de gran calidad pues se dispone de mucha mas memoria (1 Mb), mayor resolución, y mayor frecuencia de reloj. Yo mismo lo he probado en una VGA Paradise a 640x400 y los resultados son excelentes.