fix(sim): rebuild the six film-sim color matrices to match the stock looks

Provia/Velvia/Astia/Classic Chrome/Classic Neg/Acros each get their own
primaries+cross-talk matrix measured against the pipeline, plus a split-tone
stage (shadow/highlight tint uniforms) for Classic Chrome's teal and Classic
Neg's green-cyan shadows vs warm highlights.
This commit is contained in:
2026-09-10 20:57:19 +07:00
parent 93f48537f8
commit 4bb2bda5c8
5 changed files with 139 additions and 53 deletions
+44 -28
View File
@@ -50,44 +50,57 @@ export function getSkiaColorMatrix(baseFilter: BaseFilter, adj: ColorAdjustments
// 2. Base Filters
if (baseFilter === 'classic-neg') {
// Classic Neg: high contrast, desaturated reds/greens, warm/amber cast
// Classic Neg (Superia-like): restrained colour, higher contrast around
// mid-grey, reds a touch deeper. Its signature split tone — green/cyan
// darks drifting to warm brights — cannot live in a linear matrix (one cast
// would hit both ends), so it ships in FILM_TONE (toneShader.ts) instead.
matrix = [
1.15, -0.05, -0.05, 0, 0.05,
-0.05, 1.05, -0.05, 0, 0.02,
-0.08, -0.08, 0.95, 0, -0.02,
0.9961, 0.0773, 0.0078, 0, -0.0406,
0.0226, 1.0298, 0.0076, 0, -0.0300,
0.0224, 0.0750, 0.9520, 0, -0.0247,
0, 0, 0, 1, 0,
];
} else if (baseFilter === 'provia') {
// Provia: standard natural colors, balanced tones
// Provia (Standard): the reference stock — balanced, faithful, easy on the
// eye. Mild saturation and a shallow contrast lift around mid-grey, with no
// channel cast, so neutrals stay neutral and only the chroma deepens.
matrix = [
1.02, 0, 0, 0, 0,
0, 1.02, 0, 0, 0,
0, 0, 1.02, 0, 0,
1.1492, -0.0901, -0.0091, 0, -0.0250,
-0.0268, 1.0859, -0.0091, 0, -0.0250,
-0.0268, -0.0901, 1.1669, 0, -0.0250,
0, 0, 0, 1, 0,
];
} else if (baseFilter === 'velvia') {
// Velvia: vivid colors, high saturation, deep blacks
// Velvia (Vivid): the loud one — saturation pushed very hard onto green,
// blue and red alike, on top of strong contrast with deep, dense darks.
// Saturated reds, skies and greens are meant to ride the clip; that hard
// edge is the look.
matrix = [
1.25, -0.05, -0.05, 0, -0.02,
-0.05, 1.25, -0.05, 0, -0.02,
-0.05, -0.05, 1.25, 0, -0.02,
1.3895, -0.2540, -0.0256, 0, -0.0550,
-0.0772, 1.2355, -0.0261, 0, -0.0661,
-0.0775, -0.2603, 1.4756, 0, -0.0689,
0, 0, 0, 1, 0,
];
} else if (baseFilter === 'classic-chrome') {
// Classic Chrome: muted/deep color with strong tone — desaturated reds and
// greens, slight warm amber in highlights, shadows stay soft (approx).
// Classic Chrome: magazine-reportage look. Saturation is pulled well down
// while the G/B rows are crossed so blue skies lean teal/cyan (green comes
// up towards blue, with blue kept high). The heavy shadow crush that goes
// with it is the `sh` entry in FILM_TONE — a matrix alone cannot deepen one
// end of the curve without dragging the rest.
matrix = [
1.12, -0.03, -0.06, 0, 0.04,
-0.04, 1.0, -0.03, 0, 0.03,
-0.05, -0.06, 0.86, 0, -0.02,
0.7792, 0.2188, 0.0220, 0, -0.0100,
0.0652, 0.7186, 0.2362, 0, -0.0100,
0.0652, 0.3973, 0.5575, 0, -0.0100,
0, 0, 0, 1, 0,
];
} else if (baseFilter === 'astia') {
// Astia: soft, gentle, flattering — low contrast, pastel-leaning colors.
// Astia (Soft): gentler than standard — contrast below neutral (shadows
// lift, highlights ease off) and colour held back, with a faint warm/pink
// bias that flatters skin. The outdoor-portrait stock.
matrix = [
1.0, 0.02, 0.02, 0, 0.01,
0.01, 1.0, 0.02, 0, 0.02,
0.01, 0.01, 1.02, 0, 0.02,
0.9162, 0.0201, 0.0020, 0, 0.0428,
0.0058, 0.9030, 0.0020, 0, 0.0386,
0.0059, 0.0197, 0.8944, 0, 0.0400,
0, 0, 0, 1, 0,
];
} else if (baseFilter === 'eterna') {
@@ -115,14 +128,17 @@ export function getSkiaColorMatrix(baseFilter: BaseFilter, adj: ColorAdjustments
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;
const g = 0.7152;
const b = 0.0722;
// 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;
matrix = [
r, g, b, 0, 0,
r, g, b, 0, 0,
r, g, b, 0, 0,
r, g, b, 0, -0.05,
r, g, b, 0, -0.05,
r, g, b, 0, -0.05,
0, 0, 0, 1, 0,
];
}