Aula Macedonia


Curso de Programación Multimedia Bajo DOS


Artículo realizado por
José Antonio Suárez.





Capítulo 6.
Programación de tarjetas de vídeo (IV).

2.1.10.- Lectura de Ficheros PCX


El formato gráfico PCX es aquel que utiliza la compresión creada por Zsoft. Este tipo de fichero no alcanza las compresiones realizadas por otros ficheros gráficos como los GIF o los JPG, pero el tiempo que se tarda en descomprimir estos últimos es demasiado elevado. EL motivo por el que el PCX ha sido escogido es porque su compresión es bastante aceptable, acompañado de una elevada velocidad de descompresión. Si quisiéramos usar por ejemplo un JPG de la misma forma en que en se usan los PCX, el retardo sería inaceptable.

El formato PCX se encuentra en casi todos los programas gráficos que existen. En concreto, los usados para este proyecto: DPaint Enhanced, Autodesk 3D Studio y Paint Shop Pro, Photoshop. Otros programas como Corel Draw, Photo Paint o el mismo AutoCad y otros más, soportan este formato, incluido el Paintbrush de Windows, que lo incorpora como estándar junto al BMP.

Un fichero PCX comienza con una cabecera de 128 bytes. En general, esta cabecera no se usa, puesto que el control de los distintos formatos gráficos lo realiza el propio programa, y no el usuario (que podría equivocarse). Esto tiene un motivo: de nuevo la velocidad. Se perdería mucho tiempo si tuviésemos que chequear los valores de la cabecera para comprobar que en efecto es un fichero con la resolución adecuada, el número de bytes correcto...

Tras los 128 bytes de cabecera siguen los bytes de la imagen gráfica. El método usado es la técnica Run-Length-Enconding (RLE) orientada a byte.

La siguiente tabla presenta el contenido de la cabeza de un fichero PCX.
 
Byte Ítem Tamaño Descripción
0 Creador 1 Constante, 10 = ZSoft .pcx 
1 Versión 1 Información de Versión:  
0 = Versión 2.5 de PC Paintbrush.  
2 = Versión 2.8 w/palette: información.  
3 = Versión 2.8 w/o palette: información.  
4 = PC Paintbrush para Windows (además Windows usa la versión 5).  
5 = Versión 3.0 y > de PC Paintbrush y PC Paintbrush +, incluye Publisher´s Paintbrush y ficheros PCX de 24 bits. 
Codificación 1 1 = .PCX run length encoding.
3 Bits por Píxel 1 Número de bits para representar un píxel (por plano) - 1, 2, 4, o 8.
4 Ventana 8 Dimensiones de la imagen: Xmin,Ymin,Xmax,Ymax.
12 HDpi 2 Resolución horizontal de la imagen en DPI .
14 VDpi 2 Resolución vertical de la imagen en DPI.
16 Mapa de color 48 Paleta de colores.
64 Reservado 1 Constante = 0
65 Nº Planos 1 Número de planos de color.
66 Bytes por línea 2 Número de bytes necesarios para guardar una plano de línea. Tiene que ser un número impar.
68 Paleta 2 Indica cómo interpretar la paleta.  
1 = Color/BW  
2 = Escala de grises
70 HScreenSize 2 Tamaño horizontal de la pantalla en píxels.
72 VScreenSize 2 Tamaño vertical de la pantalla en píxels.
74 Relleno 54 Bytes con valor 0 hasta rellenar los 128 de la cabecera.
 

Para decodificar un fichero PCX se realiza continuamente un proceso determinado:

Leemos un byte y miramos si sus bits 7 y 8 están a 1. Si es así, los restantes seis bits indican cuántas veces se repite el próximo byte del fichero PCX. Si los bits 7 y 8 no están ambos a 1, el byte se representa a sí mismo una sola vez, y leemos el siguiente para repetir el mismo proceso. Hay que tener en cuenta que la codificación es por líneas, es decir, si estamos en una resolución de 320 columnas (y mediante la decodificación hemos obtenido 317 bytes), si el 318 tiene sus bits 7 y 8 a 1, nunca podrá indicar más de 2 repeticiones, aunque el mismo color siga repitiéndose en la siguiente línea. Se puede decir que en el caso de los PCX la codificación está orientada a línea. Un caso aparte es dónde se encuentra ubicada la información de la paleta cuando se trata de un fichero de 256 colores. Si es de 16 o menos, se guarda en la cabecera a partir de la posición 16, en el campo Mapa de Color. Pero cuando tenemos 256 colores, ésta se encuentra tras la codificación de la pantalla.

Tras el último byte de la codificación, se encuentra un byte de separación con el valor 12 en decimal y tras él, 768 bytes indican ternas consecutivas de valores RGB.

Pero estos valores no se pueden tomar directamente, ya que son bytes (0-255) y los valores RGB van de 0 a 63, por lo que tendremos que dividir el valor leído por 4.

El siguiente procedimiento se encarga de leer un fichero y guardarlo en una zona de memoria de 64000 bytes, asignando finalmente la paleta al DAC de vídeo

void LeePcxMcga(char nombre[12],BYTE *pantalla)
{
    register WORD cont,cont2;
    BYTE *direscrt,byte,r,g,b,filas;
    FILE *fichero;
    fichero=fopen(nombre,"rb"); fseek(fichero,128,0);
    direscrt=pantalla; cont=64000;
    while (cont>0)
    {
        byte=getc(fichero);
        if (byte<=192)
            { *direscrt++=byte; cont--; }
        else
        {
            cont2=byte&63; byte=getc(fichero);
            for(;cont2>0;cont2--)
                { *direscrt++=byte; cont--; }
        }
    }
    getc(fichero);
    for (cont2=0;cont2<256;cont2++)
    {
        r=getc(fichero)>>2; g=getc(fichero)>>2; b=getc(fichero)>>2;
        Establecer1ColorPaleta(cont2,r,g,b);
    }
    fclose(fichero);
}

Aparte de este procedimiento se puede hacer el siguiente:

void LeePcxMcgaR(char [12],unsigned char *,PALETA);

Es el mismo que el anterior con la salvedad de que no asigna la paleta al DAC de vídeo, sino que la almacena en la variable de tipo PALETA para su posterior uso. Esto es útil para los volcados entre pantallas que no tienen la misma paleta, puesto que si se usase el anterior procedimiento, la primera pantalla vería su paleta trastocada y el efecto sería desastroso al cambiar los colores a otros que puede que no tengan nada que ver.

 

2.1.11.- Lectura de Ficheros FLI

Un fichero FLI es un tipo de archivo en el que se guarda la información referente a una animación, estructurada de forma óptima para su posterior reproducción.

Este tipo de ficheros es soportado por programas como Autodesk Animator Pro y Autodesk 3D Studio 4.0. Es el más conocido de los ficheros de animación (junto a su hermano mayor, el FLC).

Los detalles de un fichero FLI son moderadamente complejos, pero la idea en la que se basa es simple: no hay que preocuparse en guardar las partes de un frame (pantalla hablando en términos de animación) que son las mismas que las del anterior frame. No solamente esto ahorra espacio, sino que es mucho más rápido dejar un píxel que escribirlo de nuevo.

Un fichero FLI, como todos los ficheros que almacenan algún tipo de formato que se va a usar posteriormente, tiene una cabecera. Su tamaño es de 128 bytes. Tras esta cabecera están almacenados un conjunto de frames. Utilizando la idea anterior, los siguientes frames son almacenados como la diferencia con el anterior frame. Imprescindiblemente, el primer frame almacenado debe ser guardado completamente, ya que no hay uno anterior con el que calcular la diferencia. Para comprobaciones y chequeos (no lo usamos por el motivo de la velocidad a cambio de una pequeña pérdida de posible seguridad) hay un último frame que contiene la diferencia entre el último frame anterior a éste, y el primero.

La cabecera de un fichero FLI es la siguiente:
 

Byte
Tamaño
Nombre
Significado
0
4
Tamaño
Longitud del fichero.
4
2
Magia
Constante = AF11
6
2
Frames
Número de Frames. Máximo 4000 frames.
8
2
Anchura
Anchura de la resolución de pantalla.
10
2
Altura
Altura de la resolución de pantalla.
12
2
Profundidad
Profundidad de un píxel. Normalmente 8.
14
2
Flag
Constante = 0
16
2
Velocidad
Número de ticks entre cada frame.
18
4
Siguiente
Constante = 0
22
4
Frit
Constante = 0
26
102
Expandido
Relleno de ceros hasta completar los 128 bytes.
 

Como se indicó antes, tras la cabecera vienen los frames.
Éstos a su vez tienen cada uno su propia cabecera de 16 bytes:
 

Byte
Tamaño
Nombre
Significado
0
4
Tamaño
Bytes en el frame. Como máximo 64 Kbytes.
4
2
Magia
Constante = F1FA
6
2
Chunk
Número de Chunks en el frame.
8
8
Expandido
Relleno de ceros hasta completar los 16 bytes.
 

Después de la cabecera del frame, vienen los chunks que componen el frame.

Primero viene un chunk de color si la paleta de colores ha cambiado desde el anterior frame, luego viene un chunk de píxel si los píxel han cambiado con respecto al anterior frame. Aclaremos ahora en qué consiste un chunk.

Un chunk es como una estructura determinada que contiene un tipo de información. De nuevo, cada chunk tiene una cabecera de 6 bytes:
 

Byte
Tamaño 
Nombre
Significado
0
4
Tamaño
Número de bytes en el chunk.
4
2
Tipo
Tipo del chunk.
 

Hay cinco tipos de chunk:
 

Valor
Nombre
Significado
11
FLI_COLOR
Paleta comprimida.
12
FLI_LC
Línea comprimida.
13
FLI_BLACK
Establece toda la pantalla al color 0.
15
FLI_BRUN
Compresión del primer frame con RLE.
16
FLI_COPY
64000 bytes sin comprimir.
 

Analicemos ahora en detalle lo que significa cada tipo de chunk, sabiendo que el primero es el chunk de color que antes apuntamos, y los cuatro restantes son chunks de píxel.

Además hay que decir que los esquemas de compresión son todos orientados a byte como en los PCX. Para permitir DMA (no es el caso nuestro), el FLI observa que si los datos comprimidos terminan con una longitud par, un byte de relleno es insertado para que el chunk FLI_COPY comience siempre en una dirección impar.

Análisis de los chunks:

FLI_COLOR

Los primeros 2 bytes son el número de paquetes en este chunk. A continuación vienen los propios paquetes (trozos de información comprimida). El primer byte de cada paquete indica cuántos colores hay que saltarse. El siguiente byte dice cuántos colores hay que cambiar. Si este byte es 0 se interpreta como 256 (siempre tendrá este valor el primer frame del fichero FLI). Luego están las ternas RGB de cada color que se va a cambiar. FLI_LC Este es el más común y a la vez el más complejo de los chunks. Los primeros 2 bytes son el número de líneas de la parte superior de la pantalla que son iguales al frame anterior. La siguiente palabra (2 bytes) indica el número de líneas que hay que cambiar. A continuación están los datos para los cambios. Cada línea se comprime individualmente (concepto similar al de la compresión orientada a línea de los PCX). Analicemos más profundamente esta técnica:

El primer byte de una línea comprimida indica el número de paquetes en esa línea. Si la línea no cambia con respecto al anterior frame, este valor es 0. El formato individual de cada paquete es el siguiente:

Skip_count es un simple byte. Si más de 255 píxels se van a saltar, debe ser indicado mediante 2 paquetes. Size_count es otro byte. Si es positivo, esa cantidad de bytes siguen a continuación y deben ser copiados tal cual a la pantalla. Si es negativo, le sigue un simple byte que se repite -Skip_count veces.

En el peor de los casos un frame comprimido mediante chunks FLI_LC, puede llegar a ocupar 70 Kbytes. Para prevenir esto, si en la compresión se detecta que se ha pasado de los 60000 bytes o más, no se continúa con la compresión, se deshecha y se utiliza FLI_COPY

FLI_COPY

Consiste en un conjunto de 64000 bytes de datos sin comprimir.

FLI_BLACK

Es el más simple junto al anterior. Sólo se usa en el primer frame del FLI y consiste en poner la paleta en negro total. FLI_BRUN Es como el FLI_LC, pero sin los saltos (Skip_count). Comienza inmediatamente con los datos para la primera línea y continúa línea a línea. EL primer byte contiene el número de paquetes en la línea. El formato para estos paquetes es:
  1. Size_count
  2. Datos
Si Size_count es positivo, el dato consiste en un byte que se repite Size_count veces. Si es negativo, hay -Size_count bytes de datos que se copian a la pantalla. Igual que FLI_LC, si se detecta que en la compresión se llevan almacenados más de 60000 bytes, se aborta el proceso y se utiliza FLI_COPY. Y por fin el procedimiento que se encarga de este pesado de entender y codificar, pero resultoso efecto:

void VerFLIMcga(char fichero[12],int retardo,BYTE *pant)
{
    register WORD pixel,vc;
    int segmento,desplazamiento;
    FILE *fli;
    CABECERA_FLI hdr;
    CABECERA_FRAME frm;
    unsigned long size;
    WORD type,pakets,cnt,lines,change,i,c,f,cont,cont2;
    BYTE skip,v,*aux,rojo,verde,azul;
    signed char s;
    clock_t tiks;
    PALETA paleta;

    // Apertura del fichero
    fli=fopen(fichero,"rb");
    if (!fli)
    {
        ModoTexto();
        printf("\nNo se encuentra el fichero %s",fichero);
        exit (0);
    }
    // Máximo buffer para dar velocidad en la carga del fichero
    setvbuf(fli,VBuf,_IOFBF,BUFSIZE);
    // Lectura de la cabeza
    fread(&hdr,sizeof(CABECERA_FLI),1,fli);
    // Para cada frame...
    for (f=0;f<hdr.frames;f++)
    {
        tiks=clock();
        // Lee una cabecera de frame
        fread(&frm,sizeof(CABECERA_FRAME),1,fli);
        if (frm.size!=0L)
        for (i=0;i<frm.chunks;i++)
        {
            // Cabecera del chunk
            fread(&size,sizeof(long),1,fli);
            type=getw(fli);
            // Dependiendo del tipo del frame
            switch (type)
            {
                 // Línea comprimida
                case FLI_LC:
                // Número de líneas que son iguales al frame anterior
                lines=getw(fli);
                // Número de líneas que han cambiado
                change=getw(fli);
                // Pasado a ensamblador: cont=(256*lines+64*lines) == 320*lines
                asm{
                        mov vc,4
                        mov ax,lines
                        mov bx,ax
                        shl ax,8
                        shl bx,6
                        add ax,bx
                        mov cont,ax
                }
                cont2=cont;
                for(c=0;c<change;c++)
                {
                    pakets=getc(fli);
                    asm{
                            inc vc
                    }
                    while (pakets>0)
                    {
                        skip=getc(fli);
                        asm{
                                dec pakets
                                inc vc
                                xor ah,ah
                                mov al,skip
                                add cont,ax
                                inc vc
                        }
                        s=getc(fli);
                        if (s>0)
                        {
                            asm{
                                    xor ah,ah
                                    mov al,s
                                    add vc,ax
                            }
                            salto:
                            rojo=getc(fli);
                            asm{
                                    mov ax,0xA000
                                    mov es,ax
                                    mov di,cont
                                    mov al,rojo
                                    stosb
                                    inc cont
                                    dec s
                                    jnz salto
                            }
                        }
                        else
                        {
                            s=-s;
                            v=getc(fli);
                            asm{
                                    inc vc
                                    xor ch,ch
                                    mov cl,s
                                    mov ax,0xA000
                                    mov es,ax
                                    mov di,cont
                                    mov al,v
                                    rep stosb
                                    xor ah,ah
                                    mov al,s
                                    add cont,ax
                            }
                        }
                    }
                    asm{
                                add cont2,320
                     }
                     cont=cont2;
                }
                // Si ha habido un número impar de bytes, se lee el de relleno
                if (vc&1) getc(fli);
                break;
 
                case FLI_BRUN:
                // Para contar el número de bytes descomprimidos
                asm{
                        mov vc,0
                        mov cont,0
                }
                // Compresión línea por línea
               for (skip=0;skip<200;skip++)
                {
                    // Número de paquetes en la linea
                    pakets=getc(fli);
                    asm{
                            inc vc
                    }
                    while (pakets>0)
                    {
                        // Número de veces que se repite un byte
                        s=getc(fli);
                        asm{
                                inc vc
                                dec pakets
                        }
                        // Si s es negativo, el número de bytes es -s y est n en
                        // los siguientes s bytes: no está comprimido
                        if (s<0)
                        {
                            s=-s;
                            asm{
                            xor ah,ah
                            mov al,s
                            add vc,ax
                        }
                        salto1:
                        rojo=getc(fli);
                        asm{
                                mov ax,0xA000
                                mov es,ax
                                mov di,cont
                                mov al,rojo
                                stosb
                                inc cont
                                dec s
                                jnz salto1
                        }
                    }
                    // Si es positivo, se repite el siguiente byte s veces:
                    // está comprimido
                    else
                    {
                        v=getc(fli);
                        asm{
                                inc vc
                                mov ax,0xA000
                                mov es,ax
                                mov ax,cont
                                mov di,ax
                                mov al,v
                                mov cl,s
                                xor ch,ch
                                rep stosb
                                xor ah,ah
                                mov al,s
                                add cont,ax
                        }
                    }
                }
            }
            // Si se han obtenido un número impar de bytes, entonces falta
            // otro byte que es de relleno
            if (vc&1) getc(fli);
            break;
            // Cambiar un número determinado de colores
            case FLI_COLOR:
            // Número de paquetes comprimidos
            pakets=getw(fli);
            asm{
                    mov vc,2
                    mov c,0
            }
            while (pakets--)
            {
                // Cuántos colores se saltan
                skip=getc(fli);
                asm{
                        inc vc
                        // Cada 3 bytes representa 1 color: rojo, verde y azul, por lo
                        // que se saltar n 3*número de colores bytes
                        mov al,skip
                        xor ah,ah
                        mov cl,3
                        mul cl
                        add c,ax
                        inc vc
                }
                // Cuántos colores se cambian
                cnt=getc(fli);
                // Si es 0, se interpreta como 256, de 0 a 255 colores a cambiar
                if (cnt==0)
                asm{
                        mov cnt,256
                }
                while (cnt--)
                {
                    // Se asignan los valores RGB de cada color a la paleta
                    paleta[c]=getc(fli);
                    asm{
                            inc c
                    }
                    paleta[c]=getc(fli);
                    asm{
                            inc c
                    }
                    paleta[c]=getc(fli);
                    asm{
                            inc c
                            add vc,3
                    }
                }
            }
            // Se hace efectivo el cambio de paleta
            for (lines=0,change=0;lines<256;change+=3,lines++)
            {
                rojo=paleta[change];
                verde=paleta[change+1];
                azul=paleta[change+2];
                asm{
                mov ax,lines
                mov dx,0x3c8
                out dx,al
                mov al,rojo
                mov dx,0x3c9
                out dx,al
                mov al,verde
                out dx,al
                mov al,azul
                out dx,al
            }
        }
        // Si ha habido un número impar de bytes, se lee el de relleno
        if (vc&1) getc(fli);
        break;

        // 64000 bytes sin comprimir 
        case FLI_COPY:
        aux=pant;
        // Se leen los 64000 píxeles de la pantalla
        for (pixel=0;pixel<64000;pixel++) *aux++=getc(fli);
        // Se vuelcan de una vez
        segmento=FP_SEG(pant);
        desplazamiento=FP_OFF(pant);
        asm{
                push ds
                mov ax,0xA000
                mov es,ax
                mov ds,segmento
                mov si,desplazamiento
                xor di,di
                mov cx,32000
                rep movsw
                pop ds
        }
        // Aquí no hay comprobación del número de vc, 64000 es par
        break;

        // Cls a negro
        case FLI_BLACK:
        asm{
                mov ax,0xA000
                mov es,ax
                xor di,di
                xor ax,ax
                mov cx,32000
                rep stosw
        }
        break;
         // Si no se reconoce, se sale
        default:
        ModoTexto();
        printf("\nVersión incorrecta de archivo FLI");
        exit (0);
     }
    }
    // Espera hasta el número especificado de ticks del reloj
    while (clock()-tiks<retardo);
   }
   fclose(fli);
}

Este procedimiento nos permite indicar la velocidad de su ejecución, siendo la más adecuada el valor 1 para los ordenadores rápidos (486-Pentium), y 0 para los más lentos. Si por las características globales de un ordenador determinado, con el valor 1 la animación fluye demasiado deprisa, o se quiere que avance lentamente, los valores 2 y 3 se pueden llegar a utilizar sin perder demasiada fluidez.




AULA MACEDONIA
a
MACEDONIA Magazine