28e18893cd
The built-in lens filter, simulated the way it is done in Photoshop: pull the highlights out with a hard contrast curve (out = 2.5*in - 1.5, black below 0.6), Gaussian-blur that selection, then Screen it back over the photo at reduced opacity. Screen against a near-black pixel is a no-op, so areas with no highlight within the blur radius come back byte-identical while lit areas spread their light. One 0..10 knob drives both the radius (4%..1.9% of the width, ~10..50px at 2560 wide) and the opacity (0.15..0.5), matching the recipe's 15-50px radius range. The extraction composes on top of the recipe colour matrix so a monochrome look glows white instead of leaking the original colours back. Runs in the Skia export engine only; the P5 native path is not wired into the app (dev parity probe only), so it is left alone for now.
177 lines
3.7 KiB
TypeScript
177 lines
3.7 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>;
|
|
}
|
|
|
|
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: '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 }),
|
|
},
|
|
],
|
|
filters: [
|
|
{
|
|
key: 'denoise',
|
|
label: 'NOISE REDUCTION',
|
|
min: 0,
|
|
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: 0,
|
|
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',
|
|
min: 0,
|
|
max: 10,
|
|
step: 1,
|
|
defaultValue: 0,
|
|
display: String,
|
|
get: (a) => a.hdf ?? 0,
|
|
set: (v) => ({ hdf: v }),
|
|
},
|
|
],
|
|
};
|