Funzioni di disegno

Nel programma precedente abbiamo visto una funzione che permette di scrivere una figura su file. È però possibile anche scrivere delle funzioni che realizzano disegni sulla struttura che rappresenta l'immagine. Una cosa sicuramente utile è scrivere una funzione che inizializza l'immagine, cioè mette i valori di altezza e larghezza nei corrispondenti campi, e riempie la matrice di zeri. Usiamo una funzione che restiuisce una immagine. È infatti possibile relizzare delle funzioni che hanno una struttura come valore di ritorno.

struct ImmagineBW ImmagineVuota(int l, int a) {
  struct ImmagineBW img;
  int x, y;

  img.larghezza=l;
  img.altezza=a;

  for(x=0; x<=l-1; x++)
    for(y=0; y<=a-1; y++)
      img.mat[x][y]=0;

  return img;
}

Questa funzione mette nei campi larghezza e altezza i parametri passati. Inoltre, mette a zero tutti gli elementi significativi della matrice.

Un'altra funzione che può essere utile è quella che disegna una linea orizzontale fra due punti x1, y e x2, y. Questa funzione deve ricevere come parametri le tre coordinate x1, y2, x, ed inoltre la struttura su cui fare il disegno. Si noti che la funzione modifica l'immagine, per cui questa va passata per riferimento. In altre parole, dobbiamo passare l'indirizzo della struttura e non la struttura stessa.

void DisegnaLineaOrizzontale(struct ImmagineBW *pi, int x1, int x2, int y) {
  int x;

  for(x=x1; x<=x2; x++)
    (*pi).mat[x][y]=1;
}

Se si fosse passata l'immagine per valore (ossia il parametro fosse stato struct ImmagineBW img) allora la funzione avrebbe operato con una copia locale della struttura, per cui le modifiche non avrebbero avuto effetto sulla struttura di partenza. In altre parole, chiamando DisegnaLineaOrizzontale(q,...) si sarebbe creata una nuova struttura immagine img, e le modifiche sarebbero state fatte su questa invece che su q. Passando l'indirizzo, siamo sicuri che ogni modifica fatta su *pi viene fatta sulla struttura della quale abbiamo passato l'indirizzo.

È anche possibile definire in modo analogo funzioni di disegno di linee verticali, rettangoli, linee oblique, ecc. L'immagine su cui si opera, dal momento che viene modificata dalla funzione, e vogliamo che le modifiche siano visibili al programma chiamante, va passata per riferimento.

Una volta definite queste funzioni, possiamo realizzare un programma che scrive una immagine con due linee, in questo modo:

int main() {
  struct ImmagineBW q;

                /* inizializza l'immagine */
  q=ImmagineVuota(300, 200);


                /* disegna due linee */
  DisegnaLineaOrizzontale(&q, 10, 180, 20);
  DisegnaLineaVerticale(&q, 150, 50, 150);


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

  return 0;
}

Va notato come, in questo programma, non ci serve sapere né come sono rappresentate le immagini, e nemmeno in che modo funzionano le funzioni di inizializzazione, disegno e stampa su file. Tutto quello che ci serve sapere, di ognuna di queste funzioni, è cosa fa. In altre parole, se qualcun altro vuole scrivere un programma che fa un disegno differente, tutto quello che deve sapere è che prima va chiamata la funzione ImmagineVuota passando le dimensioni della figura, e poi che per disegnare una linea si usano le funzioni DisegnaLineaOrizzontale e DisegnaLineaVerticale, e che alla fine si chiama la funzione CreaFileImmagine per scrivere l'immagine su file. Non gli serve sapere né come è fatta la struttura struct ImmagineBW e nemmeno in che modo sono realizzate le varie funzioni.

Il programma completo confun.c è qui sotto.

/*
  Funzioni per disegnare in una struttura.
*/

#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);
}


/*
  Crea una immagine vuota.
*/

struct ImmagineBW ImmagineVuota(int l, int a) {
  struct ImmagineBW img;
  int x, y;

  img.larghezza=l;
  img.altezza=a;

  for(x=0; x<=l-1; x++)
    for(y=0; y<=a-1; y++)
      img.mat[x][y]=0;

  return img;
}


/*
  Disegna una linea orizzontale.
*/

void DisegnaLineaOrizzontale(struct ImmagineBW *pi, int x1, int x2, int y) {
  int x;

  for(x=x1; x<=x2; x++)
    (*pi).mat[x][y]=1;
}


/*
  Disegna una linea verticale.
*/

void DisegnaLineaVerticale(struct ImmagineBW *pi, int x, int y1, int y2) {
  int y;

  for(y=y1; y<=y2; y++)
    (*pi).mat[x][y]=1;
}


/*
  main
*/

int main() {
  struct ImmagineBW q;

		/* inizializza l'immagine */
  q=ImmagineVuota(300, 200);


		/* disegna due linee */
  DisegnaLineaOrizzontale(&q, 10, 180, 20);
  DisegnaLineaVerticale(&q, 150, 50, 150);


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

  return 0;
}


Questo programma genera l'immagine riportata qui sotto.