#ifdef HAVE_JPEG

/*
 * xvjpeg.c - i/o routines for 'jpeg' format pictures
 *
 * LoadJFIF(fname, numcols)  -  loads a JPEG pic, does 24to8 code if nec.
 * WriteJFIF(fp, pic, w, h, rmap, gmap, bmap, numcols, colorstyle)
 */

/*
 * LoadJFIF Author: Markus Baur, University of Karlsruhe 
 *                  (s_baur@iravcl.ira.uka.de)
 * This software is provided "as is" without any express or implied warranty.
 */

/* WriteJFIF() and JPEG dialog routines written by John Bradley */

/*
 * Portions Copyright 1989, 1990, 1991, 1992 by John Bradley and
 *                                The University of Pennsylvania
 *
 * Permission to use, copy, and distribute for non-commercial purposes,
 * is hereby granted without fee, providing that the above copyright
 * notice appear in all copies and that both the copyright notice and this
 * permission notice appear in supporting documentation. 
 *
 * The software may be modified for your own purposes, but modified versions
 * may not be distributed.
 *
 * This software is provided "as is" without any expressed or implied warranty.
 *
 * The author may be contacted via:
 *    US Mail:   John Bradley
 *               GRASP Lab, Room 301C
 *               3401 Walnut St.  
 *               Philadelphia, PA  19104
 *
 *    Phone:     (215) 898-8813
 *    EMail:     bradley@cis.upenn.edu       
 */


#include "xv.h"
#include "jinclude.h"
#include <setjmp.h>


/**** Stuff for JPEGDialog box ****/

#define JWIDE 280
#define JHIGH 160
#define J_NBUTTS 2
#define J_BOK    0
#define J_BCANC  1
#define BUTTH    24

#ifdef __STDC__
static void drawJD(int, int, int, int);
static void clickJD(int, int);
static void doCmd(int);
static void writeJPEG(void);
#else
static void drawJD(), doCmd(), clickJD(), writeJPEG();
#endif
static void jselwxv();
static int writeJFIF();


/* local variables */
static char *filename;
static int   colorType;
static DIAL  qDial;
static BUTT  jbut[J_NBUTTS];
static byte *image8, *image24;

static byte        *CurImagePtr;
static byte        *pic24;
static long  	    filesize;
static jmp_buf      jmpState;
static struct external_methods_struct   e_methods;


/***************************************************/
void CreateJPEGW()
{
  jpegW = CreateWindow("xv jpeg", NULL, JWIDE, JHIGH, infofg, infobg);
  if (!jpegW) FatalError("can't create jpeg window!");
  StoreDeleteWindowProp(jpegW);

  XSelectInput(theDisp, jpegW, ExposureMask | ButtonPressMask | KeyPressMask);

  DCreate(&qDial, jpegW, 190-2, 10, 80, 100, 1, 100, 75, 5, 
	  infofg, infobg, "Quality", "%");

  BTCreate(&jbut[J_BOK], jpegW, JWIDE-140-1, JHIGH-10-BUTTH-1, 60, BUTTH, 
	   "Ok", infofg, infobg);

  BTCreate(&jbut[J_BCANC], jpegW, JWIDE-70-1, JHIGH-10-BUTTH-1, 60, BUTTH, 
	   "Cancel", infofg, infobg);

  XMapSubwindows(theDisp, jpegW);
}
  

/***************************************************/
void JPEGDialog(vis)
int vis;
{
  if (vis) {
    CenterMapWindow(jpegW, jbut[J_BOK].x + jbut[J_BOK].w/2,
		    jbut[J_BOK].y + jbut[J_BOK].h/2, JWIDE, JHIGH);
  }
  else     XUnmapWindow(theDisp, jpegW);
  jpegUp = vis;
}


/***************************************************/
int JPEGCheckEvent(xev)
XEvent *xev;
{
  /* check event to see if it's for one of our subwindows.  If it is,
     deal accordingly, and return '1'.  Otherwise, return '0' */

  int rv;
  rv = 1;

  if (!jpegUp) return 0;

  if (xev->type == Expose) {
    int x,y,w,h;
    XExposeEvent *e = (XExposeEvent *) xev;
    x = e->x;  y = e->y;  w = e->width;  h = e->height;

    /* throw away excess expose events for 'dumb' windows */
    if (e->count > 0 && e->window == qDial.win) {}

    else if (e->window == jpegW)       drawJD(x, y, w, h);
    else if (e->window == qDial.win)   DRedraw(&qDial);
    else rv = 0;
  }

  else if (xev->type == ButtonPress) {
    XButtonEvent *e = (XButtonEvent *) xev;
    int x,y;
    x = e->x;  y = e->y;

    if (e->button == Button1) {
      if      (e->window == jpegW)     clickJD(x,y);
      else if (e->window == qDial.win) DTrack(&qDial, x,y);
      else rv = 0;
    }  /* button1 */
    else rv = 0;
  }  /* button press */


  else if (xev->type == KeyPress) {
    XKeyEvent *e = (XKeyEvent *) xev;
    char buf[128];  KeySym ks;
    int stlen;
	
    stlen = XLookupString(e,buf,128,&ks,(XComposeStatus *) NULL);
    buf[stlen] = '\0';

    if (e->window == jpegW) {
      if (stlen) {
	if (buf[0] == '\r' || buf[0] == '\n') { /* enter */
	  FakeButtonPress(&jbut[J_BOK]);
	}
	else if (buf[0] == '\033') {            /* ESC */
	  FakeButtonPress(&jbut[J_BCANC]);
	}
      }
    }
    else rv = 0;
  }
  else rv = 0;

  if (rv==0 && (xev->type == ButtonPress || xev->type == KeyPress)) {
    XBell(theDisp, 50);
    rv = 1;   /* eat it */
  }

  return rv;
}


/***************************************************/
void JPEGSaveParams(fname, col)
char *fname;
int col;
{
  filename = fname;
  colorType = col;
}


/***************************************************/
static void drawJD(x,y,w,h)
int x,y,w,h;
{
  char *title  = "Save JPEG file...";
  char *title1 = "Set quality value.";
  char *title2 = "Default value: 75%";
  char *title3 = "Useful range 5-95%.";
  int  i;
  XRectangle xr;

  xr.x = x;  xr.y = y;  xr.width = w;  xr.height = h;
  XSetClipRectangles(theDisp, theGC, 0,0, &xr, 1, Unsorted);

  XSetForeground(theDisp, theGC, infofg);
  XSetBackground(theDisp, theGC, infobg);

  for (i=0; i<J_NBUTTS; i++) BTRedraw(&jbut[i]);

  XDrawString(theDisp, jpegW, theGC, 20, 29, title, strlen(title));

  XDrawString(theDisp, jpegW, theGC, 30, 29+LINEHIGH*2,title1,strlen(title1));
  XDrawString(theDisp, jpegW, theGC, 30, 29+LINEHIGH*4,title2,strlen(title2));
  XDrawString(theDisp, jpegW, theGC, 30, 29+LINEHIGH*5,title3,strlen(title3));

  XSetClipMask(theDisp, theGC, None);
}


/***************************************************/
static void clickJD(x,y)
int x,y;
{
  int i;
  BUTT *bp;

  /* check BUTTs */

  for (i=0; i<J_NBUTTS; i++) {
    bp = &jbut[i];
    if (PTINRECT(x, y, bp->x, bp->y, bp->w, bp->h)) break;
  }

  if (i<J_NBUTTS) {  /* found one */
    if (BTTrack(bp)) doCmd(i);
  }
}



/***************************************************/
static void doCmd(cmd)
int cmd;
{
  switch (cmd) {
  case J_BOK:    writeJPEG();    JPEGDialog(0);  break;
  case J_BCANC:  JPEGDialog(0);  break;
  default:        break;
  }
}





/*******************************************/
static void writeJPEG()
{
  FILE *fp;
  int   i, nc, rv, w, h;
  register byte *ip, *ep;
  byte *inpix, *bwpic, *rmap, *gmap, *bmap;

  /* get the XV image into a format that the JPEG software can grok on.
     Also, open the output file, so we don't waste time doing this format
     conversion if we won't be able to write it out */

  if (savenormCB.val) { inpix = cpic;  w = cWIDE;  h = cHIGH; }
                 else { inpix = epic;  w = eWIDE;  h = eHIGH; }

  bwpic = NULL;  rmap = r;  gmap = g;  bmap = b;
  nc = numcols;

  /* see if we can open the output file before proceeding */
  fp = OpenOutFile(filename);
  if (!fp) return;

  WaitCursor();
  
  bwpic = HandleBWandReduced(colorType, &nc, &rmap, &gmap, &bmap);
  if (bwpic) inpix = bwpic;

  /* monocity check.  if the image is mono, save it that way to save space */

  if (colorType != F_GREYSCALE) {
    for (i=0; i<nc && rmap[i]==gmap[i] && rmap[i]==bmap[i]; i++);
    if (i==nc) colorType = F_GREYSCALE;    /* made it all the way through */
  }
    

  /* first thing to do is build an 8/24-bit Greyscale/TrueColor image
     (meaning: non-colormapped) */

  if (colorType == F_GREYSCALE) {   /* build an 8-bit Greyscale image */
    image8 = (byte *) malloc(w * h);
    if (!image8) FatalError("writeJPEG: unable to malloc image8\n");

    for (i=0,ip=image8,ep=inpix; i<w * h; i++, ip++, ep++)
      *ip = MONO(rmap[*ep], gmap[*ep], bmap[*ep]);
  }

  else {    /* colorType = some color format */
    image24 = NULL;
    /* if fullcolor, and saving at current size, 'smooth' it */
    if (colorType == F_FULLCOLOR && !savenormCB.val) image24 = Smooth24();
    if (!image24) {  /* smoothing didn't/not tried.  Use 'stupid' method */
      image24 = (byte *) malloc(w * h * 3);
      if (!image24) {  /* this simply isn't going to work */
	FatalError("writeJPEG: unable to malloc image24\n");
      }

      for (i=0, ip=image24, ep=inpix; i<w*h; i++, ep++) {
	*ip++ = rmap[*ep];
	*ip++ = gmap[*ep];
	*ip++ = bmap[*ep];
      }
    }
  }

  if (bwpic) free(bwpic);  /* won't need it anymore */

  /* in any event, we've got some valid image.  Do the JPEG Thing */
  rv = writeJFIF(fp);

  /* get rid of the greyscale/truecolor image */
  if (colorType == F_GREYSCALE) free(image8);
                           else free(image24);

  if (CloseOutFile(fp, filename, rv) == 0) {
    /* everything's cool! */
    DirBox(0);
    LoadCurrentDirectory();
  }

}




/*********************************************/
/**** INTERFACE CODE FOR THE JPEG LIBRARY ****/
/*********************************************/



/********* JPEG DECOMPRESSION FUNCTIONS **********/

/**************************************************/
static void xv_jpeg_monitor(cinfo, loopcnt, looplimit)
     decompress_info_ptr cinfo;
     long loopcnt, looplimit;
{
  int a,b;
  double percent;

  WaitCursor();

#ifdef FOO  
  a = cinfo->completed_passes;
  b = cinfo->total_passes;

  percent = ((a + ((double) loopcnt / looplimit)) / (double) b) * 100.0;

  fprintf(stderr,"jpeg: %lf done.  loop: %ld, %ld  pass: %d, %d\n",
	  percent, loopcnt, looplimit, a, b);
#endif
}


/**************************************************/
static void d_ui_method_selection(cinfo)
     decompress_info_ptr cinfo;
{
  int i;

  /* select output colorspace & quantization parameters */
  if (cinfo->jpeg_color_space == CS_GRAYSCALE) {
    cinfo->out_color_space = CS_GRAYSCALE;
    cinfo->quantize_colors = FALSE;
    SetISTR(ISTR_FORMAT,"Greyscale JPEG. (%ld bytes)", filesize);
    /* fill in a greyscale colormap */
    for (i=0; i<=255; i++)
      r[i] = g[i] = b[i] = i;
  }

  else {
    cinfo->out_color_space = CS_RGB;

    if (conv24 == CONV24_FAST || conv24 == CONV24_SLOW)
         cinfo->quantize_colors = TRUE;
    else cinfo->quantize_colors = FALSE;

    if (conv24 != CONV24_FAST) cinfo->two_pass_quantize = TRUE;
    else {
      cinfo->two_pass_quantize = FALSE;
      /* For the 1-pass quantizer it's best to use 256 colors and let */
      /* the cmap sorter pick up any slack.  This produces essentially */
      /* the same results as xv's usual fast quantizer, but it reduces */
      /* swapping since no 24-bit copy of the image is needed. */
      cinfo->desired_number_of_colors = 256;
    }
    SetISTR(ISTR_FORMAT,"Color JPEG. (%ld bytes)", filesize);
  }

  jselwxv(cinfo);
}


/**************************************************/
static void output_init (cinfo)
     decompress_info_ptr cinfo;
{
  pWIDE = cinfo->image_width;
  pHIGH = cinfo->image_height;

  if (cinfo->out_color_space == CS_GRAYSCALE || 
      cinfo->quantize_colors == TRUE) {
    pic = (byte *) malloc(pWIDE * pHIGH);
    if (!pic) FatalError("Not enough memory for Image");
    CurImagePtr = pic;
  }

  else {
    pic24 = (byte *) malloc(pWIDE * pHIGH * 3);
    if (!pic24) FatalError("Not enough memory for Image");
    CurImagePtr = pic24;
  }
}


/**************************************************/
static void put_color_map (cinfo, num_colors, colormap)
     decompress_info_ptr cinfo;
     int num_colors;
     JSAMPARRAY colormap;
{
  int i;

  for (i = 0; i < num_colors; i++) {
    r[i] = GETJSAMPLE(colormap[0][i]);
    g[i] = GETJSAMPLE(colormap[1][i]);
    b[i] = GETJSAMPLE(colormap[2][i]);
  }
}


/**************************************************/
static void put_pixel_rows (cinfo, num_rows, pixel_data)
     decompress_info_ptr cinfo;
     int                 num_rows;
     JSAMPIMAGE          pixel_data;
{
  JSAMPROW ptr0, ptr1, ptr2;
  long col;
  long width = cinfo->image_width;
  int row;
  static unsigned int totrows = 0;

  if (cinfo->out_color_space == CS_GRAYSCALE || 
      cinfo->quantize_colors == TRUE) {

    for (row = 0; row < num_rows; row++) {
      ptr0 = pixel_data[0][row];
      for (col = width; col > 0; col--) {
	*CurImagePtr++ = GETJSAMPLE(*ptr0++);
      }
      totrows++;
      if ((totrows & 0x1f) == 0) WaitCursor();
    }
  }

  else {
    for (row = 0; row < num_rows; row++) {
      ptr0 = pixel_data[0][row];
      ptr1 = pixel_data[1][row];
      ptr2 = pixel_data[2][row];
      for (col = width; col > 0; col--) {
	*CurImagePtr++ = GETJSAMPLE(*ptr0++);
	*CurImagePtr++ = GETJSAMPLE(*ptr1++);
	*CurImagePtr++ = GETJSAMPLE(*ptr2++);
      }
      totrows++;
      if ((totrows & 0x1f) == 0) WaitCursor();
    }
  }
}


/**************************************************/
static void output_term (cinfo)
     decompress_info_ptr cinfo;
{
  /* no work required */
}


/**************************************************/
static void jselwxv(cinfo)
     decompress_info_ptr cinfo;
{
  cinfo->methods->output_init = output_init;
  cinfo->methods->put_color_map = put_color_map;
  cinfo->methods->put_pixel_rows = put_pixel_rows;
  cinfo->methods->output_term = output_term;
}


/**************************************************/
static void JPEG_Message (msgtext)
     char *msgtext;
{
  char tempstr[200];

  sprintf(tempstr, msgtext,
	  e_methods.message_parm[0], e_methods.message_parm[1],
	  e_methods.message_parm[2], e_methods.message_parm[3],
	  e_methods.message_parm[4], e_methods.message_parm[5],
	  e_methods.message_parm[6], e_methods.message_parm[7]);
  SetISTR(ISTR_INFO, tempstr);
}


/**************************************************/
static void JPEG_Error (msgtext)
     char *msgtext;
{
  char tempstr[200];

  sprintf(tempstr, msgtext,
	  e_methods.message_parm[0], e_methods.message_parm[1],
	  e_methods.message_parm[2], e_methods.message_parm[3],
	  e_methods.message_parm[4], e_methods.message_parm[5],
	  e_methods.message_parm[6], e_methods.message_parm[7]);
  SetISTR(ISTR_WARNING, tempstr);
  (*e_methods.free_all) ();	/* clean up memory allocation */
  longjmp(jmpState,1);
}


/*******************************************/
int LoadJFIF(fname,nc)
     char *fname;
     int   nc;
/*******************************************/
{
  int rtval;
  struct decompress_info_struct    cinfo;
  struct decompress_methods_struct dc_methods;

  /* Set up the input file */

  if ((cinfo.input_file = fopen(fname, READ_BINARY)) == NULL) return 1;

  /* figure out the file size (for Informational Purposes Only) */
  fseek(cinfo.input_file, 0L, 2);
  filesize = ftell(cinfo.input_file);
  fseek(cinfo.input_file, 0L, 0);

  cinfo.output_file = NULL;	/* only wanna read */

  pic24 = (byte *) NULL;

  /* Set up longjmp for error recovery out of JPEG_Error */
  rtval = setjmp(jmpState);
  if (rtval) {
    fclose(cinfo.input_file);	/* close input file */
    return rtval;		/* no further cleanup needed */
  }

  /* Initialize the system-dependent method pointers. */
  cinfo.methods  = &dc_methods;
  cinfo.emethods = &e_methods;
  e_methods.error_exit = JPEG_Error; /* provide my own error/message rtns */
  e_methods.trace_message = JPEG_Message;
  e_methods.trace_level = 0;	/* no tracing, thank you */
  jselmemmgr(&e_methods);	/* memory allocation routines */
  dc_methods.d_ui_method_selection = d_ui_method_selection;

  /* Set up default JPEG parameters. */
  j_d_defaults(&cinfo, TRUE);
  /* the jpeg quantizer can't handle small values of 'nc' */
  cinfo.desired_number_of_colors = (nc<16) ? 255 : nc;
  
  /* set up our progress-monitoring function */
  cinfo.methods->progress_monitor = xv_jpeg_monitor;
  
  /* Set up to read a JFIF or baseline-JPEG file. */
  /* A smarter UI would inspect the first few bytes of the input file */
  /* to determine its type. */
  jselrjfif(&cinfo);
  
  /* Do it! */
  jpeg_decompress(&cinfo);

  if (pic24) {
    /* call XV's quantizer */
    Conv24to8(pic24, pWIDE, pHIGH, nc);
    free(pic24);
  }

  /* Close input file */
  fclose(cinfo.input_file);
  
  sprintf(formatStr, "%dx%d %s JPEG. ", cinfo.image_width, cinfo.image_height, 
	  (cinfo.out_color_space == CS_GRAYSCALE) ? "Greyscale " : "Color ");
  
  SetDirRButt(F_FORMAT, F_JPEG);

  /* Got it! */
  return 0;
}






/********* JPEG COMPRESSION FUNCTIONS **********/


/**************************************************/
static void c_ui_method_selection(cinfo)
     compress_info_ptr cinfo;
{
  /* select output colorspace */
  if (colorType == F_GREYSCALE) {
    j_monochrome_default(cinfo);
  }
}


/**************************************************/
static void input_init (cinfo)
     compress_info_ptr cinfo;
{
  int w,h;

  if (colorType == F_GREYSCALE) {
    cinfo->input_components = 1;
    cinfo->in_color_space = CS_GRAYSCALE;
    CurImagePtr = image8;
  }

  else {
    cinfo->input_components = 3;
    cinfo->in_color_space = CS_RGB;
    CurImagePtr = image24;
  }

  if (savenormCB.val) { w = cWIDE;  h = cHIGH; }
                 else { w = eWIDE;  h = eHIGH; }

  cinfo->image_width  = w;
  cinfo->image_height = h;
  cinfo->data_precision = 8;
}


/**************************************************/
static void get_input_row(cinfo, pixel_row)
     compress_info_ptr cinfo;
     JSAMPARRAY        pixel_row;
{
  JSAMPROW ptr0, ptr1, ptr2;
  long col;
  static unsigned int totrows = 0;

  if (cinfo->input_components == 1) {
    ptr0 = pixel_row[0];
    for (col = cinfo->image_width; col > 0; col--) {
      *ptr0++ = *CurImagePtr++;
    }
    totrows++;
    if ((totrows & 0x1f) == 0) WaitCursor();
  }

  else {
    ptr0 = pixel_row[0];
    ptr1 = pixel_row[1];
    ptr2 = pixel_row[2];
    for (col = cinfo->image_width; col > 0; col--) {
      *ptr0++ = *CurImagePtr++;
      *ptr1++ = *CurImagePtr++;
      *ptr2++ = *CurImagePtr++;
    }
    totrows++;
    if ((totrows & 0x1f) == 0) WaitCursor();
  }
}


/**************************************************/
static void input_term (cinfo)
     compress_info_ptr cinfo;
{
  /* no work required */
}


/**************************************************/
static void jselrxv(cinfo)
     compress_info_ptr cinfo;
{
  cinfo->methods->input_init = input_init;
  cinfo->methods->get_input_row = get_input_row;
  cinfo->methods->input_term = input_term;
}



/*******************************************/
static int writeJFIF(fp)
     FILE *fp;
{
  int rtval;
  struct compress_info_struct    cinfo;
  struct compress_methods_struct c_methods;

  /* Set up longjmp for error recovery out of JPEG_Error */
  rtval = setjmp(jmpState);
  if (rtval)
    return rtval;		/* no further cleanup needed */
  
  /* Initialize the system-dependent method pointers. */
  cinfo.methods  = &c_methods;
  cinfo.emethods = &e_methods;
  e_methods.error_exit = JPEG_Error; /* provide my own error/message rtns */
  e_methods.trace_message = JPEG_Message;
  e_methods.trace_level = 0;	/* no tracing, thank you */
  jselmemmgr(&e_methods);	/* memory allocation routines */
  c_methods.c_ui_method_selection = c_ui_method_selection;

  /* Set up default JPEG parameters. */
  j_c_defaults(&cinfo, qDial.val, FALSE);

  /* set up our progress-monitoring function */
  cinfo.methods->progress_monitor = xv_jpeg_monitor;
  
  /* Select input and output */
  cinfo.input_file  = NULL;
  cinfo.output_file = fp;       /* already open */
  
  jselrxv(&cinfo);		/* we'll be reading from memory */
  jselwjfif(&cinfo);		/* and writing to JFIF file format */

  /* Do it! */
  jpeg_compress(&cinfo);
  
  return 0;
}


#endif  /* HAVE_JPEG */
