I came across HexaWS2811 that implemented dithering on the Teensy 3.1.

I came across HexaWS2811 that implemented dithering on the Teensy 3.1. He uses a gamma table with 16 sets of 256 values and switches between those. So this would take 4096 bytes in memory (which is the double of sram in an Arduino Uno). Each call the set is switched.

Could this work from Progmem? If I apply this table on the R,G,B values for the LEDarray, would it conflict with the FastLED dithering and/or colorcorrection (which might undo the applied dithering?) since that is applied when writing out the data to the strips?

I’m finding that different people use “dithering” to mean different things.

Are you basically looking for a gamma correction curve to be applied to the 8-bit source data, resulting in (for example) Q12.4 gamma-corrected data, which would then be temporally dithered onto the LEDs? (This is what I believe we’ve spoken about previously.)

There are a bunch of (simpler!) ways to transform the 8-bit source data into 8-bit (not Q12.4 or whatever) gamma-corrected data (e.g., a lookup table, as you’ve used before), but I wouldn’t really call this dithering.

On the third hand, you could transform the 8-bit source data into 8-bit gamma-corrected data using a function that varied over time or frames. E.g. alternating frames ‘rounded up’ and ‘rounded down’. This could probably be done at your code level.

And yes, you could totally use PROGMEM for a temporally-dithered gamma lookup table.

What frame rate / update rate are you wanting to output at, exclusive of the dithering? 30FPS? much faster? slower?

  • What is Q12.4?

  • I wouldn’t call a lookup table dithering either.

From what I understand is that the technique used in the Hexaws2811code is actually providing 16 lookup tables. Each update cycle a different table is used. So the frames are alternately rounded up and down.

I’m creating slow noise changes. This looks pretty good with saturation/color, however pretty awful with brightness. 30fps is fine. I use some noise code (about 25 leds). With color palette this runs pretty fast. (2-3ms on a Uno if I remember it well).

I’ll experiment with it. Just wanted to know if I’ve missed an important point that would make it totally impossible.

Just to be sure. Is there any gamma correction active in FastLED 2.1 by default? I know there isn’t a lookup table, but you talked one time about a proximation. Is this embedded in the color correction?

Working on the implementation. Posted a question on the HexaWS2811 Github including the code I’m using now (and the problem with RGB):

The Rainbow HSV-to-RGB code has some gamma correction built in, so that the “V” looks linear as you increase it. Or more linear than it otherwise would, anyway.


  • if you do all your work in HSV, and then assign those HSV values into the leds[] array (which implicitly calls hsv2rgb_rainbow), and then call show(), you’re getting one level of gamma correction already.

  • if you start from RGB, and stay in RGB the whole way, there’s no gamma correction built-in.

As for gamma correction, here’s the built-in FastLED gamma curve (“dim8”) versus the lookup table that Adafruit advocates:

*CHART 1 http://imgur.com/2PW5wYQ *

It’s actually respectably close, especially for “visual” purposes. I suspect we’re within the visual margin of error for many viewing conditions.

The only nit that I have with BOTH the Adafruit table and “dim8” is that they both bottom out to zero when they shouldn’t. For the Adafruit table, you can just change the entries from 0 to 1 (except the very first). For our side, use dim8_video instead of plain dim8; that’s exactly what it’s for.

OK, so it turns out that I have a further nit to pick with this whole situation, and it’s a purely practical one: Not only do I want linear changes in “V” to produce linear changes in brightness (which means: heavy gamma correction, especially at the low end), but I also want small changes in “V” to be visible … even at the low end, and even given that the LEDs can only hold integer values. So: there’s a conflict there. The gamma correction table says that all values from 1-50 all round down to one or two. Dim8_video rounds all values from 1-25 down to one or two. So if you’re trying to slowly ramp the brightness up, the first 50 (or 25) steps will barely show as any change at all. (This is where you would want to do temporal dithering, but let’s ignore that for the moment.)

Here’s what I’ve experimented with: “lindim8”. For the bottom half of the brightness curve, use a straight line, with slope 0.5. For the top half of the brightness curve, use dim8 (exponential). That looks like this:

*CHART 2 http://imgur.com/CiN5sHc *

DISCLAIMER: This is absolutely, definitely, “ad hoc engineering” at work – it’s based on me staring at pixels in a dark room for a long time, nothing more scientific than that.

That said, the net visual effect is pretty good. If you ramp up the brightness slowly, you get a lot of the same effect you’d get with a pure gamma curve, but input values from 1-25 map linearly to 1-12, NOT just to 1 or 2.

I’ll see if I can include that function in the next push of the library. It, like dim8, is very fast, and very small, which I care about a great deal.

The lindim8 sounds interesting. I’d love to try if it works.

I’m using the HSV2RGB_spectrum now, and there the dim8_video/scale8_video isn’t called?

Studying the Rainbow code. What is this function: scale8_video_LEAVING_R1_DIRTY ?

In the rainbow code I don’t see a call either that uses scale8 (or dim8_raw), or does this “dirty” function the same?

I wrote the HexaWS2811 referred to; yes it’s both gamma correction and temporal dithering in one; the sample uses 16 sets of gamma-correction tables (although you can use supplied python file to generate e.g. 8 or 4 sets) - this relies on quite fast updates to work without flickering, I use 110hz (max rate for the 300-led strips I’m using) and a 16-page dither pattern still causes (very minor) flicker; you’d want to use the best tradeoff given your update rate. If you’re updating at 60hz or less you’d definitely want only a 2 or 4 stage dither. It’s definitely worth doing the dithering on WS2812’s because their brightness resolution is rather poor at the low end. The “fadecandy” code is similar and that uses 400hz update (meaning short WS2812 strips are necessary, 64leds max) and at that speed you get great results from dithering. The Teensy 3.1 is enormously faster than an Atmel AVR-based Arduino and it does all the LED output with DMA; I very much advocate using the Teensy, it’s a monster compared to an AVR in every way (32 bit, more efficient CPU, much more ram/flash, more hardware, basically just ‘better’) and barely any more expensive. I’m not dissing the AVR, I’ve enjoyed it for years, but we live in the future now :slight_smile:
Anyway; if your WS2812 update rate is e.g. 30hz you should regenerate the dithering/gamma table to only be 2 stage (which will save you considerable memory also) or you’ll get objectionable flickering. You will of course also lose resolution a low intensities, but them’s the breaks.

Hi and welcome! Great to have your voice and insights here.

The multi-page gamma dither is very clever! I like it!

I suspect that on AVR, two or four pages would be sufficient (1 or 2 dither bits).

@Daniel_Garcia and I have had long conversations about “max dither depth” versus “update rate”: four bits is pretty aspirational only, three is possible, and two or one is almost always doable. The current default in FastLED is three bits, I believe-- with our thinking that we’ll eventually make it self-tuning based on the actual frame rate, given the user’s own code. Unlike FadeCandy, we don’t set a fixed update rate; that’s in the user’s control (and often highly variable).

We can do all kinds of fancy things on ARM that we can’t on AVR. Someday, we’ll start dividing features into some ARM-only and some universal (AVR+ARM). Independent of clock speed, the ability of haul data 32 bits at a time, and to have an N-but barrel shifter, makes a big difference.

I’ll take a look at the HexaWS2811 code. Always interesting to see what there is to learn there in other people’s implementation of The Ugly Parts! And someday, I’ll write up our reversed binary dithering…

Anyway, thanks for chiming in and helping out!

The _LEAVING_R1_DIRTY variations are semantically identical to the plain versions; the difference just has to do with assembly language optimizations on AVR.

Also worth noting that scale8(x,x) is the same as dim8(x), and scale8(x,x) does appear in the _rainbow code. (Or did you mean the _spectrum code?)

I meant _spectrum (so the raw hsv2rgb).

I still get the strange behavior with the primaries. They are kind of as normal, but the colors in between are a lot weaker. I’ve put “my own” hsb conversion in it as well, just to compare. With that I still get some really bright areas (yellow, green), but different.

On gist the file including the gamma table. Only FastLED2.1 is necessary (with rainbow to spectrum change in pixeltypes.h).

A potmeter on analog pin 0 controls the hue.

A good night of sleep does miracles. Applying the dithering on the brightness component (instead of applying it to red, green and blue separately) in CHSV does the trick (see modified the code):

That makes me wonder why dithering on rgb normally works good (since I suppose dithering on master brightness in FastLED is also applied in the rgb space?).

Do I always need to clean the array or do a memset in the loop?