/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION 1986
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header:scale.c 12.0$ */
/* $ACIS:scale.c 12.0$ */
/* $Source: /ibm/acis/usr/src/ibm/bitprt_ca/RCS/scale.c,v $ */

#ifndef lint
static char *rcsid = "$Header:scale.c 12.0$";
#endif

/*
 * SCALE -- filter to change size of a bitmap image.
 *	up -- scale up heuristic handles multiples
 *		then replicates every nth pixel/line.
 *	down -- deletes every nth pixel/line.
 *
 *  scale [-xnnn] [-ynnn] [-b] [-w]
 *	where 	-xnnn indicates to scale the image to nnn pixels on the x axis
 *	    	-ynnn indicates to scale the image to nnn pixels on the y axis
 *		-b indicates to preserve black data when shrinking
 *		-w indicates to preserve white data when shrinking
 *
 */
#include <stdio.h>
#include <math.h>
extern int errno, perror();
extern char *malloc();

#define PIXINWORD 16 
#define CHAR 8
#define EQUAL	0
#define EXPANDED 1
#define SHRUNKEN -1
#define VOID
#define SIZE unsigned short


static SIZE pixin,pixout;
SIZE frameword;
SIZE *inframebuf,*outframebuf;
static 	char *xmask,*ymask,*workbuffer;
static char *inbuffer,*outbuffer;
static SIZE *pibuf,*pobuf;
static int pxmask,pymask,pwbuf;
static int xmultiple,ymultiple,xscale,yscale;
static FILE *loaddev, *stordev;
static inpix = PIXINWORD;
static int favorwhite = 0;
static int favorblack = 0;
static int outpix = 0;

typedef struct {
	int width;
	int height;
} 
dumpsize;

initpix()
{
	inpix = PIXINWORD;
	outpix = 0;
}

char unpack()
{
	if (inpix == PIXINWORD)
	{
		pixin = (SIZE) *pibuf++;
		inpix = 0;
	}
	++inpix;
	return( (pixin & (1 << (PIXINWORD-inpix)) ? 1 : 0));
}

getbuffer(whichline,pixels)
int whichline,pixels;
{
	pibuf = (SIZE *) inbuffer;
	/* assume the input file was dumped as padded modulo 8 */
	if (fread(inbuffer,pixels/CHAR,1,loaddev) == 0)
	{
		perror("could not read from file");
		exit(2);
	}
	pibuf = (SIZE *) inbuffer; /* reset for unpack's use */
}

fillbuffer(pixels)
int pixels; /* dix if SHRUNKEN else dox */
{
	register int i;
	if (xscale == EQUAL)
		return(0);
	pobuf = (SIZE *) outbuffer;
	outpix = 0;


	if (xscale == EXPANDED)
		for(i = 0; i < pixels; i++)
			pack(workbuffer[i]);
	else /* xscale == SHRUNKEN */
	{ /* mask and workbuffer are same size */
		pxmask = pwbuf = 0;
		for(i = 0; i < pixels; i++)
		{
			if (xmask[i]) /* skip this pixel */
			{
				/* for favoring, look to the neighbors */
				if ((favorblack && !workbuffer[i])  ||
				    (favorwhite && workbuffer[i]))
					favorbit(i,pixels);
			}
			else
			{
				pack(workbuffer[i]);
			}
		}
		/* favor neighbors */ ;
	}
	if (outpix)
	{
		flushpix();
	}
}

favorbit(bit,pixels)
int bit,pixels;
{
	/*
	 * Favor a bit only if its left and right (shrunken) neighbors
	 * differ from it.
	 */
	int left,right;
	/* make sure there are neighbors */
	if (((left = leftneighbor(bit)) >= 0) && 
	    ((right = rightneighbor(bit,pixels)) >= 0))
	{
		if ((workbuffer[left] == workbuffer[right]) &&
		    (workbuffer[bit] != workbuffer[right])) 
			workbuffer[right] = workbuffer[bit]; 
	}
}

leftneighbor(bit)
int bit;
{
	do
	    {
		if (!xmask[bit])
			return(bit);
	}
	while (--bit >= 0);
	return(-1);
}

rightneighbor(bit,pixels)
int bit,pixels;
{
	do
	    {
		if (!xmask[bit])
			return(bit);
	}
	while (++bit < pixels);
	return(-1);
}

putbuffer(pixels)
int pixels;
{
	int outsize = pixels/CHAR;

	if (xscale == EQUAL)
		pobuf = (SIZE *) inbuffer;
	else
		pobuf = (SIZE *) outbuffer;
	/* dump the output file padded modulo 8 */
	if (pixels % CHAR) outsize++;
	if (fwrite((char *) pobuf,outsize,1,stordev) == 0)
	{
		perror("could not write to file");
		exit(2);
	}
}

pack(pix)
char pix;
{
	++outpix;
	if (pix)
		pixout |= (1 << (PIXINWORD-outpix)); 
	if (outpix == PIXINWORD)
		flushpix();
}

flushpix()
{
	*pobuf++ = (SIZE) pixout;
	pixout = 0;
	outpix = 0;
}

initxscale(from,to)
int from,to;
{
	int delta,total,i,j,mfrom;
	int space0,space1,space2;
	;
	delta=total=0;
	space0 = (from/CHAR)+2;
	if ((inbuffer = malloc((unsigned)space0)) == 0)
	{
		fprintf(stderr,
		"unable to allocate %d bytes for inbuffer\n",space0);
		exit(2);
	}
	if (from == to)
	{
		xscale = EQUAL;
		return(xscale);
	}
	if (to < from)
	{
		delta = from - to;
		xmultiple = 1;
		xscale = SHRUNKEN;
	}
	else
	{
		/* otherwise -- scale up */	
		xmultiple = to / from;
		xscale = EXPANDED;
	}
	/* allocate workbuffer space for unpacking  -- 1 char/bit */
	/* (to used for expand and contract) */
	if (xscale == SHRUNKEN)
		space1 = from;
	else
		space1 = to;
	if ((workbuffer = malloc((unsigned)space1)) == 0)
	{
		fprintf(stderr,
		"unable to allocate %d bytes for workbuffer\n",space1);
		exit(2);
	}
	space2 = (to/CHAR)+2;
	if ((outbuffer = malloc((unsigned)space2)) == 0)
	{
		fprintf(stderr,
		"unable to allocate %d bytes for outbuffer\n",space2);
		exit(2);
	}
	mfrom = from * xmultiple;
	/* prepare xmask  - 1 char/bit */
	if ((xmask = malloc((unsigned)mfrom)) == 0)
	{
		fprintf(stderr,
		"unable to allocate %d bytes for xmask\n",mfrom);
		exit(2);
	}
	for (i=0;i<mfrom;i++)
		xmask[i] = 0;	/* clear xmask */
	mfrom = from * xmultiple;

	if (xscale == SHRUNKEN)
	{
		if (delta) do
		    {
			int each;
			each = from / delta;
			if (from % delta)
				each++;
			for (i=each-1;i<from;i += each)
			{ /* if cell is marked, find a free neighbor */
				if (!xmask[i]) {
					xmask[i] = 1;
				}
				else {
					j = i;
					while (--j) {
						if (!xmask[j]) {
							xmask[j] = 1;
							break;
						}
					}
					if (j <= 0) {
						fprintf(stderr,
						"error building xmask\n"
						    );
						exit(2);
					}
				}
			}
			delta -= from / each;
		}
		while (delta > 0);
		return(xscale);
	}
	/* otherwise -- scale up */	
	total += mfrom;
	delta = to - total;
	if (delta) do {
		int each;
		each = mfrom / delta;
		if (mfrom % delta)
			each++;
		for (i=each-1;i<mfrom;i += each)
		{ /* if a cell is marked, find a free neighbor */
			if (!xmask[i]) {
				xmask[i] = 1;
			}
			else {
				j = i;
				while (--j) {
					if (!xmask[j]) {
						xmask[j] = 1;
						break;
					}
				}
				if (j <= 0) {
					fprintf(stderr,
					"error building xmask\n"
					    );
					exit(2);
				}
			}
		}
		total += mfrom / each;
		delta = to - total;
	}
	while (delta > 0);
	return(xscale);
}

inityscale(from,to)
int from,to;
{
	int delta,total,i,j,mfrom;
	delta=total=0;
	if (from == to)
	{
		yscale = EQUAL;
		return(yscale); 
	}
	if (to < from)
	{
		yscale = SHRUNKEN;
		delta = from - to;
		ymultiple = 1;
	}
	else
	{
		yscale = EXPANDED;
		ymultiple = to / from;
	}
	mfrom = from * ymultiple;
	/* prepare ymask  - 1 char/bit */
	if ((ymask = malloc((unsigned)mfrom)) == 0)
	{
		fprintf(stderr,
		"unable to allocate %d bytes for ymask\n",mfrom);
		exit(2);
	}
	for (i=0;i<mfrom;i++)
		ymask[i] = 0;	/* clear ymask */
	if (yscale == SHRUNKEN)
	{
		if (delta) do
		    {
			int each;
			each = from / delta;
			if (from % delta)
				each++;
			for (i=each-1;i<from;i += each)
			{ /* if cell is marked, find a free neighbor */
				if (!ymask[i]) {
					ymask[i] = 1;
				}
				else {
					j = i;
					while (--j) {
						if (!ymask[j]) {
							ymask[j] = 1;
							break;
						}
					}
					if (j <= 0) {
						fprintf(stderr,
						"error building ymask\n"
						    );
						exit(2);
					}
				}
			}
			delta -= from / each;
		}
		while (delta > 0);
		return(yscale);
	}
	/* otherwise -- scale up */	
	total += mfrom;
	delta = to - total;
	if (delta) do
	    {
		int each;
		each = mfrom / delta;
		if (mfrom % delta)
			each++;
		for (i=each-1;i<mfrom;i += each)
		{ 
			/* if a cell is marked, 
						 * find a free neighbor
						 */
			if (!ymask[i])
			{
				ymask[i] = 1;
			}
			else
			{
				j = i;
				while (--j)
				{
					if (!ymask[j])
					{
						ymask[j] = 1;
						break;
					}
				}
				if (j <= 0)
				{
					fprintf(stderr,
					"error building ymask\n"
					    );
					exit(2);
				}
			}
		}
		total += mfrom / each;
		delta = to - total;
	}
	while (delta > 0);
	return(yscale);
}

scalex(from,to)
int from,to;
{
	register int i,j;
	char c;

	if (xscale == EQUAL)
		return(0); /* will use a direct copy from inbuffer */
	else if (xscale == EXPANDED)
	{
		/* unpack and expand input stream */
		pxmask = pwbuf = 0;
		for(i = 0; i < from; i++)
		{
			if ((c = unpack()) < 0)
			{
				fprintf(stderr,"premature EOF\n");
				exit(2);
			}
			for (j=0; j< xmultiple; j++)
			{
				workbuffer[pwbuf] = c;
				if (xmask[pxmask])
				{
					workbuffer[++pwbuf] = c;
				}
				pxmask++;
				pwbuf++;
			}
		}
	}
	else if (xscale == SHRUNKEN)
	{
		/* unpack input stream -- shrink at pack stage  */
		/* mask and input stream are same size */
		for(i = 0; i < from; i++)
		{
			if ((c = unpack()) < 0)
			{
				fprintf(stderr,"premature EOF\n");
				exit(2);
			}
			workbuffer[i] = c;
		}
	}

}

scaley(dix,diy,dox,doy)
int dix,diy,dox,doy;
{
	register int srclineoff,loop;
	for (srclineoff=0;srclineoff<diy; srclineoff++)
	{
		getbuffer(srclineoff,dix);
		initpix();
		/* call scale to load up workbuffers and do work */
		if (yscale == SHRUNKEN)
		{
			if (ymask[pymask]) /* then skip this scanline */
				VOID;
			else
			{
				scalex(dix,dox);
				initpix();
				if (xscale == SHRUNKEN)
					fillbuffer(dix);
				else
					fillbuffer(dox);
				putbuffer(dox);
			}
			pymask++;
		}
		else if (yscale == EXPANDED)
		{
			scalex(dix,dox);
			for (loop=0; loop< ymultiple; loop++)
			{
				initpix();
				if (xscale == SHRUNKEN)
					fillbuffer(dix);
				else
					fillbuffer(dox);
				putbuffer(dox);
				if (ymask[pymask])
				{
					putbuffer(dox);
				}
				pymask++;
			}
		}
		else /* yscale == EQUAL */
		{
			scalex(dix,dox); 
			initpix();
			if (xscale == SHRUNKEN)
				fillbuffer(dix);
			else
				fillbuffer(dox);
			putbuffer(dox);
		}
	} /* end for scanlines loop */
}

main(argc,argv)
int argc; 
char *argv[];
{
	int dix,diy,dox,doy; 
	int i;
	char c;
	dumpsize inheader,outheader;

	if (argc > 1)
		for(i=1; i<argc;i++)
		{
			if ((c = *(argv[i]++)) == '-')
				switch(c = *(argv[i]++))
				{
				case 'x': 
					sscanf(argv[i],"%d",&dox); 
					break;
				case 'y': 
					sscanf(argv[i],"%d",&doy); 
					break;
				case 'b': 
					favorblack = 1;
					break;
				case 'w': 
					favorwhite = 1;
					break;
				default: 
					fprintf(stderr,
					"unrecognized option %c\n",c);
				}	
			else 
			    fprintf(stderr,"unrecognized parameter \n");
		}
	loaddev = stdin;
	stordev = stdout;
	fread(&inheader,sizeof(inheader),1,loaddev);
	dix = inheader.width;
	diy = inheader.height;
	if (!dox)
		dox = dix;
	if (!doy)
		doy = diy;
	outheader.width = dox;
	outheader.height = doy;
	fwrite(&outheader,sizeof(outheader),1,stordev);

	dox = CHAR * (dox / CHAR); /* must be multiple of CHAR */
	initxscale(dix,dox);
	inityscale(diy,doy);
	pymask = 0;
	scaley(dix,diy,dox,doy); /* this function is the heart */
	exit(0);
}

