The inversion pipeline
Every pixel runs the same per-channel chain: eight stages take a raw scan value and turn it into a finished positive. Understanding each stage tells you what every slider actually moves.
1. Clamp & read the linear scan
The pipeline starts with the raw linear value from the scanner — a number proportional to how much light passed through the film. Values are clamped to a valid range before anything else happens; out-of-range data would corrupt every downstream calculation.
2. Remove the base
The clear edge of the film is not neutral — it carries the orange mask. OpenEnlarge samples that orange base and divides it out of each pixel, channel by channel. This single step strips the mask and leaves the dye density of the image alone. See film-base calibration.
3. To density (log space)
Dividing the base by the scan and taking the base-10 logarithm converts the linear transmission ratio into density: D = log₁₀(base / scan). Density is clamped to zero so clear film reads as D = 0 (no dye). Working in log space means that equal density steps correspond to equal photographic stops — the natural unit of film. See density & log space.
4. Scale to the tone curve
The raw density D is scaled by a fixed factor before the tone curve. In the Faithful path — the active path — a frame-independent constant (FAITHFUL_SCALE = 1 / 0.700 ≈ 1.429) is applied directly to D, giving every frame in a roll the same tonal ruler regardless of its individual exposure. The concept of a per-frame white point d_max still exists (it anchors the exposure slider's neutral starting point), but the Faithful tone curve does not normalize to it: t = D / d_max is the Filmic-path form only. After scaling, D = 0 is still the film base — the scene's deepest shadow — and fully exposed density maps to the curve's white.
5. Balance
White balance shifts the neutral point so a gray card in the scene renders as gray. OpenEnlarge offers two modes: a gentle post-curve von-Kries gain (divide each channel by its white-patch value), or a subtractive "color head" that applies per-channel density gain before the tone curve — matching how an optical enlarger head would control color. See color & white balance.
6. Tone curve
The tone curve maps normalized density t to a display brightness. The Faithful curve applies a fixed-scale density-to-brightness transform with gamma ≈ 1.590 and an asymptotic shoulder above knee ≈ 0.892, on a frame-independent density scale so that tone is consistent across a whole roll. See the tone curve.
7. Look layer
A subtle normalized tanh S-curve adds mid-tone contrast and a gentle highlight roll-off without clipping. The look layer is anchored 0 → 0 and 1 → 1, so it never shifts the black point or white point — it only reshapes the middle. Strength is fixed at 2.0 for the Faithful SDR output.
8. Display encode
The result is a linear-light value in the 0–1 range ready for the screen. A final clamp discards any out-of-gamut overshoot before the value is handed to the display. No additional gamma encoding is applied in the pipeline — the OS color stack handles that.
Under the hood
The density formula is D = log₁₀(base / scan), clamped ≥ 0; one density unit ≈ 3.32 photographic stops. The white-point normalize is t = D / d_max, where d_max is the densest channel value in the frame. The Faithful tone curve uses a fixed density scale FAITHFUL_SCALE = 1 / 0.700 ≈ 1.429: this scale is applied to the raw density before the curve, making it frame-independent — identical tone across every frame of a roll. Gamma ≈ 1.590 with an asymptotic shoulder at knee ≈ 0.892. The look S-curve is a normalized tanh at strength 2.0, anchored 0 → 0 and 1 → 1. Exposure is a log-space multiply that pivots on black (≈ 1 stop/EV per unit). The CPU path and GPU (Metal) path run the identical math, so the live preview in the app matches the exported file exactly.