Higher-order operations
In addition to abstracting patterns of combining painters, we can work at a higher level, abstracting
patterns of combining painter operations. That is, we can view the painter operations as elements to
manipulate and can write means of combination for these elements -- procedures that take painter
operations as arguments and create new painter operations.
For example,
flipped-pairs
and
square-limit
each arrange four copies of a painter’s image
in a square pattern; they differ only in how they orient the copies. One way to abstract this pattern of
painter combination is with the following procedure, which takes four one-argument painter operations
and produces a painter operation that transforms a given painter with those four operations and
arranges the results in a square.
Tl
,
tr
,
bl
, and
br
are the transformations to apply to the top left
copy, the top right copy, the bottom left copy, and the bottom right copy, respectively.
(define (square-of-four tl tr bl br)
(lambda (painter)
(let ((top (beside (tl painter) (tr painter)))
(bottom (beside (bl painter) (br painter))))
(below bottom top))))
Then
flipped-pairs
can be defined in terms of
square-of-four
as follows:
24
(define (flipped-pairs painter)
(let ((combine4 (square-of-four identity flip-vert
identity flip-vert)))
(combine4 painter)))
and
square-limit
can be expressed as
25
(define (square-limit painter n)
(let ((combine4 (square-of-four flip-horiz identity
rotate180 flip-vert)))
(combine4 (corner-split painter n))))
Exercise 2.45.
Right-split
and
up-split
can be expressed as instances of a general splitting
operation. Define a procedure
split
with the property that evaluating
(define right-split (split beside below))
(define up-split (split below beside))
produces procedures
right-split
and
up-split
with the same behaviors as the ones already
defined.
Frames
Before we can show how to implement painters and their means of combination, we must first
consider frames. A frame can be described by three vectors -- an origin vector and two edge vectors.
The origin vector specifies the offset of the frame’s origin from some absolute origin in the plane, and
the edge vectors specify the offsets of the frame’s corners from its origin. If the edges are
perpendicular, the frame will be rectangular. Otherwise the frame will be a more general
parallelogram.
Figure 2.15 shows a frame and its associated vectors. In accordance with data abstraction, we need not
be specific yet about how frames are represented, other than to say that there is a constructor
make-frame
, which takes three vectors and produces a frame, and three corresponding selectors
origin-frame
,
edge1-frame
, and
edge2-frame
(see exercise 2.47).
Figure 2.15: A frame is described by three vectors -- an origin and two edges.
Figure 2.15: A frame is described by three vectors -- an origin and two edges.
We will use coordinates in the unit square (0< x,y< 1) to specify images. With each frame, we
associate a frame coordinate map, which will be used to shift and scale images to fit the frame. The
map transforms the unit square into the frame by mapping the vector v = (x,y) to the vector sum
For example, (0,0) is mapped to the origin of the frame, (1,1) to the vertex diagonally opposite the
origin, and (0.5,0.5) to the center of the frame. We can create a frame’s coordinate map with the
following procedure:
26
(define (frame-coord-map frame)
(lambda (v)
(add-vect
(origin-frame frame)
(add-vect (scale-vect (xcor-vect v)
(edge1-frame frame))
(scale-vect (ycor-vect v)
(edge2-frame frame))))))
Observe that applying
frame-coord-map
to a frame returns a procedure that, given a vector,
returns a vector. If the argument vector is in the unit square, the result vector will be in the frame. For
example,
((frame-coord-map a-frame) (make-vect 0 0))
returns the same vector as
(origin-frame a-frame)
Exercise 2.46. A two-dimensional vector
v running from the origin to a point can be represented as a
pair consisting of an x-coordinate and a y-coordinate. Implement a data abstraction for vectors by
giving a constructor
make-vect
and corresponding selectors
xcor-vect
and
ycor-vect
. In
terms of your selectors and constructor, implement procedures
add-vect
,
sub-vect
, and
scale-vect
that perform the operations vector addition, vector subtraction, and multiplying a
vector by a scalar:
Exercise 2.47. Here are two possible constructors for frames:
(define (make-frame origin edge1 edge2)
(list origin edge1 edge2))
(define (make-frame origin edge1 edge2)
(cons origin (cons edge1 edge2)))
For each constructor supply the appropriate selectors to produce an implementation for frames.
Painters
A painter is represented as a procedure that, given a frame as argument, draws a particular image
shifted and scaled to fit the frame. That is to say, if
p
is a painter and
f
is a frame, then we produce
p
’s
image in
f
by calling
p
with
f
as argument.
The details of how primitive painters are implemented depend on the particular characteristics of the
graphics system and the type of image to be drawn. For instance, suppose we have a procedure
draw-line
that draws a line on the screen between two specified points. Then we can create
painters for line drawings, such as the
wave
painter in figure 2.10, from lists of line segments as
follows:
27
(define (segments->painter segment-list)
(lambda (frame)
(for-each
(lambda (segment)
(draw-line
((frame-coord-map frame) (start-segment segment))
((frame-coord-map frame) (end-segment segment))))
segment-list)))
The segments are given using coordinates with respect to the unit square. For each segment in the list,
the painter transforms the segment endpoints with the frame coordinate map and draws a line between
the transformed points.