Skip to main content

Laboratory: Writing Your Own Procedures

Summary: In this laboratory, you will explore some of the issues that pertain to writing your own procedures using lambda.

Preparation

a. Review How Scheme Evaluates Expressions (Take 2).

b. Start GIMP and enable the DBUS Server. Then start DrRacket.

c. Save a copy of procedures-rgb-lab.rkt, which contains most of the code from the reading.

d. Open the file in DrRacket. Review the file to see what values and procedures are included. (You may find it easiest to look at the list provided by using the DrRacket (define …) menu.) Finally, click Run.

If things go wrong, see our instructions for running the CSC 151 software. Ask for help if you need it.

Exercises

Exercise 1: A Procedure for Bounding Values

Recall that in the lab on numeric values, you explored the use of the min and max procedures to compute a bounded value:

> (define bounded-val (min (max val lower) upper))

This code assumed that val, lower, and upper had already been defined. It would be helpful to create a subroutine, instead, so that we can easily limit any real number to fall within any bounds.

Take the code given above and encapsulate it in a procedure named bound. This procedure should take three parameters: val, lower, and upper. (It should take them in that order.) As a reminder, the general form of a procedure definition is

(define procedure-name
  (lambda (formal-parameters)
    body))

Here is an example of some output from the bound procedure:

> (bound 5 0 10)
5
> (bound 5 10 20)
10
> (bound 0.7 -1 1)
0.7
> (bound 1.01 -1 1)
1.0
> (bound -8.3 -1 1)
-1.0

Exercise 2: Swapping Components

In the reading, we saw two procedures for rotating the three components of a color. Let’s consider some alternate ways to switch the components.

a. Write a procedure, (irgb-swap-red-blue color), that swaps the red and blue components of color, which is an integer-encoded RGB color.

b. Write a procedure, (irgb-swap-red-green color), that swaps the red and green components of color.

c. Write a procedure that behaves like irgb-rotate by appropriately composing irgb-swap-red-blue and irgb-swap-red-green.

(define new-irgb-rotate (compose ... ...))

c. Write a procedure that swaps the green and blue components by appropriately composing irgb-swap-red-blue and irgb-swap-red-green.

(define irgb-swap-green-blue (compose ...))

Exercise 3: An Improved Greyscale Procedure

As you likely discovered in the self-check, the irgb-greyscale-3 procedure does a reasonable job, but the result doesn’t seem quite right. That’s because humans tend to perceive red, green, and blue as having different brightnesses. Hence, averaging the three does not work as one might hope. The literature (e.g., Wikipedia) suggests that we multiply the red component by about 0.299, the green by about 0.587, and the blue by about .114 and then add them all up. (More recently, there’s been a recommendation to use 0.2126 for red, 0.7152 for green, and 0.0722 for blue.) We call this value the luma.

a. Write a procedure, (irgb-luma color), that takes an integer-encoded RGB color as a parameter and returns the luma of that color using the appropriate formula.

b. Using irgb-luma and irgb-grey, write a new version of irgb-greyscale which you should call my-irgb-greyscale.

c. Try the two greyscale functions with image-variant and an image of your choice to determine if you can observe a difference.

Exercise 4: Implications of Using Formulae

Do you have a responsibility to document the source of the formula you used in your computation of irgb-luma? Why or why not?

Exercise 5: Implications of Extending Code

In the reading, we started with a set of instructions for making a greyscale value.

> (define color (irgb ...))
> (define grey-component (* 1/3 (+ (irgb-red color) (irgb-green color) (irgb-blue color))
> (define grey-color (irgb grey-component grey-component grey-component))

We then turned that code into a series of procedures. In effect, we made something that others might consider a “derivative work” of the original. If you did the same revision, you would have a responsibility to consider whether or not to cite the original.

a. Suppose that you decided to cite the original. Write a comment that gives appropriate details. (The original comes from the reading, which was created by Charlie Curtsinger, Janet Davis, Samuel A. Rebelsky, and Jerod Weinman.)

b. Suppose you wrote the code supplied with this lab with another student, perhaps for a previous lab. In writing the my-irgb-greyscale procedure, which uses, but does not change irgb-grey, do you have a responsibility to cite that partner? Why or why not?

c. Suppose you wrote the code for irgb-grey alone, but wrote my-irgb-greyscale with another student. Should you cite the code for irgb-grey? Why or why not?

Exercise 6: Partial Averaging

a. Write a procedure, (irgb-weighted-average weight color), that recomputes each parameter as a weighted average of itself and the two other parameters. You will compute the weighted average by multiplying the component by the weight, adding the two other components, and then dividing by the weight plus two.

For example, if the weight is 4, the red component is 0, the green component is 40, and the blue component is 200, the weighted average color would have

  • a red component of 40 = (4*0 + 40 + 200)/6;
  • a green component of 60 = (0 + 4*40 + 200)/6; and
  • a blue component of 140 = (0 + 40 + 4*200)/6.

If, instead, the weight were 8 and the components were the same, the weighed average color would have

  • a red component of 24 = (8*0 + 40 + 200)/10;
  • a green component of 52 = (0 + 8*40 + 200)/10; and
  • a blue component of 164 = (0 + 40 + 8*200)/10.

b. Try applying your procedure to the kitten image using various weights. For example, to use a weight of 8, you would write the following.

> (image-show (image-variant kitten (section irgb-weighted-average 8 <>)))

Exercise 7: Multiple Approaches

In the self-check for the corresponding reading, you were asked to write a procedure that computes the multiplicative inverse of a number. Here are two ways to write that procedure.

(define invert-a
  (lambda (val)
    (/ 1 val)))

(define invert-b (section / 1 <>))

a. In which cases, if any, do the two procedures behave differently?

b. Which do you prefer. Why?

Exercise 8: Helper Procedures

When writing procedures, we may often find it helpful to write additional procedures to help us accomplish our task. You saw one example of that approach in some of the irgb-greyscale procedure that appears in the reading. Here’s another. Suppose we are computing a strange color transformation: the red component of the result will be the average of the red and green components of the original; the green component will be the average of the green and blue components; and the blue component will be the average of the blue and red components. Here’s one approach to writing that procedure.

(define irgb-strange-average-1
  (lambda (color)
    (irgb (* 1/2 (+ (irgb-red color) (irgb-green color)))
          (* 1/2 (+ (irgb-green color) (irgb-blue color)))
          (* 1/2 (+ (irgb-blue color) (irgb-red color))))))

Here’s a second approach.

(define irgb-strange-average-2
  (lambda (color)
    (rgb-strange-average-2 (irgb-red color) 
                           (irgb-green color) 
                           (irgb-blue color))))

; (rgb-strange-average-2 red green blue)
;    Build an integer-encoded RGB color the uses the "strange average"
;    technique to build the components.
(define rgb-strange-average-2 
  (lambda (red green blue)
    (irgb (* 1/2 (+ red green)) (* 1/2 (+ green blue)) (* 1/2 (+ blue red)))))

Here’s a third approach.

(define irgb-strange-average-3
  (lambda (color)
    (rgb-strange-average-3 (irgb-red color) 
                           (irgb-green color) 
                           (irgb-blue color))))

(define rgb-strange-average-3 
  (lambda (red green blue)
    (irgb (ave2 red green) (ave2 green blue) (ave2 blue red))))

; (ave2 v1 v2)
;   Compute the arithmetic average of v1 and v2
(define ave2
  (lambda (v1 v2)
    (* 1/2 (+ v1 v2))))

a. What do you see as the relative advantages of each approach?

b. The use of ave2 in rewriting rgb-strange-average “feels” a bit different than the use of rgb-strange-average in rewriting irgb-strange-average. Explain what purposes each kind of helper seems to serve.

For Those With Extra Time

If you have extra time, you may find it useful to do any of the following extra problems, which emphasize Scheme issues, or the explorations, which emphasize creative work. (You need not do them in order.)

Extra 1: Extreme Components

Write a procedure, (irgb-extreme color), that takes one parameter, an integer-encoded RGB color and turns each parameter to 255 if it is at least 128 and to 0 if it is less than 128.

> (irgb->string (irgb-extreme (0 64 200)))
"0/0/255"
> (irgb->string (irgb-extreme (128 130 0)))
"255/255/0"

Hint: A clever combination of division, rounding (perhaps with floor), and multiplication can help you achieve this goal.

Extra 2: Dominance

Write a procedure, (irgb-dominant color), that takes one parameter, an integer-encoded RGB color, and produces a new color in which each component is 255 if it is the largest component (or tied for largest) and 0 otherwise.

> (irgb->string (irgb-dominant (irgb 0 5 0)))
"0/255/0"
> (irgb->string (irgb-dominant (irgb 200 199 199)))
"255/0/0"
> (irgb->string (irgb-dominant (irgb 10 0 10)))
"255/0/255"

Explorations

Exploration 1: Weighted Averages, Revisited

As you’ve discovered, irgb-weighted-average keeps components closer to the original with larger weights, and closer to the average with lower weights.

Predict what happens if you use weights of 1/2, 0, -1/2, and -1. Then try making variants of your image with each of those weights.