Selective Luminance Correction

Sister tool to Selective Color Correction. Instead of building a mask from a hue range, you build it from a luminance band — shadows, dark mids, midtones, bright mids, highlights, or any custom range — and push CMY / RGB / Luminance / Chroma / Saturation / Contrast adjustments through it. The contrast slider is the one Selective Color doesn't have: an anchored sigmoid S-curve pinned to the band endpoints, so contrast changes only reshape the midrange of your selected band without ever pushing a pixel outside it.

Why this exists. Classic selective-colour tools let you edit by hue; curves and levels let you edit globally by luminance. What very few tools let you do is combine both — “only shift the bright blues toward cyan”, “only lift the shadows saturation”, “only add contrast to the midtone dust lanes without clipping the galaxy core”. This dialog does exactly that, with the same freehand lasso, imported-mask, and live-preview niceties as Selective Color.

1. Layout

Same two-pane split as Selective Color: a scrollable left column with the Luminance Mask group, three adjustment groups (CMY / RGB / LSC), live-preview toggle and action buttons; a right column with zoom toolbar, help banner, large preview, picked-luminance readout, and region-mask readout. Window size, splitter position, small-preview toggle, live-preview toggle, mask-overlay toggle, and current zoom are all persisted to QSettings under the sellum/ namespace.

2. Building a luminance mask

2.1 Preset bands

The Preset dropdown offers five standard luminance bands plus Custom:

PresetRange (0–1)8-bit range
Shadows0.00 – 0.250–64
Dark Mids0.20 – 0.4551–115
Midtones0.35 – 0.6589–166
Bright Mids0.55 – 0.80140–204
Highlights0.75 – 1.00191–255

Any manual change to the Luminance Wheel or either slider switches the preset to Custom automatically.

2.2 The Luminance Wheel

A circular grayscale ring with two draggable white handles for the lo and hi ends of the band. The convention is unusual but intuitive once you see it: black sits at the bottom (270°), white sits at the top (90°), and the ring transitions smoothly between the two around the circle. The yellow arc between the handles shows your current luminance range. A small orange marker appears when you click to sample a pixel (see § 3).

2.3 Lum. low / Lum. high sliders

0–255 integer sliders paired with 0.0–1.0 double spinboxes. Edit either side of each pair and the other updates in step. Both also mirror the Luminance Wheel.

2.4 Smoothness

Range 0.0–0.5, default 0.05. Feather width (in luminance units) applied at both ends of the band. At 0.0 the mask is a hard rectangle; at 0.05 you get soft edges roughly one 8-bit code value wide; at 0.5 the band bleeds halfway across the luminance axis in each direction. See § 12.1 for the exact formula.

2.5 Invert range

Replaces the mask with its complement before any downstream step. Useful for “adjust everything except the midtones” operations.

2.6 Soft (proportional)

Switches the band from a hard rectangle with linear feathers to a smooth-shoulder bump: the mask peaks at 1.0 inside the band and tapers off through smoothstep curves outside. For a narrow band this becomes a bell curve; for a wide band it keeps a soft-shouldered plateau. The result removes iso-luminance contours (“blobbiness”) that pure threshold masks produce. See § 12.2.

Special case: a band that reaches a pure endpoint (lo = 0.0 or hi = 1.0) keeps that endpoint fully in at 1.0 regardless of smoothness, so a Shadows preset (0.00–0.25) still selects true black at full strength instead of tapering off into it.

2.7 Intensity

Range 0.0–2.0, default 1.0. Scalar multiplier on the mask before it's handed to the adjustment pipeline. Same semantics as in Selective Color — think of it as the strength of the adjustment.

2.8 Edge blur

Gaussian blur applied to the mask after all other mask steps, 0–150 px radius. Useful for softening sharp luminance boundaries into gradient transitions. Default 5 px (not 0 like Selective Color): luminance masks tend to have harder edges than hue masks, and a few pixels of blur almost always improves the result.

2.9 Show mask overlay

Red semi-transparent overlay in the preview showing exactly which pixels the current mask is selecting. Alpha scales with the mask value, so a soft-mode bell curve looks visibly feathered. Pair with the overlay to find the right luminance range for the structure you're trying to isolate.

2.10 Imported mask

Two controls:

2.11 Lasso region mask

Hold Alt and drag in the preview to draw a freehand closed region. On release, the polygon is filled, converted to a binary mask at full resolution, and AND'd with the luminance mask. This is particularly powerful here: you can select, say, “the midtone dust lanes only on the north side of the galaxy” without having to craft a precise mask externally. The dashed-orange outline marks the active region in the preview until you click the × Clear Region Mask button.

3. Preview interaction

Mouse:
• Click — sample the luminance at that pixel. The wheel shows an orange marker; the readout shows the value as both 0–1 float and 0–255 integer.
• Shift + Click — sample and set the luminance range to ±0.05 around that value (an 8-bit spread of about ±13 code values).
• Ctrl + Drag — pan the preview.
• Alt + Drag — draw a lasso region mask.
• Mouse wheel — zoom cursor-anchored (plain or Ctrl, with Ctrl using a bigger step).
• Zoom buttons — zoom out / in / 1:1 / fit.

3.1 Small-sized Preview (fast)

The toggle at the top-left controls whether the preview path operates on a downsampled copy (longest side ≤ 1200 px) or full resolution. On by default; always off for the Apply / Apply-as-New / Export Mask paths.

3.2 Preview changed image

Toggle between the raw image and the adjustment-applied image. Useful for before/after without undoing anything. The mask overlay works independently.

3.3 Colour boost mode

Same as Selective Color — pick Chroma (L-preserving) or Saturation (HSV S) for the colourfulness slider in the LSC group. See the Selective Color docs for the trade-off; the math is identical here.

4. The adjustment sliders

Identical to Selective Color in both range and semantics:

5. The non-monotonic contrast warning

When the Contrast slider drops below −0.667 (precisely −2/3), an orange warning appears underneath the LSC group:

⚠ Contrast below −0.67: curve is no longer monotonically increasing

The slider itself turns red (groove, fill, handle) while the condition holds. This isn't a bug — the sigmoid compression math naturally stops being monotonic past this point, so very dark pixels can end up slightly brighter than slightly-less-dark pixels, producing wavy artefacts. The slider still works; the warning just says “you're off the beaten path, verify the preview looks right”. See § 12.5.

6. Reset behaviour

6.1 After Apply or Apply as New Document

Only the adjustment sliders are reset to zero. The luminance range, preset, smoothness, invert, blur, and intensity are kept exactly as you had them. This is a deliberate difference from Selective Color — Selective Color resets everything after Apply because hue selections tend to be one-and-done, but luminance bands are often reused for multiple passes (adjust shadows for colour, then for contrast, then for luminance tweak, same band each time).

6.2 ↺ Reset button

Full reset: luminance range back to the Shadows preset (0.00–0.25), smoothness 0.05, invert off, soft off, blur 0, mask overlay off, all adjustment sliders at 0.0, intensity 1.0, Chroma mode selected. Loaded image, imported mask, and lasso region are preserved.

6.3 × Clear Region Mask

Removes any lasso region constraint. Enabled only when a region is active.

7. Applying the result

8. Fun applications

A few things this tool can do that are awkward or impossible in general-purpose editors:

Chain this with Selective Color for “only touch the bright blues”: export a luminance mask from here, then import it into Selective Color via Pick-mask-from-view and pick the Blue preset. The two masks AND together.

9. Technical details

Everything in float32 in [0, 1]. Luminance is Rec. BT.601. Core pipeline runs on the GUI thread with debounced timers (200 ms for mask and adjustment paths), identical to Selective Color. The band-locked contrast formula and the luminance wheel are the two pieces that set this tool apart.

12.1 Luminance basis

Rec. BT.601 weights, same as Selective Color:

L \;=\; 0.2989\,R + 0.5870\,G + 0.1140\,B

12.2 Soft vs hard band

Let L be the pixel luminance, L_lo and L_hi the band endpoints, and s the smoothness. In soft mode, the mask is a bump function with smoothstep shoulders:

m_{\mathrm{soft}}(L) \;=\; t(L;\; L_{\mathrm{lo}} - s,\; L_{\mathrm{lo}} + s) \cdot (1 - t(L;\; L_{\mathrm{hi}} - s,\; L_{\mathrm{hi}} + s))

where t is the smoothstep from Selective Color § 13.2. The clamp around the pure endpoints (0 and 1) is applied by replacing the relevant factor with 1 when the band reaches that endpoint, so Shadows and Highlights presets include true black and true white without a false taper.

In hard mode, the band is 1 inside and linearly feathered at the ends:

m_{\mathrm{hard}}(L) \;=\; 1,\quad L_{\mathrm{lo}} \leq L \leq L_{\mathrm{hi}}
m(L) \;=\; \frac{L - (L_{\mathrm{lo}} - s)}{s},\quad L_{\mathrm{lo}} - s \leq L < L_{\mathrm{lo}}
m(L) \;=\; 1 - \frac{L - L_{\mathrm{hi}}}{s},\quad L_{\mathrm{hi}} < L \leq L_{\mathrm{hi}} + s

The Invert checkbox replaces the result with 1 − m.

12.3 Luminance wheel geometry

The wheel maps angle (in degrees, measured counterclockwise from the positive x-axis) to luminance in [0, 1]:

L(\theta) \;=\; \frac{(270^{\circ} - \theta) \;\mathrm{mod}\; 360^{\circ}}{360^{\circ}}
\theta(L) \;=\; (270^{\circ} - 360^{\circ} L) \;\mathrm{mod}\; 360^{\circ}

Which places L = 0 (black) at the 270° position (bottom), L = 1 (white) at 90° (top), and midtones at the sides. The arc from lo to hi always moves in the direction of decreasing angle (i.e. toward higher luminance).

12.4 Band-locked contrast (anchored sigmoid)

This is the maths that distinguishes Selective Luminance from Selective Color. Let κ be the Contrast slider value, x a pixel's channel value, and [L_lo, L_hi] the current luminance band. The steepness is:

k \;=\; 6 \cdot |\kappa|

The sigmoid is normalised against its own endpoints so the transformation maps L_lo to L_lo and L_hi to L_hi exactly:

\sigma_{-} \;=\; \frac{1}{1 + e^{k}},\quad \sigma_{+} \;=\; \frac{1}{1 + e^{-k}}

Then the pixel is remapped by first centring on the band:

t \;=\; \frac{x - L_{\mathrm{lo}}}{L_{\mathrm{hi}} - L_{\mathrm{lo}}},\quad u \;=\; 2t - 1

Running through the normalised sigmoid:

e(t) \;=\; \frac{\sigma(k u) - \sigma_{-}}{\sigma_{+} - \sigma_{-}}

and then expand or compress depending on the slider sign:

\mathrm{anchored}(t) \;=\; e(t),\quad \kappa > 0
\mathrm{anchored}(t) \;=\; 2t - e(t),\quad \kappa < 0

and finally remap back out:

x' \;=\; \mathrm{anchored}(t) \cdot (L_{\mathrm{hi}} - L_{\mathrm{lo}}) \;+\; L_{\mathrm{lo}}

The output pixel is a mask-weighted blend of original and remapped:

x_{\mathrm{out}} \;=\; (1 - m) \cdot x \;+\; m \cdot x'

Because the sigmoid is anchored at both endpoints, pixels outside the band (where t is outside [0, 1]) still get remapped by the formula — but their mask value m is near zero, so the blend keeps them untouched. The feathering at the band edges comes entirely from the mask, which means contrast transitions at the band boundary are as smooth as the mask's smoothness setting allows.

12.5 Non-monotonic contrast limit

In compression mode (κ < 0) the remap is anchored(t) = 2t − e(t). Differentiating with respect to t:

\frac{d\,\mathrm{anchored}}{dt} \;=\; 2 - \frac{d\,e}{dt}

The curve stops being monotonically increasing where de/dt > 2, which happens for sufficiently large k. The threshold works out to:

\kappa \;>\; -\frac{2}{3}

Past this point (slider positions below −0.667), the compressed curve wiggles rather than monotonically decreasing in slope, so dark pixels can end up slightly brighter than slightly-less-dark pixels at the band ends — the wavy artefact the on-screen warning calls out. The slider still functions; the warning just makes the regime explicit.

12.6 Everything else

CMY, RGB primaries, Luminance additive, Chroma (L-preserving), Saturation (HSV S), and Intensity scaling are all identical to Selective Color — see that tool's § 13.6 for the formulas. Region-mask AND composition (§ 13.8) and the dual debounced-timer architecture (§ 13.9) also carry over unchanged.

12.7 Debounced pipeline

Two 200 ms single-shot QTimers:

Slider release and spin-box editing-finished force an immediate repaint so you don't wait after letting go.

12.8 OpenCV dependency

Required for Gaussian edge blur, lasso polygon fill, mask resize, region outline rendering, and HSV conversion inside the saturation path. The _build_lum_mask core itself is pure NumPy and runs without cv2; the UI will still work for the band mask path, but blur and lasso fall back to no-op / Matplotlib path respectively.

Credits. The luminance-selective editing concept — build a mask from a brightness band and apply colour/contrast through it — is a classical darkroom and digital-editing move. What's specific to this tool and Frank's work for SASpro: the circular Luminance Wheel widget (grayscale ring with black-at-bottom / white-at-top mapping, two draggable handles, sampled-luminance marker), the soft-mode bump mask with endpoint-clamped shoulders that keep pure black and pure white in-band, the anchored-sigmoid band contrast formula that pins the sigmoid to the band endpoints so contrast only reshapes the midrange of the selected band, the non-monotonic warning at κ < −2/3, the reset-adjustments-only behaviour after Apply (as opposed to Selective Color's full reset), and the UI around all of it. HSV conversion, Gaussian blur, lasso polygon fill, and image resize use OpenCV. The luminance-preserving chroma formula and the CMY/RGB adjustment math are the same classical approaches used in Selective Color (§ 13.6 there).