I would like to create a higher contrast of the generated noise.

I would like to create a higher contrast of the generated noise. In Processing (with floats) I’m using the lerp function in combination with a constrain for this. This works perfect and gives the right results.

noise = constrain(lerp(0.5, perlinNoise, perlinContrast), 0.0, 1.0);

perlinNoise is a value between 0.0 and 1.0.
perlinContrast is a value higher then 1.0. For example 1.5.

I’m now porting my code to Arduino/FastLED functions, so I’d like to create the contrast without using floats.

I use now inoise16 which returns 0 - 65535 (uint16_t). So I thought using lerp16by16 would be the best option. My lerp function:

lerp16by16(32768, inoise16, 65535)

32768, because 0.5*65536 = 32768

However this would be a contrast like 1.0 (so no change). Since I want higher contrast I have to put in a number higher then 65535. However this is impossible of course due the limits of uint16_t.

Anyone an idea how to create this contrast function without floats?

Do you really need to get the contrast IN the noise? If not: Why not getting the contrast within the colormapping after the noise calculation. Mark showed with the NoisePlusPalette code a nice example how to do that.

I use the color palettes but only for color data. I’d like to manipulate hue, saturation and brightness separately with noise. So I really would like to apply to contrast in the noise data indeed.

OK, so here’s one way to think about this:
If you want to do this:
result = input * 1.5
then another way to think about that is this:
result = input + (input * 0.5)

In places where I want to ‘scale up’ an 8-bit value by a factor between one and two (call this ‘scale_factor’, I basically do something like this:

scale_factor = 1.5
scale_factor_minus_one = scale_factor - 1
scale_factor_minus_one_frac8 = scale_factor_minus_one * 256

result = input + scale8( input, scale_factor_minus_one_frac8 )

In many cases, all of the math at the top can be (and is) computed at compile time, so the compiled code looks like this, more or less. I’m assuming scale_factor is 1.5 here, as shown above.
result = input + scale8( input, 128 )
which is actually pretty fast.

For 16-bit values, you can use lerp16by16 – just subtract “1.0” (aka 65536) from your scale value, and add your input value to the result, sort of like this – if you start with floats (stay tuned below for no floats at all)
scale_factor = 1.5
scale_factor_minus_one = scale_factor - 1
scale_factor_minus_one_fracQ16 = scale_factor_minus_one * 65536
result = input + lerp16by16( 0, input, scale_factor_minus_one_fracQ16 )

OK, so in that case (above) there are still a couple of floats in the code. Here’s how to get them out at compile time:
uint32_t scale_factor16 = 1.5 * 65536; // compile-time math
uint16_t sf16_minus_one = scale_factor16 - 65536; // also compiled out
uint16_t result16 = input16 + lerp16by16( 0, input, sf16_minus_one);

As always, there are more situation-specific details you’ll probably have to slog through, but this may be a good start, especially if “contrast” is between 1.0 and 2.0.

If you want to scale up or down, say between 0.5 and 1.5, you can do
result = (input*0.5) + (input * (contrast - 1.0) );
…but using fixed point, so more like
result = (input >> 1) + scale8( input, contrast_minus_one_frac8);
or with lerp16, etc.

See also “scale16by8” if your contrast factor only needs eight bits of precision but your data is 16-bit.

Finally, if you want scaling between 0.00 and 1.99, you can do this:
result = scale8( input, (contrast / 2) * 128) * 2;
or
result = lerp16by16( 0, input, (contrast / 2) * 32768) * 2;

I’m sure this is just a blur of numbers and code now, but maybe it will gel a little after a while.

Actually, there’s some “increase the output range” code present in the NoisePlusPalette example code that we just posted. Check out lines 98-102 here https://github.com/FastLED/FastLED/blob/FastLED2.1/examples/NoisePlusPalette/NoisePlusPalette.ino#L98

The input range is 16-240, and I want 0-255.
So first, I subtract 16 (making the range now 0-224), making a ‘shifted input’.
Then I compute scale8( input, 39), which will produce a value from 0 to 34. [oops… too high, but watch what happens].
Then I qadd8 that back to the shifted input, which actually ranges from 0 to (224 + 34) = 258… but the saturating math saves my butt here, and clamps the result to 255.

So having discovered this oops, I was about to go and change that to scale8( input, 36), which would give result from 0-255 without clamping. BUT THEN I remembered that ACTUAL output range I was seeing from the noise function was 16-238, not 16-240, and with those values my math is correct. So I’ll go change the comment so that it all works out a little better if anyone tries to re-create what’s going on. Like I just tried to do.

Update: comments now match the code, and reality, a bit better.

Great, this is exactely the kind of practical tutorial we need here! A precise step by step description of the workflow. There are still so many FastLED math functions I have no idea what they are good for yet. Those practical examples help me a lot to understand them! Thank you @Mark_Kriegsman . Herby I suggest to add a “Playground” category here or in the Wiki, where it is only about the practical usage of special FastLED functions.

Thanks Mark! Number examples work good for me. However your example only shift up. So I decided to try some things myself (thanks for the pointers and numbers, that helped me in my progress a lot).

The idea with contrast is that numbers below 0.5 go more towards 0.0 and above 0.5 go more towards 1.0.

I got the idea from this resource (used it also for saturation) which gives nice examples:

I filled in the formula for a value of 0.25 and 0.75 and then used the integer lerp with the same example.

With float math

lerp float: lerp(a, b, t) > result = a + t * (b-a)

a = 0.5 (middle grey)
b = 0.25 (noise)
t = 1.5 (contrast)

result = 0.5 + 1.5 * (0.25-0.5) = 0,125

a = 0.5 (middle grey)
b = 0.75 (noise)
t = 1.5 (contrast)

result = 0.5 + 1.5 * (0.75-0.5) = 0,875

With integer math (took 8 bit for convenience)

lerp int: lerp(a, b, t) > result = a + (t * (b-a)) >> 8).

a = 128 (middle grey) (0.5 * 256 )
b = 64 (noise) (0.25 * 256)
t = 384 (contrast)

result = 128 + (384 * (64-128) / 256 = 32 (0.125*256)

a = 128 (middle grey) (0.5 * 256)
b = 192 (noise) (0.75 * 256)
t = 384 (contrast)

result = 128 + (384 * (192-128) / 256 = 224 (0.875*256)

Since that worked good, I decided to check the min and max 0 and 255.

a = 128 (middle grey)
b = 0 (noise)
t = 384 (contrast)

result = 128 + (384 * (0-128) / 256 = - 64

a = 128 (middle grey)
b = 255 (noise)
t = 384 (contrast)

result = 128 + (384 * (255-128)/256 = 320

Solution (I think)

The problem is of course the number 384. Which is out of range of uint8_t. I experimented a bit fiddling with numbers like you explained:

256 + 128 = 384
And then doing lerp(128,192,256) + lerp(128,192,128).

However the range is actually -64 to 320. Which is 64 at each side (0-64 and 256+64).

So it would become: lerp(128,64,256) - 64 + lerp(128,64,128) - 64 (got this concept by just looking at the numbers, so I hope it works for other contrast values)

example with numbers:

128 + (256 * (192-128) / 256 = 192 - 64 = 128
128 + (128 * (192-128) / 256 = 160 - 64 = 96

224

pseudo-code:

a // always 128 (hardcoded)
b // input
scale_factor // contrast (now in floats to show the idea later hardcoded integers)

float scale_factor = 1.5; // contrast
float scale_factor_minus_one = scale_factor - 1;
uint8_t scale_factor_minus_one_frac8 = (uint8_t) scale_factor_minus_one * 256;

uint8_t delta = b - 128;

uint8_t scaled_part1 = scale8( delta, 256);
uint8_t scaled_part2 = scale8( delta, scale_factor_minus_one_frac8);

uint8_t result = (a + scaled_part1 - scale_factor_minus_one_frac8/2) + (a + scaled_part2 - scale_factor_minus_one_frac8/2);

// which we can write as
uint8_t result = 256 + scaled_part1 + scaled_part2 - scale_factor_minus_one_frac8;


I didn’t test it yet. So if you see mistakes please let my know.
Do you see optimimalisations? I still need a constrain to 0 - 255. How would I utilize qadd8 and qsub8 for that? I was
in doubt about the order.

Also I think I need to do it with 16bit math. Since I found that noise16 is more smooth (however I noticed that you put a smoothing function in the example code now as well).

@Mark_Kriegsman I did a tryout sketch. Since I use negative numbers I can’t work internally with uint8_t.

See gist:

It does what it needs to do with a contrast of 128.

contrast: 128
input : 0, 30, 64, 128, 192, 221, 255,
output : -64, -19, 32, 128, 224, 267, 318,

However with a lower contrast it goes wrong with numbers above 128 and below 128. I expect they 128 as input always returns 128.

Example:
contrast: 118
input : 0, 30, 64, 128, 192, 221, 255,
output : -49, -6, 44, 138, 231, 273, 323,

contrast: 138
input : 0, 30, 64, 128, 192, 221, 255,
output : -79, -33, 19, 118, 216, 261, 313,

Any idea’s. Is this due the use of int?

In the end I would like to use it with 16 bit noise values. Which can cause problems with int because there is no place for the sign.

Hope you have time to take a look.

Ok, I did another one which seems to work alright. Probably it can be optimized, but it gives at least the expected results:

Optimizing code is a skill on it’s own :slight_smile: I think I can better leave that to the experts over here.

// constrast factor is from 1.0 - 2.0 or 0 - 255
int applyContrast(uint8_t input, uint8_t contrast_factor)
{
// around 0
int inputShift = (int) input - 128; // -128 - 128

// 1 + (i * scale) / 256;
inputShift = inputShift + (inputShift * contrast_factor) / 256;

return (int) inputShift + 128;

}

And a version for 16bit.
I don’t now if its possible without typecasting, but I have no idea how to deal other wise with negative numbers.

uint16_t applyContrast(uint16_t input, uint8_t contrast_factor)
{ // scaling around point / dilation middle grey = 32768

// typecast to long otherwise we don't have enough space
long inputTest = input + (long(input - 32768) * contrast_factor) / 256;
return (uint16_t) constrain(inputTest,0,65535);

}