ph@pixar.UUCP (Paul Heckbert) (04/20/87)
Here's source and documentation for a program to convert picture files into postscript and print black and white halftones on an Apple laser writer. It sends postscript to the printer using the "lpr" line printer spooler. The postscript interpreter in the laser writer does the actual halftoning. The program has options for labeling the picture, displaying the luminance or just one channel, gamma correction, negation, dithering, and positional control, as well as transmission to the laser writer at 1, 2, 4, or 8 bit precision. It's similar to the code recently posted to comp.sources.wanted and comp.graphics by Mark Majka (majka@ubc-vision.uucp), but with more options. Enjoy! Has anyone experimented with sending bitmaps at 300dpi resolution to take advantage of the halftone bypass feature described in the Postscript book? Or has anybody written their own postscript code for halftoning in order to get around the current limit of 32 gray levels? Please send me mail if you have. Paul Heckbert PIXAR 415-499-3600 P.O. Box 13719 UUCP: {sun,ucbvax}!pixar!ph San Rafael, CA 94913 ARPA: ph%pixar.uucp@ucbvax.berkeley.edu ------------------------------------ # to unpack, cut here and run the following through sh # shell archive for README printpic.1 printpic.c # cat <<EOF20337 >README Here is the source and documentation for printpic, a program to convert pictures to postscript format and print them on a laser writer. The program has been tested on a VAX 780 running berkeley 4.3 UNIX feeding an Apple laser writer. You will have to modify this program if your "lpr" line printer spooler doesn't accept postscript. For portability, the program does all picture input through a simple set of interface routines. The interface to this pic package is currently oriented toward 4-channel (r,g,b,alpha), 8 bit per channel picture files, but could easily be adapted to read from frame buffer memory or handle other picture formats. The current interface is: #include <pic.h> might look something like: typedef struct {unsigned char r, g, b, a;} pic_pixel; typedef struct { ... } pic; pic *pic_open(); pic *pic_open(file, mode) char *file, *mode; open a picture file with given mode and return pointer to a structure containing state information (like fopen) pic_read_size(p, xp, yp) pic *p; int *xp, *yp; return the size of picture p in *xp and *yp pic_read_line(p, y, buf) pic *p; int y; pic_pixel *buf; read scan line y of picture p into scan line buffer buf pic_close(p) pic *p; close picture p You will have to write or find routines like this to get printpic working. If your image channels are not 8 bits then change the definitions of PVBITS and PVMAX. If your random() routine doesn't return numbers in the range 0 to 2^31-1 then you should change RANDOMRANGE2 and/or the call to random(). Paul Heckbert PIXAR P.O. Box 13719 UUCP: {sun,ucbvax}!pixar!ph San Rafael, CA 94913 ARPA: ph%pixar.uucp@ucbvax.berkeley.edu EOF20337 cat <<EOF20337 >printpic.1 .TH PRINTPIC 1 "18 April 1987" .SH NAME printpic \- make b&w halftone print of pic file on Laser Writer .SH SYNOPSIS \fBprintpic\fP [\fIoptions\fP] \fIpicfile\fP .SH DESCRIPTION \fIPrintpic\fP makes black and white halftone prints of picture files on an Apple Laser Writer. The program reads the picture file, translates it into Postscript image format and pipes this to the \fIlpr(1)\fP program for spooling to a printer. If printing is not desired then the Postscript output can be saved in a file using the \fB\-p\fP option. .PP By default the entire picture file is rotated 90 degrees so that it nearly fills the page and 8 bits of luminance are used, but the intensity transform, picture file window, and the image's placement on the page may be changed. .PP Options: .nf .ta \w'-dither amplitude 'u \fB\-P\fR \fIprinter\fP print on named Laser Writer, default is in the environment variable PRINTER \fB\-p\fR \fIoutfile\fP output Postscript to \fIoutfile\fP rather than spooling, file name '\-' means standard output \fB\-label\fR \fIlabel\fP label string to be printed below image (quote if it contains spaces) \fB\-w\fR \fIx1 x2 y1 y2\fP window to read, default is entire image \fB\-ar\fR \fIratio\fP pixel aspect ratio, default is 1 \fB\-chan\fR \fIchan\fP channel to print \fB{r|g|b|a}\fP, default is luminance \fB\-bits\fR \fInbits\fP number of bits per pixel to send, default is 8. must be 1, 2, 4, or 8. \fB\-gamma\fR \fIgamma\fP gamma, default is 1 \fB\-negate\fR negate image \fB\-dither \fIamplitude\fR turn on dithering, default amplitude is 0 (try .5) \fB\-size\fR \fIsize\fP image size on page in inches, default is 9, (the larger of width and height) \fB\-rot\fR \fIangle\fP rotation angle in degrees counter-clockwise, default is 90 (landscape format) \fB\-cen\fR \fIx\fP \fIy\fP image center on page, measured in inches from upper left, default is 4.25 5.5 .fi .PP Examples: .br .TP .7i \fBprintpic foo.pic\fP print image vertically on page on default printer. .TP \fBprintpic foo.pic -cen 4.25 3 -rot 0 -size 6 -label "8-bit halftone of foo.pic" -P gordo\fP print image horizontally near top of page, 6 inches wide, with label, on printer "gordo". .TP \fBprintpic foo.pic -dither .5 -bits 2 -chan r -p temp.ps\fP write postscript output for 2 bits of dithered red channel to \fItemp.ps\fP .SH NOTE Takes 10 to 15 minutes for a 512x488x8 image because of 9600 baud link. The laser writer prints only 5 bits of gray level max, even if you request 8. .SH SEE ALSO lpr(1) .SH AUTHOR Paul Heckbert, PIXAR EOF20337 cat <<EOF20337 >printpic.c /* * PRINTPIC: convert pic file to Postscript and print b&w halftone * on Laser Writer * * Paul Heckbert, PIXAR 25 April 86, 18 April 87 * UUCP: {sun,ucbvax}!pixar!ph * ARPA: ph%pixar.uucp@ucbvax.berkeley.edu * please mail me any major enhancements you make * * notes: * Postscript "image" command wants each scan line to start on * byte boundary */ #include <math.h> #include <stdio.h> #include <assert.h> #include <pic.h> #define STREQ(a, b) (strcmp(a, b)==0) #define MAX(a, b) ((a)>(b) ? (a) : (b)) /* the following are system-dependent */ #define RANDOMRANGE2 (1<<30) /* random() has range 0 to 2*RANDOMRANGE2-1 */ #define PVBITS 8 #define PVMAX 255 #define POINTSIZE 10 #define FONT "Helvetica-Oblique" #define PAGEWID 8.5 #define PAGEHEI 11. #define POINT(x) (int)((x)*72.) /* convert inches to points */ #define UNINIT -666 static int wx1 = UNINIT, wx2, wy1, wy2; static char *dev = 0, *outfile = 0, *label = 0; static double ar = 1., size = 9., rot = 90., gam = 1.; static double dither = 0.; /* in nbits intensity space */ static int ditheramp; /* in PVBITS intensity space */ static double cenx = PAGEWID/2., ceny = PAGEHEI/2.; static double cr = .30, cg = .59, cb = .11, ca = 0.; /* luminance coeffs */ static int nbits = 8, negate = 0; static char Usage[] = "\ printpic [options] picfile\n\ Make halftone print of pic file on Laser Writer\n\ Options:\n\ -P <printer> print on named Laser Writer, default is $PRINTER\n\ -p <file> output postscript to named file rather than spooling,\n\ file name '-' means stdout\n\ -label <string> label string printed below image\n\ -w <xmin> <xmax> <ymin> <ymax> window to read\n\ -ar <ratio> pixel aspect ratio, default is 1.\n\ -chan {rgba} channel to print, default is luminance\n\ -bits <nbits> number of bits per pixel to send, default is 8\n\ -gamma <gamma> gamma, default is 1\n\ -negate negate image\n\ -dither <amp> dither amplitude, default is 0\n\ -size <size> image size on page in inches, default is 9\n\ -rot <ang> rotation in degrees ccw, default is 90 (landscape)\n\ -center <x> <y> image center on page, inches from upper left, default 4.25 5.5\n\ "; static usage() { fprintf(stderr, Usage); exit(1); } static badarg(opt) char *opt; { fprintf(stderr, "insufficient arguments to %s\n", opt); exit(1); } main(ac, av) int ac; char **av; { char *picfile; int i, filecount; filecount = 0; for (i=1; i<ac; i++) { if (av[i][0]=='-') { if (STREQ(av[i], "-P")) { if (i+1>=ac) badarg(av[i]); dev = av[++i]; } else if (STREQ(av[i], "-p")) { if (i+1>=ac) badarg(av[i]); outfile = av[++i]; } else if (STREQ(av[i], "-label")) { if (i+1>=ac) badarg(av[i]); label = av[++i]; } else if (STREQ(av[i], "-w")) { if (i+4>=ac) badarg(av[i]); wx1 = atoi(av[++i]); wx2 = atoi(av[++i]); wy1 = atoi(av[++i]); wy2 = atoi(av[++i]); } else if (STREQ(av[i], "-ar")) { if (i+1>=ac) badarg(av[i]); ar = atof(av[++i]); } else if (STREQ(av[i], "-chan")) { if (i+1>=ac) badarg(av[i]); switch (av[++i][0]) { case 'r': cr = 1.; cg = 0.; cb = 0.; ca = 0.; break; case 'g': cr = 0.; cg = 1.; cb = 0.; ca = 0.; break; case 'b': cr = 0.; cg = 0.; cb = 1.; ca = 0.; break; case 'a': cr = 0.; cg = 0.; cb = 0.; ca = 1.; break; default : fprintf(stderr, "bad channel: %c\n", av[i][0]); exit(1); } } else if (STREQ(av[i], "-bits")) { if (i+1>=ac) badarg(av[i]); nbits = atoi(av[++i]); } else if (STREQ(av[i], "-gamma")) { if (i+1>=ac) badarg(av[i]); gam = atof(av[++i]); } else if (STREQ(av[i], "-negate")) { negate = 1; } else if (STREQ(av[i], "-dither")) { if (i+1>=ac) badarg(av[i]); dither = atof(av[++i]); } else if (STREQ(av[i], "-size")) { if (i+1>=ac) badarg(av[i]); size = atof(av[++i]); } else if (STREQ(av[i], "-rot")) { if (i+1>=ac) badarg(av[i]); rot = atof(av[++i]); } else if (STREQ(av[i], "-cen")) { if (i+2>=ac) badarg(av[i]); cenx = atof(av[++i]); ceny = atof(av[++i]); } else usage(); } else if (++filecount==1) picfile = av[i]; else usage(); } printpic(picfile); } printpic(file) char *file; { char command[40]; FILE *ofp; pic *p; if (nbits!=1 && nbits!=2 && nbits!=4 && nbits!=8) { fprintf(stderr, "nbits must be power of two\n"); exit(1); } /* * strange printer behavior: if you ask for 8 bits you get only 5. * dither accordingly */ ditheramp = dither*(1 << PVBITS-(nbits==8 ? 5 : nbits)); # ifdef DEBUG fprintf(stderr, "ar=%g size=%g rot=%g cen=(%g,%g) nbits=%d file=%s\n", ar, size, rot, cenx, ceny, nbits, file); fprintf(stderr, "gam=%g negate=%d win={%d,%d,%d,%d}\n", gam, negate, wx1, wx2, wy1, wy2); fprintf(stderr, "dither=%g ditheramp=%d\n", dither, ditheramp); fprintf(stderr, "coeffs=(%g,%g,%g,%g)\n", cr, cg, cb, ca); # endif p = pic_open(file, "r"); if (p==NULL) { fprintf(stderr, "nonexistent or bad picture file: %s\n", file); exit(1); } if (outfile==0) { /* spool to printer */ if (dev) sprintf(command, "lpr -P%s", dev); else strcpy(command, "lpr"); ofp = popen(command, "w"); if (ofp==NULL) { fprintf(stderr, "croak: can't run 'lpr' program\n"); exit(1); } } else { /* create file */ if (STREQ(outfile, "-")) ofp = stdout; else ofp = fopen(outfile, "w"); if (ofp==NULL) { fprintf(stderr, "can't create %s\n", outfile); exit(1); } } pic_to_ps(p, ofp); if (outfile==0) pclose(ofp); else if (ofp!=stdout) fclose(ofp); } /* pic_to_ps: read picture file p, write postscript to stream ofp */ pic_to_ps(p, ofp) pic *p; FILE *ofp; { int tx, ty, dx, dy, x, y, i, gray, table[PVMAX+1]; pic_pixel *line, *lp; pic_read_size(p, &tx, &ty); if (wx1==UNINIT) { wx1 = 0; wx2 = tx-1; wy1 = 0; wy2 = ty-1; } else if (wx1<0 || wx2>=tx || wy1<0 || wy2>=ty) { fprintf(stderr, "window larger than image\n"); exit(1); } dx = wx2-wx1+1; /* window size */ dy = wy2-wy1+1; fprintf(stderr, "%dx%dx%d-bit\n", dx, dy, nbits); for (i=0; i<=PVMAX; i++) { /* initialize intensity table */ gray = negate ? PVMAX-i : i; table[i] = PVMAX.*pow(gray/PVMAX., gam) + .5; } ps_head(ofp); ps_image_head(ofp, dx, dy); /* * decode picture file, ignoring lines before wy1 and skipping those * after wy2 */ assert(line = (pic_pixel *)malloc(tx*sizeof(pic_pixel))); for (y=wy1; y<=wy2; y++) { pic_read_line(p, y, line); for (lp=line+wx1, x=wx1; x<=wx2; x++, lp++) { gray = cr*lp->r + cg*lp->g + cb*lp->b + ca*lp->a; if (gray<0) gray = 0; else if (gray>PVMAX) gray = PVMAX; gray = table[gray]; /* intensity mapping */ if (ditheramp) { /* perturb gray by a random number between -amp and amp */ gray += random()/(RANDOMRANGE2/ditheramp) - ditheramp; if (gray<0) gray = 0; else if (gray>PVMAX) gray = PVMAX; } gray >>= PVBITS-nbits; /* quantize */ ps_pixel(ofp, gray, nbits); } ps_pixelflush(ofp, nbits); } pic_close(p); ps_tail(ofp); } /*-------------------- postscript nitty-gritty --------------------*/ ps_head(ofp) FILE *ofp; { char host[20]; int curtime; gethostname(host, sizeof host); curtime = time(0); fprintf(ofp, "%%!PS-Adobe-1.0\n"); fprintf(ofp, "%%%%Creator: %s:%s\n", host, getlogin()); fprintf(ofp, "%%%%CreationDate: %s", ctime(&curtime)); fprintf(ofp, "%%%%EndProlog\n"); fprintf(ofp, "%%%%Page: ? 1\n\n"); } ps_image_head(ofp, dx, dy) FILE *ofp; int dx, dy; { int strlen; double max, wid, hei; strlen = (dx*nbits+7)/8; /* this must be right or no output! */ fprintf(ofp, "/picstr %d string def\n", strlen); max = MAX(dx*ar, dy); wid = size*dx*ar/max; hei = size*dy/max; fprintf(ofp, "%d %d translate %g rotate %d %d translate\n", POINT(cenx), POINT(PAGEHEI-ceny), rot, POINT(-wid/2.), POINT(-hei/2.)); if (label) { fprintf(ofp, "/%s findfont %d scalefont setfont\n", FONT, POINTSIZE); ps_quote_string(ofp, label); fprintf(ofp, " 0 %g moveto show\n", -1.5*POINTSIZE); } fprintf(ofp, "%d %d scale\n\n", POINT(wid), POINT(hei)); fprintf(ofp, "%d %d %d [%d 0 0 %d 0 %d]\n", dx, dy, nbits, dx, -dy, dy); fprintf(ofp, "{currentfile picstr readhexstring pop}\n"); fprintf(ofp, "image\n"); } ps_tail(ofp) FILE *ofp; { fprintf(ofp, "\nshowpage\n"); } /* ps_quote_string: print string str to stream ofp, quoted for postscript */ ps_quote_string(ofp, str) FILE *ofp; char *str; { putc('(', ofp); for (; *str; str++) { switch (*str) { case '(': case ')': case '\\': putc('\\', ofp); break; } putc(*str, ofp); } putc(')', ofp); } static int accum = 0, count = 0, col = 0; /* ps_pixelflush: pad to finish this byte */ ps_pixelflush(ofp, n) FILE *ofp; int n; { while (count!=0) ps_pixel(ofp, 0, n); if (col!=0) { fprintf(ofp, "\n"); col = 0; } } /* * ps_pixel: accumulate n bit data, where n = 1, 2, 4, or 8, * outputting 2 hex digits for each byte */ ps_pixel(ofp, data, n) FILE *ofp; int data, n; { accum = accum<<n | data; count += n; if (count>=8) { fprintf(ofp, "%02x", accum); accum = 0; count = 0; col += 2; if (col>=72) { fprintf(ofp, "\n"); col = 0; } } } EOF20337 exit