Frame — Advanced Panels
The Frame tab's advanced panels are where precision tone and color work happens after the Basic panel has set overall exposure and balance. They contain three sections: the Tone Curve editor (master + per-channel point curves and parametric region sliders), Color Grading wheels (3-way plus global), and the Color Mixer (8 hue bands × hue/sat/lum) with Point Color targeting. This page describes every control, the parameter it writes, and the engine math behind it.

Tone Curve editor
The Tone Curve section (field group rooted at tc_*) provides a point-curve editor and four parametric region sliders. Together they compose into a single per-channel tone LUT that is applied after the Basic tone controls and OKLab saturation, but before Color Grading.
The channel selector at the top of the editor is a row of four colored dots: Master (light gray, tc_curve), Red (tc_red), Green (tc_green), and Blue (tc_blue). Clicking a dot switches the visible and editable curve. In Master mode the background histogram shows all three channels; switching to R, G, or B shows only that channel's histogram. Points on the curve are draggable; the editor displays an in/out readout as you drag. All four curves start as the identity diagonal (0→0, 1→1) and reset to that identity when the Tone Curve Reset button is clicked.
Below the curve editor, four parametric region sliders let you brighten or darken individual tonal zones without placing control points manually:
- Highlights — brightest quarter of the tonal scale, centered near 0.875 in display space. Field:
tc_highlights. - Lights — upper-mid quarter, centered near 0.625. Field:
tc_lights. - Darks — lower-mid quarter, centered near 0.375. Field:
tc_darks. - Shadows — darkest quarter, centered near 0.125. Field:
tc_shadows.
All four region sliders run −100 to +100 (0 = no effect). They act before the point curves in the signal chain. The full pipeline order for the Tone Curve LUT is: parametric regions → master point curve → per-channel point curve. The resulting composed LUT is then applied during finishing after OKLab saturation. See The tone curve for how the Faithful inversion curve fits into the broader pipeline.
Under the hood
Parametric region math (finish.rs): each region slider value (pre-scaled from −100..100 to −1..1) is applied as a parabolic bump to the input tonal value v:
v += region_value · REGION_GAIN · max(0, 1 − ((v − center) / REGION_WIDTH)²)
Constants: REGION_GAIN = 0.25, REGION_WIDTH = 0.22, and the four centers are REGION_CENTERS = [0.125, 0.375, 0.625, 0.875] for [shadows, darks, lights, highlights] respectively. The parabolic bump has finite support (zero outside ±REGION_WIDTH of the center) so each slider affects only its own zone and leaves adjacent zones nearly untouched.
Important distinction from Basic: the Basic panel's Highlights and Shadows controls use smoothstep shelf weights (v += hl · 0.18 · smoothstep(0.5, 1.0, v)) that are strongest at the extremes and extend across an entire half of the tonal range. The parametric region sliders use narrow parabolic bumps centered at four specific tonal positions — they are zone-selective in a fundamentally different way. The Basic controls and the parametric regions operate independently and compose additively.
Composed LUT: tone_luts() builds three LUTs of size 256. For each input index x: apply the four parametric bumps → look up the master curve → look up the per-channel curve. The three resulting LUTs (R, G, B) are sampled per pixel in the finishing pass, after the OKLab saturation step.
Color Grading
The Color Grading section provides HSL color wheels for tinting and brightening each tonal zone of the image. It is applied after the Tone Curve LUT and before the Color Mixer.
The mode selector at the top offers five views: 3-way (shows all three wheels simultaneously — Shadows at bottom-left, Midtones large in the center, Highlights at bottom-right), Shadows, Midtones, Highlights, and Global (each as a single large wheel). Switching modes does not change any parameters — it only changes which wheels are visible at once.
Each wheel encodes three values: hue (the angle around the color circle, 0–360°), saturation (the distance from center, 0–100), and luminance (a brightness lift, −100 to +100). The parameters per zone are:
- Shadows:
cg_sh_hue,cg_sh_sat,cg_sh_lum - Midtones:
cg_mid_hue,cg_mid_sat,cg_mid_lum - Highlights:
cg_hi_hue,cg_hi_sat,cg_hi_lum - Global:
cg_glob_hue,cg_glob_sat,cg_glob_lum
Dragging the puck away from center on any wheel adds a chroma offset toward that hue; a luminance lift above or below zero raises or lowers that zone's brightness. The Global wheel affects all tones simultaneously with no luma masking.
Two sliders below the wheels control how the zones blend into each other:
- Blending —
cg_blending, range 0–100, default 50. Higher values make the zone masks overlap more, so color pushes cross-fade rather than cut sharply between shadows and highlights. Lower values produce harder, more distinct zone separation. - Balance —
cg_balance, range −100 to +100, default 0. Shifts the crossover point between shadows and highlights: negative Balance moves the midpoint toward the shadows (expanding the highlights zone), positive moves it toward highlights (expanding the shadows zone).
Under the hood
Chroma offset: each wheel's hue + saturation values are converted to a zero-luma RGB push via wheel_offset(hue, sat): the pure-hue RGB at that angle is computed, its luma is subtracted, and the result is scaled by CG_COLOR_GAIN = 0.5. This gives a chroma-only push that does not shift brightness. The per-zone luminance slider is scaled separately by CG_LUM_GAIN = 0.3.
Zone masks: a pixel's luma (l = 0.2126·R + 0.7152·G + 0.0722·B) is measured and used to compute three smoothstep weights: w_sh = 1 − smoothstep(sh_edge − softness, sh_edge + softness, l), w_hi = smoothstep(hi_edge − softness, hi_edge + softness, l), and w_mid = clamp(1 − w_sh − w_hi, 0, 1). The softness is softness = 0.1 + 0.3 · blending (at default blending = 0.5: softness = 0.25). Zone edges: sh_edge = 0.33 + balance · 0.25 and hi_edge = 0.66 + balance · 0.25, both pre-scaled from the −100..100 slider range to −1..1.
Application: the output for each channel is rgb[c] + w_sh·(sh_off[c] + sh_lum) + w_mid·(mid_off[c] + mid_lum) + w_hi·(hi_off[c] + hi_lum) + (glob_off[c] + glob_lum). The Global term is always fully weighted (no mask). See Color & white balance for the perceptual rationale.
Color Mixer
The Color Mixer section has two tabs: Mixer (the 8-band HSL mixer) and Point Color (sample-driven targeting). The Reset button resets only the active tab.
The Mixer tab divides the hue circle into eight named bands: Red, Orange, Yellow, Green, Aqua, Blue, Purple, and Magenta. These correspond to the CM_BANDS constant in the API: ["red","orange","yellow","green","aqua","blue","purple","magenta"]. Band parameters are named cm_{band}_{hue|sat|lum}. Four view modes let you adjust one attribute at a time (Hue, Saturation, Luminance) or all three together (All):
- Hue — shifts the hue of pixels in that band. A positive value rotates hue clockwise around the color wheel; negative rotates counter-clockwise. Maximum shift is ±30° (the constant
CM_HUE_SHIFT_MAX = 30.0). - Saturation — multiplies the saturation of pixels in that band. Positive values enrich, negative desaturate. Near-neutral pixels (saturation below about 0.05–0.20) receive a gated-down effect to prevent noise amplification.
- Luminance — shifts the lightness of pixels in that band. At the slider extremes the maximum lift or drop is scaled by
CM_LUM_GAIN = 0.25, giving a moderate range.
All Mixer sliders run −100 to +100, with 0 as the identity. A band's effect fades smoothly to zero as the pixel's hue diverges from the band center, using a cosine falloff over a half-width of CM_FALLOFF_DEG = 50°. Adjacent bands overlap so there are no hard hue boundaries.
The Point Color tab provides sample-driven targeting: click the Pick color dropper to activate a crosshair in the viewport, then click any pixel to sample it. Its hue, saturation, and lightness are recorded as a swatch. Up to eight swatches can coexist, stored as the pc_samples array. Selecting a swatch shows five sliders:
- Hue Shift (
hue_shift) — rotates the hue of matching pixels, up to ±30° at the extremes. - Sat. Shift (
sat_shift) — multiplies saturation on the same −100..100 scale as the Mixer's Saturation, but without the Mixer's near-neutral saturation gate, so it acts on the targeted pixels directly. - Lum. Shift (
lum_shift) — lifts or lowers lightness, scaled byCM_LUM_GAIN = 0.25. - Variance (
variance, −100 to +100, default 0) — expands or contracts the saturation and luminance tolerance used to match pixels to the sample. Positive values accept a wider spread of saturation and lightness around the sampled value; negative values narrow the acceptance window to pixels that are very close matches. - Range (
range, 0–100, default 50) — controls the hue-angle half-width of the selection. At the default (50) the half-width is approximately 32.5°; at the maximum (100) it reaches 60°; at the minimum (0) it narrows to 5°.
Multiple swatches are order-independent: each sample computes its weight from the unmodified input pixel, and all shifts accumulate before being applied once. Removing a swatch (click its × badge) immediately removes its effect. The Color Mixer and Point Color operate in sequence: Mixer adjusts all pixels band-by-band first, then Point Color operates on the Mixer's output. Both run after Color Grading. See Color & white balance for the broader color pipeline context.
Under the hood
Band weight (finish.rs): for each of the eight bands, the weight for a pixel at hue h is w = 0.5 · (1 + cos(π · |wrap180(h − center)| / CM_FALLOFF_DEG)) when the angular distance is less than 50°, and 0 otherwise. Band centers (degrees): Red = 0, Orange = 30, Yellow = 60, Green = 120, Aqua = 180, Blue = 240, Purple = 280, Magenta = 320.
Mixer application: hue shift and the saturation gate are both multiplied by a saturation gate gate = smoothstep(CM_SAT_GATE_LO, CM_SAT_GATE_HI, s) with CM_SAT_GATE_LO = 0.05, CM_SAT_GATE_HI = 0.20, protecting near-neutrals from hue-shift noise. Luminance shift is not gated. Saturation is multiplicative: s_out = s · (1 + weighted_sat_factor).
Point Color weight: a sample at (hue_t, sat_t, lum_t) matches a pixel at (h, s, l) via three independent weights: a hue weight using the same cosine falloff with a range-derived half-width hw = PC_RANGE_MIN_DEG + (range/100) · (PC_RANGE_MAX_DEG − PC_RANGE_MIN_DEG) where PC_RANGE_MIN_DEG = 5.0° and PC_RANGE_MAX_DEG = 60.0°; a saturation weight ws = clamp(1 − |s − sat_t| / pc_tol(0.25, variance), 0, 1); and a luminance weight wl = clamp(1 − |l − lum_t| / pc_tol(0.25, variance), 0, 1). The tolerance function is pc_tol(base, variance) = max(0.02, base · (1 + (variance/100) · 2.0)). The combined weight is w = wh · ws · wl.