Led Tricks Gamma Correction
Led Tricks Gamma Correction
https://learn.adafruit.com/led-tricks-gamma-correction
The Issue 3
Digging Deeper 6
• Perks and Caveats
An LED driver IC — the “smarts” inside NeoPixels and other addressible LEDs — use p
ulse-width modulation, switching the LED on and off very quickly (about 400 times per
second in the case of NeoPixels), much faster than our eyes can perceive; we just see
a uniform brightness. The “on” vs. “off” time determines the intensity.
When you program in a “halfway” level (like 127 out of the maximum 255), you are
indeed getting something very close to a proper 50% duty cycle. The LEDs are doing
the correct thing.
Why yellow then, instead of orange? It’s nothing to do with the LEDs or your code, it’s
how our eyes work…
Eyes evolved to find food by daylight and evade predators by starlight. That’s a huge
dynamic range. A sort of non-linearity is built in so details can be seen at both
extremes. It’s extraordinarily sensitive at the low end…we perceive changes there as
more pronounced than objective measurement (or LED duty cycle numbers) would
suggest:
Your monitor, computer operating system and applications typically already have this
correction built in. So when you pick orange in Photoshop, the R/G/B values
shown are 255/127/0, as you’d intuitively expect. We can do something similar for
LEDs…
This table remaps linear input values (the numbers we’d like to use; e.g. 127 = half
brightness) to nonlinear gamma-corrected output values (numbers producing the
desired effect on the LED; e.g. 36 = half brightness).
result = pgm_read_byte(&gamma8[input]);
That might get tedious after a while…you can write a wrapper function around
setPixelColor() to make it easier, do all your gamma table lookups in that single place.
PROGMEM is a fantastic RAM-saver for Arduino sketches…if you’re not familiar, it’s ex
plained on the Arduino web site (), and our Memories of an Arduino () guide also
offers some insights.
Optional: you can move the gamma table to the bottom of your code (maybe you
don’t want to look at it as the first thing every time you open a sketch) by adding this
line near the top:
The table isn’t really extern (this normally means some variable or data is located in
another source file), but this lets us push it to the bottom while referring to it earlier in
our code.
Gamma correction normally would use floating-point math, not something the Arduino
excels at. This table lookup only takes about a microsecond, and despite its apparent
size it’s really much smaller than invoking the equivalent floating-point math function
(256 bytes vs. ~2KB of flash space).
This doesn’t give us ultimate control, but it’s adequate for the vast majority of cases.
Orange will now look orange!
Digging Deeper
You might want a slightly different gamma table. Here’s the code that generated our
sample. This is not an Arduino sketch…it’s written in Processing (free download ())…
chosen because it installs easily on Windows, Mac or Linux. Run this program, then
copy-and-paste the output into an Arduino sketch, replacing the gamma[] table.
This first line sets the exponent for the correction curve:
Higher values here will result in dimmer midrange colors, lower values will be brighter.
1.0 = no correction. The default of 2.8 isn’t super-scientific, just tested a few numbers
and this seemed to produce a sufficiently uniform brightness ramp along an LED strip;
maybe you’ll refine this further.
max_in and max_out set the input and output ranges of the table. The defaults here
are for the NeoPixel brightness range of 0–255…if you’re working with LPD8806
strips (which have a 7-bit brightness), max_out can be changed to 127 (might want to
leave max_in at 255, since a lot of existing code assumes 8-bit colors).
If you’re really persnickety, you can make separate tables for red, green and blue to
achieve a more neutral white balance, adjusting max_out for each.
On the downside, look at the first few lines of the gamma-correction table:
Notice the first 28 elements are all 0, the next 12 are 1, next 7 are 2, and so forth.
Input values from 1–27 all result in an “off” LED. This is the unfortunate reality of quant
ization. The LED driver only handles 256 distinct PWM settings, period. When we
“Luxury” LED drivers such as the PCA9685 () and TLC5947 (http://adafru.it/1429) use
12-bit PWM (4096 output levels) to minimize the effects of quantization. More
advanced drivers like FadeCandy use dithering to 'fake' a wider dynamic range