//
//	Take the PNG generated by jc_pack_raw, apply gamma correction,
//	interpolate the colours between the sampled pixels, and
//	generate a viewable output file.
//

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <math.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define PROGRAM "jc_expand"

void 
error(char *fmt, ...) {
   va_list ap;
   va_start(ap, fmt);
   fprintf(stderr, PROGRAM ": ");
   vfprintf(stderr, fmt, ap);
   fprintf(stderr, "\n");
   exit(1);
}

void *Alloc(int size) {
   void *vp= calloc(1, size);
   if (!vp) error("Out of memory");
   return vp;
}

#define NL "\n"

void 
usage() {
   error("Usage:" NL
	 NL PROGRAM " <in-file> <out-file>"
	 NL PROGRAM " <gamma-level> <in-file> <out-file>"
	 NL
	 NL "<gamma-level> defaults to 2.5"
	 NL "<in-file> may have extension .raw or .png"
	 NL "  .raw for raw dumps from the JamCam"
	 NL "  .png for raw dumps processed into quadrants by jc_pack_raw"
	 NL "<out-file> may have extension .png, .jpg or .pnm"
	 NL
	 NL "Gamma-processing is done on the V out of HSV, not on the RGB"
	 NL "values.  This gives much more vibrant colours."
	 NL
	 );
}

//
//	Globals
//

typedef unsigned char uchar;

int sx, sy;	// Image size
int sx2, sy2;	// Half-size
uchar *idata;	// Input data
uchar *odata;	// Output data (RGB format)

//
//	Load a binary file into memory
//

char *
load_file(char *fnam, int *lenp) {
   int fd, len;
   char *rv;

   fd= open(fnam, O_RDONLY);
   if (fd < 0) error("Can't open file: %s", fnam);

   len= lseek(fd, 0, SEEK_END);
   if (len < 0) error("Can't find file length: %s", fnam);

   lseek(fd, 0, SEEK_SET);

   rv= Alloc(len);
   if (len != read(fd, rv, len) ||
       0 != close(fd))
      error("Read error on file: %s", fnam);

   *lenp= len;
   return rv;
}

//
//	Read the input file in
//

void 
read_image(char *fnam) {
   FILE *in;
   char buf[1024];
   char ch;
   char *ext= strchr(fnam, 0) - 4;
   if (ext < fnam) ext= fnam;

   if (0 == strcmp(ext, ".raw")) {
      int len;
      char *tmp= load_file(fnam, &len);
      if (len == 261600) {
         sx= 600; sy= 436;
      } else if (len == 76800) {
         sx= 320; sy= 240;
      } else {
         error("Unknown JamCam raw file length: %d", len);
      }
      
      idata= Alloc(sx*sy);
      {
	 int xx, yy, ox, oy;
	 for (xx= 0; xx<sx; xx++) {
	    for (yy= 0; yy<sy; yy++) {
	       ox= (xx >> 1) + ((xx&1) ? sx/2 : 0);
	       oy= (yy >> 1) + ((yy&1) ? sy/2 : 0);
	       idata[ox+oy*sx]= tmp[xx+yy*sx];
	    }
	 }
      }
      free(tmp);
   } else if (0 == strcmp(ext, ".png")) {
      snprintf(buf, sizeof(buf), "pngtopnm %s", fnam);
      in= popen(buf, "r");
      if (!in) error("Error starting pngtopnm to read the input file");
      
      if (3 != fscanf(in, "P5 %d %d 255%c", &sx, &sy, &ch) ||
	  ch != '\n')
	 error("Read error or bad data in pngtopnm output");
      
      idata= Alloc(sx*sy);
      if (1 != fread(idata, sx*sy, 1, in) ||
	  pclose(in))
	 error("Read error or bad data in pngtopnm output");
   } else {
      error("Unknown extension on input file: %s", ext);
   }
   
   sx2= sx/2;
   sy2= sy/2;
}


//
//	Get a pixel value from one of the quadrants (0-3:GRBG)
//

inline int
get0(int xx, int yy) {
   return (xx < 0 || xx >= sx2 || yy < 0 || yy >= sy2) ? 0 : idata[xx+yy*sx];
}
inline int 
get1(int xx, int yy) {
   return (xx < 0 || xx >= sx2 || yy < 0 || yy >= sy2) ? 0 : idata[xx+sx2+yy*sx];
}
inline int 
get2(int xx, int yy) {
   return (xx < 0 || xx >= sx2 || yy < 0 || yy >= sy2) ? 0 : idata[xx+(yy+sy2)*sx];
}
inline int 
get3(int xx, int yy) {
   return (xx < 0 || xx >= sx2 || yy < 0 || yy >= sy2) ? 0 : idata[xx+sx2+(yy+sy2)*sx];
}

//
//	HSV <-> RGB conversion
//
//	Hue stored as 0 to 24576 (0 to 360 degrees, 360 <=> 4096*6)
//	Everything else stored on a 12-bit scale (0 to 4095)
//
//	Conversions occur on the following global variables:
//

int hh, ss, vv;
int rr, gg, bb;

#define DS(xx) ((xx) >> 12)

void
hsv2rgb() {
   int j= ((hh >> 12) + 60) % 6;
   int f= (hh & 4095);
   int v= vv;
   int p= DS(vv * (4096 - ss));
   int q= DS(vv * (4096 - DS(ss*f)));
   int t= DS(vv * (4096 - DS(ss*(4096-f))));
   
   switch (j) {
    case 0:  rr = v;  gg = t;  bb = p;  break;
    case 1:  rr = q;  gg = v;  bb = p;  break;
    case 2:  rr = p;  gg = v;  bb = t;  break;
    case 3:  rr = p;  gg = q;  bb = v;  break;
    case 4:  rr = t;  gg = p;  bb = v;  break;
    case 5:  rr = v;  gg = p;  bb = q;  break;
   }
}

#undef DS

void
rgb2hsv() {
   int min= 4095;
   int max= 0;
   int del;

   if (rr < min) min= rr;
   if (gg < min) min= gg;
   if (bb < min) min= bb;
   if (rr > max) max= rr;
   if (gg > max) max= gg;
   if (bb > max) max= bb;
   del= max-min;

   vv= max;
   ss= max ? del*4096/max : 0;
   hh= 0;

   if (ss) {
      int rc= (max - rr) * 4096 / del;
      int gc= (max - gg) * 4096 / del;
      int bc= (max - bb) * 4096 / del;
      
      if      (rr==max) hh= bc - gc;
      else if (gg==max) hh= 2*4096 + rc - bc;
      else if (bb==max) hh= 4*4096 + gc - rc;

      hh= (hh + 24576) % 24576;
   }
}


//
//	Gamma correction, in terms of HSV
//
      
int gtab[4096];

inline void 
gamma_correct() {
   rgb2hsv();
   if (vv > 4095) vv= 4095;
   if (vv < 0) vv= 0;
   vv= gtab[vv];
   hsv2rgb();
}   

//
//	Main routine
//

int 
main(int ac, char **av) {
   double gamma= 2.5;
   char dmy;
   int a;

   ac--; av++;

   if (ac == 3) {
      if (1 != sscanf(av[0], "%lf %c", &gamma, &dmy)) usage();
      ac--; av++;
   }

   if (ac != 2) usage();

   // Prepare gamma table
   gamma= 1.0/gamma;
   for (a= 0; a<4096; a++) 
      gtab[a]= (int)floor(0.5 + 4095.0 * pow(a * (1.0/4095.0), gamma));

   // Load up input file into idata
   read_image(av[0]);

   // Do the expansion
   odata= Alloc(3*sx*sy);
   {
      uchar *p, *q;
      int xx, yy;
      for (yy= 0; yy<sy2; yy++) {
	 p= odata + (yy*2) * sx * 3;
	 q= odata + (yy*2+1) * sx * 3;
	 for (xx= 0; xx<sx2; xx++) {
#define COMB4(getX,xx1,yy1,xx2,yy2) \
 (getX(xx1,yy1) * 10 + (getX(xx2,yy1) + getX(xx1,yy2) + getX(xx2,yy2)) * 2)
#define COMB2(getX1,xx1,yy1,getX2,xx2,yy2) \
 (getX1(xx1,yy1) * 8 + getX2(xx2,yy2) * 8)

	    rr= COMB4(get1, xx, yy, xx-1, yy+1);
	    gg= COMB2(get0, xx, yy, get3, xx, yy);
	    bb= COMB4(get2, xx, yy, xx+1, yy-1);
	    gamma_correct();
	    *p++= rr>>4; *p++= gg>>4; *p++= bb>>4;

	    rr= COMB4(get1, xx, yy, xx+1, yy+1);
	    gg= COMB2(get0, xx+1, yy, get3, xx, yy);
	    bb= COMB4(get2, xx+1, yy, xx, yy-1);
	    gamma_correct();
	    *p++= rr>>4; *p++= gg>>4; *p++= bb>>4;
	    
	    rr= COMB4(get1, xx, yy+1, xx-1, yy);
	    gg= COMB2(get0, xx, yy+1, get3, xx, yy);
	    bb= COMB4(get2, xx, yy, xx+1, yy+1);
	    gamma_correct();
	    *q++= rr>>4; *q++= gg>>4; *q++= bb>>4;

	    rr= COMB4(get1, xx, yy+1, xx+1, yy);
	    gg= COMB2(get0, xx+1, yy+1, get3, xx, yy);
	    bb= COMB4(get2, xx+1, yy, xx, yy+1);
	    gamma_correct();
	    *q++= rr>>4; *q++= gg>>4; *q++= bb>>4;
	 }
      }
   }

   // Write output
   {
      char buf[1024];
      FILE *out;
      char *ofnam= av[1];
      char *ext= strchr(ofnam, 0) - 4;
      if (ext < ofnam) ext= ofnam;

      if (0 == strcmp(ext, ".png")) {
	 //snprintf(buf, sizeof(buf), "pnmtopng -compression 9 >%s", ofnam);
	 snprintf(buf, sizeof(buf), "pnmtopng >%s", ofnam);
	 out= popen(buf, "w");
	 if (!out) error("Can't start pnmtopng tool");
      } else if (0 == strcmp(ext, ".jpg")) {
	 snprintf(buf, sizeof(buf), "pnmtojpeg >%s", ofnam);
	 out= popen(buf, "w");
	 if (!out) error("Can't start pnmtojpeg tool");
      } else if (0 == strcmp(ext, ".pnm")) {
	 out= fopen(ofnam, "w");
	 if (!out) error("Can't open file for writing: %s", ofnam);
      } else {
	 error("Can only handle output files with .png or .pnm extensions: %s", ofnam);
      }
      
      fprintf(out, "P6\n%d %d\n255\n", sx, sy);
      if (1 != fwrite(odata, sx*sy*3, 1, out)) 
	 error("Write error to output file or pipe");

      if (fclose(out))
	 error("Write error to output file or pipe");
   }

   return 0;
}
      
// END //

