Summary: We extend our reach using procedures that take other procedures as parameters.
Contents:
If you think back to the beginning of the reading on transforming colors, you may recall that we
suggested that one reason to learn to transform colors is that by transforming
colors, you can also build filters. Do we have enough information to write
a filter for a four-by-three image? Certainly. Suppose we wanted to compute
the complement of this image. We could write a sequence of calls to
the image.transform-pixel! procedure.
(image.transform-pixel! canvas 0 0 rgb.complement)
(image.transform-pixel! canvas 0 1 rgb.complement)
(image.transform-pixel! canvas 0 2 rgb.complement)
(image.transform-pixel! canvas 0 3 rgb.complement)
(image.transform-pixel! canvas 1 0 rgb.complement)
(image.transform-pixel! canvas 1 1 rgb.complement)
(image.transform-pixel! canvas 1 2 rgb.complement)
(image.transform-pixel! canvas 1 3 rgb.complement)
(image.transform-pixel! canvas 2 0 rgb.complement)
(image.transform-pixel! canvas 2 1 rgb.complement)
(image.transform-pixel! canvas 2 2 rgb.complement)
(image.transform-pixel! canvas 2 3 rgb.complement)
But that's an awful lot of typing. More importantly, this technique does not adapt itself easily to images of different sizes or to typical-sized images. (In a 200x200 image, which is still fairly small, there are 40,000 positions. No one wants to type that many lines of code, particularly when it is that repetitive.)
So, how do we write filters? DrFu includes a pair of helpful procedures,
(image.map transformation image) and
(image.map! transformation image). The first
builds a new image by applying the given transformation to every pixel
in another image. The second changes an image by applying the transformation. (Recall that a transformation is a function that takes a color and returns another color.)
You will explore the differences between image.map and image.map! in the lab.
In Scheme, map
typically means something akin to do to each
portion of this thing
. So, image.map means do to
each pixel
. When we learn about lists, Scheme's primary mechanism
for grouping other kinds of values, we'll find that there's a version
of map that does something with each element of the list. There's even
an rgb.map that lets us do something to each component of
an RGB color.
We are almost done on our quest to learn how to write filters. We've learned some new functions provided by DrFu, such as the transformations. We've learned about one new technique, refactoring, which involves writing new functions that encapsulate common code. However, we have not yet learned how to write these refactored procedures. We've seen that Scheme permits procedures to take other procedures as parameters, and that this permission supports refactoring. Finally, we've learned a new kind of operation, map, which lets us do something to every component of a compound value. All this put together lets us write some simple filters. For example, we can write one line of Scheme code to make a a picture bluer.
> (image.map! rgb.bluer picture)
But what if we want more interesting filters, ones that can't be described with just a single built-in transformation? One thing that we can do is to combine transformations. There are two ways to transform an image using more than one transformation: You can do each transformation in sequence, or you can use function composition, an old mathematical trick, to first combine the transformations. Consider, for example, the problem of lightening an image and then increasing the red component. We can certainly write the following:
> (image.map! rgb.lighter picture)
> (image.map! rgb.redder picture)
However, what we really want to do is, for each pixel in the picture,
make it lighter and then make it redder. In mathematics, when you
want to build a function that does the work of two functions, you
compose those functions. In DrFu, the compose
operator lets you combine multiple functions into one. Here's a
new function that makes colors lighter and then redder.
> (define lr (compose rgb.redder rgb.lighter))
> (define sample (cname->rgb "blue violet"))
> (rgb->string (lr sample))
"183/111/175"
Using function composition, we can therefore rewrite the earlier transformation as
> (image.map! lr picture)
or, as simply,
> (image.map! (compose rgb.redder rgb.lighter) picture)
In most of the initial readings for this course, you were learning some
basic operations that you could use in writing algorithms. In this
reading, while you learned about new operations, they were a different
kind of operation. image.map, image.map!,
and compose all provide a way to add control
to
your program. The two maps do something to each component of the image,
while composition sequences operations within a single Scheme
instruction.
Janet Davis (davisjan@cs.grinnell.edu)
Created September 7, 2007 based on http://www.cs.grinnell.edu/~rebelsky/Courses/CS151/2007F/Readings/transforming-rgb-reading.html