Levels control shader

January 28, 2009

A little piece of code to reproduce the Levels control of Photoshop…


Input levels:

I already talked about the gamma correction (mid-tone slider), and I won’t explain what the shadows and highlights (black/white points) sliders are doing (excellent article here) but basically these can be used to remap the tonal range of the image. Here is how it’s calculated:

#define GammaCorrection(color, gamma)  pow(color, vec3(1.0 / gamma))
#define LevelsControlInputRange(color, minInput, maxInput) min(max(color – vec3(minInput), vec3(0.0)) / (vec3(maxInput) – vec3(minInput)), vec3(1.0))
#define LevelsControlInput(color, minInput, gamma, maxInput) GammaCorrection(LevelsControlInputRange(color, minInput, maxInput), gamma)

Example with values from the 1st screenshot (blackpoint = 90/255, gamma = 4, whitepoint = 150/255), red: original color, green: blackpoint & whitepoint modified, blue: same with gamma:


Output levels:

This is useful to shorten the tonal range meaning compressing it to reduce contrast and shift it, details here.

#define LevelsControlOutputRange(color, minOutput, maxOutput) mix(vec3(minOutput), vec3(maxOutput), color)

Example with values from the 1st screenshot (min output = 40/255, max output = 180/255), red: original color, green: output levels applied:


Putting it all together:

#define LevelsControl(color, minInput, gamma, maxInput, minOutput, maxOutput) LevelsControlOutputRange(LevelsControlInput(color, minInput, gamma, maxInput), minOutput, maxOutput)

Same example but both input and output levels taken into account, red: original color, green: final result:


So these macros make it quite easy to increase or reduce contrast, shift and clip tonal range, lighten or darken shadows and highlights. I added the (GLSL / HLSL) code to the Photoshop Math shaders.



  1. Just passing by.Btw, you website have great content!

    Don’t pay for your electricity any longer…
    Instead, the power company will pay YOU!

  2. Hey these shaders and others in your articles are great. Ive been working on some film shaders myself, technicolor processing and lomo looks using LUTs. Very similar work. Great content. What license do you release these as? I may want to port some of these to Quartz Composer.


  3. hello, great explanation.
    i need to implement a similar program logic into one of my matlab projects. could you please explain how changing the input level sliders would work mathematically?
    thank you!

  4. The macro I gave in the post:
    #define LevelsControlInputRange(color, minInput, maxInput) min(max(color – vec3(minInput), vec3(0.0)) / (vec3(maxInput) – vec3(minInput)), vec3(1.0))

    Easier to read like this:
    color = max(color – minInput, 0.0) / (maxInput – minInput);
    color = min(color, 1.0);

    Remember that in high level shaders the color is normalized in the range [0, 1].

    The min level (left slider), clamps the color in the lower values (that’s why we take the max(color – minInput, 0.0). Any color < minInput will be 0 (instead of going negative).

  5. The color (or tonal range) needs to be remapped between minInput and maxInput. That’s why we divide by (maxInput – minInput).

    At the end, the max level (right slider) clamps the color in the higher values (that’s why we take the min(color, 1.0). Any color > maxInput will be 1.0.

    Make sure you read the levels article I linked in the post: http://www.cambridgeincolour.com/tutorials/levels.htm

  6. This has really helped me understand fragment-shaders!

    Could you explain how the curves tool in photoshop translates?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: