[net.sources.mac] Smoother mac->qms converter

kc@rna.UUCP (Kaare Christian) (01/27/85)

Macpainters,

Here is an enhancement of the macqms program that was originally posted
by Van Jacobson. The antecedent authors are Winkler and Patterman.
Macqms transforms a macpaint document into a format that can be printed
on a QMS laser printer. This enhancement is a filter that smoothes the images
before printing.  This increases the apparent contrast, reduces (but
does not eliminate) the jaggies, and makes most lettering look less
sparkly.  It also slows things down considerably and it expands the
data by a factor of ten or so. Some documents are improved markedly,
others suffer little improvement.  The major deficiency is
sluggishness.  Be prepared to wait, possibly a long time. This program
is a cpu-hog.  You may want to restrict its use, or have it auto-nice
itself nearly out of existence.

This filter has been designed semi-modularly so that it can be attached
to the other mac->some_printer programs.  Since our only hi-res, bitwise
printer is a QMS I've left this chore to others.

I am distributing this filter along with the slightly modified macqms
program, and the updated manual page.

Kaare Christian, uucp:cmcl2!rna!kc bell:212-570-7672

(Cut below and process with sh)
--------------------------------------------------------------------
echo filter.c
sed 's/^X//' > filter.c << 'All work and no play makes Jack a dull boy'
X/*
X * A simple convolver that converts macpaint images into 4x resolution
X * with smoothing.  This reduces the jagginess of a macpaint image, so it
X * looks nicer on the qms laser printer.  This filter assumes a 4x
X * resolution increase, but it may be adaptable to other increments.
X * This code is separate from the macqms source so that it can be
X * adapted, with minimal change, to the other macintosh->whatever filters.
X * Adaptation of this filter to the imagen macpaint routine should be
X * trivial.
X * 
X * There are two connections between this filter and the macqms code:
X * 	1. The putdata routine. It accepts data in mac format, puts it
X * in local buffers, de-length codes as necessary, and calls do_filter at
X * the end of each line.
X * 	2. The sendline routine uses the stdout, which must be connected
X * properly by macqms routines. The macqms routines must send the proper
X * qms cmds before and after the data generated by this filter.
X * 
X * There is one dependency upon the qms device:
X * 	1. The sendline routine (and the two routines it calls, squeeze
X * and booltobin) are qms specific. These will need to be replaced for other
X * output devices.
X * 
X * This filter is attached to the macqms filter by intercepting output
X * calls at the lowest level.  Macqms was modified slightly to funnel all
X * output through one low level routine, which either does the output or
X * calls putdata to output the data via this filter.
X * 
X * I have done some performance tuning. More than 3x of speed improvement
X * has been realized since the first working version.  More tuning is
X * possible. Hint, hint.
X * 
X * My thanks go to Van Jacobson for the macqms.c program.
X * 
X * This filter may be distributed, only without charge, to anyone. This
X * notice must be attached, and all modifications must be duly noted
X * by their author in the revision history.  This filter may be used for
X * any purpose, except that it may not be sold, or incorporated into
X * any software that is sold.
X * 
X * Kaare Christian, The Rockefeller Univ.  1230 York Ave. NY NY 10021
X * uucp:cmcl2!rna!kc  212-570-7672
X * 
X * Revision History:
X * 	Created Jan 22, 1985 kc
X * 
X */
X
X#include <stdio.h>
X#define MAC_SCANLINE_BITS 576
X#define MB MAC_SCANLINE_BITS
X#define QB MB*4
X
Xstatic char macbuf[MB];
Xstatic char qbuf[5][QB+1]; /* the +1 simplifies makeline */
Xstatic char outbuf[MB]; /* MB just happens to be the right size */
Xstatic int i_macbuf = 0;
Xextern int smoothval;
X
X/*
X * the input side connection to the macintosh data
X */
Xputdata(flag,byte)
Xint flag,byte;
X{
Xint i;
X
Xif (flag == -1)
X	do_filter();
Xelse if (flag == 0)
X	bintobool(byte);
Xelse for(i=0;i<flag;i++)
X	bintobool(byte);
X}
X
X/*
X * make four scan lines from one
X */
Xdo_filter()
X{
Xint line;
X
Xi_macbuf = 0;
X
Xcenters();
Xfor(line=0;line<4;line++) {
X	makeline(line);
X	clipline(line);
X	sendline(line);
X	}
Xrollbuff();
X}
X
Xrollbuff() /* place centers from qbuf[4] into qbuf[0] */
X{
Xregister int i;
Xfor(i=0;i<QB;i+=4)
X	qbuf[0][i] = qbuf[4][i];
X}
X
Xcenters()
X{
X/*
X   place data from macbuf into every fourth position of qbuf[4] 
X   Only the points inbetween the centers are candidates for filtering
X */
Xregister int i, j;
Xfor(i=0,j=0;i<QB;i+=4,j++)
X	qbuf[4][i] = macbuf[j];
X}
X
X#define LWT0 16
X#define LWT1 12
X#define LWT2 8
X#define LWT3 4
Xstatic int xyweights[4][4] = {
X	{ 16, 12, 8, 4 },
X	{ 12, 9, 6, 3 },
X	{ 8, 6, 4, 2 },
X	{ 4, 3, 2, 1}};
X
Xmakeline(N)
X{
Xregister int n = N, loc, x;
Xregister char *ptr, *p0, *p4;
Xp0 = qbuf[0];
Xp4 = qbuf[4];
Xptr = qbuf[n];
Xfor(loc=0;loc<QB;loc++) {
X	x = loc & 03; /* distance past a center */
X	if (n == 0) { /* fill in line 0 tween the centers */
X		if (x==0) 
X			ptr[loc] = ptr[loc] ? LWT0 : 0;
X		else if (x==1)
X			ptr[loc] = (ptr[loc-1] ? LWT1 : 0)
X				+ (ptr[loc+3] ? LWT3 : 0);
X		else if (x==2)
X			ptr[loc] = (ptr[loc-2] ? LWT2 : 0)
X				+ (ptr[loc+2] ? LWT2 : 0);
X		else if (x==3)
X			ptr[loc] = (ptr[loc-3] ? LWT3 : 0)
X				+ (ptr[loc+1] ? LWT1 : 0);
X		}
X	else if (x == 0) { /* fill in lines 1..3 between centers */
X		if (n==1)
X			ptr[loc] = (p0[loc] ? LWT1 : 0)
X				+ (p4[loc] ? LWT3 : 0);
X		else if (n==2)
X			ptr[loc] = (p0[loc] ? LWT2 : 0)
X				+ (p4[loc] ? LWT2 : 0);
X		else if (n==3)
X			ptr[loc] = (p0[loc] ? LWT3 : 0)
X				+ (p4[loc] ? LWT1 : 0);
X		}
X	else { /* the full xy scene */
X		int l;
X		l = loc & (~03);
X		ptr[loc] = (p0[l] ? xyweights[n][x] : 0)
X			+ (p0[l+4] ? xyweights[n][4-x] : 0)
X			+ (p4[l] ? xyweights[4-n][x] : 0)
X			+ (p4[l+4] ? xyweights[4-n][4-x] : 0);
X		}
X	}
X}
X
Xclipline(n)
Xint n;
X{
Xregister char *p, *q;
Xp = qbuf[n];
Xq = p + QB;
Xwhile(p<q)
X	if (*p > smoothval)
X		*p++ = 1;
X	else
X		*p++ = 0;
X}
X
X/*
X * Send an line of data to the qms, compressed heavily
X *  (ideally this would be less qms specific)
X */
Xsendline(n)
Xint n;
X{
Xregister int i;
Xchar *cptr;
X
Xcptr = qbuf[n];
Xfor(i=0;i<MB;i++)
X	outbuf[i] = "0123456789ABCDEF"[booltobin(&cptr)];
Xi=0;
Xwhile(i<MB)
X	i += squeeze(i);
Xputchar('\n');
X}
X
X/*
X * qms compression (note - efficiency counts here)
X */
Xsqueeze(from)
X{
Xint loc = from;
Xint n;
Xchar ch;
Xch = outbuf[loc];
Xif ((ch == '0') || (ch == 'F'))
X	while((loc < MB) && (outbuf[loc] == ch))
X		loc++;
Xn = loc-from;
Xif (n > 3) { /* squeeze it */
X	printf("^%c%03d",(ch == '0') ? 'D' : 'B', n);
X	return(n);
X	}
X/* oh well, just send one */
Xputchar(ch);
Xreturn(1);
X}
X
Xstatic char masks[8] = { 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 };
Xbintobool(byte)
Xint byte;
X{
Xint i;
Xfor(i=0;i<8;i++)
X	macbuf[i_macbuf++] = (byte & masks[i]) ? 1 : 0;
X}
X
Xbooltobin(ptr)
Xchar **ptr;
X{
Xregister int val = 0;
Xregister int i;
X
Xfor(i=4;i<8;i++)
X	if (*(*ptr)++)
X		val |= masks[i];
Xreturn(val);
X}
All work and no play makes Jack a dull boy
echo macqms.c
sed 's/^X//' > macqms.c << 'All work and no play makes Jack a dull boy'
X/*
X     This filter converts MacPaint files into Quic files for printing
X     on an QMS-1200 or -800 laser printer.  It was written by Van
X     Jacobson of Lawrence Berkeley Laboratory (van@lbl-csam.arpa,
X     ...!ucbvax!lbl-csam!van) and is in the public domain.  It was
X     loosely based on macimp.c by Winkler@harvard which was in turn
X     based on MACimp for Tops-20 by Ed Pattermann, Stanford.
X
X     Usage:
X       macqms [-l] [-s] [-f[n]] [-x<xpos>] [-y<ypos>] [-m<mag>]
X		[-n<ipp>[,<ipr>]] [file ...]
X
X     '-l' prints in "landscape" orientation (default is "portrait"
X     orientation).  <xpos> and <ypos> are the position of the upper
X     left corner of the bitmap.  They are given in inches and default
X     to 0.41 and 0.70 (which will center a 576x720 image at mag 4).
X     <mag> is a magnification for the bitmap and can be any integer
X     >0.  The default is 4.  Magnifications larger than 4 may result in
X     bizarre images.  `-n<ipp>' prints <ipp> images-per-page.  The files
X     are printed `m' across by `n/m' down. "m" can be specified (e.g.,
X     "-n4,2") or it can be defaulted.  The default is the largest number
X     of images that will fit the current magnification & orientation.
X
X     Send enhancements and bug reports to van@lbl-csam.
X
X     History:      
X	21 August 1984  VJ - created. 
X	1  Jan 1985 Hacked to pipe output to lpr.
X		-s option writes standard out.
X		(Note site dependent LPR define and the qms_init proc) kc
X	22 jan 1985 Added output filtering. Controlled by the -f option. kc
X*/
X
X#include <stdio.h>
Xdouble atof();
Xextern char **environ;
X
X#define	MAC_HEADER_LENGTH	512
X#define	MAC_SCANLINES		720	/* number of scanlines in image */
X#define	MAC_SCANLINE_BITS	576	/* number of bits in a scanline */
X#define	MAC_SCANLINE_BYTES	(MAC_SCANLINE_BITS/8)
X#define LPR	"/usr/local/bin/lpr"
X
Xint	mag=4;			/* horiz & vert magnification in dots */
Xint	window=MAC_SCANLINE_BITS;	/* horiz width of image in dots */
Xint	initial_xpos = 410;	/* left margin position in inches*1000 */
Xint	initial_ypos = 700;	/* top margin position in inches*1000 */
Xchar	orientation = 'P';	/* "portrait" orientation */
Xint	qms_pagewidth = 8500;	/* in inches*1000 */
Xint	ipp = 1;		/* 1 image per page */
Xint	ipr = 0;		/* default images per row */
Xint	images = 0;		/* number of images on current page */
Xint	xpos;
Xint	ypos;
Xint	xpos_incr;
Xint	ypos_incr;
Xint	stdoutput;		/* set means ship to std, not to lpr */
Xint	filter;			/* set means invoke the smoothing filter */
Xint	smoothval = 6;
Xchar	usage[] =
X"usage: macqms [-l] [-f] [-s[val]] [-m<mag>]\n\t\t[-x<xpos>] [-y<ypos>] [-n<ipp>] [file ..]\n";
X
Xmain(argc,argv)
Xint argc;
Xchar **argv;
X{
X	register int i;
X
X	argv++; argc--;
X	for ( i=0; i < argc; i++ )
X		{
X		if ( argv[i][0] != '-' )
X			break;
X
X		switch ( argv[i][1] )
X		{
X		case 'm':
X			mag = atoi( &argv[i][2] );
X			if ( mag <= 0 )
X				mag = 1;
X			break;
X		case 'l':
X			orientation = 'L';
X			qms_pagewidth = 11000;
X			break;
X		case 'x':
X			xpos = atof( &argv[i][2] )*1000.;
X			break;
X		case 'y':
X			ypos = atof( &argv[i][2] )*1000.;
X			break;
X		case 'n':
X			sscanf( &argv[i][2], "%d,%d", &ipp, &ipr );
X			break;
X		case 's':
X			stdoutput++;
X			break;
X		case 'f':
X			filter++;
X			if (argv[i][2] != 0)
X				smoothval = atoi(&argv[i][2]);
X			if ((smoothval < 3) || (smoothval > 16))
X				smoothval = 6;
X			break;
X		default:
X			fprintf( stderr, usage );
X			exit(1);
X		}
X	}
X	if ( i >= argc ) {
X		/* no file arguments - send contents of stdin */
X		qms_init("macqms");
X		send_one_bitmap();
X		}
X	else {
X		qms_init(argv[i]);
X		for ( ; i < argc; i++ )
X			{
X			if ( freopen( argv[i], "r", stdin ) == NULL )
X				{
X				perror( argv[i] );
X				exit(2);
X				}
X			send_one_bitmap();
X			}
X		}
X
X	/* if we have a partial page of images left over, eject it. */
X	if ( images > 0 )
X		qms_eject();
X
X	qms_end();
X	fflush(stdout) ; 
X}
X
X
Xsend_one_bitmap()
X{
X	/* set initial position & switch to plot mode */
X	printf( "^IT%05d\n^IJ%05d\n^P%04d\n", xpos, ypos, window );
X	throw_away_mac_header() ;
X	send_bitmap_data() ;
X	/* exit plot mode & update the postion for the next picture, if any */
X	printf( "^G\n" );
X	if ( ++images >= ipp )
X		qms_eject();
X	else
X		{
X		if ( (images % ipr) != 0 )
X			xpos += xpos_incr;
X		else
X			{
X			xpos = initial_xpos;
X			ypos += ypos_incr;
X			}
X		}
X}
X
Xqms_eject()
X{
X	printf("^,\n");
X	xpos = initial_xpos;
X	ypos = initial_ypos;
X	images = 0;
X}
X
Xint obytes;
Xint scanline;
X
Xsend_bitmap_data()
X{
X	register int repeat_count, i;
X
X	obytes=0;
X	scanline=0;
X
X	while ( (repeat_count = getchar()) != EOF )
X		{
X		repeat_count &= 0xff;
X		if ( repeat_count >= 128 )
X			{
X			if ( repeat_count == 128 )
X				continue;
X
X			/* this is a <repeat count><data to repeat> pair.  This
X			 * is almost identical to the qms compression scheme so
X			 * we just reformat slightly & write it out.
X			 */
X			repeat_count = 257 - repeat_count;
X			check_for_error( repeat_count );
X			printhex( repeat_count, nextbyte() );
X			}
X		else 
X			{
X			/* this is a <count><count+1 bytes of data> sequence.
X			 * We just ship out the data.
X			 */
X			check_for_error( ++repeat_count );
X			while ( repeat_count-- > 0 )
X				printhex( 0, nextbyte() );
X			}
X
X		if ( obytes >= MAC_SCANLINE_BYTES )
X			{
X			if (filter)
X				putdata(-1,0);
X			else
X				putchar( '\n' );
X			if ( ++scanline >= MAC_SCANLINES )
X				break;
X			obytes = 0;
X			}
X		}
X}
X
Xcheck_for_error( bytes )
X{
X	/* check that the result of putting "bytes" data bytes is <= 1
X	 * scan lines worth of data.
X	 */
X	if ( (obytes += bytes) > MAC_SCANLINE_BYTES )
X		{
X		/* too many bytes - assume that we dropped some & output
X		 * filler for the previous scanline.
X		 */
X		printf( "\n^C%03d00\n", MAC_SCANLINE_BYTES + bytes - obytes );
X
X		fprintf( stderr,
X			"macqms: %d bytes on scanline %d (input byte %D)\n",
X			obytes, scanline, ftell( stdin ) );
X
X		obytes = bytes;
X		scanline++;
X		}
X}
X
Xthrow_away_mac_header()
X{
X     short bytenum ;
X
X     for ( bytenum = 0 ; bytenum < MAC_HEADER_LENGTH ; bytenum ++ ) 
X          ( void ) nextbyte() ;
X}
X
Xnextbyte()
X{
X	int byte;
X
X	if ( (byte = getchar()) == EOF ) 
X		{
X		fprintf( stderr,
X			"macqms: unexpected EOF reading macpaint file.\n" );
X		exit(1);
X		}
X	return (byte);
X}
X
Xstatic char hex_tab[] = {	'0', '1', '2', '3', '4', '5', '6', '7',
X				'8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
X
Xprinthex( repeat, byte )
Xint repeat, byte;
X{
X	if (filter)
X		putdata(repeat,byte);
X	else { /* the old style */
X		/* print a byte as 2 hex digits.N.B.- the qms currently requires
X	 	* that the digits be in *upper case* so we can't use printf.
X	 	*/
X		if (repeat)
X			printf( "^C%03d", repeat );
X		putchar( hex_tab[(byte>>4) & 0xf]);
X		putchar( hex_tab[byte & 0xf]);
X		}
X}
X
Xqms_init(iname)
Xchar *iname;
X{
X	if (!stdoutput) {
X		int pf[2];
X		int kidpid;
X		if (pipe(pf)<0) {
X			perror("macqms piping problem");
X			exit(-1);
X			}
X		kidpid = fork();
X		if (kidpid == 0) {
X			/* kid */
X			/* here at rna the -Q-q arg does what the documented
X			 * -l arg is supposed to do.
X			 * It sends raw stuff to the qms. You may have to
X			 * tweak the lpr args so that raw data
X			 * (hat commands) are passed unmolested
X			 * to the qms. */
X			static char *args[] = {
X				"lpr", "-Q-q", "-J", 0, "-", 0 };
X			args[3] = iname;
X			/* child */
X			close(pf[1]);
X			dup2(pf[0],0);
X			close(pf[0]);
X			execv(LPR,args,environ);
X			perror("macqms exec error");
X			}
X		else if (kidpid > 0) {
X			close(pf[0]);
X			dup2(pf[1],1);
X			close(pf[1]);
X			}
X		else {
X			perror("macqms fork error");
X			exit(-1);
X			}
X		}
X	/* switch to quic mode & make sure the qms 
X	 * is set up the way we expect.
X	 */
X	printf( "\n^PY^-\n^ISYNTAX00000\n^F^-\n" );
X	/* set the portrait/landscape orientation */
X	printf( "^IO%c\n", orientation );
X	/*  set the magnification */
X	if (!filter)
X		printf( "^IP%02d%02d\n", mag, mag );
X	else {
X		if (mag == 4) {
X			printf( "^IP%02d%02d\n", 1, 1 );
X			window *= 4;
X			}
X		else filter = 0; /* can't filter except at mag == 4 */
X		}
X	/* set the postioning stuff needed for multiple images per page */
X	xpos = initial_xpos;
X	ypos = initial_ypos;
X	ypos_incr = (mag * MAC_SCANLINES * 10) / 3; /* inches per image  */
X	xpos_incr = (mag * MAC_SCANLINE_BITS * 10) / 3;
X	if ( ipr <= 0 )
X		ipr = (qms_pagewidth - xpos) / xpos_incr;
X}
X
Xqms_end()
X{
X	/* exit quic mode */
X	printf( "^-^PN^-\n" );
X}
All work and no play makes Jack a dull boy
echo macqms.l
sed 's/^X//' > macqms.l << 'All work and no play makes Jack a dull boy'
X.TH MACQMS local "12 August 1984"
X.UC 4
X.SH NAME
Xmacqms \- Convert Macintosh MacPaint file(s) to QMS Laser Printer format
X.SH SYNOPSIS
X.B macqms
X[ 
X.B \-l
X]
X[
X.B \-s
X]
X[
X.B \-f[n]
X]
X[
X.B \-m \fImag\fP
X]
X[
X.B \-x \fIxpos\fP
X]
X[
X.B \-y \fIypos\fP
X]
X[
X.B \-n \fIipp[,ipr]\fP
X]
X[ file ... ]
X.SH DESCRIPTION
X.B Macqms
Xconverts MacPaint document(s) into a file that can be printed on a
XQMS-1200 or QMS-800 laser printer.  If no file names are given, standard
Xinput is read.  The program
Xworks for files created by MacPaint and for Mac screen dumps created
Xwith OPTION-COMMAND-3.  It does
X.I not
Xwork for MacWrite documents.
X.PP
XCommand line options are:
X.TP
X.B \-l
Xspecifies
X.I landscape
Xorientation for the output.  The default is
X.I portrait
Xorientation.
X.TP
X.B -s
Xplaces the output on the standard output.  The default is to pipe the 
Xoutput to lpr.
X.TP
X.B \-f
Xruns the macpaint document through a smoothing filter so that it
Xappears less jaggy. The magnification must be the default (4), the
Xorientation must be portrait, and the multiple images per page feature
Xmust be turned off.
XThe optional numeric parameter
X.I n
Xcontrols the sensitivity of the filter.
XEight is the theoretically correct value, although six (the default)
Xseems to produce ``smoother'' looking pictures.  Reasonable values are
Xfrom about four to twelve.
XThis filter is very slow. Typical times (per image)
Xare six minutes on a lightly loaded VAX 780.  Much longer times (up to
Xthirty minutes of cpu time on a 750) have been observed. Typically the document increases in size by a factor of ten.
X.TP
X.B \-m \fIn\fP
Xspecifies a magnification factor.  By default, the MacPaint image is
Xmagnified by a factor of 4.  This just about fills the output page and
Xis about 30% larger than the screen image.
XThe magnification,
X.IR n ,
Xcan be any integer > 0.  However, at ``-m5'' and above the image might not fit
Xon the page and the QMS will do strange things to it.
X.TP
X.B \-x \fIpos\fP
Xpositions the left edge of the image.  The position,
X.IR pos ,
Xis given in \fIinches\fP.  The default is ``-x0.41''.
X.TP
X.B \-y \fIpos\fP
Xpositions the top edge of the image.  The default is ``-y0.70''.
X.sp
XDefault positions were chosen to center a
Xfull image on an 8.5 x 11 page at the default magnification.
X.TP
X.B \-n \fIipp[,ipr]\fP
Xprints multiple images on one page.  By default a page eject
Xis done between each MacPaint file printed.  This flag results in
X.I ipp
Ximages being printed on each page.  The number of images per row,
X.I ipr ,
Xcan be specified or will default the the maximum number of images that
Xwill fit across the page.  No space is inserted between multiple images
Xon a page so a large `composite' can be built up from smaller
Ximages.  Images are printed `row-wise'.  E.g.,
X.ce
Xmacqms -m2 -n4 f1 f2 f3 f4
Xwill print as
X.TS
Xcenter,allbox;
Xc c.
Xf1	f2
Xf3	f4
X.TE
X.SH AUTHOR
XVan Jacobson, Real Time Systems Group, Lawrence Berkeley Laboratory
X.SH SEE ALSO
Xmacget(local)
X.SH BUGS
XProbably.
XThe author obviously got hit by a bad case of ``creeping featurism''.
X
XThe filter is too slow.
XDoes anybody know how to speed up an eight million point convolver?
All work and no play makes Jack a dull boy
exit
Newsgroups: net.sources.mac

Subject: A Smoother Mac->qms converter

Macpainters,

Here is an enhancement of the macqms program that was originally posted
by Van Jacobson. The antecedent authors are Winkler and Patterman.
Macqms transforms a macpaint document into a format that can be printed
on a QMS laser printer. This enhancement is a filter that smoothes the images
before printing.  This increases the apparent contrast, reduces (but
does not eliminate) the jaggies, and makes most lettering look less
sparkly.  It also slows things down considerably and it expands the
data by a factor of ten or so. Some documents are improved markedly,
others suffer little improvement.  The major deficiency is
sluggishness.  Be prepared to wait, possibly a long time. This program
is a cpu-hog.  You may want to restrict its use, or have it auto-nice
itself nearly out of existence.

This filter has been designed semi-modularly so that it can be attached
to the other mac->some_printer programs.  Since our only hi-res, bitwise
printer is a QMS I've left this chore to others.

I am distributing this filter along with the slightly modified macqms
program, and the updated manual page.

Kaare Christian, uucp:cmcl2!rna!kc bell:212-570-7672

(Cut below and process with sh)
--------------------------------------------------------------------
echo filter.c
sed 's/^X//' > filter.c << 'All work and no play makes Jack a dull boy'
X/*
X * A simple convolver that converts macpaint images into 4x resolution
X * with smoothing.  This reduces the jagginess of a macpaint image, so it
X * looks nicer on the qms laser printer.  This filter assumes a 4x
X * resolution increase, but it may be adaptable to other increments.
X * This code is separate from the macqms source so that it can be
X * adapted, with minimal change, to the other macintosh->whatever filters.
X * Adaptation of this filter to the imagen macpaint routine should be
X * trivial.
X * 
X * There are two connections between this filter and the macqms code:
X * 	1. The putdata routine. It accepts data in mac format, puts it
X * in local buffers, de-length codes as necessary, and calls do_filter at
X * the end of each line.
X * 	2. The sendline routine uses the stdout, which must be connected
X * properly by macqms routines. The macqms routines must send the proper
X * qms cmds before and after the data generated by this filter.
X * 
X * There is one dependency upon the qms device:
X * 	1. The sendline routine (and the two routines it calls, squeeze
X * and booltobin) are qms specific. These will need to be replaced for other
X * output devices.
X * 
X * This filter is attached to the macqms filter by intercepting output
X * calls at the lowest level.  Macqms was modified slightly to funnel all
X * output through one low level routine, which either does the output or
X * calls putdata to output the data via this filter.
X * 
X * I have done some performance tuning. More than 3x of speed improvement
X * has been realized since the first working version.  More tuning is
X * possible. Hint, hint.
X * 
X * My thanks go to Van Jacobson for the macqms.c program.
X * 
X * This filter may be distributed, only without charge, to anyone. This
X * notice must be attached, and all modifications must be duly noted
X * by their author in the revision history.  This filter may be used for
X * any purpose, except that it may not be sold, or incorporated into
X * any software that is sold.
X * 
X * Kaare Christian, The Rockefeller Univ.  1230 York Ave. NY NY 10021
X * uucp:cmcl2!rna!kc  212-570-7672
X * 
X * Revision History:
X * 	Created Jan 22, 1985 kc
X * 
X */
X
X#include <stdio.h>
X#define MAC_SCANLINE_BITS 576
X#define MB MAC_SCANLINE_BITS
X#define QB MB*4
X
Xstatic char macbuf[MB];
Xstatic char qbuf[5][QB+1]; /* the +1 simplifies makeline */
Xstatic char outbuf[MB]; /* MB just happens to be the right size */
Xstatic int i_macbuf = 0;
Xextern int smoothval;
X
X/*
X * the input side connection to the macintosh data
X */
Xputdata(flag,byte)
Xint flag,byte;
X{
Xint i;
X
Xif (flag == -1)
X	do_filter();
Xelse if (flag == 0)
X	bintobool(byte);
Xelse for(i=0;i<flag;i++)
X	bintobool(byte);
X}
X
X/*
X * make four scan lines from one
X */
Xdo_filter()
X{
Xint line;
X
Xi_macbuf = 0;
X
Xcenters();
Xfor(line=0;line<4;line++) {
X	makeline(line);
X	clipline(line);
X	sendline(line);
X	}
Xrollbuff();
X}
X
Xrollbuff() /* place centers from qbuf[4] into qbuf[0] */
X{
Xregister int i;
Xfor(i=0;i<QB;i+=4)
X	qbuf[0][i] = qbuf[4][i];
X}
X
Xcenters()
X{
X/*
X   place data from macbuf into every fourth position of qbuf[4] 
X   Only the points inbetween the centers are candidates for filtering
X */
Xregister int i, j;
Xfor(i=0,j=0;i<QB;i+=4,j++)
X	qbuf[4][i] = macbuf[j];
X}
X
X#define LWT0 16
X#define LWT1 12
X#define LWT2 8
X#define LWT3 4
Xstatic int xyweights[4][4] = {
X	{ 16, 12, 8, 4 },
X	{ 12, 9, 6, 3 },
X	{ 8, 6, 4, 2 },
X	{ 4, 3, 2, 1}};
X
Xmakeline(N)
X{
Xregister int n = N, loc, x;
Xregister char *ptr, *p0, *p4;
Xp0 = qbuf[0];
Xp4 = qbuf[4];
Xptr = qbuf[n];
Xfor(loc=0;loc<QB;loc++) {
X	x = loc & 03; /* distance past a center */
X	if (n == 0) { /* fill in line 0 tween the centers */
X		if (x==0) 
X			ptr[loc] = ptr[loc] ? LWT0 : 0;
X		else if (x==1)
X			ptr[loc] = (ptr[loc-1] ? LWT1 : 0)
X				+ (ptr[loc+3] ? LWT3 : 0);
X		else if (x==2)
X			ptr[loc] = (ptr[loc-2] ? LWT2 : 0)
X				+ (ptr[loc+2] ? LWT2 : 0);
X		else if (x==3)
X			ptr[loc] = (ptr[loc-3] ? LWT3 : 0)
X				+ (ptr[loc+1] ? LWT1 : 0);
X		}
X	else if (x == 0) { /* fill in lines 1..3 between centers */
X		if (n==1)
X			ptr[loc] = (p0[loc] ? LWT1 : 0)
X				+ (p4[loc] ? LWT3 : 0);
X		else if (n==2)
X			ptr[loc] = (p0[loc] ? LWT2 : 0)
X				+ (p4[loc] ? LWT2 : 0);
X		else if (n==3)
X			ptr[loc] = (p0[loc] ? LWT3 : 0)
X				+ (p4[loc] ? LWT1 : 0);
X		}
X	else { /* the full xy scene */
X		int l;
X		l = loc & (~03);
X		ptr[loc] = (p0[l] ? xyweights[n][x] : 0)
X			+ (p0[l+4] ? xyweights[n][4-x] : 0)
X			+ (p4[l] ? xyweights[4-n][x] : 0)
X			+ (p4[l+4] ? xyweights[4-n][4-x] : 0);
X		}
X	}
X}
X
Xclipline(n)
Xint n;
X{
Xregister char *p, *q;
Xp = qbuf[n];
Xq = p + QB;
Xwhile(p<q)
X	if (*p > smoothval)
X		*p++ = 1;
X	else
X		*p++ = 0;
X}
X
X/*
X * Send an line of data to the qms, compressed heavily
X *  (ideally this would be less qms specific)
X */
Xsendline(n)
Xint n;
X{
Xregister int i;
Xchar *cptr;
X
Xcptr = qbuf[n];
Xfor(i=0;i<MB;i++)
X	outbuf[i] = "0123456789ABCDEF"[booltobin(&cptr)];
Xi=0;
Xwhile(i<MB)
X	i += squeeze(i);
Xputchar('\n');
X}
X
X/*
X * qms compression (note - efficiency counts here)
X */
Xsqueeze(from)
X{
Xint loc = from;
Xint n;
Xchar ch;
Xch = outbuf[loc];
Xif ((ch == '0') || (ch == 'F'))
X	while((loc < MB) && (outbuf[loc] == ch))
X		loc++;
Xn = loc-from;
Xif (n > 3) { /* squeeze it */
X	printf("^%c%03d",(ch == '0') ? 'D' : 'B', n);
X	return(n);
X	}
X/* oh well, just send one */
Xputchar(ch);
Xreturn(1);
X}
X
Xstatic char masks[8] = { 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 };
Xbintobool(byte)
Xint byte;
X{
Xint i;
Xfor(i=0;i<8;i++)
X	macbuf[i_macbuf++] = (byte & masks[i]) ? 1 : 0;
X}
X
Xbooltobin(ptr)
Xchar **ptr;
X{
Xregister int val = 0;
Xregister int i;
X
Xfor(i=4;i<8;i++)
X	if (*(*ptr)++)
X		val |= masks[i];
Xreturn(val);
X}
All work and no play makes Jack a dull boy
echo macqms.c
sed 's/^X//' > macqms.c << 'All work and no play makes Jack a dull boy'
X/*
X     This filter converts MacPaint files into Quic files for printing
X     on an QMS-1200 or -800 laser printer.  It was written by Van
X     Jacobson of Lawrence Berkeley Laboratory (van@lbl-csam.arpa,
X     ...!ucbvax!lbl-csam!van) and is in the public domain.  It was
X     loosely based on macimp.c by Winkler@harvard which was in turn
X     based on MACimp for Tops-20 by Ed Pattermann, Stanford.
X
X     Usage:
X       macqms [-l] [-s] [-f[n]] [-x<xpos>] [-y<ypos>] [-m<mag>]
X		[-n<ipp>[,<ipr>]] [file ...]
X
X     '-l' prints in "landscape" orientation (default is "portrait"
X     orientation).  <xpos> and <ypos> are the position of the upper
X     left corner of the bitmap.  They are given in inches and default
X     to 0.41 and 0.70 (which will center a 576x720 image at mag 4).
X     <mag> is a magnification for the bitmap and can be any integer
X     >0.  The default is 4.  Magnifications larger than 4 may result in
X     bizarre images.  `-n<ipp>' prints <ipp> images-per-page.  The files
X     are printed `m' across by `n/m' down. "m" can be specified (e.g.,
X     "-n4,2") or it can be defaulted.  The default is the largest number
X     of images that will fit the current magnification & orientation.
X
X     Send enhancements and bug reports to van@lbl-csam.
X
X     History:      
X	21 August 1984  VJ - created. 
X	1  Jan 1985 Hacked to pipe output to lpr.
X		-s option writes standard out.
X		(Note site dependent LPR define and the qms_init proc) kc
X	22 jan 1985 Added output filtering. Controlled by the -f option. kc
X*/
X
X#include <stdio.h>
Xdouble atof();
Xextern char **environ;
X
X#define	MAC_HEADER_LENGTH	512
X#define	MAC_SCANLINES		720	/* number of scanlines in image */
X#define	MAC_SCANLINE_BITS	576	/* number of bits in a scanline */
X#define	MAC_SCANLINE_BYTES	(MAC_SCANLINE_BITS/8)
X#define LPR	"/usr/local/bin/lpr"
X
Xint	mag=4;			/* horiz & vert magnification in dots */
Xint	window=MAC_SCANLINE_BITS;	/* horiz width of image in dots */
Xint	initial_xpos = 410;	/* left margin position in inches*1000 */
Xint	initial_ypos = 700;	/* top margin position in inches*1000 */
Xchar	orientation = 'P';	/* "portrait" orientation */
Xint	qms_pagewidth = 8500;	/* in inches*1000 */
Xint	ipp = 1;		/* 1 image per page */
Xint	ipr = 0;		/* default images per row */
Xint	images = 0;		/* number of images on current page */
Xint	xpos;
Xint	ypos;
Xint	xpos_incr;
Xint	ypos_incr;
Xint	stdoutput;		/* set means ship to std, not to lpr */
Xint	filter;			/* set means invoke the smoothing filter */
Xint	smoothval = 6;
Xchar	usage[] =
X"usage: macqms [-l] [-f] [-s[val]] [-m<mag>]\n\t\t[-x<xpos>] [-y<ypos>] [-n<ipp>] [file ..]\n";
X
Xmain(argc,argv)
Xint argc;
Xchar **argv;
X{
X	register int i;
X
X	argv++; argc--;
X	for ( i=0; i < argc; i++ )
X		{
X		if ( argv[i][0] != '-' )
X			break;
X
X		switch ( argv[i][1] )
X		{
X		case 'm':
X			mag = atoi( &argv[i][2] );
X			if ( mag <= 0 )
X				mag = 1;
X			break;
X		case 'l':
X			orientation = 'L';
X			qms_pagewidth = 11000;
X			break;
X		case 'x':
X			xpos = atof( &argv[i][2] )*1000.;
X			break;
X		case 'y':
X			ypos = atof( &argv[i][2] )*1000.;
X			break;
X		case 'n':
X			sscanf( &argv[i][2], "%d,%d", &ipp, &ipr );
X			break;
X		case 's':
X			stdoutput++;
X			break;
X		case 'f':
X			filter++;
X			if (argv[i][2] != 0)
X				smoothval = atoi(&argv[i][2]);
X			if ((smoothval < 3) || (smoothval > 16))
X				smoothval = 6;
X			break;
X		default:
X			fprintf( stderr, usage );
X			exit(1);
X		}
X	}
X	if ( i >= argc ) {
X		/* no file arguments - send contents of stdin */
X		qms_init("macqms");
X		send_one_bitmap();
X		}
X	else {
X		qms_init(argv[i]);
X		for ( ; i < argc; i++ )
X			{
X			if ( freopen( argv[i], "r", stdin ) == NULL )
X				{
X				perror( argv[i] );
X				exit(2);
X				}
X			send_one_bitmap();
X			}
X		}
X
X	/* if we have a partial page of images left over, eject it. */
X	if ( images > 0 )
X		qms_eject();
X
X	qms_end();
X	fflush(stdout) ; 
X}
X
X
Xsend_one_bitmap()
X{
X	/* set initial position & switch to plot mode */
X	printf( "^IT%05d\n^IJ%05d\n^P%04d\n", xpos, ypos, window );
X	throw_away_mac_header() ;
X	send_bitmap_data() ;
X	/* exit plot mode & update the postion for the next picture, if any */
X	printf( "^G\n" );
X	if ( ++images >= ipp )
X		qms_eject();
X	else
X		{
X		if ( (images % ipr) != 0 )
X			xpos += xpos_incr;
X		else
X			{
X			xpos = initial_xpos;
X			ypos += ypos_incr;
X			}
X		}
X}
X
Xqms_eject()
X{
X	printf("^,\n");
X	xpos = initial_xpos;
X	ypos = initial_ypos;
X	images = 0;
X}
X
Xint obytes;
Xint scanline;
X
Xsend_bitmap_data()
X{
X	register int repeat_count, i;
X
X	obytes=0;
X	scanline=0;
X
X	while ( (repeat_count = getchar()) != EOF )
X		{
X		repeat_count &= 0xff;
X		if ( repeat_count >= 128 )
X			{
X			if ( repeat_count == 128 )
X				continue;
X
X			/* this is a <repeat count><data to repeat> pair.  This
X			 * is almost identical to the qms compression scheme so
X			 * we just reformat slightly & write it out.
X			 */
X			repeat_count = 257 - repeat_count;
X			check_for_error( repeat_count );
X			printhex( repeat_count, nextbyte() );
X			}
X		else 
X			{
X			/* this is a <count><count+1 bytes of data> sequence.
X			 * We just ship out the data.
X			 */
X			check_for_error( ++repeat_count );
X			while ( repeat_count-- > 0 )
X				printhex( 0, nextbyte() );
X			}
X
X		if ( obytes >= MAC_SCANLINE_BYTES )
X			{
X			if (filter)
X				putdata(-1,0);
X			else
X				putchar( '\n' );
X			if ( ++scanline >= MAC_SCANLINES )
X				break;
X			obytes = 0;
X			}
X		}
X}
X
Xcheck_for_error( bytes )
X{
X	/* check that the result of putting "bytes" data bytes is <= 1
X	 * scan lines worth of data.
X	 */
X	if ( (obytes += bytes) > MAC_SCANLINE_BYTES )
X		{
X		/* too many bytes - assume that we dropped some & output
X		 * filler for the previous scanline.
X		 */
X		printf( "\n^C%03d00\n", MAC_SCANLINE_BYTES + bytes - obytes );
X
X		fprintf( stderr,
X			"macqms: %d bytes on scanline %d (input byte %D)\n",
X			obytes, scanline, ftell( stdin ) );
X
X		obytes = bytes;
X		scanline++;
X		}
X}
X
Xthrow_away_mac_header()
X{
X     short bytenum ;
X
X     for ( bytenum = 0 ; bytenum < MAC_HEADER_LENGTH ; bytenum ++ ) 
X          ( void ) nextbyte() ;
X}
X
Xnextbyte()
X{
X	int byte;
X
X	if ( (byte = getchar()) == EOF ) 
X		{
X		fprintf( stderr,
X			"macqms: unexpected EOF reading macpaint file.\n" );
X		exit(1);
X		}
X	return (byte);
X}
X
Xstatic char hex_tab[] = {	'0', '1', '2', '3', '4', '5', '6', '7',
X				'8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
X
Xprinthex( repeat, byte )
Xint repeat, byte;
X{
X	if (filter)
X		putdata(repeat,byte);
X	else { /* the old style */
X		/* print a byte as 2 hex digits.N.B.- the qms currently requires
X	 	* that the digits be in *upper case* so we can't use printf.
X	 	*/
X		if (repeat)
X			printf( "^C%03d", repeat );
X		putchar( hex_tab[(byte>>4) & 0xf]);
X		putchar( hex_tab[byte & 0xf]);
X		}
X}
X
Xqms_init(iname)
Xchar *iname;
X{
X	if (!stdoutput) {
X		int pf[2];
X		int kidpid;
X		if (pipe(pf)<0) {
X			perror("macqms piping problem");
X			exit(-1);
X			}
X		kidpid = fork();
X		if (kidpid == 0) {
X			/* kid */
X			/* here at rna the -Q-q arg does what the documented
X			 * -l arg is supposed to do.
X			 * It sends raw stuff to the qms. You may have to
X			 * tweak the lpr args so that raw data
X			 * (hat commands) are passed unmolested
X			 * to the qms. */
X			static char *args[] = {
X				"lpr", "-Q-q", "-J", 0, "-", 0 };
X			args[3] = iname;
X			/* child */
X			close(pf[1]);
X			dup2(pf[0],0);
X			close(pf[0]);
X			execv(LPR,args,environ);
X			perror("macqms exec error");
X			}
X		else if (kidpid > 0) {
X			close(pf[0]);
X			dup2(pf[1],1);
X			close(pf[1]);
X			}
X		else {
X			perror("macqms fork error");
X			exit(-1);
X			}
X		}
X	/* switch to quic mode & make sure the qms 
X	 * is set up the way we expect.
X	 */
X	printf( "\n^PY^-\n^ISYNTAX00000\n^F^-\n" );
X	/* set the portrait/landscape orientation */
X	printf( "^IO%c\n", orientation );
X	/*  set the magnification */
X	if (!filter)
X		printf( "^IP%02d%02d\n", mag, mag );
X	else {
X		if (mag == 4) {
X			printf( "^IP%02d%02d\n", 1, 1 );
X			window *= 4;
X			}
X		else filter = 0; /* can't filter except at mag == 4 */
X		}
X	/* set the postioning stuff needed for multiple images per page */
X	xpos = initial_xpos;
X	ypos = initial_ypos;
X	ypos_incr = (mag * MAC_SCANLINES * 10) / 3; /* inches per image  */
X	xpos_incr = (mag * MAC_SCANLINE_BITS * 10) / 3;
X	if ( ipr <= 0 )
X		ipr = (qms_pagewidth - xpos) / xpos_incr;
X}
X
Xqms_end()
X{
X	/* exit quic mode */
X	printf( "^-^PN^-\n" );
X}
All work and no play makes Jack a dull boy
echo macqms.l
sed 's/^X//' > macqms.l << 'All work and no play makes Jack a dull boy'
X.TH MACQMS local "12 August 1984"
X.UC 4
X.SH NAME
Xmacqms \- Convert Macintosh MacPaint file(s) to QMS Laser Printer format
X.SH SYNOPSIS
X.B macqms
X[ 
X.B \-l
X]
X[
X.B \-s
X]
X[
X.B \-f[n]
X]
X[
X.B \-m \fImag\fP
X]
X[
X.B \-x \fIxpos\fP
X]
X[
X.B \-y \fIypos\fP
X]
X[
X.B \-n \fIipp[,ipr]\fP
X]
X[ file ... ]
X.SH DESCRIPTION
X.B Macqms
Xconverts MacPaint document(s) into a file that can be printed on a
XQMS-1200 or QMS-800 laser printer.  If no file names are given, standard
Xinput is read.  The program
Xworks for files created by MacPaint and for Mac screen dumps created
Xwith OPTION-COMMAND-3.  It does
X.I not
Xwork for MacWrite documents.
X.PP
XCommand line options are:
X.TP
X.B \-l
Xspecifies
X.I landscape
Xorientation for the output.  The default is
X.I portrait
Xorientation.
X.TP
X.B -s
Xplaces the output on the standard output.  The default is to pipe the 
Xoutput to lpr.
X.TP
X.B \-f
Xruns the macpaint document through a smoothing filter so that it
Xappears less jaggy. The magnification must be the default (4), the
Xorientation must be portrait, and the multiple images per page feature
Xmust be turned off.
XThe optional numeric parameter
X.I n
Xcontrols the sensitivity of the filter.
XEight is the theoretically correct value, although six (the default)
Xseems to produce ``smoother'' looking pictures.  Reasonable values are
Xfrom about four to twelve.
XThis filter is very slow. Typical times (per image)
Xare six minutes on a lightly loaded VAX 780.  Much longer times (up to
Xthirty minutes of cpu time on a 750) have been observed. Typically the document increases in size by a factor of ten.
X.TP
X.B \-m \fIn\fP
Xspecifies a magnification factor.  By default, the MacPaint image is
Xmagnified by a factor of 4.  This just about fills the output page and
Xis about 30% larger than the screen image.
XThe magnification,
X.IR n ,
Xcan be any integer > 0.  However, at ``-m5'' and above the image might not fit
Xon the page and the QMS will do strange things to it.
X.TP
X.B \-x \fIpos\fP
Xpositions the left edge of the image.  The position,
X.IR pos ,
Xis given in \fIinches\fP.  The default is ``-x0.41''.
X.TP
X.B \-y \fIpos\fP
Xpositions the top edge of the image.  The default is ``-y0.70''.
X.sp
XDefault positions were chosen to center a
Xfull image on an 8.5 x 11 page at the default magnification.
X.TP
X.B \-n \fIipp[,ipr]\fP
Xprints multiple images on one page.  By default a page eject
Xis done between each MacPaint file printed.  This flag results in
X.I ipp
Ximages being printed on each page.  The number of images per row,
X.I ipr ,
Xcan be specified or will default the the maximum number of images that
Xwill fit across the page.  No space is inserted between multiple images
Xon a page so a large `composite' can be built up from smaller
Ximages.  Images are printed `row-wise'.  E.g.,
X.ce
Xmacqms -m2 -n4 f1 f2 f3 f4
Xwill print as
X.TS
Xcenter,allbox;
Xc c.
Xf1	f2
Xf3	f4
X.TE
X.SH AUTHOR
XVan Jacobson, Real Time Systems Group, Lawrence Berkeley Laboratory
X.SH SEE ALSO
Xmacget(local)
X.SH BUGS
XProbably.
XThe author obviously got hit by a bad case of ``creeping featurism''.
X
XThe filter is too slow.
XDoes anybody know how to speed up an eight million point convolver?
All work and no play makes Jack a dull boy
exit