Como antes referenciamos, la tarjeta de vídeo mantiene un registro
con 256 celdillas que conforman el DAC de vídeo. En cada
una de esas celdas se guarda el valor de un color determinado por la intensidad
de sus tres componentes primarias: rojo, azul y verde.
Cada uno de esos valores RGB (Red-Green-Blue) se encuentra en
un rango de 0 a 63. De esta forma, el color de un rojo brillante podrá
ser obtenido estableciendo el valor R a 63, el valor G a 0 y el valor B
a 0. Esto permite que dos colores puedan ser exactamente el mismo, pero
almacenados en distintas posiciones.
Resumanos lo que sabemos hasta ahora sobre la paleta:
-
Es un registro con 256 celdas donde se guardan tres valores distintos:
R, G y B, comprendidos entre 0 y 63.
-
Cada valor almacenado en la memoria de vídeo referencia a una de
estas celdillas, y la tarjeta de vídeo se encarga de leer estas
ternas por color y poner el color adecuado en cada píxel de la pantalla.
Ahora hay que saber cómo se pueden acceder a esas celdas y qué
se puede hacer con la paleta para conseguir algún efecto que merezca
la pena.
Para leer un valor de la paleta, el medio que nos ofrece la tarjeta
de vídeo es el puerto.
Si escribimos en el puerto 3C7h el número (0-255) del color,
al leer el valor del puerto 3C9h obtendremos el valor de la componente
R, si volvemos a leer de ese mismo puerto obtendremos el valor de G, y
por último, el de B. La tarjeta de vídeo se encarga del resto.
El procedimiento que lo implementa es:
void Leer1ColorPaleta(BYTE color,BYTE *rojo,BYTE
*verde, BYTE *azul)
{
BYTE char rrojo,vverde,aazul;
asm{
mov dx,0x3c7
mov al,color
out dx,al
mov dx,0x3c9
in al,dx
mov rrojo,al
in al,dx
mov vverde,al
in al,dx
mov aazul,al
}
*rojo=rrojo; *verde=vverde; *azul=aazul;
}
Por el contrario, si lo que queremos es establecer los valores de un
color de la paleta, escribimos en el puerto 3C8h el número del color,
y en el puerto 3C9h vamos escribiendo sucesivamente los tres valores de
R, G y B. De esto se encarga:
void Establecer1ColorPaleta(BYTE color,BYTE
rojo,BYTE verde, BYTE azul)
{
asm{
mov al,color
mov dx,0x3c8
out dx,al
mov al,rojo
mov dx,0x3c9
out dx,al
mov al,verde
mov dx,0x3c9
out dx,al
mov al,azul
mov dx,0x3c9
out dx,al
}
}
Apagar la Paleta: Fade Out
Es un efecto bastante vistoso y elegante que se puede observar continuamente
en cualquier medio audiovisual, ya sea ordenador o televisión. Consiste
en hacer ver que "se va la luz" cuando tenemos algo en pantalla.
Es decir, da la apariencia de que la luz se va apagando poco a poco hasta
ver solamente una pantalla completamente negra.
Para este fin está el procedimiento:
void ApagarPaleta(PALETA paleta,BYTE retardo)
{
asm{
push ds
xor cx,cx
mov ah,retardo
}
SIGUE_APAGANDO:
asm{
mov dx,0x3C8
xor al,al
out dx,al
mov dx,0x3DA
}
ESPERA_FIN_RETR:
asm{
in al,dx
and al,0x8
jnz ESPERA_FIN_RETR
}
ESPERA_INI_RETR:
asm{
in al,dx
and al,0x8
jz ESPERA_INI_RETR
dec ah
jnz ESPERA_FIN_RETR
mov ah,retardo
mov dx,0x3c9
lds SI,paleta
}
OTRO_DAC:
asm{
mov al,[si]
sub al,ch
jge DEC_ROJO
xor al,al
}
DEC_ROJO:
asm{
out dx,al
mov al,[si+1]
sub al,ch
jge DEC_VERDE
xor al,al
}
DEC_VERDE:
asm{
out dx,al
mov al,[si+2]
sub al,ch
jge DEC_AZUL
xor al,al
}
DEC_AZUL:
asm{
out dx,al
add si,3
inc cl
jnz OTRO_DAC
inc ch
cmp ch,00111111b
jl SIGUE_APAGANDO
pop ds
}
}
Este procedimiento necesita ser comentado por su especial concepción.
Como se puede ver, dentro del código aparece la rutina presentada
anteriormente como EsperarBarrido. El motivo de que el código
vuelva a estar aquí de nuevo en lugar de lo aconsejable, que es
hacer una llamada a la función, es por motivos de velocidad.
Cuando el C llama a una función (y cualquier otro lenguaje),
el compilador realiza un cambio de contexto, es decir, guarda todos los
valores actuales y luego realiza la llamada al nuevo procedimiento. Cuando
éste termina, se restauran los valores y se continúa con
la ejecución. Pero esto tiene una desventaja crucial para el objetivo
que nos marcamos, que es el tiempo perdido en ese cambio de contexto. Por
lo que si se incluye aquí el código de nuevo, al no ser excesivamente
largo, conviene hacerlo en lugar de la llamada aconsejable.
El tener que "sincronizar" este procedimiento con el refresco
de la pantalla es para evitar la aparición de la "nieve"
por los desajustes entre actualización de memoria y refresco de
la pantalla anteriormente comentados.
ApagarPaleta necesita que se le suministren dos valores, una
estructura que guarde los valores de la paleta y un valor que indique cómo
de lento se va "apagar" esa paleta.
El valor mínimo es 1, con lo que sólo se "esperará"
a un barrido de pantalla. El máximo viene dado por el tipo de la
variable, (64 por ser de tipo char, pero extendido a 255 por incluir
el especificador unsigned). Un valor aconsejable para transiciones
rápidas es 1, y para las lentas, 3 ó 4. A partir de 5 comienza
a perder fluidez el efecto.
El significado de "apagar la paleta" consiste en que vamos decrementando
los valores RGB de cada celda del DAC mientras no valgan todos 0. Con esto,
poco a poco (aunque rápidamente efectuado), se van oscureciendo
los colores con lo que la apariencia de que se apaga la imagen se consigue.
Encender la Paleta: Fade In
El efecto contrario se consigue con este procedimiento:
void EncenderPaleta(PALETA ,BYTE);
Este procedimiento hace lo contrario que ApagarPaleta. En EncendarPaleta
partimos de la paleta determinada con todos los componentes RGB, de
todos los colores, a 0. Paulatinamente vamos incrementando los valores
del DAC hasta hacerlos coincidir con los valores de la paleta que
le hemos pasado como variable. También se sincroniza con el barrido
de la pantalla y se dispone de una variable para determinar la velocidad
con que ejecutar el procedimiento.
Aparte de estos efectos, se encuentran otros como convertir toda paleta
(con lo que también se convierte la pantalla que se esté
viendo en ese momento) a una tonalidad determinada, como por ejemplo tonos
grises (parecerá que la imagen está en blanco y negro), o
azules, rojos, verdes...