2011-10-29

SICP Exercise 2.52: Abusing Painters

Make changes to the square limit of wave shown in figure 2.9 by working at each of the levels described above. In particular:
  1. Add some segments to the primitive wave painter of exercise 2.49 (to add a smile, for example).
  2. Change the pattern constructed by corner-split (for example, by using only one copy of the up-split and right-split images instead of two).
  3. Modify the version of square-limit that uses square-of-four so as to assemble the corners in a different pattern. (For example, you might make the big Mr. Rogers look outward from each corner of the square.)
Before we get started, let's see what our current procedures give us. We're going to be modifying the wave painter, so we'll show what that looks like with square-limit. If we try ((square-limit wave 4) window) we get:
Now let's add a smile and a pair of eyes to the wave painter. I quickly sketched out what I wanted to add as follows:
From this we can work out the coordinates for the eyes and smile and then add in another three sets of connected segments using build-segments-list as we did in exercise 2.49. The extended wave painter looks like this, with the new connected segment sets added at the bottom:
(define wave
 (segments->painter
   (append (build-segments-list (make-vect 0.0  0.85)
                                (make-vect 0.15 0.6)
                                (make-vect 0.3  0.65)
                                (make-vect 0.4  0.65)
                                (make-vect 0.35 0.85)
                                (make-vect 0.4  1.0))
           (build-segments-list (make-vect 0.6  1.0)
                                (make-vect 0.65 0.85)
                                (make-vect 0.6  0.65)
                                (make-vect 0.75 0.65)
                                (make-vect 1.0  0.35))
           (build-segments-list (make-vect 1.0  0.15)
                                (make-vect 0.6  0.45)
                                (make-vect 0.75 0.0))
           (build-segments-list (make-vect 0.6  0.0)
                                (make-vect 0.5  0.3)
                                (make-vect 0.4  0.0))
           (build-segments-list (make-vect 0.25 0.0)
                                (make-vect 0.35 0.5)
                                (make-vect 0.3  0.6)
                                (make-vect 0.15 0.4)
                                (make-vect 0.0  0.65))
           (build-segments-list (make-vect 0.4 0.9)
                                (make-vect 0.45 0.9)
                                (make-vect 0.45 0.85)
                                (make-vect 0.4 0.85)
                                (make-vect 0.4 0.9))
           (build-segments-list (make-vect 0.55 0.9)
                                (make-vect 0.6 0.9)
                                (make-vect 0.6 0.85)
                                (make-vect 0.55 0.85)
                                (make-vect 0.55 0.9))
           (build-segments-list (make-vect 0.4 0.75)
                                (make-vect 0.45 0.7)
                                (make-vect 0.55 0.7)
                                (make-vect 0.6 0.75)))))
Trying this out with (wave window) gives:
...and ((square-limit wave 4) window) gives:
Next we can work on corner-split. The exercise suggests only using one copy of the up-split and right-split images, instead of two. This is a nice straightforward change. Here's the original corner-split:
(define (corner-split painter n)
  (if (= n 0)
      painter
      (let ((up (up-split painter (- n 1)))
            (right (right-split painter (- n 1))))
        (let ((top-left (beside up up))
              (bottom-right (below right right))
              (corner (corner-split painter (- n 1))))
          (beside (below painter top-left)
                  (below bottom-right corner))))))
As you can see it defines top-left as applying beside with up as both parameter values and bottom-right as applying below with right as both parameter values. We can see the effect of corner-split by applying it to the updated wave via ((corner-split wave 4) window):
All we need to do is to remove these definitions and replace usages of top-left and bottom-right with up and right respectively. If we do this (and collapse the let statements as the nesting is no longer needed) we get:
(define (corner-split painter n)
  (if (= n 0)
      painter
      (let ((up (up-split painter (- n 1)))
            (right (right-split painter (- n 1)))
            (corner (corner-split painter (- n 1))))
        (beside (below painter up)
                (below right corner)))))
Testing this with ((corner-split wave 4) window) now gives:
...and ((square-limit wave 4) window) now gives:
Finally we can look at changing square-limit. The exercise suggests making "the big Mr. Rogers look outward from each corner of the square". If we look at the quadrants as they're shown in section 2.2.4 we can see that Mr. Rodgers currently looks inward at the center of the square. E.g. in the top-right quadrant Mr. Rodgers' face is in the bottom-left-hand corner and looks towards the left.

Now there are at least a couple of ways in which you could interpret this suggested change depending upon what orientation of the head you end up with in each quadrant. I decided to go with the simplest - I'll have Mr. Rodgers' face have the correct orientation in the bottom-right quadrant. I.e. in the bottom-right quadrant Mr. Rodgers' face will be in the bottom-left-hand corner and look towards the left. This then reduces the problem to simply swapping over the contents of opposite corners of the square. So we take the original square-limit of:
(define (square-limit painter n)
  (let ((combine4 (square-of-four flip-horiz identity
                                  rotate180 flip-vert)))
    (combine4 (corner-split painter n))))
...and then swap the top-left corner with the bottom-right and the top-right corner with the bottom-left to give:
(define (square-limit painter n)
  (let ((combine4 (square-of-four flip-vert rotate180
                                  identity flip-horiz)))
    (combine4 (corner-split painter n))))
When we try this out with ((square-limit wave 4) window) we now get:

2011-10-23

Whoops - Stalled!

No SICP posts for over a week! Well, everyone has to take a holiday at some point.

SICP Exercise 2.51: Below and Below Again

Define the below operation for painters. Below takes two painters as arguments. The resulting painter, given a frame, draws with the first painter in the bottom of the frame and with the second painter in the top. Define below in two different ways -- first by writing a procedure that is analogous to the beside procedure given above, and again in terms of beside and suitable rotation operations (from exercise 2.50).

Here's the procedure 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)))))
This takes two painters and applies transformations to them such that the first painter is squashed into the left-hand side of a frame, while the second painter is squashed into the right-hand side of the frame... Assuming, of course, that the frame is orientated normally. I.e. with the origin at the bottom left-hand corner, edge1 defining the bottom side of the frame, going from left to right, and edge2 defining the left side of the frame, going from bottom to top. We can express this mapping graphically as follows:
We can use a similar pattern to this for our first version of below, except we want the mapping produced to divide the frame into two horizontal stripes, one above the other. Graphically this is:
To do this we need a different split-point, (0, 0.5) instead of (0.5, 0), and this split-point needs to split edge2 rather than edge1. And we need to rename a few variables. Other than that, it's pretty similar:
(define (below painter1 painter2)
  (let ((split-point (make-vect 0.0 0.5)))
    (let ((paint-down
           (transform-painter painter1
                              (make-vect 0.0 0.0)
                              (make-vect 1.0 0.0)
                              split-point))
          (paint-up
           (transform-painter painter2
                              split-point
                              (make-vect 1.0 0.5)
                              (make-vect 0.0 1.0))))
      (lambda (frame)
        (paint-down frame)
        (paint-up frame)))))
And putting this to the test with...
((below wave spiral) window)
...gives:
Now onto the second version... As we noted, beside puts its first painter on the left-hand side of the frame and its second on the right-hand side. We want below to put the first painter underneath the second painter. Graphically we can view this as the following rotation:
This rotation is a 90° anti-clockwise rotation. Fortunately, we've already been given a procedure for doing this, rotate90, which was introduced in the section "Transforming and combining painters". Unfortunately, as the image above shows, this translation also has the effect of rotating the contents of the painters by 90° anti-clockwise. We need to compensate for this by rotating each of the painters themselves by 90° clockwise, which is equivalent to rotating by 270° anti-clockwise and is encapsulated in the procedure rotate270 which we defined in exercise 2.50.

Here's what we get when we put these two sets of rotations together:
(define (below painter1 painter2)
  (rotate90 (beside (rotate270 painter1)
                    (rotate270 painter2))))
As you'd expect, this gives identical results to the first version above.