8308b3e65f
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).
229 lines
5.0 KiB
TypeScript
229 lines
5.0 KiB
TypeScript
import { ColorAdjustments } from '../types';
|
|
|
|
// Static metadata for the continuous (slider) adjustments of the IQ/WB/FILTERS
|
|
// tabs — shared between AdjustmentPanel (chips + slider rows) and App (which
|
|
// needs the open row's bounds to drive drag-on-the-image adjustment). The get
|
|
// /set accessors are stateless so one table serves every render.
|
|
export interface ContinuousParamDef {
|
|
key: string;
|
|
label: string;
|
|
min: number;
|
|
max: number;
|
|
step: number;
|
|
defaultValue: number;
|
|
display: (v: number) => string;
|
|
get: (a: ColorAdjustments) => number;
|
|
set: (v: number) => Partial<ColorAdjustments>;
|
|
// PRO-only knob: the LITE build greys the chip out (PRO corner tag) and
|
|
// cannot open its row, so the value stays at its default there.
|
|
pro?: boolean;
|
|
}
|
|
|
|
const sign = (v: number) => (v > 0 ? `+${v}` : String(v));
|
|
const oneEv = (v: number) => `${v > 0 ? '+' : ''}${v.toFixed(1)} EV`;
|
|
|
|
export const PARAM_DEFS: {
|
|
iq: ContinuousParamDef[];
|
|
wb: ContinuousParamDef[];
|
|
filters: ContinuousParamDef[];
|
|
} = {
|
|
iq: [
|
|
{
|
|
key: 'exposure',
|
|
label: 'EXPOSURE',
|
|
min: -10,
|
|
max: 10,
|
|
step: 1,
|
|
defaultValue: 0,
|
|
display: sign,
|
|
get: (a) => a.exposure,
|
|
set: (v) => ({ exposure: v }),
|
|
},
|
|
{
|
|
key: 'ev',
|
|
label: 'EV',
|
|
min: -3,
|
|
max: 3,
|
|
step: 0.5,
|
|
defaultValue: 0,
|
|
display: oneEv,
|
|
get: (a) => a.exposureCompensation ?? 0,
|
|
set: (v) => ({ exposureCompensation: v }),
|
|
},
|
|
{
|
|
key: 'contrast',
|
|
label: 'CONTRAST',
|
|
min: -10,
|
|
max: 10,
|
|
step: 1,
|
|
defaultValue: 0,
|
|
display: sign,
|
|
get: (a) => a.contrast,
|
|
set: (v) => ({ contrast: v }),
|
|
},
|
|
{
|
|
key: 'color',
|
|
label: 'COLOR',
|
|
min: -10,
|
|
max: 10,
|
|
step: 1,
|
|
defaultValue: 0,
|
|
display: sign,
|
|
get: (a) => a.saturation,
|
|
set: (v) => ({ saturation: v }),
|
|
},
|
|
{
|
|
key: 'vibrance',
|
|
label: 'VIBRANCE',
|
|
min: -10,
|
|
max: 10,
|
|
step: 1,
|
|
defaultValue: 0,
|
|
display: sign,
|
|
get: (a) => a.vibrance ?? 0,
|
|
set: (v) => ({ vibrance: v }),
|
|
},
|
|
{
|
|
key: 'highlight',
|
|
label: 'HIGHLIGHT',
|
|
min: -10,
|
|
max: 10,
|
|
step: 1,
|
|
defaultValue: 0,
|
|
display: sign,
|
|
get: (a) => a.highlight ?? 0,
|
|
set: (v) => ({ highlight: v }),
|
|
},
|
|
{
|
|
key: 'shadow',
|
|
label: 'SHADOW',
|
|
min: -10,
|
|
max: 10,
|
|
step: 1,
|
|
defaultValue: 0,
|
|
display: sign,
|
|
get: (a) => a.shadow ?? 0,
|
|
set: (v) => ({ shadow: v }),
|
|
},
|
|
],
|
|
wb: [
|
|
{
|
|
key: 'temperature',
|
|
label: 'COLOR TEMP',
|
|
min: 2500,
|
|
max: 10000,
|
|
step: 100,
|
|
defaultValue: 5500,
|
|
display: (v) => `${v}K`,
|
|
get: (a) => a.temperature,
|
|
set: (v) => ({ temperature: v }),
|
|
},
|
|
{
|
|
key: 'tint',
|
|
label: 'TINT',
|
|
min: -10,
|
|
max: 10,
|
|
step: 1,
|
|
defaultValue: 0,
|
|
display: sign,
|
|
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: [
|
|
{
|
|
key: 'denoise',
|
|
label: 'NOISE REDUCTION',
|
|
min: -10,
|
|
max: 10,
|
|
step: 1,
|
|
defaultValue: 0,
|
|
display: String,
|
|
get: (a) => a.denoise,
|
|
set: (v) => ({ denoise: v }),
|
|
},
|
|
{
|
|
key: 'clarity',
|
|
label: 'CLARITY',
|
|
min: -10,
|
|
max: 10,
|
|
step: 1,
|
|
defaultValue: 0,
|
|
display: sign,
|
|
get: (a) => a.clarity,
|
|
set: (v) => ({ clarity: v }),
|
|
},
|
|
{
|
|
key: 'sharpening',
|
|
label: 'SHARPENING',
|
|
min: -10,
|
|
max: 10,
|
|
step: 1,
|
|
defaultValue: 0,
|
|
display: String,
|
|
get: (a) => a.sharpening ?? 0,
|
|
set: (v) => ({ sharpening: v }),
|
|
},
|
|
{
|
|
key: 'grain',
|
|
label: 'MONOCHROME GRAIN',
|
|
min: 0,
|
|
max: 10,
|
|
step: 1,
|
|
defaultValue: 0,
|
|
display: String,
|
|
get: (a) => a.grain,
|
|
set: (v) => ({ grain: v }),
|
|
},
|
|
{
|
|
key: 'hdf',
|
|
label: 'HDF EFFECT',
|
|
pro: true,
|
|
min: 0,
|
|
max: 10,
|
|
step: 1,
|
|
defaultValue: 0,
|
|
display: String,
|
|
get: (a) => a.hdf ?? 0,
|
|
set: (v) => ({ hdf: v }),
|
|
},
|
|
{
|
|
key: 'vignette',
|
|
label: 'VIGNETTING',
|
|
min: 0,
|
|
max: 10,
|
|
step: 1,
|
|
defaultValue: 0,
|
|
display: String,
|
|
get: (a) => a.vignette ?? 0,
|
|
set: (v) => ({ vignette: v }),
|
|
},
|
|
],
|
|
};
|