Skip to main content

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.

(define canvas (color-grid 20 20 
                           3
                           (irgb 192 0 0)
                           (irgb 192 0 192)
                           (irgb 0 0 192)
                           (irgb 192 192 0)
                           (irgb 0 192 192)
                           (irgb 255 255 255)
                           (irgb 0 192 0)
                           (irgb 0 0 0)
                           (irgb 192 192 192)))
(define picture 
  (image-load "/home/rebelsky/MediaScheme/Images/kitten.png"))

b. Save your definitions pane as conditionals-lab.rkt.

c. Open the reference page for conditionals, the reference page for drawings, the reference for GIMP images, and the reference for GIMP contexts in separate windows or tabs.

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:
;;;   irgb-brightness
;;; Parameters:
;;;   color, an integer-encoded RGB color
;;; Purpose:
;;;   Computes the brightness of color on a 0 (dark) to 100 (light) scale.
;;; Produces:
;;;   brightness, an integer
;;; Preconditions:
;;;   [No additional]
;;; Postconditions:
;;;   If color1 is likely to be perceived as lighter than color2,
;;;     then (irgb-brightness color1) > (irgb-brightness color2).
;;;   0 <= brightness <= 100
(define irgb-brightness
  (lambda (color)
    (round (* 100 (/ (+ (* 0.30 (irgb-red color))
                        (* 0.59 (irgb-green color))
                        (* 0.11 (irgb-blue color)))
                      255)))))

a. Add irgb-brightness to the Definitions pane.

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

c. Using irgb-brighter? as a helper, write a procedure, (irgb-brighter-2 color1 color2), that returns the brighter of color1 ... color2.

d. What do you expect the following to accomplish?

> (image-show (image-variant canvas irgb-brighter-2))

e. Check your answer experimentally.

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

> (define irgb-brighter-of-red (lambda (color) (irgb-brighter-2 color (irgb 255 0 0))))
> (image-show (image-variant canvas irgb-brighter-of-red))

g. Check your answer experimentally.

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 (irgb 0 0 0))
(define grey1 (irgb 96 96 96))
(define grey2 (irgb 192 192 192))
(define grey3 (irgb 255 255 255))

a. Using if expressions (not cond expressions), write a procedure, (irgb-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 canvas using irgb-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 irgb-4grey.

d. Rewrite irgb-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)

b. Check your answer experimentally.

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 some of 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.

Hint: Those are not the only cases to consider.

d. Now that you have a mechanism for determining the likely safety of selections, write a new procedure (image-safely-select-ellipse! img selection-type 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 render! procedure from the reading.

;;; Procedure:
;;;   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.
;;;   drawing is a simple drawing, that is one of
;;;     (drawing-rectangle? drawing) or
;;      (drawing-ellipse? drawing) holds.
;;; Postconditions:
;;;   drawing has been rendered on image.
(define render!
  (lambda (drawing image)
    (context-set-fgcolor! (drawing-color drawing))
    (cond
      [(equal? (drawing-type drawing) 'rectangle)
       (image-select-rectangle! image REPLACE
                                (drawing-left drawing)
                                (drawing-top drawing)
                                (drawing-width drawing)
                                (drawing-height drawing))]
      [(equal? (drawing-type drawing) 'ellipse)
       (image-select-ellipse! image REPLACE
                              (drawing-left drawing)
                              (drawing-top drawing)
                              (drawing-width drawing)
                              (drawing-height drawing))])
     (image-fill-selection! image)))

And here are two sample drawings.

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

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

a. Test 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 render! so that, after rendering, it updates the display and selects nothing.

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

d. Extend 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 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
> (render! (hshift-drawing -2 drawing-unit-circle) canvas)
Error: image-select-ellipse!: selection is outside of the bounds of the image 
> (render! (hshift-drawing 11 drawing-unit-circle) canvas)
Error: image-select-ellipse!: selection is outside of the bounds of the image 

Rewrite 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 interesting 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 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, 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 (< (irgb-brightness color) 25)
        grey0
        (if (< (irgb-brightness color) 50)
            grey1
            (if (< (irgb-brightness color) 75)
                grey2
                grey3)))))

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

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

Return to the problem.