Adafruit will not be shipping orders Thanksgiving Day, Thursday November 22, 2018. Expedited orders placed after 11am ET Wednesday November 21 will go out Friday November 23.
0

‘Constant Brightness’ HSB to RGB Algorithm

cross posted from my blog.

For an embedded project I’m working on, I had to implement an algorithm to convert from HSB (hue/saturation/brightness) to 8-bit RGB color values for PWM’ing LEDs. This is what I came up with. It creates a ‘constant brightness’ RGB value: at full saturation (no ‘whitewash’), only two of the 3 RGB colors are on at a time, and their ‘on’ times are mathematically complimentary (though not phase-complimentary) with respect to the max 255 value.

Increasing saturation will increase the overall brightness of the LEDs, but that is to be expected — for a given maximum magnitude, there is more overall energy in white light than there is in light of a particular color. The saturation calculation works by adding a constant ‘floor’ value to all channels. Individual color values are then placed between this floor and the 255 maximum. A saturation value of 0 results in all channels at 100% duty cycle.

The ‘brightness’ is the last calculation performed. It takes a saturation modified hue value, and simply proportions it to the maximum value. So, a brightness of 197 will output light which is ~197/255 of the maximum output value. Naturally, there are losses inherent to integer arithmetic, but it’s close enough for most uses. Further, the linear nature of the brightness control means it is not ‘gamma corrected’ — that would require logarithmic brightness control which, for what I’m doing, is completely unnecessary.

This algorithm uses no sine tables or floating point math, so it’s pretty fast, though it could probably be optimized to use shifts and adds instead of mults and divides. It’s also relatively small. The code itself is in C, so it can be used on most platforms.

Click ‘more’ for the code snippet and just copy/paste into a text editor —

/******************************************************************************
 * accepts hue, saturation and brightness values and outputs three 8-bit color
 * values in an array (color[])
 *
 * saturation (sat) and brightness (bright) are 8-bit values.
 *
 * hue (index) is a value between 0 and 767. hue values out of range are
 * rendered as 0.
 *
 *****************************************************************************/
void hsb2rgb(uint16_t index, uint8_t sat, uint8_t bright, uint8_t color[3])
{
	uint16_t r_temp, g_temp, b_temp;
	uint8_t index_mod;
	uint8_t inverse_sat = (sat ^ 255);

	index = index % 768;
	index_mod = index % 256;

	if (index < 256)
	{
		r_temp = index_mod ^ 255;
		g_temp = index_mod;
		b_temp = 0;
	}

	else if (index < 512)
	{
		r_temp = 0;
		g_temp = index_mod ^ 255;
		b_temp = index_mod;
	}

	else if ( index < 768)
	{
		r_temp = index_mod;
		g_temp = 0;
		b_temp = index_mod ^ 255;
	}

	else
	{
		r_temp = 0;
		g_temp = 0;
		b_temp = 0;
	}

	r_temp = ((r_temp * sat) / 255) + inverse_sat;
	g_temp = ((g_temp * sat) / 255) + inverse_sat;
	b_temp = ((b_temp * sat) / 255) + inverse_sat;

	r_temp = (r_temp * bright) / 255;
	g_temp = (g_temp * bright) / 255;
	b_temp = (b_temp * bright) / 255;

	color[RED] 	= (uint8_t)r_temp;
	color[GREEN]	= (uint8_t)g_temp;
	color[BLUE]	= (uint8_t)b_temp;
}


Lianna has optimized the code (below, in the comments), but some of her parentheses are showing up as smilies. I don’t know how to fix that so I’ll just add it to the post as preformatted text. Thanks Lianna! Great work!

Lianna’s version 1:

void hsb2rgbAN1(uint16_t index, uint8_t sat, uint8_t bright, uint8_t color[3]) {
    uint8_t temp[5], n = (index &gt;&gt; 8) % 3;
    temp[0] = temp[3] = (uint8_t)((                                        (sat ^ 255)  * bright) / 255);
    temp[1] = temp[4] = (uint8_t)((((( (index &amp; 255)        * sat) / 255) + (sat ^ 255)) * bright) / 255);
    temp[2] =          (uint8_t)(((((((index &amp; 255) ^ 255) * sat) / 255) + (sat ^ 255)) * bright) / 255);
    color[RED]  = temp[n + 2];
    color[GREEN] = temp[n + 1];
    color[BLUE]  = temp[n    ];
}

version 2:

void hsb2rgbAN2(uint16_t index, uint8_t sat, uint8_t bright, uint8_t color[3]) {
    uint8_t temp[5], n = (index &gt;&gt; 8) % 3;
// %3 not needed if input is constrained, but may be useful for color cycling and/or if modulo constant is fast
    uint8_t x = ((((index &amp; 255) * sat) &gt;&gt; 8) * bright) &gt;&gt; 8;
// shifts may be added for added speed and precision at the end if fast 32 bit calculation is available
    uint8_t s = ((256 - sat) * bright) &gt;&gt; 8;
    temp[0] = temp[3] =              s;
    temp[1] = temp[4] =          x + s;
    temp[2] =          bright - x    ;
    color[RED]  = temp[n + 2];
    color[GREEN] = temp[n + 1];
    color[BLUE]  = temp[n    ];
}


Stop breadboarding and soldering – start making immediately! Adafruit’s Circuit Playground is jam-packed with LEDs, sensors, buttons, alligator clip pads and more. Build projects with Circuit Playground in a few minutes with the drag-and-drop MakeCode programming site, learn computer science using the CS Discoveries class on code.org, jump into CircuitPython to learn Python and hardware together, or even use Arduino IDE. Circuit Playground Express is the newest and best Circuit Playground board, with support for MakeCode, CircuitPython, and Arduino. It has a powerful processor, 10 NeoPixels, mini speaker, InfraRed receive and transmit, two buttons, a switch, 14 alligator clip pads, and lots of sensors: capacitive touch, IR proximity, temperature, light, motion and sound. A whole wide world of electronics and coding is waiting for you, and it fits in the palm of your hand.

Join 8,700+ makers on Adafruit’s Discord channels and be part of the community! http://adafru.it/discord

CircuitPython – Python on Microcontrollers is here!

Have an amazing project to share? Join the SHOW-AND-TELL every Wednesday night at 7:30pm ET on Google+ Hangouts.

Join us every Wednesday night at 8pm ET for Ask an Engineer!

Follow Adafruit on Instagram for top secret new products, behinds the scenes and more https://www.instagram.com/adafruit/


Maker Business — Spotlight on Makeblock, one of the latest companies to find success in the STEAM market

Wearables — Emphasize the light

Electronics — Avoid serial confusion!

Biohacking — BDNF a Biohackers Best Friend

Python for Microcontrollers — Python powers costumes, and community @circuitpython @micropython @ThePSF #Python

Get the only spam-free daily newsletter about wearables, running a "maker business", electronic tips and more! Subscribe at AdafruitDaily.com !



2 Comments

  1. Yeah, I don’t like unoptimized code either, but gcc does wonders with some of these expressions. Still, I would go with something like the following, if only because it’s shorter, so less error-prone but functionally 1:1 equivalent:

    void hsb2rgbAN1(uint16_t index, uint8_t sat, uint8_t bright, uint8_t color[3]) {
    uint8_t temp[5], n = (index >> 8) % 3;
    temp[0] = temp[3] = (uint8_t)(( (sat ^ 255) * bright) / 255);
    temp[1] = temp[4] = (uint8_t)((((( (index & 255) * sat) / 255) + (sat ^ 255)) * bright) / 255);
    temp[2] = (uint8_t)(((((((index & 255) ^ 255) * sat) / 255) + (sat ^ 255)) * bright) / 255);
    color[RED] = temp[n + 2];
    color[GREEN] = temp[n + 1];
    color[BLUE] = temp[n ];
    }

    If speed was more critical or not compiling with gcc (or emulated divs were longer than expected), I would settle for something like the following:

    void hsb2rgbAN2(uint16_t index, uint8_t sat, uint8_t bright, uint8_t color[3]) {
    uint8_t temp[5], n = (index >> 8) % 3;
    // %3 not needed if input is constrained, but may be useful for color cycling and/or if modulo constant is fast
    uint8_t x = ((((index & 255) * sat) >> 8) * bright) >> 8;
    // shifts may be added for added speed and precision at the end if fast 32 bit calculation is available
    uint8_t s = ((256 – sat) * bright) >> 8;
    temp[0] = temp[3] = s;
    temp[1] = temp[4] = x + s;
    temp[2] = bright – x ;
    color[RED] = temp[n + 2];
    color[GREEN] = temp[n + 1];
    color[BLUE] = temp[n ];
    }

    Approximation statistics (compared to output from your code), % of output within +-n:
    0:(42.7%) 1:(36.4%) 2:(17.5%) 3:(3.1%) 4:(0.3%)

    Looks quite similar 🙂

  2. D*** these emoticons… and whitespace folding…
    Testing PRE:

    void hsb2rgbAN2(uint16_t index, uint8_t sat, uint8_t bright, uint8_t color[3]) {
    uint8_t temp[5], n = (index >> 8) % 3;
    // %3 not needed if input is constrained, but may be useful for color cycling and/or if modulo constant is fast
    uint8_t x = ((((index & 255) * sat) >> 8) * bright) >> 8;
    // shifts may be added for added speed and precision at the end if fast 32 bit calculation is available
    uint8_t s = ((256 – sat) * bright) >> 8;
    temp[0] = temp[3] = s;
    temp[1] = temp[4] = x + s;
    temp[2] = bright – x ;
    color[RED] = temp[n + 2];
    color[GREEN] = temp[n + 1];
    color[BLUE] = temp[n ];
    }

    In any case, substitute “eight” “right parenthesis” in place of every smiley with sunglasses at the above post…

Sorry, the comment form is closed at this time.