## HSV(ish) Colour Wheel in Python

Years back I wrote something about HSV colour cycling for Arduino. Things have moved on: we’re all writing code in MicroPython/CircuitPython now and 8-bit micro-controllers are looking decidedly quaint. Yes, yes; some of you must still write code in PIC assembly language and I’m sure that’s very lovely for you indeed don’t @ me.

If you look at the output of a typical HSV to RGB algorithm, the components map something like this:

These lines remind me so much of sine waves, if very blocky ones. The red trace in particular is just the cosine function, with the input range of 0..2Ï€ and the output range of -1..1 both mapped to 0..1. The green and blue traces are just the red trace shifted horizontally by â…“ and â…” respectively.

Since we have transcendental functions in MicroPython, we don’t have to fossick about with linear approximations. The common RGB LED function wheel() uses similar linear interpolation as the graph above. Why make do with blocky cogwheels when you can have a smooth colour wheel?

```def cos_wheel(pos):
# Input a value 0 to 255 to get a colour value.
# scruss (Stewart Russell) - 2019-03 - CC-BY-SA
from math import cos, pi
if pos < 0:
return (0, 0, 0)
pos %= 256
pos /= 255.0
return (int(255 * (1 + cos( pos            * 2 * pi)) / 2),
int(255 * (1 + cos((pos - 1 / 3.0) * 2 * pi)) / 2),
int(255 * (1 + cos((pos - 2 / 3.0) * 2 * pi)) / 2))
```

Quite elegant, I thought. Yeah, it may be computationally expensive, but check next year when we’ll all be running even faster Âµcs. Certainly none of the mystery switch statements or nested conditionals you’ll see in other code. Just maths, doing its thing.

## much improved HSV colour cycling LED on Arduino

There were some flaws in the post HSV colour cycling LED on Arduino. This does much more what I wanted:

```/*
HSVÂ fade/bounceÂ forÂ ArduinoÂ -Â StewartÂ C.Â RussellÂ -Â scruss.comÂ -Â 2010/09/19

Wiring:
LEDÂ isÂ RGBÂ commonÂ cathodeÂ (SparkFunÂ sku:Â COM-09264Â orÂ equivalent)
Â Â Â Â *Â DigitalÂ pinÂ Â 9Â â†’Â 165Î©Â resistorÂ â†’Â LEDÂ RedÂ pin
Â Â Â Â *Â DigitalÂ pinÂ 10Â â†’Â 100Î©Â resistorÂ â†’Â LEDÂ GreenÂ pin
Â Â Â Â *Â DigitalÂ pinÂ 11Â â†’Â 100Î©Â resistorÂ â†’Â LEDÂ BlueÂ pin
Â Â Â Â *Â GNDÂ â†’Â LEDÂ commonÂ cathode.
*/

#defineÂ REDÂ Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â 9Â // pin for red LED; green on RED+1 pin, blue on RED+2 pin
#defineÂ DELAYÂ Â Â Â Â Â Â Â Â Â Â Â Â Â 2

long rgb[3];
long rgbval, k;
float hsv[3] = {
Â Â 0.0,Â 0.5,Â 0.5
};
float hsv_min[3] = {
Â Â 0.0,Â 0.0,Â 0.4Â // keep V term greater than 0 for smoothness
};
float hsv_max[3] = {
Â Â 6.0,Â 1.0,Â 1.0
};
float hsv_delta[3] = {
Â Â 0.0005,Â 0.00013,Â 0.00011
};

/*
chosenÂ LEDÂ SparkFunÂ sku:Â COM-09264
Â hasÂ MaxÂ LuminosityÂ (RGB):Â (2800,Â 6500,Â 1200)mcd
Â soÂ weÂ normalizeÂ themÂ allÂ toÂ 1200Â mcdÂ -
Â RÂ Â 1200/2800Â Â =Â Â 0.428571428571429Â Â Â =Â Â Â 109/256
Â GÂ Â 1200/6500Â Â =Â Â 0.184615384615385Â Â Â =Â Â Â Â 47/256
Â BÂ Â 1200/1200Â Â =Â Â 1.0Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â =Â Â Â 256/256
Â */
long bright[3] = {
Â Â 109,Â 47,Â 256
};

void setup () {
Â Â for (k=0; k<3; k++) {
Â Â Â Â pinMode(RED + k, OUTPUT);
Â Â Â Â analogWrite(RED + k, rgb[k] * bright[k]/256);
Â Â Â Â if (k>1 && random(100) > 50) {
Â Â Â Â Â Â // randomly twiddle direction of saturation and value increment on startup
Â Â Â Â Â Â hsv_delta[k]Â *=Â -1.0;
Â Â Â Â }
Â Â }
}

void loop() {
Â Â for (k=0; k<3; k++) { // for all three HSV values
Â Â Â Â hsv[k]Â +=Â hsv_delta[k];
Â Â Â Â if (k<1) { // hue sweeps simply upwards
Â Â Â Â Â Â if (hsv[k] > hsv_max[k]) {
Â Â Â Â Â Â Â Â hsv[k]=hsv_min[k];
Â Â Â Â Â Â }Â Â Â Â
Â Â Â Â }
Â Â Â Â else { // saturation or value bounce around
Â Â Â Â Â Â if (hsv[k] > hsv_max[k] || hsv[k] < hsv_min[k]) {
Â Â Â Â Â Â Â Â hsv_delta[k]Â *=Â -1.0;
Â Â Â Â Â Â Â Â hsv[k]Â +=Â hsv_delta[k];
Â Â Â Â Â Â }
Â Â Â Â }
Â Â Â Â hsv[k]Â =Â constrain(hsv[k], hsv_min[k], hsv_max[k]); // keep values in range
Â Â }

Â Â rgbval=HSV_to_RGB(hsv[0],Â hsv[1],Â hsv[2]);
Â Â rgb[0]Â =Â (rgbvalÂ &Â 0x00FF0000)Â >>Â 16;Â // there must be better ways
Â Â rgb[1]Â =Â (rgbvalÂ &Â 0x0000FF00)Â >>Â 8;
Â Â rgb[2]Â =Â rgbvalÂ &Â 0x000000FF;

Â Â for (k=0; k<3; k++) { // for all three RGB values
Â Â Â Â analogWrite(RED + k, rgb[k] * bright[k]/256);
Â Â }
Â Â delay(DELAY);
}

long HSV_to_RGB( float h, float s, float v ) {
Â Â /*
Â Â Â Â Â modifiedÂ fromÂ AlvyÂ RayÂ Smith'sÂ site:
Â Â Â http://www.alvyray.com/Papers/hsv2rgb.htm
Â Â Â HÂ isÂ givenÂ onÂ [0,Â 6].Â SÂ andÂ VÂ areÂ givenÂ onÂ [0,Â 1].
Â Â Â RGBÂ isÂ returnedÂ asÂ aÂ 24-bitÂ longÂ #rrggbb
Â Â Â */
Â Â int i;
Â Â float m, n, f;

Â Â // not very elegant way of dealing with out of range: return black
Â Â if ((s<0.0) || (s>1.0) || (v<0.0) || (v>1.0)) {
Â Â Â Â return 0L;
Â Â }

Â Â if ((h < 0.0) || (h > 6.0)) {
Â Â Â Â return long( v * 255 ) + long( v * 255 ) * 256 + long( v * 255 ) * 65536;
Â Â }
Â Â iÂ =Â floor(h);
Â Â fÂ =Â hÂ -Â i;
Â Â if ( !(i&1) ) {
Â Â Â Â fÂ =Â 1Â -Â f;Â // if i is even
Â Â }
Â Â mÂ =Â vÂ *Â (1Â -Â s);
Â Â nÂ =Â vÂ *Â (1Â -Â sÂ *Â f);
Â Â switch (i) {
Â Â case 6:
Â Â case 0: // RETURN_RGB(v, n, m)
Â Â Â Â return long(v * 255 ) * 65536 + long( n * 255 ) * 256 + long( m * 255);
Â Â case 1: // RETURN_RGB(n, v, m)
Â Â Â Â return long(n * 255 ) * 65536 + long( v * 255 ) * 256 + long( m * 255);
Â Â case 2:  // RETURN_RGB(m, v, n)
Â Â Â Â return long(m * 255 ) * 65536 + long( v * 255 ) * 256 + long( n * 255);
Â Â case 3:  // RETURN_RGB(m, n, v)
Â Â Â Â return long(m * 255 ) * 65536 + long( n * 255 ) * 256 + long( v * 255);
Â Â case 4:  // RETURN_RGB(n, m, v)
Â Â Â Â return long(n * 255 ) * 65536 + long( m * 255 ) * 256 + long( v * 255);
Â Â case 5:  // RETURN_RGB(v, m, n)
Â Â Â Â return long(v * 255 ) * 65536 + long( m * 255 ) * 256 + long( n * 255);
Â Â }
}Â
```