// Traitement numérique du signal
// Projet 2
//
// Binarisation d'image par diffusion d'erreur
//
// Christophe Boyanique
// Emmanuel Pinard
// Mars 1999
//
//
// Transformation d'une image 256 niveaux de gris (pgm) en image
// monochrome (pbm)


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


// Definition de macros:
#define min(a,b)     (a<b)?(a):(b)
#define abs(a)       (a<0)?(-a):(a)


// Definitions des codes de retour du programme:
#define NO_ERR       0
#define BAD_PARAMS   1
#define NO_FILE      2
#define BAD_FILE     3
#define READ_ERROR   4
#define WRITE_ERROR  5

#define NOHEAD       0
#define HEADER       1

#define RAW          0
#define FLOYD        1


// Prototypage des fonctions:
void usage(char *name);
int convert(int method, int head, int mid, char *input, char *output);
int read_number(FILE *handle);
int write_number(FILE *handle, int n, int ligne);
void clear(void *adr, size_t len);



// Fonction principale du programme
//
int main(int argc, char **argv)
{
  int dum;
  int mid = 127;

  if (argc!=4)
  {
    usage(argv[0]);
    return BAD_PARAMS;
  }

  dum = atoi((char *)&argv[1][2]);
  if (dum>0 && dum<255)
    mid = dum;

  if (argv[1][1] == 'b')
    return convert(RAW, HEADER, mid, argv[2], argv[3]);
  else if (argv[1][1] == 'B')
    return convert(RAW, NOHEAD, mid, argv[2], argv[3]);
  else if (argv[1][1] == 'f')
    return convert(FLOYD, HEADER, mid, argv[2], argv[3]);
  else if (argv[1][1] == 'F')
    return convert(FLOYD, NOHEAD, mid, argv[2], argv[3]);
  else
  {
    usage(argv[0]);
    return BAD_PARAMS;
  }

}



// Fonction usage() affichant la syntaxe du programme
//
void usage(char *name)
{
  printf("Usage: %s -b[num] input.pgm output.pbm (Binarisation)\n", name);
  printf("       %s -f[num] input.pgm output.pbm (Floyd-Steinberg)\n", name);

}


// Fonction de conversion par la binarisation
// Fonction de conversion par l'algorithme de Floyd-Steinberg
//
// Les paramètres sont:
// int mid:       seuil de binarisation
// char *input:   nom du fichier d'entrée
// char *output:  nom du fichier de sortie
//
int convert(int method, int head, int mid, char *input, char *output)
{
  FILE *hin, *hout;      // Handles de fichier
  char buf[80];          // Buffer de lecture
  int err;               // Retour d'erreur de fonctions
  int width, height;     // Dimensions de l'image
  int value, nvalue;     // Valeur d'un pixel
  long cnt;              // Compteur de pixel
  long adr;              // Buffer alloué dynamiquement pour la
  int *adr1, *adr2;      // diffusion d'erreur
  long len;
  int diff;              // Erreur à propager
  int sumdiff, absdiff;  // Variables temporaires d'erreur 
  int corr[4];           // Vecteur d'erreurs réparties
  int i, j;              // Indice de boucle for
  double xsb=0,ysb=0,sb; // Rapport signal bruit

  if (method == FLOYD)
    printf("Grayscale to monochrome (Floyd/Steinberg), middle @ %d/256\n", mid);
  else
    printf("Grayscale to monochrome (Binarisation), middle @ %d/256\n", mid);

  printf("Opening file %s for reading... ", input);

  hin = fopen(input, "r");        // Ouverture du fichier en lecture
  if (hin == NULL)
  {
    printf("error!\n");
    return NO_FILE;
  }
  printf("Ok\n");

  printf("File type is: ");       // Détermination du type de fichier
  err = (int)fread((void *)buf, (size_t)1, (size_t)2, hin);
  if (err != 2 || buf[0]!='P' || buf[1]!='2')
  {
    printf("unknown!\n");
    fclose(hin);
    return BAD_FILE;
  }
  printf("ASCII PGM (P2)\n");

  printf("Image size is: ");      // Détermination de la taille de l'image
  width = read_number(hin);
  if (width<=0)
  {
    printf("error!\n");
    fclose(hin);
    return BAD_FILE;
  }
  height = read_number(hin);
  if (height<=0)
  {
    printf("error!\n");
    fclose(hin);
    return BAD_FILE;
  }
  printf("%dx%d\n", width, height);

  printf("Grayscale level: ");    // Détermination de la résolution de l'image
  if ( read_number(hin) != 255)
  {
    printf("not 256!\n");
    fclose(hin);
    return BAD_FILE;
  }
  printf("256\n");

  printf("Opening file %s for writing... ", output);
  hout = fopen(output, "w");      // Ouverture du fichier de sortie
  if (hout == NULL)
  {
    printf("error!\n");
    fclose(hin);
    return WRITE_ERROR;
  }
  printf("Ok\n");

  if (head == HEADER)
  {
    printf("Writing headers ");     // Ecriture du header du fichier
    sprintf(buf,"P1\n# CREATOR: pgm2pnm Christophe Boyanique/Emmanuel Pinard\n");
    err = (int)fwrite((const void *)buf, (size_t)1, strlen(buf), hout);
    if (err != strlen(buf))
    {
      printf("error!\n");
      fclose(hout);
      fclose(hin);
      return WRITE_ERROR;
    }
    sprintf(buf,"%3d %3d\n", width, height);
    err = (int)fwrite((const void *)buf, (size_t)1, strlen(buf), hout);
    if (err != strlen(buf))
    {
      printf("error!\n");
      fclose(hout);
      fclose(hin);
      return WRITE_ERROR;
    }
    printf("Ok\n");
  }

  printf("Converting file... ");
  cnt = 0L;                       // Initialise le compteur de pixel

  if (method == FLOYD)
  {
    // Allocation d'un buffer pour stocker la diffusion d'erreur.
    // Le buffer contient 2 lignes d'images
    len = (long)width * sizeof(int);
    adr  = (long)malloc( 2L * len );   // 1ère ligne du buffer (ligne courante)
    if (adr <= 0)
    {
      printf("Memory allocation error!\n");
      fclose(hout);
      fclose(hin);
      return WRITE_ERROR;
    }

    adr1 = (int *)adr;                 // 1ère ligne du buffer (ligne courante)
    adr2 = (int *)((long)adr1 + len);  // 2ème ligne du buffer
    clear((void *)adr1, (int)len);  // Efface la 1ère ligne du buffer
    clear((void *)adr2, (int)len);  // Efface la 2ème ligne du buffer
  }

  for (j=0; j<height; j++)        // Balayage par ligne
  {
    if (method == FLOYD)
    {
      // On décale la 2ème ligne vers le haut (à la place de la 1ère)
      // et on efface pour commencer une nouvelle 2ème ligne
      memcpy((void *)adr1, (void *)adr2, (int)len);
      clear((void *)adr2, (int)len);
    }

    for (i=0; i<width; i++)       // Balayage par colonne
    {
      value = read_number(hin);   // Lit la valeur du pixel
      if (value < 0)
      {
        printf("\nread error!\n");
        free((void *)adr);
        fclose(hout);
        fclose(hin);
        return READ_ERROR;
      }

      // Calcul de x numerateur du rapport signal/bruit
      xsb += value*value;

      if (method == FLOYD)
      {
        // On additionne l'erreur à la valeur lue du pixel
        value += adr1[i];
        if (value > 255)
          value = 255;
        else if (value < 0)
          value = 0;
      }

      // On calcule la différence à appliquer et la valeur binarisée:
      if (value>mid)
      {
        diff = value - 255;
        absdiff = -diff;          // absdiff contient la valeur absolue
        nvalue = 0;               // de l'erreur à diffuser
      }
      else
      {
        diff = value - 0;
        absdiff = diff;
        nvalue = 1;
      }

      // Calcul de y numerateur du rapport signal/bruit
      ysb += (value - (255*(1-nvalue)))*(value - (255*(1-nvalue)));

      if (method == FLOYD)
      {
        // Efface le vecteur d'erreurs
        corr[0] = corr[1] = corr[2] = corr[3] = 0;

        // sumdiff contient l'erreur à répartir, à chaque répartition
        // d'erreur sa valeur diminue
        sumdiff = absdiff;

        // Calcul du vecteur d'erreur

        // Répartit 3/16 à i-1,j+1:
        if ( i < width-1 )
        {
          corr[0] = min(sumdiff, absdiff * 7 / 16);
          sumdiff -= corr[0];
        }

        if ( j < height-1 )
        {
          // Répartit 5/16 à i,j+1:
          corr[2] = min(sumdiff, absdiff * 5 / 16);
          sumdiff -= corr[2];

          // Répartit 3/16 à i-1,j+1:
          if ( i > 0)
          {
            corr[1] = min(sumdiff, absdiff * 3 / 16);
            sumdiff -= corr[1];
          }

          // Répartit 1/16 à i+1,j+1:
          if ( i < width-1 )
          {
            corr[3] = min(sumdiff, absdiff * 1 / 16);
            sumdiff -= corr[3];
          }
        }

        // Retablit le signe de l'erreur
        if ( diff < 0 )
        {
          corr[0] *= -1;
          corr[1] *= -1;
          corr[2] *= -1;
          corr[3] *= -1;
        }

        // Répartition effective de l'erreur

        // Répartit 3/16 à i-1,j+1:
        if ( i < width-1 )
          adr1[i+1] += corr[0];

        if ( j < height-1 )
        {
          // Répartit 5/16 à i,j+1:
          adr2[i] += corr[2];

          // Répartit 3/16 à i-1,j+1:
          if ( i > 0 )
            adr2[i-1] += corr[1];

          // Répartit 1/16 à i+1,j+1:
          if ( i < width-1 )
            adr2[i+1] += corr[3];
        }
      }
      cnt++;                          // Incrémente le compteur
      if (cnt%35L == 0)               // Retour à la ligne
        err = write_number(hout, nvalue, 1);
      else                            // Pas de retour à la ligne
        err = write_number(hout, nvalue, 0);
      if (err != nvalue)
      {
        printf("\nwrite error!\n");
        free((void *)adr);
        fclose(hout);
        fclose(hin);
        return WRITE_ERROR;
      }
    }

  }

  printf("Done\n");

  sb  = 10 * log10( xsb / ysb );
  printf("Rapport Signal/Bruit: %.2f\n", sb);

  if (method == FLOYD)
    free((void *)adr);            // Desallocation du buffer
  fclose(hout);                   // Fermeture des fichiers
  fclose(hin);

  return NO_ERR;
}



// Fonction de lecture d'un nombre dans un fichier
//
int read_number(FILE *handle)
{
  int n = 0;                      // Nombre à renvoyer
  int ok = 0;                     // Drapeau d'erreur
  unsigned char c;                // Charactere lu dans le fichier

  // Première boucle destinée à purger tous les caractères inutiles
  // (fin de ligne, espace, commentaires...)
  do
  {
    fread((void *)&c, (size_t)1, (size_t)1, handle);
    if ( c == '#' )               // Cas d'un commentaire:
    {                             // il faut de nouveau boucler jusqu'à
      do                          // la fin de la ligne
      {
        fread((void *)&c, (size_t)1, (size_t)1, handle);
      } while ( c!='\n' && !feof(handle) );
    }
  } while ( ( c<'0' || c>'9') && !feof(handle) );

  // Deuxième boucle lisant le nombre une fois que l'on est bien
  // positionné dans le fichier
  while ( ( c>='0' && c<='9') && !feof(handle) )
  {
    ok = 1;
    n = n*10 + (int)c - (int)'0';
    fread((void *)&c, (size_t)1, (size_t)1, handle);
  }

  if (ok == 1)
    return n;

  return -1;
}



// Fonction d'écriture d'un nombre dans un fichier
//
int write_number(FILE *handle, int n, int ligne)
{
  char buf[6];                    // Buffer d'écriture
  int err;                        // Drapeau d'erreur

  if (n>999)                      // Max: nombre à 3 chiffres
    return -1;

  if (ligne != 0)                 // Retour à la ligne
    sprintf(buf, "%d\n", n);
  else                            // sinon espace pour prochain nombre
    sprintf(buf, "%d ", n);

  err = fwrite((const void *)buf, (size_t)1, (size_t)strlen(buf), handle);

  if ( err != strlen(buf) );
    return n;

  return -1;
}



// Efface une zone mémoire
//
void clear(void *adr, size_t len)
{
  char *p = (char *)adr;
  size_t i;

  for (i=0; i<len; i++)
    *p++=0;
}


