From bd7efce600f50c015d3cfef05b7e8c5fc5b771eb Mon Sep 17 00:00:00 2001 From: 3dtours Date: Wed, 9 Sep 2026 11:32:30 +0700 Subject: [PATCH] feat(presets): add LEICA simulation chip; collapse recipe strip on sim select --- src/components/AdjustmentPanel.tsx | 8 +++++++- src/types/index.ts | 2 +- src/utils/colorUtils.ts | 16 ++++++++++++++++ src/utils/defaultRecipes.ts | 5 +++++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/components/AdjustmentPanel.tsx b/src/components/AdjustmentPanel.tsx index 46987b0..2238f0d 100644 --- a/src/components/AdjustmentPanel.tsx +++ b/src/components/AdjustmentPanel.tsx @@ -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', diff --git a/src/types/index.ts b/src/types/index.ts index bb45ebf..9dee659 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -34,7 +34,7 @@ export const ASPECT_RATIO_W_H: Record, 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) diff --git a/src/utils/colorUtils.ts b/src/utils/colorUtils.ts index 05c1778..754fc4d 100644 --- a/src/utils/colorUtils.ts +++ b/src/utils/colorUtils.ts @@ -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; diff --git a/src/utils/defaultRecipes.ts b/src/utils/defaultRecipes.ts index dc71004..19458ff 100644 --- a/src/utils/defaultRecipes.ts +++ b/src/utils/defaultRecipes.ts @@ -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).