feat(presets): add LEICA simulation chip; collapse recipe strip on sim select

This commit is contained in:
2026-09-09 11:32:30 +07:00
parent 09dbc72cce
commit bd7efce600
4 changed files with 29 additions and 2 deletions
+7 -1
View File
@@ -302,7 +302,13 @@ export default function AdjustmentPanel({
key: s.id,
label: s.name,
active: currentRecipeId === s.id,
onPress: () => onSelectRecipe(filmSimToRecipe(s)),
// Choosing a simulation is a deliberate switch away from the saved
// recipes: collapse the RECIPES strip above so it never lingers
// over the freshly-applied sim.
onPress: () => {
setShowRecipes(false);
onSelectRecipe(filmSimToRecipe(s));
},
})),
{
key: 'recipes-toggle',
+1 -1
View File
@@ -34,7 +34,7 @@ export const ASPECT_RATIO_W_H: Record<Exclude<AspectRatio, 'full'>, number> = {
// cool tint in shade, plus a seasonal atmospheric haze/fog veil.
export type CinemaSeason = 'spring' | 'summer' | 'autumn' | 'winter';
export type BaseFilter = 'classic-neg' | 'classic-chrome' | 'provia' | 'velvia' | 'astia' | 'eterna' | 'monochrome' | 'none';
export type BaseFilter = 'classic-neg' | 'classic-chrome' | 'provia' | 'velvia' | 'astia' | 'eterna' | 'monochrome' | 'leica' | 'none';
export interface ColorAdjustments {
exposure: number; // -10 to +10 (mapped to matrix multiplier or offset)
+16
View File
@@ -98,6 +98,22 @@ export function getSkiaColorMatrix(baseFilter: BaseFilter, adj: ColorAdjustments
0.02, 0.02, 0.94, 0, 0.02,
0, 0, 0, 1, 0,
];
} else if (baseFilter === 'leica') {
// Leica digital look (M/Q/SL): neutral-but-warm, deep yet open shadows,
// restrained saturation with deep reds and true greens — never the punchy
// chroma of Velvia or the amber of Classic Neg. A linear matrix cannot do
// the smooth highlight rolloff (that's the tone shader's job), so this only
// carries the mild contrast/warmth/colour balance half:
// - gentle contrast around mid-grey (deep blacks, no lifted blacks)
// - slight warm cast in highlights (R up, B down), near-neutral white
// - reds a touch deeper, blues slightly muted towards natural skies
// - green mostly untouched (Leica greens stay true, not olive)
matrix = [
1.08, -0.02, -0.01, 0, -0.025,
-0.015, 1.06, -0.01, 0, -0.017,
-0.02, -0.015, 1.04, 0, -0.013,
0, 0, 0, 1, 0,
];
} else if (baseFilter === 'monochrome') {
// Acros Monochrome: ITU-R BT.709 luma — pure grayscale, zero color leakage
const r = 0.2126;
+5
View File
@@ -43,6 +43,11 @@ export const FILM_SIMS: FilmSim[] = [
{ 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 } },
// Leica digital look (M/Q/SL) is NOT a film stock: neutral-but-warm colour
// with restrained saturation, deep open shadows and gentle highlight rolloff.
// Matrix-only here (like every sim) — the tonal half lives in the leica
// matrix (mild contrast + warm neutrality); users stack DR/tone on top.
{ id: 'sim-leica', name: 'LEICA', baseFilter: 'leica', adjustments: { ...DEFAULT_ADJUSTMENTS } },
];
// Film sim → full Recipe (never persisted; sims are applied on the fly).