Usare una struttura per rappresentare una immagine

La strategia seguita nelle pagine precedenti per scrivere un file immagine andava bene per immagini semplici. Infatti, quello che si faceva era un doppio ciclo, in cui per ogni punto, si controllava se doveva essere bianco o nero in base alle specifiche di come doveva essere fatta l'immagine. Questo va bene per disegnare un singolo rettangolo, ma si provi a pensare come va modificato il codice se i rettangoli da disegnare sono quattro: l'istruzione di test se un punto è all'interno di essi diventa molto lunga. Si tenga anche presente che l'immagine con quattro rettangoli è molto semplice, e che può servire scrivere immagini molto più complicate.

A meno che l'immagine non sia realmente molto semplice, si usa di solito un'altra strategia: si memorizza l'immagine in una matrice, e ogni volta che si vuole disegnare qualcosa si cambiano gli elementi di questa matrice. Ogni volta che si vuole il file immagine, si stampa la matrice su file.

Per essere più precisi, per rappresentare l'immagine servono due interi che identificano larghezza e altezza, più una matrice grande abbastanza per contenere tutti i pixel. Dal momento che queste tre variabili descrivono complessivamente la stessa cosa, usiamo una struttura. Per la matrice, ipotizziamo che l'immagine non sia più larga di 400×400.

struct ImmagineBW {
  int larghezza;
  int altezza;
  int mat[400][400];
};

Con questa struttura possiamo memorizzare immagini di diverse dimensioni, a condizioni che entrambe le dimensioni siano comprese fra 0 e 400.

Il primo passo da fare è quello di definire una variabile di tipo immagine per rappresentare l'immagine che stiamo costruendo. La seconda cosa da fare è quella di dire quanto è grande l'immagine, ossia mettere i valori giusti di larghezza e altezza nei campi della variabile immagine.

A questo punto, mettiamo 0 in tutti gli elementi significativi della matrice. Questo equivale a ``cancellare'' l'immagine, ossia a definire inizialmente tutti i punti come bianchi. Se non si compie questa operazione la matrice contiene valori non inizializzati, per cui i suoi valori sono indeterminati.

Abbiamo ora una lavagna su cui disegnare. Se per esempio vogliamo disegnare il punto di coordinate x,y, basta mettere a uno l'elemento della matrice di indici x e y. Per il rettangolo, facciamo un doppio ciclo, e in questo modo riusciamo a fare questa operazione per tutti i punti del rettangolo.

Il programma completo matrice.c è qui sotto.

/*
  Definisce il tipo immagine come una matrice.
*/

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


/* definizione del tipo */

struct ImmagineBW {
  int larghezza;
  int altezza;
  int mat[400][400];
};


/*
  crea un file a partire da una matrice
*/

void CreaFileImmagine(char *nomefile, struct ImmagineBW img) {
  FILE *fd;
  int x, y;

		/* apre il file */
  fd=fopen(nomefile, "w");
  if( fd==NULL ) {
    perror("Errore in apertura del file");
    exit(1);
  }


		/* stampa la matrice */
  fprintf(fd, "P1\n");
  fprintf(fd, "%d %d\n", img.larghezza, img.altezza);

  for(y=img.altezza-1; y>=0; y--)
    for(x=0; x<=img.larghezza-1; x++)
      if( img.mat[x][y] )
        fprintf(fd, "1\n");
      else
        fprintf(fd, "0\n");


		/* chiude il file */
  fclose(fd);
}
  

/*
  main
*/

int main() {
  struct ImmagineBW q;
  int x, y;

		/* inizializza la matrice */
  q.larghezza=200;
  q.altezza=100;

  for(x=0; x<=q.larghezza-1; x++)
    for(y=0; y<=q.altezza-1; y++)
      q.mat[x][y]=0;


		/* disegna il rettangolo nella matrice */
  for(x=10; x<=40; x++)
    for(y=10; y<=20; y++)
      q.mat[x][y]=1;


		/* scrive l'immagine su file */
  CreaFileImmagine("matrice.pbm", q);

  return 0;
}

L'immagine che viene generata da questo programma è riportata qui sotto.