<< . .

. 18
( : 45)



. . >>

Once again, we can learn best by computer experiments with the expr ¬le
(cf. Chapter 8); this time the idea is to play with transforms:

You type And the result is
identity (0,0,1,0,0,1)
identity shifted (a,b) (a,b,1,0,0,1)
identity scaled s (0,0,s,0,0,s)
identity xscaled s (0,0,s,0,0,1)
identity yscaled s (0,0,1,0,0,s)
identity slanted s (0,0,1,s,0,1)
identity rotated 90 (0,0,0,-1,1,0)
identity rotated 30 (0,0,0.86603,-0.5,0.5,0.86603)
identity rotatedaround ((2,3),90) (5,1,0,-1,1,0)
(x,y) rotatedaround ((2,3),90) (-y+5,x+1)
(x,y) reflectedabout ((0,0),(0,1)) (-x,y)
(x,y) reflectedabout ((0,0),(1,1)) (y,x)
(x,y) reflectedabout ((5,0),(0,10)) (-0.8y-0.6x+8,0.6y-0.8x+4)

EXERCISE 15.1
Guess the result of ˜(x,y) reflectedabout ((0,0),(1,0))™.

EXERCISE 15.2
What transform takes (x, y) into (’x, ’y)?

EXERCISE 15.3
(’(x, y)) transformed t = ’((x, y) transformed t).
True or false:
Chapter 15: Transformations 143


In order to have some transform variables to work with, it™s necessary to ˜hide™ hide
inverse
some declarations and commands before giving the next exprs:
known
unknown
You type And the result is left-handed dangerous bend
hide(transform t[]) t1 (xpart t1,ypart t1,xxpart...)
hide(t1=identity zscaled(1,2)) t1 (0,0,1,-2,2,1)
hide(t2=t1 shifted (1,2)) t2 (1,2,1,-2,2,1)
t2 xscaled s (s,2,s,-2s,2,1)
unknown t2 false
transform t2 true
t1=t2 false
t1<t2 true
inverse t2 (-1,0,0.2,0.4,-0.4,0.2)
inverse t2 transformed t2 (0,0,0.99998,0,0,0.99998)
hide(t3 transformed t2=identity) t3 (-1,0,0.2,0.4,-0.4,0.2)
The inverse function ¬nds the transform that undoes the work of another; the equation
that de¬nes t3 above shows how to calculate an inverse indirectly, without using inverse .
Like numeric expressions and pair expressions, transform expressions can be
either “known” or “unknown” at any given point in a program. (If any
component of a transform is unknown, the whole transform is regarded as unknown.)
You are always allowed to use the constructions
known transformed known
unknown transformed known
known transformed unknown
but will balk at ˜ unknown transformed unknown ™. This is not the most
lenient rule that could have been implemented, but it does have the virtue of being
easily remembered.
EXERCISE 15.4
If z1 and z2 are unknown pairs, you can™t say ˜z1 shifted z2 ™, because ˜shifted z2 ™
is an unknown transform. What can you legally say instead?
EXERCISE 15.5
Suppose dbend is a picture variable that contains a normal dangerous bend
sign, as in the “reverse-video” example of Chapter 13. Explain how to transform it
into the left-handed dangerous bend that heads this paragraph.
The next three lines illustrate the fact that you can specify a transform com-
pletely by specifying the images of three points:
You type And the result is
hide((0,0)transformed t4=(1,2)) t4 (1,2,xxpart t4,xypart t4,...)
hide((1,0)transformed t4=(4,5)) t4 (1,2,3,xypart t4,3,yypart t4)
hide((1,4)transformed t4=(0,0)) t4 (1,2,3,-1,3,-1.25)
The points at which the transform is given shouldn™t all lie on a straight line.
144 Chapter 15: Transformations


Now let™s use transformation to make a little ornament, based on a ˜ ™ shape ornament
rotatedaround
replicated four times:
addto
picture




(Figure 15a will be inserted here; too bad you can™t see it now.)




The following program merits careful study:

beginchar ("4", 11pt #, 11pt #, 0);
1
pickup pencircle scaled 3/4pt yscaled 1/3 rotated 30;
2
transform t;
3
t = identity rotatedaround((.5w, .5h), ’90);
4
x2 = .35w; x3 = .6w;
5
y2 = .1h; top y3 = .4h;
6
path p; p = z2 {right } . . . {up }z3 ;
7
top z1 = point .5 of p transformed t;
8
draw z1 . . . p;
9
addto currentpicture also currentpicture transformed t;
10
addto currentpicture also currentpicture transformed (t transformed t);
11
labels(1, 2, 3); endchar;
12

Lines 3 and 4 compute the transform that moves each ˜ ™ to its clockwise neighbor.
Lines 5“7 compute the right half of the ˜ ™. Line 8 is the most interesting: It puts
point z1 on the rotated path. Line 9 draws the ˜ ™, line 10 changes it into two, and
line 11 changes two into four. The parentheses on line 11 could have been omitted, but
it is much faster to transform a transform than to transform a picture.

will transform a picture expression only when txx , txy , tyx , and tyy
are integers and either txy = tyx = 0 or txx = tyy = 0; furthermore, the values
of tx and ty are rounded to the nearest integers. Otherwise the transformation would
not take pixel boundaries into pixel boundaries.

EXERCISE 15.6
Explain how to rotate the ornament by 45—¦ .
Chapter 15: Transformations 145


Plain maintains a special variable called currenttransform , currenttransform
¬ll
behind the scenes. Every ¬ll and draw command is a¬ected by this variable; draw
for example, the statement ˜¬ll p™ actually ¬lls the interior of the path nonsquare pixels
aspect ratio
p transformed currenttransform pickup
DICKENS
TWAIN
instead of p itself. We haven™t mentioned this before, because currenttransform
is usually equal to identity ; but nonstandard settings of currenttransform can be
used for special e¬ects that are occasionally desired. For example, it™s possible
to change ˜ ™ to ˜ ™ by simply saying
currenttransform := identity slanted 1/4
and executing the programs of logo.mf that are described in Chapter 11; no
other changes to those programs are necessary.
It™s worth noting that the pen nib used to draw ˜ ™ was not
slanted when currenttransform was changed; only the “tracks” of the pen, the
paths in draw commands, were modi¬ed. Thus the slanted image was not simply
obtained by slanting the unslanted image.
When fonts are being made for devices with nonsquare pixels, plain -
will set currenttransform to ˜identity yscaled aspect ratio ™, and pickup
will similarly yscale the pen nibs that are used for drawing. In this case the slanted
˜ ™ letters should be drawn with
currenttransform := identity slanted 1/4 yscaled aspect ratio .
EXERCISE 15.7
Our program for ˜ ™ doesn™t work when pixels aren™t square. Fix it so that
it handles a general aspect ratio .




Change begets change. Nothing propagates so fast.
” CHARLES DICKENS, Martin Chuzzlewit (1843)

There are some that never know how to change.
” MARK TWAIN, Joan of Arc (1896)
(page 146)




16
Calligraphic
E¬ects
Chapter 16: Calligraphic E¬ects 147


Pens were introduced in Chapter 4, and we ought to make a systematic study of Pens
pen
what can do with them before we spill any more ink. The purpose pickup
of this chapter will be to explore the uses of “¬xed” pen nibs”i.e., variables pen expression
currentpen
and expressions of type pen”rather than to consider the creation of shapes by top
means of outlines or penstrokes. bot
lft
When you say ˜pickup pen expression ™, the macros of plain - rt
do several things for you: They create a representation of the speci¬ed draw
drawdot
pen nib, and assign it to a pen variable called currentpen ; then they store away ¬lldraw
information about the top, bottom, left, and right extents of that pen, for use savepen
clear pen memory
in top , bot , lft , and rt operations. A draw or drawdot or ¬lldraw command penrazor
will make use of currentpen to modify the current picture. pensquare
convex polygon
You can also say ˜pickup numeric expression ™; in this case the nu- makepen
meric expression designates the code number of a previously picked-up pen turning number
that was saved by ˜savepen™. For example, the logo.mf ¬le in Chapter 11
begins by picking up the pen that™s used to draw ˜ ™, then it says
˜logo pen := savepen™. Every character program later in that ¬le begins with
the command ˜pickup logo pen ™, which is a fast operation because it doesn™t
require the generation of a new pen representation inside the computer.
Caution: Every time you use savepen, it produces a new integer value and
stashes away another pen for later use. If you keep doing this, ™s
memory will become cluttered with the representations of pens that you may never
need again. The command ˜clear pen memory™ discards all previously saved pens
and lets start afresh.

But what is a pen expression ? Good question. So far in this book, almost
everything that we™ve picked up was a pencircle followed by some sequence of
transformations; for example, the logo pen of Chapter 11 was ˜pencircle xscaled px
yscaled py ™. Chapter 13 also made brief mention of another kind of pen, when it said

pickup penrazor scaled 10;

this command picks up an in¬nitely thin pen that runs from point (’5, 0) to point
(5, 0) with respect to its center. Later in this chapter we shall make use of pens like

pensquare xscaled 30 yscaled 3 rotated 30;

this pen has a rectangular boundary measuring 30 pixels — 3 pixels, inclined at an
angle of 30—¦ to the baseline.

You can de¬ne pens of any convex polygonal shape by saying ˜makepen p™,
where p is a cyclic path. It turns out that looks only at the key
points of p, not the control points, so we may as well assume that p has the form
z0 - - z1 - - etc. - - cycle. This path must have the property that it turns left at every
key point (i.e., zk+1 must lie to the left of the line from zk’1 to zk , for all k), unless the
cycle contains fewer than three key points; furthermore the path must have a turning
number of 1 (i.e., it must not make more than one counterclockwise loop). Plain -
™s penrazor stands for ˜makepen ((’.5, 0) - - (.5, 0) - - cycle)™, and pensquare
is an abbreviation for ˜makepen (unitsquare shifted ’(.5, .5))™. But pencircle is not
148 Chapter 16: Calligraphic E¬ects


de¬ned via makepen; it is a primitive operation of . It represents a true circle
circle of diameter 1, passing through the points (±.5, 0) and (0, ±.5). pen primary
(
)
The complete syntax for pen expressions is rather short, because you can™t nullpen
really do all that much with pens. But it also contains a surprise: future pen primary
pencircle
pen primary ’’ pen variable makepen
pen secondary
| ( pen expression ) future pen secondary
| nullpen pen tertiary
pen expression
future pen primary ’’ pencircle nullpen
| makepen path primary ¬lldraw
pen secondary ’’ pen primary ¬ll
beginchar
future pen secondary ’’ future pen primary future pen
| future pen secondary transformer diamond-shaped nib
| pen secondary transformer
pen tertiary ’’ pen secondary
| future pen secondary
pen expression ’’ pen tertiary

The constant ˜nullpen™ is just the single point (0, 0), which is invisible”unless you
use it in ¬lldraw, which then reduces to ¬ll. (A beginchar command initializes
currentpen to nullpen, in order to reduce potentially dangerous dependencies between
the programs for di¬erent characters.) The surprise in these rules is the notion of a
“future pen,” which stands for a path or an ellipse that has not yet been converted into
™s internal representation of a true pen. The conversion process is rather
complicated, so procrastinates until being sure that no more transforma-
tions are going to be made. A true pen is formed at the tertiary level, when future
pens are no longer permitted in the syntax.

The distinction between pens and future pens would make no di¬erence to a
user, except for another surprising fact: All of ™s pens are convex
polygons, even the pens that are made from pencircle and its variants! Thus, for
example, the pen you get from an untransformed pencircle is identical to the pen you
get by specifying the diamond-shaped nib

makepen ((.5, 0) - - (0, .5) - - (’.5, 0) - - (0, ’.5) - - cycle).

And the pens you get from ˜pencircle scaled 20™ and ˜pencircle xscaled 30 yscaled 20™
are polygons with 32 and 40 sides, respectively:




(Figure 16a&b will be inserted here; too bad you can™t see it now.)
Chapter 16: Calligraphic E¬ects 149


The vertices of the polygons, shown as heavy dots in this illustration, all have “half- digitization
Hobby
integer” coordinates; i.e., each coordinate is either an integer or an integer plus 1/2.
Every polygon that comes from a pencircle is symmetric under 180—¦ rotation; further-
more, there will be re¬‚ective left/right and top/bottom symmetry if the future pen is
a circle, or if it™s an ellipse that has not been rotated.

This conversion to polygons explains why future pens must, in general, be
distinguished from ordinary ones. For example, the extra parentheses in
˜(pencircle xscaled 30) yscaled 20™ will yield a result quite di¬erent from the elliptical
polygon just illustrated. The parentheses force conversion of ˜pencircle xscaled 30™
from future pen to pen, and this polygon turns out to be

(12.5, ’0.5) - - (15, 0) - - (12.5, 0.5)
- - (’12.5, 0.5) - - (’15, 0) - - (’12.5, ’0.5) - - cycle,

an approximation to a 30 — 1 ellipse. Then yscaling by 20 yields




(Figure 16c will be inserted here; too bad you can™t see it now.)




Why does work with polygonal approximations to circles, instead
of true circles? That™s another good question. The main reason is that suitably
chosen polygons give better results than the real thing, when digitization is taken into
account. For example, suppose we want to draw a straight line of slope 1/2 that™s
exactly one pixel thick, from (0, y) to (200, y + 100). The image of a perfectly circular
along this line has outlines that run from (0, y ± ±) to
pen of diameter 1 that travels √
(200, y + 100 ± ±), where ± = 5/4 ≈ 0.559. If we digitize these outlines and ¬ll the
region between them, we ¬nd that for some values of y (e.g., y = 0.1) the result is
...
a repeating pixel pattern like ˜ . . . ™; but for other values of y (e.g., y = 0.3)
...
the repeating pattern of pixels is 50 percent darker: ˜ . . . ™. Similarly, some
diagonal lines of slope 1 digitize to be twice as dark as others, when a truly circular
pen is considered. But the diamond-shaped nib that uses for a pencircle
of diameter 1 does not have this defect; all straight lines of the same slope will digitize
to lines of uniform darkness. Moreover, curved lines drawn with the diamond nib
always yield one pixel per column when they move more-or-less horizontally (with slopes
between +1 and ’1), and they always yield one pixel per row when they move vertically.
By contrast, the outlines of curves drawn with circular pens produce occasional “blots.”
Circles and ellipses of all diameters can pro¬tably be replaced by polygons whose sub-
pixel corrections to the ideal shape will produce better digitizations; does
this in accordance with the interesting theory developed by John D. Hobby in his Ph.D.
dissertation (Stanford University, 1985).
150 Chapter 16: Calligraphic E¬ects


It™s much easier to compute the outlines of a polygonal pen that follows a peno¬set
makepath
given curve than to ¬gure out the corresponding outlines of a truly circular
¬llin
pen; thus polygons win over circles with respect to both quality and speed. When a penrazor
curve is traveling in a direction between the edge vectors zk+1 ’ zk and zk ’ zk’1 of a endpoints
drawdot
polygonal pen, the curve™s outline will be o¬set from its center by zk . If you want ¬ne draw
control over this curve-drawing process, provides the primitive operation cuto¬
currentpen
˜peno¬set w of p™, where w is a vector and p is a pen. If w = (0, 0), the result is (0, 0);
if the direction of w lies strictly between zk+1 ’ zk and zk ’ zk’1 , the result is zk ; and
if w has the same direction as zk+1 ’ zk for some k, the result is either zk or zk+1 ,
whichever ¬nds most convenient to compute.
EXERCISE 16.1
Explain how to use peno¬set to ¬nd the point or points at the “top” of a pen
(i.e., the point or points with largest y coordinate).
The primitive operation ˜makepath p™, where p is a (polygonal) pen whose
vertices are z0 , z1 , . . . , zn’1 , produces the path ˜z0 . . controls z0 and z1 . . z1 . .
etc. . . zn’1 . . controls zn’1 and z0 . . cycle™, which is one of the paths that might have
generated p. This gives access to all the o¬sets of a pen.
When a pencircle is transformed by any of the operations in Chapter 15, it
changes into an ellipse of some sort, since all of ™s transformations
preserve ellipse-hood. The diameter of the ellipse in each direction θ is decreased by
2 min(| sin θ|, | cos θ|) times the current value of ¬llin , before converting to a polygon;
this helps to compensate for the variation in thickness of diagonal strokes with respect
uses ¬llin
to horizontal or vertical strokes, on certain output devices. (
only when creating polygons from ellipses, but users can of course refer to ¬llin within
their own routines for drawing strokes.) The ¬nal polygon will never be perfectly ¬‚at
like penrazor, even if you say ˜xscaled 0™ and/or ˜yscaled 0™; its center will always be
surrounded at least by the basic diamond nib that corresponds to a circle of diameter 1.
EXERCISE 16.2
Run on the expr ¬le of Chapter 8 and look at what is typed
when you ask for ˜pencircle™ and ˜pencircle scaled 1.1™. (The ¬rst will exhibit the
diamond nib, while the second will show a polygon that™s equivalent to pensquare.)
Continue experimenting until you ¬nd the “threshold” diameter where
decides to switch between these two polygons.
™s polygonal pens work well for drawing lines and curves, but this
pleasant fact has an unpleasant corollary: They do not always digitize well
at the endpoints, where curves start and stop. The reason for this is explored further
in Chapter 24; polygon vertices that give nice uniform stroke widths might also be
“ambiguous” points that cause di¬culties when we consider rounding to the raster.
Therefore a special drawdot routine is provided for drawing one-point paths. It is
sometimes advantageous to apply drawdot to the ¬rst and last points of a path p,
after having said ˜draw p™; this can fatten up the endpoints slightly, making them look
more consistent with each other.
Plain also provides two routines that can be used to clean up
endpoints in a di¬erent way: The command ˜cuto¬ (z, θ)™ removes half of the
currentpen image at point z, namely all points of the pen that lie in directions between
Chapter 16: Calligraphic E¬ects 151


(θ ’ 90)—¦ and (θ + 90)—¦ from the center point. And the command ˜cutdraw p™ is an cutdraw
T
abbreviation for the following three commands:
addto
cull
draw p; cuto¬ (point 0 of p, 180 + angle direction 0 of p); picture
cuto¬ (point in¬nity of p, angle direction in¬nity of p). doublepath
pen lft
lft
The e¬ect is to draw a curve whose ends are clipped perpendicular to the starting and
rt
ending directions. For example, the command top
bot
cutdraw z4 . . controls z1 and z2 . . z6

produces the following curve, which invites comparison with the corresponding uncut

<< . .

. 18
( : 45)



. . >>