Lesson 01

Gamma encoding

Screens don't treat brightness the way math does. Halfway between black and white isn't the number you'd expect — and if you blend colors without accounting for that, the middle goes muddy.

Try this first: the strip below fades one color into another. Switch the blend space from linear to encoded and watch the middle of the fade turn dull and dark. Then turn on the gray proof. Come back and read once your eyes have seen it.

Why "half" isn't half

Set a pixel to 0.5 and you might expect half the light of 1.0. You don't get it. A display turns stored values into emitted light along a curve close to light = value2.2, so a stored 0.5 comes out around 0.22 of full brightness — noticeably dimmer than halfway. That mapping isn't a bug; it's deliberate, and it's called gamma.

encode decode stored value → light emitted → 0 1 ½ → 0.22
Two directions of the same curve. Decode (the display's job, the EOTF) bends a stored value down to light. Encode (the OETF) is its inverse — what you apply to a light value to store it. sRGB uses a power near 2.2 plus a short linear segment close to black.

Why not just store light directly?

Because your eyes aren't linear either. Human vision is far more sensitive to differences among dark tones than bright ones. If you spread a limited set of integer codes (8 bits, say) evenly across light, you burn most of them on highlights you can't tell apart and leave visible banding in the shadows. Spreading them evenly across encoded values matches perception — more codes where the eye actually needs them.

Even steps in linear light
Even steps in stored (encoded) value

The top strip steps evenly through linear light: the dark end lurches and the bright end barely changes — a wasteful use of steps. The bottom strip steps evenly through stored values, and those read as even jumps in brightness. That match is the entire reason encoding exists.

Blend in light, not in code

Here's where it bites. To blend two colors — a fade, a 50/50 mix, the average of four texels — you have to average the light, not the stored values. Averaging encoded values averages two points on a curve, which lands below the true midpoint and reads as muddy and dark. The fix is three steps: decode to linear, blend, re-encode.

That's exactly the toggle at the top of this page. In linear the fade blends correctly; in encoded the middle sags. The gray proof makes it undeniable: a field of one-pixel black/white lines physically averages to linear 50% gray, and only the patch mixed in linear light matches it — the naive 0.5 patch sits too bright beside it.