Colour
Conversion Algorithms
Site Map Feedback

Download:

 See Also:
Up HSL

The Rainbow world of Hue.

To tell a computer what colour to set a pixel to, RGB values are used to make life easy for the display units which are made using Red, Green and Blue dots.
Colors.h contains methods to pick colours from the spectrum using a number between 0 and 1; to change the brightness of a given colour, to blend and Alphablend colours.
Lets start by finding out how a Rainbow spectrum might be made in terms of Red, Green and Blue elements and make some code to colour things in all the colours of the Rainbow. Since Rainbows are thought of as starting at Red, the intensity of Red must be at a maximum to start with, like a cosine curve. Green should peak next, and finally Blue. The peaks should be spread equally through the range:


Cosine Spectrum

In the image, the (horizontal) range is called 'Ratio' and shown the cosine curves and spectrum. The vertical scale is intensity of each of the RGB elements and is labelled 'Fn'.

Imagine that you have a number of things that all need to be different colours. A GetRainbowColour(Ratio) function that lets you allocate a colour to each thing would be valuable. If you have a hundred lines, what colour is line fifty? The Ratio would be 100/50.0 which is 0.5 (cyan). So Ratio = LineCount / LineIndex (but make sure you're not doing an integer divide)!

for(int ThingIndex=0; ThingIndex<ThingCount; ++ThingIndex) {
  Thing.Color=GetRainbowColour(ThingCount/double(ThingIndex));
}

So now you see why the methods were called Ratio and Fn: The Ratio depends on how many things you want to colour, and Fn is short for ... 'Function'! Fn is the intensity for each component of the RGB. So given a Ratio, find: RGB(Fn(Ratio), Fn(Ratio + 2.0/3.0), Fn(Ratio + 1.0/3.0)) Since cosines are periodic, it doesn't matter that Ratio has been added to. Imagine pushing the peak of the green wave to the right until it is over the red peak - you have to push from Ratio=1/3 to Ratio=1 which is a push of 2/3. In other words, for a Ratio of 1/3, Fn should be maximum for Green. But Fn works for Red, so add 2/3 and you have a Ratio of 1 which will return the maximum required.

At the moment, using cosines isn't going to give the expected spectrum. At a Ratio of zero, the vertical scale shows that the colour wouldn't be pure Red! Green and Blue would both be at a quarter of their intensity too. You can see that when one cosine curve is at it's minimum, the other two have the same high value at three quarters of their maximum intensity giving yellow, cyan and magenta as the 'pure' colours. To get pure Red, Green and Blue, simply clip the intensity scale and only look at the middle range where the spectrum is drawn. You can see the clipped red line starts at Fn=255, and the Green and Blue start at Fn=0. So a Ratio of zero gives pure Red, a Ratio of 1/3 gives pure Green and 2/3 gives pure Blue. Turning this into code (VBA) gives the following:

Function GetRainbowColour(Ratio As Double) As OLE_COLOR
  GetRainbowColour = RGB(Fn(Ratio), Fn(Ratio + 2 / 3), Fn(Ratio + 1 / 3))
End Function

Function Fn(Ratio As Double) As Integer
  Const PI As Double = 3.14159265358979
  Fn = 255 * Cos(2 * PI * Ratio) + 127
  If Fn < 0 Then Fn = 0 ' Clipping:
  If Fn > 255 Then Fn = 255
End Function

The range of Ratio is from 0 to 1, but Cos takes values from 0 to 2*PI, so multiply Ratio by 2*PI before using Cos. Cos gives an answer between -1 and +1. Since the cosine of a third of a circle is +0.5, the cosine curves intersect at -0.5 to +0.5. Now, to clip the result of the cos to -0.5 to +0.5 and scale to get the result to be between 0 and 255. First add 0.5 to the Cos result giving a range of -0.5 to 1.5. Multiply by 255 to get the range [0,1] and all that is left is the clipping.

The example is in VBA because in lower level languages like C++, using a cosine method would be very slow. In scripting languages, though, it is usually faster to do the operation with the fewest function calls, which is the cosine method above. Since we are ignoring the curved top and bottoms of the curves we could approximate the shape we want to straight lines and avoid using cos altogether for languages where cos is slow. To model the straight line function, make Fn follow the line with the simplest behaviour to model, which is the Green line (because the rising part starts at zero so the first line is a simple scaling of the Ratio):


Green Spectrum

For the first sixth of the range, Fn will rise from 0 to 255 where it stays for a further third, then falls off for a sixth and stays at zero for the final third. Now it is clear that Fn can have a better name: GetGreen. So the C++ code would be:

COLORREF GetRainbowColour(double Ratio) {return RGB(GetGreen(Ratio+1/3.), GetGreen(Ratio), GetGreen(Ratio-1/3.));}
BYTE GetGreen(double Ratio) {
  while(Ratio<0) ++Ratio; // Using modf was slower
  while(Ratio>1) --Ratio;
       if(Ratio<1/6.) return 255*   6*Ratio;
  else if(Ratio<3/6.) return 255;
  else if(Ratio<4/6.) return 255*(4-6*Ratio);
                      return 0;
}

This code is simply splitting the Ratio into six parts and returning a rising straight line for the first sixth, a falling stright line for the fourth sixth giving the shape of the Green line. The calling 'GetRainbowColour' function now displaces the Red and Blue values by adding and subtracting a third from the Ratio. Since Ratio can come into Fn outside of the 0.0-1.0 range, the first two lines of Fn pull Ratio into range. This is a very fast way to pick colours from the spectrum, but there is always room for optimisation!

Since there are only three components being calculated, it is better to write separate functions for each specific component. Additionally, remembering that this is a clipped approximation allows another optimisation. If the decision is made to clip the results, each function can model the ramp sections as a triangle wave which will be clamped. If the spectrum is split into three, one component is always zero, so negative values don't need clipping. This diagram shows the lines that will be modeled before clipping:


Clipable Components

and here's the code, taken from the gColor class which has its RGB components as type double in the Interval [0,1]:

void gColor::SetFromHue(const double& H) { // H is Interval [0,1]
  if(H<1/3.) {//         _    _
    R=2-H*6;  //   Red: | \__/ |
    G=H*6;    //        0 __   1
    B=0;      // Green: |/  \__|
  }else if(H<2/3.) { // 0   __ 1
    R=0;      //  Blue: |__/  \|
    G=4-H*6;  //        0 |  | 1
    B=H*6-2;  //         1/3 2/3
  }else{
    R=H*6-4;
    G=0;
    B=(1-H)*6;
  }
  if(R>1) R=1;
  if(G>1) G=1;
  if(B>1) B=1;
}

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.