Representing painters as procedures erects a powerful abstraction barrier in the picture language. We
can create and intermix all sorts of primitive painters, based on a variety of graphics capabilities. The
details of their implementation do not matter. Any procedure can serve as a painter, provided that it
takes a frame as argument and draws something scaled to fit the frame.
28
Exercise 2.48. A directed line segment in the plane can be represented as a pair of vectors -- the
vector running from the origin to
the start-point of the segment, and the vector running from the origin
to the end-point of the segment. Use your vector representation from exercise 2.46 to define a
representation for segments with a constructor
make-segment
and selectors
start-segment
and
end-segment
.
Exercise 2.49. Use
segments->painter
to define the following primitive painters:
a. The painter that draws the outline of the designated frame.
b. The painter that draws an ‘‘X’’ by connecting opposite corners of the frame.
c. The painter that draws a diamond shape by connecting the midpoints of the sides of the frame.
d. The
wave
painter.
Transforming and combining painters
An operation on painters (such as
flip-vert
or
beside
) works by creating a painter that invokes
the original painters with respect to frames derived from the argument frame. Thus, for example,
flip-vert
doesn’t have to know how a painter works in order to flip it -- it just has to know how to
turn a frame upside down: The flipped painter just uses the original painter, but in the inverted frame.
Painter operations are based on the procedure
transform-painter
, which takes as arguments a
painter and information on how to transform a frame and produces a new painter. The transformed
painter, when called on a frame, transforms the frame and calls the original painter on the transformed
frame. The arguments to
transform-painter
are points (represented as vectors) that specify the
corners of the new frame: When mapped into the frame, the first point specifies the new frame’s origin
and the other two specify the ends of its edge vectors. Thus, arguments within the unit square specify a
frame contained within the original frame.
(define (transform-painter painter origin corner1 corner2)
(lambda (frame)
(let ((m (frame-coord-map frame)))
(let ((new-origin (m origin)))
(painter
(make-frame new-origin
(sub-vect (m corner1) new-origin)
(sub-vect (m corner2) new-origin)))))))
Here’s how to flip painter images vertically:
(define (flip-vert painter)
(transform-painter painter
(make-vect 0.0 1.0) ; new origin
(make-vect 1.0 1.0) ; new end of edge1
(make-vect 0.0 0.0))) ; new end of edge2
Using
transform-painter
, we can easily define new transformations. For example, we can
define a painter that shrinks its image to the upper-right quarter of the frame it is given:
(define (shrink-to-upper-right painter)
(transform-painter painter
(make-vect 0.5 0.5)
(make-vect 1.0 0.5)
(make-vect 0.5 1.0)))
Other transformations rotate images counterclockwise by 90 degrees
29
(define (rotate90 painter)
(transform-painter painter
(make-vect 1.0 0.0)
(make-vect 1.0 1.0)
(make-vect 0.0 0.0)))
or squash images towards the center of the frame:
30
(define (squash-inwards painter)
(transform-painter painter
(make-vect 0.0 0.0)
(make-vect 0.65 0.35)
(make-vect 0.35 0.65)))
Frame transformation is also the key to defining means of combining two or more painters. The
beside
procedure, for example, takes two painters, transforms them to paint in the left and right
halves of
an argument frame respectively, and produces a new, compound painter. When the
compound painter is given a frame, it calls the first transformed painter to paint in the left half of the
frame and calls the second transformed painter to paint in the right half of the frame:
(define (beside painter1 painter2)
(let ((split-point (make-vect 0.5 0.0)))
(let ((paint-left
(transform-painter painter1
(make-vect 0.0 0.0)
split-point
(make-vect 0.0 1.0)))
(paint-right
(transform-painter painter2
split-point
(make-vect 1.0 0.0)
(make-vect 0.5 1.0))))
(lambda (frame)
(paint-left frame)
(paint-right frame)))))
Observe how the painter data abstraction, and in particular the representation of painters as procedures,
makes
beside
easy to implement. The
beside
procedure need not know anything about the details
of the component painters other than that each painter will draw something in its designated frame.