CSC151.01 2009F Functional Problem Solving : Readings
Primary: [Front Door] [Schedule] - [Academic Honesty] [Instructions]
Current: [Outline] [EBoard] [Reading] [Lab] - [Assignment]
Groupings: [Assignments] [EBoards] [Examples] [Exams] [Handouts] [Labs] [Outlines] [Projects] [Readings]
References: [A-Z] [By Topic] - [Scheme Report (R5RS)] [R6RS] [TSPL4]
Related Courses: [CSC151.02 2009F (Weinman)] [CSC151.02 2009S (Davis)] [CSC151 2008S (Rebelsky)]
Misc: [SamR] [MediaScript] [GIMP]
Summary: We explore how to expand the power of color transformations, first by applying them to images rather than to individual pixels then by combining them into new transformations.
If you think back to the end of the previous reading on
transforming RGB images, you may recall that we were starting
to write filters that transformed not just individual colors but whole
images. Here is one set of commands that transforms a four-by-three
image called canvas
.
(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 1 0 rgb-complement) (image-transform-pixel! canvas 1 1 rgb-complement) (image-transform-pixel! canvas 1 2 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 3 0 rgb-complement) (image-transform-pixel! canvas 3 1 rgb-complement) (image-transform-pixel! canvas 3 2 rgb-complement)
That's an awful lot of typing. (Even though it seems that we've been typing a lot over the past few days, we do want to type less, and will look for techniques for doing so.) Think about what happens in a 200x200 image, which has 40,000 positions. It's also a lot to change if we decide to, say, make the image darker rather than complementing it.
So, is there a better way to write image filters? That is, how
can we write filters that are faster, that are pure when we want
purity, and that don't require thousands of lines of incredibly
repetitive code? MediaScript includes a pair of helpful procedures,
(
and
image-variant
image
colortrans
)(
. The first builds a
new image by setting each pixel in the new image to the result of
applying the given color transformation to every pixel in another image.
The second changes an existing image by applying the transformation
“in place”. You will explore the differences between
the two in the lab.
image-transform!
image
colortrans
)
Using image-variant
(or image-transform!
)
and the color transformations we learned in the previous reading, we can
now transform images in a few basic ways: we can lighten images,
darken images, complement images (and perhaps even compliment the
resulting images), and so on and so forth.
As you may recall, we started this process in the previous reading, as we began to consider how one writes filters that create new images from old. We are now almost done on this quest. We've learned some new functions provided by MediaScript, such as the transformations. We've learned about one new technique, refactoring, which involves writing new functions that encapsulate common code. We've seen that Scheme permits procedures to take other procedures as parameters, and that this permission supports refactoring. 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.
>
(define bluer-picture (image-variant picture rgb-bluer))
Let's consider a few examples. We'll start with this public domain
image of a kitten, which we will refer to as kitten
.
http://public-photo.net/displayimage-2485.html
Here are a few variants of the image.
(image-variant kitten rgb-redder)
(image-variant kitten rgb-lighter)
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:
>
(define intermediate-picture (image-variant picture rgb-redder))
>
(define modified-picture (image-variant intermediate-picture rgb-lighter))
It is not necessary to name the intermediate image. We can instead choose
to nest the calls to image-variant
,
using something like
>
(define modified-picture (image-variant (image-variant picture rgb-redder) rgb-lighter))
However, even this more concise instruction still creates the intermediate,
redder, version of the picture.
What we really want to do is make just one new image in which
the color transformation is “lighter and redder”,
not one image which is redder and another which is lighter than the
intermediate image. In mathematics, when you want to build a function
that does the work of two functions, you compose
those functions. In MediaScript, the compose
procedure lets you combine multiple procedures into one. Here's a
new procedure that makes colors lighter and then redder.
>
;(define lr (compose rgb-redder rgb-lighter))
>
;(define sample (color-name->rgb "blueviolet"))
>
;(rgb->string sample)
"138/43/226"
>
;(rgb->string (lr sample))
"186/59/242"
Using function composition, we can therefore rewrite the earlier transformation as
>
(define modified-picture (image-variant picture lr))
or, as simply,
>
(define modified-picture (image-variant picture (compose rgb-redder rgb-lighter)))
(image-variant kitten (compose rgb-lighter rgb-redder))
What's the difference between this instruction and the nested calls to
image-variant
? In effect, we've changed the way
you sequence operations. That is, rather than having to write multiple
instructions, in sequence, to get something done, we could instead
insert information about the sequencing into a single instruction.
By using composition, along with nesting, we can then express our
algorithms more concisely and often more clearly. It is also likely to
be a bit more efficient, since we make one new image, rather than two.
Because compose
is a bit long to write, because
a circle is used for the mathematical composition function, and because
we sometimes want to compose more than two procedures, MediaScript also
provides a procedure, (
,
that composes all of the procedures (applying them from right to
left).
o
proc1
proc2
...
procn-1
procn
)
(image-variant kitten (o rgb-bluer rgb-bluer rgb-bluer))
As we saw in the previous reading, instead of relying on the primary RGB transformations we can also write our own transformations. Let's start with a transformation that extracts just the red component of a color, setting the other two components to zero.
;;; Procedure: ;;; rgb-only-red ;;; Parameters: ;;; rgb, an RGB color ;;; Purpose: ;;; Create a new RGB color that contains only the red component ;;; of rgb. ;;; Produces: ;;; red, an RGB color ;;; Preconditions: ;;; [No additional] ;;; Postconditions: ;;; (rgb-red red) = (rgb-red rgb) ;;; (rgb-green red) = 0 ;;; (rgb-blue red) = 0 (define rgb-only-red (lambda (rgb) (rgb-new (rgb-red rgb) 0 0)))
(image-variant kitten rgb-only-red)
Similarly, we can write a transformation that sets the red component to zero.
;;; Procedure: ;;; rgb-no-red ;;; Parameters: ;;; rgb, an RGB color ;;; Purpose: ;;; Create a new RGB color whose red component is zero. ;;; Produces: ;;; not-red, an RGB color ;;; Preconditions: ;;; [No additional] ;;; Postconditions: ;;; (rgb-red not-red) = 0 ;;; (rgb-green not-red) = (rgb-green rgb) ;;; (rgb-blue not-red) = (rgb-blue rgb) (define rgb-no-red (lambda (rgb) (rgb-new 0 (rgb-green rgb) (rgb-blue rgb))))
(image-variant kitten rgb-no-red)
If you think back to our initial encounter with compose
,
we did something that may be a bit surprising. We first noted that
image-variant
takes a procedure as its second parameter.
We then built our own procedure, lr
, and used it to
modify the picture.
>
;(define lr (compose rgb-redder rgb-lighter))
>
(define modified-picture (image-variant picture lr))
That, in itself, should not be too surprising (except that we've found
a new way to define functions, without using lambda). What may be
surprising is what we did next. We replaced
lr
with the compose
expression.
>
(define modified-picture (image-variant picture (compose rgb-redder rgb-lighter)))
As you may recall from our discussion of Scheme evaluation, when
the Scheme interpreter encounters a define
,
it effectively enters the name and the corresponding value into a table.
Then, when it sees a name later, it looks up the name, and gets the
corresponding value (in this case, an expression). In substituting the
compose
expression for lr
, we
sidestepped one phase of the Scheme interpreter. Why? Because it lets
us avoid taking the time to name that expression.
We can even do the same thing with procedures defined using the more
familiar lambda
expression. Consider, for example,
our instruction to build a variant of the sample image with only a red
component.
(define rgb-only-red (lambda (rgb) (rgb-new (rgb-red rgb) 0 0)))
>
(image-variant kitten rgb-only-red)
Instead of relying on the Scheme interpreter to substitute in the lambda expression, we can write the lambda expression ourselves. That is, we can write
>
(image-variant kitten (lambda (rgb) (rgb-new (rgb-red rgb) 0 0)))
Now, when we want a different variant, say, one with only the blue component,
we need not define a new procedure. We just plug in the body of that
procedure into a call to image-variant
.
(image-variant kitten (lambda (rgb) (rgb-new 0 0 (rgb-blue rgb))))
Similarly, here's a variant in which we swap the red and blue components.
(image-variant kitten
(lambda (rgb)
(rgb-new (rgb-blue rgb) 0 (rgb-red rgb))))
(Think about what Andy Warhol could have done with these variants!)
You'll note that in neither case did we name the procedure that we used to transform the colors in the image. Procedures that are used without naming them are called anonymous procedures. While there are a host of reasons for using anonymous procedures, we frequently use anonymous procedures when we expect to use a procedure only once. In the cases above, keeping only the blue component or swapping the red and blue components are not typical transformations. Hence, it is probably not worth the time and effort to write (and, as importantly, to document) new procedures.
(image-variant
image
fun
)
image
, each of whose pixels is computed
by applying fun
to the color of the
corresponding pixel in image
.
(image-transform!
image
fun
)
image
in place by setting
each pixel to the result of applying fun
to
that current pixel color.
(compose
f
g
)
((compose f g) x)
is the same as (f (g x))
.
(o
f1
f2
...
fn-1
fn
)
f
, in turn, starting with
fn
and
working backwards. The composition, when applied to a value,
x
, produces the same result as
(f1
(f2
(...
(fn-1
(fn x)))))
.
Primary: [Front Door] [Schedule] - [Academic Honesty] [Instructions]
Current: [Outline] [EBoard] [Reading] [Lab] - [Assignment]
Groupings: [Assignments] [EBoards] [Examples] [Exams] [Handouts] [Labs] [Outlines] [Projects] [Readings]
References: [A-Z] [By Topic] - [Scheme Report (R5RS)] [R6RS] [TSPL4]
Related Courses: [CSC151.02 2009F (Weinman)] [CSC151.02 2009S (Davis)] [CSC151 2008S (Rebelsky)]
Misc: [SamR] [MediaScript] [GIMP]
Copyright (c) 2007-9 Janet Davis, Matthew Kluber, Samuel A. Rebelsky, and Jerod Weinman. (Selected materials copyright by John David Stone and Henry Walker and used by permission.)
This material is based upon work partially supported by the National Science Foundation under Grant No. CCLI-0633090. Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the National Science Foundation.
This work is licensed under a Creative Commons
Attribution-NonCommercial 2.5 License. To view a copy of this
license, visit http://creativecommons.org/licenses/by-nc/2.5/
or send a letter to Creative Commons, 543 Howard Street, 5th Floor,
San Francisco, California, 94105, USA.