SICP §2.2.4(図形言語 その8 [ペインタの変換と組み合わせ])

前回のエントリまでで、やっと実際の描画処理までが繋がったけども、beside とか below とか flip-* 系の実装がまだ空中に浮いたままの状態なんすよ。
これを解決するのが今回のエントリとなる。


肝となる考え方は、ペインタを新しく指定するフレームに沿うように変換をかけるってこと。
『ペインタ』に変換をかけるだけなので、当然ながら出来上がるものはやはり『ペインタ』。


ではその実装を紹介しましょう。

(define (transform-painter painter origin corner1 corner2)
  ;ペインタとなる手続を返却する。(フレームを受け取ってるよね)
  (lambda (frame)
    ;受け取ったframeに元づいたベクタ変換を行う手続を m に束縛する。
    (let ((m (frame-coord-map frame)))
      ;生成したベクタ変換手続を使用して、本手続きの生成時に渡された基点ベクタを変換。
      (let ((new-origin (m origin)))
	;本手続きの生成時に渡された辺ベクタ(2つ)を手続 m で変換し、
	;更にそれらと変換後の基点ベクタとの差分を求めて新しいフレームを生成、
	;そのフレームをペインタに渡して描画を行う。
	(painter
	 (make-frame new-origin
		     (sub-vect (m corner1) new-origin)
		     (sub-vect (m corner2) new-origin)))))))

えらい込み入ったロジックに見えますね。コメント書きながら理解していかないと何やってるかわからん。。

さて、この transform-painter 手続を使用すると、様々なバリエーションのペインタを生成する手続を定義することができるようになる!

;ペインタの画像を上下逆転する手続
(define (flip-vert painter)
  (transform-painter painter
		     (make-vect 0.0 1.0)   ;新しい origin
		     (make-vect 1.0 1.0)   ;edge1 の新しい端点
		     (make-vect 0.0 0.0))) ;edge2 の新しい端点

;与えられたフレームの右上の四半分に画像を縮めるペインタ
(define (shrink-to-upper-right painter)
  (transform-painter painter
		     (make-vect 0.5 0.5)   ;origin をフレームの中心に指定
		     (make-vect 1.0 0.5)   ;edge1 を中心点から考えて指定
		     (make-vect 0.5 1.0))) ;edge2 を中心点から考えて指定

;反時計回りに 90° 回転するペインタ
(define (rotate90 painter)
  (transform-painter painter
		     (make-vect 1.0 0.0)   ;origin を右下隅に指定
		     (make-vect 1.0 1.0)   ;edge1 を右上隅に指定
		     (make-vect 0.0 0.0))) ;edge2 を原点に指定

;フレームの右上と左下を結ぶ線を中心として圧縮描画するペインタ(菱形に表示されるっぽ。)
(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)))

;第1引数のペインタを左側へ、第2引数のペインタを右側へ表示するペインタ
;(※「beside」ですよ〜♪)
(define (beside painter1 painter2)
  ;右側用と左側用のペインタを先に生成しちゃう。
  (let ((left (transform-painter painter1
				 (make-vect 0.0 0.0)
				 (make-vect 0.0 1.0)
				 (make-vect 0.5 0.0)))
	(right (transform-painter painter2
				  (make-vect 0.5 0.0)
				  (make-vect 0.5 1.0)
				  (make-vect 1.0 0.0))))
    ;生成したペインタを使って描画する合成ペインタを返す。
    (lambda (frame)
      (left frame)
      (right frame))))

こーゆー感じで定義していくわけね〜。。やっとすっきりしたっすよ。。線画ペインタの変換は、つまるところベクタ変換がミソなわけですな。いや〜長かった。。
さて、これが理解できると、このあと問題で出てくる上下変換ペインタとかいろいろ合成できるようになるねぇ。