/*
 * pcxtoppm.c - Converts from a PC Paintbrush PCX file to a PPM file.
 *
 * Copyright (c) 1990 by Michael Davidson
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * This file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.
 *
 */

#define	PCX_MAGIC	0x0a					/* PCX magic number               */
#define	PCX_HDR_SIZE	128					/* size of PCX header             */
#define	PCX_256_COLORS	0x0c					/* magic number for 256 colors    */

#define	MAXCOLORS   	256
#define	MAXPLANES	4
#define	PCX_MAXVAL	255

static void read_pcx_image ARGS((FILE * fp, unsigned char *buf, int BytesPerLine, int Planes, int Height));
static void pcx_planes_to_pixels ARGS((unsigned char *pixels, unsigned char *bitplanes, int bytesperline, int planes, int bitsperpixel));
static void pcx_unpack_pixels ARGS((unsigned char *pixels, unsigned char *bitplanes, int bytesperline, int planes, int bitsperpixel));
static int GetByte ARGS((FILE * fp));
static int GetWord ARGS((FILE * fp));

int main(argc, argv)
    int argc;
    char *argv[];
{
  register int i;
  FILE *ifp;
  char *ifname;
  int Version;
  int Xmin, Ymin, Xmax, Ymax;
  int Width, Height;
  register int x, y;
  int Planes;
  int BitsPerPixel;
  int BytesPerLine;
  unsigned char Red[MAXCOLORS], Green[MAXCOLORS], Blue[MAXCOLORS];
  unsigned char *pcximage;
  unsigned char *pcxplanes;
  unsigned char *pcxpixels;
  pixel **pixels;

  ppm_init(&argc, argv);

  switch (argc) {
    case 1:
      ifname = "standard input";
      ifp = stdin;
      break;
    case 2:
      ifname = argv[1];
      ifp = pm_openr(ifname);
      break;
    default:
      pm_usage("[pcxfile]");
      break;
  }

  /*
   * read the PCX header
   */
  if (GetByte(ifp) != PCX_MAGIC)
    pm_error("%s is not a PCX file", ifname);

  Version = GetByte(ifp);					/* get version #                       */

  if (GetByte(ifp) != 1)					/* check for PCX run length encoding      */
    pm_error("%s has unknown encoding scheme", ifname);

  BitsPerPixel = GetByte(ifp);
  Xmin = GetWord(ifp);
  Ymin = GetWord(ifp);
  Xmax = GetWord(ifp);
  Ymax = GetWord(ifp);

  Width = (Xmax - Xmin) + 1;
  Height = (Ymax - Ymin) + 1;

  (void)GetWord(ifp);						/* ignore horizontal resolution */
  (void)GetWord(ifp);						/* ignore vertical resolution   */

  /*
   * get the 16-color color map
   */
  for (i = 0; i < 16; i++) {
    Red[i] = GetByte(ifp);
    Green[i] = GetByte(ifp);
    Blue[i] = GetByte(ifp);
  }

  (void)GetByte(ifp);						/* skip reserved byte    */
  Planes = GetByte(ifp);					/* # of color planes     */
  BytesPerLine = GetWord(ifp);					/* # of bytes per line   */
  (void)GetWord(ifp);						/* ignore palette info   */

  /*
   * check that we can handle this image format
   */
  switch (BitsPerPixel) {
    case 1:
      if (Planes > 4)
	pm_error("can't handle image with more than 4 planes");
      break;

    case 2:
    case 4:
    case 8:
      if (Planes == 1)
	break;
    default:
      pm_error("can't handle %d bits per pixel image with %d planes",
	       BitsPerPixel, Planes);
  }

  /*
   * read the pcx format image
   */
  fseek(ifp, (long)PCX_HDR_SIZE, 0);
  pcximage = (unsigned char *)pm_allocrow(BytesPerLine * Planes, Height);
  read_pcx_image(ifp, pcximage, BytesPerLine, Planes, Height);

  /*
   * 256 color images have their color map at the end of the file
   * preceeded by a magic byte
   */
  if (BitsPerPixel == 8) {
    if (GetByte(ifp) != PCX_256_COLORS)
      pm_error("bad color map signature");

    for (i = 0; i < MAXCOLORS; i++) {
      Red[i] = GetByte(ifp);
      Green[i] = GetByte(ifp);
      Blue[i] = GetByte(ifp);
    }
  }

  pixels = ppm_allocarray(Width, Height);
  pcxpixels = (unsigned char *)pm_allocrow(Width + 7, 1);

  /*
   * convert the image
   */
  for (y = 0; y < Height; y++) {
    pcxplanes = pcximage + (y * BytesPerLine * Planes);

    if (Planes == 1) {
      pcx_unpack_pixels(pcxpixels, pcxplanes,
			BytesPerLine, Planes, BitsPerPixel);
    }
    else {
      pcx_planes_to_pixels(pcxpixels, pcxplanes,
			   BytesPerLine, Planes, BitsPerPixel);
    }

    for (x = 0; x < Width; x++) {
      i = pcxpixels[x];
      PPM_ASSIGN(pixels[y][x], Red[i], Green[i], Blue[i]);
    }
  }

  pm_close(ifp);

  ppm_writeppm(stdout, pixels, Width, Height, (pixval) 255, 0);

  pm_close(stdout);

  exit(0);
}

static void read_pcx_image(fp, buf, BytesPerLine, Planes, Height)
    FILE *fp;
    unsigned char *buf;
    int BytesPerLine;
    int Planes;
    int Height;
{
  int c;
  int nbytes;
  int count;

  nbytes = BytesPerLine * Planes * Height;

  while (nbytes > 0) {
    c = GetByte(fp);
    if ((c & 0xc0) != 0xc0) {
      *buf++ = c;
      --nbytes;
      continue;
    }

    count = c & 0x3f;
    c = GetByte(fp);
    if (count > nbytes)
      pm_error("repeat count spans end of image, count = %d, nbytes = %d", count, nbytes);

    nbytes -= count;
    while (--count >= 0)
      *buf++ = c;
  }
}

/*
 * convert multi-plane format into 1 pixel per byte
 */
static void pcx_planes_to_pixels(pixels, bitplanes, bytesperline, planes, bitsperpixel)
    unsigned char *pixels;
    unsigned char *bitplanes;
    int bytesperline;
    int planes;
    int bitsperpixel;
{
  int i, j;
  int npixels;
  unsigned char *p;

  if (planes > 4)
    pm_error("can't handle more than 4 planes");
  if (bitsperpixel != 1)
    pm_error("can't handle more than 1 bit per pixel");

  /*
   * clear the pixel buffer
   */
  npixels = (bytesperline * 8) / bitsperpixel;
  p = pixels;
  while (--npixels >= 0)
    *p++ = 0;

  /*
   * do the format conversion
   */
  for (i = 0; i < planes; i++) {
    int pixbit, bits, mask;

    p = pixels;
    pixbit = (1 << i);
    for (j = 0; j < bytesperline; j++) {
      bits = *bitplanes++;
      for (mask = 0x80; mask != 0; mask >>= 1, p++)
	if (bits & mask)
	  *p |= pixbit;
    }
  }
}

/*
 * convert packed pixel format into 1 pixel per byte
 */
static void pcx_unpack_pixels(pixels, bitplanes, bytesperline, planes, bitsperpixel)
    unsigned char *pixels;
    unsigned char *bitplanes;
    int bytesperline;
    int planes;
    int bitsperpixel;
{
  register int bits;

  if (planes != 1)
    pm_error("can't handle packed pixels with more than 1 plane");
  if (bitsperpixel == 8) {
    while (--bytesperline >= 0)
      *pixels++ = *bitplanes++;
  }
  else if (bitsperpixel == 4) {
    while (--bytesperline >= 0) {
      bits = *bitplanes++;
      *pixels++ = (bits >> 4) & 0x0f;
      *pixels++ = (bits) & 0x0f;
    }
  }
  else if (bitsperpixel == 2) {
    while (--bytesperline >= 0) {
      bits = *bitplanes++;
      *pixels++ = (bits >> 6) & 0x03;
      *pixels++ = (bits >> 4) & 0x03;
      *pixels++ = (bits >> 2) & 0x03;
      *pixels++ = (bits) & 0x03;
    }
  }
  else if (bitsperpixel == 1) {
    while (--bytesperline >= 0) {
      bits = *bitplanes++;
      *pixels++ = ((bits & 0x80) != 0);
      *pixels++ = ((bits & 0x40) != 0);
      *pixels++ = ((bits & 0x20) != 0);
      *pixels++ = ((bits & 0x10) != 0);
      *pixels++ = ((bits & 0x08) != 0);
      *pixels++ = ((bits & 0x04) != 0);
      *pixels++ = ((bits & 0x02) != 0);
      *pixels++ = ((bits & 0x01) != 0);
    }
  }
}

static int GetByte(fp)
    FILE *fp;
{
  int c;

  if ((c = getc(fp)) == EOF)
    pm_error("unexpected end of file");

  return c;
}

static int GetWord(fp)
    FILE *fp;
{
  int c;

  c = GetByte(fp);
  c |= (GetByte(fp) << 8);
  return c;
}

/* ppmtopcx.c - read a portable pixmap and produce a PCX file
 *
 * Copyright (C) 1990 by Michael Davidson.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  This software is provided "as is" without express or
 * implied warranty.
 */

#define MAXCOLORS	256
#define	MAXPLANES	4

/*
 * Pointer to function returning an int
 */
typedef void (*vfunptr) ARGS((int, int, unsigned char *, int, int));

static void PCXEncode ARGS((FILE * fp, int GWidth, int GHeight, int Colors, int Red[], int Green[], int Blue[], vfunptr GetPlanes));
static void PutPlane ARGS((FILE * fp, unsigned char *buf, int Size));
static void ReadPlanes ARGS((int y, int width, unsigned char *buf, int planes, int bits));
static void Putword ARGS((int w, FILE * fp));
static void Putbyte ARGS((int b, FILE * fp));

static pixel **pixels;
static colorhash_table cht;

int main(argc, argv)
    int argc;
    char *argv[];
{
  FILE *ifp;
  int argn, rows, cols, colors, i;
  pixval maxval;
  pixel black_pixel;
  colorhist_vector chv;
  int Red[MAXCOLORS], Green[MAXCOLORS], Blue[MAXCOLORS];
  char *usage = "[ppmfile]";

  ppm_init(&argc, argv);

  argn = 1;

  if (argn < argc) {
    ifp = pm_openr(argv[argn]);
    ++argn;
  }
  else
    ifp = stdin;

  if (argn != argc)
    pm_usage(usage);

  pixels = ppm_readppm(ifp, &cols, &rows, &maxval);

  pm_close(ifp);

  /* Figure out the colormap. */
  pm_message("computing colormap...");
  chv = ppm_computecolorhist(pixels, cols, rows, MAXCOLORS, &colors);
  if (chv == (colorhist_vector) 0)
    pm_error(
	      "too many colors - try doing a 'ppmquant %d'", MAXCOLORS);
  pm_message("%d colors found", colors);

  /* Force black to slot 0 if possible. */
  PPM_ASSIGN(black_pixel, 0, 0, 0);
  ppm_addtocolorhist(chv, &colors, MAXCOLORS, &black_pixel, 0, 0);

  /* Now turn the ppm colormap into the appropriate PCX colormap. */
  if (maxval > 255)
    pm_message(
		"maxval is not 255 - automatically rescaling colors");
  for (i = 0; i < colors; ++i) {
    if (maxval == 255) {
      Red[i] = PPM_GETR(chv[i].color);
      Green[i] = PPM_GETG(chv[i].color);
      Blue[i] = PPM_GETB(chv[i].color);
    }
    else {
      Red[i] = (int)PPM_GETR(chv[i].color) * 255 / maxval;
      Green[i] = (int)PPM_GETG(chv[i].color) * 255 / maxval;
      Blue[i] = (int)PPM_GETB(chv[i].color) * 255 / maxval;
    }
  }

  /* And make a hash table for fast lookup. */
  cht = ppm_colorhisttocolorhash(chv, colors);
  ppm_freecolorhist(chv);

  /* All set, let's do it. */
  PCXEncode(stdout, cols, rows, colors, Red, Green, Blue, ReadPlanes);

  exit(0);
}

/*****************************************************************************
 *
 * PCXENCODE.C    - PCX Image compression interface
 *
 * PCXEncode( FName, GHeight, GWidth, Colors, Red, Green, Blue, GetPlanes )
 *
 *****************************************************************************/

/* public */

static void PCXEncode(fp, GWidth, GHeight, Colors, Red, Green, Blue, GetPlanes)
    FILE *fp;
    int GWidth, GHeight;
    int Colors;
    int Red[], Green[], Blue[];
    vfunptr GetPlanes;
{
  int BytesPerLine;
  int Planes;
  int BitsPerPixel;
  unsigned char *buf;
  int i;
  int n;
  int y;

  /*
   * select number of planes and number of bits
   * per pixel according to number of colors
   */
  /*
   * 16 colors or less are handled as 1 bit per pixel
   * with 1, 2, 3 or 4 color planes.
   * more than 16 colors are handled as 8 bits per pixel
   * with 1 plane
   */
  if (Colors > 16) {
    BitsPerPixel = 8;
    Planes = 1;
  }
  else {
    BitsPerPixel = 1;
    if (Colors > 8)
      Planes = 4;
    else if (Colors > 4)
      Planes = 3;
    else if (Colors > 2)
      Planes = 2;
    else
      Planes = 1;
  }

  /*
   * Write the PCX header
   */
  Putbyte(0x0a, fp);						/* .PCX magic number            */
  Putbyte(0x05, fp);						/* PC Paintbrush version        */
  Putbyte(0x01, fp);						/* .PCX run length encoding     */
  Putbyte(BitsPerPixel, fp);					/* bits per pixel               */

  Putword(0, fp);						/* x1   - image left            */
  Putword(0, fp);						/* y1   - image top             */
  Putword(GWidth - 1, fp);					/* x2   - image right           */
  Putword(GHeight - 1, fp);					/* y2   - image bottom          */

  Putword(GWidth, fp);						/* horizontal resolution        */
  Putword(GHeight, fp);						/* vertical resolution          */

  /*
   * Write out the Color Map for images with 16 colors or less
   */
  n = (Colors <= 16) ? Colors : 16;
  for (i = 0; i < n; ++i) {
    Putbyte(Red[i], fp);
    Putbyte(Green[i], fp);
    Putbyte(Blue[i], fp);
  }
  for (; i < 16; ++i) {
    Putbyte(255, fp);
    Putbyte(255, fp);
    Putbyte(255, fp);
  }

  Putbyte(0, fp);						/* reserved byte                */

  Putbyte(Planes, fp);						/* number of color planes       */

  BytesPerLine = ((GWidth * BitsPerPixel) + 7) / 8;
  Putword(BytesPerLine, fp);					/* number of bytes per scanline */

  Putword(1, fp);						/* pallette info                */

  for (i = 0; i < 58; ++i)					/* fill to end of header  */
    Putbyte(0, fp);

  buf = (unsigned char *)malloc(MAXPLANES * BytesPerLine);

  for (y = 0; y < GHeight; ++y) {
    (*GetPlanes) (y, GWidth, buf, Planes, BitsPerPixel);

    for (i = 0; i < Planes; ++i)
      PutPlane(fp, buf + (i * BytesPerLine), BytesPerLine);
  }

  /*
   * color map for > 16 colors is at end of file
   */
  if (Colors > 16) {
    Putbyte(0x0c, fp);						/* magic for 256 colors */
    for (i = 0; i < Colors; ++i) {
      Putbyte(Red[i], fp);
      Putbyte(Green[i], fp);
      Putbyte(Blue[i], fp);
    }
    for (; i < MAXCOLORS; ++i) {
      Putbyte(255, fp);
      Putbyte(255, fp);
      Putbyte(255, fp);
    }
  }

  fclose(fp);
}

static void PutPlane(fp, buf, Size)
    FILE *fp;
    unsigned char *buf;
    int Size;
{
  unsigned char *end;
  int c;
  int previous;
  int count;

  end = buf + Size;

  previous = *buf++;
  count = 1;

  while (buf < end) {
    c = *buf++;
    if (c == previous && count < 63) {
      ++count;
      continue;
    }

    if (count > 1 || (previous & 0xc0) == 0xc0) {
      count |= 0xc0;
      Putbyte(count, fp);
    }
    Putbyte(previous, fp);
    previous = c;
    count = 1;
  }

  if (count > 1 || (previous & 0xc0) == 0xc0) {
    count |= 0xc0;
    Putbyte(count, fp);
  }
  Putbyte(previous, fp);
}

static unsigned long PixMap[8][16] =
{
  0x00000000L, 0x00000080L, 0x00008000L, 0x00008080L,
  0x00800000L, 0x00800080L, 0x00808000L, 0x00808080L,
  0x80000000L, 0x80000080L, 0x80008000L, 0x80008080L,
  0x80800000L, 0x80800080L, 0x80808000L, 0x80808080L,
};

static void ReadPlanes(y, width, buf, planes, bits)
    int y;
    int width;
    unsigned char *buf;
    int planes;
    int bits;
{
  static int first_time = 1;
  unsigned char *plane0, *plane1, *plane2, *plane3;
  int i, j, x;

  /*
   * 256 color, 1 plane, 8 bits per pixel
   */
  if (planes == 1 && bits == 8) {
    for (x = 0; x < width; ++x)
      buf[x] = ppm_lookupcolor(cht, &pixels[y][x]);
    return;
  }

  /*
   * must be 16 colors or less, 4 planes or less, 1 bit per pixel
   */
  if (first_time) {
    for (i = 1; i < 8; ++i)
      for (j = 0; j < 16; ++j)
	PixMap[i][j] = PixMap[0][j] >> i;
    first_time = 0;
  }

  i = (width + 7) / 8;

  plane0 = buf;
  plane1 = plane0 + i;
  plane2 = plane1 + i;
  plane3 = plane2 + i;

  i = 0;
  x = 0;

  while (x < width) {
    register unsigned long t;

    t = PixMap[0][ppm_lookupcolor(cht, &pixels[y][x++])];
    t |= PixMap[1][ppm_lookupcolor(cht, &pixels[y][x++])];
    t |= PixMap[2][ppm_lookupcolor(cht, &pixels[y][x++])];
    t |= PixMap[3][ppm_lookupcolor(cht, &pixels[y][x++])];
    t |= PixMap[4][ppm_lookupcolor(cht, &pixels[y][x++])];
    t |= PixMap[5][ppm_lookupcolor(cht, &pixels[y][x++])];
    t |= PixMap[6][ppm_lookupcolor(cht, &pixels[y][x++])];
    t |= PixMap[7][ppm_lookupcolor(cht, &pixels[y][x++])];

    plane0[i] = t;
    plane1[i] = t >> 8;
    plane2[i] = t >> 16;
    plane3[i++] = t >> 24;
  }
}

/*
 * Write out a word to the PCX file
 */
static void Putword(w, fp)
    int w;
    FILE *fp;
{
  fputc(w & 0xff, fp);
  fputc((w / 256) & 0xff, fp);
}

/*
 * Write out a byte to the PCX file
 */
static void Putbyte(b, fp)
    int b;
    FILE *fp;
{
  fputc(b & 0xff, fp);
}

/* The End */
