Transforming Images

Summary: We extend our reach using procedures that take other procedures as parameters.

Contents:


From Transforming Pixels to Transforming Images

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.

Composing Transformations

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)

Key Concepts

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
Last revised September 9, 2007