diff --git a/docker/frontend/src/App.tsx b/docker/frontend/src/App.tsx index 6ca1338..5f2d6ca 100644 --- a/docker/frontend/src/App.tsx +++ b/docker/frontend/src/App.tsx @@ -2233,6 +2233,69 @@ export function Workspace() { || (Object.keys(DEFAULT_GPS_STYLE) as (keyof GpsStyle)[]).some((k) => gpsStyle[k] !== DEFAULT_GPS_STYLE[k]); }, [recipe, simId, frameId, crop, rotation, straighten, markOn, markText, markStyle, useGeotag, gpsStyle]); + // The chosen mask's knobs, as the app's own rulers: one parameter to a row, + // the same row every other number in the app is edited with. EXPOSURE is the + // spec's EV (-5..+5, its own `pow(2.0, e)`); CONTRAST and SATURATION are the + // app's -10..+10, which the shader reads as the spec's -1..+1; and a radial + // mask adds the feather it fades over — the percentage of its own axis the + // alpha holds full before it dies at the rim. + const maskRulers: SliderDef[] = !selMask + ? [] + : [ + { + key: 'mask-exposure', + label: 'EXPOSURE', + value: selMask.exposure, + min: -MASK_EXPOSURE_MAX, + max: MASK_EXPOSURE_MAX, + step: 0.1, + display: `${selMask.exposure > 0 ? '+' : ''}${selMask.exposure.toFixed(1)}`, + edited: selMask.exposure !== 0, + onChange: (v) => setMaskKnob({ exposure: v }), + onReset: () => setMaskKnob({ exposure: 0 }), + }, + { + key: 'mask-contrast', + label: 'CONTRAST', + value: selMask.contrast, + min: -10, + max: 10, + step: 1, + display: selMask.contrast > 0 ? `+${selMask.contrast}` : String(selMask.contrast), + edited: selMask.contrast !== 0, + onChange: (v) => setMaskKnob({ contrast: Math.round(v) }), + onReset: () => setMaskKnob({ contrast: 0 }), + }, + { + key: 'mask-saturation', + label: 'SATURATION', + value: selMask.saturation, + min: -10, + max: 10, + step: 1, + display: selMask.saturation > 0 ? `+${selMask.saturation}` : String(selMask.saturation), + edited: selMask.saturation !== 0, + onChange: (v) => setMaskKnob({ saturation: Math.round(v) }), + onReset: () => setMaskKnob({ saturation: 0 }), + }, + ...(selMask.kind === 'radial' + ? [ + { + key: 'mask-feather', + label: 'FEATHER', + value: Math.round(selMask.feather * 100), + min: 0, + max: 100, + step: 5, + display: `${Math.round(selMask.feather * 100)}%`, + edited: selMask.feather !== MASK_DEFAULT_FEATHER, + onChange: (v: number) => setMaskKnob({ feather: v / 100 }), + onReset: () => setMaskKnob({ feather: MASK_DEFAULT_FEATHER }), + }, + ] + : []), + ]; + const resetChip = (): ChipDef => ({ key: 'reset-all', label: resetDirty ? 'RESET *' : 'RESET', @@ -2768,9 +2831,9 @@ export function Workspace() { {/* column 2 — FX's GRADIENT MASK, while one is chosen: the shape's own - name, the way to take it off the photo, and the three knobs it - grades with. A radial mask adds the feather it fades over, since - that is the number its rim is made of. */} + name, the way to take it off the photo, and its knobs as the app's + own rulers — one parameter to a row. A radial mask adds the feather + it fades over, since that is the number its rim is made of. */} {selMask ? (