From 8f615f6cd7bb4c0d0c1fcad24dc111bc501f9316 Mon Sep 17 00:00:00 2001 From: locphamtran Date: Wed, 16 Sep 2026 17:17:11 +0700 Subject: [PATCH] Give ACRIPES a straight BT.709 ramp and end-only tone --- src/utils/colorUtils.ts | 25 +++++++++++++++---------- src/utils/defaultRecipes.ts | 27 +++++++++++++++++---------- src/utils/toneShader.ts | 10 ++++++++++ 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/utils/colorUtils.ts b/src/utils/colorUtils.ts index 5288efd..4a69a24 100644 --- a/src/utils/colorUtils.ts +++ b/src/utils/colorUtils.ts @@ -172,17 +172,22 @@ export function getSkiaColorMatrix(baseFilter: BaseFilter, adj: ColorAdjustments 0, 0, 0, 1, 0, ]; } else if (baseFilter === 'monochrome') { - // Acros: pure BT.709 grayscale, zero colour leakage. Contrast is a touch - // above neutral and eased around mid-grey, so the darks keep texture - // instead of plugging to black. (Grain/sharpness stay the user's call — - // a film sim here is the colour grade only.) - const r = 0.2339; - const g = 0.7867; - const b = 0.0794; + // Acros: pure BT.709 grayscale, zero colour leakage, and NO gain or offset. + // The weights sum to 1.0 and the offset is 0, so this matrix is a straight + // luminance readout: nothing clips at either end and every grey step the + // sensor recorded survives. (An earlier 1.10x / -0.05 version plugged every + // black below ~5% and blew every white above ~95% — exactly the fine + // graduation this stock is prized for.) The deep black and the highlight + // shoulder live in FILM_TONE (toneShader.ts), where the curve can be shaped + // one end at a time without touching the midtones. (Grain/sharpness stay + // the user's call — a film sim here is the colour grade only.) + const r = 0.2126; + const g = 0.7152; + const b = 0.0722; matrix = [ - r, g, b, 0, -0.05, - r, g, b, 0, -0.05, - r, g, b, 0, -0.05, + r, g, b, 0, 0, + r, g, b, 0, 0, + r, g, b, 0, 0, 0, 0, 0, 1, 0, ]; } diff --git a/src/utils/defaultRecipes.ts b/src/utils/defaultRecipes.ts index f94cbe7..22cb950 100644 --- a/src/utils/defaultRecipes.ts +++ b/src/utils/defaultRecipes.ts @@ -24,11 +24,12 @@ export const DEFAULT_ADJUSTMENTS: ColorAdjustments = { }; // Standard Fuji film simulations. These are the fixed "look" presets shown at -// the front of the PRESETS chip row: tapping one applies the simulation via -// its baseFilter colour matrix ONLY — every other knob resets to its neutral -// default (0 / auto) so the sim is a clean starting point, never a baked -// recipe. Any look beyond the matrix (grain, DR, CC, contrast…) is added by -// the user afterwards. +// 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. export interface FilmSim { id: string; // Recipe-like id used only while the sim is the current selection name: string; @@ -43,7 +44,7 @@ export const FILM_SIMS: FilmSim[] = [ { 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-acros', name: 'ACRIPES', baseFilter: 'monochrome', adjustments: { ...DEFAULT_ADJUSTMENTS, grain: 3 } }, // 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 @@ -150,15 +151,21 @@ export const DEFAULT_RECIPES: Recipe[] = [ baseFilter: 'monochrome', adjustments: { exposure: 0, - contrast: 4, + // 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: 3, - shadow: -2, + highlight: 0, + shadow: 0, denoise: 0, clarity: 3, - grain: 6, + grain: 5, colorChrome: 'none', colorChromeBlue: 'none', dynamicRange: 'auto', diff --git a/src/utils/toneShader.ts b/src/utils/toneShader.ts index b94eea2..2325f36 100644 --- a/src/utils/toneShader.ts +++ b/src/utils/toneShader.ts @@ -189,6 +189,16 @@ export interface ToneUniforms { const FILM_TONE: Partial>> = { 'classic-chrome': { sh: -0.28 }, 'classic-neg': { shT: [-0.018, 0.009, 0.013], hlT: [0.024, 0.008, -0.012] }, + // Acros. A black-and-white stock IS its grey ramp, so this entry only shapes + // the two ENDS and leaves the middle an identity: a smooth shadow toe that + // reaches a true black (no film-base lift, no flat grey wash) and a highlight + // shoulder that stops just short of white instead of clipping a cloud to + // paper. Mid-tones see neither mask, so the ramp keeps every step the matrix + // handed over — which is what 'deep black' costs in a colour stock and does + // not have to cost here. + // Gains are TONE_SKSL's own (sh * 0.34, hl * 0.22), so -0.12 puts the toe at + // ~5% and -0.05 trims the top ~1%. + monochrome: { sh: -0.12, hl: -0.05 }, }; export function getToneUniforms(adj: ColorAdjustments, baseFilter?: BaseFilter): ToneUniforms {