Publi

Leyendo archivos de imagen en formato BMP en C

n1214332197_30461081_3311160

Hoy vamos a practicar a leer una imagen desde un archivo BMP desde C. Aunque existen muchas APIs disponibles que son capaces de hacerlo, y mucho mejor que lo que voy a plantear (puesto que nos limitaremos a BMPs sin compresión y a 24bits por pixel), es un buen ejercicio para leer archivos con un formato especificado y documentado.

Para este tipo de archivos, tendremos dos cabeceras disponibles, la primera será la cabecera de fichero, y la segunda, la cabecera de información de imagen, que las definimos aquí:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef struct bmpFileHeader
{
  /* 2 bytes de identificación */
  uint32_t size;        /* Tamaño del archivo */
  uint16_t resv1;       /* Reservado */
  uint16_t resv2;       /* Reservado */
  uint32_t offset;      /* Offset hasta hasta los datos de imagen */
} bmpFileHeader;

typedef struct bmpInfoHeader
{
  uint32_t headersize;      /* Tamaño de la cabecera */
  uint32_t width;               /* Ancho */
  uint32_t height;          /* Alto */
  uint16_t planes;                  /* Planos de color (Siempre 1) */
  uint16_t bpp;             /* bits por pixel */
  uint32_t compress;        /* compresión */
  uint32_t imgsize;     /* tamaño de los datos de imagen */
  uint32_t bpmx;                /* Resolución X en bits por metro */
  uint32_t bpmy;                /* Resolución Y en bits por metro */
  uint32_t colors;              /* colors used en la paleta */
  uint32_t imxtcolors;      /* Colores importantes. 0 si son todos */
} bmpInfoHeader;

Todos los campos tienen un comentario que los explica, pero los detallaré algo más:

  • De primeras nos encontramos dos bytes de identificación que no he introducido en el registro, y es así por la alineación de variables. Es mejor leerlos a parte y luego leer el struct que os presento.
  • size (en FileHeader) es el tamaño del archivo completo, lo podremos usar para comprobar si hay más datos al final del archivo, o para ver si el archivo está cortado, es decir, como comprobación de errores
  • offset (en FileHeader), es la distancia en bytes desde que termina la cabecera de información hasta que empiezan los datos. No le vamos a hacer mucho caso a esto, sólo que puede ser que el archivo tenga más información entre la cabecera y los datos.
  • headersize (en InfoHeader), será el tamaño de la cabecera, dado que hay varias versiones del formato, ésta puede ser mucho más grande, aunque nosotros sólo leeremos siempre 40bytes.
  • planes (en InfoHeader), son los planos de color. Sólo está documentado el valor 1 de este campo, por tanto será el único que tratemos.
  • bpp (en InfoHeader), son los bits por pixel de la imagen, para el ejemplo, siempre serán 24, ya que no soportamos imágenes con otra profundidad de color.
  • compress (en InfoHeader), para este ejemplo será siempre 0, ya que no trataremos compresión.
  • imgsize (en InfoHeader), será el tamaño de los datos de imagen, al igual que size nos puede servir para control de errores, y para ver cuánta memoria vamos a reservar, aunque este tamaño, para el ejemplo será ancho*alto*3 (24bits por pixel)
  • bpmx, bpmy en (InfoHeader), será la resolución de la imagen en pixels por metro.
  • colors, imxtcolors en (InfoHeader), valdrán 0 en este ejemplo, ya que no usaremos imágenes con paleta de colores.

Ya estamos preparados para leer el archivo de imagen con la siguiente función:

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
unsigned char *LoadBMP(char *filename, bmpInfoHeader *bInfoHeader)
{

  FILE *f;
  bmpFileHeader header;     /* cabecera */
  unsigned char *imgdata;   /* datos de imagen */
  uint16_t type;        /* 2 bytes identificativos */

  f=fopen (filename, "r");
  if (!f)
    return NULL;        /* Si no podemos leer, no hay imagen*/

  /* Leemos los dos primeros bytes */
  fread(&type, sizeof(uint16_t), 1, f);
  if (type !=0x4D42)        /* Comprobamos el formato */
    {
      fclose(f);
      return NULL;
    }

  /* Leemos la cabecera de fichero completa */
  fread(&header, sizeof(bmpFileHeader), 1, f);

  /* Leemos la cabecera de información completa */
  fread(bInfoHeader, sizeof(bmpInfoHeader), 1, f);

  /* Reservamos memoria para la imagen, ¿cuánta?
     Tanto como indique imgsize */

  imgdata=(unsigned char*)malloc(bInfoHeader->imgsize);

  /* Nos situamos en el sitio donde empiezan los datos de imagen,
   nos lo indica el offset de la cabecera de fichero*/

  fseek(f, header.offset, SEEK_SET);

  /* Leemos los datos de imagen, tantos bytes como imgsize */
  fread(imgdata, bInfoHeader->imgsize,1, f);

  /* Cerramos */
  fclose(f);

  /* Devolvemos la imagen */
  return imgdata;
}

Esta función devolverá los datos de la imagen como resultado de la función, y a través de un parámetro de entrada/salida, devolveremos también el infoHeader.
Lo que hacemos es simplemente leer las cabeceras, aunque para la primera cabecera, como dijimos antes, leemos primero los 2 bytes identificativos del formato de archivo, éstos tienen que valer 0x4D42 (MB), aunque como los datos están en little-endian (el byte menos significativo primero), lo leeremos al revés (BM), que corresponde a uno de los formatos de imagen que podemos alojar dentro de un BMP.

Por último, vamos a probar todo esto con un ejemplo, para no complicar las cosas con APIs gráficas, o cálculos de histogramas, se me ocurrió hacer una pequeña representación de la imagen en la consola. Para este código, creé una imagen con ImageMagick de la siguiente manera:

$ convert -background red -fill blue -font /usr/share/fonts/truetype/ttf-liberation/LiberationMono-Bold.ttf -pointsize 72 «label:Poesía Binaria» +matte poesia.bmp

+matte nos servirá para eliminar el canal alfa por defecto de la imagen.

Ahora, en nuestro código incluimos los struct y la función anteriores con un par de cosas más:

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

typedef struct bmpFileHeader
{
  /* 2 bytes de identificación */
  uint32_t size;        /* Tamaño del archivo */
  uint16_t resv1;       /* Reservado */
  uint16_t resv2;       /* Reservado */
  uint32_t offset;      /* Offset hasta hasta los datos de imagen */
} bmpFileHeader;

typedef struct bmpInfoHeader
{
  uint32_t headersize;      /* Tamaño de la cabecera */
  uint32_t width;       /* Ancho */
  uint32_t height;      /* Alto */
  uint16_t planes;          /* Planos de color (Siempre 1) */
  uint16_t bpp;             /* bits por pixel */
  uint32_t compress;        /* compresión */
  uint32_t imgsize;     /* tamaño de los datos de imagen */
  uint32_t bpmx;        /* Resolución X en bits por metro */
  uint32_t bpmy;        /* Resolución Y en bits por metro */
  uint32_t colors;      /* colors used en la paleta */
  uint32_t imxtcolors;      /* Colores importantes. 0 si son todos */
} bmpInfoHeader;

unsigned char *LoadBMP(char *filename, bmpInfoHeader *bInfoHeader);
void DisplayInfo(bmpInfoHeader *info);
void TextDisplay(bmpInfoHeader *info, unsigned char *img);

int main()
{
  bmpInfoHeader info;
  unsigned char *img;

  img=LoadBMP("poesia.bmp", &info);
  DisplayInfo(&info);
  TextDisplay(&info, img);

  return 0;
}

void TextDisplay(bmpInfoHeader *info, unsigned char *img)
{
  int x, y;
  /* Reducimos la resolución vertical y horizontal para que la imagen entre en pantalla */
  static const int reduccionX=6, reduccionY=4;
  /* Si la componente supera el umbral, el color se marcará como 1. */
  static const int umbral=90;
  /* Asignamos caracteres a los colores en pantalla */
  static unsigned char colores[9]=" bgfrRGB";
  int r,g,b;

  /* Dibujamos la imagen */
  for (y=info->height; y>0; y-=reduccionY)
    {
      for (x=0; x<info->width; x+=reduccionX)
    {
      b=(img[3*(x+y*info->width)]>umbral);
      g=(img[3*(x+y*info->width)+1]>umbral);
      r=(img[3*(x+y*info->width)+2]>umbral);

      printf("%c", colores[b+g*2+r*4]);
    }
      printf("\n");
    }
}

unsigned char *LoadBMP(char *filename, bmpInfoHeader *bInfoHeader)
{

  FILE *f;
  bmpFileHeader header;     /* cabecera */
  unsigned char *imgdata;   /* datos de imagen */
  uint16_t type;        /* 2 bytes identificativos */

  f=fopen (filename, "r");
  if (!f)
    return NULL;        /* Si no podemos leer, no hay imagen*/

  /* Leemos los dos primeros bytes */
  fread(&type, sizeof(uint16_t), 1, f);
  if (type !=0x4D42)        /* Comprobamos el formato */
    {
      fclose(f);
      return NULL;
    }

  /* Leemos la cabecera de fichero completa */
  fread(&header, sizeof(bmpFileHeader), 1, f);

  /* Leemos la cabecera de información completa */
  fread(bInfoHeader, sizeof(bmpInfoHeader), 1, f);

  /* Reservamos memoria para la imagen, ¿cuánta?
     Tanto como indique imgsize */

  imgdata=(unsigned char*)malloc(bInfoHeader->imgsize);

  /* Nos situamos en el sitio donde empiezan los datos de imagen,
   nos lo indica el offset de la cabecera de fichero*/

  fseek(f, header.offset, SEEK_SET);

  /* Leemos los datos de imagen, tantos bytes como imgsize */
  fread(imgdata, bInfoHeader->imgsize,1, f);

  /* Cerramos */
  fclose(f);

  /* Devolvemos la imagen */
  return imgdata;
}

void DisplayInfo(bmpInfoHeader *info)
{
  printf("Tamaño de la cabecera: %u\n", info->headersize);
  printf("Anchura: %d\n", info->width);
  printf("Altura: %d\n", info->height);
  printf("Planos (1): %d\n", info->planes);
  printf("Bits por pixel: %d\n", info->bpp);
  printf("Compresión: %d\n", info->compress);
  printf("Tamaño de datos de imagen: %u\n", info->imgsize);
  printf("Resolucón horizontal: %u\n", info->bpmx);
  printf("Resolucón vertical: %u\n", info->bpmy);
  printf("Colores en paleta: %d\n", info->colors);
  printf("Colores importantes: %d\n", info->imxtcolors);
}

Compilamos el código anterior y para una imagen como esta:
poesia
nos queda algo como esto:
pbinaria

Lo más raro es la forma de acceder a cada pixel (podremos simplificar más adelante), y es que la imagen está almacenada boca abajo, por lo que la primera línea que leamos será la última que se debe mostrar, por otra parte, los colores están en formato BGR, y, generalmente para acceder a cada pixel lo podremos hacer con:

img[3*(x+y*info->width)+comp]

Teniendo en cuenta, como ya dijimos que la coordenada y va desde la última línea a la primera, y que:

  • Si queremos leer la componente azul, comp=0
  • Si queremos leer la componente verde, comp=1
  • Si queremos leer la componente roja, comp=2

Más adelante, iré publicando algunas aplicaciones prácticas de todo esto, y pronto, una función parecida a LoadBMP que se encargue de salvar los archivos y así empezar a probar filtros gráficos.

Actualizado: 2011/06/26 13:00 : Añadida la información de las componentes de color al final.
Actualizado: 2016/09/18 : Se ha añadido la imagen original con la que se ejecuta el programa.

También podría interesarte....

There are 8 comments left Ir a comentario

  1. Pingback: Bitacoras.com /

  2. Pingback: Poesía binaria » #tuentiContest solución del Challenge 14 en C. Colours are beautiful /

  3. Pingback: Poesía binaria » Salvando archivos de imagen BMP en C /

  4. Carlos /
    Usando Google Chrome Google Chrome 52.0.2743.116 en Windows Windows 7

    Hola mi duda consiste en que no tengo claro parte del código se le da la dirección de donde se encuentra la imagen, para cárgala. Como es la imagen original. Gracias.

    Saludos.

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 48.0 en Ubuntu Linux Ubuntu Linux

      Hola Carlos,
      He añadido la imagen original en el post. Si tienes alguna duda con respecto al código, por favor, dime qué parte exactamente e intentaré que todo quede claro.

      Saludos!

  5. rootc /
    Usando Google Chrome Google Chrome 55.0.2883.87 en Windows Windows 7

    muchisimas gracias amigos de poesia binaria me ha servido bastante la información .Me tomo el atrevimiento primero de felicitarlos por que son unos de los pocos que ha publicado algo al respecto y segundo dar un aporte al código: se me presento un pequeño problema al correr el código en windows 7 (si ya lo se windows no sirve) para ciertas características del archivo bmp. y es la siguiente si el ancho del archivo en bytes no es múltiplo de 4 la imagen se deforma o es ilegible tuve unos días analizando como resolver el problema para que pudiera leer todos los archivos bmp sin fallas y corri el siguiente codigo:

    #include
    #include
    #include
    #include
    #include
    #include

    using namespace std;
    FILE *archivo;
    uint8_t datos[54];
    char letra;
    uint8_t mos;
    uint16_t tipo;
    uint32_t tamano;
    uint32_t reservado;
    uint32_t init;
    uint32_t cabecera;
    uint32_t anchura;
    uint32_t altura;
    uint16_t planos;
    uint16_t tpunto;
    uint32_t comprimido;
    uint32_t timagen;
    uint32_t rhoriz;
    uint32_t rvertic;
    uint32_t tcolor;
    uint32_t cimport;

    uint8_t rojo;
    uint8_t verde;
    int azul;

    int contador;
    //int ancho;
    int componente;
    int pixel;
    int linea;
    float zbuffer[500][500][3];//nota si se declata como int causa problemasen la lectra de algunos archivos
    int columna;

    int i,j;

    bool bandera_ajuste;
    int salto;
    int residuo;
    int pase;
    bool permiso=true;

    int main()
    {
    printf(«\n programa que lee un archivo byte a byte \n»);
    printf(«%s»,datos );
    archivo=fopen(«C:\\Users\\c\\Desktop\\copia3.bmp»,»r»);
    if (archivo==NULL)
    {
    printf («\n error al abrir el achivo»);
    system(«pause»);
    return(-1);
    }
    fread(&tipo,sizeof(uint16_t),1,archivo);
    printf(«\n tipo de archivo %x»,tipo);
    fread(&tamano,sizeof(uint32_t),1,archivo);
    printf(«\n tamaño del archivo= %d»,tamano);
    fread(&reservado,sizeof(uint32_t),1,archivo);
    printf(«\n reservado= %d»,reservado);
    fread(&init,sizeof(uint32_t),1,archivo);
    printf(«\n los datos inician en la posicion= %d»,init);
    fread(&cabecera,sizeof(uint32_t),1,archivo);
    printf(«\n tamaño de cabecera del bit map= %d»,cabecera);
    fread(&anchura,sizeof(uint32_t),1,archivo);
    printf(«\n Anchura en pixel= %d»,anchura);
    fread(&altura,sizeof(uint32_t),1,archivo);
    printf(«\n Altura en pixel= %d»,altura);
    fread(&planos,sizeof(uint16_t),1,archivo);
    printf(«\n Numero de planos= %d»,planos);
    fread(&tpunto,sizeof(uint16_t),1,archivo);
    printf(«\n Tamaño de cada punto= %d bits»,tpunto);
    fread(&comprimido,sizeof(uint32_t),1,archivo);
    printf(«\n estado de compresion=»);
    if(comprimido==0){printf(» no»);}
    printf(» comprimido»);
    fread(&timagen,sizeof(uint32_t),1,archivo);
    printf(«\n tamaño dela imagen= %d»,timagen);
    fread(&rhoriz,sizeof(uint32_t),1,archivo);
    printf(«\n Resolución horizontal= %d»,rhoriz);
    fread(&rvertic,sizeof(uint32_t),1,archivo);
    printf(«\n Resolución vertical= %d»,rvertic);
    fread(&tcolor,sizeof(uint32_t),1,archivo);
    printf(«\n Tabla de color= %d»,tcolor);
    fread(&cimport,sizeof(uint32_t),1,archivo);
    printf(«\n Contador de colores importantes= %d»,cimport);
    fclose(archivo);

    if(fmod(anchura,4)>0)
    {
    printf(«\n ESTE ARCHIVO VA A CAUSAR PROBLES ACTIVAR COMPENSADOR»);
    bandera_ajuste=TRUE;
    salto=(3*anchura)+1;
    residuo=fmod(anchura,4);
    printf(» primer salto en %d»,salto);
    printf(«\n residuo en %d»,residuo);
    }

    //system(«pause»);

    /*******************************/
    archivo=fopen(«C:\\Users\\c\\Desktop\\copia3.bmp»,»r+b»);// aqui cambien por la direccion del archivo que deseen abrir
    if (archivo==NULL)
    {
    printf («\n error al abrir el achivo»);
    system(«pause»);
    return(-1);
    }
    contador=0;
    linea=1;
    //otroerror biendocumentado si se coloca una variable aqui luego al llamarla da un valor extraño ejemplo int componente
    /*****************************/
    printf(«\n»);
    while(!feof(archivo))
    {
    //letra=fgetc(archivo);
    fread(&azul,sizeof(uint8_t),1,archivo);
    //printf(«\nleyendo dato %d= %d que en letras es %c»,contador,azul,azul);
    //Sleep(20);
    contador++;
    if(contador>=55)
    {
    //printf(» OJO DATO DE COLOR «);
    if(bandera_ajuste==true)
    {
    if((contador-54)==salto)
    permiso=false;
    }

    if(permiso==true)
    {
    componente++;
    }
    if(permiso==false)
    {
    pase++;

    if(pase==residuo)
    {
    salto=(contador-54)+(3*anchura)+1;
    pase=0;
    permiso=true;
    }

    }

    //printf(» %d»,componente);
    if(componente==1&&permiso==true)
    {
    //printf(«\n dato %d= %d,%d: «,pixel,altura-linea,columna);
    //printf(» R(%d,%d,%d),»,altura-linea,columna,componente-1);
    if(linea<=altura)
    {
    zbuffer[altura-linea][columna][0]=azul;
    //printf("buffer(%d,%d)=%d",altura-linea,columna,zbuffer[altura-linea][columna]);
    }
    }
    if(componente==2&&permiso==true)
    {
    //printf(" G(%d,%d,%d),",altura-linea,columna,componente-1);
    if(linea<=altura)
    {
    zbuffer[altura-linea][columna][1]=azul;
    }
    }
    if(componente==3&&permiso==true)
    {
    // printf(" B(%d,%d,%d),",altura-linea,columna,componente-1);

    if(linea<=altura)
    {
    zbuffer[altura-linea][columna][2]=azul;
    }
    componente=0;
    pixel++;
    columna++;
    }

    if(azul==0)
    {
    printf("\n %d=%d",contador-54,azul);
    }

    //printf(",%d",azul);
    //printf("anchura*linea=%d",anchura*linea);

    //zbuffer[altura-linea][columna][componente-1]=azul;
    //printf("= %d",zbuffer[altura-linea][columna][componente-1]);
    if(pixel==anchura*linea)
    {
    //printf(" salto de linea en este pixel");
    //printf(" linea %d",linea);
    linea++;
    columna=0;
    }
    }
    }

    printf("\n");
    fclose(archivo);

    for(i=0;i<=altura-1;i++)
    {
    for(j=0;j<=anchura-1;j++)
    //for(j=0;j<=76;j++)
    {
    //printf("%d",zbuffer[i][j]);
    if(zbuffer[i][j][0]==255)
    {
    printf(" ");
    }
    else{printf("*");}
    }
    printf("\n");
    }
    /**/
    a:
    printf("pregunte por un bit especifico");
    printf("indique el eje x");
    scanf("%d",&i);
    printf("indique el eje y");
    scanf("%d",&j);
    if(zbuffer[i][j][0]==255)
    {
    printf("\n vale=O\n");
    }
    else{printf("\n vale=I\n");}
    goto a;
    system("pause");
    }

  6. Mayra /
    Usando Mozilla Firefox Mozilla Firefox 70.0 en Ubuntu Linux Ubuntu Linux

    Holaaaa. En serio muchìsimas gracias por el aporte, me ha servido muchìsimo, aunque tengo un problema. AL momento de ejecutar no me muestra la imagen desglosada como a ti, solo la informaciòn de la misma. Ya la guardè en formato .bmp y tambièn me fijè que el nombre en el còdigo corresponda al de la foto, pero aùn asì no me sale. Espero que puedas ayudarme un poco con esto, muchìsimas gracias!!

  7. robert /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    Acabo de abordar las respuestas de 7 letras en 4 Fotos 1 Palabra, ¡y qué ejercicio mental! El desafío era real, pero la sensación de logro cuando se resolvió cada rompecabezas hizo que todo valiera la pena. ¿A quién más le encanta la capacidad intelectual que exige este juego en el nivel de 7 letras?

Leave a Reply to Carlos Cancle Reply