Fade through more more natural rainbow spectrum in HSV/HSB


I'm trying to control some RGB LEDs and fade from red to violet. I'm using an HSV to RGB conversion so that I can just sweep from hue 0 to hue 300 (beyond that it moves back towards red). The problem I noticed though is that it seems to spend far to much time in the cyan and blue section of the spectrum. So I looked up what the HSV spectrum is supposed to look like, and found thisL

enter image description here

I didn't realize that more than half the spectrum was spent between green and blue.

But I'd really like it to look much more like this:

enter image description here

With a nice even blend of that "standard" rainbow colors. I'd imagine that this would end up being some sort of s-curve of the normal hue values, but am not really sure how to calculate that curve.

An actual HSV to RGB algorithm that handles this internally would be great (any code really, though it's for an Arduino) but even just an explanation of how I could calculate that hue curve would be greatly appreciated.


Answers:


http://www.fourmilab.ch/documents/specrend/ has a fairly detailed description of how to convert a wavelength to CIE components (which roughly correspond to the outputs of the three kinds of cone sensors in your eyes) and then how to convert those to RGB values (with a warning that some wavelengths don't have RGB equivalents in a typical RGB gamut).

Or: there are various "perceptually uniform colour spaces" like CIE L*a*b* (see e.g. http://en.wikipedia.org/wiki/Lab_color_space); you could pick one of those, take equal steps along a straight line joining your starting and ending colours in that space, and convert to RGB.

Either of those is likely to be overkill for your application, though, and there's no obvious reason why they should be much -- or any -- better than something simpler and purely empirical. So why not do the following:

  1. Choose your starting and ending colours. For simplicity, let's suppose they have S=1 and V=1 in HSV space. Note them down.
  2. Look along the hue "spectrum" that you posted and find a colour that looks to you about halfway between your starting and ending points. Note this down.
  3. Now bisect again: find colours halfway between start and mid, and halfway between mid and end.
  4. Repeat once or twice more, so that you've divided the hue scale into 8 or 16 "perceptually equal" parts.
  5. Convert to RGB, stick them in a lookup table, and interpolate linearly in between.
  6. Tweak the RGB values a bit until you have something that looks good.

This is totally ad hoc and has nothing principled about it at all, but it'll probably work pretty well and the final code will be basically trivial:

void compute_rgb(int * rp, int * gp, int * bp, int t) {
  // t in the range 0..255 (for convenience)
  int segment = t>>5; // 0..7
  int delta = t&31;
  int a=rgb_table[segment].r, b=rgb_table[segment+1].r;
  *rp = a + ((delta*(b-a))>>5);
  a=rgb_table[segment].g; b=rgb_table[segment+1].g;
  *gp = a + ((delta*(b-a))>>5);
  a=rgb_table[segment].b; b=rgb_table[segment+1].b;
  *bp = a + ((delta*(b-a))>>5);
}

(you can make the code somewhat clearer if you don't care about saving every available cycle).

For what it's worth, my eyes put division points at hue values of about (0), 40, 60, 90, 150, 180, 240, 270, (300). Your mileage may vary.