💬Comments welcome. To leave a note, select any text and click the note / highlight button that pops up — or open the panel with the tab at the top-right (‹). Notes are visible only inside our private review group.
PS1 implements brightness/contrast, gamma, and white balance as point operations. → Chapter 1.5, Problem sets (appendix).
The simplest image-editing operations are independent at each pixel: each output pixel depends only on the value of the input pixel at the very same spot, with no reference to its neighbors or to where it sits. These are point operations, and they cover a remarkable fraction of the edits photographers actually make: exposure, brightness, contrast, levels, freehand tone curves, gamma, and the lookup tables that bake them in. This chapter is about the tonal half of that family: what they can and cannot do, and the single question that decides whether each is done right or wrong, namely the numerical space it acts in. The color half, saturation and vibrance, black-and-white conversion, and video color grading, is the companion chapter Chapter 8.5, Color point operations.
Two framing cautions before the toolkit. First, these sliders are not independent: brightness, exposure, contrast, levels, and the tone curve all bend the same transfer line, so they overlap and partly undo one another; the general tone curve at the end of the chapter literally subsumes the rest, and any given look can be reached several ways. Second, they are not all equally meaningful: a few are objective, radiometric operations faithful to how light behaves (exposure is a multiply in linear light), while most are stylistic (brightness, contrast, the S-curve) with no single physically correct form, only a choice of space and taste. Knowing which kind you are using tells you whether "which space?" has a right answer (radiometric: yes) or merely a pleasant one (stylistic: pick the look).
Recall the distinction between changing values, remapping coordinates, and combining neighboring samples (Figure 8.1.13; Chapter 8.1, Image representation). These categories describe useful building blocks, not every possible image algorithm.
A point operation touches the range only. The output pixel depends solely on the input pixel's value at the same location, and one and the same rule is used everywhere:
There is a single input-to-output curve f, applied identically at every pixel. In words: read a pixel's value, find where that value lands on the curve, write the answer back: no neighbors consulted, nothing moved. Brightness, contrast, exposure, gamma, and lookup tables are all point operations, and we begin here because they are the simplest to reason about and the cheapest to run.
A domain operation maps output coordinates to input coordinates, $I_\mathrm{out}(x,y)=I_\mathrm{in}(x\prime,y\prime)$. For noninteger positions, interpolation combines neighboring samples and can change their values; see Chapter 6.5, Resampling and upsampling.
A neighborhood operation sits in between: the output pixel is computed from a whole window of input pixels around the same spot. Blur averages a pixel with its neighbors; sharpening, edge detection, and the bilateral filter all read a neighborhood. Those get their own chapter on Chapter 6.2, Convolution.
Figure 8.1.13(reproduced). The three families of image operation, sorted by what each output pixel may read. Point / range (left): the output depends only on the input pixel's value at the same location: one curve applied everywhere (exposure, contrast, lookup tables). Domain (center): pixels are relocated, their colors unchanged (resize, rotate, warp). Neighborhood (right): the output is computed from a window of nearby input pixels (blur, sharpen, bilateral). This chapter is the left column.
We develop tonal curves on a scalar value L. Applying a nonlinear curve independently to RGB channels can change hue and saturation. Scaling the channels using a mapped luminance behaves differently; Chapter 8.5, Color point operations treats this choice.
Since a point operation is just one curve f applied to every pixel, the curve is the operation, and photo editors hand it to you directly: the curves tool plots input value across the horizontal axis, output value up the vertical, and lets you bend the line by hand. The "do nothing" case (leave every value untouched) is the 45° diagonal. Everything in this chapter is some way of bending that diagonal: lift it to brighten, steepen it to add contrast, pin its ends to set black and white points, or just drag it freehand.
Controlling that mapping is not a digital invention. Photography has always been the craft of deciding how scene radiance becomes printed tone (film stock, exposure, development, paper grade, and the enlarger all shape it) and the digital tone curve is the most direct handle on the same problem. There is, though, one question we must answer before we bend anything, and it runs through the whole chapter: in which numerical space does the curve act?
Sidebar 8.3.1: the Zone System
The Zone System connects scene exposure placement to negative development and print tones. Exposure placements are one stop apart; the printed zones do not form a sequence of doubling luminances. Chapter 18.2, Global tone mapping distinguishes these two uses of the zone scale.
The same image can be stored as linear-light values (proportional to the physical radiance the sensor measured), as gamma/sRGB-encoded values (the perceptually spaced numbers a PNG actually holds), or as log values (handy when the range is enormous). We met these encodings in Chapter 8.1, Image representation; here they stop being a storage detail and start changing the result. Apply the very same arithmetic in linear light versus in sRGB and you get visibly different pixels, so every operation below carries a note about the space it belongs in.
💡 Big lesson (L4.6) — additive vs. multiplicative → choice of encoding
Exposure multiplies linear-light values; independent contributions of light add. Log coordinates turn exposure ratios into differences. An offset in encoded values is a distinct appearance control. Choose the domain from the operation and its model.
An exposure adjustment scales linear-light values. During capture, a longer shutter or larger aperture can collect more light; raising ISO changes the camera’s gain or rendering convention and does not itself add photons. A software exposure change scales an existing measurement without improving its capture SNR or recovering clipped data. For s stops, the multiplier is $2^s$:
$$ L_\text{out} = 2^{s}\, L_\text{in}. $$
If E denotes the output encoding, the displayed curve is $E(2^sE^{-1}(V))$. Its shape depends on E: a pure power law gives a scaled straight line before clipping, while sRGB also has a toe and offset. Values above display white can be retained in the working image even when the preview clips them (Figure 8.3.2).
Figure 8.3.1.A point-operation playground. Every pixel is mapped through the same function, independent of its neighbors: exposure, brightness, contrast, gamma, saturation, threshold, and invert all compose into the single input→output transfer curve shown at right. Adjust the controls (or load your own image) to watch the curve and the result change together; the rest of this section covers these one at a time.Figure 8.3.2.Exposure as a multiply in linear light. Left, the input. Center, the same photo at +1 stop (×2 in linear light), brighter throughout, with the brightest highlights clipped to white. Right, the transfer curve $L_\text{out} = 2^{s} L_\text{in}$ drawn in display (sRGB) space: it bows above the 45° diagonal, lifting the shadows strongly and flattening into the ceiling at 1. The 45° diagonal (no change) is shown for reference.
The "in linear light" qualifier is not pedantry. It marks the difference between a correct exposure change and a wrong one. The library makes the distinction concrete: imageops.exposure(img, stops) decodes to linear light, multiplies, then re-encodes; ask it for the naive version instead and it multiplies the gamma-encoded numbers directly, which is exactly what happens when you forget to linearize. The two part ways sharply, and the next figure is devoted to showing how.
💡 Big lesson (L8.3) — exposure scales linear light (a multiply) or shifts log values (an add)
Exposure scaling multiplies linear-light values by $k=2^s$ or adds $s$ in base-2 log units. For a pure gamma encoding $V=L^{1/\gamma}$ it multiplies V by $k^{1/\gamma}$. For an arbitrary transfer function, decode, scale, and encode. Output clipping is a separate operation.
Figure 8.3.3.Why the space matters for exposure. The same "+1 stop" done two ways. Top, the correct way, multiply in linear light (decode → ×2 → re-encode): tones brighten smoothly and in proportion. Bottom, the naive way, multiply the gamma/sRGB numbers directly: the curve is a straight ramp of slope 2 that clips everything above input ½ to white, blowing out the entire upper half of the tonal range. Same intended edit, very different pixels; exposure belongs in linear light.
Doing it in the encoded domain, correctly. Sometimes you cannot leave the encoded values (an 8-bit buffer, a shader handed display-referred pixels), so it pays to know exactly what a stop costs there. For a pure gamma curve $V = L^{1/\gamma}$ the decode-scale-encode round trip collapses to a single multiply,
so an $s$-stop change on gamma-encoded values is just multiplication by $2^{s/\gamma}$ (at $\gamma=2.2$ a stop is $\times 2^{1/2.2}\approx 1.37$, not $\times 2$). The instant the transfer function is not a pure power the shortcut breaks: real sRGB carries a linear toe and an offset power segment, so there is no closed-form multiply and you must decode, scale, and re-encode,
The examples below accept input values in $[0,1]$. Positive exposure adjustments can produce outputs above 1; the code preserves them instead of clipping them:
Listing 8.3.1 · exposure on gamma-encoded valuesembed
def expose_gamma(V, stops, gamma=2.2): # pure-gamma shortcut: one multiplyreturn (2.0 ** (stops / gamma)) * V # == ((2**stops) * V**gamma) ** (1/gamma)def expose_srgb(V, stops): # real sRGB: decode, scale, re-encode
lin = V / 12.92if V <= 0.04045else ((V + 0.055) / 1.055) ** 2.4
lin *= 2.0 ** stops
return12.92 * lin if lin <= 0.0031308else1.055 * lin ** (1 / 2.4) - 0.055
// Exposure on an sRGB-encoded image: decode -> scale -> re-encode, fused.
Func expose_srgb(Func V, Expr stops) {
Var x, y, c;
Expr v = clamp(V(x, y, c), 0.0f, 1.0f);
Expr lin = select(v <= 0.04045f, v / 12.92f,
pow((v + 0.055f) / 1.055f, 2.4f))
* pow(2.0f, stops);
Func out;
out(x, y, c) = select(lin <= 0.0031308f, 12.92f * lin,
1.055f * pow(lin, 1.0f / 2.4f) - 0.055f);
return out;
}
Use the power-law shortcut only when that is the actual transfer function. Use the piecewise sRGB formula for sRGB, and the specified inverse/forward functions for other encodings.
Here brightness means a simple additive offset in a stated working space. Commercial sliders with that name may use more complicated curves. Our teaching version is
$$ L_\text{out} = L_\text{in} + b, $$
This shifts the curve upward by b and maps zero to b. Use an offset to change the black point deliberately; use exposure scaling when the intended change is multiplicative. Neither operation can decide which tone should appear black without information about the scene or a rendering choice.
Figure 8.3.4.Brightness (additive) versus exposure (multiplicative). Left, the input. Center, two outputs matched to lift the midtones by the same amount: brightness $L+b$ raises the blacks off zero, fogging the shadows into flat gray, while exposure $2^{s} L$ keeps black at black and scales in proportion. Right, both transfer curves: brightness is the diagonal shifted straight up (a parallel line, intercept b); exposure passes through the origin and fans upward. Only the multiplicative curve preserves true black.
The visible difference is easiest to see at black. Multiplication leaves zero unchanged; a positive offset raises it to b. A dark scene can therefore acquire a gray floor under this simple brightness operation. That can be a deliberate look, but it is a poor substitute for exposure scaling when the aim is to simulate a change in collected light.
Where exposure moves all the tones the same way, contrast spreads them apart or pulls them together. The idea is to pick a pivot value, a mid-gray that stays put, and push every other value away from it (more contrast) or toward it (less). Values above the pivot get brighter, values below get darker, and the pivot itself never moves. As a curve, that is a straight line through the pivot whose slope is the contrast gain g:
$$ L_\text{out} = g\,(L_\text{in} - p) + p, $$
where p is the pivot. Read it as: measure how far a value sits from the pivot, scale that distance by the gain, add it back. A gain above 1 steepens the line and raises contrast; a gain below 1 flattens it and lowers contrast. The steeper the line, the sooner it slams into the floor at 0 and the ceiling at 1, so strong contrast clips both shadows and highlights (Figure 8.3.5).
💡 Big lesson (L8.4) — contrast is a pivot: choose what stays put
A contrast change rotates values about a chosen pivot: $L_\text{out} = p + g\,(L_\text{in}-p)$. The pivot $p$ is exactly what is left unchanged, and it also decides whether the picture gets brighter or darker overall: a high pivot pulls most of the image down (darkening), a low pivot pushes it up (brightening). Unlike exposure, contrast is not radiometrically meaningful, it is a stylistic stretch, so the encoding matters less (no single space is "correct"); what matters is a sensible pivot, and the sensible pivot does depend on the space: roughly 18% mid-gray in linear light, the numeric midpoint in gamma or log. Pick the pivot deliberately: it is the whole character of the operation.
Sidebar 8.3.2: 18% gray, and the space it lives in
A useful example pivot is 0.18 in normalized linear light, which encodes to about 0.461 in sRGB. A surface’s 18% reflectance is not automatically a sensor value of 0.18: illumination and exposure determine its reading. Meter calibration is not universally an 18% target. To compare contrast operations, map the same chosen pivot into each working space.
Figure 8.3.5.Contrast as a steepening about a pivot. Left, the input; center, increased contrast (gain > 1): shadows deepen, highlights brighten, midtones near the pivot barely move; right, the transfer curve, a straight line through the mid-gray pivot p whose slope is the gain. Steepening drives the line into the floor (0) and ceiling (1), clipping the darkest and lightest tones. The 45° diagonal (gain 1, no change) is shown for reference.
The pivot and the domain both affect contrast. A linear-domain adjustment preserves its chosen linear pivot; a log-domain scale changes luminance ratios by a power. An encoded-domain adjustment acts on displayed code differences. Choose the appearance you want and inspect the clipping at both ends (Figure 8.3.6). On the photographs in the figure, the three give recognizably different looks. Steepening the encoded values, what most editors do, pivots about a perceptual mid-gray and roughly tracks how the eye judges contrast. Steepening linear values weights the highlights more and, on these images, reads harsher, with shadows crushed faster. Steepening log values stretches ratios uniformly and reads gentlest across a wide range. None is wrong; they are three looks, and an editor can offer the choice.
Figure 8.3.6.The same contrast boost in three spaces. The identical gain about a mid-gray pivot, applied in sRGB/gamma (perceptual mid-gray, the usual editor behavior), in linear light (acting on physical radiance, harsher in the highlights, crushes the shadows), and in log/density (a uniform stretch in ratios, gentlest across a wide range). For the log version the black is clamped to a small ε to avoid log(0). Each panel shows the output and its transfer curve; the look differs because the pivot and the spacing of tones differ between spaces.
Sidebar 8.3.3: the S-curve
A straight contrast line clips abruptly the moment it touches 0 or 1. Photographers usually prefer a sigmoid (S-shaped) tone curve instead: steep through the midtones (where the eye is most sensitive) but easing gently into the shadows and highlights so the extremes roll off rather than clip. This is exactly the shape of film's response and of a camera's built-in "look," and it is the bridge both to the general tone curve below and to the tone-mapping operators of the next chapter.
The midpoint parameter $\gamma_m$ changes the interior without moving the endpoints. Handle coincident endpoints explicitly. Filling the whole output range is a choice: a foggy or low-contrast scene need not contain a true black or white.
Subtracting a calibrated additive light offset is meaningful in linear light. General haze also attenuates scene light and varies with depth, so an arbitrary black-point adjustment is not a complete haze inversion. A histogram minimum cannot identify the offset without further assumptions; see Chapter 8.2, What the numbers mean.
A display-space levels adjustment is an appearance operation. A sensor black-level correction is calibration in raw units. Keep them separate even if both lower dark values.
Use the histogram to see which values an endpoint choice will clip, then judge the image. Small percentile trims can avoid letting a single outlier set the range, but they still intentionally discard extremes (Figure 8.3.7).
Figure 8.3.7.Levels on a real photo. Left, the input, a flat, hazy coastal scene whose tones are bunched in the middle of the range, with no true black and a dull off-white that never reaches 1 (its luma histogram, below, is a narrow hump bounded by the black- and white-point markers). Center, after levels: the used range $[\text{black}, \text{white}]$ has been stretched out to fill $[0, 1]$, so the histogram spreads to both edges and the image gains contrast and snap. Right, the transfer curve: flat (clipped) below the black point, flat (clipped) at 1 above the white point, and a straight stretch between the two anchors (a mid-gray gamma would bow this middle segment).
An additive pedestal can explain a washed-out image. Add the same positive value to a dark patch and a bright patch: their difference stays the same, but their ratio moves closer to one. Subtracting a known pedestal in linear light reverses that part of the change. Choosing a black point by eye is less specific; it can improve a rendering without estimating the physical stray light. If the scene really has no black object, forcing its minimum to zero removes valid signal. Haze adds the further complication of depth-dependent attenuation as well as scattered light.
Exposure, contrast, and levels are all constrained shapes: a fan, a straight steepen, a clipped stretch. The most general point operation simply lets you draw the curve freehand: the tone curve tool gives you a handful of control points to drag wherever you please, and the editor threads a smooth interpolating curve through them (Figure 8.3.8). A monotone curve, one that never decreases, preserves the ordering of tones. A non-monotone curve deliberately reverses that ordering over part of its range, as in solarization effects. Catmull–Rom interpolation (Catmull & Rom 1974 (interpolating splines)) can overshoot even when the control points are ordered. Use a monotonicity-preserving interpolant when tone reversals must be excluded.
Figure 8.3.8.The general tone curve. A few draggable control points (dots) are interpolated by a smooth monotone spline (solid) into an arbitrary transfer curve; the 45° identity is shown dashed. Because the spline is kept monotone (never decreasing), a brighter input never maps to a darker output, so tones cannot invert. This single freehand curve subsumes the exposure bow, the contrast steepen, and the levels stretch of the earlier figures.
Sidebar 8.3.4: splines (the math)
A spline fits a smooth curve through a set of control points (knots) $(x_i, y_i)$ by stitching together low-degree polynomial pieces: for our purposes one cubic per interval $[x_i, x_{i+1}]$, with the pieces joined for continuity. The cleanest way to write each piece is the cubic Hermite form. On $[x_i, x_{i+1}]$, set $t = \frac{x - x_i}{x_{i+1} - x_i} \in [0, 1]$ and write
The piece passes through $y_i$ at $t=0$ and $y_{i+1}$ at $t=1$ no matter what; the only free choice is the tangent (slope) $m_i$ at each knot, and that choice is what separates the schemes:
Catmull–Rom (interpolating, $C^1$): take the tangent as the centered difference $m_i = \frac{y_{i+1} - y_{i-1}}{x_{i+1} - x_{i-1}}$, local and cheap, but it can overshoot between knots and so go non-monotone.
natural cubic ($C^2$): demand continuous second derivatives and minimize the total curvature $\int (p'')^2$; this couples all the knots into one tridiagonal linear system for the slopes, the smoothest fit, but it too can overshoot.
monotone cubic (Fritsch–Carlson, Fritsch & Carlson 1980 (monotone cubic)): start from Hermite tangents, then clamp them so every segment is monotone. Write the secant slope $\Delta_i = \frac{y_{i+1} - y_i}{x_{i+1} - x_i}$; where a secant is flat ($\Delta_i = 0$) set the neighboring tangents to $0$, and otherwise keep $(\alpha, \beta) = (m_i/\Delta_i,\ m_{i+1}/\Delta_i)$ nonnegative and inside the circle $\alpha^2 + \beta^2 \le 9$. This guarantees no inversion, exactly the property a tone curve needs (brighter in $\Rightarrow$ brighter out), which is why it is the standard choice here.
B-splines are the approximating cousin: the curve follows a control polygon and need not pass through the knots, smoother and easy to keep monotone, at the cost of exact interpolation.
Sidebar 8.3.5: spline families (drag to compare)
Figure 8.3.9.Spline families through the same control points. Piecewise-linear, natural cubic, and Catmull-Rom all interpolate (pass through every point); a B-spline only approximates, staying inside the convex hull of its control points. The Bézier option draws the same Catmull-Rom curve, written as cubic Bézier segments; the natural cubic and the B-spline are both $C^2$, the others only $C^1$ or $C^0$. The trade-off (interpolation vs smoothness, global vs local control) is the same one faced by the tone curve above and by the resampling kernels of a later chapter. Drag a point to feel how local each scheme is.
Sidebar 8.3.6: how analog photography shaped the same mapping
Long before a draggable digital curve, photographers controlled the scene-tone → printed-tone mapping entirely by physical and chemical means, and the modern tone curve is the digital descendant of that whole toolkit.
Exposure decides where a scene luminance is placed on the film's response: the Zone System's "place this shadow on Zone III and let the highlights fall where they may."
The film's characteristic (H&D) curve and its gamma / contrast are set partly by the emulsion and partly by development: time, temperature, agitation, choice of developer. Photographers deliberately push or pull (over- or under-develop) and use "N / N+1 / N−1" development to expand or compress the scene's tonal range onto the film.
Pre-exposure / flashing (a uniform sub-threshold exposure) lifts deep shadows above the film's toe, raising the floor without touching the highlights (the analog of a black-point lift).
At print time the paper grade (contrast grade 0–5) sets the print's slope, and local dodging and burning (holding back or adding light region by region) place each tone, the ancestor of the masked local point operations below.
The free-form tone curve, plus those masked local edits, is the digital reincarnation of this entire workflow. For the canonical treatment, see Ansel Adams' trilogy, The Camera, The Negative, and The Print, with the Zone System developed in The Negative.
A freehand tone curve subsumes everything earlier in the chapter (you could draw the exposure scale, the contrast steepen, or the levels stretch by hand) which is why it is the photographer's most general tonal tool. The tone-mapping chapter pushes the idea to its physical conclusion with principled, automatically chosen curves (the Reinhard operator $L/(1+L)$ and relatives, Reinhard et al. 2002) for compressing high-dynamic-range scenes; here we simply note that the general curve is the container they all fit into.
Once a point operation is any fixed curve f, there is a cheap trick that applies to all of them at once. If pixel values come from a small finite set, the 256 levels of an 8-bit channel, say, there is no need to evaluate f at every pixel. Evaluate it once per possible value, store the 256 answers in a small table, and then for each pixel just look up its result. This precomputed table is a look-up table (LUT), and it turns an arbitrarily complicated tone curve into a single array index per pixel, the same cost whether f is a gamma curve, an S-curve, or a hand-drawn spline.
Independent per-channel one-dimensional LUTs represent separate R, G, and B curves, including diagonal white-balance gains in the appropriate linear space. They cannot represent a general color matrix or saturation adjustment, which mixes channels. A three-dimensional LUT maps an RGB triple to another triple and interpolates between a finite grid of samples. Its domain, interpolation, out-of-range behavior, and input/output encodings are part of the specification. A LUT stores an approximation to a mapping, not an invertibility guarantee.
For a 1-D table with 256 entries, one channel value chooses a position on one axis. A 3-D table with 33 entries per axis has 35,937 grid points, each holding an output RGB triple. Interpolation among nearby grid points approximates a function of all three input channels. That additional dependence lets a 3-D table express a saturation change that a separate curve on each channel cannot in general represent. Diagonal white-balance gains, however, already fit in three independent 1-D curves.
Both tables encode fixed mappings. If an adjustment depends on a neighborhood, a face mask, or statistics recomputed for each image, those dependencies must be supplied outside the table or used to choose its contents. Precomputing a color mapping does not remove its input-domain requirements.
This blends the original and adjusted values in the specified working space. For exposure, blending the two outputs gives a factor $1-m+m2^s$; scaling the stops by the mask instead gives $2^{ms}$. They agree at mask values zero and one but differ in between. A mask inferred from neighboring pixels makes the complete algorithm nonlocal, even though applying the finished mask is pointwise.
The connection to the darkroom is exact (Figure 8.3.10). Wherever the mask is bright, the adjustment acts; where it is dark, the pixel is untouched. Run a local exposure through a mask and you have reinvented dodging (locally brightening, holding light back in the printing easel) and burning (locally darkening, giving extra exposure): the only difference is that the mask is now a soft digital matte instead of a piece of cardboard waved over the paper. A radial gradient lifts a face out of the shadows; a linear gradient is the graduated-ND filter that holds back a bright sky; a painted brush burns down one distracting highlight. Everything earlier in the chapter, exposure, contrast, the tone curve, becomes a local tool the moment it is gated by a mask.
Figure 8.3.10.Local (masked) point operations: digital dodging and burning. The same point operation (here exposure) is applied only where a mask allows. Radial gradient: dodge (brighten) the center, lifting the subject. Linear gradient: burn the top, the digital graduated-ND that tames a bright sky. Paintbrush: burn a chosen region. The lower row shows each mask (white = full effect). Interactive: pick the mask shape and the adjustment (exposure for dodge/burn, or contrast), set the amount, and drag the mask's handle, or in brush mode paint directly on the image.
In CompPhotoRoom
The Exposure, Contrast, Whites and Blacks sliders and the point-curve editor of Chapter 8.13, A raw developer in the browser are this chapter's operations, run live: consecutive per-channel curves compose into one 4096-entry lookup table per channel, the LUT move above doing real work, and the ? on each slider shows the exact code beside this chapter's pseudocode.
Selections can come from a value or color threshold, a painted brush, a gradient, an edge-aware tool, or semantic segmentation. These choices change what the complete operation depends on. An edge-aware selection uses image structure to keep an adjustment within an object; its use of color similarity connects it to the affinities used by the bilateral filter. The finished mask can still be applied with the pointwise blend above.
Recap: big lessons of this chapter
💡 Big lesson (L4.6) — additive vs. multiplicative → choice of encoding
Exposure multiplies linear-light values; independent contributions of light add. Log coordinates turn exposure ratios into differences. An offset in encoded values is a distinct appearance control. Choose the domain from the operation and its model.
💡 Big lesson (L8.3) — exposure scales linear light (a multiply) or shifts log values (an add)
Exposure scaling multiplies linear-light values by $k=2^s$ or adds $s$ in base-2 log units. For a pure gamma encoding $V=L^{1/\gamma}$ it multiplies V by $k^{1/\gamma}$. For an arbitrary transfer function, decode, scale, and encode. Output clipping is a separate operation.
💡 Big lesson (L8.4) — contrast is a pivot: choose what stays put
A contrast change rotates values about a chosen pivot: $L_\text{out} = p + g\,(L_\text{in}-p)$. The pivot $p$ is exactly what is left unchanged, and it also decides whether the picture gets brighter or darker overall: a high pivot pulls most of the image down (darkening), a low pivot pushes it up (brightening). Unlike exposure, contrast is not radiometrically meaningful, it is a stylistic stretch, so the encoding matters less (no single space is "correct"); what matters is a sensible pivot, and the sensible pivot does depend on the space: roughly 18% mid-gray in linear light, the numeric midpoint in gamma or log. Pick the pivot deliberately: it is the whole character of the operation.
Figure 8.3.5(reproduced). Contrast as a steepening about a pivot. Left, the input; center, increased contrast (gain > 1): shadows deepen, highlights brighten, midtones near the pivot barely move; right, the transfer curve, a straight line through the mid-gray pivot p whose slope is the gain. Steepening drives the line into the floor (0) and ceiling (1), clipping the darkest and lightest tones. The 45° diagonal (gain 1, no change) is shown for reference.