/*
** ppmtopict.c - read a portable pixmap and produce a Macintosh PICT2 file.
**
** Copyright (C) 1990 by Ken Yap <ken@cs.rochester.edu>.
**
** 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.
*/

#include "ppm.h"
#include "ppmcmap.h"

#define HEADER_SIZE		512

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

/* Opcodes */
#define PICT_NOP		0x00
#define PICT_clipRgn		0x01
#define PICT_bkPat		0x02
#define PICT_txFont		0x03
#define PICT_txFace		0x04
#define PICT_txMode		0x05
#define PICT_spExtra		0x06
#define PICT_pnSize		0x07
#define PICT_pnMode		0x08
#define PICT_pnPat		0x09
#define PICT_thePat		0x0A
#define PICT_ovSize		0x0B
#define PICT_origin		0x0C
#define PICT_txSize		0x0D
#define PICT_fgColor		0x0E
#define PICT_bkColor		0x0F
#define PICT_txRatio		0x10
#define PICT_picVersion		0x11
#define	PICT_blPixPat		0x12
#define	PICT_pnPixPat		0x13
#define	PICT_fillPixPat		0x14
#define	PICT_pnLocHFrac		0x15
#define	PICT_chExtra		0x16
#define	PICT_rgbFgCol		0x1A
#define	PICT_rgbBkCol		0x1B
#define	PICT_hiliteMode		0x1C
#define	PICT_hiliteColor	0x1D
#define	PICT_defHilite		0x1E
#define	PICT_opColor		0x1F
#define PICT_line		0x20
#define PICT_line_from		0x21
#define PICT_short_line		0x22
#define PICT_short_line_from	0x23
#define PICT_long_text		0x28
#define PICT_DH_text		0x29
#define PICT_DV_text		0x2A
#define PICT_DHDV_text		0x2B
#define PICT_frameRect		0x30
#define PICT_paintRect		0x31
#define PICT_eraseRect		0x32
#define PICT_invertRect		0x33
#define PICT_fillRect		0x34
#define PICT_frameSameRect	0x38
#define PICT_paintSameRect	0x39
#define PICT_eraseSameRect	0x3A
#define PICT_invertSameRect	0x3B
#define PICT_fillSameRect	0x3C
#define PICT_frameRRect		0x40
#define PICT_paintRRect		0x41
#define PICT_eraseRRect		0x42
#define PICT_invertRRect	0x43
#define PICT_fillRRect		0x44
#define PICT_frameSameRRect	0x48
#define PICT_paintSameRRect	0x49
#define PICT_eraseSameRRect	0x4A
#define PICT_invertSameRRect	0x4B
#define PICT_fillSameRRect	0x4C
#define PICT_frameOval		0x50
#define PICT_paintOval		0x51
#define PICT_eraseOval		0x52
#define PICT_invertOval		0x53
#define PICT_fillOval		0x54
#define PICT_frameSameOval	0x58
#define PICT_paintSameOval	0x59
#define PICT_eraseSameOval	0x5A
#define PICT_invertSameOval	0x5B
#define PICT_fillSameOval	0x5C
#define PICT_frameArc		0x60
#define PICT_paintArc		0x61
#define PICT_eraseArc		0x62
#define PICT_invertArc		0x63
#define PICT_fillArc		0x64
#define PICT_frameSameArc	0x68
#define PICT_paintSameArc	0x69
#define PICT_eraseSameArc	0x6A
#define PICT_invertSameArc	0x6B
#define PICT_fillSameArc	0x6C
#define PICT_framePoly		0x70
#define PICT_paintPoly		0x71
#define PICT_erasePoly		0x72
#define PICT_invertPoly		0x73
#define PICT_fillPoly		0x74
#define PICT_frameSamePoly	0x78
#define PICT_paintSamePoly	0x79
#define PICT_eraseSamePoly	0x7A
#define PICT_invertSamePoly	0x7B
#define PICT_fillSamePoly	0x7C
#define PICT_frameRgn		0x80
#define PICT_paintRgn		0x81
#define PICT_eraseRgn		0x82
#define PICT_invertRgn		0x83
#define PICT_fillRgn		0x84
#define PICT_frameSameRgn	0x88
#define PICT_paintSameRgn	0x89
#define PICT_eraseSameRgn	0x8A
#define PICT_invertSameRgn	0x8B
#define PICT_fillSameRgn	0x8C
#define PICT_BitsRect		0x90
#define PICT_BitsRgn		0x91
#define PICT_PackBitsRect	0x98
#define PICT_PackBitsRgn	0x99
#define PICT_shortComment	0xA0
#define PICT_longComment	0xA1
#define PICT_EndOfPicture	0xFF
#define	PICT_headerOp		0x0C00

static void putFill ARGS(( FILE *fd, int n ));
static void putShort ARGS(( FILE *fd, int i ));
static void putLong ARGS(( FILE *fd, long i ));
static void putFixed ARGS(( FILE *fd, int in, int frac ));
static void putRect ARGS(( FILE *fd, int x1, int x2, int y1, int y2 ));
static int putRow ARGS(( FILE *fd, int row, int cols, pixel *rowpixels, char *packed ));

#define MAXCOLORS 256
static colorhash_table cht;

int
main(argc, argv)
int argc;
char *argv[];
{
	FILE *ifp;
	int argn, rows, cols, colors, i, row, oc;
	register pixel **pixels;
	char *packed;
	pixval maxval;
	long lmaxval, rval, gval, bval;
	colorhist_vector chv;


	ppm_init( &argc, argv );

	argn = 1;
	if (argn < argc)
	{
		ifp = pm_openr(argv[1]);
		argn++;
	}
	else
		ifp = stdin;
	if (argn != argc)
		pm_usage("[ppmfile]");

	pixels = ppm_readppm(ifp, &cols, &rows, &maxval);
	if (cols < 8)
		pm_error("ppm input too narrow, must be >= 8 pixels wide" );
	lmaxval = (long)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 'pnmquant %d'", MAXCOLORS);
	pm_message("%d colors found", colors );

	/* Make a hash table for fast color lookup. */
	cht = ppm_colorhisttocolorhash(chv, colors);

	/* write the header */
	putFill(stdout, HEADER_SIZE);

	/* write picSize and picFrame */
	putShort(stdout, 0);
	putRect(stdout, 0, 0, rows, cols);

	/* write version op and version */
	putShort(stdout, PICT_picVersion);
	putShort(stdout, 0x02FF);
	putShort(stdout, PICT_headerOp);
	putLong(stdout, -1L);
	putFixed(stdout, 0, 0);
	putFixed(stdout, 0, 0);
	putFixed(stdout, cols, 0);
	putFixed(stdout, rows, 0);
	putFill(stdout, 4);

	/* seems to be needed by many PICT2 programs */
	putShort(stdout, PICT_clipRgn);
	putShort(stdout, 10);
	putRect(stdout, 0, 0, rows, cols);

	/* write picture */
	putShort(stdout, PICT_PackBitsRect);
	putShort(stdout, cols | 0x8000);
	putRect(stdout, 0, 0, rows, cols);
	putShort(stdout, 0);	/* pmVersion */
	putShort(stdout, 0);	/* packType */
	putLong(stdout, 0L);	/* packSize */
	putFixed(stdout, 72, 0);	/* hRes */
	putFixed(stdout, 72, 0);	/* vRes */
	putShort(stdout, 0);	/* pixelType */
	putShort(stdout, 8);	/* pixelSize */
	putShort(stdout, 1);	/* cmpCount */
	putShort(stdout, 8);	/* cmpSize */
	putLong(stdout, 0L);	/* planeBytes */
	putLong(stdout, 0L);	/* pmTable */
	putLong(stdout, 0L);	/* pmReserved */
	putLong(stdout, 0L);	/* ctSeed */
	putShort(stdout, 0);	/* ctFlags */
	putShort(stdout, colors-1);	/* ctSize */

	/* Write out the colormap. */
	for (i = 0; i < colors; i++)
	{
		putShort(stdout, i);
		rval = PPM_GETR(chv[i].color);
		gval = PPM_GETG(chv[i].color);
		bval = PPM_GETB(chv[i].color);
		if (lmaxval != 65535L)
		{
			rval = rval * 65535L / lmaxval;
			gval = gval * 65535L / lmaxval;
			bval = bval * 65535L / lmaxval;
		}
		putShort(stdout, (short)rval);
		putShort(stdout, (short)gval);
		putShort(stdout, (short)bval);
	}

	putRect(stdout, 0, 0, rows, cols);	/* srcRect */
	putRect(stdout, 0, 0, rows, cols);	/* dstRect */
	putShort(stdout, 0);			/* mode */

	/* Finally, write out the data. */
	packed = (char*) malloc((unsigned)(cols+cols/MAX_COUNT+1));
	oc = 0;
	for (row = 0; row < rows; row++)
		oc += putRow(stdout, row, cols, pixels[row], packed);

	/* if we wrote an odd number of pixdata bytes, pad */
	if (oc & 1)
		(void) putc(0, stdout);
	putShort(stdout, PICT_EndOfPicture);

	lmaxval = ftell(stdout) - HEADER_SIZE;
	if (fseek(stdout, (long)HEADER_SIZE, 0) >= 0)
		putShort(stdout, (short)(lmaxval & 0xffff));

	exit(0);
}

static void
putFill(fd, n)
FILE *fd;
int n;
{
	register int i;

	for (i = 0; i < n; i++)
		(void) putc(0, fd);
}

static void
putShort(fd, i)
FILE *fd;
int i;
{
	(void) putc((i >> 8) & 0xff, fd);
	(void) putc(i & 0xff, fd);
}

#if __STDC__
static void
putLong( FILE *fd, long i )
#else /*__STDC__*/
static void
putLong(fd, i)
FILE *fd;
long i;
#endif /*__STDC__*/
{
	(void) putc((int)((i >> 24) & 0xff), fd);
	(void) putc(((int)(i >> 16) & 0xff), fd);
	(void) putc(((int)(i >> 8) & 0xff), fd);
	(void) putc((int)(i & 0xff), fd);
}

static void
putFixed(fd, in, frac)
FILE *fd;
int in, frac;
{
	putShort(fd, in);
	putShort(fd, frac);
}

static void
putRect(fd, x1, x2, y1, y2)
FILE *fd;
int x1, x2, y1, y2;
{
	putShort(fd, x1);
	putShort(fd, x2);
	putShort(fd, y1);
	putShort(fd, y2);
}

#define	RUNLENGTH
#ifdef	RUNLENGTH

#define		runtochar(c)	(257-(c))
#define		counttochar(c)	((c)-1)

static int
putRow(fd, row, cols, rowpixels, packed)
FILE *fd;
int row, cols;
pixel *rowpixels;
char *packed;
{
	register int i;
	int packcols, count, run, rep, oc;
	register pixel *pP;
	pixel lastp;
	register char *p;

	run = count = 0;
	for (cols--, i = cols, pP = rowpixels + cols, p = packed, lastp = *pP;
		i >= 0; i--, lastp = *pP, pP--)
	{
		if (PPM_EQUAL(lastp, *pP))
			run++;
		else if (run < RUN_THRESH)
		{
			while (run > 0)
			{
				*p++ = ppm_lookupcolor(cht, &lastp);
				run--;
				count++;
				if (count == MAX_COUNT)
				{
					*p++ = counttochar(MAX_COUNT);
					count -= MAX_COUNT;
				}
			}
			run = 1;
		}
		else
		{
			if (count > 0)
				*p++ = counttochar(count);
			count = 0;
			while (run > 0)
			{
				rep = run > MAX_RUN ? MAX_RUN : run;
				*p++ = ppm_lookupcolor(cht, &lastp);
				*p++ = runtochar(rep);
				run -= rep;
			}
			run = 1;
		}
	}
	if (run < RUN_THRESH)
	{
		while (run > 0)
		{
			*p++ = ppm_lookupcolor(cht, &lastp);
			run--;
			count++;
			if (count == MAX_COUNT)
			{
				*p++ = counttochar(MAX_COUNT);
				count -= MAX_COUNT;
			}
		}
	}
	else
	{
		if (count > 0)
			*p++ = counttochar(count);
		count = 0;
		while (run > 0)
		{
			rep = run > MAX_RUN ? MAX_RUN : run;
			*p++ = ppm_lookupcolor(cht, &lastp);
			*p++ = runtochar(rep);
				run -= rep;
		}
		run = 1;
	}
	if (count > 0)
		*p++ = counttochar(count);

	packcols = p - packed;		/* how many did we write? */
	if (cols > 250)
	{
		putShort(fd, packcols);
		oc = packcols + 2;
	}
	else
	{
		(void) putc(packcols, fd);
		oc = packcols + 1;
	}

	/* now write out the packed row */
	while(p != packed)
	{
		--p;
		(void) putc(*p, fd);
	}

	return (oc);
}

#else	/* RUNLENGTH */

/* real dumb putRow with no compression */
static int
putRow(fd, row, cols, rowpixels, packed)
FILE *fd;
int row, cols;
pixel *rowpixels;
char *packed;
{
	register int i, j, bc, oc;
	register pixel *pP;

#if notdef
	bzero(aux, cols); /* aux?? */
#endif /*notdef*/
	bc = cols + (cols + MAX_COUNT - 1) / MAX_COUNT;
	if (bc > 250)
	{
		putShort(fd, bc);
		oc = bc + 2;
	}
	else
	{
		(void) putc(bc, fd);
		oc = bc + 1;
	}
	for (i = 0, pP = rowpixels; i < cols;)
	{
		if (cols - i > MAX_COUNT)
		{
			(void) putc(MAX_COUNT - 1, fd);
			for (j = 0; j < MAX_COUNT; j++)
			{
				(void) putc(ppm_lookupcolor(cht, pP), fd);
				pP++;
			}
			i += MAX_COUNT;
		}
		else
		{
			(void) putc(cols - i - 1, fd);
			for (j = 0; j < cols - i; j++)
			{
				(void) putc(ppm_lookupcolor(cht, pP), fd);
				pP++;
			}
			i = cols;
		}
	}
	return (oc);
}
#endif	/* RUNLENGTH */
