|
|
Este formato gráfico
fue creado por ZSoft y difundido por la famosa familia de programas de
dibujo Paintbrush. Pero es su facilidad de codificación y su rápida
descompresión lo que lo han hecho existir entre los formatos gráficos
que hoy existen, a pesar de tener uno de los peores ratios de compresión.
Nosotros nos centraremos en este formato
para representar imágenes en el modo 13h, es decir trataremos imágenes
de hasta 320x200 pixeles.
El formato PCX esta dividido básicamente
en tres partes:
1. La cabecera
2. Los datos o imagen
3. La paleta
1. La cabecera:
la conforman los 128 primeros bytes, y nos da información sobre
la imagen. Es lo primero que debemos leer de un archivo para saber una
serie de datos de la imagen.
Las características presentadas
en la cabecera son las siguientes:
Byte 0: Identificación (El
valor 10 significa ZSoft)
Byte 1: Versión
Byte 2: Tipo de compresión
Byte 3: Numero de bits por pixel
(8)
Byte 4: X mínima (2 bytes)
Byte 6: Y mínima (2 bytes)
Byte 8: X máxima (2 bytes)
Byte 10: Y máxima (2 bytes)
Byte 12: Resolución horizontal
en DPI (2 bytes)
Byte 14: Resolución vertical
en DPI (2 bytes)
Byte 16: Datos para la paleta de
16 colores (48 bytes)
Byte 64: Reservado
Byte 65: Número de planos
de color
Byte 66: Bytes por línea
(2 bytes)
Byte 68: Si es 1 = Color, 2 = Escala
de grises (2 bytes)
Byte 70: Reservados (58 bytes)
2. Los datos o imagen:
Aquí se encuentran los datos con una codificación algo especial,
denominada RLE (Run Length Encoded), es decir una codificación por
longitud de serie. Es una técnica de compresión algo antigua.
Se basa en no repetir datos de igual valor si son consecutivos, mediante
un algoritmo algo técnico. Para que quede mas claro como funciona
este algoritmo, supongamos que tenemos los siguientes bytes de una imagen:
62 62 62 62 62 62 62
Si codificamos esto con RLE nos quedaría
como: 07 62
Es decir, primero guardamos la longitud
de la cadena, seguido por el valor que se repite.
Para ir decodificando cada byte de la imagen,
debemos empezar después de la cabecera de 128 bytes. Primero leemos
un byte. Si los dos bits mas significativos valen cero, entonces el byte
leído es un byte de datos y simplemente lo almacenamos en la memoria.
Si los dos bits mas significativos valen uno, entonces el byte es un contador
que indica el número de veces que se repite el siguiente byte.
Para comprobar los dos bits más
significativos, hacemos lo siguiente:
if((byte & 192)==192) // Los
dos bits están activados, por lo tanto es un contador
else
// es un byte de datos
En el caso de que los dos bits superiores
estén activos, entonces usamos ese byte como contador, pero desactivando
los dos bits superiores, ya que estos solo sirven para indicar que el byte
que leímos es un contador.
Para desactivar los dos bits superiores,
solamente hacemos la operación lógica & del byte que
leímos con el valor 63. Es decir hacemos esto:
contador=byte & 63;
3. La paleta:
Al final se haya la paleta que tiene un primer byte que debe ser 12, o
en hexadecimal 0Ch. Esto es para indicar que la paleta contiene 256 colores.
Como cada color se guarda como una estructura RGB, es decir, por combinación
de los colores primarios rojo, verde y azul, tendremos 768 datos para contener
la paleta de 256 colores.
Antes de comenzar a crear la rutina para
leer este tipo de archivos. Deberemos crearnos una estructura para guardar
los datos de la cabecera:
typedef struct
{
char
Fabricante;
// Fabricante (0ah = ZSoft)
char
Version;
// Número de versión del pcx
char
Compresion;
// Hay compresión? 1=Si RLE
char
BPP;
// Bits por pixel
int
xmin;
// Dimensiones del pcx
int
ymin;
int
xmax;
int
ymax;
int
Hres;
// Resoluciones de la imagen (DPI)
int
Vres;
char
Palette16[48];
// Paleta de color (no la usamos)
char
Reservado;
// No se utiliza, tiene que valer 0
char
Numero_planos; //
Indica el número. de planos de color en la imagen
int
Bytes_por_linea;
// Numero de bytes por línea de la imagen
int
Palette_type;
// Como interpretamos la paleta
char
Reservados[58]; //
Reservados para futuras ampliaciones
} PcxHead; |
De esta forma tenemos un nuevo tipo de
dato, que contiene la información necesaria para manejar la cabecera
de un archivo PCX.
Ahora veremos como cargar una imagen y
guardarla en una pantalla virtual.
int LoadPcx(char *filename,
int off_x, int off_y, t_paleta paleta, unsigned char *where)
{
enum{ARCHIVO_NO_ENCONTRADO=0,
NO_ES_ZSOFT=-1, MUY_GRANDE=-2, OK=1};
unsigned
int cont, ancho, alto, x=0, y=0;
unsigned
char byte, listo=0;
PcxHead
head;
FILE
*f;
if((f=fopen(filename,
"rb"))==NULL) return ARCHIVO_NO_ENCONTRADO;
fread(&head,
sizeof(char), 128, f);
if(head.Fabricante!=0x0A)
{
fclose(f);
return NO_ES_ZSOFT;
}
ancho=(head.xmax-head.xmin)+1;
alto=(head.ymax-head.ymin)+1;
if((ancho>320)
|| (alto>200))
{
fclose(f);
return MUY_GRANDE;
}
while(!listo)
{
byte=fgetc(f);
// Si los 2 bits mas significativos estan activados, entonces el byte es
un contador
if((byte&192)==192) // 192=11000000b
{
cont=byte & 63; // 63=00111111b
byte=fgetc(f);
for(; cont>0; cont--)
{
where[((off_y+y)<<8)+((off_y+y)<<6)+(off_x+x)]=byte;
x++;
if(x==ancho) // Si ya terminamos la línea
{
x=0; // Volvemos
a empezar en la línea siguiente
y++;
}
// Si ya terminamos todas las líneas de la imagen
if(y==alto) listo=1;
}
}
// Si los 2 bits mas significativos no están activados, entonces
es un byte de datos
else
{
where[((off_y+y)<<8)+((off_y+y)<<6)+(off_x+x)]=byte;
x++;
if(x==ancho) // Si ya terminamos la línea
{
x=0;
// Volvemos a empezar en la línea siguiente
y++;
}
// Si ya terminamos todas las líneas de la imagen
if(y==alto) listo=1;
}
}
fgetc(f);
// Se lee el byte de separación de valor 12d
for(cont=0;
cont<768; cont++)
paleta[cont]=fgetc(f)>>2; // dividimos por 4
fclose(f);
return
OK;
} |
Como ven la función necesita los
siguientes parámetros: el nombre del archivo PCX, las coordenadas
donde comenzara a dibujarse la imagen (esquina superior izquierda), comúnmente
en (0,0), una variable para guardar la paleta y por último la pantalla
virtual donde guardar la imagen.
Ahora veremos una modificación de
la funcion anterior. La ventaja de esta función, es que ahora no
desperdiciaremos memoria, si la imagen por ejemplo es de 15x15 pixeles.
La otra ventaja es que se guarda el ancho y el alto de la imagen, así
podremos usar esta función para cargar sprites y luego para colocarlos
en pantalla utilizaremos la funcion PutSprite().
int CargaPcx(char *filename,
t_paleta paleta, unsigned char **where)
{
enum{ARCHIVO_NO_ENCONTRADO=0,
NO_ES_ZSOFT=-1, MUY_GRANDE=-2,
NO_MEMORIA=-3, OK=1};
unsigned
int i, cont, total, ancho, alto;
unsigned
char byte;
PcxHead
head;
FILE
*f;
if((f=fopen(filename,
"rb"))==NULL) return ARCHIVO_NO_ENCONTRADO;
fread(&head,
sizeof(char), 128, f);
if(head.Fabricante!=0x0A)
{
fclose(f);
return NO_ES_ZSOFT;
}
ancho=(head.xmax-head.xmin)+1;
alto=(head.ymax-head.ymin)+1;
if((ancho>320)
|| (alto>200))
{
fclose(f);
return MUY_GRANDE;
}
if((*where=(BYTE*)(malloc((ancho*alto)+4)))==NULL)
return NO_MEMORIA;
memcpy(*where,
&ancho, 2);
memcpy(*where+2,
&alto, 2);
total=ancho*alto;
for(i=0;
i<total;)
{
byte=fgetc(f);
if((byte&192)==192)
{
cont=byte & 63;
byte=fgetc(f);
for(; cont>0; cont--)
{
*(*where+i+4)=byte;
i++;
}
}
else
{
*(*where+i+4)=byte;
i++;
}
}
fgetc(f);
for(cont=0;
cont<768; cont++)
paleta[cont]=fgetc(f)>>2;
fclose(f);
return OK;
} |
Ejemplos
Librería gráfica:
vgalib.h
Volver
a formatos gráficos
|