FastLED HSV Colors

kriegsman edited this page Jan 30, 2014 · 11 revisions

The FastLED Hue-Saturation-Value color model differs from 'traditional' computer HSV color models in two important respects: first is differences in the numeric range of values used to represent colors (everything here is a one-byte value from 0-255), and second is in the mapping of hue numbers to colors themselves (FastLED defaults to using a richer 'rainbow' color map, instead of the traditional 'spectrum' color mapping).

Numeric range differences: everything here is 0-255

In 'traditional' computer HSV color models, hue is represented as a number (of degrees) from 0-360. Saturation and value are often represented as numbers (percentages) from 0-100. But neither "360" nor "100" is a particularly computer-native number, and there's no strong reason to use 'degrees' to represent hue, nor 'percentages' to represent saturation or value; they're all pretty much arbitrary scales. Accordingly, to make your code smaller, faster, and more efficient, the FastLED library uses simple one-byte values (from 0-255) for hue, and for saturation, and for value. The performance implications are discussed further below, but suffice it to say that it's faster this way.

Color map: "Rainbow" vs "Spectrum"

Traditional computer HSV color models use a 'spectrum' color map, and FastLED does offer an "hsv2rgb_spectrum" function. However, by default FastLED uses a 'rainbow' color map instead of a spectrum. The 'rainbow' color map provides more evenly-spaced color bands, including a band of 'yellow' which is the same width as other colors, and which has an appropriately high inherent brightness. Traditional 'spectrum' HSV color maps have much narrower bands of yellow, and the yellow can also appear muddy. (Wikipedia has further discussion about the nature of spectrum-vs-rainbow: http://en.wikipedia.org/wiki/Rainbow#Number_of_colours_in_spectrum_or_rainbow )

Here is the "Rainbow" color map that FastLED uses for everything by default: FastLED Rainbow color map Click here for full-size chart.

Here is the "Spectrum" color map that FastLED provides if you call hsv2rgb_spectrum explicitly: FastLED Spectrum color map Click here for full-size chart.

These charts show several things about each part of the color map:

  • The bottom grayscale bar is the Radiance of each color: the total amount of light emitted. The FastLED color maps have extremely uniform radiance across the entire color map, and a correspondingly uniform power consumption across colors. In the Rainbow color map, rendering yellow takes a little bit more power than other colors, but otherwise the power usage and radiance curves are absolutely flat.
  • The top grayscale bar is the Luminance (Y) of each color: Luminance is Radiance weighted by the sensitivity of the human eye to each component of the light, and represents the apparent brightness of each color, more or less. All things being equal, the human eye is most sensitive to green.
  • The mixed color itself, rendered in such a way that it 'looks about right' on a computer monitor, despite the fact that in practice the color will be generated by a set of independent LEDs.
  • The Red, Green, and Blue components of each color, shown below the color mix.

Why FastLED full-range one-byte hues are faster

Animations using FastLED HSV colors are often be much, much faster than traditional HSV code, because FastLED HSV code has been designed explicitly for microcontroller environments where every byte and every cycle counts. One of the big design decisions was to represent hue as a number from 0-255, rather than from 0-359 (or 0-95); here's a code example of how the FastLED hue range design (from 0-255) makes your animation code faster and more compact, just by keeping 'hue' down to a single full-range one-byte number.

If 'hue' were to run from 0-359, as with traditional HSV ranges, here's roughly what you'd have to do have a variable that cycles to a new hue each time through your main loop, stepping by an arbitrary amount each time, and wrapping around back to the start.

uint16_t gHue = 0;
uint8_t  gHueDelta = 3;

void loop() {
  gHue += gHueDelta; // compute new hue
  gHue = gHue % 360; // bring hue back in range
  ...
}

This code (above) takes up about 84 bytes of program space, and can execute the "hue calculation" about 75,000 times per second. The use of "%" could be replaced with some IF statements and subtraction, but if you want to be able to add anything you like to gHue and have it still fall in the legal range of 0-359, this is basically what you'd wind up doing. (Using a hue value of 0-95, as shows up in some 'color wheel' HSV functions winds up with similar constraints; you have to keep checking to keep the hue 'in range' from zero to 95.)

Contrast this: the same functionality using FastLED's 0-255 range for HSV hues:

uint8_t gHue = 0;
uint8_t gHueDelta = 3;

void loop() {
  gHue += gHueDelta; // compute new hue value, automatically in range
  ...
}

This code, using FastLED's hue range of 0-255 takes up less than half the program space (just 34 bytes), and can execute the "hue calculation" about 1.5 million times per second: that's twenty times faster.

Of course, there's far more to animation performance than just incrementing one variable. But by keeping the range of 'hue' to a single, one-byte value with the full range of 0-255 is a design decision we've made that leads to the rest of your animation code becoming more compact, fast, and efficient as well.