light: the SHADOW knob rides half its anchor, so a lift stops drawing the band above it flat — a waterfall's spray came back at 0.10 of its own contrast, 0.55 now
This commit is contained in:
@@ -32,7 +32,11 @@ import { HSL_BANDS, hslBandGaps, isMonochromeBase } from './colorUtils';
|
||||
// measurement is monotone for every combination of the four at full deflection.
|
||||
// A knob moves its anchor by a quarter of the ramp, so +10 BLACKS puts the toe
|
||||
// on 0.25 and -10 WHITES rolls the head down to 0.75: the reach a tonal range
|
||||
// slider has in the program this layout copies, without the inversion.
|
||||
// slider has in the program this layout copies, without the inversion. SHADOW is
|
||||
// the one knob held to half of that, because the knot it moves is the HEAD of
|
||||
// the quarter above it and not an end of the ramp: a band cannot be lifted at
|
||||
// its head and keep its slope at the same time, so the knob's travel is what has
|
||||
// to give — see the measured note on a1.
|
||||
//
|
||||
// WHITE and BLACK are not the per-channel toe and shoulder they were on the WB
|
||||
// tab any more. The doc puts the two points on the ends of the SAME ramp as the
|
||||
@@ -103,9 +107,12 @@ const BAND_BLOCK = hslBandGaps()
|
||||
|
||||
// How far a tonal-range knob moves its own knot, in ramp units. A quarter is
|
||||
// the reach the program this layout copies gives a slider: at full deflection
|
||||
// the four together can put the toe on the midpoint, the head on it, or either
|
||||
// end on the quarter next to it — and never past, because each knot is clamped
|
||||
// inside the one before it.
|
||||
// the toe can reach the quarter above it and the head can be rolled onto it —
|
||||
// and never past, because each knot is clamped inside the one after it.
|
||||
//
|
||||
// SHADOW is the one knob that moves HALF of it. The knot it owns is the HEAD of
|
||||
// the 0.25..0.50 quarter, so a unit of lift is a unit of slope that quarter
|
||||
// loses, and at a whole quarter the band is drawn flat — see the note on a1.
|
||||
export const TONE_ANCHOR = 0.25;
|
||||
|
||||
// The tone and exposure maths, in ONE copy, because two passes ask it: the
|
||||
@@ -125,8 +132,9 @@ float lin(float e0, float e1, float x) {
|
||||
return clamp((x - e0) / (e1 - e0), 0.0, 1.0);
|
||||
}
|
||||
// The ramp the pixel is rebuilt through. Knots on 0.00, 0.25, 0.50, 0.75 and
|
||||
// 1.00; a knob moves the knot it owns by TONE_ANCHOR of the ramp, and each knot
|
||||
// is held inside the one before it so the five can never cross. 0.50 is fixed:
|
||||
// 1.00; a knob moves the knot it owns by TONE_ANCHOR of the ramp — SHADOW by
|
||||
// half of that, its knot being the head of a band — and each knot is held inside
|
||||
// the one after it so the five can never cross. 0.50 is fixed:
|
||||
// it is the one point all four sliders leave alone, which is what keeps a
|
||||
// mid-grey a mid-grey while the ends move around it. Straight between the knots,
|
||||
// so every knob on zero is exactly the identity (see the note at the head of
|
||||
@@ -186,7 +194,19 @@ vec3 lightMove(vec3 c, float t, float o) {
|
||||
vec3 toneRamp(vec3 c, float t, float bl, float sh, float hl, float wh, float dr) {
|
||||
float a4 = 1.0 + ${TONE_ANCHOR} * wh - dr * 0.18;
|
||||
float a3 = clamp(0.75 + ${TONE_ANCHOR} * hl - dr * 0.09, 0.5, a4);
|
||||
float a1 = clamp(0.25 + ${TONE_ANCHOR} * sh + dr * 0.06, 0.0, 0.5);
|
||||
// SHADOW rides HALF the anchor, and that is a measured ceiling rather than a
|
||||
// taste. a1 is the HEAD of the quarter above it, so the band 0.25..0.50 pays
|
||||
// in slope for every unit this knot rises — the slope is (0.5 - a1) / 0.25 —
|
||||
// and that band is where a waterfall's spray and a sunlit rock sit. At the
|
||||
// whole anchor the knot lands ON the midpoint and draws the band flat: on a
|
||||
// real frame (DSCF1701, misty valley, 21% of its pixels in the band) SHADOW
|
||||
// +90 came back with 0.10 of the band's own spread and a mean of 0.392 where
|
||||
// it was 0.489 — the milky white sheet the knob was reported for, bright areas
|
||||
// with their contrast gone. At half: 0.55 of the spread, mean 0.441, and the
|
||||
// toe still opens at 1.45x. Half also keeps the knob smooth over its whole
|
||||
// travel — a clamp alone would pin it from +50 on and leave the top of the
|
||||
// slider dead — which is why the RATE is halved and not just the ceiling.
|
||||
float a1 = clamp(0.25 + ${TONE_ANCHOR} * 0.5 * sh + dr * 0.06, 0.0, 0.5);
|
||||
float a0 = clamp(${TONE_ANCHOR} * bl + dr * 0.12, 0.0, a1);
|
||||
float o = mix(a0, a1, lin(0.00, 0.25, t));
|
||||
o = mix(o, mix(a1, 0.5, lin(0.25, 0.50, t)), step(0.25, t));
|
||||
@@ -638,11 +658,17 @@ export interface ToneUniforms {
|
||||
// Chrome crushes the shadows hard while muting colour. Those two parts live
|
||||
// here instead of in the 4x5 matrix, which cannot move one end of the curve
|
||||
// without also moving the other.
|
||||
//
|
||||
// The `sh` here is written at DOUBLE what the knob's own arithmetic reads, which
|
||||
// is not a taste either: SHADOW moves its knot half an anchor per unit (see a1
|
||||
// in TONE_MATH_SKSL), so a stock that wants the toe on 0.18 has to ask for -0.56
|
||||
// to land it there. The stock's crush is the knot it puts the ramp on; the unit
|
||||
// it is written in belongs to the knob, not to the look.
|
||||
const FILM_TONE: Partial<Record<BaseFilter, Partial<ToneUniforms>>> = {
|
||||
'classic-chrome': { sh: -0.28 },
|
||||
'classic-chrome': { sh: -0.56 },
|
||||
// Classic Vivid is Classic Chrome's sibling — the shadow crush belongs to the
|
||||
// stock, not to the matrix rows, so it comes along.
|
||||
'classic-vivid': { sh: -0.28 },
|
||||
'classic-vivid': { sh: -0.56 },
|
||||
'classic-neg': { shT: [-0.018, 0.009, 0.013], hlT: [0.024, 0.008, -0.012] },
|
||||
// Acros. A black-and-white stock IS its grey ramp, so this entry only shapes
|
||||
// the two ENDS and leaves the middle an identity: a smooth shadow toe that
|
||||
@@ -651,18 +677,19 @@ const FILM_TONE: Partial<Record<BaseFilter, Partial<ToneUniforms>>> = {
|
||||
// paper. Mid-tones are between the 0.25 and the 0.75 knots, so they keep
|
||||
// every step the matrix handed over — which is what 'deep black' costs in a
|
||||
// colour stock and does not have to cost here.
|
||||
// The values move the two end knots of the ramp: -0.12 puts the toe on 0.22
|
||||
// and -0.05 rolls the head to 0.7375 (each is TONE_ANCHOR = 0.25 per unit).
|
||||
monochrome: { sh: -0.12, hl: -0.05 },
|
||||
// The values move the two end knots of the ramp: -0.24 puts the toe on 0.22
|
||||
// and -0.05 rolls the head to 0.7375 (HIGHLIGHT is TONE_ANCHOR per unit, the
|
||||
// doubled SHADOW half of it).
|
||||
monochrome: { sh: -0.24, hl: -0.05 },
|
||||
// B&W HIGH CONTRAST. Acros' ramp with both ends pushed hard: a deeper toe
|
||||
// (-0.32 against Acros' -0.12, so 0.17 against 0.22) so the darks reach true
|
||||
// (-0.64 against Acros' -0.24, so 0.17 against 0.22) so the darks reach true
|
||||
// black, and a shoulder that LIFTS instead of rolling (-0.05 → +0.26, the
|
||||
// head going to 0.815), which is the whites step of the brief. The stretch
|
||||
// between the two inner knots (0.25 and 0.75) is still the identity, so the
|
||||
// long smooth stretch of the greys survives — that is what keeps a hard push
|
||||
// off the posterised look, and the strength the stock needs on the greys is
|
||||
// its matrix slope (SIM_CONTRAST_BIAS in colorUtils), not another move here.
|
||||
'mono-high-contrast': { sh: -0.32, hl: 0.26 },
|
||||
'mono-high-contrast': { sh: -0.64, hl: 0.26 },
|
||||
};
|
||||
|
||||
export function getToneUniforms(adj: ColorAdjustments, baseFilter?: BaseFilter): ToneUniforms {
|
||||
|
||||
Reference in New Issue
Block a user