If temporal dithering is no option:

If temporal dithering is no option: what would be the best way to make sure that the lowest bit of r, g and b is always 1? I want no single unlit led - preferably independent from the choosen global brightness level.

Well, let’s ignore global brightness and dithering and color correction and so on for a bit.

This code makes sure that a pixel is at least (1,1,1):

leds[i] |= CGRB(1,1,1);

That’s the vertical “or” bar, and the equals sign. The resulting pixel is sort of the or-ing together of the original value and (1,1,1).

But… will (1,1,1) always light up the LEDs? Definitely NOT. Here are some reasons why:

  1. if the brightness is turned down below 255 even a little, (1,1,1) will round down to (0,0,0). Global brightness does NOT use “scale8_video”; it can’t, for speed reasons.

  2. due to the way the brightness dimming works, there’s (currently) always a drop of at least one for each color channel. So (4,5,6) in your led array is sent to the LED strip as (3,4,5). We know about this issue, and it’s one we’d love to improve, but this is the cost of the global brightness and color correction magic. Pretty easy to work with IF you know that it’s happening.

  3. Not all LED strips are actually 8-bits deep! For example, the LPD8806 (which is nice and fast) only displays seven bits of each color, so (1,1,1) is the same as (0,0,0). Likewise (3,4,5) is the same as (2,4,6), and that’s just the nature of the LED controller chip.

  4. color correction and temperature adjustments. If you have color correction set to an extreme example like (255,128,255), then if you had a pixel with led array values of (2,2,2), it would get color corrected down to (1,0,1).

SO.

So if you want to ‘make sure’ that all LEDs are always lit, you need to do the “or-equals” operation on every pixel AND you need to figure out what value to “or” the pixels with to make sure they’re lit.

Here’s a starter idea:

// calculation brightness floor
uint8_t floor = (65535 / FastLED.getBrightness()) >> 8;
// adjust for rounding
floor = qadd8( floor, 1);
// create floorColor
CRGB floorColor( floor, floor, floor);

for( uint16_t i = 0; i < NUM_LEDS; i++) {
leds[i] |= floorColor;
}

You might have to adjust the rounding number from 1 up to 2, but this is probably a good start. If you’re using color correction, you’ll have to reverse out the effect of that, too. Lot of work…

Thanks for your elaboration, Mark! The usecase is to calculate a simplex noise - use this for a color mapping (which might lead to some 0s or not) - and to “lift” the RGB values up, if necessary. I did this with some if leds[i].r < 1… which looks ugly and works only with full brightness / no color correction. I’ll try out your or-equal magic, thank you a lot!

The or-equal business is basically the same as
if( existing < new) existing = new
for each color channel.

The real trick is computing what the floor value should be. The example I gave above should be a good starting point – but it doesn’t take into account color correction as it is written there.