Transforming RGB Colors
- Summary
- We examine the building blocks of one of the common kinds of algorithms used for RGB colors: Generating new colors from existing colors.
Introduction
We have just started to learn about RGB colors, and so the operations we might do with colors are somewhat basic. How will we expand what we can do, and what we can write? In part, we will learn new Scheme techniques, applicable not just to image computation, but to any computation. In part, we will learn new functions in the Mediascheme library that support more complex image computations.
We’ve been exploring how one expresses colors using the RGB representation, as well as an integer encoding of that representation. Once we understand that representation, what can we do next? One obvious thing is to compute new colors from old; given a color, we can often compute a related color (e.g., a darker version, a lighter version, the complement, something with increased red, and so on and so forth).
Why is it useful to be able to transform a color? Because a typical image is just made up of a lot of individual colors. And once you can transform one color, you can transform the whole image. One common algorithmic approach to images is the construction of filters, algorithms that systematically convert one image to another image. Complex filters can do a wide variety of things to an image, from making it look like the work of an impressionist painter to making it look like the image has been painted onto a sphere. However, it is possible to write simple filters with not much more Scheme than you know already.
Over the next few readings and labs we will consider filters that are constructed by transforming each color in an image using an algorithm that converts one RGB color to another. In the first RGB lab, you began to think about such algorithms as you computed the pseudo-complement of an RGB color or varied the components of the color. In this reading and the corresponding lab, we will consider the basic building blocks of filters: Mediascheme’s basic operations for transforming colors and the ways to combine them into more complex color transformations. In the next reading, we will see how to use those transformations to transform whole images. After that, we’ll explore how you write your own color and image transformations.
Some Basic Transformations
We will start with a few basic transformations that Mediascheme includes.
Making Colors Lighter and Darker
The simplest transformations are irgb-darker
and irgb-lighter
. These operations make a color a little bit darker and a little bit lighter. If you apply them repeatedly, you can make darker and darker (or lighter and lighter) colors, eventually reaching black (or white).
> (define sample (color-name->irgb "blueviolet"))
> (irgb->string sample)
"138/43/226"
> (define darker-sample (irgb-darker sample))
> (irgb->string darker-sample)
"122/27/210"
> (define lighter-sample (irgb-lighter sample))
> (irgb->string lighter-sample)
"154/59/242"
> (define doubly-darker-sample (irgb-darker (irgb-darker sample)))
> (irgb->string doubly-darker-sample)
"106/11/194"
(color-swatch sample)
(color-swatch sample darker-sample)
(color-swatch sample lighter-sample)
(color-swatch sample doubly-darker-sample)
Note that these are pure procedures. When you compute a darker or lighter version of a color, the purpose is to create a new color. Hence, the original color is unchanged.
Transforming Components
In addition to making the color uniformly darker or lighter, we can also increase individual components using irgb-redder
, irgb-greener
, and irgb-bluer
.
> (define sample (color-name->irgb "blueviolet"))
> (irgb->string sample)
"138/43/226"
> (irgb->string (irgb-redder sample))
"170/43/226"
> (irgb->string (irgb-greener sample))
"138/75/226"
> (irgb->string (irgb-bluer sample))
"138/43/255"
(color-swatch sample (irgb-redder sample))
(color-swatch sample (irgb-greener sample))
(color sample (irgb-bluer sample))
As the examples suggest, for some people, making a color slightly redder, greener, or bluer can be hard to detect. Sometimes it’s easier to see the changes if we make the transformations a few times. (Since the first call to irgb-bluer
increases the blue component to its largest value, we won’t see further increases.)
(color-swatch sample (irgb-redder (irgb-redder sample)))
(color-swatch sample (irgb-greener (irgb-greener sample)))
(color-swatch sample (irgb-bluer (irgb-bluer sample)))
Other Basic Transformations
The irgb-rotate
procedure rotates the red, green, and blue components of a color, setting red to green, green to blue, and blue to red. It is intended mostly for fun, but it can also help us think about the use of these components.
The irgb-phaseshift
procedure is another procedure with less clear uses. It adds 128 to each component with a value less than 128 and subtracts 128 from each component with a value of 128 or more. While this is somewhat like the computation of a pseudo-complement, it also differs in some ways. Hence, Mediascheme also provides an irgb-complement
procedure that computes the pseudo-complement of an RGB color.
> (define sample (color-name->irgb "blueviolet"))
> (irgb->string sample)
"138/43/226"
> (irgb->string (irgb-rotate sample))
"43/226/138"
> (irgb->string (irgb-phaseshift sample))
"10/171/98"
> (irgb->string (irgb-complement sample))
"117/212/29"
(color-swatch sample (irgb-rotate sample))
(color-swatch sample (irgb-phaseshift sample))
(color-swatch sample (irgb-complement sample))
Combining Colors
You are probably familiar with a painter’s palette, which many painters use to mix colors. You will eventually develop your own ways to mix RGB colors. For now, we’ll start with three mechanisms for mixing colors.
(irgb-add irgb1 irgb2)
combines two IRGB colors by adding the values of their individual components. If the sum of any two components is above 255, it gets capped at 255. As you might expect, this approach means that adding colors generally gives a brighter color.(irgb-average irgb1 irgb2)
combines two IRGB colors by averaging the values of their individual components.(irgb-subtract irgb1 irgb2)
combines two IRGB colors by subtracting the second from the first. If the difference is below 0, we use 0.irgb-subtract
is often the inverse ofirgb-add
, but not always. (You should consider when it’s not.)
Here are some examples, in both code and image form.
> (define darkred (irgb 192 0 0))
> (define medgrey (irgb 128 128 128))
> (irgb->string (irgb-add darkred medgrey))
"255/128/128"
> (irgb->string (irgb-average darkred medgrey))
"160/64/64"
> (irgb->string (irgb-subtract darkred medgrey))
"64/0/0"
> (irgb->string (irgb-subtract medgrey darkred))
"0/128/128"
(color-swatch darkred (irgb-add darkred medgrey) medgrey)
(color-swatch darkred (irgb-average darkred medgrey) medgrey)
(color-swatch darkred (irgb-subtract darkred medgrey) medgrey)
(color-swatch medgrey (irgb-subtract medgrey darkred) darkred)
Composing Transformations
What do we want to do when we want to combine transformations? Say, what if we want to make a color both redder and darker? One option is to simply sequence the transformations, as we did in some examples before.
> (define redder-darker-sample (irgb-redder (irgb-darker sample)))
In most cases, that approach seems to work fairly well. However, if we want to apply the same combination of transformations to multiple colors, we can end up retyping a lot of the same code again and again.
> (define variant1 (irgb-complement (irgb-redder (irgb-redder (irgb-complement color1)))))
> (define variant2 (irgb-complement (irgb-redder (irgb-redder (irgb-complement color2)))))
> (define variant3 (irgb-complement (irgb-redder (irgb-redder (irgb-complement color3)))))
> (define variant4 (irgb-complement (irgb-redder (irgb-redder (irgb-complement color4)))))
What can we do? If you think back to algebra, you might remember the composition operation, o. As we learned it, (f o g)(x) = f(g(x)). (Yes, that’s the traditional style of parentheses for mathematics, not what we’re now using in Scheme.)
Conveniently, our variant of Scheme includes a similar operation called compose
. Let’s look at a simple example. In this example, you’ll note that increment
means “add 1” and square
means “multiply by itself.” Just as we define the result of an expression, we can similarly define the function created by composing two functions.
> (increment 1)
2
> (square 2)
4
> (define is (compose square increment))
> (is 1)
4
> (is 2)
9
> (define si (compose increment square))
> (si 1)
2
> (si 2)
5
> (si 3)
10
The Scheme compose
function differs from the mathematical composition operation in that the Scheme version lets us compose as many operations as we want.
> (define add4 (compose increment increment increment increment))
> (define to-the-8th (compose square square square))
> (add4 1)
5
> (add4 11)
15
> (to-the-8th 2)
256
> (to-the-8th 10)
100000000
Why is this useful? Because it means we can define a new color transformation as a composition of other transformations. So, instead of writing the four very repetitious lines above, we can instead write the following.
> (define irgb-modify (compose irgb-complement irgb-redder irgb-redder irgb-complement))
> (define variant1 (irgb-modify color1))
> (define variant2 (irgb-modify color2))
> (define variant3 (irgb-modify color3))
> (define variant4 (irgb-modify color4))
Isn’t that much nicer?
Reviewing Key Concepts
What should you take away from what we’ve just learned? You now know a few more functions in Mediascheme, particularly functions that transform colors. You’ve learned a few functions for combining colors. You’ve also learned how to define a few of your own functions by composing existing functions with compose
. You’re now ready to write a few simple transformations of your own.
For the immediate future, knowing the particular transformations will be helpful. Over the longer term, knowing about composition and the techniques that support composition will be even more helpful.
Reference
(irgb-lighter irgb-color)
MediaScheme Color Procedure.- Build a lighter version of the given color.
(irgb-darker irgb-color)
MediaScheme Color Procedure.- Build a darker version of the given color.
(irgb-redder irgb-color)
MediaScheme Color Procedure- Build a redder version of the given color.
(irgb-greener irgb-color)
MediaScheme Color Procedure.- Build a greener version of the given color.
(irgb-bluer irgb-color)
MediaScheme Color Procedure.- Build a bluer version of the given color.
(irgb-rotate irgb-color)
MediaScheme Color Procedure.- Rotate the three components of the given color, setting the red component to the value of the green component, the green component to the value of the blue component, and blue compnent to the value of the red component.
(irgb-phaseshift irgb-color)
MediaScheme Color Procedure.- “Phase shift” the color by adding 128 to components less than 128 and subtracting 128 from components greater than 128.
(irgb-complement irgb-color)
MediaScheme Color Procedure.- Compute the psuedo-complement of the given color.
(irgb-add irgb-color-1 irgb-color-2)
MediaScheme Color Procedure.- Add the corresponding RGB components of
irgb-color-1
andirgb-color-2
. If any component sum is greater than 255, uses 255 for the resulting component. (irgb-average irgb-color-1 irgb-color-2)
MediaScheme Color Procedure.- Average the corresponding RGB components of
irgb-color-1
andirgb-color-2
. (irgb-subtract irgb-color-1 irgb-color-2)
MediaScheme Color Procedure.- Subtract the RGB components of
irgb-color-2
from the corresponding components ofirgb-color-1
. If any component difference is less than 0, uses 0 for the resulting component. (compose f g)
Traditional Higher-Order Procedure.- Build a one-parameter procedure that applies g to its parameter, and then f to that result.
((compose f g) x)
is the same as(f (g x))
.
Self Checks
Check 1: Combining Colors
As you may have noted, the expression (irgb-subtract medgrey darkred)
gave a greenish color. Explain why.
Check 2: Procedures as Parameters
In the reading, we considered ways to combine procedures. When we wanted a procedure that took a value, added 1, and then squared it, we wrote something like the following.
> (define is (compose square increment))
Why do you think we didn’t (or couldn’t) just write the following instruction instead?
> (define is (square increment))
Check 3: Parts of Algorithms
As you think about the components of an algorithm, what roles does composition serve?