\datethis
@* Introduction. This program prepares data for the examples in my
paper ``Fonts for digital halftones.'' The input file (|stdin|) is
assumed to be an EPS file output by Adobe Photoshop$^{\rm TM}$
on a Macintosh with the binary EPS option, having a resolution
of 72 pixels per inch.
This file either has $m$ rows of $n$ columns each,
or $m+n-1$ rows of $m+n-1$ columns each,
or $2m$ rows of $2n$ columns each; in the second case the image
has been rotated $45^\circ$ clockwise. (These images were
obtained by starting with a given $km\times kn$ image, optionally rotating
it $45^\circ$, and then using Photoshop's Image Size operation to reduce
to the desired number of pixel units. In my experiments I took $k=8$,
so that I could also use the dot diffusion method;
but $k$ need not be an integer. Larger values of $k$ tend to make
the reduced images more accurate than smaller values do.)
The output file (|stdout|) is a sequence of ASCII characters that
can be placed into \TeX\ files leading
to typeset output images of size $8m\times8n$,
using fonts like those described in the paper.
In the first case, we output $m$ lines of 65-level pixel data.
In the second (rotated) case, we output $2m$ lines of 33-level pixel data.
In the third case, we output $2m$ lines of 17-level pixel data.
@d m 64 /* base number of rows */
@d n 55 /* base number of columns */
@d r 64 /* $\max(m,n)$ */
@p
#include
float a[m+m+2][n+r]; /* darknesses: 0.0 is white, 1.0 is black */
@;
main(argc,argv)
int argc; char* argv[];
{
register int i,j,k,l,p;
int levels,trash,ii,jj;
float dampening=1.0, brightness=1.0;
@;
@;
fprintf(stderr,"Making %d lines of %d-level data\n",
(levels<65? m+m: m), levels);
printf("\\begin%shalftone\n", levels==33? "alt": "");
@;
@;
}
@ Optional command-line arguments allow the user to multiply the diffusion
constants by a |dampening| factor and/or to multiply the
brightness by a |brightness| factor.
@=
if (argc>1 && sscanf(argv[1],"%g",&dampening)==1) {
fprintf(stderr,"Using dampening factor %g\n", dampening);
if (argc>2 && sscanf(argv[2],"%g",&brightness)==1)
fprintf(stderr," and brightness factor %g\n", brightness);
}
@ Macintosh conventions indicate the end of a line by the
ASCII $\langle\,$carriage return$\,\rangle$ character
(i.e., control-M, aka \.{\char`\\r}), but the \CEE/ library is
set up to work best with newlines (i.e., control-J, aka \.{\char`\\n}).
We aren't worried about efficiency, so we simply input one character
at a time. This program assumes Macintosh conventions.
The job here is to look for the sequence \.{Box:} in the input,
followed by 0, 0, the number of columns, and the number of rows.
@d panic(s) {fprintf(stderr,s);@+exit(-1);}
@=
k=0;
scan: if (k++>1000) panic("Couldn't find the bounding box info!\n");
if (getchar()!='B') goto scan;
if (getchar()!='o') goto scan;
if (getchar()!='x') goto scan;
if (getchar()!=':') goto scan;
if (scanf("%d %d %d %d",&llx,&lly,&urx,&ury)!=4 || llx!=0 || lly!=0)
panic("Bad bounding box data!\n");
if (urx==n && ury==m) levels=65;
else if (urx==n+n && ury==m+m) levels=17;
else if (urx==m+n-1 && ury==urx) levels=33;
else panic("Bounding box doesn't match the formats I know!\n");
@ @=
int llx,lly,urx,ury; /* bounding box parameters */
@ After we've seen the bounding box, we look for \.{beginimage\char`\\r};
this will be followed by the pixel data, one character per byte.
@=
k=0;
skan: if (k++>10000) panic("Couldn't find the pixel data!\n");
if (getchar()!='b') goto skan;
if (getchar()!='e') goto skan;
if (getchar()!='g') goto skan;
if (getchar()!='i') goto skan;
if (getchar()!='n') goto skan;
if (getchar()!='i') goto skan;
if (getchar()!='m') goto skan;
if (getchar()!='a') goto skan;
if (getchar()!='g') goto skan;
if (getchar()!='e') goto skan;
if (getchar()!='\r') goto skan;
if (levels==33) @@;
else @;
if (getchar()!='\r') panic("Wrong amount of pixel data!\n");
@ Photoshop follows the conventions of photographers who consider
0~to be black and 1~to be white; but we follow the conventions of
computer scientists who tend to regard 0~as devoid of ink (white)
and 1~as full of ink (black).
We use the fact that global arrays are initially zero to assume that
there are all-white rows of 0s above and below the
input data in the rectangular case.
@=
for (i=1;i<=ury;i++) for (j=0;j=
{
for (i=0;i=0 && ii=0 && jj
@^Steinberg, Louis Ira@>
for adaptive grayscale~[{\sl Proceedings of the
Society for Information Display\/ \bf17} (1976), 75--77].
The idea is to find the best
available density value, then to diffuse the error into adjacent
pixels that haven't yet been processed.
Given a font with $k$ black dots in character~$k$ for $0\le k\le l$,
we might assume that the apparent density of the $k$th character
would be $k/l$.
But physical properties of output devices make the actual density
nonlinear. The following table is based on measurements
from observations on font \.{ddith300}
with a Canon {\mc LBP-CX} laserprinter,
and it should be accurate enough for practical purposes on similar
machines. But in fact
the measurements could not be terribly precise, because the readings were not
strictly monotone, and because the amount of toner was found to vary
between the top and bottom of a page. Users should make their
own measurements before adapting this routine to other equipment.
@=
float d[65]={0.000,0.060,0.114,0.162,0.205,0.243,0.276,0.306,0.332,0.355,@|
0.375,0.393,0.408,0.422,0.435,0.446,0.456,0.465,0.474,0.482,@|
0.490,0.498,0.505,0.512,0.520,0.527,0.535,0.543,0.551,0.559,@|
0.568,0.577,0.586,0.596,0.605,0.615,0.625,0.635,0.646,0.656,@|
0.667,0.677,0.688,0.699,0.710,0.720,0.731,0.742,0.753,0.764,@|
0.775,0.787,0.798,0.810,0.822,0.835,0.849,0.863,0.878,0.894,@|
0.912,0.931,0.952,0.975,1.000};
@ In the main loop, we will want to find the best approximation to
$a[i][j]$ from among the available densities $d[0]$, $d[p]$, $d[2p]$,
$d[3p]$,~\dots, where $p$ is 1, 2, or~4. A straightforward modification
of binary search works well for this purpose:
@=
if (a[i][j]<=0.0) l=0;
else if (a[i][j]>=1.0) l=64;
else {@+register int lo_l=0, hi_l=64;
while (hi_l-lo_l>p) {@+register int mid_l=(lo_l+hi_l)>>1;
/* |hi_l-lo_l| is halved each time, so |mid_l| is a multiple of~|p| */
if (a[i][j]>=d[mid_l]) lo_l=mid_l;
else hi_l=mid_l;
}
if (a[i][j]-d[lo_l]<=d[hi_l]-a[i][j]) l=lo_l;
else l=hi_l;
}
@ The rectangular case is simplest, so we consider it first. Our strategy
will be to go down each column, starting at the left, and to disperse the
error to the four unprocessed neighbors.
@d alpha 0.4375 /* 7/16, error diffusion to S neighbor */
@d beta 0.1875 /* 3/16, error diffusion to NE neighbor */
@d gamma 0.3125 /* 5/16, error diffusion to E neighbor */
@d delta 0.0625 /* 1/16, error diffusion to SE neighbor */
@=
{@+register float err;
if (i==0 || i>ury) l=0; /* must use white outside the output region */
else @;
err=a[i][j]-d[l];
a[i][j]=(float)(l/p); /* henceforth |a[i][j]| is a level not a density */
if (i<=ury) a[i+1][j]+=alpha*dampening*err;
if (j0) a[i-1][j+1]+=beta*dampening*err;
a[i][j+1]+=gamma*dampening*err;
if (i<=ury) a[i+1][j+1]+=delta*dampening*err;
}
}
@ The rotated case is essentially the same, but the unprocessed
neighbors of |a[i][j]| are now |a[i+1][j]|, |a[i][j+1]|, |a[i+1][j+1]|,
and~|a[i+2][j+1]|. (For example, the eight neighbors of $K$ in
the matrices of section~\matrixsec\ are $B$, $F$, $J$, $O$, $T$, $P$,
$L$,~$G$.)
Some of the computation in this step is redundant because the values
are known to be~zero.
@=
{@+register float err;
if ((i>>1)<=j-n || (i>>1)>j)
l=0; /* must use white outside the output region */
else @;
err=a[i][j]-d[l];
a[i][j]=(float)(l/p); /* henceforth |a[i][j]| is a level not a density */
if (i=
p=64/(levels-1);
if (p!=2) {
for (j=0;j;
for (i=1;i<=ury;i++) {
for (j=0;j;
for (i=0;i>1)]);
printf(".\n");
}
}
printf("\\endhalftone\n");
@* Index.