web: move WHITE/BLACK onto the WB tab as per-channel points
LIGHT already spends its slope budget on the tone knees, so the two end points were doing nothing a tone knob could not. On WB they act on each channel's own distance from the end: the darker channel of a shadow and the brighter channel of a highlight move most, which neutralises a cast at the toe and the shoulder. Both shuffles stay cubic in the channel value, so every channel's curve is still monotonic (>= 0.46).
This commit is contained in:
@@ -80,8 +80,8 @@ export interface ColorAdjustments {
|
||||
wbBlue?: number; // Fuji WB fine-tune, Blue axis (-9..9) — extra blue-channel gain
|
||||
highlight: number; // -10 to +10 (- pulls highlights down / roll, + brightens)
|
||||
shadow: number; // -10 to +10 (+ lifts shadows, - deepens)
|
||||
whites?: number; // -10 to +10 (white point: the very top of the tone curve)
|
||||
blacks?: number; // -10 to +10 (black point: the very toe of the tone curve)
|
||||
whites?: number; // -10 to +10 (WB white point: per channel, the shoulder)
|
||||
blacks?: number; // -10 to +10 (WB black point: per channel, the toe)
|
||||
denoise: number; // -10 to +10 (+ = blur sigma; - = add film grain back)
|
||||
clarity: number; // -10 to +10 (mapped to matrix convolution / bloom)
|
||||
grain: number; // 0 to 10 (mapped to noise turbulence opacity/scale)
|
||||
|
||||
@@ -105,30 +105,6 @@ export const PARAM_DEFS: {
|
||||
get: (a) => a.shadow ?? 0,
|
||||
set: (v) => ({ shadow: v }),
|
||||
},
|
||||
{
|
||||
// The end points of the tone curve: WHITE lifts/crushes the top, BLACK the
|
||||
// toe. Same range as the CREATE form's rows, so a look round-trips.
|
||||
key: 'whites',
|
||||
label: 'WHITE',
|
||||
min: -10,
|
||||
max: 10,
|
||||
step: 1,
|
||||
defaultValue: 0,
|
||||
display: sign,
|
||||
get: (a) => a.whites ?? 0,
|
||||
set: (v) => ({ whites: v }),
|
||||
},
|
||||
{
|
||||
key: 'blacks',
|
||||
label: 'BLACK',
|
||||
min: -10,
|
||||
max: 10,
|
||||
step: 1,
|
||||
defaultValue: 0,
|
||||
display: sign,
|
||||
get: (a) => a.blacks ?? 0,
|
||||
set: (v) => ({ blacks: v }),
|
||||
},
|
||||
],
|
||||
wb: [
|
||||
{
|
||||
@@ -153,6 +129,32 @@ export const PARAM_DEFS: {
|
||||
get: (a) => a.tint ?? 0,
|
||||
set: (v) => ({ tint: v }),
|
||||
},
|
||||
{
|
||||
// The two points live on WB, not on LIGHT: they shift each channel's own
|
||||
// end of the ramp, which balances a cast at the toe and the shoulder
|
||||
// rather than adding another tone knob. Same range as the CREATE form's
|
||||
// rows, so a look round-trips.
|
||||
key: 'whites',
|
||||
label: 'WHITE',
|
||||
min: -10,
|
||||
max: 10,
|
||||
step: 1,
|
||||
defaultValue: 0,
|
||||
display: sign,
|
||||
get: (a) => a.whites ?? 0,
|
||||
set: (v) => ({ whites: v }),
|
||||
},
|
||||
{
|
||||
key: 'blacks',
|
||||
label: 'BLACK',
|
||||
min: -10,
|
||||
max: 10,
|
||||
step: 1,
|
||||
defaultValue: 0,
|
||||
display: sign,
|
||||
get: (a) => a.blacks ?? 0,
|
||||
set: (v) => ({ blacks: v }),
|
||||
},
|
||||
],
|
||||
filters: [
|
||||
{
|
||||
|
||||
@@ -25,9 +25,11 @@ import { BaseFilter, ColorAdjustments } from '../types';
|
||||
// (Fuji extended DR); 0/auto/DR100 = no extra curve.
|
||||
// hl - highlight -1..1: + lifts toward white, - rolls the bright side down.
|
||||
// sh - shadow -1..1: + lifts the dark side, - deepens it.
|
||||
// wh - white point -1..1: + pushes the top of the ramp toward white, - rolls
|
||||
// it down. Cubic end-weight, so it never fights the HL knee.
|
||||
// bl - black point -1..1: + lifts the toe (faded black), - crushes it.
|
||||
// wh - white point -1..1: per channel, from the WB tab. + lifts the shoulder,
|
||||
// - rolls it down. Cubic weight in the channel's own value, so the
|
||||
// brighter channel of a highlight moves most.
|
||||
// bl - black point -1..1: per channel. + lifts the toe (faded black), -
|
||||
// crushes it; the darker channel of a shadow moves most.
|
||||
// vib - vibrance -1..1: chroma-masked saturation. It rides along in this shader
|
||||
// (rather than the colour matrix) because it needs per-pixel chroma:
|
||||
// already-vivid pixels move least, so skins/skies deepen without the neon
|
||||
@@ -66,20 +68,20 @@ vec4 main(vec2 xy) {
|
||||
// Dynamic range: gentle shadow lift + highlight roll (protect brights).
|
||||
o += dr * 0.12 * shMask * (1.0 - t);
|
||||
o -= dr * 0.18 * hlMask * t;
|
||||
// White/black point. The HL/SH knees above already spend the slope budget
|
||||
// (each just under 1.0), so these two use cubic end-weights instead of
|
||||
// another smoothstep: only the last/first stretch of the ramp moves, and the
|
||||
// derivative stays >= 1 - 3*0.18 = 0.46 for every combination, so the curve
|
||||
// can never invert. + lifts that end (faded blacks / brighter whites),
|
||||
// - digs it in (true black / rolled white).
|
||||
o += bl * 0.18 * (1.0 - t) * (1.0 - t) * (1.0 - t);
|
||||
o += wh * 0.18 * t * t * t;
|
||||
o = clamp(o, 0.0, 1.0);
|
||||
// Lightness takes the curve, hue stays: the colour difference is gained
|
||||
// only part-way so darkening cannot collapse a colour to black and lifting
|
||||
// cannot blow a dark saturated colour out to white.
|
||||
float cg = clamp(o / max(t, 0.0004), 0.55, 1.35);
|
||||
rgb = clamp(vec3(o) + (rgb - vec3(t)) * cg, 0.0, 1.0);
|
||||
// WHITE/BLACK points, per channel. The weight is cubic in the channel's own
|
||||
// distance from the end, so in the shadows the darker channels move most and
|
||||
// in the highlights the brighter ones do: the two points pull R, G and B
|
||||
// toward a common toe and shoulder, which is a white-balance move (it
|
||||
// neutralises a cast) and not another tone slider. Each channel's curve is
|
||||
// still monotonic — 1 - 3*0.18 = 0.46 at worst — so no value can invert.
|
||||
vec3 dk = 1.0 - rgb;
|
||||
rgb = clamp(rgb + bl * 0.18 * dk * dk * dk + wh * 0.18 * rgb * rgb * rgb, 0.0, 1.0);
|
||||
// Split tone (stock look): the shadows and the highlights may each carry
|
||||
// their own tint, so the two ends of the curve can drift opposite ways
|
||||
// (Classic Neg: green-cyan darks, warm brights) without touching mid-greys.
|
||||
@@ -189,8 +191,8 @@ export interface ToneUniforms {
|
||||
dr: number; // 0..1
|
||||
hl: number; // -1..1 (adjustments.highlight / 10)
|
||||
sh: number; // -1..1 (adjustments.shadow / 10)
|
||||
wh: number; // -1..1 (adjustments.whites / 10 — white point)
|
||||
bl: number; // -1..1 (adjustments.blacks / 10 — black point)
|
||||
wh: number; // -1..1 (adjustments.whites / 10 — WB white point, per channel)
|
||||
bl: number; // -1..1 (adjustments.blacks / 10 — WB black point, per channel)
|
||||
vib: number; // -1..1 (adjustments.vibrance / 10)
|
||||
shT: [number, number, number]; // shadow split-tone RGB bias, -1..1
|
||||
hlT: [number, number, number]; // highlight split-tone RGB bias, -1..1
|
||||
|
||||
Reference in New Issue
Block a user