This tutorial is about color, which is no doubt a very important aspect of
computer graphics.
First is explained how light is built up and why we actually see different
colors.
Then a some color models are explained: first the RGB color model used by
computers, and examples showing how to do color arithmetics in RGB, and then the
more intuitive HSL/HSV models are explained.
Then code is given that allows you to convert between color models, for example
to allow a user to pick a color using the HSV color model and then convert it to
RGB so the computer can use it, or to create rainbow gradients, or to change the
color of images.
Light
Before starting about color models, it's important to know how the human eye and
brain turn light into color.
Light itself is an electromagnetic wave. Electromagnetic waves are similar to
sound waves in that they contain different frequencies, but are electromagnetic
and can propagate in vacuum. EM waves are thus a signal that's made out of one
or more frequencies, for example the EM waves used by a microwave oven are very
high frequency, while radio waves are very low frequency. The eye is only
sensitive to a very narrow band of frequencies, namely the frequencies between
429 THz and 750 THz (1 THz = 1 TeraHertz = 10^12 Hz). All other EM waves can't
be seen.
Monochromatic light is light made up of one single pure frequency (this is
certainly not the general case, most light you see is multichromatic).
Monochromatic light looks to the eye as a pure color, and can never be white or
magenta. Since it contains only one frequency, the wave of monochromatic light
can be represented as a sine:
The height of the sine is the amplitude or how bright the light is. The width of
one period (called lambda) is the wavelength of the light, and is inversely
related to the frequency: since the light travels at 300000000 m/s, it's
wavelength is 300000000/f where f is the frequency. So the visible spectrum of
light has wavelengths from around 400 to 700 nm.(1 nm = 10^-9 meter).
This visible spectrum shows all the possible colors that can be made out of
monochromatic light. Some light sources, such as lasers and Natrium lights, send
out monochromatic light, but in general, light is multichromatic. For example,
the sun sends out white light, which is light that contains ALL frequencies!
That means the sum of red, yellow, green, blue and violet light looks like
white! Physically speaking, it's not white at all, it's the sum of a lot of sine
curves, but the human brain makes it look white. Color is thus something
psychologically, and not something physical.
Lightwaves are a sum of many different frequencies, or the sum of many sine
curves. Each of these sine curves has it's own frequency, and can have it's own
amplitude. A spectrum shows for each frequency the amplitude. For more
information about spectra in general, see the beginnings of the chapter about
Fourier Transforms.
Here's an example of such a spectrum:
It is the spectrum of a yellow LED I found. The top of the spectrum is the
Dominant Frequency, and that is the color our eyes will usually see if light
with this spectrum shines on it. If this yellow LED would have been
monochromatic, the spectrum would have looked like this instead:
And to the eyes, the color would look the same. So here an interesting fact
shows up: light with different spectra, can still look the same to the eye! If
the human eyes would be able to distinguish every single spectrum as a different
color, we would've been able to see gazillions of different colors, but the
human eye works differently and turns a whole spectrum into only 3 signals: the
amount of detected red, green and blue, and combinations of those make the
colors we can see.
The spectrum of white light is as follows (the height of the curve doesn't
really matter):
All frequencies are equally much in the light, only then it looks purely white
to the brain. In all other cases, a certain frequency will be dominant and then
that frequency will be the color the brain sees.
The spectrum of black light looks like this:
Indeed, there's no light at all, the amplitude of every frequency is zero. Black
is the color the brain gives to the absence of light.
The spectrum of magenta, a color that can't be made with monochromatic light,
could look like this:
Both blue and red have a high amplitude, and the mix of blue and red frequencies
looks like magenta or purple to the brain.
The Eye and Color Perception
This section isn't about how the physical structure of the eye and the lens
work, but about how the eye and the brain distinguish different colors.
So light falls on the retina, and on the retina are 2 types of cells with
photosensitive chemicals, photoreceptors: rods and cones. The rods only detect
whether or not light is present, and are important at night. So rods are
sensitive to the whole spectrum at once and can't tell what frequency the light
has, and thus can't provide any color information. To detect color, you'd need
photoreceptors that are sensitive to only a certain frequency. That's exactly
what the cones do:
There are 3 types of cones, those that are sensitive to red, those sensitive to
green, and those sensitive to blue. Such a rod isn't sensitive to a single
frequency, they overlap a bit, it's just sensitive mostly to a certain
frequency.
For example, yellow has a frequency between red and green. This yellow frequency
will excite both the red and green cones a bit, and the human brain converts the
signal "both red and green cones are excited" to "yellow". Even the blue cones
are still a bit excited by yellow light, but neglectable.
If light falls on the eye that has two frequencies: red and green, it'll also
excite both the red and green cones, so this light will show up as yellow as
well, even though it doesn't contain any yellow frequency at all.
If blue light falls on the retina, the blue cones are excited very strongly,
while the green and red ones will give only a neglectable signal. And the brain
turns the signal "mainly the blue cone is excited" to "blue".
White light contains all frequencies, so if white light falls on the
retina, all 3 types of cones are excited, and the brain turns the signal "green,
red and blue cones all excited" into "white".
The above explains how the brain creates different "hues" of colors out of the
incoming signal, but it also gives a certain brightness to the light, based on
how strong the incoming signal is: if it's very strong, the brain indicates it
as a very bright red, white, ..., but if it's very weak, it'll be almost black.
And then there's also the "saturation" of the color, this is based on the
relative difference in strength each color type gives: if the red signal is very
strong, but blue and green are also pretty strong, the color will have a low
saturation, it's red-grayish or red-whitish. If however the red signal would be
very strong, and the blue and green signal very weak, a very red color shows up.
Since different spectra can look exactly the same for us, and some animals have
different types of color receptors, it's possible that two colors that look the
same to us, look like two different colors for some animal.
The above process happens on every location of the retina separately, so that a
complex 2-dimensional image is formed where each location on the image can have
it's own color.
Thanks to the 3 types of cones, there are 8 (2^3) main colors one can
distinguish:
- No cones excited: Black
- Red cones excited, but not the Blue and Green ones: Red
- Green cones excited, but not the Blue and Red ones: Green
- Blue cones excited, but not the Red and Green ones: Blue
- Red and Green cones excited, but no the Blue ones: Yellow
- Blue and Green cones excited, but no the Red ones: Cyan
- Red and Blue cones excited, but no the Green ones: Magenta
- All three the cone types excited: White
You can of course distinguish much more colors than these 8 because each
receptor type can have different levels of excitement.
Color blindness means one or more of the color types of cones are missing or
less sensitive, for example if you miss the red one, you can only see the
difference between light that has mainly green and light that has mainly blue.
Light with mainly red, will show up as green for such a person, because the
green receptors are still more sensitive to red than the blue ones. People who
have 2 types of cones missing, and have thus only one type left, see in black
and white, because only two main types of signals now exist: "the cone is
excited" and "the cone is not excited". Imagine how much more colors a human
would be able to see if he had 4 types of color receptors instead of only 3.
One final question remains: violet is on one side of the spectrum, while red is
totally on the other side. Violet is much closer to the blue receptors of the
eye than the red ones, so you'd think violet light would look like pure blue to
the eye. But violet looks a bit more like purple, hinting that it has some red
in it, why could that be?
The reason is that violet has such a high frequency, too high for the blue
receptor as well, that the signal is very weak for both the blue and the red
receptor. Relatively speaking, the red and blue signal will thus be pretty close
to each other, and the color will show up more like purple than like blue for
the brain!
The RGB Color
Model
The RGB color model works exactly like those color receptors of the human eye
work: the RGB color model describes a color by using 3 variables, Red, Green and
Blue. These variables can be compared to the strength of the signals from the 3
types of color receptors in the nerves. A computer or TV screen works this way
too: it has 3 types of cells, Red, Green and Blue, and can make each type
brighter or darker independently, exciting the correct receptors of the eye to
create the desired color. If you look with a magnifying glass to a white area of
your computer screen, you can see that the color white is actually made out of
the 3 colors red, green and blue. This means the white emitted by a computer
screen is different from white sunlight: while white sunlight contains photons
of all frequencies (except a few), the computer screen only has 3
frequencies. The human eye can't see the difference between these two kinds of
white.
The RGB color model is the one you'll mostly be dealing with in computer
graphics. It's also called the additive color model, because you add 3 color
components together to form any color. In 24-bit color, each of the 3 components
R, G and B is an 8-bit variable that can be an integer number between 0 and 255.
0 means the color component is off (black), while 255 means it's at it's full
intensity. 127 is half intensity. This means color 0,0,0 is the darkest black,
color 255,0,0 is the brightest red, color 0,255,0 is the brightest green and
color 0,0,255 is the brightest blue. 255,255,255 is the brightest white and
127,127,127 is gray. 32-bit color is the same but with an extra 8-bit alpha
channel added that can be used for transparency of textures, ...
The RGB color model isn't very intuitive, so here's a table containing some
common RGB values:
Here is a table with common RGB color values:
R |
G |
B |
Hex Value
|
Color |
0 |
0 |
0 |
000000 |
Black |
255 |
0 |
0 |
FF0000 |
Red |
0 |
255 |
0 |
00FF00 |
Green |
0 |
0 |
255 |
0000FF |
Blue |
255 |
255 |
0 |
FFFF00 |
Yellow |
255 |
0 |
255 |
FF00FF |
Magenta |
0 |
255 |
255 |
00FFFF |
Cyan |
255
|
128
|
128
|
FF8080
|
Bright Red
|
128
|
255
|
128
|
80FF80
|
Bright Green
|
128
|
128
|
255
|
8080FF
|
Bright Blue
|
64 |
64 |
64 |
404040 |
Dark
Grey |
128 |
128 |
128 |
808080 |
Intermediate Grey |
192 |
192 |
192 |
C0C0C0 |
Bright
Grey |
255 |
255 |
255 |
FFFFFF |
White |
This way, you should be able to guess that 128,0,0 is dark red, 255,128,192 is
pink and 16,16,16 is very dark gray. The Hex value is the hexadecimal code of
the color, used for example in HTML.
The R, G and B values are the ones to fill in as parameters for functions of
QuickCG like pset, drawLine, drawCircle to give the color.
In RGB color, the higher the values of R, G and B, the brighter the color will
be, and if R=G=B, the color will be a shade of gray.
If you set R=x, G=y, B=z, you can represent RGB color on a cube, where the
origin is black and the corner at R=255,G=255,B=255 is white:
RGB Arithmetic
By doing calculations on the RGB values of the pixels of an image you can
perform various color effects.
Here's a table of the operations you can do with RGB color, screenshots and code
will follow in the next sections. These operations are given for the 24-bit
color model with 8 bit per channel, so 255 is the maximum value of a color.
Colors channels can also be represented as floating point numbers between 0.0
and 1.0, then you have to replace the value "255" by "1.0". C represents the
channel together or the total color, while R, G and B represent the Red, Green
and Blue channel separately.
Operation
|
Formula
|
Effect
|
Negative
|
255-C
|
Returns the opposite color, for example
white becomes black, red becomes cyan, ...
|
Darken
|
C/p or C-p
|
Divide the color though some constant
(larger than 1), or subtract a constant from it, to make it darker.
|
Brighten
|
C*p or C+p
|
Multiply the color by some constant
(larger than 1), or add a constant to it, to make it brighter.
|
Greyscale
|
(R+G+B)/3
|
Calculate the average of the 3 channels
to get a gray color with the same brightness.
|
Remove Channel
|
R=0, G=0 and/or B=0
|
By setting one or more channels to 0,
you completely remove that color component from the picture.
|
Swap Channels
|
R=G, G=R, ...
|
Swap the values of two color channels
to get an image with a completely different color.
|
We'll try all these formulas on the following BMP image of a flower:
NegativeImage
The following code will load a BMP image of 200*133 pixels, calculate it's
negative, and display the result. ColorRGB is the struct containing 3 integers
r, g and b, to describe the rgb color.
ColorRGB image[200][133];
int main(int argc, char *argv[])
{
screen(200, 133, 0, "RGB Color");
loadBMP("pics/flower.bmp",image[0], 200, 133);
ColorRGB color; //the color for the pixels
for(int x = 0; x < w; x++)
for(int y = 0; y < h; y++)
{
//here the negative color is calculated!
color.r = 255 - image[x][y].r;
color.g = 255 - image[x][y].g;
color.b = 255 - image[x][y].b;
pset(x, y, color);
}
redraw();
sleep();
return 0;
}
|
image[x][y].r is the red component of pixel x, y of the image, so 255 -
image[x][y][0] is the negative of it. This is done for each color channel.
Here's the result:
You could as well have typed "color = RGB_White - image[x][y]"
instead of the 3 lines of code, because the ColorRGB struct supports a few
operators.
Change the
Brightness
To change the brightness, divide R, G and B through a number larger than 1 to
make it darker, or multiply them with that number to make it brighter. If the
color component becomes higher than 255, truncate it to 255.
For example, to make the image double as dark, change the 3 lines of code that
made the image negative in the previous example, to:
color.r = image[x][y].r / 2;
color.g = image[x][y].g / 2;
color.b = image[x][y].b / 2;
|
Or to make it 1.5 times as dark, use:
color.r = int(image[x][y].r / 1.5);
color.g = int(image[x][y].g / 1.5);
color.b = int(image[x][y].b / 1.5);
|
To make it twice as bright, use:
color.r = image[x][y].r * 2;
color.g = image[x][y].g * 2;
color.b = image[x][y].b * 2;
if(color.r > 255) color.r = 255;
if(color.g > 255) color.g = 255;
if(color.b > 255) color.b = 255;
|
Instead of dividing or multiplying, you can also add or subtract a number
instead, this even gives better results when making it brighter:
color.r = image[x][y].r + 50;
color.g = image[x][y].g + 50;
color.b = image[x][y].b + 50;
if(color.r > 255) color.r = 255;
if(color.g > 255) color.g = 255;
if(color.b > 255) color.b = 255;
|
Or darker:
ccolor.r = image[x][y].r - 50;
color.g = image[x][y].g - 50;
color.b = image[x][y].b - 50;
if(color.r < 0) color.r = 0;
if(color.g < 0) color.g = 0;
if(color.b < 0) color.b = 0;
|
Greyscale
One way to grayscale an image is to calculate the average of the 3 color
components and use this average as the value for the shade of gray:
color.r = image[x][y].r - 50;
color.g = image[x][y].g - 50;
color.b = image[x][y].b - 50;
color.r = color.g = color.b = (color.r + color.g + color.b) / 3;
|
Swapping and Removing Channels
By removing channels, you completely remove a color of the image, for example if
you remove the red of the flower you get this:
color.r = 0; //red component set to zero
color.g = image[x][y].g;
color.b = image[x][y].b;
|
If you remove green instead, you get:
And if you remove blue, you get:
The last image looks quite similar to the original because there wasn't much
blue in the image, though white areas now look green or yellow, and everything
has become a bit darker.
Swapping two channels can give results with a totally different color, for
example if we swap the red and green channel the flower becomes green while the
background becomes reddish:
color.r = image[x][y].g; //the green component of the image
color.g = image[x][y].r; //the red component of the image
color.b = image[x][y].b; //the blue component of the image
|
And if red and blue are swapped instead, the flower becomes of course blue:
To make the flower yellow, set both R and G to the red channel of the image:
color.r = image[x][y].r; //the red component of the image
color.g = image[x][y].r; //the red component of the image
color.b = image[x][y].b; //the blue component of the image
|