Blog  |   Puzzles  |   Books  |   About

A tutorial by jim bumgardner (@jbum on twitter)

6/2012: You'll find a great, mind-blowing overview of how color perception works on Jason Cohen's blog. He points to a far more in-depth source which will also reward your time.

12/2011: Charlie Loyd has written a nice analysis of the techniques presented in this article, and makes a good case for sine-generated rainbows (which he has nicely dubbed the "Sinebow") as an aesthetic improvement over the more common HSB-space rainbows (which have brightness/saturation spokes).

1/2011: Since I wrote this tutorial, CSS3 has introduced the hsla() color specifications, which makes the kinds of tricks explained in this article much easier on browsers that support CSS3. Since my intent was to teach some simple color theory and applied mathematics, I suggest you pretend this feature doesn't exist as you read this article. Then, afterwards, feel free to use it willy nilly! However, the sinebow presented here has some aesthetic improvements over an HSL hue sweep, as you will see.

This is a tutorial on how to produce sequences of discrete colors, for use in fonts, graphics or tables, such as in the color strips shown here, or the title of this article.

Obviously you can make a webpage look incredibly annoying if you overuse this technique (and by overuse, I mean use at all), but you may have fun learning to do it, and you will improve your javascript skills, and your understanding of how colors work.

I am going to use javacsript for my code examples, but you can use the same basic techniques in any language, and indeed I originally developed these tricks in C, but I now use the same old tricks in C++, Perl, Processing, Java, Flash Actionscript and other languages.

To get the most out of this tutorial, you will need to know a little Javascript, and a little HTML. However, you won't need to know a whole lot. I mean c'mon. I even explain how hexadecimal works in this document, and why '#FF0000' comes out red, and so on...

Well, let's get to it, shall we?

Converting R,G,B values to HTML hex notation

On computer monitors, you get different colors by combining red, green and blue. The red, green and blue levels are called 'color components'.

To generate rainbow-like sequences of colors, I am going to be manipulating the individual RGB components of each subsequent color. So the first thing I need is a utility function to convert individual red, green and blue values to an HTML hex color specification, which looks something like this:


This represents an RGB color with 8-bits per component. In an 8-bit color system, the values of the color components, R, G, and B can go from 0-255. But the HTML color specification uses hexadecimal notation for each of the components, so that they each occupy two digits.

In hexadecimal, each color component (red, green, and blue) occupies two digits, like so: #RRGGBB. So our color has the following hex values:

ComponentValue (in hex)
Hexadecimal numbers are base 16 numbers. You can use the following table to convert two digit hex numbers to their decimal equivalents.


If you don't have a calculator handy that does hexadecimal -> decimal conversion, it's not too hard to do it yourself. Just take the first digit, convert it to decimal (using the above table, which is easily memorized, especially if you're not busy friday), multiply it by 16, and then add the decimal equivalent of the second digit. For example, the number AB corresponds to 10*16 + 11, or 171.

Using this formula, you will see that the color #AABBCC corresponds to the following values:

ComponentValue (in hex)Value (in decimal)

What I need for this tutorial is a function that does the reverse - converts 170,187,204 back to #AABBCC. I would use it like this:

output = RGB2Color(170,187,204);

And know that the string '#AABBCC' is in the output value, ready to be output as HTML, using the document.write() command.

Here is the function.

  function RGB2Color(r,g,b)
    return '#' + byte2Hex(r) + byte2Hex(g) + byte2Hex(b);
My conversion function relies on yet another utility function, byteToHex(), which converts each of the color components from a numeric value (such as 170) to a hexadecimal string (such as 'AA'):
  function byte2Hex(n)
    var nybHexString = "0123456789ABCDEF";
    return String(nybHexString.substr((n >> 4) & 0x0F,1)) + nybHexString.substr(n & 0x0F,1);
This function relies on the binary arithmetic operators >> and &, which treat the numbers as binary, which is closely related to hexdecimal. Every 4 bits (or binary digits) in a binary number corresponds to a single hex digit, 0-F. So this function takes the leftmost 4 bits of the number (given by n >> 4, which shifts the number right by 4 bits) and converts it to hex, and then takes the rightmost 4 bits of the number (given by n & 0x0F, which masks off the rightmost 4 bits) and converts it to hex.

I should point out that decimal -> hex conversion (and number formatting in general) is much easier in languages that provide a 'printf' function - something sorely missing from Javascript.

In Perl, you can do this same conversion in one line, like so:

  output = sprintf '#%02x%02x%02x', $r, $g, $b;

If you can live without an old-style CSS color specification, you can also produce colors more simply without byte2Hex, like so:

  function RGB2Color(r,g,b)
    return 'rgb(' + Math.round(r) + ',' + Math.round(g) + ',' + Math.round(b) + ')';

Sine wave cycles

The basic tool I am going to use to make color cycles is the sine wave. There is a function in Javascript, Math.sin() which will produce a smoothly undulating value that goes from 0 to 1 to 0 to -1 and back to 0. Here are some samples:

Sine waves are really useful in real life, contrary to what you might have inadvertantly learned in trig class, and they are especially useful for all kinds of things relating to graphics and music.

To get this pattern, I have input into the Math.sin() function a value which is steadily increasing. You can produce a series of sine wave values using a loop, like so:

var frequency = .3;
for (var i = 0; i < 32; ++i)
   Document.write( Math.sin(frequency * i)  );
You may have noticed that the sine wave pattern starts to repeat right about when frequency*i is equal to 6.2. The precise value where it repeats is actually 2π (2 times PI) or 6.28318, which happens to correspond to the circumfrence of a circle with a radius of 1. As you may already know, sine waves are closely related to circles. The halfway point in the sine wave is π exactly, or 3.14159...

At this time I would like to apologize for how crappy the π (PI) character looks in the san-serif font used in 80% of the computers out there. If I were using lovely Times Roman, the π character would be far more recognizeable. Sadly, I'm not, because for just about everything else I find the San Serif font more readable. But I digress...

There is a relationship between these π units which are used with the Math.sin() function (called radians) and the more familiar degrees which go from 0-360 (degrees are an old Babylonian unit -- the Babylonians really liked numbers like 60 and 360 because all kinds of numbers go evenly into them. Also, the Babylonians hadn't invented pie yet...)


You can produce a value for π in Javascript by saying:

Like so:

I personally have π memorized up to 8 digits. Kind of a waste of gray matter, really.

Speaking of gray matter...

Using sine waves to make shades of gray

I can use the return value of Math.sin() to make fluctuating color values. However, I need to do a little conversion. As shown above, the return value of Math.sin() goes from -1 to 1. The color components I use to make RGB colors go from 0 to 255. So I need to translate a number from the range (-1 --> 1) into (0 --> 255).

This isn't too hard because we know that we want the sine value to start at some center value, and then go up and down by some amount. Since the sine wave goes up to 1, and down to -1, it is already normalized (meaning that it's maximum value is 1, a very useful number). We can just multiply it by some other number, and it will go up to that number, and down to the negative version of that number.

For our color values, it will work well if we use 255/2 for our center value, and have the sine value go up and down with an amplitude of 255/2 (which will take us to 255 and down to 0.

color_component = Math.sin(frequency*i)*255/2 + 255/2;
I usually express this, using integers, as:
color_component = Math.sin(frequency*i)*128 + 127;
...which isn't exactly the same, but close enough.

More generally, when we want to use a sine wave to oscillate something, we will use the formula

value = Math.sin(frequency*increment)*amplitude + center;

frequency is a constant that controls how fast the wave oscillates
increment is a variable that counts up, typically provided by a loop
amplitude controls how high (and low) the wave goes
center controls the center position of the wave.

Now I can modify my original sine wave loop, and produce a series of test colors.

var frequency = .3;
var amplitude = 127;
var center = 128;

for (var i = 0; i < 32; ++i)
   v = Math.sin(frequency*i) * amplitude + center;

   // Note that &#9608; is a unicode character that makes a solid block
   document.write( '<font style="color:' + RGB2Color(v,v,v) + '">&#9608;</font>');
Inside the document.write I am producing a color by calling RGB2Color(v,v,v). Since I pass the same value in 3 times, for red, green, and blue, I will get a gray color. In gray colors, red, green, and blue have the same value. To view the color, I print out a block character, using the unicode value &#9608;.

And here's what the code produces -- a kind of color sine wave:

For fun, I can try using a series of different values for frequency. This is what I'll get if I add an additional outer loop that changes the value of frequency.

Using out-of-phase sine waves to make rainbows

Now, as I mentioned, the reason the above gradient looks gray is beacuse I am using the same value for red, green, and blue. One way we can try to get colors is to add a constant to the value of v that we are using for the green and blue parameters. So instead of saying:
I might say:
This produces the following color sequence:

This is interesting, but it is not quite what I want. A better technique is to generate 3 out-of-phase sine waves, by doing something like this:

var frequency = .3;
for (var i = 0; i < 32; ++i)
   red   = Math.sin(frequency*i + 0) * 127 + 128;
   green = Math.sin(frequency*i + 2) * 127 + 128;
   blue  = Math.sin(frequency*i + 4) * 127 + 128;

   document.write( '<font color="' + RGB2Color(red,green,blue) + '">&#9608;</font>');

Which produces this:

As you can see I am using the values 2 and 4 to change the alignment (or phase) of the green and blue sine waves. I chose 2 and 4 because they nearly divide the range of the sine wave (2π or 6.2) into three equal parts, which puts each sine wave approximately 1/3rd of a cycle, or 120°, out of phase, like so:

red     (phase = 0)
green   (phase = 2)
blue    (phase = 4)

If I wanted exactly 120° I can use 2*Math.PI/3 in place of 2, and 4*Math.PI/3 in place of 4, which produces this nearly identical (but perfect) color cycle:

However, the results are so close, that I think 2 and 4 are fine substitutes. It is said that ancient Egyptians thought that π was equal to three. And in this tutorial, we're gonna code like an Egyptian.

More about the hue cycle

This basic phenomenon - that three 120° out-of-phase sine waves produces a rainbow effect is something I've known about for a long time - since the mid 1980s actually. I originally discovered it just by playing around with using sine waves to make color (a frequent activity which took the place of having a life). Once I learned this trick, I started using it whenever I wanted an assortment of bright colors. For example, the dots in my Whitney Music Box are colored using this system.

Later, when I learned more about color theory, I found out that what I'm doing is basically causing the hue of the color to travel around the circle of a color wheel. If you plot the path that I'm making on the hue-saturation circle commonly seen in color pickers, you'll find it doesn't produce a perfect circle, but more of a trefoil pattern, like so:

Note: I used the Processing language to produce this illustration. Making it in Javascript would be a bit of a chore. If you like graphics programming in Javascript, you'll love it in Processing.js, and you will enjoy my Processing blog (and unfinished book): The Joy of Processing.

Even though it doesn't make a perfect hue circle, the sine-wave trefoil makes a pretty color gradient, and it's pretty easy to get a rainbow effect in just a few lines of code. I actually prefer it to the "correct" HSB / HSL hue cycle, because it has more consistent brightness. It minimizes the spokes at yellow, cyan and magenta, which you can see in the picture.

Now, I'll talk about how to modify the function to produce a greater assortment of colors, and different shades, such as pastels.

Generalizing it

In the next few sections, I'm going to use a general purpose function for drawing these color gradients. It allows me to specify separate parameters for frequency and phase for each color component. Here's the code:
  function makeColorGradient(frequency1, frequency2, frequency3,
                             phase1, phase2, phase3,
                             center, width, len)
    if (center == undefined)   center = 128;
    if (width == undefined)    width = 127;
    if (len == undefined)      len = 50;

    for (var i = 0; i < len; ++i)
       var red = Math.sin(frequency1*i + phase1) * width + center;
       var grn = Math.sin(frequency2*i + phase2) * width + center;
       var blu = Math.sin(frequency3*i + phase3) * width + center;
       document.write( '<font color="' + RGB2Color(red,grn,blu) + '">&#9608;</font>');
And here's the code for drawing the basic rainbow gradient.
Which looks like this:

Making pastels

The basic trick to getting pastels is to change the gamut of the color components to use lighter colored values for the color components. So, when we convert the sine values, instead of convering to the full range (0 --> 255), we convert to something like (205 - 255). We change the last two parameters (center and width) as follows. Before:

   // center = 128, width = 127
   makeColorGradient(.3,.3,.3,0,2,4, 128,127);
   // center = 230, width = 25
   makeColorGradient(.3,.3,.3,0,2,4, 230,25);
In the changed code, the 230 is the center of the sine wave, and the 25 is the maximum deviation from this center value. So the sine wave starts at 230, goes up to (230+25) and goes down to (230-25). In other words, it has a range of (205-255).

And this makes a pastel strip:

I can get darker pastels by using a wider range (center=200, width=55), like so:

Get more stripes by increasing the frequency

When you change the value of frequency, you can get the colors to change more quickly, or more slowly. Up to now, I've mostly been using an frequency value of .3, but here are some other values:

Experimenting with phase

For the basic rainbow effect, I separated the phases of each sine wave by 120°. What happens if I put the phases closer together? Let's find out:

Get more variety by using separate frequencies for each component

Another trick I've used which produces greater color variety is to put each color component on a different frequency. Here's the code:

redFrequency = .1;
grnFrequency = .2;
bluFrequency = .3;
center = 128;
width = 127;
...and here's what it looks like:

If I separate out the individual channels, you can see what is going on:

red     (freq = .1)
green   (freq = .2)
blue    (freq = .3)

Using three different frequencies may produce colors with a low saturation value, such as the black seen in the example above, or grays or whites. This happens when the three sine waves cross the same value simultaneously.

Getting cycles to repeat

Let's say you want the color cycle to repeat every 6 steps. How do you do it? The way I accomplish this is by using a frequency value which corresponds to 1/6th of 2π. Remember that the sine wave repeats every 2π, so this will make the colors repeat every 6 increments. Here's the code...
center = 128;
width = 127;
steps = 6;
frequency = 2*Math.PI/steps;
And here's what it looks like:

Getting cycles to not repeat

If you don't want the colors to repeat exactly, then use frequency values which don't go evenly into 2π. It turns out that the number 2.4 works very well for this. 2.4 in radians, is very close to the golden angle (137.51°), which is the angle that many plants grow new shoots to maximize the sunlight received by leaves. Here I'm using essentially the same technique - I am maximizing the distance between color repeats (which would be like overlapping leaves around a stem). 2.4 is a good frequency to use if you're selecting colors for a pie chart or graph, and you're not sure how many data values you're going to need to plot. Here's the code.
center = 128;
width = 127;
frequency = 2.4;
And here's what it looks like with a frequency of 2.4:

If you combine this with the trick of using separate freqencies for each color, and insure that none of the individual frequencies are multiples of each other, you can get even more variety. Here I'm using frequency of 1.666, 2.666, and 4.666.

center = 128;
width = 127;
redFrequency = 1.666;
grnFrequency = 2.666;
bluFrequency = 3.666;

Finally, here's a function that does the rainbow effect on a line of text.

function colorText(str,phase)
    if (phase == undefined) phase = 0;
  center = 128;
  width = 127;
  frequency = Math.PI*2/str.length;
  for (var i = 0; i < str.length; ++i)
     red   = Math.sin(frequency*i+2+phase) * width + center;
     green = Math.sin(frequency*i+0+phase) * width + center;
     blue  = Math.sin(frequency*i+4+phase) * width + center;
     document.write( '<font style="color:' + RGB2Color(red,green,blue) + '">' + str.substr(i,1) + '</font>');

Well, that's it. I hope you enjoyed the tutorial!

If you have any questions or comments, add them to the blog entry about this article, which you'll find here.

My second Javascript tutorial: Using the Arcane Operators in Javascript/Actionscript

My other blog: The Joy of Processing

Copyright © 2024 by KrazyDad. All Rights Reserved.
Privacy Policy
Contact Krazydad
Discord server