SHARPENING was the doc's §4.2 kernel with the two parts of §4.2 missing from
it. `CLARITY_SKSL` evaluated the 3x3 unsharp mask with Mask = 1 on every pixel
and no coring at all, so a flat sky, a cheek and a noise speckle all took the
gain an eyelash took. That is `thay_doi_thong_so_giong_lightroom.md` §1 written
out as a bug: "khi Sharpen, ảnh nổi đầy sạn hạt cát" — the knob could not raise
the contrast of an edge without raising the noise of everything beside it, and
on a grainy frame the second effect won.
`SHARPEN_SKSL` is the doc's own line, `Image + Amount x HighPass x Mask`, with
the two terms it names:
- EDGE DETECTION: the Sobel magnitude G = sqrt(Gx^2 + Gy^2) on luminance, put
through the doc's soft threshold smoothstep(T, T + 0.1, G). Flat fields
read G = 0 and get Mask = 0 — the pixel is handed back untouched.
- DETAIL (halo coring): a high-pass under SHARPEN_CORE is a speckle, not a
detail, and is suppressed. The coring is soft (a ramp across the threshold,
not a cliff) so a detail sitting on it is not switched on and off from one
pixel to the next.
The HIGH-PASS is the doc's Radius, held at one image pixel — 0.7-0.9px on a
Retina panel — and it is a LUMINANCE high-pass carried by all three channels.
A per-channel kernel sharpens a red edge against a green one and draws a colour
fringe down every contour; the file's own §3 rule is to keep R/L, G/L and B/L
where they were.
`scripts/sharpen-check.mjs` pins the three properties the old kernel could not
have: a flat field and a field of grain come back unchanged, a step below the
threshold comes back unchanged, and a hard step moves apart on both sides while
the flat halves beside it stay put. `CLARITY_SKSL` and `clarityUniforms` are
gone with it, and the header note that said CanvasKit had two convolution steps
to replace now says the one it has.
Checked: node scripts/sharpen-check.mjs; node scripts/denoise-check.mjs; node
scripts/tone-base-check.mjs; node scripts/highlight-knee-check.mjs; node
scripts/auto-tone-check.mjs; node scripts/half-check.mjs; node
scripts/mask-wb-check.mjs; node scripts/preview-match-check.mjs; node
scripts/raw-develop-check.mjs; node scripts/white-level-check.mjs; node
scripts/wb-table-check.mjs; npx tsc --noEmit.
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
.recipefiles; 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.