/*******************************************************************************
*                                                                              *
*                                   Viewmol                                    *
*                                                                              *
*                             H A R D C O P Y . C                              *
*                                                                              *
*                 Copyright (c) Joerg-R. Hill, October 2003                    *
*                                                                              *
********************************************************************************
*
* $Id: hardcopy.c,v 1.6 2003/11/07 11:02:29 jrh Exp $
* $Log: hardcopy.c,v $
* Revision 1.6  2003/11/07 11:02:29  jrh
* Release 2.4
*
* Revision 1.5  2000/12/10 15:08:16  jrh
* Release 2.3
*
* Revision 1.4  1999/05/24 01:25:46  jrh
* Release 2.2.1
*
* Revision 1.3  1999/02/07 21:50:02  jrh
* Release 2.2
*
* Revision 1.2  1998/01/26 00:47:54  jrh
* Release 2.1
*
* Revision 1.1  1996/12/10  18:41:06  jrh
* Initial revision
*
*/
#include<locale.h>
#include<png.h>
#include<pwd.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<X11/Xatom.h>
#include<X11/Xlib.h>
#include<X11/Intrinsic.h>
#include<X11/StringDefs.h>
#include<Xm/Xm.h>
#include<GL/glu.h>
#include "tiffio.h"
#include "viewmol.h"
#include "dialog.h"

char *saveTiff(int, char *, int);
char *saveVectorFile(int, char *, int);
void writeTiff(TIFF *, unsigned char *, int, int, XColor *);
char *savePng(int, char *, int);
char *savePostScriptBitmap(int, char *);
unsigned char *getImage(int, Dimension *, Dimension *, int *, int *, XColor *, int);
int rle_encode(char *, char *, int);

extern void GetMessageBoxButton(Widget, XtPointer, caddr_t);
extern char *getStringResource(Widget, char *);
extern FILE *hpglInit(char *, double, double, double, double, double, double,
                      int);
extern FILE *PostscriptInit(char *, double, double, double, int, int);
extern void PostscriptClose(FILE *);
extern void (*drawString)(char *, double, double, double, double, GLuint, char);
extern FILE *raytraceInit(char *, Dimension, Dimension);
extern void raytraceClose(FILE *);
extern void setDrawingDevice(int);
extern void *getmem(size_t, size_t);
extern void *expmem(void *, size_t, size_t);
extern void fremem(void **);
extern void redraw(int);
extern void hpglClose(FILE *);
extern int  messgb(Widget, int, char *, struct PushButtonRow *, int);
extern int  StringWidth(XFontStruct *, char *);
extern int  StringHeight(XFontStruct *);
extern void setWindowColor(int, Pixel, const float *);
extern void writeFeedback(FILE *, int, GLfloat *, int, int, int, int);
extern XVisualInfo *getVisualInfo(Display *, Visual *);

extern Widget topShell;
extern struct MOLECULE *molecules;
extern struct WINDOW windows[];
extern Pixel stdcol[9];
extern double paperWidth, paperHeight, sphereres;
/* extern char title[]; */
extern char *text, *textPointer;
/* extern int na, nb; */
extern int iwavef, shadows, ground;
extern int rgbMode, swapBuffers, primitive, debug;

char *saveTiff(int window, char *file, int compression)
{
  static struct PushButtonRow buttons[] = {{"continue", GetMessageBoxButton, (XtPointer)0, NULL}};
  FILE *f;
  TIFF *tif;
  struct passwd *entry;
  Dimension width, height;
  XColor *colors=NULL;
  unsigned char *image;
  char str[MAXLENLINE], *word;
  int ncolors, colorDepth, imol;
  register long rps;

  if (windows[window].set >= 0)
    imol=windows[window].set;
  else
    imol=0;
  if (&molecules[imol] == (struct MOLECULE *)NULL) return(NULL);

/* Try to open file to catch possible problems with permissions
   before using TIFF library to open file */

  if ((f=fopen(file, "w")) == NULL)
  {
    word=getStringResource(topShell, "unableToOpen");
    sprintf(str, word, file);
    messgb(topShell, 1, str, buttons, 1);
    return(NULL);
  }
  else
  {
    fclose(f);
    if ((tif=TIFFOpen(file, "w")) == NULL)
    {
      word=getStringResource(topShell, "unableToOpen");
      sprintf(str, word, file);
      messgb(topShell, 1, str, buttons, 1);
      return(NULL);
    }
  }

  image=getImage(window, &width, &height, &ncolors, &colorDepth, colors, FALSE);
  TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width);
  TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height);
  TIFFSetField(tif, TIFFTAG_COMPRESSION, compression);
  rps=(long)8*1024/TIFFScanlineSize(tif);
  TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, rps == 0 ? 1L : rps);
  TIFFSetField(tif, TIFFTAG_XRESOLUTION, 1.0);
  TIFFSetField(tif, TIFFTAG_YRESOLUTION, 1.0);
  TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, 1);
  TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
  TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_BOTLEFT);
  entry=getpwuid(getuid());
  TIFFSetField(tif, TIFFTAG_ARTIST, entry->pw_gecos);
  TIFFSetField(tif, TIFFTAG_IMAGEDESCRIPTION, molecules[imol].title);
  strcpy(str, PROGRAM);
  strcat(str, " ");
  strcat(str, VERSION);
  TIFFSetField(tif, TIFFTAG_SOFTWARE, str);
  TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
  TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3);
  TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
  writeTiff(tif, image, width, height, colors);
  TIFFClose(tif);
  if (colors != NULL) free((void *)colors);
  free((void *)image);
  word=getStringResource(topShell, "TIFFSaved");
  return(word);
}

void writeTiff(TIFF *tif, unsigned char *image, int width, int height,
               XColor *colors)
{
  unsigned char *scanline=NULL;
  unsigned char *ssc=NULL;
  unsigned int  *ssi=NULL;
  register unsigned int v;
  register int i, j;
  register unsigned char *pp;

  if (colors)
    ssi=(unsigned int *)image;
  else
    ssc=image;

  if (colors) scanline=(unsigned char *)malloc(TIFFScanlineSize(tif));
  for (j = 0; j < height; j++)
  {
    if (!colors)
    {
      scanline=(unsigned char *)ssc;
      ssc += 3*width;
    }
    else
    {
      pp = scanline;
      for (i = 0; i < width; i++)
      {
        v = *ssi;
        pp[0]=(char)colors[v].red;
        pp[1]=(char)colors[v].green;
        pp[2]=(char)colors[v].blue;
        pp += 3;
        ssi++;
      }
    }
    if (TIFFWriteScanline(tif, scanline, j, 0) < 0) break;
  }
  if (colors) free(scanline);
}

char *saveVectorFile(int window, char *file, int format)
{
  static struct PushButtonRow buttons[] = {{"continue", GetMessageBoxButton,
                                            (XtPointer)0, NULL}};
  FILE *f=NULL;
  Dimension width, height;
  int modeSave=windows[window].mode, nentries=(-1), size, shadowsSave;
  int imol;
  GLfloat *feedbackBuffer;
  double fontSizeX, fontSizeY;
/*const float black[4]   = {0.0, 0.0, 0.0, 0.0};*/
  int portrait, smooth=FALSE, ntrials=5;
  char *word=NULL, str[MAXLENLINE];
  const char *locale;

  if (windows[window].set >= 0)
    imol=windows[window].set;
  else
    imol=0;
  glXMakeCurrent(XtDisplay(windows[window].widget), XtWindow(windows[window].widget), windows[window].context);
  swapBuffers=FALSE;
  shadowsSave=shadows;
  if (format != POSTSCRIPT) shadows=FALSE;
  strcpy(str, PROGRAM);
  strcat(str, " ");
  strcat(str, VERSION);
  strcat(str, ": ");
  strcat(str, molecules[imol].title);
  XtVaGetValues(windows[window].widget,
                XtNwidth, &width,
                XtNheight, &height,
                NULL);
  portrait=paperWidth < paperHeight ? TRUE : FALSE;
  fontSizeX=(double)StringWidth((windows[window].font), "1000.");
  fontSizeY=(double)StringHeight(windows[window].font);
  locale=setlocale(LC_ALL, "C");
  switch (format)
  {
    case HPGL:       f=hpglInit(file, (double)width, (double)height, paperWidth,
                                paperHeight, fontSizeX/110., fontSizeY/40.,
                                portrait);
                     ground=FALSE;
                     break;
    case POSTSCRIPT: if (window == VIEWER && (windows[window].mode != WIREMODEL || iwavef != ALL_OFF)
                         && primitive == GLU_FILL) smooth=TRUE;
                     f=PostscriptInit(file, paperWidth, paperHeight, fontSizeY,
                                      window, smooth);
                     break;
    case RAYTRACER:  f=raytraceInit(file, width, height);
                     if (windows[window].mode == WIREMODEL)
                       windows[window].mode=STICKMODEL;
                     break;
  }
  if (f != NULL)
  {
    setDrawingDevice(format);
    if (format == RAYTRACER)
      redraw(window);
    else
    {
      size=(size_t)(molecules[imol].na*(sphereres/10.+0.1)*(sphereres/10.+0.1)
          *4152+molecules[imol].nb*1000.*sphereres/10.);
      feedbackBuffer=getmem(size, sizeof(GLfloat));
      text=(char *)getmem(200, sizeof(char));
      textPointer=text;
      while (nentries < 0 && ntrials)
      {
        glFeedbackBuffer(size, GL_3D_COLOR, feedbackBuffer);
        (void)glRenderMode(GL_FEEDBACK);
        redraw(window);
/*      setWindowColor(FOREGROUND, stdcol[BLACK], black);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(windows[window].left, windows[window].right, windows[window].bottom,
                windows[window].top, windows[window].near, windows[window].far);
        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();
        glLoadIdentity();
        (*drawString)(str, windows[window].right, windows[window].bottom,
                      windows[window].near, 1.0, windows[window].GLfontId, 'l');
        glPopMatrix(); */
        nentries=glRenderMode(GL_RENDER);
        if (debug) printf("Size of feedback buffer: %d, used: %d\n", size, nentries);
        if (nentries > 0)
          writeFeedback(f, nentries, feedbackBuffer, window, format, smooth, portrait);
        else
        {
          size*=2;
          feedbackBuffer=expmem((void *)feedbackBuffer, (size_t)size, sizeof(GLfloat));
          ntrials--;
        }
      }
      fremem((void *)&feedbackBuffer);
      fremem((void *)&text);
      if (!ntrials)
      {
        word=getStringResource(topShell, "unableToWriteFeedback");
        messgb(topShell, 1, word, buttons, 1);
      }
    }
    setDrawingDevice(SCREEN);
  }
  else
  {
    word=getStringResource(topShell, "unableToOpen");
    sprintf(str, word, file);
    messgb(topShell, 1, str, buttons, 1);
    swapBuffers=TRUE;
    shadows=shadowsSave;
    return(NULL);
  }
  switch (format)
  {
    case HPGL:       hpglClose(f);
                     word=getStringResource(topShell, "HPGLSaved");
                     ground=TRUE;
                     break;
    case POSTSCRIPT: PostscriptClose(f);
                     word=getStringResource(topShell, "PostscriptSaved");
                     break;
    case RAYTRACER:  raytraceClose(f);
                     word=getStringResource(topShell, "RaytracerSaved");
                     windows[window].mode=modeSave;
                     break;
  }
  swapBuffers=TRUE;
  shadows=shadowsSave;
  (void)setlocale(LC_ALL, locale);
  return(word);
}

char *savePng(int window, char *file, int transparentBackground)
{
  static struct PushButtonRow buttons[] = {{"continue", GetMessageBoxButton, (XtPointer)0, NULL}};
  char str[MAXLENLINE], *word;
#ifdef PNG_MAX_PALETTE_LENGTH
  FILE *f;
  png_structp pngStruct;
  png_infop pngInfo;
  png_colorp palette=(png_colorp)NULL;
  png_time modTime;
  png_color_16 background;
  png_text text[4];
  png_byte *image;
  png_bytepp rowPointers;
  struct passwd *entry;
  Dimension width, height;
  XColor *colors=NULL;
  int ncolors, colorDepth, imol;
  register png_uint_32 i;

  if (windows[window].set >= 0)
    imol=windows[window].set;
  else
    imol=0;
  if (&molecules[imol] == (struct MOLECULE *)NULL) return(NULL);

  if (!(pngStruct=png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL,
		                          NULL, NULL)))
  {
    return(NULL);
  }
  if (!(pngInfo=png_create_info_struct(pngStruct)))
  {
    png_destroy_write_struct(&pngStruct, (png_infopp)NULL);
    return(NULL);
  }
  if (setjmp(png_jmpbuf(pngStruct)))
  {
    png_destroy_write_struct(&pngStruct, &pngInfo);
    return(NULL);
  }
  if ((f=fopen(file, "wb")) == NULL)
  {
    png_destroy_write_struct(&pngStruct, &pngInfo);
    word=getStringResource(topShell, "unableToOpen");
    sprintf(str, word, file);
    messgb(topShell, 1, str, buttons, 1);
    return(NULL);
  }
  png_init_io(pngStruct, f);

  image=(png_byte *)getImage(window, &width, &height, &ncolors, &colorDepth, colors, FALSE);
  if (colorDepth < 8)
    colorDepth=8;
  png_set_IHDR(pngStruct, pngInfo, (png_uint_32)width, (png_uint_32)height, colorDepth,
               PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
               PNG_FILTER_TYPE_BASE);
  if (colors != NULL)
  {
    palette=(png_colorp)png_malloc(pngStruct, PNG_MAX_PALETTE_LENGTH*sizeof(png_color));
    for (i=0; i<ncolors; i++)
    {
      palette[i].red=(png_byte)colors[i].red;
      palette[i].green=(png_byte)colors[i].green;
      palette[i].blue=(png_byte)colors[i].blue;
    }
    png_set_PLTE(pngStruct, pngInfo, palette, PNG_MAX_PALETTE_LENGTH);
  }
  png_convert_from_time_t(&modTime, time(NULL));
  png_set_tIME(pngStruct, pngInfo, &modTime);
  if (transparentBackground)
  {
    /* In RGB mode we are retrieving the image as GL_UNSIGNED_BYTE,
       therefore we need to convert to byte here as well */
    background.red=(png_uint_16)(255.*windows[window].background_rgb[0]);
    background.green=(png_uint_16)(255.*windows[window].background_rgb[1]);
    background.blue=(png_uint_16)(255.*windows[window].background_rgb[2]);
    png_set_tRNS(pngStruct, pngInfo, (png_bytep)NULL, 1, &background);
  }
  png_write_info(pngStruct, pngInfo);
  rowPointers=(png_bytepp)getmem((size_t)height, sizeof(png_bytep));
  for (i=0; i<height; i++)
  {
    rowPointers[height-i-1]=(png_bytep)(image+3*i*width);
  }
  png_write_image(pngStruct, rowPointers);
  entry=getpwuid(getuid());
  text[0].key="Title";
  text[0].text=molecules[imol].title;
  text[0].compression=PNG_TEXT_COMPRESSION_NONE;
  text[1].key="Author";
  text[1].text=entry->pw_gecos;
  text[1].compression=PNG_TEXT_COMPRESSION_NONE;
  strcpy(str, PROGRAM);
  strcat(str, " ");
  strcat(str, VERSION);
  text[2].key="Software";
  text[2].text=str;
  text[2].compression=PNG_TEXT_COMPRESSION_NONE;
  text[3].key="Creation Time";
  text[3].text=png_convert_to_rfc1123(pngStruct, &modTime);
  text[3].compression=PNG_TEXT_COMPRESSION_NONE;
  png_set_text(pngStruct, pngInfo, text, 4);
  png_write_end(pngStruct, pngInfo);
  if (colors != NULL)
  {
    png_free(pngStruct, palette);
    fremem((void *)colors);
  }
  fremem((void *)&image);
  fremem((void *)&rowPointers);
  png_destroy_write_struct(&pngStruct, &pngInfo);
  fclose(f);
  word=getStringResource(topShell, "PNGSaved");
  return(word);
#else
  word=getStringResource(topShell, "wrongPNGversion");
  sprintf(str, word, PNG_LIBPNG_VER_STRING);
  messgb(topShell, 1, str, buttons, 1);
  return(NULL);
#endif
}

char *savePostScriptBitmap(int window, char *file)
{
  static struct PushButtonRow buttons[] = {{"continue", GetMessageBoxButton, (XtPointer)0, NULL}};
  FILE *f;
  char str[MAXLENLINE], *word;
  Dimension width, height;
  XColor *colors=NULL;
  double fontSizeY;
  int ncolors, colorDepth, imol, icount, i, j;
  const char *locale;
  unsigned char *image;

  if (windows[window].set >= 0)
    imol=windows[window].set;
  else
    imol=0;
  if (&molecules[imol] == (struct MOLECULE *)NULL) return(NULL);

  fontSizeY=(double)StringHeight(windows[window].font);
  f=PostscriptInit(file, paperWidth, paperHeight, fontSizeY,
                   window, FALSE);
  if (f == NULL)
  {
    word=getStringResource(topShell, "unableToOpen");
    sprintf(str, word, file);
    messgb(topShell, 1, str, buttons, 1);
    return(NULL);
  }
  image=getImage(window, &width, &height, &ncolors, &colorDepth, colors, FALSE);
  locale=setlocale(LC_ALL, "C");
  icount=0;
  for (i=0; i<height; i++)
  {
    for (j=0; j<3*width; j+=3)
    {
      /* Write red, green, and blue components of one pixel as hex digits */
      fprintf(f, "%02x%02x%02x", image[3*i*width+j], image[3*i*width+j+1], image[3*i*width+j+2]);
      icount+=6;
      if (icount >= 78)
      {
        /* Limit line length to 80 characters maximum */
        fprintf(f, "\n");
        icount=0;
      }
    }
  }
  fremem((void *)&image);
  fprintf(f, "\n");
  locale=setlocale(LC_ALL, locale);
  PostscriptClose(f);
  word=getStringResource(topShell, "PostscriptSaved");
  return(word);
}

unsigned char *getImage(int window, Dimension *width, Dimension *height, int *ncolors,
                        int *colorDepth, XColor *colors, int separate)
{
  Colormap colormap;
  Visual *vi;
  XVisualInfo *visualInfo;
  unsigned char *image, *pimage;
  int i, offset;

  XtVaGetValues(windows[window].widget, XmNwidth, width, XmNheight, height,
                XmNcolormap, &colormap, NULL);
  if (*width % 4 != 0) *width-=(*width % 4);
  XtVaGetValues(topShell, XtNvisual, &vi, NULL);
  visualInfo=getVisualInfo(XtDisplay(windows[window].widget), vi);
  *ncolors=visualInfo->colormap_size;
  *colorDepth=visualInfo->bits_per_rgb;
  glXMakeCurrent(XtDisplay(windows[window].widget), XtWindow(windows[window].widget),
                 windows[window].context);
  if (rgbMode)
  {
    image=(unsigned char *)getmem((*width+1)*(*height+1), 3*sizeof(unsigned char));
    if (separate)
    {
      pimage=image;
      offset=(*width+1)*(*height+1);
      glReadPixels((GLint)0, (GLint)0, (GLsizei)(*width), (GLsizei)(*height), GL_RED,
                   GL_UNSIGNED_BYTE, pimage);
      pimage+=offset;
      glReadPixels((GLint)0, (GLint)0, (GLsizei)(*width), (GLsizei)(*height), GL_GREEN,
                   GL_UNSIGNED_BYTE, pimage);
      pimage+=offset;
      glReadPixels((GLint)0, (GLint)0, (GLsizei)(*width), (GLsizei)(*height), GL_BLUE,
                   GL_UNSIGNED_BYTE, pimage);
    }
    else
      glReadPixels((GLint)0, (GLint)0, (GLsizei)(*width), (GLsizei)(*height), GL_RGB,
                   GL_UNSIGNED_BYTE, image);
  }
  else
  {
    image=(unsigned char *)getmem((*width+1)*(*height+1), sizeof(unsigned int));
    glReadPixels((GLint)0, (GLint)0, (GLsizei)(*width), (GLsizei)(*height),
                 GL_COLOR_INDEX, GL_UNSIGNED_INT, image);
    colors=(XColor *)getmem(*ncolors, sizeof(XColor));
    for (i=0; i<*ncolors; i++)
      colors[i].pixel=(long)i;
    XQueryColors(XtDisplay(windows[window].widget), colormap, colors, *ncolors);
    for (i=0; i<*ncolors; i++)
    {
      colors[i].red >>= 8;
      colors[i].green >>= 8;
      colors[i].blue >>= 8;
    }
  }
  return(image);
}
#if 0
int rle_encode(char *scanline, char *rleline, int wide)
{
  /* generates a rle-compressed version of the scan line.
   * rle is encoded as such:
   *    <count> <value>                      # 'run' of count+1 equal pixels
   *    <count | 0x80> <count+1 data bytes>  # count+1 non-equal pixels
   *
   * count can range between 0 and 127
   *
   * returns length of the rleline vector
   */

  int  i, j, blocklen, isrun, rlen;
  char block[256], pix;

  blocklen = isrun = rlen = 0;

  for (i=0; i<wide; i++) {
    /* there are 5 possible states:
     *   0: block empty.
     *   1: block not empty, block is  a run, current pix == previous pix
     *   2: block not empty, block is  a run, current pix != previous pix
     *   3: block not empty, block not a run, current pix == previous pix
     *   4: block not empty, block not a run, current pix != previous pix
     */

    pix = scanline[i];
   if (!blocklen) {                    /* case 0:  empty */
      block[blocklen++] = pix;
      isrun = 1;
    }

    else if (isrun) {
      if (pix == block[blocklen-1]) {   /* case 1:  isrun, prev==cur */
        block[blocklen++] = pix;
      }
      else {                            /* case 2:  isrun, prev!=cur */
        if (blocklen>1) {               /*   we have a run block to flush */
          rleline[rlen++] = blocklen-1;
          rleline[rlen++] = block[0];
          block[0] = pix;               /*   start new run block with pix */
          blocklen = 1;
        }
        else {
          isrun = 0;                    /*   blocklen<=1, turn into non-run */
          block[blocklen++] = pix;
        }
      }
    }

    else {   /* not a run */
      if (pix == block[blocklen-1]) {   /* case 3:  non-run, prev==cur */
        if (blocklen>1) {               /*  have a non-run block to flush */
          rleline[rlen++] = (blocklen-1) | 0x80;
          for (j=0; j<blocklen; j++)
            rleline[rlen++] = block[j];

          block[0] = pix;               /*  start new run block with pix */
          blocklen = isrun = 1;
        }
       else {
          isrun = 1;                    /*  blocklen<=1 turn into a run */
          block[blocklen++] = pix;
        }
      }
      else {                            /* case 4:  non-run, prev!=cur */
        block[blocklen++] = pix;
      }
    }

    if (blocklen == 128) {   /* max block length.  flush */
      if (isrun) {
        rleline[rlen++] = blocklen-1;
        rleline[rlen++] = block[0];
      }
      else {
        rleline[rlen++] = (blocklen-1) | 0x80;
        for (j=0; j<blocklen; j++)
          rleline[rlen++] = block[j];
      }
      blocklen = 0;
    }
  }
  if (blocklen) {   /* flush last block */
    if (isrun) {
      rleline[rlen++] = blocklen-1;
      rleline[rlen++] = block[0];
    }
   else {
      rleline[rlen++] = (blocklen-1) | 0x80;
      for (j=0; j<blocklen; j++)
        rleline[rlen++] = block[j];
    }
  }
  return(rlen);
}
#endif
