mercoledì 21 aprile 2010

Lettura di una bitmap

Le immagini bitmap, introdotte nel 1990 con Windows 3.0, organizzano le informazioni all'interno di più strutture dati. Il file inizia con la struttura dati BITMAPFILEHEADER, di 14 byte. All'interno di questa prendono posto alcune importanti informazioni come la dimensione in byte del file, lo scostamento del primo pixel dell'immagine a partire dall'inizio del file e una stringa in ascii con il valore BM (valore decimale 19778, esadecimale 4D42). La successiva struttura dati, BITMAPINFOHEADER, di 40 byte, raccoglie invece altre informazioni, come ad esempio: le dimensioni in pixel dell'immagine, il numero di byte usato per ogni pixel, la risoluzione orizzontale e verticale etc... Qui trovate una descrizione dettagliata delle informazioni che è possibile rintracciare negli header di un file bmp. L'ultima struttura dati è infine la mappa dei pixel: ad ogni pixel corrisponde un colore sotto forma di codice (o indice se l'immagine usa una tavolozza di colori). Ogni codice specifica le componenti cromatiche dei colori primari (rosso, verde e blu) miscelati per ottenere il colore assegnato al pixel!
Possiamo leggere e scrivere queste informazioni (sia gli header che la mappa dei pixel) aprendo il file in modalità binaria e leggendo con fread n byte alla volta, a seconda della struttura dati. E' possibile assegnare i byte letti con fread a delle variabili (header1 e header2) e usare queste informazioni all'interno di un programma. Nel codice che propongo, dopo aver letto le informazioni (utili tra l'altro ad allocare in memoria un array di n char, uno per ogni pixel) eseguo prima con fseek il riposizionamento del puntatore all'inizio della mappa dei pixel (usando lo scostamento rimediato con la variabile header1), successivamente eseguo la lettura dei pixel. Il programma termina con la stampa della mappa dei pixel ma può essere esteso ulteriormente per l'editing dell'immagine (in tal caso si accederà al pixel con fwrite).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#pragma pack(2) // usa 2 byte per impachettare la struttura, che è di 14 byte (non è un multiplo del byte)!
/* BITMAPFILEHEADER (14 byte) */
typedef struct {
   unsigned short int bfType;
   unsigned int bfSize;
   unsigned short int bfReserved1,bfReserved2;
   unsigned int bfOffBits;
} BITMAPFILEHEADER;

#pragma pack()
/* BITMAPINFOHEADER (40 byte) */
typedef struct {
   unsigned int biSize;
   int biWidth;
   int biHeight;
   unsigned short int biBitCount;
   unsigned int biCompression;
   unsigned int biSizeImage;
   int biXPelsPerMeter;
   int biYPelsPerMeter;
   unsigned int biClrUsed;
   unsigned int biClrImportant;
} BITMAPINFOHEADER;

/* struttura per un pixel */
typedef struct {
   unsigned char blue;
   unsigned char green;
   unsigned char red;
} PIXEL;

/* codice (binari e esadecimali) relativi ai file bmp */
#define DECBMPCODE 19778
#define HEXBMPCODE 0x4D42

int main (int argc, char*argv[]) {
   FILE *input_file;
   BITMAPFILEHEADER header1;
   BITMAPINFOHEADER header2;
   PIXEL pixel, *image;
   int i,j,n_pixel=0;

   if (argc==1) {
      printf("Specifica il file da usare!\n");
      return -1;
   }
   else {
      if ((input_file=fopen(argv[1],"rb+"))!=NULL) {
         fread(&header1,sizeof(header1),1,input_file);
         if (header1.bfType==DECBMPCODE) {
            printf("Header del file %s:\n",argv[1]);
            printf(" - Tipo di file: %x\n",header1.bfType);
            printf(" - Dimensione del file: %u Kb (%u byte)\n",header1.bfSize/1024,header1.bfSize);
            printf(" - Offset al primo bit nella mappa: %u\n",header1.bfOffBits);
            fread(&header2,sizeof(header2),1,input_file);
            printf("Header delle informazioni:\n");
            printf(" - Dimensione del blocco informazione: %u byte\n",header2.biSize);
            printf(" - Larghezza dell'immagine: %u pixel\n",header2.biWidth);
            printf(" - Altezza dell'immagine: %u pixel\n",header2.biHeight);

            /* Calcolo del padding */
            int pitch = header2.biWidth * 3;
            if (pitch % 4 != 0) pitch += 4 - (pitch % 4);
            int padding = pitch - (header2.biWidth * 3);
            printf(" - Pitch: %u\n", pitch);
            printf(" - Padding: %u\n", padding);

            fseek(input_file,header1.bfOffBits,SEEK_SET);
            image=(PIXEL*)malloc((header2.biWidth*header2.biHeight)*sizeof(PIXEL));
            printf("\nComponenti dei pixel: #pixel(RGB)\n");
            for (i=0;i<header2.biHeight;i++) {
               for (j=0;j<header2.biWidth;j++) {
                  fread(&pixel, sizeof(PIXEL),1,input_file);
                  printf("%2d(%u,%u,%u) ",n_pixel,pixel.red,pixel.green,pixel.blue);
                  image[n_pixel]=pixel;
                  n_pixel++;
               }
               fseek(input_file, padding, SEEK_CUR);
               printf("\n");
            }
            free(image);
         }
         else {
            printf("Il file %s non è un file bmp!\n",argv[1]);
            return -1;
         }
      }
      else {
         printf("Non è stato possibile aprire il file %s!\n",argv[1]);
         return -1;
      }
   }
   return 0;
}


E' possibile verificare il programma con una delle seguenti immagini:



3 commenti:

  1. Ciao, vorrei chiederti alcune cose sul programma che hai scritto.
    Ho provato a passargli un bmp che ha altezza 2 e larchezza 10.
    Come output ottengo la prima riga in mariera corretta, la seconda no.
    L'immagine è costituita solo da pixel rossi, quindi l'output dovrebbe essere di questo tipo:
    0(255,0,0)1(255,0,0)2(255,0,0)3(255,0,0)4(255,0,0)5(255,0,0)6(255,0,0)7(255,0,0)8(255,0,0)9(255,0,0)
    10(255,0,0)11(255,0,0)12(255,0,0)13(255,0,0)14(255,0,0)15(255,0,0)16(255,0,0)17(255,0,0)18(255,0,0)19(255,0,0)

    Mentre con il tuo codice ottengo:
    0(255,0,0) 1(255,0,0) 2(255,0,0) 3(255,0,0) 4(255,0,0) 5(255,0,0) 6(255,0,0) 7(255,0,0) 8(255,0,0) 9(255,0,0)
    10(0,0,0) 11(0,255,0) 12(0,255,0) 13(0,255,0) 14(0,255,0) 15(0,255,0) 16(0,255,0) 17(0,255,0) 18(0,255,0) 19(0,255,0)

    perchè questa anomalia?

    Sto cercando di capire come elaborare una immagine bmp per conteggiare quanti pixel rossi ci sono ed il tuo programma mi sembra un buon punto di inizio.
    Ti chiedo gentimente di aiutarmi, appena avrò finito se vuoi potrei postarti il tuo programma con le relative modifiche.
    Grazie, Marcello

    RispondiElimina
  2. Credo di aver capito l'errore, dopo la prima riga ci sono 4 byte di padding che il programma dovrebbe saltare prima di riprendere a leggere dal file. Ecco perché i successivi valori sono interpretati male. Basta aggiungere l'istruzione fseek(input_file, 2, SEEK_CUR) al termine del ciclo for più interno. Appena posso correggo il codice aggiungendo l'istruzione appena detta.

    RispondiElimina
  3. Ho corretto il codice aggiungendo delle istruzioni per il calcolo del padding da usare nella funzione fseek(). Grazie per la segnalazione ;)

    RispondiElimina