mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-12 17:00:37 -06:00
Part of the Lens Blur row (`LR-EDIT-LENSBLUR`, ⬜ → 🟡: no depth map yet). Related: #377 (SAM 3 subject selection, used here for the Lens Blur focus).
## Problem
Lens Blur settings existed but nothing rendered them: there was no way to add a shallow depth of field, keep the subject sharp while the rest falls out of focus, or blur only part of a photo through a mask.
## Change
**Edit → Lens Blur** (`section.lensBlur`, ⌘6)
- **Apply**, **Amount**, **Boost** (brighter bokeh highlights) and **Transition** (how gradually the blur grows away from what is in focus, so the subject doesn't look cut out).
- **In Focus**: *Subject* (a mask) or a fixed shape: **Auto / Round / Horizontal / Vertical**, buttons drawn in code that work on the light themes.
- **Move & Resize** (tool `lensBlurShape`) drags the ellipse on the photo and stretches it by its handles. Each drag is one undo step; a shape button puts the ellipse back in the middle.
- **Find Subject** (`lensBlur.findSubject`) runs only when clicked, never automatically on Apply.
- It starts from the focus point the camera recorded, when the file has one (Sony ILCE-1 and ILCE-7SM3, the models `lightcraft_meta` reads today). Otherwise it starts from the subject estimate.
- SAM 3 keeps the prominent people and animals: those at the focus point and those of a similar size. When the focus point falls on neither, it falls back to an object click at that point.
- The result is a normal Object mask ("Lens Blur Subject") that can be refined in Masking.
- It runs on the background segment worker, and on remote SAM 3 too (`Operation::Subject`). While the mask is pending, the UI shows a spinner and the render keeps the subject estimate in focus, so the photo is never blurred whole.
- Without the model, it returns the model-missing error, changes nothing, and the UI offers the SAM 3 download.
- **Brush Refinement**: Focus and Blur brushes painted on the photo (`lensBlur.brushStroke`, `lensBlur.brushReset`), with Size, Feather, Flow, Amount and Auto Mask.
- While Apply is off, In Focus, the shapes, Move & Resize and Find Subject are disabled.
**Masking: a Lens Blur block in every mask** (`section:maskLensBlur`, under Refine Edges)
- **Blur: Selection / Outside**. Outside blurs what the mask leaves out, without inverting the mask's other adjustments.
- **Amount**, **Boost** and **Transition**, per mask. The header's eye switches the block off and keeps its values; the header folds the block.
- Stored as `lens_blur`, `lens_blur_outside`, `lens_blur_boost`, `lens_blur_transition` and `lens_blur_off` on `LocalAdjustments`, set through `mask.adjust`.
**Rendering** (`crates/pipeline/src/lens_blur.rs`)
- Runs in the scene-linear stage after denoise; its settings are part of `pre_nr_key`.
- Disc (bokeh) kernel at four levels on coarse grids, with a normalized convolution weighted by the circle of confusion, so sharp areas don't bleed halos into the blur. Transition uses a chamfer distance to the in-focus area.
- The radius is relative to the long edge, so the blur doesn't depend on resolution. The work stays bounded on large exports: the disc kernel has a fixed size, and each level's coarse grid stays within a cell budget.
- Zoomed windows, both 8-bit and deep (16-bit/float), read around them and are cut back to the request. The grids follow the whole output's origin, and the automatic subject estimate is made once from the whole photo. A window therefore matches the whole render.
- GPU path: read back, apply on the CPU, upload (`crates/gpu/src/render.rs`).
- Hostile values (NaN or infinite sizes, out-of-range areas and focus points, oversized stroke lists) are sanitized or rejected, never a panic.
All new strings are in the 17 locales.
**Not in this PR:**
- Real depth. The falloff is the image distance from the focus, not scene depth; a depth model (e.g. Depth Anything V2 Small on candle) would be a separate PR.
- Rotating the ellipse.
- Aperture shapes.
- Focus points for more camera makes and models.
- Find Subject doesn't resume by itself after the SAM 3 download (the Masking tools do). Like the existing Object / Describe jobs, it shows no row in the activity stack.
## How it was checked
- [x] Full `cargo xtask ci` on Windows 11 x86_64 after the latest merge of `main` (465c8722):
- fmt, clippy, heif, parity, layers, assets and wasm pass;
- every Lens Blur test passes;
- the only failures are five tests outside this change that fail on Windows on `main` too: `export::tests::sixteen_bit_zip_tiff_is_never_larger_than_uncompressed`, the two GPU out-of-memory tests in `gpu::ctx`, `dialog_role::settings_is_a_dialog_too` (needs craft-fonts) and `folder_drop::dont_ask_again_moves_the_next_drop_at_once`.
- [x] Tests that fail without the change:
- **pipeline**:
- the `lens_blur` tests: shapes and moved or hostile areas, blur growth away from the focus, the fallback while an AI mask is pending, brushes, masks blurring their selection or outside with Transition, hostile input;
- `large_image_blur_work_is_bounded`: on a 60 MP export with the strongest global and per-mask settings, the disc kernel stays a fixed size and every level's coarse grid stays within its cell budget, including an offset render window;
- window/full-render parity at U8 and U16, including the automatic subject.
- **engine**: brushes with undo; Find Subject without the model changes nothing; deleting the focus mask clears the focus; a partial effect preset sets `lens_blur`; with the `sam` feature, Find Subject fills the waiting mask.
- **segment**: `prominent_subjects`, and remote `Subject` validation.
- **catalog**: `op_variants_are_versioned` with the new format version.
- **ui-egui**: the brushes painting on the photo, the shape buttons, the controls disabled while Apply is off, Move & Resize with undo and redo, the mask block (side, sliders, eye, folding).
- [x] User-visible change: ran the app and looked at the result (dark and light themes).
## Screenshots
**Before / after**
<img width="1402" height="952" alt="before" src="https://github.com/user-attachments/assets/8baceb99-7708-4c25-990c-8da29b6bc1b4" />
<img width="1320" height="937" alt="after" src="https://github.com/user-attachments/assets/a1476510-558a-4460-8766-b25bda0dfd3d" />
**Edit → Lens Blur** and **the mask's Lens Blur block**
<img width="457" height="626" alt="main painel" src="https://github.com/user-attachments/assets/47a19637-29a8-4446-a003-2eef416975b7" />
<img width="463" height="333" alt="mask painel" src="https://github.com/user-attachments/assets/345ffc14-66d2-4adc-af3c-b756727201de" />
## Merging
- **Overlap:** #1713 (Clearfall; a draft since 2026-10-14 that conflicts with main) implements the same row on the CPU. It has aperture shapes and XMP roundtrip, which this PR lacks. This PR adds the per-mask block, SAM 3 Find Subject from the camera's focus point, the GPU path and window parity. Only one should land; happy to port what #1713 has that this lacks.
- **Existing edits and libraries:** unchanged. Lens Blur is off by default and every new field is omitted from the JSON at its default, so existing settings keep their JSON and hashes.
- **Shared numbers taken:** catalog format `VERSION` 12 (`crates/catalog/src/journal.rs`). main already uses 10 (`Mask.on_photo`) and 11 (`EmbeddedLens.source`), so Lens Blur takes one past them. If another pull request lands with 12 first, I'll take the next one when I merge main. No render-cache, look or process version change.
- **Where it came from:** own implementation; nothing derived from GPL code or Adobe data. The camera focus point comes from the existing `lightcraft_meta` reader; subject selection uses SAM 3 through the existing segment crate.
- **New dependencies, `unsafe`, build scripts, network access, spawned processes, deleted files, CI or `xtask` gate changes:** none. The remote SAM 3 protocol gains one request (`Operation::Subject`). An older worker rejects it with an error, as `docs/ai-masks.md` notes.
## Checklist
- [x] No new panic path in non-test code
- [x] New text on screen goes through `tr()` with keys in all `locales/*.json`; ⌘6 opens the new section (KEY-SECTIONS)
- [x] `docs/parity-checklist.md` rows updated (LR-EDIT-LENSBLUR 🟡, LR-MASK-SLIDERS, KEY-SECTIONS); `docs/gaps.md` has no Lens Blur entry
- [x] New code has a caller
- [x] No new assets (the shape icons are drawn in code)
- [x] Ready to merge
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
<!-- coderabbit-ai-generated:v1 -->
## Summary by CodeRabbit
* **New Features**
* Added Lens Blur with adjustable strength, highlight boost, and transition controls, plus Round, Wide, and Tall focus shapes.
* Keep a subject or selected mask in focus, find people or animals automatically, and refine focus or blur with brushes. Move and resize the focus area directly on the photo.
* Apply Lens Blur to individual masks, including selections or areas outside them.
* **Bug Fixes**
* Improved Lens Blur rendering for cropped views and preserved focus-mask assignments correctly when masks are edited.
* **Documentation**
* Updated feature and control guides, with Lens Blur interface translations added across multiple languages.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->