Exercise 2.49: Use segments->painter
to define the following primitive painters:
The painter that draws the outline of the designated frame.
The painter that draws an “X” by connecting opposite corners of the frame.
The painter that draws a diamond shape by connecting the midpoints of the sides
of the frame.
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
(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:
(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.
练习 2.49:用 segments->painter 定义下列基本画家:
绘制指定框架轮廓的画家。
通过连接框架相对两角来绘制"X"的画家。
通过连接框架各边中点来绘制菱形的画家。
wave 画家。
变换与组合画家
画家上的操作(如 flip-vert 或 beside)的工作原理是:创建一个新的画家,该画家以从参数框架派生出的框架来调用原来的画家。例如,flip-vert 无需知道画家的具体工作方式便可将其翻转——它只需知道如何将框架上下颠倒:翻转后的画家只是在倒置的框架中使用原来的画家。
画家操作基于过程 transform-painter,该过程以一个画家和如何变换框架的信息为参数,产生一个新的画家。变换后的画家在被调用时,会变换框架并以变换后的框架调用原来的画家。transform-painter 的参数是若干点(以向量表示),指定新框架的各个角:映射到框架中时,第一个点指定新框架的原点,另外两个点指定其边向量的终点。因此,单位正方形内的参数指定了包含在原框架内的新框架。
(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)))))))
以下是如何垂直翻转画家图像:
(define (flip-vert painter)
(transform-painter
painter
(make-vect 0.0 1.0) ; 新原点
(make-vect 1.0 1.0) ; edge1 的新终点
(make-vect 0.0 0.0))) ; edge2 的新终点
利用 transform-painter,我们可以方便地定义新的变换。例如,我们可以定义一个将图像缩小至给定框架右上四分之一区域的画家:
(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)))
其他变换将图像逆时针旋转 90 度
(define (rotate90 painter)
(transform-painter painter
(make-vect 1.0 0.0)
(make-vect 1.0 1.0)
(make-vect 0.0 0.0)))
或将图像向框架中心压缩:
(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)))
框架变换也是定义组合两个或多个画家的方法的关键。例如,beside 过程接受两个画家,将它们分别变换为在参数框架的左半部分和右半部分作画,并产生一个新的复合画家。当这个复合画家被给定一个框架时,它调用第一个变换后的画家在框架左半部分作画,调用第二个变换后的画家在框架右半部分作画:
(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)))))
注意画家的数据抽象——特别是将画家表示为过程——如何使 beside 的实现变得简洁。beside 过程无需了解各组成画家的任何实现细节,只需知道每个画家将在其指定框架内绘制某些内容即可。
(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))))))) (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 (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))) (define (rotate90 painter)
(transform-painter painter
(make-vect 1.0 0.0)
(make-vect 1.0 1.0)
(make-vect 0.0 0.0))) (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))) (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)))))