I'm having a small issue with using HSV colors.

I’m having a small issue with using HSV colors. I currently don’t have access to the LED strip setup so I’m using the serial monitor for debugging.

The problematic section of my code is converting a dynamically generated HSV colour to RGB. As reported by the serial monitor, the last color generated within the debug time is HSV 145,184,255 (204°,72%,100%). This falls within my expected ranges.

Immediately after generating this color I’m outputting its RGB values, which come out to RGB 19,78,145. This is clearly incorrect, as V=255 should mean at least one of the RGB channels is 255. A color picker confirms that this should output RGB 71,181,255.

Affected code:
…// newCol defined
data[i][ii]=CRGB(newCol);
if (millis() < 4000) { // Stop output after a while
Serial.print(“Set HSV “);
Serial.print(newCol.h);
Serial.print(”:”);
Serial.print(newCol.s);
Serial.print(":");
Serial.println(newCol.v);
Serial.print(“Got RGB “);
Serial.print(data[i][ii].r);
Serial.print(”:”);
Serial.print(data[i][ii].g);
Serial.print(":");
Serial.println(data[i][ii].b);
Serial.println();
}

Hi @1icri_Old_Account - thanks for the thoughtful and detailed start to this discussion!

From the data and code you provided, I think that the built-in HSV-to-RGB in FastLED is functioning correctly as designed. You may want to implement your own conversion, or use a different one, but I think the built-in one is working as intended. A little more info follows below.

There are several different ways to map HSV colors into RGB space, and they all have pros and cons; there is no one universal conversion that gets everything perfect. So when a developer chooses an HSV-to-RGB conversion, they’re making design decisions and trade-offs.

One of the big design decisions that we made with FastLED was this: we wanted all the HSV colors at a given Saturation and Value to produce the same amount of light (and use the same amount of electrical power) regardless of what Hue they were at. And that is designed into the built-in FastLED HSV-to-RGB color conversions, as can be seen on the color charts on this wiki page: https://github.com/FastLED/FastLED/wiki/FastLED-HSV-Colors

Here’s the text from that page on this topic: “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.”

So here’s how this relates to what you noticed: In this conversion model: just because “V” is 255 doesn’t mean that any one RGB channel must be 255.
Look at the R G and B graphs on the wiki page (above) to see what the design is.

So, consider the design of an HSV-to-RGB conversion, with constant-power / constant-brightness being a design goal. We decide that obviously Red is [255,0,0] (total power=255), and that also obviously Blue is [0,0,255] (total power=255). What should Purple be?

The ‘classic’ answer from the world of LCD screens and CRT monitors is [255,0,255]. That’s the answer you may get from screen/monitor -based color pickers and color converters as well. But in our world of individually powered LEDs, that formulation of purple has two problems. First, it draws twice as much power (total=510) as Red or Blue, and second it is not at all the same apparent brightness as Red or Blue.

We chose a different design: we chose constant brightness / constant power across all hues. And that means that Purple has to have the same total power as Red, and that means that Purple has to come out of the HSV-to-RGB conversion as something like [128,0,127] – with a total power of 255 – even though none of the R G or B channels are ‘maxed out’ at 255.

And yes, that Purple does not appear as bright as [255,0,255], but it does appear as bright as Red, or Blue, and we designed for constant brightness. (Well, except for a little bump in the yellows, which are a separate topic.)

And to be clear, there’s nothing stopping you from writing a simple little HSV-to-RGB conversion that would return [255,0,255] for the purple hue, and using that in your code in FastLED. But the built-in HSV-to-RGB conversion is designed for constant power across all hues, not maximum brightness at each hue separately.

You’re totally welcome to make other choices, and to implement other HSV-to-RGB conversions that have other design goals, but I think the built-in one that FastLED gives you ‘for free’ is working as designed here.

Make sense?

@Mark_Kriegsman Hi, yes I see now that your decisions make perfect sense. I forgot to check what the intended functionality of FastLED’s HSV color utilities was. As I don’t have access to LEDs for testing, I first created and tweaked my entire animation in Processing, making heavy use of HSV there. I then simply ported over to Arduino, using FastLED utilities simply for speed. On that note, could you recommend an suitable implementation which runs at a speed comparable to FastLED’s implementation?

Ah! Totally understandable.

Just so I understand: You’re now looking for an HSV-to-RGB conversion with the same ‘math’ as the one built in to Processing, but ported to Arduino?

@Mark_Kriegsman Yes, but keeping the 0-255 range if possible. Also I’m not sure how well optimised Processing’s implementation is, but I’m sure I can fiddle around with it a bit.

I did this lookup table based on the HSV from Processing: https://gist.github.com/kasperkamperman/990cd7424e12af5318f3. You could remove the interpolation, than you have 255 steps over the whole “circle”.

@Kasper_Kamperman Thanks for that, I may save it for future projects. However, on this occasion I’ve got very limited space so I’d prefer not to use lookup tables. Also, after many frustrated evenings, I’ve managed to produce a somewhat fast implementation myself.

Hereby another implementation that I made one year ago. This has all the colours bright and probably is comparable with the Processing HSB. I put some effort in optimising it based on the principles of how Mark designed his algorithm: https://gist.github.com/kasperkamperman/eeb9f1ca8a6c2b61f216