c403dd04c8
The chip lands after ACRIPES and is a mono stock of its own, so it gets its own
baseFilter ('mono-high-contrast') rather than borrowing Acros': the PHOTO STYLE
chips are keyed by baseFilter, and the two greys must sit side by side.
The look is the B&W MIX plus a push at both ends. The mix rides the matrix — a
non-BT.709 row set (0.38/0.56/0.06, identical rows, sum 1.00) so a red roof
reads bright, a blue sky deep, and the separation is contrast before any curve.
The push rides FILM_TONE (shadow -0.32, highlight +0.26) so the ends move
without touching the midtones, and the midtone slope is SIM_CONTRAST_BIAS (4
contrast units) next to the sim's own exposure bias. The knobs stay at neutral:
a sim is colour and tone only.
Both B&W stocks being mono is now asked once, through isMonochromeBase, so the
colour-only stages (saturation, white balance, R/B fine-tune, chrome, hue
mixer) and the MONO strip label can never half-apply to one of them.
327 lines
9.6 KiB
TypeScript
327 lines
9.6 KiB
TypeScript
import { Recipe, ColorAdjustments } from '../types';
|
|
|
|
// Fallback values for every adjustable knob. Old recipes/sessions persisted in
|
|
// AsyncStorage lack the newer fields — merge this under stored JSON to keep
|
|
// every adjustment well-defined (see storageUtils.getAllRecipes / App session).
|
|
export const DEFAULT_ADJUSTMENTS: ColorAdjustments = {
|
|
exposure: 0,
|
|
contrast: 0,
|
|
saturation: 0,
|
|
temperature: 5500,
|
|
tint: 0,
|
|
wbRed: 0,
|
|
wbBlue: 0,
|
|
highlight: 0,
|
|
shadow: 0,
|
|
denoise: 0,
|
|
clarity: 0,
|
|
grain: 0,
|
|
colorChrome: 'none',
|
|
colorChromeBlue: 'none',
|
|
dynamicRange: 'auto',
|
|
sharpening: 0,
|
|
hslHue: 0,
|
|
hslSat: 0,
|
|
hslLum: 0,
|
|
exposureCompensation: 0,
|
|
};
|
|
|
|
// 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 → the stock's colour and tone it must stand for. This table is the
|
|
// REFERENCE ONLY: the string at the left is just the app's label for the chip
|
|
// (the legacy 'PES' spelling), and what matters is that the sim behind it grades
|
|
// the picture the way the stock at the right does. So a change to a label must
|
|
// never touch the grading, and a change to a grading never renames the chip:
|
|
// PROVIPES Provia / Standard
|
|
// VELVIPES Velvia / Vivid
|
|
// CLASSIC CHRIPES Classic Chrome
|
|
// CLASSIC VIVIDIPES Velvia spliced with Classic Chrome at the blue row
|
|
// CLASSIC NEGIPES Classic Negative (Superia-like)
|
|
// ASTIPES Astia / Soft
|
|
// ETERNIPES Eterna (cinema)
|
|
// ACRIPES Acros (monochrome)
|
|
// B&W HIGH CONTRAST Acros through a filter, ends pushed hard
|
|
// 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;
|
|
baseFilter: Recipe['baseFilter'];
|
|
adjustments: ColorAdjustments;
|
|
}
|
|
|
|
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 } },
|
|
{ id: 'sim-bw-high-contrast', name: 'B&W HIGH CONTRAST', baseFilter: 'mono-high-contrast', 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. 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, with the one
|
|
// brightness step the stock owns folded in through SIM_EXPOSURE_BIAS in
|
|
// colorUtils, never as an `adjustments` value).
|
|
{ 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).
|
|
export function filmSimToRecipe(sim: FilmSim): Recipe {
|
|
return {
|
|
id: sim.id,
|
|
name: sim.name,
|
|
baseFilter: sim.baseFilter,
|
|
adjustments: { ...sim.adjustments },
|
|
frameId: 'none',
|
|
useGeotag: true,
|
|
};
|
|
}
|
|
|
|
export const DEFAULT_RECIPES: Recipe[] = [
|
|
{
|
|
id: 'classic-neg-default',
|
|
name: 'CLASSIC NEGIPES.',
|
|
baseFilter: 'classic-neg',
|
|
adjustments: {
|
|
exposure: 2,
|
|
contrast: 1,
|
|
saturation: -1,
|
|
temperature: 6300,
|
|
tint: 2,
|
|
highlight: -2,
|
|
shadow: 1,
|
|
denoise: 1,
|
|
clarity: 2,
|
|
grain: 4,
|
|
colorChrome: 'weak',
|
|
colorChromeBlue: 'none',
|
|
dynamicRange: 'auto',
|
|
sharpening: 0,
|
|
exposureCompensation: 0,
|
|
},
|
|
frameId: 'none',
|
|
useGeotag: true,
|
|
},
|
|
{
|
|
id: 'velvia-default',
|
|
name: 'VELVIPES VIVID',
|
|
baseFilter: 'velvia',
|
|
adjustments: {
|
|
exposure: 0,
|
|
contrast: 2,
|
|
saturation: 3,
|
|
temperature: 5500,
|
|
tint: 0,
|
|
highlight: 1,
|
|
shadow: -1,
|
|
denoise: 2,
|
|
clarity: 4,
|
|
grain: 1,
|
|
colorChrome: 'strong',
|
|
colorChromeBlue: 'none',
|
|
dynamicRange: 'auto',
|
|
sharpening: 0,
|
|
exposureCompensation: 0,
|
|
},
|
|
frameId: 'none',
|
|
useGeotag: true,
|
|
},
|
|
{
|
|
id: 'provia-default',
|
|
name: 'PROVIPES STD',
|
|
baseFilter: 'provia',
|
|
adjustments: {
|
|
exposure: 1,
|
|
contrast: 0,
|
|
saturation: 0,
|
|
temperature: 5000,
|
|
tint: 0,
|
|
highlight: 0,
|
|
shadow: 0,
|
|
denoise: 2,
|
|
clarity: 1,
|
|
grain: 2,
|
|
colorChrome: 'none',
|
|
colorChromeBlue: 'none',
|
|
dynamicRange: 'auto',
|
|
sharpening: 0,
|
|
exposureCompensation: 0,
|
|
},
|
|
frameId: 'none',
|
|
useGeotag: true,
|
|
},
|
|
{
|
|
id: 'acros-default',
|
|
name: 'ACRIPES MONO',
|
|
baseFilter: 'monochrome',
|
|
adjustments: {
|
|
exposure: 0,
|
|
// Low: the deep black comes from FILM_TONE's toe and the micro-contrast
|
|
// from CLARITY, both of which add separation WITHOUT flattening the grey
|
|
// ramp. The old 4 / highlight 3 / shadow -2 stack was three global
|
|
// contrast moves on top of a matrix that was already clipping — it
|
|
// bought its deep black by throwing away every step below 9% and every
|
|
// one above 91%.
|
|
contrast: 2,
|
|
saturation: 0,
|
|
temperature: 5000,
|
|
tint: 0,
|
|
highlight: 0,
|
|
shadow: 0,
|
|
denoise: 0,
|
|
clarity: 3,
|
|
grain: 5,
|
|
colorChrome: 'none',
|
|
colorChromeBlue: 'none',
|
|
dynamicRange: 'auto',
|
|
sharpening: 0,
|
|
exposureCompensation: 0,
|
|
},
|
|
frameId: 'none',
|
|
useGeotag: true,
|
|
},
|
|
{
|
|
id: 'retro-amber-default',
|
|
name: 'RETRO AMBER 🏮',
|
|
baseFilter: 'classic-neg',
|
|
adjustments: {
|
|
exposure: 3,
|
|
contrast: 2,
|
|
saturation: 1,
|
|
temperature: 7500,
|
|
tint: -2,
|
|
highlight: -3,
|
|
shadow: 2,
|
|
denoise: 3,
|
|
clarity: -2,
|
|
grain: 5,
|
|
colorChrome: 'strong',
|
|
colorChromeBlue: 'none',
|
|
dynamicRange: 'auto',
|
|
sharpening: 0,
|
|
exposureCompensation: 0,
|
|
},
|
|
frameId: 'classic-white',
|
|
useGeotag: true,
|
|
},
|
|
|
|
{
|
|
id: 'cinema-spring-default',
|
|
name: 'CINEMA SPRING',
|
|
baseFilter: 'none',
|
|
adjustments: {
|
|
exposure: 0,
|
|
contrast: 1,
|
|
saturation: 1,
|
|
temperature: 5800,
|
|
tint: 0,
|
|
highlight: -1,
|
|
shadow: 1,
|
|
denoise: 2,
|
|
clarity: 1,
|
|
grain: 3,
|
|
colorChrome: 'none',
|
|
colorChromeBlue: 'none',
|
|
dynamicRange: 'auto',
|
|
sharpening: 0,
|
|
exposureCompensation: 0,
|
|
},
|
|
frameId: 'none',
|
|
cinema: 'spring',
|
|
useGeotag: true,
|
|
},
|
|
{
|
|
id: 'cinema-summer-default',
|
|
name: 'CINEMA SUMMER',
|
|
baseFilter: 'none',
|
|
adjustments: {
|
|
exposure: 0,
|
|
contrast: 2,
|
|
saturation: 2,
|
|
temperature: 6000,
|
|
tint: -1,
|
|
highlight: 1,
|
|
shadow: -1,
|
|
denoise: 1,
|
|
clarity: 2,
|
|
grain: 2,
|
|
colorChrome: 'weak',
|
|
colorChromeBlue: 'none',
|
|
dynamicRange: 'auto',
|
|
sharpening: 0,
|
|
exposureCompensation: 0,
|
|
},
|
|
frameId: 'none',
|
|
cinema: 'summer',
|
|
useGeotag: true,
|
|
},
|
|
{
|
|
id: 'cinema-autumn-default',
|
|
name: 'CINEMA AUTUMN',
|
|
baseFilter: 'none',
|
|
adjustments: {
|
|
exposure: 0,
|
|
contrast: 1,
|
|
saturation: 2,
|
|
temperature: 7200,
|
|
tint: -2,
|
|
highlight: -2,
|
|
shadow: 1,
|
|
denoise: 2,
|
|
clarity: 0,
|
|
grain: 4,
|
|
colorChrome: 'weak',
|
|
colorChromeBlue: 'none',
|
|
dynamicRange: 'auto',
|
|
sharpening: 0,
|
|
exposureCompensation: 0,
|
|
},
|
|
frameId: 'none',
|
|
cinema: 'autumn',
|
|
useGeotag: true,
|
|
},
|
|
{
|
|
id: 'cinema-winter-default',
|
|
name: 'CINEMA WINTER',
|
|
baseFilter: 'none',
|
|
adjustments: {
|
|
exposure: 1,
|
|
contrast: -1,
|
|
saturation: -2,
|
|
temperature: 4500,
|
|
tint: 2,
|
|
highlight: -2,
|
|
shadow: 2,
|
|
denoise: 3,
|
|
clarity: -2,
|
|
grain: 3,
|
|
colorChrome: 'none',
|
|
colorChromeBlue: 'weak',
|
|
dynamicRange: 'auto',
|
|
sharpening: 0,
|
|
exposureCompensation: 0,
|
|
},
|
|
frameId: 'none',
|
|
cinema: 'winter',
|
|
useGeotag: true,
|
|
},
|
|
];
|