23

How would you go about making a range of RGB colours evenly spaced over the spectral colour range? So as to look like a real rainbow.

AnnanFay
  • 9,573
  • 15
  • 63
  • 86

8 Answers8

18

Use HSL instead: fix the brightness and saturation and vary the hue from 0 to 360, then convert to RGB.

HSL describes colors as they are perceived by people. RGB describes them as they are used by machines. So you can't really do anything visually pleasing directly using RGB.

Laurent
  • 5,953
  • 14
  • 43
  • 59
18

You can use the HSV color space and walk across the Hue dimension.

Community
  • 1
  • 1
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
8

The simplest approach is to do a linear interpolation (in RGB) between each consecutive pair in this sequence:

  • #ff0000 red
  • #ffff00 yellow
  • #00ff00 green
  • #00ffff cyan
  • #0000ff blue
  • #ff00ff magenta
  • #ff0000 back to red

This should get you pretty much the same result as sweeping through the hue values in HSV or HSL, but lets you work directly in RGB. Note that only one component changes for each interpolation, which simplifies things. Here's a Python implementation:

def rainbow():
  r, g, b = 255, 0, 0
  for g in range(256):
    yield r, g, b
  for r in range(255, -1, -1):
    yield r, g, b
  for b in range(256):
    yield r, g, b
  for g in range(255, -1, -1):
    yield r, g, b
  for r in range(256):
    yield r, g, b
  for b in range(255, -1, -1):
    yield r, g, b
Laurence Gonsalves
  • 137,896
  • 35
  • 246
  • 299
2

The other solutions require rather large amounts of code and conditional branching, which makes them unsuitable for GPUs. I recently arrived at the following magically simple formula in GLSL. It's essentially the same in OpenCL:

vec3 HueToRGB(float hue) {
  vec3 h = vec3(hue, hue + 1.0/3.0, hue + 2.0/3.0);
  return clamp(6.0 * abs(h - floor(h) - 0.5) - 1.0, 0.0, 1.0);
}

This will give you a rainbow color that corresponds to the given hue value in linear RGB. To use in an image, convert to sRGB and then multiply by 255.

Here is a C++17 version:

std::array<float, 3> HueToRGB(float hue, float *rgb) {
  std::array<float, 3> rgb;
  for (unsigned i = 0; i < 3; ++i) {
    const float h = hue + i / 3.f;
    rgb[i] = std::clamp(6.f * std::fabs(h - std::floor(h) - 0.5f) - 1.f, 0.f, 1.f);
  }
  return rgb;
}

The key here is to realize that the graph of each of the R, G, B coordinates in function of the hue value is a clamped value of a periodic triangle function, and that can be obtained as the absolute value of a sawtooth function, x - floor(x).

Krzysztof Kosiński
  • 4,225
  • 2
  • 18
  • 23
1
  • Red (web colour) (Hex: #FF0000) (RGB: 255, 0, 0)
  • Orange (colour wheel Orange) (Hex: #FF7F00) (RGB: 255, 127, 0)
  • Yellow (web colour) (Hex: #FFFF00) (RGB: 255, 255, 0)
  • Green (X11) (Electric Green) (HTML/CSS “Lime”) (Colour wheel green) (Hex: #00FF00) (RGB: 0, 255, 0)
  • Blue (web colour) (Hex: #0000FF) (RGB: 0, 0, 255)
  • Indigo (Electric Indigo) (Hex: #6600FF) (RGB: 102, 0, 255)
  • Violet (Electric Violet) (Hex: #8B00FF) (RGB: 139, 0, 255)

Between each colour make linear interpolation.

Luka Rahne
  • 10,336
  • 3
  • 34
  • 56
  • linear interpolation in which color space? ;) – Stefano Borini Jul 31 '09 at 10:15
  • In RGB of course. You might take this points for linear interpolations #FF0000 #FFFF00 #00FF00 #00FFFF #0000FF #FF00FF #FF0000 in that case it would look like less natural. I took "rainbow" colors intepolations when making fancy RGB lightings of glass – Luka Rahne Jul 31 '09 at 10:38
  • Isn't it better to end on Vivid violet instead of Electric violet? – AnnanFay Jul 31 '09 at 16:11
  • I was looking at http://en.wikipedia.org/wiki/Violet_(color) which says Electric violet is in the middle of the violet spectrum while vivid violet is at the very edge. – AnnanFay Jul 31 '09 at 20:05
1

This class will do it with PHP, pass the constructor the number of colours you want in your rainbow and the $sequence property will contain an array of rrggbb hex codes.

class color
{
    public $sequence = array();

    /**
     * constructor fills $sequence with a list of colours as long as the $count param
     */
    public function __construct($count, $s = .5, $l = .5)
    {
        for($h = 0; $h <= .85; $h += .85/$count)    //.85 is pretty much in the middle of the violet spectrum
        {
            $this->sequence[] = color::hexHSLtoRGB($h, $s, $l);
        }
    }

    /**
     * from https://stackoverflow.com/questions/3597417/php-hsv-to-rgb-formula-comprehension#3642787
     */
    public static function HSLtoRGB($h, $s, $l)
    {

        $r = $l;
        $g = $l;
        $b = $l;
        $v = ($l <= 0.5) ? ($l * (1.0 + $s)) : (l + $s - l * $s);
        if ($v > 0){
              $m;
              $sv;
              $sextant;
              $fract;
              $vsf;
              $mid1;
              $mid2;

              $m = $l + $l - $v;
              $sv = ($v - $m ) / $v;
              $h *= 6.0;
              $sextant = floor($h);
              $fract = $h - $sextant;
              $vsf = $v * $sv * $fract;
              $mid1 = $m + $vsf;
              $mid2 = $v - $vsf;

              switch ($sextant)
              {
                    case 0:
                          $r = $v;
                          $g = $mid1;
                          $b = $m;
                          break;
                    case 1:
                          $r = $mid2;
                          $g = $v;
                          $b = $m;
                          break;
                    case 2:
                          $r = $m;
                          $g = $v;
                          $b = $mid1;
                          break;
                    case 3:
                          $r = $m;
                          $g = $mid2;
                          $b = $v;
                          break;
                    case 4:
                          $r = $mid1;
                          $g = $m;
                          $b = $v;
                          break;
                    case 5:
                          $r = $v;
                          $g = $m;
                          $b = $mid2;
                          break;
              }
        }
        return array('r' => floor($r * 255.0),
                    'g' => floor($g * 255.0), 
                    'b' => floor($b * 255.0)
                    );
    }

    //return a hex code from hsv values
    public static function hexHSLtoRGB($h, $s, $l)
    {
        $rgb = self::HSLtoRGB($h, $s, $l);
        $hex = base_convert($rgb['r'], 10, 16) . base_convert($rgb['g'], 10, 16) . base_convert($rgb['b'], 10, 16);
        return $hex;
    }
}

To test e.g.:

$c = new color(100);
foreach($c->sequence as $col)
  print "<div style='background-color:#$col'>$col</div>\n";

I only claim credit for packaging this up into a class, the original function was found in this post: PHP HSV to RGB formula comprehension

Community
  • 1
  • 1
james-geldart
  • 709
  • 7
  • 9
0

I know it is rather old question, but here is my simple and easy to understand solution, that should be easy to use in most programming languages. Replace steps and whichStep with Your own values.

int steps = 1280;
int stepChange = 1280 / steps;
int change = stepChange * whichStep;
int r=0, g=0, b=0;

if (change < 256)
{
    r = 255;
    g += change;
}
else if (change < 512)
{
    r = 511 - change;
    g = 255;
}
else if (change < 768)
{
    g = 255;
    b = change-512;
}
else if (change < 1024)
{
    g = 1023 - change;
    b = 255;
}
else
{
    r = change - 1024;
    b = 255;
}
-1

I can draw a rainbow programmatically with JavaScript and HTML5.

RainbowVis-JS example with rainbow arc

I make a gradient from rgb(255,0,0) -> rgb(255,255,0) -> rgb(0,255,0) -> rgb(0,255,255) -> rgb(0,0,255) -> rgb(255,0,255).

I calculate the hex colour values along the gradient using my RainbowVis-JS library (which just chains gradients together). I draw the arc shape with HTML5 Canvas, looping through the colours.

<!DOCTYPE html>
<html>
  <head>
    <script src="rainbowvis.js"></script>
  </head>
  <body>
    <script type="text/javascript">
      window.onload = function(){

        var RAINBOW_WIDTH = 60;
        var RAINBOW_RADIUS = 130;

        // Library class
        var rainbow = new Rainbow();
        rainbow.setNumberRange(1, RAINBOW_WIDTH);
        rainbow.setSpectrum(
          'FF0000', 'FFFF00', '00FF00',
          '00FFFF', '0000FF', 'FF00FF'
        );

        // Canvas
        var canvas = document.getElementById('MyCanvas');
        var context = canvas.getContext('2d');

        context.lineWidth = 1;
        for (var i = 1; i <= RAINBOW_WIDTH; i++) {
          context.beginPath();
          context.arc(canvas.width/2, canvas.width/2, RAINBOW_RADIUS - i+1, 
            Math.PI, 0, false);
          context.strokeStyle = '#' + rainbow.colourAt(i); // Library function
          context.stroke();
        }
      };
    </script>
    <canvas id="MyCanvas" width="300" height="300">
      <p>Rainbow arc example. Your browser doesn't support canvas.</p>
    </canvas>
  </body>
</html>
anomal
  • 2,249
  • 2
  • 19
  • 17