AnteriorPosterior

8.5. Ficheros binarios

Por: Nacho Cabanes
Actualizado: 20-04-2019 10:30
Tiempo de lectura estimado: 10 min.

 

C++

8.5. Ficheros binarios

Hasta ahora nos hemos centrado en los ficheros de texto, que son sencillos de crear y de leer. Pero también podemos manejar ficheros que contengan información de cualquier tipo. Se les conoce como "ficheros binarios".

Abrir un fichero binario es casi igual de fácil que uno de texto: basta añadir ", ifstream::binary" cunado se abre:

ifstream fichero(nombre.c_str(), ifstream::binary);

Podemos leer un byte usando ".get()":

char dato = fichero.get();

Si el dato no está en la siguiente posición del fichero, podemos saltar a cualquier posición con ".seekg(posicion, origen)":

fichero.seekg(0, fichero.beg);

(Los posibles puntos de origen para el salto son el principio del fichero (".beg"), la posición actual (".cur") y el final del fichero (".end")).

También podemos saber en qué posición nos encontramos con ".tellg()":

miFichero.seekg (0, miFichero.end);
int longitudFichero = miFichero.tellg();

(Como se ve en ese ejemplo, podemos usar ".tellg()" para saber el tamaño del fichero, si antes tenemos la precaución de situarnos al final del fichero).

Con eso tenemos suficiente para hacer un ejercicio más complicado, un primer problema real. Vamos a abrir un fichero que sea una imagen en formato BMP y a mostrar en pantalla si está comprimido o no. Para eso necesitamos antes saber cómo se guarda la información en un fichero BMP, pero esto es algo fácil de localizar en Internet, y se podría resumir así:

FICHEROS .BMP
Un fichero BMP está compuesto por las siguientes partes: una cabecera de fichero, una cabecera del bitmap, una tabla de colores y los bytes que definirán la imagen.

En concreto, los datos que forman la cabecera de fichero y la cabecera de bitmap son los siguientes:

TIPO DE INFORMACIÓN POSICIÓN EN EL ARCHIVO
Tipo de fichero (letras BM) 0-1
Tamaño del archivo 2-5
Reservado 6-7
Reservado 8-9
Inicio de los datos de la imagen 10-13
Tamaño de la cabecera de bitmap 14-17
Anchura (píxeles) 18-21
Altura (píxeles) 22-25
Número de planos 26-27
Tamaño de cada punto 28-29
Compresión (0=no comprimido) 30-33
Tamaño de la imagen 34-37
Resolución horizontal 38-41
Resolución vertical 42-45
Tamaño de la tabla de color 46-49
Contador de colores importantes 50-53

Con esta información nos basta para nuestro propósito: la compresión se indica en la posición 30 del fichero, ocupa 4 bytes (lo mismo que un “int” en los sistemas operativos de 32 bits), y si es un 0 querrá decir que la imagen no está comprimida. Nos bastaría con mirar el primero de esos 4 bytes, y lo podríamos conseguir así:

// Introducción a C++, Nacho Cabanes
// Ejemplo 08.05:
// Información sobre un fichero BMP (1)
 
#include <iostream>
#include <fstream>
using namespace std;
 
int main()
{
    string nombre;
    char datos[256];
    int compresion;
 
    cout << "Comprobador de imágenes BMP" << endl;
    cout << "Dime el nombre del fichero: ";
    getline(cin, nombre);
 
    ifstream fichero(nombre.c_str(), ifstream::binary);
 
    if ( fichero.fail() )
        cout << "No encontrado" << endl;
    else 
    {
        fichero.seekg(30, fichero.beg);
        fichero.read(datos,1);
        fichero.close();
 
        compression = datos[0];
        if (compresion == 0) 
            cout << "Sin compresión";
        else
            cout << "BMP Comprimido";
    }
 
    return 0;
}

Ejercicios propuestos:

  • (8.5.1) Mejorar el ejemplo 08.05, para que compruebe si realmente se trata de un fichero BMP (el primer carácter debe ser 'B' y el segundo debe ser 'M'.).

8.6. Lectura por bloques en ficheros binarios

En un fichero binario, no sólo podemos leer byte a byte. También podemos leer todo un bloque de golpe, lo que en general resultará más rápido. En este caso, utilizamos “.read(datos,cantidad)”, donde "datos" será un array de caracteres, del tamaño que nosotros decidamos. Así, podríamos leer los 54 bytes de cabecera de un fichero BMP todos juntos, y acceder a cada uno de ellos después de cerrar el fichero. (De todos modos, este programa tiene alguna complicación adicional, que comentaremos tras el fuente de ejemplo):

// Introducción a C++, Nacho Cabanes
// Ejemplo 08.06:
// Información sobre un fichero BMP (2)
 
#include <iostream>
#include <fstream>
using namespace std;
 
int main()
{
    int tamanyoCabecera = 54;
    char * cabecera = new char[tamanyoCabecera];
 
    string nombre;
    unsigned int compresion, ancho, alto;
    char marca1, marca2;
 
    cout << "Comprobador de imágenes BMP" << endl;
    cout << "Dime el nombre del fichero: ";
    getline(cin, nombre);
 
    ifstream fichero(nombre.c_str(), ifstream::binary);
    if ( fichero.fail() )
        cout << "No encontrado" << endl;
    else 
    {
        marca1 = fichero.get();  // Leo los dos primeros bytes
        marca2 = fichero.get();
        if ((marca1 =='B') && (marca2 =='M'))  // Si son BM
        {
            cout << "Marca del fichero: " 
                << marca1 << marca2 << endl;
 
            // Leo toda la cabecera
            fichero.seekg(0, fichero.beg);    
            fichero.read(cabecera, tamanyoCabecera);
            fichero.close();
 
            // Y calculo y muestro datos individuales
            ancho = (unsigned char) cabecera[18] + 
                (unsigned char) (cabecera[19])*256 +
                (unsigned char) (cabecera[20])*256*256 + 
                (unsigned char&##41; (cabecera[21])*256*256*256;
            cout << "Ancho: " << ancho << endl;
 
            alto = (unsigned char) cabecera[22] + 
                (unsigned char) (cabecera[23])*256 +
                (unsigned char) (cabecera[24])*256*256 + 
                (unsigned char) (cabecera[25])*256*256*256; 
            cout << "Alto: " << alto << endl;    
 
            compresion = cabecera[30];
            switch (compresion) 
            {
                case 0: cout << "Sin compresión"; break;
                case 1: cout << "Compresión RLE 8 bits"; break;
                case 2: cout << "Compresión RLE 4 bits"; break;
            }
            cout << endl;
        } 
        else  // Si la marca no es BM
            cout << "No parece un fichero BMP" << endl;  
    }
 
    return 0;
}

Los detalles "avanzados" que muestra este ejemplo son:

  • El ancho y el alto no son datos de un único byte. Son número enteros de 4 bytes. En este caso, el convenio más usado (lo que se conoce como "little endian") es que los datos se guardan "al revés": el primer dato que leeremos será el byte menos significativo del número final, el segundo dato será el siguiente byte menos significativo, y así sucesivamente, hasta llegar al último byte, que es el más significativo. Por eso, el dato se debe calcular haciendo "ancho = byte1 + byte2*256 + byte3*256*256 + byte4*256*256*256;
  • El tipo de datos "char" puede almacenar números negativos. De hecho, si guardamos el número "224" como dato de tipo "char" y luego lo volvemos a leer, se nos dirá que el dato era "-32". Por eso, antes de realizar la operación anterior, le decimos que tome cada dato como "unsigned char" (sin signo, considerando siempre que se trata de un número positivo). Más adelante hablaremos de "unsigned" y de otros modificadores.

Si queremos guardar bloques de información en un fichero binario, la estructura será básicamente la misma, salvo que usaremos "ofstream" en vez de "ifstream", y guardaremos con "write", que es similar a "read". Pero eso queda para los ejercicios...

Ejercicios propuestos:

  • (8.6.1) Hacer un programa que muestre información sobre una imagen en formato GIF (se deberá localizar en Internet los detalles sobre dicho formato): versión, ancho de la imagen (en píxeles), alto de la imagen y cantidad de colores.
  • (8.6.2) Crear un programa que muestre información sobre una imagen en formato PCX: ancho de la imagen (en píxeles), alto de la imagen y cantidad de colores.
  • (8.6.3) Diseñar un programa que duplique un fichero existente, creando un array para almacenar todo su contenido, y volcando este array a un nuevo fichero.

542 visitas desde el 20-04-2019

AnteriorPosterior