mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-31 05:28:25 -06:00
LRC-DEV-PROCESS: process versions, so a look change can ship without changing existing edits (#146)
Each photo's develop settings record the rendering process they are interpreted by (`DevelopSettings::process`). There is one process, V1, the pipeline as it is; nothing renders differently (180 CPU 8/16-bit and GPU renders of procedural scenes, and the saved settings JSON and hash64, are byte-identical to main). Settings saved without the field load as V1 (a field-level serde default), new settings get the latest (`Default`), and V1 is left out when saving, so files and preview-cache keys stay as they were. A damaged value (anything but a whole number up to 2^32 - 1) reads as V1 too, so it never costs the rest of the settings (an unparsable `lc:settings` sidecar is ignored). Reset moves a photo to the latest process, section and slider resets don't; virtual copies inherit it; copy / paste / sync / Auto Sync / presets don't carry it; a number from a newer LightCraft is kept and renders with the newest known process. The process is not an edit (Edited badges, import-look checks). The base tone map is chosen per process in one place (`finish::base_tone`, shared by CPU and GPU). Photo > Update to Current Process (`develop.updateProcess`) moves selected photos on an older process to the latest as one undo step. Its menu enablement follows the selection; a call that names photos (`ids` / `id`) is validated on those instead, through a new opt-in `CommandSpec::explicit_targets` (execute checks enablement before the handler sees `ids`). `develop.get` reports the process. The rule and how to add V2: docs/process-versions.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8f924a9e60
commit
2e817a45f4
@@ -38,7 +38,9 @@ WITHOUT ROWID or virtual table produces an explicit error, handled as a warning
|
||||
Develop settings reuse the existing XMP/preset mapper. Supported sliders, curves and supported
|
||||
mask structures remain editable, but this is approximate rendering: camera profiles, Adobe AI
|
||||
models, some masking/retouch fields and process-version algorithms are not reproduced. Unmapped
|
||||
fields are reported. When a recovery archive is saved, it retains source settings/history/snapshots
|
||||
fields are reported. Lightroom's `ProcessVersion` is not mapped: newly imported photos are on
|
||||
LightCraft's latest rendering process, and photos already in the library keep theirs
|
||||
([process-versions.md](process-versions.md)). When a recovery archive is saved, it retains source settings/history/snapshots
|
||||
and original smart-collection rules. Smart collections become regular albums with current
|
||||
membership. Archived history and snapshots are source data, not native LightCraft history yet.
|
||||
Lightroom's `-999999` deferred-adjustment sentinel is omitted from both catalog and XMP mappings;
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ During development you can also point the client at `cargo run --release -p ligh
|
||||
| `query_photos {filter?, sort?, offset?, limit?}` | Photos in the current view (or matching a catalog `Filter`) |
|
||||
| `select_photos {ids, active?, mode?}` | Set the selection / active photo. Every id (and `active`) must be in the library: an unknown id is a tool error (`no such photo 9999`) and the selection and active photo are left as they were |
|
||||
| `list_controls {section?}` | Every develop slider: id (`light.exposure`…), range, default, current value |
|
||||
| `get_develop {id?}` | Full develop-settings JSON |
|
||||
| `get_develop {id?}` | Full develop-settings JSON, with `process`: the rendering process the photo is on ([process-versions.md](process-versions.md)) |
|
||||
| `set_develop {id?, values?, settings?, label?}` | `values`: `{controlId: number}`; `settings`: partial develop JSON deep-merged. Undoable |
|
||||
| `apply_preset {preset, amount?, ids?}` | Apply a preset (ids from `cmd_presets_list`) |
|
||||
| `crop {id?, rect?, angle?, reset?}` | Normalized crop rect `[x0,y0,x1,y1]` and straighten angle; at least one of `rect`, `angle`, `reset: true` |
|
||||
|
||||
+7
-5
@@ -47,10 +47,10 @@ and prints the summary; `cargo xtask parity --write` refreshes the summary table
|
||||
| X. Cross-cutting behaviours (BEHAV) | 18 | 5 | 1 | 1 | 8/8 (100%) | 6/8 (75%) |
|
||||
| Y. Menus | 82 | 0 | 4 | 8 | 47/47 (100%) | 23/23 (100%) |
|
||||
| Z. Keyboard shortcuts (desktop) | 75 | 3 | 3 | 1 | 49/52 (94%) | 23/24 (96%) |
|
||||
| Lightroom Classic extras | 32 | 11 | 39 | 9 | — | 22/24 (92%) |
|
||||
| **Total** | 394 | 38 | 84 | 36 | 193/200 (97%) | 142/152 (93%) |
|
||||
| Lightroom Classic extras | 33 | 11 | 39 | 9 | — | 23/25 (92%) |
|
||||
| **Total** | 395 | 38 | 84 | 36 | 193/200 (97%) | 143/153 (93%) |
|
||||
|
||||
Weighted completion (✅ = 1, 🟡 = ½, 🚫 left out): **80.0%** of 516 in-scope rows — P0 98.2% of 200 · P1 95.7% of 152 · P2 42.9% of 163.
|
||||
Weighted completion (✅ = 1, 🟡 = ½, 🚫 left out): **80.1%** of 517 in-scope rows — P0 98.2% of 200 · P1 95.8% of 153 · P2 42.9% of 163.
|
||||
<!-- /parity:summary -->
|
||||
|
||||
## Top gaps
|
||||
@@ -71,7 +71,8 @@ Take the first one nobody is working on.
|
||||
3. **LR-IMP-CAMERA-COVERAGE** (P0): per-model verification; grow the CC0 corpus and fix per-model bugs (like the CR2
|
||||
colour-filter layout, fixed in #85 by reading the file's own tag).
|
||||
4. **LR-BEHAV-RENDER-FIDELITY** (P1): a side-by-side fidelity suite against Lightroom renders (kept local in `plan/`),
|
||||
then tune tone, highlights, texture/clarity/dehaze, NR and sharpening against it.
|
||||
then tune tone, highlights, texture/clarity/dehaze, NR and sharpening against it. A retune that changes how existing
|
||||
settings render ships as a new process version, so existing edits keep their look (`docs/process-versions.md`).
|
||||
4b. **LR-VIEW-ZOOM** (P0): finish the sharp zoomed views of issue #323: windows for compare, overlays and soft proofing
|
||||
(Before/After has them); check sharpness and the wide-stage margin on real raw photos (only synthetic scenes so far).
|
||||
5. **LR-EDIT-OPTICS-PROFILE** (P1): a lens-profile database of our own (corrections embedded in DNG files and the
|
||||
@@ -517,7 +518,7 @@ under the UI command that wraps it (a no-op error); the UI command now wins.
|
||||
| LR-BEHAV-OFFLINE | Offline editing | P1 | ✅ | | local-first: everything works offline |
|
||||
| LR-BEHAV-WEB-SAFETY | Library safety in the browser build | P1 | 🟡 | `cmd:file.backupLibrary`, `cmd:file.restoreLibrary`, `apps/lightcraft-web/src/safety.rs`, `apps/lightcraft-web/src/backup.rs`, `apps/lightcraft-web/src/files.rs` | web only (experimental): Back Up Library (zip of the catalog and every stored original) and a non-destructive Restore; failed saves show as unsaved and are retried; a photo that can't be stored isn't added; one tab per library (Web Locks); notices for storage that isn't persistent and for a library that can't open; `?reset` asks first; a panic shows a message. Missing: `?safe` start, deleting originals of removed photos, zip64 (backups > 4 GB) |
|
||||
| LR-BEHAV-GPU | GPU acceleration | P0 | ✅ | `crates/gpu/src/render.rs`, `cmd:app.gpu`, `docs/gpu-pipeline.md` | CPU fallback per render on device limits, out of memory, device errors / loss, incomplete or black results (`perf.gpuReason`, `perf.gpuFallback`); short submissions for slow iGPUs; backend choice (DX12 only on Windows, `LIGHTCRAFT_GPU_BACKEND` / `WGPU_BACKEND`), device created after the window opens, crash sentinel (issue #136); no tiling beyond the buffer limit (CPU) |
|
||||
| LR-BEHAV-RENDER-FIDELITY | Rendering matches Lightroom | P1 | 🟡 | `crates/pipeline/src/lib.rs`, `crates/pipeline/src/tone.rs` | every slider exists and works, but the character of the result (tone curve shape, highlight recovery, texture / clarity / dehaze, noise reduction, sharpening, default look) is tuned by eye; there is no systematic side-by-side comparison against Lightroom on the same CC0 raws. Needs a fidelity suite: Lightroom reference renders kept only in the local `plan/` (never committed), compared per slider and preset with a perceptual metric |
|
||||
| LR-BEHAV-RENDER-FIDELITY | Rendering matches Lightroom | P1 | 🟡 | `crates/pipeline/src/lib.rs`, `crates/pipeline/src/tone.rs` | every slider exists and works, but the character of the result (tone curve shape, highlight recovery, texture / clarity / dehaze, noise reduction, sharpening, default look) is tuned by eye; there is no systematic side-by-side comparison against Lightroom on the same CC0 raws. Needs a fidelity suite: Lightroom reference renders kept only in the local `plan/` (never committed), compared per slider and preset with a perceptual metric. Tuning that changes how existing settings render ships as a new process version (LRC-DEV-PROCESS, `docs/process-versions.md`) |
|
||||
| LR-BEHAV-DRAGDROP | Drag and drop | P1 | ✅ | `crates/ui-egui/src/lib.rs`, `crates/ui-egui/src/panels/grid.rs` (`drag_feedback`), `crates/ui-egui/src/panels/left.rs` (`drop_target`, `album_drag_over`, `top_level_drop_target`, `drag_auto_scroll`) | files → app (import); grid photos → an album row (adds the selection, with a count badge while dragging); sidebar album / folder rows → a folder (moves it; its name follows the pointer); a drag of albums or grid photos near the top or bottom edge of the sidebar scrolls it (faster nearer the edge, only while the pointer is over the sidebar) |
|
||||
| LR-BEHAV-TOAST | Toast notifications | P1 | ✅ | `crates/ui-egui/src/panels/mod.rs` | |
|
||||
| LR-BEHAV-SIDEBAR-COLLAPSE | Collapsible left-sidebar sections | P2 | ✅ | `crates/ui-egui/src/panels/left.rs` (`sidebar_section_header`), `crates/ui-egui/src/state.rs` (`collapsed_sidebar`), `crates/ui-egui/src/tests_panels.rs` | click the Albums, Local, By Date or Keywords header (or drive `ui.clickWidget` `sidebarSection:<albums\|local\|byDate\|keywords>`) to fold or unfold the section; a chevron after the title shows the state; the choice is kept across restarts (`collapsedSidebar` in the UI state). UI-only (no command), like the By Date / Keywords disclosure triangles; photos dropped on a folded Albums section have no target |
|
||||
@@ -768,6 +769,7 @@ From `08-lightroom-classic-extras.md` (Classic-only features) and part 2 of `06-
|
||||
| LRC-DEV-CALIB | Calibration panel | P1 | ✅ | `ctl:calibration.*` | |
|
||||
| LRC-DEV-TAT | Targeted adjustment tools | P1 | ✅ | `cmd:develop.targeted` (`target`: curve / hue / sat / lum) | |
|
||||
| LRC-DEV-DEFAULTS | Per-camera raw defaults | P1 | ✅ | `cmd:library.preferences` (`camera`, `import.perCamera`), `crates/ui-egui/src/panels/settings.rs` | Settings → Import: raw default and per-camera presets |
|
||||
| LRC-DEV-PROCESS | Process versions, Update to Current Process | P1 | ✅ | `cmd:develop.updateProcess`, `cmd:develop.reset`, `cmd:develop.get`, `crates/develop/src/settings.rs`, `crates/pipeline/src/finish.rs`, `docs/process-versions.md` | each photo records the rendering process its settings are interpreted by: settings saved before this existed load as V1, new photos and Reset get the latest, virtual copies inherit, copy / paste / sync / presets keep each photo's own; Photo ▸ Update to Current Process (one undo step, enabled while a selected photo is on an older process); a number from a newer LightCraft is kept and renders with the newest known process. Only V1 exists so far: this is what lets a retuned default tone (#146) or stronger Light sliders (#196) ship without changing existing edits |
|
||||
| LRC-DEV-VIEWOPTIONS | Develop view options | P2 | ⬜ | | |
|
||||
| LRC-DEV-VIDEO | Video frame capture | P2 | ⬜ | | |
|
||||
| LRC-MAP-VIEW | Map view | P2 | ⬜ | | |
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# Process versions
|
||||
|
||||
Every photo's develop settings record the **rendering process** they are interpreted by
|
||||
(`DevelopSettings::process`, a `ProcessVersion` in `crates/develop/src/settings.rs`). Lightroom calls
|
||||
this the process version. It lets the look of LightCraft improve (a new default tone curve, issue #146;
|
||||
stronger Light sliders, issue #196) without changing a single photo someone has already edited.
|
||||
|
||||
Today there is one process, **V1**: LightCraft's rendering from before process versions existed.
|
||||
|
||||
## The rule
|
||||
|
||||
**A change that alters how existing settings render ships as a new process. V1 is frozen.**
|
||||
|
||||
- A deliberate change of look (tone model, slider response, default curve, how a stage interprets a
|
||||
value) goes into a new process. Photos stay on the process they have until someone runs Update to
|
||||
Current Process (or Reset).
|
||||
- Bug fixes that make output correct where it was broken (a crash, NaN or black output, a decoder
|
||||
error, CPU and GPU disagreeing) are not looks and can land in place, as before.
|
||||
- Changes to how a file is decoded or which camera colour it gets change the source, not how settings
|
||||
are interpreted; the process doesn't reach that stage today.
|
||||
|
||||
## Adding a process
|
||||
|
||||
1. Add a variant to `Process` (`V2`), append it to `Process::ALL` (oldest first), give it its number in
|
||||
`Process::version` and make it `Process::LATEST`.
|
||||
2. The compiler then points at every `match` on `Process`. The base tone map is chosen in one place,
|
||||
`lightcraft_pipeline::finish::base_tone` (both the CPU and the GPU renderer take it from there): give
|
||||
V2 its own arm and leave V1's alone. A process that changes another stage branches there the same
|
||||
way, on `s.process.process()`; if that stage is cached (`Plan::lin_key`, the planes in `local.rs`),
|
||||
add the process to its key.
|
||||
3. Keep `settings_saved_before_process_versions_load_as_v1` (`crates/develop/src/tests_process.rs`)
|
||||
passing: settings saved without the field must stay V1, not become the new latest. That is the
|
||||
field's `#[serde(default = "ProcessVersion::legacy")]`; the struct-level `Default` gives new
|
||||
settings the latest instead.
|
||||
4. Render a few procedural scenes on both processes and compare, and update `docs/parity.md`.
|
||||
|
||||
## What gets which process
|
||||
|
||||
| | Process |
|
||||
|---|---|
|
||||
| A newly imported, merged or Lightroom-imported photo | the latest |
|
||||
| Settings saved without the field (catalogs and their snapshots, versions and history, `lc:settings` in XMP sidecars, browser libraries: everything from before process versions), or with a damaged value | V1 |
|
||||
| Reset (`develop.reset`) | the latest |
|
||||
| Reset of a section or a slider | unchanged |
|
||||
| Update to Current Process (`develop.updateProcess`) | the latest, for photos on an older one; one undo step. The menu item follows the selection; a call with `ids` / `id` acts on those photos whatever is selected |
|
||||
| Virtual copy, duplicate | the source's |
|
||||
| Copy / paste / sync / Auto Sync / presets | unchanged (see below) |
|
||||
| `crs:` edits (XMP from other raw developers, Lightroom catalogs, imported presets) | unchanged; a new photo has the latest |
|
||||
| Restoring a version or history step | the one it had |
|
||||
|
||||
**Copy, paste, sync and presets** never carry the process: it is in no settings group, so the photos
|
||||
they change keep their own and interpret the pasted values with it. (Lightroom offers the process
|
||||
version as a separate checkbox when syncing; LightCraft's groups are coarser and keep it out.) Partial
|
||||
settings JSON that names `process` itself (`develop.merge`, a hand-written preset) sets it.
|
||||
|
||||
**Lightroom edits** are re-interpreted by LightCraft's mapper (`crates/engine/src/crs.rs`), which is
|
||||
written against the current rendering. `crs:ProcessVersion` numbers Adobe's renderer, not ours, and is
|
||||
ignored; a photo imported with Lightroom edits is on LightCraft's latest process, and an existing
|
||||
LightCraft photo that receives them keeps its own.
|
||||
|
||||
**A number this build doesn't know** (a library or sidecar written by a newer LightCraft) loads, is
|
||||
kept when saved, and renders with the newest process this build has. Update to Current Process never
|
||||
moves a photo to an older process; Reset does move it to this build's latest.
|
||||
|
||||
**A damaged value** (anything but a whole number from 0 to 2^32 - 1; `2.0` counts as 2) reads as V1,
|
||||
like a missing field, and costs nothing else: the rest of the settings load from the catalog, a
|
||||
snapshot, the op log or an `lc:settings` sidecar as usual. The damaged value isn't kept: the photo is
|
||||
saved as V1.
|
||||
|
||||
## Storage
|
||||
|
||||
The process is saved as `"process": <number>` in the settings JSON, except V1, which is left out.
|
||||
Settings made before process versions existed are therefore saved exactly as before, and their
|
||||
preview-cache keys (`DevelopSettings::hash64`) don't change. `develop.get` (MCP `get_develop`) always
|
||||
states the process.
|
||||
+4
-2
@@ -26,7 +26,7 @@ What we write (standard namespaces, so other tools can read the metadata):
|
||||
| Capture time, GPS | `exif:DateTimeOriginal`, `photoshop:DateCreated`, `exif:GPSLatitude`/`GPSLongitude` |
|
||||
| Pick / reject flag | `lc:flag` (`pick`, `reject`, `none`) |
|
||||
| Location | `lc:location` |
|
||||
| Develop settings | `lc:settings` — our complete `DevelopSettings` as JSON (exact round trip, incl. masks, spots, crop) |
|
||||
| Develop settings | `lc:settings` — our complete `DevelopSettings` as JSON (exact round trip, incl. masks, spots, crop and the rendering process; settings without `process` are V1, see [process-versions.md](process-versions.md)) |
|
||||
|
||||
`lc:` is `http://ns.lightcraft.app/lc/1.0/`.
|
||||
|
||||
@@ -100,7 +100,9 @@ Our pipeline renders differently, so **values carry over but the look is approxi
|
||||
We read these fields; we never write them. Only fields in the packet are applied: the result is a partial settings
|
||||
object that gets merged like a preset, so everything else keeps its current or default value. Packets marked
|
||||
`crs:AlreadyApplied="True"` are skipped, because those pixels already contain the edit. Only process-version 2012+ field
|
||||
names are read (e.g. `Exposure2012`, not the older `Exposure`).
|
||||
names are read (e.g. `Exposure2012`, not the older `Exposure`). `crs:ProcessVersion` itself numbers the other
|
||||
application's renderer and is ignored: the photo keeps LightCraft's own process, the latest for a newly imported photo
|
||||
([process-versions.md](process-versions.md)).
|
||||
|
||||
| `crs:` field(s) | LightCraft control | Notes |
|
||||
|---|---|---|
|
||||
|
||||
Reference in New Issue
Block a user