/*
 *   rgb2hsv.c
 *
 *   Mark J. Stock, with help, 1998-09-25
 *
 *   This program will convert a PPM image in RGB to a PPM image
 *   with the r, g, and b channels representing hue, saturation,
 *   and value.
 *
 *   Hopefully, I may use this to convert heightfield data for
 *   various planets and moons available online to real, 8- or
 *   16-bit grayscale data. In addition, I hope to be able to 
 *   remove the ugly shadowing so commonly done on these images.
 *
 */


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

#define MAX_SIZE 1834

typedef struct color_data {
   unsigned char r;       /* red color 0-255 */
   unsigned char g;       /* green color 0-255 */
   unsigned char b;       /* blue color 0-255 */
} COLOR;

COLOR data[MAX_SIZE][MAX_SIZE];

int usebin = 1;		/* if ==1, write binary PPMs */
int inheight, inwidth;


void main(int argc, char **argv) {

   int i, j;
   char infile[80];
   char outfile[80] = "-";
   char progname[80];
   COLOR this;		/* regularly-used color */

   /* int read_ppm(char [80]); */
   /* int write_ppm(char [80]); */
   void RGBtoHSV(COLOR, COLOR*);

   /*
    * Parse command-line args
    */
   (void) strcpy(progname,argv[0]);
   if (argc < 2) (void) Usage(progname,0);
   if (strncmp(argv[1], "-help", 2) == 0)
      (void) Usage(progname,0);
   (void) strcpy(infile,argv[1]);
   for (i=2; i<argc; i++) {
      if (strncmp(argv[i], "-help", 2) == 0)
         (void) Usage(progname,0);
      else if (strncmp(argv[i], "-outfile", 2) == 0)
         (void) strcpy(outfile,argv[++i]); 
      else
         (void) Usage(progname,0);
   }

   /* initialize data */
   for (i=0; i<MAX_SIZE; i++) {
      for (j=0; j<MAX_SIZE; j++) {
         data[i][j].r = 0;
         data[i][j].g = 0;
         data[i][j].b = 0;
      }
   }


   /* read in the data */
   (void) read_ppm(infile);

   /* do the conversion to HSV */
   for (i=0; i<inheight; i++) {
      for (j=0; j<inwidth; j++) {
         (void) RGBtoHSV(data[i][j],&this);
         data[i][j] = this;
      }
   }

   /* write the new data out to a file */
   (void) write_ppm(outfile);

   exit(0);
}




/* r,g,b values are from 0 to 1
// h = [0,360], s = [0,1], v = [0,1]
//              if s == 0, then h = -1 (undefined) */

void RGBtoHSV( COLOR rgb, COLOR *hsv )
{
   /* COLOR hsv; */
   int min, max, delta;
   float tempf;

   /* note: h is r; s is g; v is b */

   /* min = MIN( rgb.r, rgb.g, rgb.b ); */
   /* max = MAX( rgb.r, rgb.g, rgb.b ); */
   if (rgb.r > rgb.g && rgb.r > rgb.b) {
      max = rgb.r;
      if (rgb.g > rgb.b) {
         min = rgb.b;
      } else {
         min = rgb.g;
      }
   } else if (rgb.g > rgb.r && rgb.g > rgb.b){
      max = rgb.g;
      if (rgb.r > rgb.b) {
         min = rgb.b;
      } else {
         min = rgb.r;
      }
   } else if (rgb.b > rgb.r && rgb.b > rgb.g){
      max = rgb.b;
      if (rgb.r > rgb.g) {
         min = rgb.g;
      } else {
         min = rgb.r;
      }
   }
   hsv->b = max;

   delta = max - min;

   if( max != 0 )
      hsv->g = (256.0*delta) / max;
   else {
      /* rgb.r = rgb.g = rgb.b = 0                // s = 0, h is undefined */
      hsv->g = 0;
      /* although h and v to be undefined, they must have a legal value, 0 */
      hsv->r = 0;
      return;
   }

   /* original offsets are 0, 2, 4; I made them -1, 1, 3 to fit map data */
   if( rgb.r == max )
      tempf = -1.0 + ( (float)rgb.g - rgb.b )/(float)delta;         /* between yellow & magenta */
   else if( rgb.g == max )
      tempf = 1.0 + ( (float)rgb.b - rgb.r )/(float)delta;     /* between cyan & yellow */
   else
      tempf = 3.0 + ( (float)rgb.r - rgb.g )/(float)delta;     /* between magenta & cyan */

   tempf = tempf * 60.0;
   if( tempf < 0 )
      tempf += 360.0;
   if (tempf > 360.0)
      tempf -= 360.0;
   tempf *= (256.0/360.0);
   hsv->r = (unsigned char)tempf;

   return;
}



/* ---------- Routines for writing ------------------------------------- */


/*
 * Write a Poskanzer PixMap (.PPM) file of the imagemap
 */
int write_ppm(char filename[80]) {

   int i,j;
   COLOR this;
   FILE *fp;
   char use_stdout[2] = "-";
   void write_error(char [80],int);

   /* check to see if user put a .ppm at the end */

   /* open the .ppm file for writing (or write to stdout) */
   if (use_stdout[0] == filename[0]) {
      fp = stdout;
   } else {
      fp = fopen(filename,"w");
      if (fp==NULL) {
         fprintf(stderr,"Could not open output file %s\n",filename);
         exit(0);
      }
   }

   /* write a header */
   if (usebin) {
      fprintf(fp,"P6\n");
   } else {
      fprintf(fp,"P3\n");
   }
   fprintf(fp,"%d %d %d\n",inwidth, inheight, 255);

   /* write each data point as an int to the file */
   for (i=0; i<inheight; i++) {
      for (j=0; j<inwidth; j++) {
         this = data[i][j];
         if (usebin) {
            putc((char)this.r,fp);
            putc((char)this.g,fp);
            putc((char)this.b,fp);
         } else {
            fprintf(fp,"%d %d %d\n",this.r,this.g,this.b);
         }
      }
   }

   /* close the file before returning to main */
   (void) fclose(fp);

   /* everything went OK, return zero */
   return(0);
}


/* ---------- Routines for reading ------------------------------------- */

/*
 * Read a Poskanzer PixMap (.PPM) file and save in array data[][]
 * This subroutine uses code from John Beale's er1u/inout.c
 */
int read_ppm(char filename[80]) {

   int i,j;
   int ix,iy;
   int npoints;            /* # points that should be in file */
   int points_read;        /* # points read from hf datafile */
   int xsize, ysize, maxval;
   int raw_val;
   char sbuf[512],dummy[2];
   char *junk;
   unsigned char fbin;                            /* PPM binary format flag */
   unsigned char oneb;                            /* one byte from file */
   FILE *fp;
   void write_error(char [80],int);

   /* open the .ppm file for reading */
   fp = fopen(filename,"rb");
   if (fp==NULL) {
      fprintf(stderr,"Could not open input file %s\n",filename);
      exit(0);
   }
   fprintf(stderr,"Opening file %s\n",filename);

   /* read the header */
   if (fread(&sbuf,sizeof(char),2,fp) != 2) {
      fprintf(stderr,"   Could not read file, exiting.");
      return(1);	/* unsuccessful */
   } else {
      sbuf[2] = 0x00;
      fprintf(stderr,"   Initial file string: %s\n",sbuf);
   }

   fbin = 1;		/* assume PPM binary */
   if (strcmp(sbuf,"P6") == 0) fbin = 1; /* PGM binary format */

   if ((strcmp(sbuf,"P6") != 0) && (strcmp(sbuf,"P3") != 0)) {
      fclose(fp);	/* close file, suck. */
      return(1);	/* unsuccessful */
   }

   /* file is PPM, either P6 (binary) or P3 (ascii) */

   while ((fscanf(fp,"%s",sbuf) != EOF) && sbuf[0] == '#') {
      /* fprintf(stderr,"comment:"); */
      fscanf(fp,"%[^\n]",sbuf);  /* read comment beyond '#' */
      fscanf(fp,"%[\n]",dummy);  /* read newline */
      fprintf(stderr,"   #%s\n",sbuf);            /* display comment */
   }

   xsize = (int) strtoul(sbuf, &junk, 10); /* cvt. xsize of array */
   fscanf(fp,"%s",sbuf);                 /* read ysize of array */
   ysize = (int) strtoul(sbuf, &junk, 10); /* cvt. ysize */
   fscanf(fp,"%s",sbuf);               /* read maxval of array */
   maxval = strtod(sbuf, &junk);

   fprintf(stderr,"   xsize %d  ysize %d  maxval %d\n",xsize,ysize,maxval);

   /* check size, make sure its not too big */
   if (xsize > MAX_SIZE) write_error("Input image width is too large.",1);
   if (ysize > MAX_SIZE) write_error("Input image height is too large.",1);

   points_read = 0;              /* haven't read any data points yet */
   npoints = xsize*ysize;
   ix = 0;
   iy = 0;                       /* fill array 0..xsize-1, 0..ysize-1 */

   fprintf(stderr,"   reading in...");
   if (fbin) {
      /* fread(&oneb,sizeof(unsigned char),1,fp);	/* read that line feed */
      /* fprintf(stderr,"read the one line feed\n");
      fflush(stderr); */
      while ((fread(&oneb,sizeof(unsigned char),1,fp) == 1) && (points_read < npoints) ) {
         /* fprintf(stderr,"read in one pixel - red (%d)\n",oneb);
         fflush(stderr); */
         data[iy][ix].r = (unsigned char) oneb;
         fread(&oneb,sizeof(unsigned char),1,fp);
         data[iy][ix].g = (unsigned char) oneb;
         fread(&oneb,sizeof(unsigned char),1,fp);
         data[iy][ix].b = (unsigned char) oneb;
         if (++ix > xsize-1) {
            ix = 0;
            iy++;
         }
         ++points_read;
      } /* end while not end-of-file */
   } else {
      while ((fscanf(fp,"%s",sbuf) != EOF) && (points_read < npoints) ) {
         /* a = strtod( sbuf, &junk); */
         data[iy][ix].r = (unsigned char)atoi(sbuf);
         fscanf(fp,"%s",sbuf);
         data[iy][ix].g = (unsigned char)atoi(sbuf);
         fscanf(fp,"%s",sbuf);
         data[iy][ix].b = (unsigned char)atoi(sbuf);
         if (++ix > xsize-1) {
            ix = 0;
            iy++;
         }
         points_read++;
      }
   } /* end if fbin */

   fprintf(stderr,"%lu elements read.\n",points_read);
   if (points_read != npoints) {
      fprintf(stderr,"   Warning: %lu points missing.\n",npoints-points_read);
   }
   fclose(fp);
   fprintf(stderr,"   Files closed.\n");

   inheight = ysize;
   inwidth = xsize;

   /* everything went OK, return zero */
   return(0);
}


void write_error(char message[80],int code) {

   fprintf(stderr,"%s\n",message);
   exit(code);
}


int Usage(char progname[80],int status) {

   /* Usage for rgb2hsv */
   static char **cpp, *help_message[] =
   {
       "where options include:",
       "  -help          writes this help information",
       "  -outfile name  glacier thickness output file (ASCII PGM)",
       " ",
       "Options may be abbreviated to an unambiguous length.",
       NULL
   };

   fprintf(stderr, "usage:\n  %s infile [-options ...]\n\n", progname);
   for (cpp = help_message; *cpp; cpp++)
      fprintf(stderr, "%s\n", *cpp);
   exit(status);
   return(0);
}

