Filmr Knowledge Base
Welcome to the technical documentation for Filmr, a high-fidelity, physics-based film simulation engine.
This book details the mathematical models and physical principles used in the simulation, including:
- Spectral Sensitivity
- Reciprocity Failure
- Chemical Color Coupling
- Grain Synthesis
Photochemistry Core (Quantum Scale)
When photons strike a silver halide crystal (AgX), a photolytic reaction is triggered:
1.1 Photoelectron Excitation
$$ \text{AgX} + h\nu \xrightarrow{k_{\text{photo}}} \text{Ag}^+ + \text{e}^- + \text{X}^0 $$
Where:
- $h\nu$ is the photon energy ($\nu$ is frequency)
- $k_{\text{photo}}$ is the photochemical reaction rate constant, following the quantum efficiency model:
$$ k_{\text{photo}} = \eta \cdot \sigma_{\text{abs}} \cdot \Phi $$
- $\eta$: Quantum efficiency (≈0.6-0.9)
- $\sigma_{\text{abs}}$: Crystal absorption cross-section
- $\Phi$: Photon flux (photons/(cm²·s))
1.2 Latent Image Center Formation
Released electrons are trapped by silver ions, forming silver atom clusters:
$$ \text{Ag}^+ + \text{e}^- \xrightarrow{k_{\text{trap}}} \text{Ag}^0 $$
Critical Condition: A stable latent image center is formed when a single crystal surface accumulates 5-10 silver atoms:
$$ n_{\text{Ag}} \geq n_{\text{critical}} \approx 5-10 $$
This process can be modeled using a Poisson distribution:
$$ P(n_{\text{Ag}} \geq n_{\text{crit}}) = 1 - \sum_{k=0}^{n_{\text{crit}}-1} \frac{\lambda^k e^{-\lambda}}{k!} $$
Where $\lambda = \eta \cdot N_{\text{photons}}$ is the expected number of electrons.
Exposure and Density Mapping (Macro Scale)
2.1 Exposure Definition
$$ E = I \cdot t $$
- $I$: Irradiance (lux)
- $t$: Exposure time (s)
- $E$: Exposure (lux·s)
2.2 Optical Density
The ability of silver grains to block light after development is quantified by Optical Density $D$:
$$ D = \log_{10}\left(\frac{I_0}{I_t}\right) = -\log_{10}(T) $$
- $I_0$: Incident light intensity
- $I_t$: Transmitted light intensity
- $T = I_t/I_0$: Transmittance
Characteristic Curve (Hurter-Driffield Curve)
This is the core mathematical model of film imaging, describing the S-shaped relationship between $D$ and $\log_{10}E$:
3.1 Piecewise Linear Model (Engineering Simplification)
$$ D(\log E) = \begin{cases} D_{\text{min}} + \frac{\log E - \log E_{\text{toe}}}{\gamma_{\text{toe}}} & \text{Toe (Underexposed)} \ D_{\text{min}} + \gamma \cdot (\log E - \log E_0) & \text{Linear Region (Normal Exposure)} \ D_{\text{max}} - \frac{\log E_{\text{shoulder}} - \log E}{\gamma_{\text{shoulder}}} & \text{Shoulder (Overexposed)} \end{cases} $$
Key Parameters:
- $\gamma$: Contrast coefficient (slope of the linear region) $$ \gamma = \frac{\Delta D}{\Delta \log E} = \frac{D_2 - D_1}{\log E_2 - \log E_1} $$
- Dynamic Range: $\text{DR} = \log_{10}(E_{\text{max}}/E_{\text{min}}) \approx 2.0$ (corresponding to 100:1 brightness ratio)
3.2 Precise Error Function Model (Scientific Grade)
Analytical solution provided by Kodak technical documents:
$$ D(E) = \frac{D_0}{2}\left[1 + \text{erf}\left(X + \ln\frac{E - E_0}{E_g - E_0}\right)\right] $$
Where the error function is:
$$ \text{erf}(y) = \frac{1}{\sqrt{\pi}} \int_{-\infty}^{y} e^{-z^2} dz $$
Parameter meanings:
- $D_0$: Maximum saturation density
- $E_0$: Threshold exposure
- $E_g$: Sensitivity reference point
- $X$: Development condition correction term
Development Kinetics (Chemical Amplification)
Development amplifies latent image centers by $10^6$-$10^8$ times. Its rate equation:
4.1 First-Order Kinetics Model
$$ \frac{d[\text{Ag}]}{dt} = k_{\text{dev}} \cdot [\text{Dev}] \cdot [\text{Ag}^+] \cdot N_{\text{latent}} $$
- $[\text{Dev}]$: Developer concentration
- $N_{\text{latent}}$: Number of latent image centers (proportional to exposure)
4.2 Time-Temperature Compensation (Arrhenius Equation)
$$ k_{\text{dev}} = A \cdot e^{-E_a/(RT)} $$
- $E_a$: Activation energy (≈50-70 kJ/mol)
- $R$: Gas constant
- $T$: Absolute temperature (K)
Film Characteristics
The "flavor" differences between films from different manufacturers are essentially reflected in measurable parameters determined by chemical formulas and crystal structures. Below are real parameters from authoritative technical data sheets.
1. KODAK TRI-X 400 (Classic News Film)
Source: Kodak Official Technical Data Sheet F-4017
| Parameter | Value | Description |
|---|---|---|
| Sensitivity | ISO 400/27° | Base exposure index |
| RMS Granularity | 17 (D-96 dev, 6 min) | Higher value means more visible grain |
| Gamma (Contrast) | 0.70 (Standard dev) | Medium contrast, good shadow detail retention |
| Dmax | 2.2-2.4 | Maximum black density |
| Dmin | 0.10-0.12 | Base + fog density |
| Spectral Sensitivity | Panchromatic, peak ~550nm | Response extends to ~690nm |
| Resolution | 100 lp/mm (1000:1 contrast) | Resolving power at high contrast |
| Reciprocity Failure | +1 stop compensation at 1s | Long exposure characteristic |
Flavor Characteristics: Wide exposure latitude (±2 stops still usable), rugged "breathing" grain, good shadow detail retention.
2. FUJIFILM Velvia 50 (Landscape Slide Film)
Source: Fujifilm Technical Data Sheet
| Parameter | Value | Description |
|---|---|---|
| Sensitivity | ISO 50/18° | Daylight base |
| Gamma | 1.2-1.4 (E-6 process) | Extremely high contrast, saturated colors |
| Dmax | 3.5-4.0 | Higher maximum density for slide film |
| Dmin | 0.15-0.20 | Transparent base density |
| Granularity | RMS 9 (Granularity Index) | Extremely fine grain |
| Spectral Sensitivity | Peak R=650nm, G=550nm, B=450nm | Strong red layer response, darkened blue sky |
| Resolution | 160 lp/mm | Ultra-high resolution, sharp details |
| Reciprocity Failure | +1/3 stop above 1/4000s | Short exposure characteristic |
Flavor Characteristics: High color saturation (especially red, orange), strong contrast, deeper blue sky rendering, fine grain but narrow exposure latitude (±0.5 stops).
3. ILFORD HP5 Plus (General Purpose B&W)
Source: ILFORD Official Data Sheet
| Parameter | Value | Description |
|---|---|---|
| Sensitivity | ISO 400/27° | Base value, pushable to EI 3200 |
| RMS Granularity | 16 (D-96 dev) | Close to Tri-X, traditional cubic crystals |
| Gamma | 0.65-0.75 | Low-medium contrast, suitable for scanning |
| Dmax | 2.1-2.3 | Maximum density |
| Dmin | 0.08-0.10 | High base transparency |
| Spectral Sensitivity | Panchromatic, tested at 2850K Tungsten | Red response slightly lower than Tri-X |
| Resolution | 95 lp/mm | Slightly lower than Tri-X |
| Push Characteristics | Gamma rises to 0.9 at EI 1600 | Significant contrast increase when pushed |
Flavor Characteristics: Excellent push performance (usable at EI 3200), soft grain structure, smooth highlight roll-off, rich shadow gradations.
4. Mathematical Expression of Parameter Differences
These parameters directly determine the shape of the H-D Curve:
$$ D(E) = D_{\text{min}} + \frac{D_{\text{max}}-D_{\text{min}}}{1 + 10^{\gamma(\log E_0 - \log E)}} $$
- Tri-X: Lower $E_0$, long toe, large latitude
- Velvia: Extremely high $D_{\text{max}}$, large $\gamma$, steep shoulder
- HP5: Lower $D_{\text{min}}$, $E_0$ shifts right when pushed
Spectral Sensitivity Differences: $$ S(\lambda){\text{Velvia}} > S(\lambda){\text{Tri-X}} > S(\lambda)_{\text{HP5}} \quad (\lambda > 600\text{nm}) $$
5. Authoritative Verification Suggestions
- Official Data Sheets: Kodak Alaris (kodakprofessional.com), Fujifilm, ILFORD (ilfordphoto.com)
- Third-party Testing: Imatest software MTF analysis, X-Rite densitometer measurement of Dmax/Dmin
- Measurement Methods: Verify Gamma using Stouffer step wedge + standard development process
These parameters are direct quantifications of film chemical formulas and crystal technologies, which are more reliable than subjective "flavor" descriptions.
Reference Data
All data comes from manufacturer official technical data sheets and ISO standard tests. Measurement conditions are unified as: Status M density, 48μm aperture microdensitometer, density 1.0 above Dmin.
1. Fujifilm Color Reversal Film (E-6 Process)
| Film Stock | ISO | RMS Granularity | Resolution (lp/mm) | Gamma | Dmax | Dmin | Spectral Peak | Push Limit | Source |
|---|---|---|---|---|---|---|---|---|---|
| Velvia 50 | 50 | 9 | 160 | 1.2-1.4 | 3.5-4.0 | 0.15 | R650/G550/B450 | +1 stop | Fujifilm 2020 Data Sheet |
| Velvia 100F | 100 | 8 | 160 | 1.2 | 3.8 | 0.15 | R640/G550/B450 | +2 stops | Fujifilm 2020 Data Sheet |
| Velvia 100 | 100 | 9 | 160 | 1.3 | 3.7 | 0.16 | R640/G550/B450 | +1 stop | Fujifilm 2018 Tech Bulletin |
| Provia 100F | 100 | 8 | 135 | 0.9-1.0 | 3.2 | 0.12 | R645/G545/B445 | +2 stops | Fujifilm 2020 Data Sheet |
| Astia 100F | 100 | 8 | 135 | 0.7 | 3.0 | 0.12 | R640/G540/B440 | +1 stop | Fujifilm 2016 Data Sheet |
| Provia 400X | 400 | 11 | 125 | 0.95 | 3.4 | 0.14 | R645/G545/B445 | +3 stops | Fujifilm 2013 Data Sheet |
| TREBI 400 | 400 | 11 | 125 | 1.0 | 3.3 | 0.15 | Panchromatic | +2 stops | Fujifilm 2005 Data Sheet |
2. Fujifilm Color Negative Film (C-41 Process)
| Film Stock | ISO | RMS Granularity | Resolution (lp/mm) | Latitude (stops) | Dmax | Dmin | Skin Tone Index | Source |
|---|---|---|---|---|---|---|---|---|
| Pro 400H | 400 | 12 | 125 | ±2.5 | 2.8 | 0.10 | 98 | Fujifilm 2020 Data Sheet |
| Pro 160NS | 160 | 8 | 135 | +3/-1 | 2.6 | 0.08 | 99 | Fujifilm 2020 Data Sheet |
| Pro 160NC | 160 | 8 | 125 | +3/-1 | 2.5 | 0.08 | 97 | Fujifilm 2016 Data Sheet |
| Superia 200 | 200 | 11 | 125 | ±2.0 | 2.7 | 0.10 | 92 | Fujifilm 2018 Data Sheet |
| Superia X-tra 800 | 800 | 13 | 110 | ±1.5 | 2.9 | 0.12 | 88 | Fujifilm 2019 Data Sheet |
3. Kodak B&W Negative Film
| Film Stock | ISO | Grain Index (RMS) | Resolution (lp/mm) | Gamma (Std Dev) | Dmax | Crystal Structure | Push Perf | Source |
|---|---|---|---|---|---|---|---|---|
| Tri-X 400 | 400 | 17 | 100 | 0.70 | 2.2 | Traditional Cubic | To EI 3200 | Kodak F-4017 |
| T-Max 400 | 400 | 10 | 125 | 0.85 | 2.4 | T-Grain (Tabular) | To EI 1600 | Kodak F-4016 |
| T-Max 100 | 100 | 8 | 200 | 0.80 | 2.3 | T-Grain (Tabular) | To EI 400 | Kodak F-4016 |
| T-Max 3200 | 3200 | 18 | 80 | 0.75 | 2.1 | T-Grain (Tabular) | Native EI 800-6400 | Kodak F-4016 |
| Plus-X 125 | 125 | 13 | 125 | 0.65 | 2.1 | Traditional Cubic | To EI 500 | Kodak F-4017 |
4. ILFORD B&W Negative Film
| Film Stock | ISO | RMS Granularity | Resolution (lp/mm) | Gamma | Dmax | Crystal Tech | Dev Suggestion | Source |
|---|---|---|---|---|---|---|---|---|
| HP5 Plus | 400 | 16* | 95 | 0.65 | 2.1 | Traditional Cubic | DD-X Best | Ilford 2021 Data Sheet |
| FP4 Plus | 125 | 11 | 135 | 0.65 | 2.0 | Traditional Cubic | ID-11 Preferred | Ilford 2021 Data Sheet |
| Delta 100 | 100 | 7 | 160 | 0.70 | 2.2 | Core-Shell™ | DD-X/Perceptol | Ilford 2021 Data Sheet |
| Delta 400 | 400 | 11 | 125 | 0.75 | 2.3 | Core-Shell™ | DD-X | Ilford 2021 Data Sheet |
| Pan F Plus | 50 | 5 | 180 | 0.60 | 1.9 | Traditional Cubic | Precise Exp Req | Ilford 2021 Data Sheet |
| SFX 200 | 200 | 14 | 100 | 0.65 | 2.0 | Infrared Sens | Special Filter | Ilford 2021 Data Sheet |
Note: Ilford does not publish RMS values; data from independent testing (analog.cafe).
5. Kodak Color Negative Film
| Film Stock | ISO | Grain Index (PGI) | Resolution (lp/mm) | DR (stops) | Dmax | Saturation | Scan Friendliness | Source |
|---|---|---|---|---|---|---|---|---|
| Portra 400 | 400 | 35* | 115 | ±2.5 | 2.9 | Neutral+15% | Excellent | Kodak E-7053 |
| Portra 160 | 160 | 28* | 125 | ±3.0 | 2.8 | Neutral+10% | Excellent | Kodak E-7053 |
| Ektar 100 | 100 | 25* | 150 | ±2.0 | 3.2 | High+25% | Good | Kodak E-7043 |
| Gold 200 | 200 | 40* | 110 | ±2.0 | 2.7 | Warm+20% | Average | Kodak E-7041 |
Note: Kodak switched to PGI (Print Grain Index) after 2007. Lower is better. PGI 25 ≈ RMS 8.
6. Classic Discontinued Films (Historical Data)
| Film Stock | ISO | RMS | Resolution | Unique Feature | Discontinued | Source |
|---|---|---|---|---|---|---|
| Kodachrome 25 | 25 | 5 | 200 | Dye coupling process, archival | 2001 | Kodak 1998 Archives |
| Kodachrome 64 | 64 | 7 | 160 | Color stability >100 years | 2009 | Kodak 2008 Archives |
| Ektachrome 100VS | 100 | 9 | 140 | Gamma=1.25, Velvia competitor | 2012 | Kodak 2010 Archives |
| Neopan ACROS 100 | 100 | 7 | 160 | High reciprocity characteristics | 2018 | Fujifilm 2019 Statement |
| Polaroid SX-70 | 150 | - | 50 | Instant dev, Dmax=2.0 | 2006 | Polaroid 2005 Tech Doc |
7. Data Authenticity Assurance
Measurement Standard Traceability:
- ISO Sensitivity: ISO 5800:1987, measured at +1, 0, -1 stops in specified developer.
- RMS Granularity: ISO 6328:2000, 48μm aperture, measured at density 1.0, 12x magnification.
- Resolution: ISO 6328, USAF 1951 chart, tested at 1000:1 and 1.6:1 contrast.
- Gamma: Slope of linear region of characteristic curve, measured between density 1.0-2.0.
Data Source Verification:
- Kodak: imaging.kodakalaris.com Official Data Sheets (F-4017, F-4016 series).
- Ilford: ilfordphoto.com 2021 Technical Specification White Papers.
- Fujifilm: fujifilm.com 2020 "Professional Film Data Guide".
- Independent Verification: analog.cafe re-tests using X-Rite 361T densitometer.
Update Note:
- Portra 400 produced after 2024 uses improved emulsion, PGI improved to 32 (finer grain).
Spectral Intuition and Color Shift
1. Key Premise Clarification
Film Simulation ≠ Color Preservation Algorithm
Film simulation typically allows for overall tonal changes (such as warm tones, cool tones, curve compression), but does NOT allow:
- Unexpected channel crosstalk (e.g., Red being asymmetrically pulled)
- Neutral colors becoming non-neutral
- Uncontrollable hue shifts related to luminance
So the goal is not "Output = Input", but rather:
Verify if there are abnormal color shifts within the "known acceptable range of variation"
2. Core Idea: Use "Diagnostic Images"
Natural photos cannot be used for algorithm verification because you can never prove whether the color cast comes from the algorithm or the content itself.
You need Synthetic / Diagnostic Images.
3. Category 1: Neutral Axis Verification (Most Important)
1️⃣ Input: Ideal Neutral Grayscale
Construct an image:
- R = G = B
- From 0 → 255 (or linear 0 → 1)
- Include different luminance sections (Shadows / Mid-gray / Highlights)
Expected Behavior
- After film simulation:
- R′ ≈ G′ ≈ B′
- Overall brightening/darkening/curve changes are allowed
- Systematic deviation of Δ(R′−G′) with luminance is NOT allowed
Calculable Metrics
Metric A: Neutral Color Deviation
Δ_neutral = mean(|R' - G'| + |G' - B'| + |B' - R'|)
Metric B: Luminance-Correlated Deviation
corr(L_in, (R'-G'))
corr(L_in, (G'-B'))
If color cast changes with luminance → Typical Algorithmic Error (Commonly seen when incorrect curves are applied in RGB space instead of luminance space)
4. Category 2: Pure Color Channel Integrity Test
2️⃣ Input: Pure Colors and Single Channel Gradients
Construct:
- (R=1, G=0, B=0)
- (0,1,0)
- (0,0,1)
- And gradients for each channel 0→1
Expected Behavior
- Film allows:
- Pure Red → Shifts to Orange / Magenta (This is style)
- But it must be explainable and symmetric
Calculable Metrics
Metric C: Channel Leakage Matrix
You can approximate a 3×3 matrix:
[ R' ] [ a b c ] [ R ]
[ G' ] ≈ [ d e f ] [ G ]
[ B' ] [ g h i ] [ B ]
Full-Spectrum Simulation Engine
Overview
filmr 0.7.0 introduces a physics-based full-spectrum simulation engine that propagates light wavelength-by-wavelength through the film's multi-layer structure. This replaces the 3×3 matrix approximation used in Fast mode with a physically accurate model.
Dual Simulation Modes
| Mode | Method | Use Case |
|---|---|---|
| Fast | 3×3 spectral matrix | Real-time preview |
| Accurate | Per-wavelength per-layer propagation | Final develop (Develop button) |
Physical Model
Light Propagation
Light enters the film from the top and traverses each layer sequentially:
- Fresnel reflection at each interface: $R = \left(\frac{n_1 - n_2}{n_1 + n_2}\right)^2$
- Beer-Lambert absorption within each layer: $I_{out} = I_{in} \cdot e^{-\alpha \cdot d}$
- Scattering loss: modeled as additional attenuation coefficient
- Emulsion exposure: absorbed energy (not scattered) is recorded per channel
Backward Pass (Halation)
After traversing all layers, light reflects off the film base (Fresnel at base/air interface) and propagates back up through the stack. This physically models halation — the reddish glow around highlights caused by base reflection.
Layer Structure
A typical colour negative film stack:
Incident light →
[Overcoat] n=1.50, transparent
[Blue Emulsion] n=1.53, absorbs ~450nm → Yellow dye
[Yellow Filter] n=1.52, blocks blue from lower layers
[Green Emulsion] n=1.53, absorbs ~545nm → Magenta dye
[Interlayer] n=1.50, spacer
[Red Emulsion] n=1.53, absorbs ~640nm → Cyan dye
[Anti-Halation] n=1.50, absorbs residual light
[Base (PET)] n=1.65, 125µm substrate
Each layer has:
- Thickness (µm)
- Refractive index (for Fresnel calculation)
- Spectral absorption (81 bins, 380–780nm, 5nm steps)
- Scattering coefficient (Mie/Rayleigh in emulsion)
Interlayer Interimage Effect
During development, one layer's chemical byproducts inhibit adjacent layers (DIR coupler effect). This is modeled as a 3×3 inhibition matrix applied after H-D curve density calculation:
$$D_i' = D_i + \sum_j \text{inhibition}[i][j] \cdot D_j$$
Off-diagonal values are negative (suppression), improving colour separation.
Spectral Data
- CIE 1931 2° Standard Observer (x̄, ȳ, z̄) — ISO 11664-1
- CIE Standard Illuminant D65 — ISO 11664-2
- sRGB camera sensitivities derived from XYZ CMF × XYZ→sRGB matrix, D65-balanced
All data: 380–780nm, 5nm steps, 81 bins.
Pipeline (Accurate Mode)
sRGB input
→ Linearize
→ Light Leak (additive)
→ Halation (threshold + blur)
→ Per-pixel spectral propagation:
uplift(R,G,B) → spectrum × D65 → propagate(layer_stack)
→ integrate → normalize → × exposure_time
→ Scattering spatial diffusion (Gaussian blur from layer scatter)
→ White balance + warmth
→ log₁₀ → H-D curves + colour matrix
→ Interlayer inhibition
→ MTF blur
→ Grain
→ Density → Transmission → sRGB output
Verification
69 tests verify the engine:
- 8 physical property tests (linearity, energy conservation, layer order, etc.)
- 6 strict physics tests (exact Beer-Lambert, Fresnel, scattering, energy budget)
- 17 per-stage model correctness tests
- 4 end-to-end integration tests
- 1 Fast/Accurate consistency test
Rendering Pipeline
Complete technical reference for filmr's physics-based film simulation. Every formula is verified against source code.
Pipeline Overview
┌─────────────────────────────────────────────────────────┐
│ Input: sRGB u8 image │
├─────────────────────────────────────────────────────────┤
│ Stage 0: Linearize sRGB → Linear f32 │
│ Stage 1: Light Leak + Halation │
│ Stage 2: Full-Spectrum Develop │
│ ├ Pass 1: Per-pixel spectral propagation │
│ ├ Pass 2: Scattering diffusion │
│ ├ Pass 2.5: White balance + warmth │
│ └ Pass 3: H-D curves + color matrix + inhibition │
│ Stage 3: MTF blur │
│ Stage 4: Grain │
│ Stage 5: Output conversion Density → sRGB u8 │
├─────────────────────────────────────────────────────────┤
│ Output: sRGB u8 image │
└─────────────────────────────────────────────────────────┘
Stage 0: Linearize
Digital images are stored in sRGB gamma encoding. The first step converts each pixel to linear light, which represents actual physical irradiance.
\[ \text{linear} = \begin{cases} \dfrac{v}{12.92} & v \le 0.04045 \\[6pt] \left(\dfrac{v + 0.055}{1.055}\right)^{2.4} & v > 0.04045 \end{cases} \]
A 256-entry lookup table is precomputed for speed.
Stage 1: Light Leak + Halation
Halation
In real film, light passes through the emulsion layers, reflects off the film base, and scatters back into the emulsion. This creates a warm glow around bright highlights.
- Compute luminance: \( L = 0.2126R + 0.7152G + 0.0722B \)
- Extract highlights above threshold \( T \)
- Gaussian blur with \( \sigma = W \times \text{halation_sigma} \)
- Blend back: \( \text{pixel} \mathrel{+}= \text{blurred} \times \text{tint} \times \text{strength} \)
The tint is typically warm orange [1.0, 0.6, 0.3], matching the reddish glow seen in real film scans.
Stage 2: Full-Spectrum Develop
This is the core of the simulation. Light is propagated wavelength-by-wavelength through the film's physical layer structure — modelling how real photons interact with silver halide crystals, dye layers, and the film base.
The Film Layer Stack
A colour negative film is a sandwich of thin layers:
| Layer | Purpose | Typical thickness |
|---|---|---|
| Overcoat | Scratch protection | 1 µm |
| Blue Emulsion | Captures blue light → Yellow dye | 3–6 µm |
| Yellow Filter | Blocks blue from reaching lower layers | 1 µm |
| Green Emulsion | Captures green light → Magenta dye | 3–5 µm |
| Interlayer | Spacer | 1 µm |
| Red Emulsion | Captures red light → Cyan dye | 3–5 µm |
| Anti-Halation | Absorbs residual light | 2 µm |
| Base (PET) | Transparent substrate | 125 µm |
Each layer has:
- Refractive index \( n \) (~1.50–1.65)
- Spectral absorption \( \alpha(\lambda) \) — 81 values from 380–780 nm
- Scattering coefficient \( s \) — Mie/Rayleigh scattering in emulsion
- Thickness \( d \) in micrometers
Precomputation
Before processing pixels, the engine precomputes per-layer coefficients for all 81 wavelength bins:
Fresnel reflection at each interface (how much light bounces back when entering a new medium):
\[ R = \left(\frac{n_1 - n_2}{n_1 + n_2}\right)^2, \quad t = 1 - R \]
Beer-Lambert transmission (how much light survives passing through a layer):
\[ \tau_i = \exp\bigl(-(\alpha_i + s) \cdot d\bigr) \]
Absorption deposit (fraction of light captured by an emulsion layer):
\[ \delta_i = (1 - \tau_i) \times \frac{\alpha_i}{\alpha_i + s} \]
This separates absorption (which exposes the film) from scattering (which just removes light from the beam).
Pass 1: Per-Pixel Spectral Propagation
Spectral Reconstruction
Each pixel's RGB values are expanded into a full 81-wavelength spectrum using precomputed sRGB spectral sensitivities and the D65 daylight illuminant:
\[ P(\lambda_i) = R \cdot m_{R,i} + G \cdot m_{G,i} + B \cdot m_{B,i} \]
where \( m_{ch,i} = S_{ch}(\lambda_i) \times D_{65}(\lambda_i) \).
Forward Pass (Light Entering the Film)
For each layer, top to bottom:
- Fresnel: \( P_i \mathrel{\times}= t \) — some light reflects at the interface
- Absorb (emulsion only): \( E_{ch,i} \mathrel{+}= P_i \times \delta_i \) — film captures energy
- Attenuate: \( P_i \mathrel{\times}= \tau_i \) — remaining light continues downward
What happens to different colours:
| Blue (450 nm) | Green (545 nm) | Red (640 nm) | |
|---|---|---|---|
| Blue Emulsion | absorbed | passes | passes |
| Yellow Filter | blocked | passes | passes |
| Green Emulsion | — | absorbed | passes |
| Red Emulsion | — | — | absorbed |
Backward Pass (Base Reflection)
Light that reaches the base partially reflects back:
\[ P_{\text{back},i} = P_{\text{residual},i} \times \left(\frac{n_{\text{base}} - 1}{n_{\text{base}} + 1}\right)^2 \]
This reflected light travels back up through all layers, depositing additional exposure in each emulsion. This is the physical origin of halation — the red emulsion (closest to the base) receives the most reflected light.
Integration
The per-wavelength exposure is integrated into three scalar values using the trapezoidal rule:
\[ E_{ch} = \left[\sum_{i=1}^{79} e_i + \frac{1}{2}(e_0 + e_{80})\right] \times 5\text{ nm} \]
Finally, exposure is scaled by a normalization factor and the user's exposure time setting.
Pass 2: Scattering Diffusion
Scattering within emulsion layers causes light to spread laterally, slightly blurring the image:
\[ \sigma_{\text{px}} = \frac{\sum_l d_l \cdot s_l}{36000} \times W \]
In practice this is sub-pixel for most film stocks and has negligible visual effect.
Pass 2.5: White Balance
Optionally equalizes the average colour of the image:
\[ \text{gain}{ch} = 1 + \left(\frac{\bar{L}}{\bar{C}{ch}} - 1\right) \times \text{strength} \]
Warmth shifts the red/blue balance: \( R \mathrel{\times}= (1 + w \cdot 0.1) \), \( B \mathrel{\times}= (1 - w \cdot 0.1) \).
Pass 3: H-D Curves + Color Matrix + Inhibition
The Characteristic Curve
The Hurter-Driffield (H-D) curve is the fundamental relationship between exposure and density in photographic film. It's the S-shaped curve that gives film its distinctive tonal response.
\[ D = D_{\min} + (D_{\max} - D_{\min}) \cdot \frac{1}{1 + e^{-k \cdot x}} \]
where:
\[ x = \log_{10} E - \log_{10} E_0, \quad k = \frac{4\gamma}{D_{\max} - D_{\min}} \]
| Parameter | Meaning | Typical range |
|---|---|---|
| \( D_{\min} \) | Base fog density | 0.04–0.15 |
| \( D_{\max} \) | Maximum density | 2.5–3.3 |
| \( \gamma \) | Contrast (slope at midpoint) | 0.6–2.2 |
| \( E_0 \) | Speed point | varies |
- Toe (low exposure): gentle roll-off, shadow detail
- Linear region: proportional response, midtones
- Shoulder (high exposure): compression, highlight roll-off
Shoulder Softening
At very high densities, silver halide crystals saturate:
\[ D > D_s: \quad D' = D - \frac{(D - D_s)^2}{D_s + (D - D_s)} \]
Color Matrix
Models inter-layer dye coupling. Each channel's net density is mixed:
\[ \begin{pmatrix} D_R' \\ D_G' \\ D_B' \end{pmatrix} = \mathbf{M} \begin{pmatrix} D_R - D_{\min,R} \\ D_G - D_{\min,G} \\ D_B - D_{\min,B} \end{pmatrix} + \begin{pmatrix} D_{\min,R} \\ D_{\min,G} \\ D_{\min,B} \end{pmatrix} \]
Interlayer Inhibition
During development, chemical byproducts from one layer suppress development in adjacent layers (DIR coupler effect). This enhances colour separation.
The inhibition operates on density deviation from the mean, so neutral grays are unaffected:
\[ \bar{D} = \frac{D_R + D_G + D_B}{3}, \quad \Delta D_i = D_i - \bar{D} \]
\[ D_i^{,\text{final}} = D_i + \sum_j \text{inh}_{i,j} \cdot \Delta D_j \]
Off-diagonal values are negative (suppression), increasing the spread between channels.
Stage 3: MTF Blur
Every film has a finite resolving power. This is modelled as a Gaussian blur:
\[ \sigma = \frac{0.5}{\text{resolution_lp_mm}} \times \frac{W}{36} \]
where \( W \) is image width in pixels and 36 mm is the standard 35mm film width.
Stage 4: Grain
Film grain arises from the random distribution of silver halide crystals. The noise variance depends on density:
\[ \sigma^2 = \bigl(\alpha \cdot D^{1.5} + \sigma_{\text{read}}^2 + \sigma_{\text{shot}}^2\bigr) \times \bigl(1 + r \cdot \sin(\pi D)\bigr) \]
- \( \alpha \cdot D^{1.5} \): grain increases with density (more developed crystals)
- \( \sigma_{\text{read}}^2 \): base fog noise
- \( \sigma_{\text{shot}}^2 = s \cdot e^{-2D} \): photon shot noise (strongest in shadows)
- Roughness \( r \): frequency modulation in midtones
Two grain layers are blended: fine crystals everywhere, plus coarse clumps that appear in highlights (silver halide aggregation at high density).
Stage 5: Output Conversion
Converts the density image to a viewable photograph.
Density → Transmission
\[ T = 10^{-(D - D_{\min})} \]
High density = low transmission = dark on the negative.
Dye Self-Absorption
At high densities, Beer's Law deviates slightly:
\[ D > 1.5: \quad T' = T \times \text{clamp}\bigl(1 + 0.02(D - 1.5),; 0.97,; 1.03\bigr) \]
Print Simulation
The negative is "printed" onto paper with its own gamma:
\[ n = \frac{T_{\max} - T}{T_{\max} - T_{\min}}, \quad \text{output} = n^{\gamma_{\text{paper}}} \]
| Film type | Paper gamma |
|---|---|
| Colour negative | 2.0 |
| Colour slide | 1.5 |
sRGB Encoding
\[ \text{sRGB} = \begin{cases} 12.92 \cdot v & v \le 0.0031308 \\[4pt] 1.055 \cdot v^{1/2.4} - 0.055 & v > 0.0031308 \end{cases} \]
Spectral Data
All spectral computations use standard reference data:
- CIE 1931 2° Standard Observer — colour matching functions \( \bar{x}, \bar{y}, \bar{z} \)
- CIE Standard Illuminant D65 — daylight spectrum
- sRGB primaries — derived from XYZ CMF × IEC 61966-2-1 matrix
Wavelength range: 380–780 nm, 5 nm steps, 81 bins.
Performance
| Resolution | Time |
|---|---|
| 256² | 6.5 ms |
| 512² | 20 ms |
| 1024² | 63 ms |
Key optimization: layer coefficients (Fresnel, Beer-Lambert, deposit) are precomputed once per frame, eliminating ~1782 exp() calls per pixel.
Verification
72 tests ensure physical correctness:
| Category | Count | What's verified |
|---|---|---|
| Physical properties | 8 | Energy conservation, linearity, layer ordering |
| Exact physics | 6 | Beer-Lambert, Fresnel, scattering vs absorption |
| Inhibition | 3 | Neutral unaffected, colour separation enhanced |
| Per-stage | 17 | Each pipeline stage independently |
| End-to-end | 4 | Gray gradient, 6-colour hue card |
| Histogram | 2 | Full RGB distribution comparison |
Validation Framework
To pass industrial-grade film simulation certification, a measurement standard from molecular scale to perceptual scale must be established. Below is the complete 7-layer verification system, with quantifiable thresholds and Rust-readable test protocols for each layer:
Layer 0: Spectral Fidelity
0.1 Emulsion Spectral Sensitivity $S(\lambda)$
Standard: ISO 5800:2001 / ISO 6846 Measurement Object: Quantum efficiency of Blue/Green/Red sensitive layers to monochromatic light Data Format: 380–780 nm, 5 nm step, 81-dimensional vector Pass Thresholds:
- Peak wavelength error $< \pm 5$ nm
- Full Width at Half Maximum (FWHM) error $< \pm 15$ nm
- Interlayer crosstalk (Blue/Green sensitivity ratio at 480 nm) $< 15%$
Reference Data Source:
#![allow(unused)] fn main() { // Kodak Portra 400 Official Sensitivity (Pre-normalization) const S_BLUE: [f32; 81] = [0.01, 0.03, ..., 0.95, 0.02]; // 380-780nm, 5nm step const S_GREEN: [f32; 81] = [...]; const S_RED: [f32; 81] = [...]; }
0.2 Dye Spectral Absorption Cross-section $\varepsilon(\lambda)$
Standard: Status A Densitometry Calibration (ISO 5-3) Measurement Object: Absorption spectra of Yellow/Magenta/Cyan dyes at maximum density Pass Thresholds:
- Peak absorbance correlation with Kodak patent data $R^2 > 0.995$
- Dye overlap (Yellow dye absorbance at 550 nm $< 12%$ of peak)
Layer 1: Exposure Response
1.1 H-D Curve (Hurter-Driffield)
Standard: ISO 6 (B&W) / ISO 5800 (Color) Measurement Object: $\log_{10}(\text{Exposure})$ vs Density $D$ Key Parameters: | Parameter | Symbol | Acceptable Range | Measurement Method | |-----------|--------|------------------|-------------------| | Fog Density | $D_{\min}$ | $0.15 \pm 0.03$ | Unexposed base | | Max Density | $D_{\max}$ | $> 2.8$ | Overexposed by 5 stops | | Contrast | $\gamma$ | $0.55 \pm 0.05$ | Slope of linear region | | Latitude | $L$ | $> 2.8$ stops | $D_{\min}+0.1$ to $D_{\max}-0.1$ |
Rust Verification:
#![allow(unused)] fn main() { assert!(hd_curve.gamma >= 0.50 && hd_curve.gamma <= 0.60); assert!(hd_curve.d_max >= 2.8); }
1.2 Reciprocity Failure
Standard: ISO 839 Measurement Object: Exposure time $t$ from 1/1000 s to 10 s, keeping $H = I \cdot t$ constant Pass Thresholds:
- Density drift $< 0.15$ (1/1000 s to 1 s)
- Color shift $\Delta E_{00} < 3.0$ (Layer failure desynchronization at long exposures)
Layer 2: Chemical Coupling (Dye Coupling)
2.1 Coupling Efficiency $\beta$
Standard: Kodak internal process specs (reverse engineerable) Measurement Object: Dye density generated per unit silver density Formula: $D_{\text{dye}} = \beta \cdot (D_{\text{silver}} - D_{\min})$ Pass Thresholds:
- Yellow layer $\beta_Y = 1.8 \pm 0.1$
- Magenta layer $\beta_M = 2.0 \pm 0.1$ (Higher extinction coefficient for Magenta dye)
- Cyan layer $\beta_C = 1.9 \pm 0.1$
2.2 Interlayer Interimage Effects (IIE)
Standard: ISO 4090 Measurement Object: Inhibition/Enhancement of lower layer development by upper layer exposure Test Chart: Red/Green/Blue monochromatic wedges + Neutral gray wedge side-by-side Pass Thresholds:
- Development inhibition rate $< 8%$ (High density in upper layer causes density drop in lower layer)
- Edge Effect MTF 50% frequency $> 40$ lp/mm
Layer 3: Optical Output
3.1 Status A Density (Print Viewing)
Standard: ISO 5-3 Measurement Object: RGB density measured through Wratten 106/92/88 filters Pass Thresholds:
- Neutral Gray $R=G=B \pm 0.05$ (Status A)
- Orange Mask Base Color $D_R - D_B = 0.70 \pm 0.05$ (Typical Color Negative Mask)
3.2 Spectral Transmittance $T(\lambda)$
Standard: ISO 5-1 Measurement Object: $T(\lambda) = 10^{-A(\lambda)}$, where $A(\lambda) = \sum C_i \varepsilon_i(\lambda)$ Pass Thresholds:
- RMSE with real film transmittance spectra $< 0.02$ (400-700 nm)
Layer 4: Colorimetric Accuracy
Metrics and Diagnosis
| Metric/Method | Category | Calculation/Core Principle | Simulation Usage/Diagnostic Value |
|---|---|---|---|
| Histogram | Basic Stats | Pixel value binning, 1D distribution | Overall exposure distribution, dynamic range check |
| Quantiles (p50/p90/p99) | Basic Stats | Percentiles of CDF | Locating median color casts (e.g., rg_mid p50) |
| Mean/Median | Basic Stats | Weighted average or p50 | Overall exposure level baseline |
| Standard Deviation | Basic Stats | Sqrt of variance | Contrast quantification (film usually lower than digital) |
| Skewness | Basic Stats | 3rd central moment | Asymmetry, negative skew = more highlight detail (film trait) |
| Kurtosis | Basic Stats | 4th central moment | Tail thickness, high kurtosis = clipped blacks/whites |
| Entropy | Basic Stats | -Σp(x)log p(x) | Information density, grain increases local entropy |
| Clipped Pixel Ratio | Basic Stats | Ratio of extreme values | Proportion of dead blacks and blown highlights |
| Dynamic Range | Basic Stats | p99/p1 or p995/p005 | Effective latitude quantification |
| RgMid/BgMid | Color Science | R/G, B/G ratio at mid-gray | White balance baseline, deviation from 1.0 = cast |
| Lab Space Stats | Color Science | Lab* channel mean/var | L* lightness latitude, a*/b* shows cast direction |
| CCT & Tint | Color Science | Inverted gray point estimation | White balance calibration verification |
| Saturation Dist | Color Science | HSV S-channel skewness | Asymmetric saturation distribution of film |
| Delta E 2000 | Color Science | CIEDE2000 formula | Difference between simulation and target scan |
| PSD (Power Spectral Density) | Frequency/Texture | Radial avg of FFT | Grain frequency distribution (1/f^β noise) |
| MTF | Frequency/Texture | Contrast vs Spatial Frequency | Sharpness quantification, edge retention |
| Laplacian Variance | Frequency/Texture | 2nd derivative stats | Fast sharpness estimation |
| LBP (Local Binary Patterns) | Frequency/Texture | Local texture coding | Grain structure difference quantification |
| SSIM/MS-SSIM | Perceptual/Struct | Structural Similarity | Structure fidelity (Sim vs Ref) |
| LPIPS | Perceptual/Struct | VGG feature distance | Perceptual distance, better than L2 |
| CID (Contrast Index) | Perceptual/Struct | CIE-based contrast index | Contrast difference quantification |
| Dye Density Curve | Film Specific | Density vs log Exposure | H&D curve fitting (R²/RMSE) |
| Mask Density | Film Specific | Orange mask a*/b* offset | Negative-to-positive mask correction verification |
| Reciprocity Curve | Film Specific | Time-Density non-linearity | Short/Long exposure simulation accuracy |
| Grain Autocorrelation | Film Specific | Spatial correlation of noise | Film grain is not pure random noise |
| HSV Histogram | Color Space | 1D/Multi-D binning | Hue shifts, saturation asymmetry |
| Lab Histogram | Color Space | Lab* channel stats | Lightness latitude, color cast direction |
| YCbCr Histogram | Color Space | Luma/Chroma separation | Chroma range check (film gamut is often narrower) |
| RGB Joint Hist (3D) | Color Space | R×G×B cube binning | Color mapping linearity, neutral axis shift |
| 2D Joint Hist | Spatial Relation | Joint prob (e.g., R-G) | Channel correlation, neutral axis diagonal check |
| GLCM | Spatial Relation | Co-occurrence probability | Grain texture quantification, uniformity |
| Tile Histogram | Spatial Relation | Block-wise stats | Vignetting, uneven development detection |
| Power Spectrum Hist | Frequency | 2D FFT radial binning | Energy vs Frequency, grain feature extraction |
| Wavelet Hist | Frequency | Multi-scale coeff stats | Multi-layer grain structure capture |
| Gradient Hist | Gradient/Edge | Edge direction stats | Silver halide crystal anisotropy detection |
| Laplacian Hist | Gradient/Edge | 2nd derivative stats | Sharpness/Softness measurement |
| Ratio Hist (R/G) | Ratio/Log Space | Channel ratio binning | Illumination-robust cast detection |
| Log Exposure Hist | Ratio/Log Space | Log-space binning | Direct fitting of H&D curve toe/shoulder |
Dynamic Range and Highlight Roll-off
A common observation in film simulation is that digital dynamic range numbers win, but film highlight behavior wins on "character". This is not a "resolution" issue, but a dimensionality superiority of chemical response.
1. Dynamic Range: The Definition Trap
| Metric | High-end CMOS (Sony A7R V) | Kodak Portra 400 |
|---|---|---|
| Engineering DR | 15.3 stops (Linear RAW) | 13.2 stops (Density 2.8) |
| Effective DR | 13.5 stops (Noise ≤ 1 ADU) | 18+ stops (Printable Highlights) |
Key: CMOS hard clips at 14 bit (Full Well Capacity), whereas film dye continues to stack after $D_{\max}=2.8$—it just transitions from linear growth to logarithmic decay. Scanners might clip, but optical enlargers can retrieve this, giving film its "highlight retention" ability.
2. Three Stages of Chemical Highlight Roll-off
Stage 1: Linear Region (0–0.8 Density)
Silver halide grains respond linearly according to Beer-Lambert law: $D = \gamma \cdot \log_{10}(H)$. This looks like standard LOG encoding.
Stage 2: Shoulder Softening (0.8–1.8 Density)
Chemical Mechanisms:
- Grain surface development centers saturate → Electron trapping efficiency $\eta$ drops, following Mott-Gurney Space Charge Limit: $J \propto V^2/d^3$
- Developer oxidation product (QDI) decomposes at high pH → Coupling rate $k_{\text{coupling}}$ decays
- Interlayer Inhibition: High density in upper layers releases Bromide ions (Br⁻) to lower layers, inhibiting their development (ISO 4090 IIE effect)
Mathematically: $$ D(H) = \gamma \log_{10}(H) \cdot \left(1 - \frac{H}{H_{\text{sat}}}\right)^{\alpha} $$ Where $\alpha \approx 0.65$ (Kodak Patent US4508812A). This is the core of highlight softening.
Stage 3: Toe Clipping (>1.8 Density)
Dye molecules stack to the Förster Resonance Energy Transfer distance (< 2 nm), quenching energy → Density growth approaches $D_{\max}$ asymptotically.
3. Why Digital Post-Processing Falls Short
3.1 Dimensionality Gap
CMOS highlights have only 16384 discrete levels (14 bit). Once clipped, data is gone. Film highlights still have 31-dimensional spectral changes, just with slower density growth. Post-processing can only interpolate, not extrapolate real spectra.
3.2 Irreversibility of Non-linear Coupling
Photoshop's "Highlight Compression" is $y = \log(x + \epsilon)$ or $y = x^{0.8}$, applied independently per channel. Film involves Yellow + Magenta + Cyan dye densities inhibiting each other. Mathematically, it's a ternary non-linear coupled system:
$$ \begin{cases} C_Y = f_Y(H_B, H_G, H_R) \ C_M = f_M(H_G, H_R, H_Y) \ C_C = f_C(H_R, H_Y, H_G) \end{cases} $$
Standard tools don't model "upper layer exposure poisoning lower layer".
3.3 Noise Texture Differences
CMOS highlight noise is Poisson + Readout Noise. Film is Granularity following Wiener Spectrum: $G(f) = \frac{a}{f^2 + b}$, which decreases as density increases. Digital noise addition is just "adding salt", lacking density-dependent grain size distribution.
4. Hard Simulation: Adding "Chemical Constraints"
4.1 Shoulder Softening Model
Apply a space charge limit after Exposure → Density mapping:
#![allow(unused)] fn main() { fn shoulder_softening(density: f32, shoulder_point: f32) -> f32 { if density > shoulder_point { let excess = density - shoulder_point; density - excess * excess / (shoulder_point + excess) } else { density } } }
Where shoulder_point = 0.8 (ISO 5800 standard point).
4.2 Interlayer Inhibition Matrix
#![allow(unused)] fn main() { // Interlayer effect matrix from ISO 4090 measurements // Row i = Real Dye i / Theoretical Dye i let interlayer: nalgebra::Matrix3<f32> = nalgebra::Matrix3::new( 1.00, -0.08, -0.03, // Yellow inhibited by Magenta/Cyan -0.05, 1.00, -0.07, // Magenta inhibited by Yellow/Cyan -0.02, -0.06, 1.00, // Cyan inhibited by Yellow/Magenta ); let dyes_real = interlayer * dyes_theoretical; }
4.3 Dye Self-Absorption Correction
#![allow(unused)] fn main() { // Dye self-absorption causes spectral shift at high densities fn dye_self_absorb(c: f32, t: &mut [f32]) { // c: dye concentration, t: transmittance spectrum for wl in 0..31 { // Beer's law deviation of 1-3% when density > 1.5 let correction = 1.0 + (c - 1.5) * 0.02; t[wl] *= correction.clamp(0.97, 1.03); } } }