Is there a simple way to increase the contrast of the colors in my

(Jeremy Williams) #1

Is there a simple way to increase the contrast of the colors in my LED array? I’m using FASTLed in a 16x16 matrix that displays BMP graphics and would like to adjust contrast without manipulating the source images.

(Stefan Petrick) #2

You could try something like val = dim8_raw(val);
or val = dim8_video(val);

“To account for the fact that these values are linear, while human perception is not, there’s functions to adjust the dim/brightness of a value from the 0-255 value, linear, to a more perceptual range.”

Details here:

A good led color correction also helps to improve the seen contrast.

(Stefan Petrick) #3

From a photographic point of view increasing contrast means to spread the histogram: dark colors become darker, bright colors become even brighter and the mid tones stay (nearly) untouched. So it depends highly on your BMP data which way of processing is efficient. If you have only mid tones in the raw data you can try the trick to manipulate only the histogram of one or two basic color channels instead of r, g and b. If you would post some of your bitmaps I could maybe suggest more precise how to filter them.

(Kasper Kamperman) #4

Here I have an implementation that I’ve made (with help of the @Mark_Kriegsman ) to change contrast in noise. Of course you can apply it on a Palette as well.

See discussion over here:

(Mark Kriegsman) #5

It’s really remarkable the difference in net perceived color, brightness, and contrast between pixels on a screen and LED pixels, isn’t it?

I’m finding that when converting from screen colors to LED colors, I pretty much have to apply some kind of gamma correction to make it look even close to the same visually. I’ve wound up using gamma correction factors between 2.0 and 4.0, with the sweet spot being around 2.5 – actually a little higher for the green channel (2.7?).

I’ve got some gamma adjusting code which I’ll paste in here in a moment. It (currently) uses floating point math, so I wouldn’t put it in your innermost loops, but it’s perfectly OK to use as a conversion function when you read an image.

Of course if you can pre-adjust the gamma (on the origin PC?), you don’t have to worry about it once the data is on the Arduino.

(Mark Kriegsman) #6

Here are the simple (floating point) gamma adjustment functions I mentioned: one takes a single value and adjusts the gamma of a single brightness level, the second one applies a uniform gamma correction to a CRGB color, and the last one applies three different gamma corrections to the R, G, and B channels of a CRGB color.

// Apply a gamma correction to a single brightness level
uint8_t applyGamma_video( uint8_t brightness, float gamma)
float orig;
float adj;
orig = (float)(brightness) / (255.0);
adj = pow( orig, gamma) * (255.0);
uint8_t result = (uint8_t)(adj);
if( (brightness > 0) && (result == 0)) {
result = 1; // never gamma-adjust a positive number down to zero
return result;

// Apply a uniform gamma correction of a CRGB color
CRGB applyGamma_video( const CRGB& orig, float gamma)
CRGB adj;
adj.r = applyGamma_video( orig.r, gamma);
adj.g = applyGamma_video( orig.g, gamma);
adj.b = applyGamma_video( orig.b, gamma);
return adj;

// Apply different gamma corrections to each channel of
// a CRGB color.
CRGB applyGamma_video(
const CRGB& orig, float gammaR, float gammaG, float gammaB)
CRGB adj;
adj.r = applyGamma_video( orig.r, gammaR);
adj.g = applyGamma_video( orig.g, gammaG);
adj.b = applyGamma_video( orig.b, gammaB);
return adj;

You’d use the code like this, more or less:

CRGB bmpColor;
CRGB ledColor;

// … somehow load bmpColor with an input color …

ledColor = applyGamma( bmpColor, 2.5);

// … now store and/or display ledColor

This will help make the colors richer (more saturated) and less pale and washed-out looking on the LEDs. The “_video” notation in the function names is there because these functions (like scale8_video) have a special clause that makes sure that no positive (non-zero) input value is ever ‘rounded down’ to zero. This is a tweak that we make specifically for RGB LEDs which can only display integer values of light; rounding down to the next lower brightness level is always OK – UNLESS we’re already at “1”, in which case we should just stay there. These functions take care of that.

You should play around with the gamma correction factor (2.5 is a good start, 2.2 is popular, too) to see what number works out best for your particular input sources and LED outputs – there is no one true correct value, it’s just whatever looks best to you.

I recently did some conversions of screen-based RGB colors to colors to be displayed on LEDs, and I used different corrections for each channel. I wound up using redGamma=2.6, greenGamma=2.2, and blueGamma=2.5, but again, these are just what looked about right to my eye for the application I was working on.

Oh, and just for the record, I was also using the FastLED color correction on the LED strip: setCorrection( TypicalLEDStrip), which tends to cut the green power a bit. Given the low gamma I had to use in my green conversion, I wonder if the input sources were already correcting for the oversensitivity of the eye to green. Since my input source was a semi-anonymous data file of RGB values, I can’t really go and ask the original author, and I suspect even if I could, all I’d get was an answer like “Well, it looked right to me on my screen, so I went with it.”

(Kasper Kamperman) #7

Hereby a luminance table generator script (for Processing). You can use the script of Mark as well to render a lookup table (to prevent floating point math in your code). Take in account that with Fastled a value of 1 is equal to 0 as well.

(Mark Kriegsman) #8

Great code, thank you, @Kasper_Kamperman .

(Marc Miller) #9

Great discussion. I totally appreciate this info.

(Jeremy Williams) #10

Thanks guys, this is really great info. I implemented dim8_video as suggested by @Stefan_Petrick first since it was the simplest and the result is quite dramatic – a huge improvement and very close to what I want to see. I converted the RGB values separately after they had been written to the array like this:

void adjustContrast()
for (int i = 0; i < NUM_LEDS; i++)
leds[i].r = dim8_video(leds[i].r);
leds[i].g = dim8_video(leds[i].g);
leds[i].b = dim8_video(leds[i].b);

The only problem is that running at lower brightness settings (e.g. FastLED.setBrightness(30)) causes some of the rgb sub-pixels to crush to black (salmon turn red, very dark grays turn black, etc.), despite using the _video version of the function. Any advice on how to correct this?

(Mark Kriegsman) #11

( dim8_video is basically a gamma correction of 2.0, just so you know … it’s a pretty good start. )

Can you share some of the numeric input and output values that you’re seeing for the colors that aren’t coming out the way you’d like?

(Jeremy Williams) #12

@Mark_Kriegsman Sure, here are some examples…

At brightness 30:
0xee1c24 looks red
0x231f20 looks black

At brightness 10: looks black
At brightness 20: looks blue
At brightness 30: returns to gray

(Mark Kriegsman) #13

And by ‘brightness’ you mean “FastLED.setBrightness(…)”, right?

(Jeremy Williams) #14

That’s correct. I should also say I’ve disabled both color correction & temperature settings (i.e. both set to 0xFFFFFF).

(Mark Kriegsman) #15

Thanks for the specific examples; I’ll try to take a look tonight.

(Jeremy Williams) #16

I just discovered that a global brightness setting of 1 “FastLED.setBrightness(1)” displays a completely black screen, same as a setting of 0 – regardless of the dim8_video adjustments we discussed. Perhaps this is related?

I’m running the latest 3.1 branch on a Teensy LC.

(Mark Kriegsman) #17

Integer math; everything rounds down.

(Mark Kriegsman) #18

To expand a bit: brightness multiplies each value by N/256ths – and rounds down.

(Ian Auch) #19

I’m looking at doing a similar project. Can anyone point me in the direction of some good documentation about how to display BMP graphics using FastLED?

(Jeremy Williams) #20

@Ian_Auch It’s probably best to start a new thread since your question is tangential, but I would start by examining the bmpDraw function in the following sketch. It uses the Adafruit Neopixel library but you can easily port it to FastLED.

@Mark_Kriegsman I’ll be curious what you have to say after you’ve tried the colors above. It appears that some subpixel values are so dark that they’re being rounded to zero (which is interesting since both dim8_video and FastLED.setBrightness are supposed to clamp at a value of 1, at least when color is present).