This reading has been significantly rewritten for Fall 2015. Some errors may have been introduced.
Summary: We explore how to expand the power of color transformations, first by applying them to images rather than to individual colors then by further combining them into new transformations.
You may have been asking why we have been focusing on transforming individual colors, other than that using the transformations helps us better understand the underlying representation. Here's one reason: Many image filters are written by applying a transformation to each color in the image.
So, how do we write image filters? That is, how do we generalize
color transformations to image filters?
MediaScript includes a helpful procedure,
(, that builds a new
image by setting the color at each position in the new image to
the result of applying the given color transformation to the color
at the corresponding position in the original image.
image-variant image
colortrans)
Using image-variant 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.
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 two variants of the image.
(image-variant kitten irgb-redder)
(image-variant kitten irgb-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 that you learned how to do in Scheme in the last reading. Consider, for example, the problem of lightening an image and then increasing the red component. We can certainly write the following sequence of definitions.
>(define intermediate-picture (image-variant picture irgb-redder))>(define modified-picture (image-variant intermediate-picture irgb-lighter))
However, it is not necessary to name the intermediate image.
We can instead choose to nest the calls to
image-variant, using something like this
definition.
>(define modified-picture (image-variant (image-variant picture irgb-redder) irgb-lighter))
However, even this more concise instruction still creates the intermediate (redder but not lighter) version of the picture. Can we make each color in the image both redder and lighter?
In the previous reading, we learned that we can define a new transformation
by combining other transformations with the composition function,
. Using that function, we can write
the following instructions.
compose
>(define irgb-fun (compose irgb-lighter irgb-redder))>(image-variant picture irgb-fun)
But even that is a bit verbose. Do we really want to name
when we only use it
once? No. Fortunately, Scheme lets us use the function created
by irgb-fun without naming it, just as
it lets us use most expressions without naming them.
compose
>(define modified-picture (image-variant picture (compose irgb-lighter irgb-redder)))
(image-variant kitten (compose irgb-lighter irgb-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 can 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.
It may make sense to be able to transform an image by transforming
every color in it. But where do we get the images to start with?
Mediascheme include an
function that loads an image from the computer and returns an integer
that we can use to identify the image in calls to
image-load and
image-show.
The image-variant
function that takes one parameter, a string that gives the full
path to the file containing the image. (You may recall that
“string” is the term we use for a sequence of text in Scheme,
and that we surround strings with double quotation marks. For
example, image-load"/home/rebelsky/Desktop/kitten.jpg".
For example, here's a sequence of operations that show the image of the kitten that we use in this reading.
>(define pic (image-load "/home/rebelsky/Desktop/kitten.jpg"))>(image-show pic)
You'll note that there are two separate “names” for
the image. In the file system, we name it
"/home/rebelsky/Desktop/kitten.jpg". However, the
Scheme program has no natural way of understanding whether that is
intended to be the name of a file, or a color name, or a student
name, or something else. The
command tells DrRacket what the string represents and converts it into
a form that we can then use. And, is our habit whenever we compute a
value, we name that computed value, in this case as image-loadpic.
It's useful to be able to load images so that we can manipulate them.
It's even more useful to be able to save images that we've
created. The procedure (
saves an image to a specified file. You should
provide the full path name to the file, surrounding it by
double-quotation marks. For example, you might use something like
image-save
image filename)"/home/student/images/masterpiece.png". The suffix you
give to the file name determines the type of file that is saved - jpg,
gif, png, and so forth.
Here's a simple sequence of operations to make and save a strange version of our kitten.
>(define pic (image-load "/home/rebelsky/Desktop/kitten.jpg"))>(image-show pic)>(define strange (image-variant pic (compose rgb-complement rgb-rotate)))>(image-show strange)>(image-save strange "/home/student/Desktop/strange-kitten.png")
Note that you can use image-save with any image
we make, whether it's created with
,
image-load,
or any of the other image-making and image-modifying commands
we will learn.
image-variant
So far, so good. We know how to load images with
. We know
how to make new versions of existing images using
image-load. We know how to
save the result using image-variant.
Are we missing anything?
image-save
It turns out that we're missing a few things. Right now,
the only way we can make a variant of an image is using
one of the unary (single-parameter)
procedures, either the built-in procedures, such as
, or ones we create
with the composition operator, irgb-redder.
But we know other procedures transforming colors, such as
compose, that are not
unary. Can we use them?
irgb-subtract
It doesn't make sense to write , since (irgb-subtract picture
(irgb 0 0 255))picture is not
an integer-encoded RGB color. (Unfortunately, Mediascheme will let
us do so because both colors and images are represented as integers,
and it doesn't know what the integer represents.) It also doesn't
make sense to use as
the second parameter to irgb-subtract,
as in image-variant(image-variant pic irgb-subtract), because we don't have
a place to specify the color we are subtracting. In this case,
DrRacket will issue an error message.
>(define pic (image-load "/home/rebelsky/Desktop/kitten.jpg"))>(image-variant pic irgb-subtract)share/lib/gigls/guard.rkt:28:6: irgb-subtract: expects 2 parameters, given 1 in (irgb-subtract 3037011)
We might be tempted to write something like (image-variant
pic (irgb-subtract (irgb 0 0 255)). However, that
will also cause problems, since it looks like we are calling
on a single value,
and not the two values it is supposed to take.
irgb-subtract
>(image-variant pic (irgb-subtract (irgb 0 0 255)))share/lib/gigls/guard.rkt:28:6: irgb-subtract: expects 2 parameters, given 1 in (irgb-subtract 255)
In fact, it's probably good that we get an error message here, since
it's not clear whether (irgb 0 0 255) is supposed to be
the first or second parameter to irgb-subtract - are
we subtracting (irgb 0 0 255) from each color, or are
we subtracting each color from (irgb 0 0 255)?
To handle situations like this, our variant of Scheme includes a
special procedure, , that
lets you fill in some parameters to a function. It takes the form
section(. When we want
to fill in a particular parameter, we write the value we want.
When we want to leave a parameter blank, we write the special symbol
section procedure
param-info ...)<>. For example, here's a function that subtracts
the blue component from every color.
>(define irgb-subtract-blue (section irgb-subtract <> (irgb 0 0 255)))>(image-show (image-variant pic irgb-subtract-blue))
If, instead, we want to subtract the current color from white (which is how we computed the pseudo-complement), we can swap the place that we put the special symbol.
>(define irgb-sub-from-white (section irgb-subtract (irgb 255 255 255) <>))>(image-show (image-variant pic irgb-sub-from-white))
As in the case of unary functions created with , we don't have to name the function we create.
compose
>(image-show (image-variant pic (section irgb-average (irgb 0 0 127) <>)))
a. If you have not already done so, type the following in a terminal window.
/home/rebelsky/bin/csc151-update
b. Follow the steps for setting up the GIMP+Mediascheme combination from the lab on RGB colors.
c. Create a new relatively small image using color-grid.
Use a variety of colors, as well as black and white. For example,
(require gigls/unsafe)
(define sample (color-grid 10 5 3
"red" "white" "black"
"yellow" "green" "violet"
(irgb 128 0 128) (irgb 128 0 255) (irgb 255 0 128)))
d. Load the kitten image from the reading:
(define picture (image-load "/home/rebelsky/MediaScheme/Images/kitten.png"))
a. What do you expect to happen when you use
image-variant to complement each pixel in
sample, using the following instruction?
>(image-show (image-variant sample irgb-complement))
b. Check your answer experimentally.
c. What do you expect to have happen if you use
image-variant to complement each color in
picture? (You would use nearly the same instruction,
substituting picture for sample.)
d. Check your answer experimentally.
e. What do you expect to happen when you use
image-variant and compose
to doubly complement each color in sample, using the
following instruction?
>(image-show (image-variant canvas (compose irgb-complement irgb-complement)))
f. Check your answer experimentally.
a. Create a variant of sample using
irgb-redder.
b. You may have noticed that in check 2, we were able to undo the complement transformation by complementing again. Is there an easy way to undo the redden operation? (You do not have to write code; just explain how to do it.)
c. Are there transformations or sequences of transformations that would be impossible to undo? (That is, can you do something to an image such that there is nothing that you can do to the revised image that will bring back the original image?)
(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.
(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))))).
(image-load
filename)
(image-save
image
fname)
image in the specified file
(which should provide the full path to the file). The type
of the image (JPEG, GIF, PNG, etc.) is determined by the suffix
of the file name.
(section
procedure
value-or-hole-1
...
value-or-hole-n)
procedure, leaving
the rest as parameters for the result procedure. Holes
(parameters for the result proedure) are indicated with the
<> symbol.