3dtours bd57dd72b3 EXPOSURE reads in stops: -5..+5, two decimals, on an unchanged store
The tone spec draws EXPOSURE as `min="-5" max="5" step="0.01"` with a `0.00`
readout, and the app's row was still the original whole-unit knob: -10..+10 in
one step, printed as a bare `+2`. The recipe file behind it has always carried
units where one unit is EV_PER_UNIT = 0.25 of a stop, which is the same ±2.5 EV
travel, so the two are the same range said two ways.

The def now talks stops on its face and units in the store: `min: -5,
max: 5, step: 0.01`, a `twoStops` readout (`0.00`, `+0.50`, `-0.25`, no unit
because the label is the unit), and accessors that translate — `get` multiplies
by EV_PER_UNIT, `set` divides and rounds to 1/10000. EV_PER_UNIT is exported for
that one use. Because the ×10 of `deepen` is not applied here and the store's
field is untouched, a recipe that shipped with EXPOSURE 2 still means the two
units it always meant: nothing gets brighter or darker, and half a stop is
still 2 units in the file. The knob steps by 0.01, which is 0.04 units — the
slider can express a quarter stop where the old one could only express quarter
stops, and everything between them.

TINT and TEMPERATURE keep the web's ranges (tint ±100 on the ±10 store scale,
temperature 2500..10000 K). The spec's ±150 tint and its 2000..50000 K
temperature were reviewed and left as they are: the tint at ±100 is already
what `deepen` publishes and the wider pair would move stored looks.

Verified:
- `npx tsc --noEmit` clean; `npm run build` clean.
- Repo checks re-run green: `highlight-knee-check`, `auto-tone-check`,
  `half-check`, `white-level-check`, `preview-match-check` — the last two render
  real looks through the engine, so a shifted EXPOSURE scale would have shown up
  as a brightness change in recipes that carry a non-zero one.
- Live browser pass on the deployed build: the row reports `min=-5 max=5
  step=0.01`, prints `+1.00` at a full stop, `+0.01` at a hundredth, `-0.25` at a
  quarter stop down and `0.00` at rest, and EXPOSURE +1.00 still lifts the
  graded frame (mean luma 184.3 -> 203.4).

ponytail: the CREATE RECIPES form (RecipeCreatePanel) still edits a sim's raw
adjustments in store units, EXPOSURE included. Leave it there until that form is
asked for stops: it is a store editor, not the develop panel, and every field on
it is in the same units.

Co-authored-by: PenguinHarness <noreply@penguin.local>
2026-09-29 17:09:01 +07:00
2026-07-17 09:33:24 +07:00
2026-09-16 18:11:17 +07:00
2026-07-17 09:33:24 +07:00
2026-07-17 09:33:24 +07:00
2026-07-17 09:33:24 +07:00
2026-07-17 09:33:24 +07:00
2026-07-17 09:33:24 +07:00
2026-07-17 09:33:24 +07:00
2026-07-17 09:33:24 +07:00
2026-07-17 09:33:24 +07:00
2026-07-17 09:33:24 +07:00
2026-07-17 09:33:24 +07:00

RecipesCam

RecipesCam is a camera and photo-editing app built around recipes — reusable looks that carry a film simulation plus a full set of adjustments. You shoot or open a photo, dial in a look, and keep it as a recipe you can apply again, share as a .recipe file, or save to your account.

It ships twice from one repository: a React Native (Expo) app for iOS and Android, and a self-contained web build under docker/ that runs the same render pipeline in the browser.

What it does

  • Shoot with a recipe applied. Live viewfinder, GPS-tagged captures, and the recipe baked into the saved file.
  • Film simulations. Built-in looks — PROVIPES, VELVIPES, CLASSIC CHRIPES, CLASSIC NEGIPES, ASTIPES, ETERNIPES, ACRIPES, B&W HIGH CONTRAST and the LC STREETLIFE pair — each with its own grain and tone curve.
  • The full adjustment set. Exposure, contrast, highlights and shadows, saturation, colour temperature, clarity, grain, and an HSL mixer with a colour picker that samples straight off the photo.
  • Geometry. Crop to a fixed ratio or free-form, quarter turns, and a straighten ruler, plus printed frames (classic border, retro instant, wall frame portrait/landscape).
  • Finishing. Watermark and GPS stamp, EXIF carried through the export, JPEG written with a proper 300 DPI JFIF header.
  • On-device upscaling. A Real-ESRGAN pass runs locally when an export asks for more pixels than the source has — no server sees the photo.
  • Recipes. Save, favourite, export and import .recipe files; the web build keeps them in your account, the phone build also keeps them on device.

The two builds

Build Where Stack
iOS / Android repo root Expo + React Native, @shopify/react-native-skia for the render pipeline, NativeWind for styling
Web docker/ Vite + React, CanvasKit (canvaskit-wasm) for the same pipeline, Fastify + SQLite API for accounts and recipes

The render engine is shared by design: the web build compiles the app's own src/utils/* and type definitions unchanged, with @shopify/react-native-skia aliased to a CanvasKit shim (docker/frontend/src/engine/skiaShim.ts). A look looks the same on both because it is the same code.

Running the web build

cd docker
cp .env.example .env
docker compose up -d --build
# → http://localhost:8090

Photos never leave the browser: grading, framing, watermarking and JPEG encoding all run in the visitor's tab; the API only stores accounts and recipe JSON. See docker/README.md for the layout and the proxy setup.

Running the app

npm install
npx expo start          # Expo Go / dev client
npx expo run:android    # or run:ios for a native build

Repository layout

App.tsx, src/           the Expo app: screens, tool rail, viewfinder, shaders
docker/                 the web build (frontend + API + compose file)
  frontend/shared/      vendored copies of the app's types and utils
  frontend/src/engine/  CanvasKit shim, export engine, super-resolution
docs/                   privacy policy
THIRD_PARTY_NOTICES.md  licences of the bundled fonts, models and libraries

Licence

See LICENSE and THIRD_PARTY_NOTICES.md.

S
Description
No description provided
Readme 7.8 GiB
Languages
TypeScript 92.9%
Kotlin 5.7%
JavaScript 1.3%