/*****************************************************************
 * flpcx.c: FBM Release 1.0 25-Feb-90 Michael Mauldin
 *
 * Copyright (C) 1989,1990 by Michael Mauldin.  Permission is granted
 * to use this file in whole or in part for any purpose, educational,
 * recreational or commercial, provided that this copyright notice
 * is retained unchanged.  This software is available to all free of
 * charge by anonymous FTP and in the UUNET archives.
 *
 * flpcx.c: 
 *
 * CONTENTS
 *	write_pcx (image, stream)
 *	read_pcx (image, stream, mstr, mlen)
 *
 * EDITLOG
 *	LastEditDate = Mon Jun 25 00:17:11 1990 - Michael Mauldin
 *	LastFileName = /usr2/mlm/src/misc/fbm/flpcx.c
 *
 * HISTORY
 * 25-Jun-90  Michael Mauldin (mlm@cs.cmu.edu) Carnegie Mellon
 *	Package for Release 1.0
 *
 * 03-Jan-90  Michael Mauldin (mlm) at Carnegie Mellon University
 *	Added write_pcx, fixed bugs with read_pcx
 *	Beta release (version 0.97) mlm@cs.cmu.edu
 *
 * 12-Nov-88  Michael Mauldin (mlm) at Carnegie-Mellon University
 *	Created.
 *****************************************************************/

# include <stdio.h>
# include <math.h>
# include <ctype.h>
# include "fbm.h"

/****************************************************************
 * pcx.h: Paintbrush file format header, as per "ZSoft Technical
 *	Reference Manual for Publisher's Paintbrush, PC Paintbrush Plus,
 *	PC Paintbrush and Frieze Graphics.", 1988,  ZSoft corporation,
 *
 *	450 Franklin Rd. Suite 100 / Marietta, GA  30067 / 404-428-0008
 *
 * HISTORY
 * {1}	 1-Sep-87  Michael L. Mauldin (mlm) at cognac
 * 	Created.
 * 
 ****************************************************************/

# define UBYTE	unsigned char	/*  8 bits unsigned		*/
# define WORD	short		/* 16 bits signed		*/

typedef struct pcxstruct {
  UBYTE	Manufacturer;		/* 10 == ZSoft PCX		*/
  UBYTE	Version;		/* Version Information		*/
				/*	0 == ver 2.5		*/
				/*	2 == ver 2.8 w/pallete	*/
				/*	3 == 2.8 w/o pallete	*/
				/*	5 == ver 3.0 w/pallete	*/
  UBYTE	Encoding;		/* 01 == PCX run-length encoding */
  UBYTE	BitsPerPixel;		/* 8/number of pixels per byte	*/
  WORD	Window[4];		/* xmin, ymin, xmax, ymax	*/
  WORD	Hres;			/* Horizontal resolution	*/
  WORD	Vres;			/* Vertical resolution		*/
  UBYTE	Colormap[16][3];	/* Color Pallete, RGB in 0..255	*/
  UBYTE	Reserved;		/* Reserved			*/
  UBYTE NPlanes;		/* Number of Color Planes	*/
  WORD	BytesPerLine;		/* Number of bytes per scan line */
  WORD	Palette;		/* 1 = color/BW, 2 = grayscale  */
  UBYTE	Filler[58];		/* Pad header to 128 bytes	*/
} PCXHDR;

# define XMIN 0
# define YMIN 1
# define XMAX 2
# define YMAX 3

# define CNTMSK 0xc0
# define MAXCNT 0x3f

# define swapword(X) ((((X)&0xff) << 8) | (((X) & 0xff00) >> 8))

/****************************************************************
 * write_pcx: Write PC Paintbrush format
 ****************************************************************/

#ifndef lint
static char *fbmid =
"$FBM flpcx.c <1.0> 25-Jun-90  (C) 1989,1990 by Michael Mauldin, source \
code available free from MLM@CS.CMU.EDU and from UUNET archives$";
#endif

write_pcx (image, stream)
FBM *image;
FILE *stream;
{ register unsigned char *line = NULL, *bmp;
  register int r, c, i, word, width, height;
  int rowlen;
  PCXHDR phdr;
  
  width = image->hdr.cols;
  height = image->hdr.rows;

  if (image->hdr.bits != 1)
  { fprintf (stderr, "write_pcx: can't handle %d bits per pixel\n", image->hdr.bits);
    return (0);
  }

  if (image->hdr.physbits != 8)
  { fprintf (stderr, "write_pcx: can't handle %d bits per pixel\n", image->hdr.physbits);
    return (0);
  }

  rowlen = (width + 7) / 8;
  line = (unsigned char *) malloc ((unsigned) rowlen);

  /* Initialize the PCX header */
  phdr.Manufacturer = 10;	/* 10 == ZSoft PCX              */
  phdr.Version = 3;		/* ver 2.8 with pallette        */
  phdr.Encoding = 1;		/* 1 == PCX run-length encoding */
  phdr.BitsPerPixel = 1;	/* 8/number of pixels per byte	*/
  phdr.Window[0] = 1;		/* xmin                         */
  phdr.Window[1] = 1;		/* ymin                         */
  phdr.Window[2] = width;	/* xmax                         */
  phdr.Window[3] = height;	/* ymax                         */
  phdr.Hres = 300;		/* Horizontal resolution (300 dpi) */
  phdr.Vres = 300 * image->hdr.aspect; /* Vertical resolution   */

  /* Clear out the color map */
  for (c=0; c<16; c++)
    for (r=0; r<3; r++)
      phdr.Colormap[c][r] = 0;

  phdr.Reserved = 0;		/* Reserved			*/
  phdr.NPlanes = 1;		/* Number of Color Planes	*/
  phdr.BytesPerLine = rowlen;	/* Number of bytes per scan line */
  phdr.Palette = 0;		/* 0=bw, 1=color, 2==grey       */

  /* Clear filler */
  for (c=0; c<58; c++) phdr.Filler[c] = 0;

  /* PCX uses Little Indian byte order, swap on SUNS, RTs, ... */
  if (machine_byte_order () == BIG)
  { phdr.Window[0] =     swapword (phdr.Window[0]);
    phdr.Window[1] =     swapword (phdr.Window[1]);
    phdr.Window[2] =     swapword (phdr.Window[2]);
    phdr.Window[3] =     swapword (phdr.Window[3]);
    phdr.Hres =          swapword (phdr.Hres);
    phdr.Vres =          swapword (phdr.Vres);
    phdr.BytesPerLine =  swapword (phdr.BytesPerLine);
    phdr.Palette =       swapword (phdr.Palette);
  }
  
  /* Write out header */
  fwrite ((char *) &phdr, sizeof (phdr), 1, stream);

  /* For each scan line */
  for (r=0; r<height; r++)
  { 
    /* bmp points to scan line in memory in FBM format */
    bmp = &(image->bm[r * image->hdr.rowlen]);

    /* Pack bits into a row of 8 bits per byte, then call writepcxrow */
    for (i=0; i<rowlen; i++)
    { word = 0;
      for (c=0; c<8; c++)
      { word <<= 1;
        word |= *bmp++ ? 1 : 0;
      }
      line[i] = word;
    }
    
    writepcxrow (line, rowlen, stream);
  }
  
  free ((char *) line);
  return (1);
}


/****************************************************************
 * Writepcxrow
 ****************************************************************/

# define CNTMSK	0xc0
# define MAXCNT 0x3f

writepcxrow (row, len, stream)
UBYTE *row;
FILE *stream;
{ register int cnt, byte;

  cnt=1;
  byte = *row++;
  len--;
  
  while (len > 0 || cnt > 0)
  { if (len > 0 && cnt < MAXCNT && *row == byte)
    { cnt++; row++; len--; }
    else
    { if (cnt > 1 || (byte & CNTMSK) == CNTMSK)
      { fputc (CNTMSK | cnt, stream);
	fputc (byte, stream);
      }
      else if (cnt == 1)
      { fputc (byte, stream); }
      
      cnt=0;

      if (len > 0)
      { byte = *row++; cnt++; len--; }
    }
  }
}

/****************************************************************
 * read_pcx: Read PC Paintbrush format
 ****************************************************************/

read_pcx (image, rfile, mstr, mlen)
FBM *image;
FILE *rfile;
char *mstr;
int mlen;
{ PCXHDR phdr;
  char *hp;
  register unsigned char *bmp;
  register int k, r, c, bit, byte, mask, width, height, rowlen;
  int depth, ptype, color, enc, clrlen, totalbytes;
  unsigned char *buf, *tail;

  /* Read PCX file header */
  hp = (char *) &phdr;

  if (mlen > 0) strncpy (hp, mstr, mlen);

  if (! fread ((char *) hp+mlen, sizeof (phdr) - mlen, 1, rfile))
  { perror ("read_fbm (header)"); return (0); }

  if (phdr.Manufacturer != PCX_MAGIC)
  { fprintf (stderr,
	     "Error, file is not a PCX file, magic %02x is not 0a\n",
	      phdr.Manufacturer);
    return (0);
  }
  
  /* PCX uses Little Indian byte order, swap on SUNS, RTs, ... */
  if (machine_byte_order () == BIG)
  { phdr.Window[0] =     swapword (phdr.Window[0]);
    phdr.Window[1] =     swapword (phdr.Window[1]);
    phdr.Window[2] =     swapword (phdr.Window[2]);
    phdr.Window[3] =     swapword (phdr.Window[3]);
    phdr.Hres =          swapword (phdr.Hres);
    phdr.Vres =          swapword (phdr.Vres);
    phdr.BytesPerLine =  swapword (phdr.BytesPerLine);
    phdr.Palette =  swapword (phdr.Palette);
  }
  
# ifdef DEBUG
  fprintf (stderr, "Manufacturer        %d\n", phdr.Manufacturer);
  fprintf (stderr, "Version             %d\n", phdr.Version);
  fprintf (stderr, "Encoding            %d\n", phdr.Encoding);
  fprintf (stderr, "BitsPerPixel        %d\n", phdr.BitsPerPixel);
  fprintf (stderr, "Window0             %d\n", phdr.Window[0]);
  fprintf (stderr, "Window1             %d\n", phdr.Window[1]);
  fprintf (stderr, "Window2             %d\n", phdr.Window[2]);
  fprintf (stderr, "Window3             %d\n", phdr.Window[3]);
  fprintf (stderr, "Hres                %d\n", phdr.Hres);
  fprintf (stderr, "Vres                %d\n", phdr.Vres);
  fprintf (stderr, "Reserved            %d\n", phdr.Reserved);
  fprintf (stderr, "NPlanes             %d\n", phdr.NPlanes);
  fprintf (stderr, "BytesPerLine        %d\n", phdr.BytesPerLine);
  fprintf (stderr, "Palette             %d\n", phdr.Palette);
# endif

  /* Now extract relevant features of PCX file header */
  width =  phdr.Window[XMAX] - phdr.Window[XMIN] + 1;
  height = phdr.Window[YMAX] - phdr.Window[YMIN] + 1;
  depth =  phdr.NPlanes;
  ptype =  phdr.Version;
  color =  ((ptype == 2) || (ptype == 5)) &&
	   phdr.NPlanes > 1 &&
	   phdr.Palette != 2;
  enc =    phdr.Encoding;

  if (phdr.BitsPerPixel != 1)
  { fprintf (stderr, "%s %d bits per pixel with %d planes\n", 
	     "Error in PCX file, can't handle", 
	     phdr.BitsPerPixel, depth);
    return (0);
  }

  /* Initialize image header */
  image->hdr.cols = width;
  image->hdr.rows = height;
  image->hdr.planes = 1;
  image->hdr.bits = (color || depth > 1) ? 8 : 1;
  image->hdr.physbits = 8;
  image->hdr.rowlen = rowlen = 16 * ((width + 15) / 16);
  image->hdr.plnlen = rowlen * height;
  image->hdr.clrlen = clrlen = color ? (16 * 3) : 0;
  image->hdr.aspect = 1.0;
  image->hdr.title[0] = '\0';
  image->hdr.credits[0] = '\0';

  /* Describe what we are doing */
  fprintf (stderr, "Reading PCX file [%dx%d]", width, height);
  if (phdr.BitsPerPixel > 1)
    fprintf (stderr, ", %d bits per pixel", phdr.BitsPerPixel);
  if (depth > 1)
    fprintf (stderr, ", %d planes", depth);
  if (clrlen > 0)
    fprintf (stderr, ", %d colors", clrlen/3);
  fprintf (stderr, "\n");

  /* Allocate space */
  alloc_fbm (image);

  /* Read colormap if need be */
  if (clrlen > 0)
  { fprintf (stderr, "reading %d (really 16) colors\n", clrlen / 3);

    for (c=0; c<16; c++)
    { image->cm[c]    = phdr.Colormap[c][0];
      image->cm[c+16] = phdr.Colormap[c][1];
      image->cm[c+32] = phdr.Colormap[c][2];
    }
  }

  /* Zero out the bits */
  bmp = image->bm;
  tail = bmp + image->hdr.plnlen;

  while (bmp < tail) { *bmp++ = 0; }
  
  /* Bytes per scan line */
  totalbytes = depth * phdr.BytesPerLine;
  buf = (unsigned char *) malloc ((unsigned) totalbytes);

  /* Now read bits */
  for (r=0; r<height; r++)
  { bmp = &(image->bm[r * rowlen]);
  
    /* Read a scan line */
    if (pcxline_read (enc, buf, totalbytes, rfile) == 0)
    { fprintf (stderr, "Premature EOF in row %d, totalbytes %d\n",
	       r, totalbytes);
      free ((char *) buf);
      return (1);
    }
    
# ifdef MONDO_DEBUG
    if (r == 211)
    { register int col = 0;

      fprintf (stderr, "Row %d, %d bytes:", r, totalbytes);
      for (c=0; c<totalbytes; c++)
      { if (col%32 == 0 || c%(width/8) == 0) { fprintf (stderr, "\n%3d:", c); col = 0; }
        if (col++ %8  == 0) fprintf (stderr, " ");
        fprintf (stderr, "%02x", buf[c]);
      }
      fprintf (stderr, "\n");
      
      col = 0;
      for (c=0; c<width; c++)
      { byte = c/8;
        mask = 0x80 >> (c%8);
	bit = ((buf[byte            ] & mask) ? 1 : 0) | 
	      ((buf[byte+(width/8)  ] & mask) ? 2 : 0) | 
	      ((buf[byte+2*(width/8)] & mask) ? 4 : 0) | 
	      ((buf[byte+3*(width/8)] & mask) ? 8 : 0);
        if (col%50 == 0) { fprintf (stderr, "\n%3d:", c); col=0; }
        if (col++%10 == 0) { fprintf (stderr, " ", c); }
	fprintf (stderr, "%1x", bit);
      }
      fprintf (stderr, "\n");
    }
# endif

    /* Decode scan line into row of image */
    if (depth == 1)
    { bmp = &(image->bm[r * rowlen]);

      for (c=0; c<width; c++)
      { byte = c>>3;
	mask = 0x80 >> (c&7);
	*bmp++ = (buf[byte] & mask) ? WHITE : BLACK;
      }
    }
    else
    { for (k=0; k<depth; k++)
      { bmp = &(image->bm[r * rowlen]);
	bit = 1 << k;

	  
        for (c=0; c<width; c++)
	{ byte = ((c + k*width)>>3);
          mask = 0x80 >> (c&7);

	  *bmp++ |= (buf[byte] & mask) ? bit : 0;
        }
      }
    }
  }

  if (depth > 1)
  { fprintf (stderr, "Read %d planes successfully\n", depth); }

  free ((char *) buf);

  return (1);
}

/****************************************************************
 * encget (pbyt, pcnt, fid)	Page 10 of ZSoft Manual
 ****************************************************************/

encget (pbyt, pcnt, fid)
int *pbyt;	/* Where to place data */
int *pcnt;	/* Where to place count */
FILE *fid;	/* Image file stream */
{ register int i;

  *pcnt = 1;		/* Safety play */
  if (EOF == (i = getc (fid))) return (EOF);
  if (CNTMSK == (CNTMSK & i))
  { *pcnt = MAXCNT & i;
    if (EOF == (i = getc (fid))) return (EOF);
  }
  *pbyt = i;
  return (0);
}

/****************************************************************
 * pcxline_read
 ****************************************************************/
pcxline_read (enc, buf, total, fid)
unsigned char *buf;	/* Output buffer */
int total;		/* Bytes in one scan line */
FILE *fid;		/* Input stream */
{ int data, count, len=0;

  if (enc != 1)
  { return (fread ((char *) buf, 1, total, fid)); }

  while (len < total)
  { if (EOF == encget (&data, &count, fid))
      return (len);
    while (count > 0) { *buf++ = data; len++; count--; }
  }
  
  if (count > 0)
  { fprintf (stderr, "%s, after %d bytes, lost %d bytes of %02x\n",
	     "Error in reading scan lines", total, count, data);
  }

  return (len);
}
