This site is under construction. Please expect some changes.
In our initial exploration of images, you learned about how to make images using simple shapes. In our initial work, we limited ourselves to named colors. However, one of the great advantages of computational image making is that it is possible to describe colors that do not have a name. In fact, it is often better to use a more precise definition than is possible with a name. After all, we may not agree on what precisely something like “springgreen” or “burlywood” means. (One color scheme that we’ve found has both “Seattle salmon” and “Oregon salmon”. Would you know how those two colors relate?)
In fact, it may not only be more accurate to represent colors non-textually, it may also be more efficient, since the computer will often need to look up color names in a table to convert them to an underlying representation.
The most popular scheme for representing colors for display on the computer screen is RGB. In this scheme, we build each color by combining varying amounts of the three primary colors, red, green, and blue. (What, you think that red, yellow, and blue are the primary colors? It turns out that primary works differently when you’re transmitting light, as on the computer screen, than when you’re reflecting light, as when you color with crayons on paper.)
So, for example, purple is created by combining a lot of red, a lot of blue, and essentially no green. You get different purple-like colors by using different amounts of red and blue or even different ratios of red and blue.
When we describe the amount of red, green, and blue, we traditionally use integers between 0 and 255 to describe each component color. Why do we start with 0? Because we might not want any contribution from that color. Why do we stop with 255? Because 255 is one less than 2
If there are 256 possible values for each component, then there are 16,777,216 different colors that we can represent in standard RGB. Can the eye distinguish all of them? Not necessarily. Nonetheless, it is useful to know that this variety is available, and many eyes can make very fine distinctions between nearby colors.
Racket usually adds a fourth component to colors: an alpha value. We will ignore that for the time being. But you may see an extra 255 being added to the end of colors you create, at least when you view them in numeric form.
Let us now turn to the primary procedures we will use to work with RGB colors.
We build a new color with the (rgb red-component green-component blue-component
)` procedure. We can also set the opacity of the color by adding a fourth component, typically referred to as the alpha channel or just alpha. With with the other components, the alpha channel is between 0 and 255.
Here are a few colors.
> (rgb 255 0 0)
> (rgb 0 255 0)
> (rgb 0 128 0)
> (rgb 255 0 255)
> (rgb 191 0 191)
> (rgb 127 0 127)
> (rgb 127 0 192)
> (rgb 192 0 127)
> (rgb 0 0 255)
> (rgb 0 0 255 255)
> (rgb 0 0 255 192)
> (rgb 0 0 255 128)
> (rgb 0 0 255 64)
> (rgb 0 0 255 0)
Can you explain why we chose each set of examples?
You may note that we often prefer to use color names. We can convert a color name to an RGB color using (color-name->rgb name)
.
> (color-name->rgb "purple")
> (color-name->rgb "salmon")
> (color-name->rgb "yellow")
If you give color-name->rgb
something other than a color name, it will return the special value #f
, which represents “false”.
> (color-name->rgb "csc151")
#f
But what color names are available? The procedure (all-color-names)
, which takes no parameters, gives you all the valid color names.
> (all-color-names)
'("aliceblue"
"antiquewhite"
"aqua"
"aquamarine"
"azure"
...
"whitesmoke"
"yellow"
"yellow green"
"yellowgreen")
There are 181 names, including the with/without space equivalents, such as "yellow green"
and "yellowgreen"
.
Since you may not want to peruse the full list, there’s also a find-colors
procedure.
> (find-colors "violet")
'("blue violet" "blueviolet" "darkviolet" "medium violet red"
"mediumvioletred" "palevioletred" "violet" "violet red" "violetred")
It returns an empty list when you give it something that’s not a color name.
> (find-colors "ugly")
null
Obviously, just seeing a color on the screen doesn’t let you compute with it. Hence, there are procedures to extract the red, green, blue, and alpha components of any RGB color.
> (color-name->rgb "palevioletred")
> (rgb-red (color-name->rgb "palevioletred"))
219
> (rgb-green (color-name->rgb "palevioletred"))
112
> (rgb-blue (color-name->rgb "palevioletred"))
147
> (rgb-alpha (color-name->rgb "palevioletred"))
255
In creating works, many artists and visual designers consider the applications of complementary colors. A pair of colors is complementary if the sum of the two colors is a kind of grey (including black or white). What does it mean to sum two colors? Well, it turns out that complementarity is really defined only for a different representation of colors (hue, saturation, and value, or HSV). Nonetheless, we can come close to simulating it in RGB, so we will call complementary colors defined using their RGB values pseudo-complementary colors.
In RGB, we can add the colors by adding the corresponding components (capping the sum at 255) or by averaging the corresponding components. We’ll use the former technique, because it can be a bit easier to analyze capping.
For example, the pseudo-complement of green (0/255/0) is magenta (255/0/255) because when we add them together, we get 255/255/255, which is white.
> (rgb 0 255 0)
> (rgb 255 0 255)
Depending on what you accept as the definition of “grey”, colors can have many pseudo-complements. For example, consider the color 128/0/0, which is similar to maroon. One logical pseudo-complement to that color is 127/255/255 (a color for which there is no name, but which seems to be similar to aquamarine), since when we add the two colors together, we get 255/255/255, which is still white. However, one might also consider 0/128/128 (a color similar to teal) as a pseudo-complement, since when we add the two together, we get 128/128/128, a nice medium grey.
> (rgb 128 0 0)
> (rgb 127 255 255)
> (rgb 128 0 0)
> (rgb 0 128 128)
> (rgb 128 128 128)
In general, when we say “pseudo-complementary color”, we mean the one which, when we add the RGB components to those of the first color, we get white. When we ask for multiple pseudo-complements for the same color, we’ll mean those that, when added, give us a color in which all three components are the same (that is, a version of grey).
As the design detour suggests, we will often want to build new colors from prior colors. For example, given a color, we might want to complete the pseudo-complement of that color. Rather than doing it by hand, we can have the computer do the computation for us.
How? The algorithm should be fairly straightforward.
c
is 255 minus the
red component of `c.c
is 255 minus the
green component of `c.c
is 255 minus the
blue component of `c.rgb
.;;; (color-pseudo-complement c) -> color?
;;; c : color?
;;; Compute the pseudo-complement of a color
(define color-pseudo-complement
(lambda (c)
(rgb (- 255 (rgb-red c))
(- 255 (rgb-green c))
(- 255 (rgb-blue c)))))
Let’s give it a try.
> (color-pseudo-complement (rgb 255 0 255))
> (rgb-red (color-pseudo-complement (rgb 255 0 255)))
0
> (rgb-green (color-pseudo-complement (rgb 255 0 255)))
255
> (rgb-blue (color-pseudo-complement (rgb 255 0 255)))
0
We can, of course, write color transformations that do a wide variety of things. For example, if we want to simulate the experience of people who cannot readily distinguish red and green, we can set both the red and green components to something closer to the average of the red and green components of the original. (This approach doesn’t really give you the experience of red-green color-blind people, but it may give some sense.)
;;; (rgb-merge-red-green r) -> rgb
;;; r : rgb?
;;; Make both the red and green components closer to the average of the
;;; two components.
(define rgb-merge-red-green
(lambda (c)
(rgb (quotient (+ (rgb-red c) (rgb-red c) (rgb-green c)) 3)
(quotient (+ (rgb-red c) (rgb-green c) (rgb-green c)) 3)
(rgb-blue c))))
Let’s try it out.
> (rgb-merge-red-green (rgb 0 0 0))
> (rgb-merge-red-green (rgb 255 0 0))
> (rgb-merge-red-green (rgb 0 255 0))
> (rgb-name->rgb "violet")
> (rgb-merge-red-green (color-name->rgb "violet"))
It certainly did something. We probably need more context to see if it achieved our goals.
One way to get more context is to use the color transformations on complete images. The procedure (pixel-map color-transformation image)
does just that.
There are several ways to load an image with Scamper.
One of them, with-image-file
, allows to load an image file, e.g., a PNG or JPEG, from our local computer.
(with-image-file fn)
outputs a file chooser widget that we can use to select an image file.
Once that file is selected, the argument fn
is run with the image loaded from the file.
fn
is a one-argument function that takes an image (loaded from disk) as input and produces a potentially modified version of the input image as output.
For example, suppose we had an image of a kitten on disk:
We can invoke with-image-file
and select that image to process it.
If we want to simply output the original image to the screen, we can provide a lambda
that simply returns the original image given to it as input:
(with-image-file
(lambda (img) img))
This transformation function is relatively boring. Putting together what we learned in this reading, we can now compute the complement of every pixel in the image. (More about pixels in a subsequent reading.)
(with-image-file
(lambda (img) (pixel-map color-pseudo-complement img)))
Sure, that looks a bit like a color negative, right? (Have you ever seen a color negative? Has the author of this piece dated themselves?)
How about our other procedure?
(with-image-file
(lambda (img) (pixel-map color-merge-red-green img)))
Fewer differences there, but we do see something happening.
What value do you expect for each of these expressions?
a. (rgb-red (rgb 200 100 50))
b. (rgb-green (rgb 200 100 50))
c. (rgb-blue (rgb 200 100 50))
d. Check your answers experimentally.
What value do you expect for each of these expressions? (Please guess about the components on a-f; it’s fine if you are not quite right.)
a. (rgb-red (color-name->rgb "red"))
b. (rgb-green (color-name->rgb "red"))
c. (rgb-blue (color-name->rgb "red"))
d. (rgb-red (color-name->rgb "darksalmon"))
e. (rgb-green (color-name->rgb "darksalmon"))
f. (rgb-blue (color-name->rgb "darksalmon"))
g. Check your answers experimentally.
Write a procedure, (remove-blue color)
, that takes a rgb
color as a parameter and removes the blue component of the color, setting it to 0 in the new color.
> (remove-blue (color-name->rgb "white"))
> (remove-blue (color-name->rgb "blue"))
> (remove-blue (color-name->rgb "purple"))
> (remove-blue (rgb 255 0 255))
The kitten image was downloaded from http://public-photo.net/displayimage-2485.html. Unfortunately, the site behind that URL has disappeared. Nonetheless, the kitten image lives on.