[comp.lang.postscript] C source for picture to postscript converter

ph@degas.Berkeley.EDU (Paul Heckbert) (10/20/87)

Somebody was requesting postscript programs, so here you go.
The following program converts raster pictures into postscipt image
format for halftone printing.

A slightly earlier version of this was posted to net.sources back in April.
The popular "Stop Reagan" postscript image was printed with this program.

------------

# to unpack, cut here and run the following shell archive through sh
# contents: README printpic.1 printpic.c
#
sed 's/^X//' <<'EOF17251' >README
XHere is the source and documentation for printpic, a program to convert
Xpictures to postscript format and print them on a printer.
X
XThe program has been tested on a VAX 780 running berkeley 4.3 UNIX
Xfeeding an Apple laser writer.  You will have to modify this program if
Xyour "lpr" line printer spooler doesn't accept postscript.
X
XFor portability, the program does all picture input through a simple set of
Xinterface routines.  The interface to this pic package is currently oriented
Xtoward 4-channel (r,g,b,alpha), 8 bit per channel picture files, but could
Xeasily be adapted to read from frame buffer memory or handle other picture
Xformats.  You will have to implement this interface.  The current interface is:
X
X    #include <pic.h>
X	might look something like:
X	    typedef struct {unsigned char r, g, b, a;} pic_pixel;
X	    typedef struct { ... } pic;
X	    pic *pic_open();
X
X    pic *pic_open(file, mode)
X    char *file, *mode;
X	open a picture file with given mode and return pointer to a structure
X	containing state information (like fopen)
X
X    pic_read_size(p, xp, yp)
X    pic *p; int *xp, *yp;
X	return the size of picture p in *xp and *yp
X
X    pic_read_line(p, y, buf)
X    pic *p; int y; pic_pixel *buf;
X	read scan line y of picture p into scan line buffer buf
X
X    pic_close(p)
X    pic *p;
X	close picture p
X
XYou will have to write or find routines like this to get printpic working.
XIf your image channels are not 8 bits then change the definitions of PVBITS
Xand PVMAX.
XIf your random() routine doesn't return numbers in the range 0 to 2^31-1 then
Xyou should change RANDOMRANGE2 and/or the call to random().
X
XPaul Heckbert, CS grad student
X508-7 Evans Hall, UC Berkeley		UUCP: ucbvax!ph@degas
XBerkeley, CA 94720			ARPA: ph@degas.berkeley.edu
EOF17251
sed 's/^X//' <<'EOF17252' >printpic.1
X.TH PRINTPIC 1 "18 April 1987"
X.SH NAME
Xprintpic \- make b&w halftone print of pic file on printer
X.SH SYNOPSIS
X\fBprintpic\fP [\fIoptions\fP] \fIpicfile\fP
X.SH DESCRIPTION
X\fIPrintpic\fP makes black and white halftone prints of picture files
Xon a Postscript printer.
XThe program reads the
Xpicture file, translates it into Postscript image format
Xand pipes this to the \fIlpr(1)\fP program for spooling to a printer.
XIf printing is not desired then the Postscript output can
Xbe saved in a file using the \fB\-p\fP option.
X.PP
XBy default the entire picture file is
Xrotated 90 degrees so that it nearly fills the page
Xand 8 bits of luminance are used,
Xbut the intensity transform, picture file window,
Xhalftone resolution,
Xand the image's placement on the page may be changed.
X.PP
XOptions:
X.nf
X.ta \w'-dither amplitude   'u
X\fB\-P\fR \fIprinter\fP	print on named printer, default is in the environment variable PRINTER
X\fB\-p\fR \fIoutfile\fP	output Postscript to \fIoutfile\fP rather than spooling, file name '\-' means standard output
X\fB\-label\fR \fIlabel\fP	label string to be printed below image (quote if it contains spaces)
X\fB\-w\fR \fIx1 x2 y1 y2\fP	window to read, default is entire image
X\fB\-ar\fR \fIratio\fP	pixel aspect ratio, default is 1
X\fB\-chan\fR \fIchan\fP	channel to print \fB{r|g|b|a}\fP, default is luminance
X\fB\-bits\fR \fInbits\fP	number of bits per pixel to send, default is 8.  must be 1, 2, 4, or 8.
X\fB\-gamma\fR \fIgamma\fP	gamma, default is 1
X\fB\-negate\fR	negate image
X\fB\-dither \fIamplitude\fR	turn on dithering, default amplitude is 0 (try .5)
X\fB\-size\fR \fIsize\fP	image size on page in inches, default is 9, (the larger of width and height)
X\fB\-rot\fR \fIangle\fP	rotation angle in degrees counter-clockwise, default is 90 (landscape format)
X\fB\-cen\fR \fIx\fP \fIy\fP	image center on page, measured in inches from upper left, default is 4.25 5.5
X\fB\-dpi\fR \fIsize\fP	halftone mesh size in dots per inch, default is 50
X.fi
X.PP
XExamples:
X.br
X.TP .7i
X\fBprintpic foo.pic\fP
Xprint image vertically on page on default printer.
X.TP
X\fBprintpic foo.pic -cen 4.25 3 -rot 0 -size 6 -label "8-bit halftone of foo.pic" -P gordo\fP
Xprint image horizontally near top of page, 6 inches wide, with label,
Xon printer "gordo".
X.TP
X\fBprintpic foo.pic -dither .5 -bits 2 -chan r -dpi 10 -p temp.ps\fP
Xwrite postscript output for 2 bits of dithered red channel at 10 dots per inch
Xto \fItemp.ps\fP
X.SH NOTE
XTypically takes 10 to 15 minutes on an Apple Laserwriter
Xfor a 512x488x8 image because of 9600 baud link.
XThe laser writer prints only 5 bits of gray level max, even if you request 8.
X.SH SEE ALSO
Xlpr(1)
X.SH AUTHOR
XPaul Heckbert, UC Berkeley
EOF17252
sed 's/^X//' <<'EOF17253' >printpic.c
X/*
X * PRINTPIC: convert pic file to Postscript and print b&w halftone on printer
X *
X * 25 April 86, 18 April 87
X *
X * Paul Heckbert, CS grad student
X * 508-7 Evans Hall, UC Berkeley	UUCP: ucbvax!ph@degas
X * Berkeley, CA 94720			ARPA: ph@degas.berkeley.edu
X * please mail me any major enhancements you make
X *
X * notes:
X *   Postscript "image" command wants each scan line to start on
X *	byte boundary
X */
X
X#include <math.h>
X#include <stdio.h>
X#include <assert.h>
X
X#include <pic.h>
X#define STREQ(a, b) (strcmp(a, b)==0)
X#define MAX(a, b) ((a)>(b) ? (a) : (b))
X
X/* the following are system-dependent */
X#define RANDOMRANGE2 (1<<30)	/* random() has range 0 to 2*RANDOMRANGE2-1 */
X#define PVBITS 8
X#define PVMAX 255
X
X#define POINTSIZE 10
X#define FONT "Helvetica-Oblique"
X#define PAGEWID 8.5
X#define PAGEHEI 11.
X#define POINT(x) (int)((x)*72.)		/* convert inches to points */
X#define UNINIT -666
X
Xstatic int wx1 = UNINIT, wx2, wy1, wy2;
Xstatic char *dev = 0, *outfile = 0, *label = 0;
Xstatic double ar = 1., size = 9., rot = 90., gam = 1.;
Xstatic double dither = 0.;		/* in nbits intensity space */
Xstatic int ditheramp;			/* in PVBITS intensity space */
Xstatic double cenx = PAGEWID/2., ceny = PAGEHEI/2.;
Xstatic double ssize = 50.;
Xstatic double cr = .30, cg = .59, cb = .11, ca = 0.;	/* luminance coeffs */
Xstatic int nbits = 8, negate = 0;
X
Xstatic char Usage[] = "\
Xprintpic [options] picfile\n\
XMake halftone print of pic file on printer\n\
X	Options:\n\
X-P <printer>	print on named printer, default is $PRINTER\n\
X-p <file>	output postscript to named file rather than spooling,\n\
X		file name '-' means stdout\n\
X-label <string>	label string printed below image\n\
X-w <xmin> <xmax> <ymin> <ymax>		window to read\n\
X-ar <ratio>	pixel aspect ratio, default is 1.\n\
X-chan {rgba}	channel to print, default is luminance\n\
X-bits <nbits>	number of bits per pixel to send, default is 8\n\
X-gamma <gamma>	gamma, default is 1\n\
X-negate		negate image\n\
X-dither <amp>	dither amplitude, default is 0\n\
X-size <size>	image size on page in inches, default is 9\n\
X-rot <ang>	rotation in degrees ccw, default is 90 (landscape)\n\
X-cen <x> <y>	image center on page, inches from upper left, default 4.25 5.5\n\
X-dpi <size>	halftone mesh size in dots per inch, default 50\n\
X";
X
Xstatic usage()
X{
X    fprintf(stderr, Usage);
X    exit(1);
X}
X
Xstatic badarg(opt)
Xchar *opt;
X{
X    fprintf(stderr, "insufficient arguments to %s\n", opt);
X    exit(1);
X}
X
Xmain(ac, av)
Xint ac;
Xchar **av;
X{
X    char *picfile;
X    int i, filecount;
X
X    filecount = 0;
X    for (i=1; i<ac; i++) {
X	if (av[i][0]=='-') {
X	    if (STREQ(av[i], "-P")) {
X		if (i+1>=ac) badarg(av[i]);
X		dev = av[++i];
X	    }
X	    else if (STREQ(av[i], "-p")) {
X		if (i+1>=ac) badarg(av[i]);
X		outfile = av[++i];
X	    }
X	    else if (STREQ(av[i], "-label")) {
X		if (i+1>=ac) badarg(av[i]);
X		label = av[++i];
X	    }
X	    else if (STREQ(av[i], "-w")) {
X		if (i+4>=ac) badarg(av[i]);
X		wx1 = atoi(av[++i]);
X		wx2 = atoi(av[++i]);
X		wy1 = atoi(av[++i]);
X		wy2 = atoi(av[++i]);
X	    }
X	    else if (STREQ(av[i], "-ar")) {
X		if (i+1>=ac) badarg(av[i]);
X		ar = atof(av[++i]);
X	    }
X	    else if (STREQ(av[i], "-chan")) {
X		if (i+1>=ac) badarg(av[i]);
X		switch (av[++i][0]) {
X		    case 'r': cr = 1.; cg = 0.; cb = 0.; ca = 0.; break;
X		    case 'g': cr = 0.; cg = 1.; cb = 0.; ca = 0.; break;
X		    case 'b': cr = 0.; cg = 0.; cb = 1.; ca = 0.; break;
X		    case 'a': cr = 0.; cg = 0.; cb = 0.; ca = 1.; break;
X		    default : fprintf(stderr, "bad channel: %c\n", av[i][0]);
X			exit(1);
X		}
X	    }
X	    else if (STREQ(av[i], "-bits")) {
X		if (i+1>=ac) badarg(av[i]);
X		nbits = atoi(av[++i]);
X	    }
X	    else if (STREQ(av[i], "-gamma")) {
X		if (i+1>=ac) badarg(av[i]);
X		gam = atof(av[++i]);
X	    }
X	    else if (STREQ(av[i], "-negate")) {
X		negate = 1;
X	    }
X	    else if (STREQ(av[i], "-dither")) {
X		if (i+1>=ac) badarg(av[i]);
X		dither = atof(av[++i]);
X	    }
X	    else if (STREQ(av[i], "-size")) {
X		if (i+1>=ac) badarg(av[i]);
X		size = atof(av[++i]);
X	    }
X	    else if (STREQ(av[i], "-rot")) {
X		if (i+1>=ac) badarg(av[i]);
X		rot = atof(av[++i]);
X	    }
X	    else if (STREQ(av[i], "-cen")) {
X		if (i+2>=ac) badarg(av[i]);
X		cenx = atof(av[++i]);
X		ceny = atof(av[++i]);
X	    }
X	    else if (STREQ(av[i], "-dpi")) {
X		if (i+1>=ac) badarg(av[i]);
X		ssize = atof(av[++i]);
X	    }
X	    else usage();
X	}
X	else {
X	    filecount++;
X	    picfile = av[i];
X	}
X    }
X    if (filecount!=1) usage();
X
X    printpic(picfile);
X}
X
Xprintpic(file)
Xchar *file;
X{
X    char command[40];
X    FILE *ofp;
X    pic *p;
X
X    if (nbits!=1 && nbits!=2 && nbits!=4 && nbits!=8) {
X	fprintf(stderr, "nbits must be power of two\n");
X	exit(1);
X    }
X    /*
X     * strange printer behavior: if you ask for 8 bits you get only 5.
X     * dither accordingly
X     */
X    ditheramp = dither*(1 << PVBITS-(nbits==8 ? 5 : nbits));
X
X#   ifdef DEBUG
X	fprintf(stderr, "ar=%g size=%g rot=%g cen=(%g,%g) nbits=%d file=%s\n",
X	    ar, size, rot, cenx, ceny, nbits, file);
X	fprintf(stderr, "gam=%g negate=%d win={%d,%d,%d,%d}\n",
X	    gam, negate, wx1, wx2, wy1, wy2);
X	fprintf(stderr, "dither=%g ditheramp=%d\n", dither, ditheramp);
X	fprintf(stderr, "coeffs=(%g,%g,%g,%g)\n", cr, cg, cb, ca);
X#   endif
X
X    p = pic_open(file, "r");
X    if (p==NULL) {
X	fprintf(stderr, "nonexistent or bad picture file: %s\n", file);
X	exit(1);
X    }
X
X    if (outfile==0) {		/* spool to printer */
X	if (dev) sprintf(command, "lpr -P%s", dev);
X	else strcpy(command, "lpr");
X	ofp = popen(command, "w");
X	if (ofp==NULL) {
X	    fprintf(stderr, "croak: can't run 'lpr' program\n");
X	    exit(1);
X	}
X    }
X    else {			/* create file */
X	if (STREQ(outfile, "-")) ofp = stdout;
X	else ofp = fopen(outfile, "w");
X	if (ofp==NULL) {
X	    fprintf(stderr, "can't create %s\n", outfile);
X	    exit(1);
X	}
X    }
X
X    pic_to_ps(p, ofp);
X
X    if (outfile==0) pclose(ofp);
X    else if (ofp!=stdout) fclose(ofp);
X}
X
X/* pic_to_ps: read picture file p, write postscript to stream ofp */
X
Xpic_to_ps(p, ofp)
Xpic *p;
XFILE *ofp;
X{
X    int tx, ty, dx, dy, x, y, i, gray, table[PVMAX+1];
X    pic_pixel *line, *lp;
X
X    pic_read_size(p, &tx, &ty);
X    if (wx1==UNINIT) {
X	wx1 = 0; wx2 = tx-1;
X	wy1 = 0; wy2 = ty-1;
X    }
X    else if (wx1<0 || wx2>=tx || wy1<0 || wy2>=ty) {
X	fprintf(stderr, "window larger than image\n");
X	exit(1);
X    }
X    dx = wx2-wx1+1;		/* window size */
X    dy = wy2-wy1+1;
X
X    fprintf(stderr, "%dx%dx%d-bit\n", dx, dy, nbits);
X    for (i=0; i<=PVMAX; i++) {	/* initialize intensity table */
X	gray = negate ? PVMAX-i : i;
X	table[i] = PVMAX.*pow(gray/PVMAX., gam) + .5;
X    }
X
X    ps_head(ofp);
X    ps_image_head(ofp, dx, dy);
X
X    /*
X     * decode picture file, ignoring lines before wy1 and skipping those
X     * after wy2
X     */
X    assert(line = (pic_pixel *)malloc(tx*sizeof(pic_pixel)));
X    for (y=wy1; y<=wy2; y++) {
X	pic_read_line(p, y, line);
X	for (lp=line+wx1, x=wx1; x<=wx2; x++, lp++) {
X	    gray = cr*lp->r + cg*lp->g + cb*lp->b + ca*lp->a;
X	    if (gray<0) gray = 0;
X	    else if (gray>PVMAX) gray = PVMAX;
X	    gray = table[gray];		/* intensity mapping */
X	    if (ditheramp) {
X		/* perturb gray by a random number between -amp and amp */
X		gray += random()/(RANDOMRANGE2/ditheramp) - ditheramp;
X		if (gray<0) gray = 0;
X		else if (gray>PVMAX) gray = PVMAX;
X	    }
X	    gray >>= PVBITS-nbits;	/* quantize */
X	    ps_pixel(ofp, gray, nbits);
X	}
X	ps_pixelflush(ofp, nbits);
X    }
X    pic_close(p);
X
X    ps_tail(ofp);
X}
X
X/*-------------------- postscript nitty-gritty --------------------*/
X
Xps_head(ofp)
XFILE *ofp;
X{
X    char host[20];
X    int curtime;
X
X    gethostname(host, sizeof host);
X    curtime = time(0);
X    fprintf(ofp, "%%!PS-Adobe-1.0\n");
X    fprintf(ofp, "%%%%Creator: %s:%s\n", host, getlogin());
X    fprintf(ofp, "%%%%CreationDate: %s", ctime(&curtime));
X    fprintf(ofp, "%%%%EndProlog\n");
X    fprintf(ofp, "%%%%Page: ? 1\n\n");
X}
X
Xps_image_head(ofp, dx, dy)
XFILE *ofp;
Xint dx, dy;
X{
X    int strlen;
X    double max, wid, hei;
X
X    strlen = (dx*nbits+7)/8;	/* this must be right or no output! */
X    fprintf(ofp, "/picstr %d string def\n", strlen);
X    max = MAX(dx*ar, dy);
X    wid = size*dx*ar/max;
X    hei = size*dy/max;
X    fprintf(ofp, "%d %d translate %g rotate %d %d translate\n",
X	POINT(cenx), POINT(PAGEHEI-ceny), rot, POINT(-wid/2.), POINT(-hei/2.));
X    if (label) {
X	fprintf(ofp, "/%s findfont %d scalefont setfont\n", FONT, POINTSIZE);
X	ps_quote_string(ofp, label);
X	fprintf(ofp, " 0 %g moveto show\n", -1.5*POINTSIZE);
X    }
X    fprintf(ofp, "%d %d scale\n", POINT(wid), POINT(hei));
X    fprintf(ofp, "currentscreen 3 -1 roll pop %5.1f 3 1 roll setscreen\n\n",
X    	ssize);
X
X    fprintf(ofp, "%d %d %d [%d 0 0 %d 0 %d]\n", dx, dy, nbits, dx, -dy, dy);
X    fprintf(ofp, "{currentfile picstr readhexstring pop}\n");
X    fprintf(ofp, "image\n");
X}
X
Xps_tail(ofp)
XFILE *ofp;
X{
X    fprintf(ofp, "\nshowpage\n");
X}
X
X/* ps_quote_string: print string str to stream ofp, quoted for postscript */
X
Xps_quote_string(ofp, str)
XFILE *ofp;
Xchar *str;
X{
X    putc('(', ofp);
X    for (; *str; str++) {
X	switch (*str) {
X	    case '(':
X	    case ')':
X	    case '\\':
X		putc('\\', ofp);
X		break;
X	}
X	putc(*str, ofp);
X    }
X    putc(')', ofp);
X}
X
Xstatic int accum = 0, count = 0, col = 0;
X
X/* ps_pixelflush: pad to finish this byte */
X
Xps_pixelflush(ofp, n)
XFILE *ofp;
Xint n;
X{
X    while (count!=0) ps_pixel(ofp, 0, n);
X    if (col!=0) {
X	fprintf(ofp, "\n");
X	col = 0;
X    }
X}
X
X/*
X * ps_pixel: accumulate n bit data, where n = 1, 2, 4, or 8,
X * outputting 2 hex digits for each byte
X */
X
Xps_pixel(ofp, data, n)
XFILE *ofp;
Xint data, n;
X{
X    accum = accum<<n | data;
X    count += n;
X    if (count>=8) {
X	fprintf(ofp, "%02x", accum);
X	accum = 0;
X	count = 0;
X	col += 2;
X	if (col>=72) {
X	    fprintf(ofp, "\n");
X	    col = 0;
X	}
X    }
X}
EOF17253
exit