#include <stdio.h>
#include <string.h>

#ifdef __STDC__
#define ARGS(alist) alist
#else
#define ARGS(alist) ()
#endif

#define MAXWIDTH       4096

#define PageSideMargin 12
#define PageTopMargin  12
#define PageWidth      612
#define PageHeight     792

#define put_string nc=strlen(s); for(i=0;i<nc;i++) (*pb)(s[i]); Nbyte += nc

typedef unsigned char byte;

/***********************************************************************
 *                                                                     *
 * Name: PSencode                                    Date:    13.01.93 *
 * Author: E.Chernyaev (IHEP/Protvino)               Revised:          *
 *                                                                     *
 * Function: Output image in PostScript format (runlength encoding)    *
 *                                                                     *
 * Input: Width      - image width                                     *
 *        Height     - image height                                    *
 *        Ncol       - number of colors                                *
 *        R[]        - red components                                  *
 *        G[]        - green components                                *
 *        B[]        - blue components                                 *
 *        ScLine[]   - array for scan line (byte per pixel)            *
 *        get_scline - user routine to read scan line:                 *
 *                       get_scline(y, Width, ScLine)                  *
 *        pb         - user routine for "put_byte": pb(b)              *
 *                                                                     *
 * Return: size of PS                                                  *
 *                                                                     *
 ***********************************************************************/
long PSencode(Width, Height, Ncol, R, G, B, ScLine, get_scline, pb)
         int  Width, Height, Ncol;
         byte R[], G[], B[], *ScLine;
         void (*get_scline) ARGS((int, int, byte *)), (*pb) ARGS((byte));
{
  extern char ProgName[], ProgVers[], FileName[];

  long    Nbyte;
  char    s[80], **q;
  byte    *ptr, *end;
  int     i, nc, k, current, previous, run, y;
  int     Xmin, Xmax, Ymin, Ymax;
  float   Angle, Scale, ScaleX, ScaleY, DeltaX, DeltaY;

  static char h[] = "0123456789abcdef";

  static char *PostScript[] = {
    "%***********************************************************************",
    "%*                                                                     *",
    "%* Object: Image decoding PS-routine                    Date: 01.02.93 *",
    "%* Author: Evgeni CHERNYAEV (chernaev@vxcern.cern.ch)                  *",
    "%*                                                                     *",
    "%* Function: Display a run-length encoded color image.                 *",
    "%*           The image is displayed in color on viewers and printers   *",
    "%*           that support color Postscript, otherwise it is displayed  *",
    "%*           as grayscale.                                             *",
    "%*                                                                     *",
    "%***********************************************************************",
    "/byte 1 string def",
    "/color 3 string def",
    "systemdict /colorimage known { /cnt 3 def } { /cnt 1 def } ifelse",
    "/String 256 cnt mul string def",
    "%***********************************************************************",
    "/DecodePacket            % Decode color packet                         *",
    "%***********************************************************************",
    "{",
    "  currentfile byte readhexstring pop 0 get",
    "  /Nbyte exch 1 add cnt mul def",
    "  /color ColorMap currentfile byte readhexstring pop 0 get get def",
    "  String dup",
    "  0 cnt Nbyte 1 sub { color putinterval dup } for",
    "  pop 0 Nbyte getinterval",
    "} bind def",
    "%***********************************************************************",
    "/DisplayImage            % Display run-length encoded color image      *",
    "%***********************************************************************",
    "{",
    "  gsave",
    "  currentfile String readline pop",
    "  token { /degrees exch def pop } { } ifelse",
    "  degrees rotate",
    "  currentfile String readline pop",
    "  token { /x exch def } { } ifelse",
    "  token { /y exch def pop } { } ifelse",
    "  x y translate",
    "  currentfile String readline pop",
    "  token { /x exch def } { } ifelse",
    "  token { /y exch def pop } { } ifelse",
    "  x y scale",
    "  currentfile String readline pop",
    "  token { /columns exch def } { } ifelse",
    "  token { /rows exch def pop } { } ifelse",
    "  currentfile String readline pop",
    "  token { /Ncol exch def pop } { } ifelse",
    "  /ColorMap Ncol array def",
    "  systemdict /colorimage known {",
    "    0 1 Ncol 1 sub {",
    "      ColorMap exch",
    "      currentfile 3 string readhexstring pop put",
    "    } for",
    "    columns rows 8",
    "    [ columns 0 0 rows neg 0 rows ]",
    "    { DecodePacket } false 3 colorimage",
    "  }{",
    "    0 1 Ncol 1 sub {",
    "      ColorMap exch",
    "      1 string dup 0",
    "      currentfile color readhexstring pop pop",
    "      color 0 get 0.299 mul",
    "      color 1 get 0.587 mul add",
    "      color 2 get 0.114 mul add",
    "      cvi put put",
    "    } for",
    "    columns rows 8",
    "    [ columns 0 0 rows neg 0 rows ]",
    "    { DecodePacket } image",
    "  } ifelse",
    "  grestore",
    "  showpage",
    "} bind def",
    "%***********************************************************************",
    "%*                          Image decoding                             *",
    "%***********************************************************************",
    "DisplayImage",
    NULL
  };

  /*   C H E C K   P A R A M E T E R S   */
  
  if (Width <= 0 || Width > MAXWIDTH || Height <= 0 || Height > MAXWIDTH) {
    fprintf(stderr,
            "\n%s: incorrect image size: %d x %d\n", ProgName, Width, Height);
    return 0;
  }

  if (Ncol <= 0 || Ncol > 256) {
    fprintf(stderr,"\n%s: wrong number of colors: %d\n", ProgName, Ncol);
    return 0;
  }

  /*   F I N D   R O T A T I O N   A N G L E ,   S C A L E,
       T R A N S L A T I O N   &   B O U N D I N G   B O X   */

  if (Width < Height) {
    Angle  = 0.0;
    ScaleX = (float)(PageWidth - 2*PageSideMargin) / (float)(Width);
    ScaleY = (float)(PageHeight - 2*PageTopMargin) / (float)(Height);
    Scale  = ScaleX < ScaleY ? ScaleX : ScaleY;
    ScaleX = Width*Scale;
    ScaleY = Height*Scale;
    DeltaX = PageWidth - (ScaleX + 2*PageSideMargin);
    if (DeltaX > 0.)
      DeltaX = 0.5*DeltaX + PageSideMargin;  
    else
      DeltaX = PageSideMargin;
    DeltaY = PageHeight - (ScaleY + 2*PageTopMargin);
    if (DeltaY > 0.)
      DeltaY = 0.5*DeltaY + PageTopMargin;  
    else
      DeltaY = PageTopMargin;
    Xmin   = DeltaX; 
    Ymin   = DeltaY; 
    Xmax   = DeltaX + ScaleX; 
    Ymax   = DeltaY + ScaleY; 
  }else{
    Angle  = 90.0;
    ScaleX = (float)(PageHeight - 2*PageTopMargin) / (float)(Width);
    ScaleY = (float)(PageWidth - 2*PageSideMargin) / (float)(Height);
    Scale  = ScaleX < ScaleY ? ScaleX : ScaleY;
    ScaleX = Width*Scale;
    ScaleY = Height*Scale;
    DeltaX = PageHeight - (ScaleX + 2*PageTopMargin);
    if (DeltaX > 0.)
      DeltaX = 0.5*DeltaX + PageTopMargin;  
    else
      DeltaX = PageTopMargin;
    DeltaY = PageWidth - (ScaleY + 2*PageSideMargin);
    if (DeltaY > 0.)
      DeltaY = 0.5*DeltaY + PageSideMargin;  
    else
      DeltaY = PageSideMargin;
    Xmin   = DeltaY; 
    Ymin   = DeltaX; 
    Xmax   = DeltaY + ScaleY; 
    Ymax   = DeltaX + ScaleX; 
    DeltaY = -(DeltaY + ScaleY);
  }

  /*   O U T P U T   H E A D E R   */

  Nbyte = 0;
  sprintf(s,"%%!PS-Adobe-2.0 EPSF-2.0\n");               put_string;
  sprintf(s,"%%%%Title: %s\n",FileName);                 put_string;
  sprintf(s,"%%%%Creator: %s %s\n", ProgName, ProgVers); put_string;
  sprintf(s,"%%%%BoundingBox: %d %d %d %d\n", Xmin, Ymin, Xmax, Ymax);
                                                         put_string;
  sprintf(s,"%%%%EndComments\n");                        put_string;

  /*   O U T P U T   P O S T S C R I P T   P R O G R A M M   */

  for (q=PostScript; *q; q++) {
    sprintf(s,"%s\n",*q);                          put_string;
  }

  /*   O U T P U T   I M A G E   D A T A   */

  sprintf(s,"%.2f\n", Angle);                      put_string;
  sprintf(s,"%.2f %.2f\n", DeltaX, DeltaY);        put_string;
  sprintf(s,"%.2f %.2f\n", ScaleX, ScaleY);        put_string;
  sprintf(s,"%d %d\n", Width, Height);             put_string;
  sprintf(s,"%d\n",Ncol);                          put_string;

  for (k=0; k<Ncol; k++) {
    sprintf(s,"%02x%02x%02x", R[k], G[k], B[k]);   put_string; 
    if (k % 10 == 9 || k == Ncol-1) { 
      sprintf(s,"\n");                             put_string;
    }else{
      sprintf(s," ");                              put_string;
    }
  }

  /*   R U N - L E N G T H    C O M P R E S S I O N   */

  run   = 0;
  nc    = 0;
  s[72] = '\n';
  s[73] = '\0';
  for(y=0; y<Height; y++) {
    (*get_scline)(y, Width, ScLine);
    ptr = ScLine;
    end = ptr + Width;
    if (y == 0) previous = *ptr++;
    while (ptr < end) {
      current = *ptr++;
      if (current == previous && run < 255) {
        run++;
        continue;
      }
      if (nc == 72) {
        put_string; 
        nc = 0;
      }
      s[nc++] = h[run / 16];
      s[nc++] = h[run % 16];
      s[nc++] = h[previous / 16];
      s[nc++] = h[previous % 16];
      previous = current;
      run = 0;
    }
  }

  if (nc == 72) {
    put_string; 
    nc = 0;
  }
  s[nc++] = h[run / 16];
  s[nc++] = h[run % 16];
  s[nc++] = h[previous / 16];
  s[nc++] = h[previous % 16];
  s[nc++] = '\n';
  s[nc]   = '\0';
  put_string; 

  /*   O U T P U T   T R A I L E R   */

  sprintf(s,"%%%%Trailer\n");                      put_string;
  return Nbyte;
}
