- 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.
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.
Watermark editing no longer covers the photo: the preview letterboxes the
image inside its fit rect while the deck is open, and the idle hint is gone.
The FRAME tab can rotate a library photo 90 degrees clockwise. Rotation
swaps the pixel dimensions instead of the frame, so the polaroid/wall window
keeps its aspect and every consumer (cover crop, watermark area, export) picks
up the new orientation from width()/height() with no extra layout branch. The
rotate surface is CPU-backed on purpose - a GPU snapshot loses its pixels
before the next frame and renders the photo black.
Custom watermark text can now be dragged anywhere on the photo, double-tapped
to re-edit, and is only baked into the file once ACCEPT is pressed; the export
engine takes the same position. It also gained COLOR/SIZE/FONT chips, with the
font list read from the OS via Skia's FontMgr so any installed family can be
used.
The framed windows (polaroid card / wall frame opening) now take the same pinch + double-tap gestures as plain library, clamped so the photo keeps covering the mat opening, and the visible sub-rectangle is handed to the export engine so the file matches the preview.
Wall frame export also stopped crashing: on Android release an image asset resolves to a drawable *name* ('wallframe') and is flagged downloaded, so Asset.downloadAsync short-circuited and readAsStringAsync threw 'Unsupported scheme for location wallframe'. Clearing that state lets the native module copy the drawable into the cache.
Lightroom-style 'Screen' output sharpen (unsharp mask, 3x3 convolution
via Skia ImageFilter.MakeMatrixConvolution, amount 0.5, kernel sums to
1) applied to final pixels before JPEG encode. Camera-capture branch
passes { dpi: 300, sharpen: true }; library-mode export untouched.
Export pixels were already full resolution - sharpen is a new final
GPU pass, no pixel-count change.
Skia JPEG re-encode drops original header (72dpi camera default). New
src/utils/jpegDpi.ts rewrites JFIF APP0 density + EXIF X/YResolution
(endian-aware) or inserts minimal JFIF when neither exists; applied via
processAndExportPhoto options.dpi in the camera-capture branch only.
Library-mode exports untouched. Pixels never change - metadata only.