#include <stdio.h>

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

#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_word ARGS((int));

/***********************************************************************
 *                                                                     *
 * Name: PCXencode                                   Date:    18.11.92 *
 * Author: E.Chernyaev (IHEP/Protvino)               Revised:          *
 *                                                                     *
 * Function: PCX 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 PCX                                                 *
 *                                                                     *
 ***********************************************************************/
long PCXencode(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, n;

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

  if (Ncol <= 0 || Ncol > 256) {
    fprintf(stderr,"\nPCXencode: 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   H E A D E R   */
 
  put_byte(0x0a);                       /* PCX magic number */
  put_byte(0x05);                       /* PC Paintbrush version */
  put_byte(0x01);                       /* PCX run length encoding */
  put_byte(8);                          /* bits per pixel */

  put_word(0);                          /* image left */
  put_word(0);                          /* image top  */
  put_word(Width-1);                    /* image right */
  put_word(Height-1);                   /* image bottom */

  put_word(Width);                      /* horizontal resolution */
  put_word(Height);                     /* vertical resolution */

  n = (Ncol < 16) ? Ncol : 16;          /* dummy color palette */
  for (i = 0; i < n; ++i) {
    put_byte(R[i]);
    put_byte(G[i]);
    put_byte(B[i]);
  }
  for (; i < 16; ++i) {
    put_byte(255);
    put_byte(255);
    put_byte(255);
  }

  put_byte(0);                          /* reserved byte */
  put_byte(1);                          /* number of color planes */
  put_word(Width);                      /* number of bytes per scanline */
  put_word(1);                          /* pallette info */

  for (i = 0; i < 58; ++i) {              /* fill to end of header */
    put_byte(0);
  }

  /*   W R I T E   I M A G E   */

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

  /*   W R I T E   P A L E T T E  */

  put_byte(0x0c);                       /* magic for 256 colors */
  for (i = 0; i < Ncol; ++i) {
    put_byte(R[i]);
    put_byte(G[i]);
    put_byte(B[i]);
  }
  for (; i < 256; ++i) {
    put_byte(255);
    put_byte(255);
    put_byte(255);
  }

  return Nbyte;
}

static void put_scline(buf, size)
                 byte *buf;
                 int  size;
{
  byte       *ptr, *end;
  int        current, previous, count;
  
  ptr      = buf; 
  end      = ptr + size;
  previous = *ptr++;
  count    = 1;

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

  while (ptr < end) {
    current  = *ptr++;
    if (current == previous && count < 63) {
      count++;
      continue;
    }
    if (count > 1 || (previous & 0xc0) == 0xc0) {
      count |= 0xc0;
      put_byte(count);
    }
    put_byte(previous);
    previous = current;
    count    = 1;
  }
  if (count > 1 || (previous & 0xc0) == 0xc0) {
    count |= 0xc0;
    put_byte(count);
  }
  put_byte(previous);
}

static void put_word(i)
                 int i;
{
  put_byte(i & 0xff);
  put_byte((i>>8) & 0xff);
} 
