#include <stdio.h>

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

#define HEADER_SIZE          512
#define MAXCOLORS            256

#define PICT_clipRgn         0x01       /* PICT opcodes */
#define PICT_picVersion      0x11
#define PICT_PackBitsRect    0x98
#define PICT_EndOfPicture    0xFF
#define PICT_headerOp        0x0C00

#define put_byte(A) (*put_b)((byte)(A)); Nbyte++

typedef unsigned char byte;

static long      Nbyte;
static void     (*put_b) ARGS ((byte));

static void     put_scline ARGS((byte *, int));
static void     put_short ARGS((int));
static void     put_long ARGS((long));
static void     put_fixed ARGS((int, int));
static void     put_rect ARGS((int, int, int, int));
static void     put_fill ARGS((int));

/***********************************************************************
 *                                                                     *
 * Name: PICTencode                                  Date:    20.11.92 *
 * Author: E.Chernyaev (IHEP/Protvino)               Revised:          *
 *                                                                     *
 * Function: PICT (Macintosh) compression of the image                 *
 *                                                                     *
 * Input: Width      - image width  (must be >= 8)                     *
 *        Height     - image height (must be >= 8)                     *
 *        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 PICT                                                *
 *                                                                     *
 ***********************************************************************/
long PICTencode(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));
{
  int        i;

  /*   C H E C K   P A R A M E T E R S   */
  
  if (Width < 8 || Height < 8) {
    fprintf(stderr,"\nPICTencode: very small image, must be >= 8x8\n");
    return 0;
  }

  if (Width > 4096 || Height > 4096) {
    fprintf(stderr,"\nPICTencode: too big image: %d x %d\n", Width, Height);
    return 0;
  }

  if (Ncol <= 0 || Ncol > 256) {
    fprintf(stderr,"\nPICTencode: wrong number of colors: %d\n", Ncol);
    return 0;
  }
      
  /*   I N I T I A L I S A T I O N   */
      
  put_b   = pb;
  Nbyte   = 0;

  /*   W R I T E   O U T   T H E   H E A D E R   */

  put_fill(HEADER_SIZE);                /* write the header */

  put_short(0xffff);                    /* write picSize(?) and picFrame */
  put_rect(0, 0, Height, Width);

  put_short(PICT_picVersion);           /* write version op and version */
  put_short(0x02FF);
  put_short(PICT_headerOp);
  put_long(-1L);
  put_fixed(0, 0);
  put_fixed(0, 0);
  put_fixed(Width, 0);
  put_fixed(Height, 0);
  put_fill(4);

  put_short(PICT_clipRgn);              /* needed by many PICT2 programs */
  put_short(10);
  put_rect(0, 0, Height, Width);

  put_short(PICT_PackBitsRect);         /* write picture */
  put_short(Width | 0x8000);
  put_rect(0, 0, Height, Width);
  put_short(0);                         /* pmVersion */
  put_short(0);                         /* packType */
  put_long(0L);                         /* packSize */
  put_fixed(72, 0);                     /* hRes */
  put_fixed(72, 0);                     /* vRes */
  put_short(0);                         /* pixelType */
  put_short(8);                         /* pixelSize */
  put_short(1);                         /* cmpCount */
  put_short(8);                         /* cmpSize */
  put_long(0L);                         /* planeBytes */
  put_long(0L);                         /* pmTable */
  put_long(0L);                         /* pmReserved */
  put_long(0L);                         /* ctSeed */
  put_short(0);                         /* ctFlags */
  put_short(Ncol-1);                    /* ctSize */

  /*   W R I T E   O U T   T H E   C O L O R M A P   */

  for (i = 0; i < Ncol; i++) {
    put_short(i);
    put_short((int)(R[i]*65535L/255L));
    put_short((int)(G[i]*65535L/255L));
    put_short((int)(B[i]*65535L/255L));
  }

  put_rect(0, 0, Height, Width);        /* srcRect */
  put_rect(0, 0, Height, Width);        /* dstRect */
  put_short(0);                         /* mode */


  /*   W R I T E   O U T   T H E   I M A G E   */

  for (i = 0; i < Height; i++) {
    (*get_scline)(i, Width, ScLine);
    put_scline(ScLine, Width);
  }

  if (Nbyte & 1) { put_byte(0); }       /* if we wrote odd # of bytes */

  put_short(PICT_EndOfPicture);
  return (Nbyte);
}

#define RUN_THRESH        3
#define MAX_RUN          128            /* 0xff = 2, 0xfe = 3, etc */
#define MAX_COUNT        128            /* 0x00 = 1, 0x01 = 2, etc */

#define RunToChar(c)     (257-(c))
#define CountToChar(c)   ((c)-1)

static void put_scline(buf, size)
                 byte *buf;
                 int  size;
{
  byte       *ptr, *end;
  int        current, previous, run, count, rep;
  
  /*   F I N D   N U M B E R   O F   B Y T E S   TO BE WRITTEN   */

  ptr      = buf; 
  end      = ptr + size;
  previous = *ptr++;
  run      = 1;
  count    = 0;
  rep      = 0;
  while (ptr < end) {
    current = *ptr++;
    if (current == previous && run < MAX_RUN) {
      run++;
      continue;
    }
    if (run < RUN_THRESH) {
      count   += run;
      previous = current;
      run      = 1;
      continue;
    }
    rep += count + (count+MAX_COUNT-1)/MAX_COUNT + 2; 
    previous = current;
    run      = 1;
    count    = 0;
  }
  if (run < RUN_THRESH) {               /* scan line finished */
    count += run;
    run    = 0;
  }
  rep += count + (count+MAX_COUNT-1)/MAX_COUNT;
  if (run > 0) rep += 2;

  /*   W R I T E    B Y T E   C O U N T E R   */

  if (size > 250)
    put_short(rep);
  else
    { put_byte(rep); }

  /*   R U N   L E N G T H   E N C O D I N G   */

  ptr      = buf; 
  previous = *ptr++;
  run      = 1;
  count    = 0;
  while (ptr < end) {
    current = *ptr++;
    if (current == previous && run < MAX_RUN) {
      run++;
      continue;
    }
    if (run < RUN_THRESH) {
      count += run;
      previous = current;
      run = 1;
      continue;
    }
    ptr   -= run + count;
    while (count > 0) {
      rep = count > MAX_COUNT ? MAX_COUNT : count;
      put_byte(CountToChar(rep));
      count -= rep;
      while (rep > 0) {
        put_byte(*ptr++);
        rep--;
      }
    }
    ptr   += run;
    put_byte(RunToChar(run));
    put_byte(previous);
    previous = current;
    run = 1;
  }
  if (run < RUN_THRESH) {               /* scan line finished */
    count += run;
    run    = 0;
  }
  ptr   -= run + count;
  while (count > 0) {
    rep = count > MAX_COUNT ? MAX_COUNT : count;
    put_byte(CountToChar(rep));
    count -= rep;
    while (rep > 0) {
      put_byte(*ptr++);
      rep--;
    }
  }
  if (run > 0) {
    put_byte(RunToChar(run));
    put_byte(previous);
  }
}

static void put_short(k)
                  int k;
{
  put_byte((k >> 8) & 0xFF);
  put_byte(k & 0xFF);
}  

static void put_long(k)
                long k;
{
  put_byte((k >> 24) & 0xFF);
  put_byte((k >> 16) & 0xFF);
  put_byte((k >>  8) & 0xFF);
  put_byte(k & 0xFF);
}

static void put_fixed(in, frac)
                  int in, frac;
{
  put_short(in);
  put_short(frac);
}

static void put_rect(x1, x2, y1, y2)
                 int x1, x2, y1, y2;
{
  put_short(x1);
  put_short(x2);
  put_short(y1);
  put_short(y2);
}

static void put_fill(n)
                 int n;
{
  int i;

   for (i=0; i<n; i++) { put_byte(0); }
}
