web: a film sim is colour and tone only
The ten PHOTO STYLE sims now carry nothing but their stock's own grade, and each is named for the stock it stands for: PROVIA, VELVIA, CLASSIC CHROME, CLASSIC VIVID (Velvia spliced with Classic Chrome at the blue row), CLASSIC NEGATIVE, ASTIA, ETERNA, ACROS, LC STREETLIFE CLASSIC, LC STREETLIFE VIVID. Grain, clarity, saturation and light moves were dropped from their `adjustments`, so a sim is a clean starting point and the general knobs read their defaults while the look still lands on the pixels. LC STREETLIFE VIVID keeps the one brightness step its stock needs, but as SIM_EXPOSURE_BIAS in colorUtils rather than as an adjustment: it is folded in where the Exposure slider applies, so the picture gets the lift and the parameter stays at 0. Also in this checkpoint: the watermark/GPS boxes and their colour pickers, the WATERMARK chip column, the real admin stats, and the fix that stopped presets from doubling and a frame from refusing to come off when a photo was reopened (/file is the finished render, /base the editable pixels).
This commit is contained in:
@@ -138,6 +138,15 @@ function normalizeGainLuma(gain: number[]): { r: number; g: number; b: number }
|
||||
return { r: gain[0] * s, g: gain[1] * s, b: gain[2] * s };
|
||||
}
|
||||
|
||||
// Sim-owned exposure bias: brightness a stock needs that is part of the stock
|
||||
// itself, never a user knob. Kept beside the matrices it compensates so the
|
||||
// values are one hop from the rows they lift. Only LC STREETLIFE VIVID carries
|
||||
// one — its matrix is a pure separation with no lift, and without this it reads
|
||||
// a full step darker than the other sims.
|
||||
const SIM_EXPOSURE_BIAS: Partial<Record<BaseFilter, number>> = {
|
||||
'leica-vivid': 2,
|
||||
};
|
||||
|
||||
// Generate a 4x5 ColorMatrix (array of 20 floats) based on base style and
|
||||
// adjustments. Tone-domain knobs (dynamicRange/highlight/shadow) and the
|
||||
// exposure-compensation EV gain are intentionally NOT part of the matrix —
|
||||
@@ -145,7 +154,12 @@ function normalizeGainLuma(gain: number[]): { r: number; g: number; b: number }
|
||||
// and EV is applied either as hardware AE bias (camera) or by the caller via
|
||||
// applyExposureGain (library), never doubled.
|
||||
export function getSkiaColorMatrix(baseFilter: BaseFilter, adj: ColorAdjustments): number[] {
|
||||
const exposure = adj.exposure;
|
||||
// Sim-owned exposure bias, in Exposure-slider units. A sim is colour only and
|
||||
// its `adjustments` stay neutral, so this does NOT surface on the Exposure
|
||||
// knob — it is folded in here, at the same stage and with the same gain/offset
|
||||
// mapping the knob uses, so the pixels get the lift the stock needs while the
|
||||
// UI keeps reading 0.
|
||||
const exposure = (adj.exposure ?? 0) + (SIM_EXPOSURE_BIAS[baseFilter] ?? 0);
|
||||
const contrast = adj.contrast;
|
||||
const saturation = adj.saturation;
|
||||
const temperature = adj.temperature;
|
||||
@@ -259,9 +273,10 @@ export function getSkiaColorMatrix(baseFilter: BaseFilter, adj: ColorAdjustments
|
||||
// digital era: ~20% of chroma gain (still well under Velvia's 39%, so it
|
||||
// sparkles instead of shouting) on the same warm R>G>B bias CLASSIC has, so
|
||||
// VIVID is a brighter read of one look rather than a second, neutral one.
|
||||
// No positive lift here: the brightness is the sim's own exposure value
|
||||
// (defaultRecipes), which keeps the matrix inside 0..1 and pure white out of
|
||||
// the clip — white lands at most at 1.000 on the red row.
|
||||
// No positive lift in the rows themselves, so the matrix stays inside the
|
||||
// 0..1 heads: the brightness this stock needs rides SIM_EXPOSURE_BIAS above
|
||||
// (+2), which keeps white out of a hard clip and keeps the sim's
|
||||
// `adjustments` neutral so the Exposure knob still reads 0.
|
||||
matrix = [
|
||||
1.125, -0.100, -0.010, 0, -0.015,
|
||||
-0.055, 1.110, -0.055, 0, -0.005,
|
||||
|
||||
@@ -26,13 +26,31 @@ export const DEFAULT_ADJUSTMENTS: ColorAdjustments = {
|
||||
exposureCompensation: 0,
|
||||
};
|
||||
|
||||
// Standard Fuji film simulations. These are the fixed "look" presets shown at
|
||||
// the front of the PRESETS chip row: tapping one applies the simulation — its
|
||||
// baseFilter colour matrix plus whatever the stock itself carries outside the
|
||||
// matrix (its FILM_TONE curve, and a grain amount when the grain IS the look,
|
||||
// as on Acros). Every other knob still resets to its neutral default (0 /
|
||||
// auto), so a sim is a clean starting point, never a baked recipe; any further
|
||||
// look (DR, CC, contrast, more grain) is added by the user afterwards.
|
||||
// Film simulations. These are the fixed "look" presets at the front of the
|
||||
// PRESETS chip row, and each one is COLOUR AND TONE ONLY.
|
||||
//
|
||||
// A sim carries exactly two things: its `baseFilter` colour matrix
|
||||
// (colorUtils.ts) and, where the stock needs to shape one end of the curve, its
|
||||
// FILM_TONE entry (toneShader.ts). Everything the sim does to the picture is in
|
||||
// those two places — including the brightness a stock owns: that is folded into
|
||||
// the matrix through SIM_EXPOSURE_BIAS (colorUtils.ts), never into
|
||||
// `adjustments`. `adjustments` is therefore the neutral default for every sim —
|
||||
// no grain, no clarity, no saturation push, no exposure/highlight/shadow move —
|
||||
// because those are the user's knobs, not the stock's. A sim is a clean starting
|
||||
// point, never a baked recipe; any further look is added afterwards.
|
||||
//
|
||||
// Name → stock it stands for. The name IS the sim's identity and the tag it
|
||||
// exports as, so it is spelled as the stock is:
|
||||
// PROVIA Provia / Standard
|
||||
// VELVIA Velvia / Vivid
|
||||
// CLASSIC CHROME Classic Chrome
|
||||
// CLASSIC VIVID Velvia spliced with Classic Chrome at the blue row
|
||||
// CLASSIC NEGATIVE Classic Negative (Superia-like)
|
||||
// ASTIA Astia / Soft
|
||||
// ETERNA Eterna (cinema)
|
||||
// ACROS Acros (monochrome)
|
||||
// LC STREETLIFE CLASSIC Leica Authentic Classic
|
||||
// LC STREETLIFE VIVID Leica Vivid
|
||||
export interface FilmSim {
|
||||
id: string; // Recipe-like id used only while the sim is the current selection
|
||||
name: string;
|
||||
@@ -41,27 +59,22 @@ export interface FilmSim {
|
||||
}
|
||||
|
||||
export const FILM_SIMS: FilmSim[] = [
|
||||
{ id: 'sim-provia', name: 'PROVIPES', baseFilter: 'provia', adjustments: { ...DEFAULT_ADJUSTMENTS } },
|
||||
{ id: 'sim-velvia', name: 'VELVIPES', baseFilter: 'velvia', adjustments: { ...DEFAULT_ADJUSTMENTS } },
|
||||
{ id: 'sim-classic-chrome', name: 'CLASSIC CHRIPES', baseFilter: 'classic-chrome', adjustments: { ...DEFAULT_ADJUSTMENTS } },
|
||||
{ id: 'sim-classic-vivid', name: 'CLASSIC VIVIDIPES', baseFilter: 'classic-vivid', adjustments: { ...DEFAULT_ADJUSTMENTS } },
|
||||
{ id: 'sim-classic-neg', name: 'CLASSIC NEGIPES', baseFilter: 'classic-neg', adjustments: { ...DEFAULT_ADJUSTMENTS } },
|
||||
{ id: 'sim-astia', name: 'ASTIPES', baseFilter: 'astia', adjustments: { ...DEFAULT_ADJUSTMENTS } },
|
||||
{ id: 'sim-eterna', name: 'ETERNIPES', baseFilter: 'eterna', adjustments: { ...DEFAULT_ADJUSTMENTS } },
|
||||
{ id: 'sim-acros', name: 'ACRIPES', baseFilter: 'monochrome', adjustments: { ...DEFAULT_ADJUSTMENTS, grain: 3 } },
|
||||
{ id: 'sim-provia', name: 'PROVIA', baseFilter: 'provia', adjustments: { ...DEFAULT_ADJUSTMENTS } },
|
||||
{ id: 'sim-velvia', name: 'VELVIA', baseFilter: 'velvia', adjustments: { ...DEFAULT_ADJUSTMENTS } },
|
||||
{ id: 'sim-classic-chrome', name: 'CLASSIC CHROME', baseFilter: 'classic-chrome', adjustments: { ...DEFAULT_ADJUSTMENTS } },
|
||||
{ id: 'sim-classic-vivid', name: 'CLASSIC VIVID', baseFilter: 'classic-vivid', adjustments: { ...DEFAULT_ADJUSTMENTS } },
|
||||
{ id: 'sim-classic-neg', name: 'CLASSIC NEGATIVE', baseFilter: 'classic-neg', adjustments: { ...DEFAULT_ADJUSTMENTS } },
|
||||
{ id: 'sim-astia', name: 'ASTIA', baseFilter: 'astia', adjustments: { ...DEFAULT_ADJUSTMENTS } },
|
||||
{ id: 'sim-eterna', name: 'ETERNA', baseFilter: 'eterna', adjustments: { ...DEFAULT_ADJUSTMENTS } },
|
||||
{ id: 'sim-acros', name: 'ACROS', baseFilter: 'monochrome', adjustments: { ...DEFAULT_ADJUSTMENTS } },
|
||||
// The rangefinder digital look (M/Q/SL) is not a film stock, so it ships as its own
|
||||
// pair instead of one look that has to compromise:
|
||||
// CLASSIC — the stock character: deep solid darks, natural shadows, rich but
|
||||
// quiet colour. Its micro-contrast is CLARITY (a local unsharp mask in both
|
||||
// renderers), never a global saturation boost, and a whisker of grain
|
||||
// keeps it feeling like film rather than digital sharpness.
|
||||
// VIVID — the same channel separation opened up for the digital era: lifted
|
||||
// exposure, lifted shadows, a little colour. It reads bright and flattering
|
||||
// straight off the shutter, no editing pass needed.
|
||||
// Both share the 'leica'/'leica-vivid' matrices; the tonal half is the sim's
|
||||
// own adjustments, which every apply path now carries into the panel.
|
||||
{ id: 'sim-leica', name: 'LC STREETLIFE CLASSIC', baseFilter: 'leica', adjustments: { ...DEFAULT_ADJUSTMENTS, clarity: 2, grain: 1 } },
|
||||
{ id: 'sim-leica-vivid', name: 'LC STREETLIFE VIVID', baseFilter: 'leica-vivid', adjustments: { ...DEFAULT_ADJUSTMENTS, exposure: 2, saturation: 2, clarity: 2, shadow: 1, highlight: -1 } },
|
||||
// pair instead of one look that has to compromise. Both are still colour and
|
||||
// tone only: CLASSIC is Leica Authentic (the matrix carries the channel
|
||||
// separation and the warm European cast, nothing else), VIVID is Leica Vivid
|
||||
// (the same separation opened up — again in the matrix, not as an exposure or
|
||||
// saturation knob).
|
||||
{ id: 'sim-leica', name: 'LC STREETLIFE CLASSIC', baseFilter: 'leica', adjustments: { ...DEFAULT_ADJUSTMENTS } },
|
||||
{ id: 'sim-leica-vivid', name: 'LC STREETLIFE VIVID', baseFilter: 'leica-vivid', adjustments: { ...DEFAULT_ADJUSTMENTS } },
|
||||
];
|
||||
|
||||
// Film sim → full Recipe (never persisted; sims are applied on the fly).
|
||||
|
||||
Reference in New Issue
Block a user