Commit Graph

8 Commits

Author SHA1 Message Date
3dtours 0e9f78bd5e web: give the grain a size of its own, and read the count off the print
MONOCHROME GRAIN was one integer knob 0..10 with one meaning, how much. It is now
a strip of three: AMOUNT — the same knob, in half steps — SIZE, a percentage of
the stock's own grain cell (50..200%, so the same number means the same texture
relative to the picture on both platforms), and an inert readout of N/INCH, the
clump count the two knobs and the stock add up to in the print's own terms (300
dpi = 300px of the 1080-wide reference the knob was tuned at).

Emulsion is not one grain size across the frame: the coating settles unevenly.
The field now prints that — the same hash read slowly (ZONE_FREQ = 1/96 cells,
turned off the axes, smoothed so a border between two patches is a slope and not
a seam) swings each patch's own cell by half of ZONE_SWING either way, ±20%.
Nothing in it moves the field's mean: a coarser patch prints bigger clumps, not a
brighter one, which is why the strip can read out one number while the frame
carries a range.

A patch may not swing a cell under the pixel the target can print, or the clumps
are sub-pixel and print as static — aliasing, not a finer emulsion. The shader
takes that floor as a `mincell` uniform beside the cell (u, mincell, seed.xy, in
declaration order): an export passes one output pixel, a preview one device pixel
(1 / PixelRatio), which is the floor the phone's preview already needed.

SIZE is stored as an integer percent so no float noise reaches the recipe JSON,
and it is read by the same two engines that read grain: the web's
grainCell(width, stock, sizePct) and the phone's grainCell(width, minCell,
sizePct). The chip above the strip carries the amount in half steps the way TEMP
carries the kelvin, and the readout moves with SIZE, not with AMOUNT.

Measured:
  grain-controls-test.cjs 20/0 — the strip carries grp-grain, grain:amount,
    grain:size and grain-inch; the AMOUNT ruler is 0..10 step 0.5, and 3 -> 3.5
    moves the frame (sigma 18.53 -> 21.91, new hash) without moving the readout;
    10 -> sigma 60.43, 0 -> sigma 0; SIZE 200% -> 130/INCH (sigma 40.06), 50% ->
    522/INCH (66.56: under the preview's pixel floor what is printed is static,
    not finer grain); the region claim on an 8x8 grid of the flat frame gives
    tile sd 51.0..66.1, max/min 1.295, and a tile mean spread of 1.14 — a coarser
    patch is not a brighter one.
  grain-size-test.cjs 17/0 (was 12/0) — the SIZE rule and the readout on the
    module itself: 200% doubles the cell, 50% halves it, the output pixel still
    floors the smaller one. 4000px file cell 3.704 against 1.083 device px on a 3x
    preview, the old one-dp floor 2.77x coarser, rho1 0.627 against 0.074. The
    harness built its own 3-uniform array; it now passes [u, mincell, seed.xy]
    like every other caller.
  _grain-zone-ck.cjs — the zone's own contribution, at preview scale (cell 1.70,
    1600px, 8x8 tiles of 200px): zone on, tile sd 23.22..24.82 (ratio 1.069);
    zone off, 24.42..24.79 (ratio 1.015). Nothing else differs.
  _grain-ck.cjs — the clump field is otherwise what it was: rho1 0.62
    classic-neg / 0.12 velvia, residual autocorr 0.035 against 0.036 with the
    swing forced to 0, peak/median 32.3 against 27.6.
  _grain-spectrum.cjs (app, 1600px render) — residual autocorr 0.017..0.018,
    spectral peak/median 4.6..6.1: the slow lattice adds no peak of its own.
  grain-stock-test 53/0, sims-test 31/0, fx-mono-test 15/0, wb-preset-test 33/0,
    temp-swatch-test 33/0, wm-font-test 38/0, grain-analog-test 7/0 (its grain
    selectors moved to the strip).
  tsc: web clean; the phone's scoped config reports exactly the pre-change
    baseline (Viewfinder.tsx's own errors, none new).

ponytail: the amount is fractional now, so the two recipe-create forms read grain
through their own half() instead of the int() that would truncate the half the
ruler just spent — every other knob there is still whole. The SIZE knob is one
number for the whole strip: no way to dial a single patch, and no seed control.
The readout is the DESIGN count the field is built on, never a per-patch
measurement.
2026-09-23 15:26:23 +07:00
admin a85ebf0a81 Sell PRO as an in-app unlock, take shared photos, and let FX run negative
- One free Play app (com.locphamtran.recipescamera) with an expo-iap PRO
  unlock, replacing the paid apk for the store channel
- Android SEND/SEND_MULTIPLE share target feeds photos into the editor,
  on cold start and on resume
- BACK button in the TopBar and press-and-hold PEEK replace the chip row
- NOISE REDUCTION and SHARPENING accept -10: a negative SHARPENING softens
  the frame before the grain pass, a negative NOISE REDUCTION re-grains it,
  in the Skia engine, the live preview and the Kotlin export path alike
2026-09-16 06:48:22 +07:00
admin 27a9dfb65f Ship HDF EFFECT to PRO only, and clean the build for Play
Rename LIGHT GLOW to HDF EFFECT and gate it behind PRO: LITE renders the
chip greyed out with a PRO tag and cannot open its slider; restoration of
older settings that carry hdf > 0 still blocks export in LITE.

Play/licence pass:
- drop unused permissions (ACTIVITY_RECOGNITION, READ_MEDIA_AUDIO/VIDEO,
  SYSTEM_ALERT_WINDOW) and requestLegacyExternalStorage
- rename the Leica sims to LC STREETLIFE CLASSIC/VIVID, drop the brand from
  comments; fix app name text, canvaskit copyright, LICENSE owner
- remove expo-image-picker and expo-sensors (no JS import), delete dead
  exports (computeMatrixTint, cinemaSeasonName, base64 helpers,
  formatCoordinate)
- versionCode 3 / versionName 1.2.1, drop the personal email from About
- add docs/privacy-policy.html for the Play listing

Size: R8 + shrinkResources + bundle compression + ABI trim, 161 MB -> 76 MB.
2026-09-15 11:36:52 +07:00
admin 7c7a244b84 Rail without icons, FX glow chip renamed
The rail chips carried a lucide glyph before the label; drop the icons and
the icon field so the labels stand alone (the row is a plain text rail now,
and each chip is ~50px narrower). Also rename the FX knob from the
lens-vendor term to LIGHT GLOW - same key, same maths, just the label.
2026-09-10 10:44:33 +07:00
admin 6c7d5b8f43 Keep the existing COLOR chip label
Reverts the COLOR -> SATURATION rename: the control already existed, so
it stays as it was.
2026-09-10 10:28:38 +07:00
admin 65d0850317 FX vignetting, LIGHT vibrance/saturation, reticle unlock
- FX: new VIGNETTING 0-10 control (chip + slider). Darkens the four
  corners of the photo with a radial gradient whose radius is the photo
  diagonal/2, so the falloff stays circular on any aspect. Applied in
  preview (camera overlay, polaroid/wall window, plain library) and in
  exportEngine pass 6b, always on the picture, never on the mat.
- LIGHT: new VIBRANCE -10..+10 (chroma-masked saturation in the tone
  shader: vivid pixels move least). Renamed the existing saturation
  control chip from COLOR to SATURATION - same param, clearer label.
- Fix: <Shader> takes a RuntimeEffect, not a gradient SkShader; the
  preview vignette threw "Object is not a HostObject" when it got one.
  Use the declarative <RadialGradient> child on the Rect instead.
- Preview: drop the "AE/AF LOCKED - TAP TO UNLOCK" banner; the lock
  reticle is now itself the unlock touch target.
2026-09-10 10:22:55 +07:00
admin 28e18893cd Add HDF (Highlight Diffusion Filter) to the FX tab
The built-in lens filter, simulated the way it is done in Photoshop: pull the
highlights out with a hard contrast curve (out = 2.5*in - 1.5, black below 0.6),
Gaussian-blur that selection, then Screen it back over the photo at reduced
opacity. Screen against a near-black pixel is a no-op, so areas with no
highlight within the blur radius come back byte-identical while lit areas
spread their light.

One 0..10 knob drives both the radius (4%..1.9% of the width, ~10..50px at
2560 wide) and the opacity (0.15..0.5), matching the recipe's 15-50px radius
range. The extraction composes on top of the recipe colour matrix so a
monochrome look glows white instead of leaking the original colours back.

Runs in the Skia export engine only; the P5 native path is not wired into the
app (dev parity probe only), so it is left alone for now.
2026-09-10 09:45:51 +07:00
3dtours 09dbc72cce feat: v1.1 — highlight-weighted metering, settings modal, photo viewer, shutter sound control, cleanup 2026-09-09 09:41:55 +07:00