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; // 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 }), }, { // The two ends of the same ramp the four tone sliders move together (see // TONE_SKSL): WHITE is the knot on 1.00 and BLACK the one on 0.00, each // pulled toward the middle as the knob comes down. Not a white-balance // move any more, so they sit with the other two on the TONE panel. 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: [ { 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 }), }, ], 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 }), }, { // The haze the frame's own pixels carry (dark channel prior, toneShader's // DEHAZE_SKSL): positive takes the scattered light out, negative puts it // back — the same expression, with the transmission pushed above 1, which // is the knob a photo shot through mist wants from this side. Its chip sits // with the other spatial knob, CLARITY. key: 'dehaze', label: 'DEHAZE', min: -10, max: 10, step: 1, defaultValue: 0, display: sign, get: (a) => a.dehaze ?? 0, set: (v) => ({ dehaze: 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, // Half a step: the grain is a look, not a switch, and the AMOUNT chip in // the GRAIN strip is what spends the half. step: 0.5, defaultValue: 0, display: String, get: (a) => a.grain, set: (v) => ({ grain: v }), }, { // The SIZE knob: percent of the stock's own cell (grainShader.ts), so the // same number means the same texture RELATIVE to the picture on both // platforms and the readout beside it is the stock's, scaled. key: 'grainSize', label: 'GRAIN SIZE', min: 50, max: 200, step: 5, defaultValue: 100, display: (v) => `${v}%`, get: (a) => a.grainSize ?? 100, set: (v) => ({ grainSize: 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 }), }, ], };