Skip to content

Repository files navigation

Lumière

A darkroom in the browser. Lumière develops camera RAW files through a physically modelled film pipeline: real characteristic curves, grain derived from silver-halide counting statistics, development chemistry, and printing onto paper with toning. It also develops scanned negatives by inverting them through the curves of the stock they were actually shot on.

Everything runs locally. There is no account, no cloud, and no network call anywhere in the application — your photographs and your edits never leave the machine.

Rust and WebAssembly decode; WebGL2 renders. Every control re-runs the whole chain at interactive speed.

Try it in the browser — bring your own RAW file; nothing is uploaded.

The develop view: film browser, the photograph, and the develop panels

Why it exists

Film emulation usually means a look-up table and a noise overlay. That reads as wrong immediately to anyone who has shot film, because film is not a filter: it is an emulsion with a characteristic curve, a crystal structure, a development process and a print. Lumière models those, and measures itself against the manufacturers' own published numbers.

Claim How it is verified
Grain matches the real stock Rendered RMS granularity at D=1.0 through a 48 µm aperture is within 4 % of the datasheet value for every tested stock, with no per-stock fudge factor
Grain is a counting process, not noise The density distribution's skewness flips sign between low and high coverage — filtered noise cannot do this, and the old noise path is kept as a negative control that fails the test
Paper grades are real grades Print gamma spans a factor 3.39 across grades 00–5, against 3.40 predicted independently from the papers' published ISO range
Scan inversion follows the stock A synthetic negative round-trips with 0.0004 stops of crossover; developing it as the wrong stock is 445× worse
Culling is genuinely fast 8.3 ms median photo-to-photo, straight off the embedded preview, with zero full decodes

Numbers come from tools/bench.mjs and tools/verify.mjs, which drive the real application in a real browser against real camera files. Claims without a measurement do not ship.

Twelve developed frames: one portrait through several stocks, plus pushed Tri-X, expired Gold, cross-processed Ektachrome, bleach bypass and a selenium-toned print

Every tile is a real export through the develop path. Top row: one portrait clean, then through Portra 400, Velvia 50 and Tri-X 400. Below: CineStill 800T's halation on a night street, Ektar on a landscape, Gold 200 expired ten years warm, Tri-X pushed two stops in Rodinal 1+50, Ektachrome cross-processed in C-41, Superia with bleach bypass, T-Max P3200's grain, and HP5 Plus on a selenium-toned print. The portraits are used with the photographer's permission, the other frames are CC0; see credits.

Every tile is the open photograph rendered through that stock

The film browser renders your actual photograph through every stock. Hovering a tile opens a 1:1 inset so grain and structure can be judged before committing.

What it does

Develops RAW files. Canon CR2 and CR3 (including lossy CRAW), Nikon NEF and Adobe DNG including Apple ProRAW are verified against real camera files. Sony ARW and Fujifilm RAF decode through the same library but are not yet covered by the test suite. Demosaicing is Malvar-He-Cutler; colour goes through the camera's own matrix into linear Rec.2020 and stays floating point to the end.

Simulates film, physically. Seventeen stocks — Portra, Ektar, Gold, Kodachrome, Ektachrome, Tri-X, T-Max, Velvia, Provia, Superia, Acros, HP5+, FP4+, Pan F+, Delta, CineStill 800T — each with density-versus-log-exposure curves from the manufacturer's data. Grain is a boolean point process: a virtual crystal lattice, log-normal grain radii, Bernoulli development, and aperture-integrated counting statistics, with the lattice solved from each stock's published granularity. Halation adapts to the frame's own specular content.

Simulates development. Eight developers (D-76, XTOL, Rodinal dilutions, HC-110 B, DD-X, stand), push and pull through real development kinetics rather than a contrast slider, temperature, agitation-driven compensating development and Mackie-line edge effects, expired film by age and storage temperature, E-6/C-41 cross-processing with genuine crossover, and bleach bypass computed from retained silver density.

Prints. Multigrade paper grades, fibre and resin-coated stocks, print exposure in stops, selenium, sepia and gold toning as density-dependent hue shifts on the print curve — selenium acts on the silver-rich shadows and barely touches the highlights, which is exactly why it looks the way it does — split toning, and dry-down.

Dodges and burns. Local adjustments over radial, linear, brush and luminosity-range masks, with exposure measured in stops, because that is how a dodge under an enlarger is timed. Plus spotting for dust, stored as vectors so it survives re-cropping and re-exporting.

Develops scanned negatives. Pick the stock the film was shot on and Lumière inverts through its actual characteristic curves, removing the orange mask as a measured per-channel density offset rather than a white-balance guess. The same physics engine that emulates film for digital captures develops real film that was scanned.

The library: a light table of the current catalog

Organises a shoot. Multiple catalogs, ratings and pick/reject flags, colour labels, keywords, variants, snapshots, batch export, contact sheets, and a full-screen keyboard culling mode that never decodes a file. Catalogs export as .lumiere bundles that carry edits, thumbnails and styles — with or without the originals — so a catalog moves between machines and relinks to the files wherever they live.

Exports JPEG, PNG-8, PNG-16 and TIFF-16 with cropping, rotation, resizing and framing.

The Process panel: developer, push, temperature and agitation, with the computed development time

Tri-X 400 pushed two stops in Rodinal 1+50. The panel shows the computed development time, because push is modelled as development, not as contrast.

Running it

Requirements: Rust with the wasm32-unknown-unknown target, wasm-bindgen-cli 0.2.122, and Node 20+.

bash crates/rawcore/build.sh   # build the decoder into web/src/wasm
cd web && npm install && npm run dev

Then open the printed URL and drop a RAW file onto the window.

Verifying it

cargo test -p rawcore --release        # decoder, colour, encoders
node web/src/film/selftest.ts          # film and chemistry model
node tools/verify.mjs                  # end-to-end, headless, real RAW files
node tools/bench.mjs                   # performance budgets

tools/verify.mjs builds the production bundle, serves it, drives headless Chrome over the DevTools protocol and asserts on rendered pixels. Put a few RAW files in samples/ first; the harness names the ones it expects.

Keyboard

G library · D develop · C crop · A auto tone · 15 rate · 0 clear rating · P pick · X reject · U unflag · \ hold for before/after · 0 fit · 1 100 % · arrows move between photographs.

How it is built

Path What lives there
crates/rawcore RAW decode, demosaic, colour transform, encoders (Rust, compiled to wasm)
third_party/rawler vendored RAW decoder, patched for wasm (see its LUMIERE-PATCHES.md)
web/src/gpu the WebGL2 develop pipeline
web/src/film film stocks, chemistry, paper, LUT construction
web/src/scan scanned-negative inversion
web/src/masks, web/src/spot dodging and burning, spotting
web/src/library, web/src/bundle catalogs, persistence, .lumiere archives
web/src/ui the interface
docs/ the design and physics specifications the code is written against

The docs/ directory is worth reading if you care how any of this works. film-physics.md and chemistry.md describe the models and cite their sources; product.md records the research the feature set is based on and the decisions it forced, including where a measurement disagreed with an expectation and the expectation lost.

Licence

GPL-3.0-or-later. Lumière vendors rawler (LGPL-2.1) with small patches for WebAssembly, documented in third_party/rawler/LUMIERE-PATCHES.md; that directory remains under its own licence.

Status

Working and measured, but young. The develop pipeline, film and chemistry models, scan inversion, catalog and printing tools are implemented and covered by the test suites above. Known limits are recorded honestly in CHANGELOG.md and in the docs/ specifications rather than glossed over — including the performance budget that was revised when a measurement proved the original number unreachable.

About

A darkroom in the browser: RAW development through physically modelled film. Real characteristic curves, silver-halide grain statistics, development chemistry and paper printing. Also develops scanned negatives. Rust/WebAssembly + WebGL2.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages