◩ Graphics & images

Gamma correction and linear light: why gradients come out dark

What gamma is, why sRGB values are not proportional to light, and how to work in linear space to get correct gradients, blending and image resizing.

Try creating a gradient from pure red to pure green with a simple average of the RGB values. In the middle a dark brown band appears, which would not exist if you mixed two lights in reality. Or resize a photo with thin white lines on a black background: after shrinking, it looks darker than the original. Both problems have the same cause, gamma, and it is one of the most misunderstood topics in digital graphics.

Perception is not linear

The human eye does not perceive brightness in proportion to the amount of light. We are far more sensitive to differences in dark areas than in bright ones: going from one to two candles in a dark room is a huge change, going from a hundred to a hundred and one is imperceptible.

If light were encoded linearly with 8 bits, that is, with 256 levels equally spaced in terms of energy, many levels would be wasted in the highlights, where the eye cannot tell differences apart, and there would be too few in the shadows, where visible steps would appear.

The solution: encoding with a curve

To make better use of the available bits, values are stored after applying a curve that "expands" the shadows and "compresses" the highlights. In simplified form:

encoded_value = linear_light ^ (1 / 2.2)
linear_light  = encoded_value ^ 2.2

The sRGB standard actually uses a piecewise function, with a small linear segment near black and a power of 2.4 for the rest, but overall it behaves much like a gamma of 2.2. With values normalised between 0 and 1, the encoded value 0.5 corresponds to about 21% of the light of white.

sRGB value (8-bit) Approximate linear light
0 0%
64 about 5%
128 about 22%
188 about 50%
255 100%

The gray that looks "halfway" between black and white therefore emits about a fifth of the light. And to get half the light of white you need a value around 188.

Where gamma causes problems

Gamma encoding is excellent for storing and transmitting images. It becomes a problem when physical calculations are performed on encoded values as if they were light. Typical cases are:

  • Gradients: interpolating between two colors on sRGB values produces central areas that are too dark and sometimes desaturated.
  • Layer blending and transparency: a white layer at 50% over black should produce a gray emitting half the light, but the direct calculation gives the value 128, which emits a fifth.
  • Resizing: averaging light and dark pixels in gamma space darkens fine detail; light text on a dark background and photos of stars are the most obvious examples.
  • Blurring: point lights blurred in gamma space become dull smudges instead of bright discs, as they would in a real lens.
  • 3D lighting: adding lights on gamma values produces scenes with unnatural contrast.

Working in linear space

The correct procedure for these calculations is always the same:

  1. Decode: convert sRGB values to linear light by applying the inverse function.
  2. Compute: perform averaging, blending, blurring or lighting on the linear values.
  3. Re-encode: reapply the sRGB curve before saving or displaying.

Working in linear needs more bits: with only 8 bits per channel the shadows would fill with steps. That is why programs operating in linear space internally use 16-bit integers or floating-point numbers.

In practice:

  • modern 3D software and game engines compute lighting in linear and convert only at output;
  • many photo-editing programs offer an option to blend colors with gamma 1.0 or to work in a linear space;
  • graphics cards support "sRGB" texture formats that are decoded automatically on read and encoded on write, at no cost.

A concrete example

In my work I have often seen thumbnails generated by automated scripts in which white characters on a dark background turned gray and nearly unreadable. The code was averaging pixels directly on the sRGB values. Converting to linear before resizing and back to sRGB afterwards solved the problem with no other changes. It is a fix of a few lines, but bear in mind that each library has its own default behaviour: some already work in linear, others do not.

Monitor gamma and file gamma

A clarification to avoid confusion: "gamma" is also used for the monitor's response. For decades cathode-ray tube screens naturally had a response similar to a power of about 2.2–2.5, and signal encoding adapted to that characteristic. Modern screens do not have this physical behaviour, but they emulate it to remain compatible. The result is that, if everything is set up correctly, the file's encoding and the screen's response cancel each other out and the image looks as intended.

In short

  • The eye is more sensitive to differences in the shadows: that is why values are encoded with a gamma curve.
  • In sRGB the value 128 corresponds to about a fifth of the light of white, not half.
  • Gradients, blending, resizing, blurring and lighting should be computed in linear light.
  • The method is always decode, compute, re-encode, with more than 8 bits during computation.
  • File gamma and screen gamma, if set up correctly, cancel each other out.
MM
Marco Marzaduri

Marco Marzaduri works in cybersecurity, digital forensics and web development. A computer science graduate, he combines rigorous technical analysis with attention to the people who use technology.

Read also