Schedule Readings Labs Assignments Syllabus Reference Contact CSC 151, 2012S

# Laboratory: Conditionals

Summary: In this laboratory, you will explore three of Scheme's primary conditional control operations, `if`, `when`, and `cond`.

## Reference

If expressions typically have the form

```(`if` `test` `consequent` `alternative`)
```

The Scheme interpreter first evaluates the test. If the value of the test is false (that is, `#f`), the interpreter evaluates the alternative and returns the value. If the value of the test is truish (that is, anything that is not false), the interpreter evaluates the consequent and returns its value.

When expressions typically have the form

```(`when`
`test`
`exp1`
...
`expn`)
```

The Scheme interpreter first evaluates the test. If the value of the test is false (that is, `#f`), the interpreter ignores the rest of the expression. If the value of the test is truish, the interpreter evaluates the expressions in turn.

Cond expressions typically have the form

```(`cond`
(`test0` `exp0`)
(`test1` `exp1`)
...
(`testn` `expn`)
(else `alternative`))
```

Scheme evaluates each test in turn until one is truish. It then evaluates the corresponding expression and returns its value. If none of the tests is truish, then it evaluates the alternative and returns its value.

## Preparation

a. Add the following to your definitions pane to make it easy to create and show a variety of sample images. (Make sure to replace username with your user name.)

```(define grid (image-show (image-new 3 3)))
(define canvas (image-show (image-new 200 200)))
(define picture

(image-set-pixel! grid 0 0 (rgb-new 192 0 0))
(image-set-pixel! grid 1 0 (rgb-new 192 0 192))
(image-set-pixel! grid 2 0 (rgb-new 0 0 192))
(image-set-pixel! grid 0 1 (rgb-new 192 192 0))
(image-set-pixel! grid 1 1 (rgb-new 0 192 192))
(image-set-pixel! grid 2 1 (rgb-new 255 255 255))
(image-set-pixel! grid 0 2 (rgb-new 0 192 0))
(image-set-pixel! grid 1 2 (rgb-new 0 0 0))
(image-set-pixel! grid 2 2 (rgb-new 192 192 192))
```

b. Save your definitions pane as `conditionals-lab.scm`.

c. Open the reference page for drawings in a separate window or tab.

## Exercises

### Exercise 1: Comparing Brightness, Revisited

Recently, we've considered a number of activities that required us to work with the brightness of colors. We typically compute brightness using an expression which can be encapsulated in a procedure as follows:

```;;; Procedure:
;;;   rgb-brightness
;;; Parameters:
;;;   color, an RGB color
;;; Purpose:
;;;   Computes the brightness of color on a 0 (dark) to 100 (light) scale.
;;; Produces:
;;;   b, an integer
;;; Preconditions:
;;;   color is a valid RGB color.  That is, each component is between
;;;     0 and 255, inclusive.
;;; Postconditions:
;;;   If color1 is likely to be perceived as lighter than color2,
;;;     then (brightness color1) > (brightness color2).
;;;   0 <= b <= 100
(define rgb-brightness
(lambda (color)
(round (* 100 (/ (+ (* 0.30 (rgb-red color))
(* 0.59 (rgb-green color))
(* 0.11 (rgb-blue color)))
255)))))
```

a. Add `rgb-brightness` to the Definitions pane.

b. Using `rgb-brightness` as a helper, write a procedure, ```(rgb-brighter? color1 color2)```, that returns true if `color1` is brighter than `color2` and false otherwise.

c. Using `rgb-brightness` or `rgb-brighter?` as a helper, write a procedure, ```(rgb-brighter-2 color1 color2)```, that returns the brighter of `color1` and `color2`.

d. What do you expect the following to accomplish?

````>` `(image-transform! grid rgb-brighter-2)`
```

f. As you may have noted, the transformation was invalid because `rgb-brighter-2` expects two colors, not one. Let's try an appropriate call to `rgb-brighter-2`. What do you expect the following to accomplish?

````>` `(image-transform! grid (r-s rgb-brighter-2 (rgb-new 255 0 0)))`
```

g. Check your answer experimentally. You may want to zoom in on `grid` first, so that the changes are clearer.

f. Save the definitions pane. You will want to use these procedures later.

### Exercise 2: Shades of Grey

Consider the following four colors:

```(define grey0 (rgb-new 0 0 0))
(define grey1 (rgb-new 96 96 96))
(define grey2 (rgb-new 192 192 192))
(define grey3 (rgb-new 255 255 255))
```

a. Using `if` expressions (not `cond` expressions), write a procedure, ```(rgb-4grey color)```, that returns

• `grey0`, if the brightness of `color` is less than 25;
• `grey1`, if the brightness of `color` is at least 25 but less than 50;
• `grey2`, if the brightness of `color` is at least 50 but less than 75; or
• `grey3`, if the brightness of `color` is at least 75.

b. Build and show a variant of `grid` using `rgb-4grey`. (Testing our procedure on a small image is a good way to make sure that there are no glaring errors.)

c. Build and show a variant of `picture` using `rgb-4grey`.

d. Rewrite `rgb-4grey` so that it uses `cond` rather than `if`.

When you are done with this exercise, you may want to look at the notes on this exercise.

### Exercise 3: Safer Selections

a. What do you expect the following expressions to accomplish?

````>` `(image-select-ellipse! canvas REPLACE -50 0 40 40)`
`>` `(image-select-rectangle! canvas REPLACE 10 220 50 50)`
```

c. As you should have observed, both of those expressions generate an error because the selection is outside the bounds of the image. Write a new predicate, ```(image-contains-selection? img left top width height)```, that determines whether the given selection is likely to be within `img`.

Hint: One case in which a selection is invalid is when the left edge of the selection is greater than or equal to the width of the image.

Hint: Another case in which a selection is invalid is when the right edge (given by adding the width to the left edge) is less than 0.

d. Now that you have a mechanism for determining the likely safety of selections, write a new procedure ```(image-safely-select-ellipse! img operation left top width height)``` that checks whether the selection is likely to be within the bounds of the image and, if so, does the selection using `image-select-ellipse!`. If the selection is unlikely to be within the bounds of the image, `image-safely-select-ellipse!` should select nothing.

Hint: You may want to use `when` to make your decision.

### Exercise 4: Rendering Drawings

Here is the `drawing-render!` procedure from the reading.

```;;; Procedure:
;;;   drawing-render!
;;; Parameters:
;;;   drawing, a drawing value
;;;   image, an image id
;;; Purpose:
;;;   Render drawing on the image.
;;; Produces:
;;;   [Nothing; called for side effect]
;;; Preconditions:
;;;   drawing can be rendered on image.
;;; Postconditions:
;;;   drawing has been rendered on image.
(define drawing-render!
(lambda (drawing image)
(cond
((equal? (drawing-type drawing) 'rectangle)
(context-set-fgcolor! (drawing-color drawing))
(image-select-rectangle! image REPLACE
(drawing-left drawing)
(drawing-top drawing)
(drawing-width drawing)
(drawing-height drawing))
(image-fill-selection! image))
((equal? (drawing-type drawing) 'ellipse)
(context-set-fgcolor! (drawing-color drawing))
(image-select-ellipse! image REPLACE
(drawing-left drawing)
(drawing-top drawing)
(drawing-width drawing)
(drawing-height drawing))
(image-fill-selection! image))
(else
(image-draw-line! image 0 0 (image-width image) (image-height image))))))
```

And here are two sample drawings.

```(define d1
(drawing-hshift
(drawing-vshift
(drawing-scale
(drawing-recolor
drawing-unit-square
"red")
50)
40)
30)
)

(define d2
(drawing-vscale
(drawing-hscale
(drawing-recolor
drawing-unit-circle
"green")
60)
120)
)
```

a. Test `drawing-render!` to verify that it does, in fact, render both drawings.

b. As you may have observed, we don't immediately see the rendered drawing. Update `drawing-render!` so that, after rendering, it updates the display and selects nothing.

c. Extend `drawing-render!` so that it outlines the drawing in black. (Recall that you can stroke the drawing with `image-stroke!` to outline the image.)

d. Extend `drawing-render!` so that it outlines the drawing in black if the drawing is a light color and outlines the drawing in grey if the drawing is a dark color. (You can choose your own metric for dark and light.)

## For Those With Extra Time

Those with extra time may choose to do the extra problems, which focus on programming tasks, or the explorations, which focus on the application of procedures to image creation.

### Extra 1: Shades of Grey, Revisited

As you may recall from the reading on Boolean values and predicate procedures, it is possible to use `and` and `or` to achieve conditional behavior. The subsequent reading on conditionals provides some additional information on the use of `and` and `or`.

Using those techniques, rewrite `rgb-4grey` so that it uses neither `if` nor `cond`. That is, you'll need to find ways to express the same outcome using `and` and `or`.

### Extra 2: Safely Rendering Drawings

One problem with `drawing-render!` is that it causes an error if we try to render a drawing outside of the image. For example,

````>` `(define canvas (image-new 10 10))`
`canvas`
`>` `(drawing-render! (drawing-hshift drawing-unit-circle -2) canvas)`
Error: image-select-ellipse!: selection is outside of the bounds of the image
`>` `(drawing-render! (drawing-hshift drawing-unit-circle 11) canvas)`
Error: image-select-ellipse!: selection is outside of the bounds of the image
```

Rewrite `drawing-render!` to that it only renders drawings that fall within the bounds of the canvas.

### Extra 3: Preserving Context

Consider the following procedure that draws a simple logo in a color and position of the user's choice.

```;;; Procedure:
;;;   simple-logo!
;;; Parameters:
;;;   img, an image
;;;   left, a real number
;;;   top, a real number
;;;   color, a color
;;; Purpose:
;;;   Renders a simple logo on img, with the left edge of the logo
;;;   at left, the top edge at top.  Uses color as the primary color
;;;   of the logo
;;; Produces:
;;;   img, the same image
;;; Preconditions:
;;;   0 <= left < (width img) [unverified]
;;;   0 <= top < (height img) [unverified]
;;; Postconditions:
;;;   img now contains a rendering of the simple logo.
(define simple-logo!
(lambda (img left top color)
; Save appropriate parts of the context
(let ((saved-color (context-get-fgcolor))
(saved-brush (context-get-brush)))
; Select the logo
(image-select-rectangle! img REPLACE left top 50 50)
(image-select-ellipse! img SUBTRACT
(+ left 15) (+ top 10)
25 25)
(image-select-ellipse! img SUBTRACT
(- left 5) (- top 10)
30 30)
(image-select-ellipse! img INTERSECT
left top
60 60)
; Fill it with the main color
(context-set-fgcolor! color)
(image-fill! img)
; Outline it with a fuzzy brush to get an intersting effect
(context-set-brush! "Circle Fuzzy (09)")
(image-stroke! img)
; Restore the context, as best as is possible
(image-select-nothing! img)
(context-set-brush! saved-brush)
(context-set-fgcolor! saved-color)
; Return the updated image
img)))```

a. Make sure that you understand what the procedure does by rendering a few copies of the logo on `canvas`.

b. As you may have observed, this procedure tries to “preserve” the context by grabbing the color and brush before changing them, and restoring them to the old values after it has changed them. While this strategy works fine if we're only drawing one or two logos, it can add unnecessary work if we are drawing a lot of logos in the same color.

Rewrite `simple-logo!` so that it only restores the brush and color if they are different from the brush and color used to create the logo. Consider using `when` to make decisions about whether or not to set and restore the brush and color.

c. If we don't care about the current brush and color (or if we are changing them a lot), we may want to give a hint to the programmer that she need not restore the brush and color. MediaScheme includes three procedures that do just that.

• `(context-preserve?)` checks whether or not procedures are encouraged to preserve the context.
• `(context-preserve-off!)` makes `context-preserve?` return false (`#f`).
• `(context-preserve-on!)` makes `context-preserve?` return true (`#t`).

Rewrite `simple-logo!` so that it only restores the color and brush if `context-preserve?` returns true.

### Extra 4: Safer Selections, Revisited

In exercise 3, you wrote a procedure, `image-safely-select-ellipse!` that first checked whether the selection was in the image and, if not, simply avoided the selection operation. However, the choice to ignore the selection may not always be appropriate. For example, if the new selection is to replace the prior selection, we should probably select nothing. In contrast, if the new selection is to be subtracted from the current selection, we should maintain the current selection.

Rewrite `image-safely-select-ellipse!` to do what you consider appropriate for each of the five cases:

• The selection is valid (in which case, you can just use `image-select-ellipse!`)
• The selection is not valid, and the type is `REPLACE`
• The selection is not valid, and the type is `ADD`
• The selection is not valid, and the type is `SUBTRACT`
• The selection is not valid, and the type is `INTERSECT`

## Explorations

Explorations are intended for students interested in further exploring the design aspects of these techniques. They also provide students who finish early with extra activities that may challenge them in different ways.

### Exploration 1: Rendering, Revisited

You've already noted that `drawing-render!` can do more than simply select an area and then fill it. For example, we can stroke the drawing after filling it. (We could even stroke it multiple times.)

Write a procedure, `drawing-render-excessively!`, that renders a drawing in a more interesting manner. You might, for example, stroke the drawing with a large brush, a medium brush, and a small brush, each of different colors. You might draw a contrasting smaller shape within the object. (E.g., within a square of edge length d, you can draw a circle of diameter d; within a circle of diameter d, you can draw a square of edge length (/ d (sqrt 2).)

## Notes on the Exercises

### Notes on Exercise 2: Shades of Grey

The `if`-based solution will look something like the following.

```(define rgb-4grey
(lambda (color)
(if (< (rgb-brightness color) 25)
grey0
(if (< (rgb-brightness color) 50)
grey1
(if (< (rgb-brightness color) 75)
grey2
grey3)))))
```

The `cond`-based solution will look something like the following.

```(define rgb-4grey
(lambda (color)
(cond
((< (rgb-brightness color) 25)
grey0)
((< (rgb-brightness color) 50)
grey1)
((< (rgb-brightness color) 75)
grey2)
(else
grey3))))
```