// Traitement numérique du signal
// Projet 2
//
// Binarisation d'image par diffusion d'erreur
//
// Christophe Boyanique
// Emmanuel Pinard
// Mars 1999
//
//
// Transformation d'une image 24 bits (ppm) en image 8 bits (ppm)
//
// Binarise sur 8 bits (3/3/2 bits par composante):
// - 3bits: 0 36 73 109 146 182 219 255
// - 2bits: 0 85 170 255


#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, 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)
{

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

  if (argv[1][1] == 'b')
    return convert(RAW, HEADER, argv[2], argv[3]);
  else if (argv[1][1] == 'B')
    return convert(RAW, NOHEAD, argv[2], argv[3]);
  else if (argv[1][1] == 'f')
    return convert(FLOYD, HEADER, argv[2], argv[3]);
  else if (argv[1][1] == 'F')
    return convert(FLOYD, NOHEAD, 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 input.ppm output.ppm (Binarisation)\n", name);
  printf("       %s -f input.ppm output.ppm (Floyd-Steinberg)\n", name);

}


// Fonction de conversion par la binarisation
// Fonction de conversion par l'algorithme de Floyd-Steinberg
//
// Les paramètres sont:
// char *input:   nom du fichier d'entrée
// char *output:  nom du fichier de sortie
//
int convert(int method, int head, 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
  int c;                 // indice de boucle for
  double rsb,vsb,bsb,sb; // Rapport signal bruit
  double xsb=0,ysb=0,xr=0,yr=0,xv=0,yv=0,xb=0,yb=0;


  if (method == FLOYD)
    printf("True Color 24 bits to True Color 8 bits (Floyd/Steinberg)\n");
  else
    printf("True Color 24 bits to True Color 8 bits (Binarisation)\n");

  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]!='3')
  {
    printf("unknown!\n");
    fclose(hin);
    return BAD_FILE;
  }
  printf("ASCII PPM (P3)\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("Composant 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,"P3\n# CREATOR: ppm24to8 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,"%d %d\n%d\n", width, height, 255);
    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 * (long)sizeof(int);
    adr  = (long)malloc( 6L * len );  // Buffer
    if (adr <= 0L)
    {
      printf("Memory allocation error!\n");
      fclose(hout);
      fclose(hin);
      return WRITE_ERROR;
    }
    clear((void *)adr, 6L * len);   // Efface le 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
      adr1 = (int *)adr;
      adr2 = (int *)( adr + (3L * len) );
      memcpy((void *)adr1, (void *)adr2, 3L * len);
      clear((void *)adr2, 3L * len);
    }

    for (i=0; i<width; i++)       // Balayage par colonne
    {
      for (c=0; c<3; c++)         // Balayage par composante
      {
        if (method == FLOYD)
        {
          adr1 = (int *)adr;
          adr2 = (int *)( adr + (3L * len) + ((long)c * len) );
        }

        value = read_number(hin);   // Lit la valeur du pixel
        if (value < 0)
        {
          printf("\nread error!\n");
          if (method == FLOYD)
            free((void *)adr);
          fclose(hout);
          fclose(hin);
          return READ_ERROR;
        }

        // Calcul de x numerateur du rapport signal/bruit
        xsb += value*value;
        if (cnt%3 == 0)
          xr += value*value;
        else if (cnt%3 == 1)
          xv += value*value;
        else if (cnt%3 == 2)
          xb += 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;
        }

        if ( c != 2 )
        {
          // Binarise le pixel sur 3 bits (8 plages de valeurs)
          if (value > 237)
            nvalue = 255;
          else if (value > 200)
            nvalue = 219;
          else if (value > 164)
            nvalue = 182;
          else if (value > 127)
            nvalue = 146;
          else if (value > 91)
            nvalue = 109;
          else if (value > 54)
            nvalue = 73;
          else if (value > 18)
            nvalue = 36;
          else
            nvalue = 0;
        }
        else
        {
          // Binarise le pixel sur 2 bits (4 plages de valeurs)
          if (value > 212)
            nvalue = 255;
          else if (value > 127)
            nvalue = 170;
          else if (value > 42)
            nvalue = 85;
          else
            nvalue = 0;
        }

        // Calcul de y numerateur du rapport signal/bruit
        ysb += (value - nvalue)*(value - nvalue);
        if (cnt%3 == 0)
          yr += (value - nvalue)*(value - nvalue);
        else if (cnt%3 == 1)
          yv += (value - nvalue)*(value - nvalue);
        else if (cnt%3 == 2)
          yb += (value - nvalue)*(value - nvalue);

        if (method == FLOYD)
        {
          // On calcule la différence à appliquer et la valeur binarisée:
          diff = value - nvalue;
          absdiff = abs(diff);

          // 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%16L == 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");
          if (method == FLOYD)
            free((void *)adr);
          fclose(hout);
          fclose(hin);
          return WRITE_ERROR;
        }

      }
    }

  }

  printf(" Done\n");

  sb  = 10 * log10( xsb / ysb );
  rsb = 10 * log10( xr / yr );
  vsb = 10 * log10( xv / yv );
  bsb = 10 * log10( xb / yb );

  printf("Rapport Signal/Bruit: %.2f\n", sb);
  printf("    R: %.2f\n", rsb);
  printf("    V: %.2f\n", vsb);
  printf("    B: %.2f\n", bsb);

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


