mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-01 05:57:09 -06:00
Merge origin/main into #447 (render cache version 22, as main is at 21; parity row keeps main's CR3 AspectInfo note)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+39
-1
@@ -42,6 +42,42 @@ The result is stored with the mask: the model's 288 × 288 mask logits over the
|
||||
need the model, so masks survive moving the library to a machine without it, and the web build
|
||||
renders them. Editing the photo's look later does not recompute a mask (click again to update).
|
||||
|
||||
## Remote Metal inference
|
||||
|
||||
Native builds with `sam` can keep the editor, RAW files, library, and exports local while
|
||||
a Mac runs SAM 3. Build both ends from the same revision. On the Mac, install the model
|
||||
as described below, then run:
|
||||
|
||||
```sh
|
||||
cargo build --release --locked -p lightcraft-segment --bin lightcraft-sam3-worker
|
||||
target/release/lightcraft-sam3-worker "$HOME/Library/Application Support/LightCraft/models/sam3" 127.0.0.1:8793
|
||||
```
|
||||
|
||||
On the editing computer, start an SSH tunnel in one terminal and LightCraft in another:
|
||||
|
||||
```sh
|
||||
ssh -N -T -o ExitOnForwardFailure=yes -L 127.0.0.1:8793:127.0.0.1:8793 user@your-mac
|
||||
LIGHTCRAFT_SAM3_REMOTE=127.0.0.1:8793 lightcraft
|
||||
```
|
||||
|
||||
Object clicks, Describe prompts, and detail refinement use the remote worker. Only the
|
||||
rendered model input (long edge at most 1008 pixels) and prompts cross the tunnel. The
|
||||
worker caches the current photo; detail crops do not replace that cache. Returned masks
|
||||
stay in the local library, so later adjustments and exports work without the connection.
|
||||
|
||||
The worker requires Metal and accepts one editing connection at a time. It loads weights
|
||||
on demand and releases them when the client disconnects or remains idle for ten minutes.
|
||||
Both endpoints require loopback addresses: SSH provides authentication and encryption.
|
||||
The listener has no independent authentication; do not expose it through a public proxy. Any
|
||||
process of any user on the worker Mac can connect to it, and on the editing computer a local
|
||||
process listening on the port while the tunnel is down receives the rendered model input, so use
|
||||
this only on machines whose local users you trust.
|
||||
|
||||
No local weights are required in remote mode. An unavailable worker produces an explicit
|
||||
error, not a silent CPU fallback. Restore the tunnel and retry. Unset
|
||||
`LIGHTCRAFT_SAM3_REMOTE` to use the local model again. This offloads segmentation only,
|
||||
not RAW development, denoising, or upscaling. The model licence below still applies.
|
||||
|
||||
## Getting the model
|
||||
|
||||
### In the app (recommended)
|
||||
@@ -150,10 +186,12 @@ Therefore:
|
||||
| `mask.objectPoint` | `{x, y, exclude?: bool, id?}` | one click on the selected mask's Object selection (≤ 64 clicks) |
|
||||
| `mask.refineDetail` | `{id?, component?}` | the zoomed-in detail pass → `{started}` |
|
||||
| `segment.prepare` | `{}` | load the model and analyze the active photo → `{busy}` |
|
||||
| `segment.model.status` | `{}` | `{available, installed, dir, loaded, busy, analyzing, sizeBytes, license, licenseUrl, mirrors, download}` |
|
||||
| `segment.model.status` | `{}` | `{available, installed, remote, dir, loaded, busy, analyzing, sizeBytes, license, licenseUrl, mirrors, download}` |
|
||||
| `segment.model.download` | `{acknowledged: true}` | start the download in the background → `{started}` |
|
||||
| `segment.model.cancel` | `{}` | stop it (it resumes next time) → `{cancelled}` |
|
||||
|
||||
`remote` reports the configured endpoint, not its health. `installed` describes local weights only.
|
||||
|
||||
Coordinates are normalized to the uncropped, oriented photo, like every mask shape. `seg` passes
|
||||
a stored segmentation (as `mask.list` / the develop settings hold it): the mask is made without
|
||||
the model. Without the model, the AI kinds fail with an error starting "The SAM 3 model is not
|
||||
|
||||
+16
-1
@@ -5,6 +5,12 @@ The CR3 adapter reads the CRX Bayer track, `CMP1` compression descriptor,
|
||||
black/white levels and the default crop come from the file. A Dual Pixel delta
|
||||
track is never substituted for the primary Bayer track.
|
||||
|
||||
The in-camera aspect ratio (3:2, 4:3, 16:9, 1:1) is not applied to the sensor data. The maker note's
|
||||
`AspectInfo` (tag `0x009a`: aspect code, cropped width, height, left, top) gives the shot's rectangle inside the
|
||||
recommended crop, and the decoder uses it as the default crop, so the developed image has the shape the photographer
|
||||
framed (PowerShot SX70 HS, EOS 250D, PowerShot G5 X Mark II, EOS M6 Mark II and EOS M50 samples). A rectangle that
|
||||
is missing or does not fit inside the crop is ignored. The embedded JPEG still shows the whole frame.
|
||||
|
||||
The decoder is independent Rust code shared by desktop, CLI and WebAssembly;
|
||||
it has no operating-system imaging dependency or external decoder. Its sources
|
||||
are the public prose descriptions and patents cited in
|
||||
@@ -55,7 +61,16 @@ The version `0x200` QP predictor and entropy framing were checked independently.
|
||||
Controlled input mutations establish floor averaging of paired QP rows before
|
||||
the nonlinear integer table, then multiplication, division by eight, addition
|
||||
of the subband base and a minimum step of one. QP values 131–167 are verified;
|
||||
unmeasured values and an odd QP-map height are rejected. Partial-subband and
|
||||
unmeasured values are rejected.
|
||||
|
||||
An odd QP-map height (EOS R7: 4732 sensor rows give 1183 QP rows) leaves the
|
||||
last row of the second-level high-frequency bands without a partner row; that
|
||||
row's QP is used on its own. The odd-height map decodes to the exact end of its
|
||||
entropy stream. This rule is not yet checked against a black-box reference:
|
||||
it was checked on paired R7 RAW and C-RAW captures of the same scene (ISO 100
|
||||
and 6400), whose developed images match visually and whose sensor means agree
|
||||
within 0.1% at ISO 6400. Files with even-height maps never reach this rule, so
|
||||
the six verified fixtures are unchanged. Partial-subband and
|
||||
rounded-plane coding, vertical C-RAW tiling and adaptive low-frequency gains
|
||||
remain unsupported. The decoder bounds image dimensions, sample count,
|
||||
tile count, marker headers and temporary allocations. The full sensor buffer is
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
# AI Denoise
|
||||
|
||||
Edit ▸ Detail ▸ **AI Denoise** is a per-photo switch above **Amount** (0–100).
|
||||
Turning it on starts at 50, or preserves an already positive Amount. Turning it off
|
||||
preserves Amount and mixes none of the cleaned picture. It affects only the requesting
|
||||
photo, independently of Auto Sync, and is undoable. Manual luminance and colour noise
|
||||
reduction remain separate controls.
|
||||
|
||||
If no model is selected, turning it on opens **Settings ▸ AI Denoise** without changing
|
||||
the photo. After installation, the pending request enables the original photo even if
|
||||
the selection changed. Cancel the pending action in Settings. Cancelled downloads,
|
||||
deleted or relinked photos, switching libraries and quitting cancel the request.
|
||||
An active slider gesture finishes before the pending action resumes.
|
||||
|
||||
## Models and terms
|
||||
|
||||
**No model weights are bundled.** Models live in the per-user `denoise-models` folder
|
||||
(override with `LIGHTCRAFT_DENOISE_MODELS`). Settings provides **Install from file…**
|
||||
for a user-supplied ONNX file with a `denoise-model.json` beside it. The default build
|
||||
offers no model downloads. The separate `rawnind-model` Cargo feature enables the
|
||||
pinned RawNIND offer only when a release's model policy permits it.
|
||||
Terms must be acknowledged before installation or
|
||||
download. Downloads use the shared pure-Rust `lightcraft-fetch` client, with resume,
|
||||
progress, cancellation, size limits and SHA-256 verification.
|
||||
|
||||
RawNIND's optional ONNX weights are **GPL-3.0**, as recorded in the pinned model catalog.
|
||||
They are downloaded separately after consent and read by our own Rust executor.
|
||||
Whether releases should offer this download remains a maintainer policy decision;
|
||||
permissively licensed or independently trained weights remain a gap. This is not a
|
||||
claim that optional downloading settles licensing questions. No GPL decoder or neural
|
||||
runtime code is included. Model metadata is in `crates/denoise-core/src/known.rs`.
|
||||
|
||||
Installation checks the manifest, model hash and size, then runs a synthetic self-test:
|
||||
finite output of the promised shape, repeatability, the declared scale and reduced
|
||||
noise. Unsupported operators, malformed files and a failed self-test return errors.
|
||||
The UI performs copying, extraction, hashing, parsing and CPU/GPU self-tests on one
|
||||
background installation worker. `denoise.models.downloads` and `denoise.pump` report
|
||||
`installing`, `installed` or `failed`; an existing model is preserved on a failed replacement.
|
||||
Command clients can request the same asynchronous path with `background: true`.
|
||||
The synchronous command remains available for scripts that require the result immediately.
|
||||
|
||||
The current contract is `bayerToRgb`: normalized, black-subtracted, non-white-balanced
|
||||
RGGB planes `[1, 4, tile, tile]` produce camera RGB `[1, 3, 2*tile, 2*tile]`.
|
||||
RawNIND uses a 512-cell tile, 64-cell overlap and a mean-matched nominal gain of 1e6.
|
||||
See `DenoiserManifest` for the validated JSON schema.
|
||||
|
||||
## Photos, previews and exports
|
||||
|
||||
Original RAW files stay untouched. A cleaned picture is cached under
|
||||
`<library>/denoise/<key>.lcdn`, keyed by source content, model and algorithm version.
|
||||
The compressed half-float cache has checked headers, bounded windows and checksums.
|
||||
It can be cleared or recreated; it is not a new catalog photo or a duplicate DNG.
|
||||
|
||||
Plain and cleaned camera RGB go through the same highlight reconstruction, white
|
||||
balance, colour matrix, DNG profile tables, camera hue/saturation look, crop and
|
||||
orientation. Amount blends the two before the remaining develop pipeline, so edits
|
||||
and exports use the same data. At zero Amount or with Detail disabled, the ordinary
|
||||
render path remains in use.
|
||||
|
||||
The queue prioritizes the open and selected photos, pauses after input, and adjusts
|
||||
its worker count while the user works. Progress, cancellation and retry are available
|
||||
in Settings and through commands. The default cache limit is 20 GB; unused products
|
||||
are evicted oldest first. A failed file is not continuously retried.
|
||||
|
||||
An export with a nonzero effective Amount creates a missing product synchronously.
|
||||
It returns a clear error if it cannot produce the denoised result; it never silently
|
||||
exports the plain image instead. Unsupported sensor layouts retain manual noise
|
||||
reduction and report their limitation.
|
||||
|
||||
## Pure-Rust CPU and GPU execution
|
||||
|
||||
The CPU executor reads a bounded subset of ONNX into checked network data. Convolutions
|
||||
use bounded im2col blocks and `faer-core 0.17.1` with its pure-Rust GEMM backend,
|
||||
without BLAS, C dependencies or an inner thread pool. That backend includes
|
||||
architecture-specific assembly in its dependencies. Tiles share the outer worker
|
||||
pool. A scalar implementation checks the optimized operators on synthetic networks.
|
||||
Tract is not a workspace dependency or a product feature.
|
||||
CPU inference and the GPU model module are feature-gated; ordinary engine/web builds
|
||||
do not compile the faer/GEMM backend. The engine depends on the inference crate only through `denoise = ["dep:lightcraft-denoise", …]`;
|
||||
lightweight `lightcraft-denoise-core` metadata and cache formats remain available without inference. Pictures are capped at 100 megapixels; at most
|
||||
four tile calls run together, chosen within the process-wide working-memory budget.
|
||||
One idle CPU workspace is retained, and cache costs reserve room for the blended picture.
|
||||
|
||||
The GPU executor uses our WGSL kernels through wgpu. It checks a real model's output
|
||||
against the CPU and falls back on errors, unsupported networks or a slower device.
|
||||
Setup has a time limit and a crash marker. Automatic selection estimates throughput
|
||||
from a check tile; this estimate can choose imperfectly on unmeasured hardware.
|
||||
Settings offers Automatic, Graphics card and Processor. GPU tests cover supported
|
||||
synthetic networks against the scalar executor; a real-model test compares CPU/GPU.
|
||||
|
||||
### Historical performance, measured 2026-10-07
|
||||
|
||||
These are the contributor's measurements **before the current upstream integration**,
|
||||
on a Ryzen 9 7945HX and RTX 4090 Laptop GPU (DX12), release build, model SHA-256
|
||||
`da27509dab6a2915da67e988acd86cf71f9d5bbc8d1aa0ed32933578a887b901`.
|
||||
The 24.34 MP Sony a7 III photo is 35 tiles. Numbers exclude model loading, source
|
||||
decode, GPU setup, cache encoding and export, but include packing and blending.
|
||||
|
||||
| Work | Pure-Rust CPU | Prior Tract reference | GPU |
|
||||
|---|---:|---:|---:|
|
||||
| One warm 512-cell tile, one caller | 1148 ms | 1463 ms | 15.1 ms |
|
||||
| Whole photo, 16 CPU / 4 GPU feeding workers | 5.185 s | 9.534 s | 0.582 s |
|
||||
|
||||
The largest relative differences were 1.68e-6 CPU/reference and 1.57e-6 CPU/GPU for
|
||||
the check tile, below the required 1e-3 tolerance. Reference comparisons were made
|
||||
on the older contributor branch; that reference runtime is intentionally excluded
|
||||
from the current workspace. These numbers are not a fresh benchmark of this rebase.
|
||||
|
||||
Fresh checks can be run using an already installed model and CC0 raw samples:
|
||||
|
||||
```text
|
||||
LC_DENOISE_MODEL=<model.onnx> cargo test --release -p lightcraft-gpu --lib real_model_on_the_gpu_matches_cpu -- --ignored --nocapture
|
||||
LC_DENOISE_MODEL=<model.onnx> LC_DENOISE_RAW=<corpus/raw> cargo test --release -p lightcraft-engine --features rawnind-model --test denoise_cpu -- --ignored --nocapture
|
||||
LC_DENOISE_MODEL=<model.onnx> LC_DENOISE_RAW=<corpus/raw> cargo test --release -p lightcraft-engine --features rawnind-model --test denoise_real -- --ignored --nocapture
|
||||
```
|
||||
|
||||
## Commands and limits
|
||||
|
||||
UI, CLI, control channel and MCP dispatch `denoise.toggle`, `denoise.settings`,
|
||||
`denoise.queue`, `.cancel`, `.status`, `.pump`, `.clear`, and `denoise.models.*`
|
||||
(`list`, `install`, `download`, `downloads`, `downloadCancel`, `test`, `remove`,
|
||||
`select`). The develop control is `enhance.denoise`.
|
||||
|
||||
Native apps enable inference through their `denoise` feature. A build without it and
|
||||
the web build report inference unavailable. Manifest/cache primitives still compile
|
||||
for WASM. Settings and models remain opt-in; nothing is downloaded on launch.
|
||||
|
||||
This implementation supports **Bayer RAW only**. X-Trans, Foveon, demosaiced DNG and
|
||||
non-RAW images remain unsupported by this model contract. No measured quality parity
|
||||
with Lightroom is claimed. Apple/Metal and wider hardware verification, model policy,
|
||||
a linear-RGB contract and a side-by-side fidelity suite remain future work.
|
||||
@@ -44,6 +44,17 @@ Overrides (read once at launch):
|
||||
the compute device always used Vulkan + DX12 (+ Metal).
|
||||
- `LIGHTCRAFT_GPU=0`: GPU rendering off for the process (the window is unaffected).
|
||||
|
||||
**DX12 shader compiler (issue #471).** Every instance (window and compute) compiles DX12 shaders with
|
||||
FXC (`d3dcompiler_47.dll`, part of Windows; `lightcraft_gpu::backend::backend_options`). wgpu's
|
||||
default, `Auto`, loads whichever `dxcompiler.dll` the DLL search path finds first. LightCraft ships
|
||||
none, so that copy belongs to another program (a Windows SDK, a folder on `PATH`). An older copy without
|
||||
`dxil.dll` beside it (e.g. DXC 1.7) warns that the DXIL is unsigned, wgpu treats the warning as a
|
||||
compile error, its indirect-validation pipelines fail, the device is lost, and the window never opened
|
||||
(`WGPU error: Parent device is lost`). `WGPU_DX12_COMPILER=dxc | auto | fxc` (wgpu's variable) still
|
||||
chooses; with `dxc`, put `dxcompiler.dll` *and* `dxil.dll` (DXC ≥ 1.8.2502) next to `lightcraft.exe`.
|
||||
FXC builds the compute kernels somewhat slower than DXC (≈ 9 s vs 7.5 s for the GPU test suite on an
|
||||
RTX 4080), off the startup path.
|
||||
|
||||
**Off the startup path.** The compute device is created on a background thread once the window is
|
||||
up (`gpu::warm_up` from the first frame's settings), and never while GPU rendering is off: with
|
||||
Settings ▸ Performance ▸ *Use the GPU for rendering* unchecked (applied before the window opens),
|
||||
|
||||
+9
-9
@@ -26,7 +26,7 @@ and prints the summary; `cargo xtask parity --write` refreshes the summary table
|
||||
| C. Views & navigation (VIEW) | 15 | 1 | 1 | 0 | 8/9 (89%) | 4/4 (100%) |
|
||||
| D. Search & filter (FILT) | 11 | 1 | 1 | 0 | 4/4 (100%) | 4/4 (100%) |
|
||||
| E. Metadata (META) | 5 | 1 | 0 | 0 | 2/2 (100%) | 2/2 (100%) |
|
||||
| F. Edit panel — global adjustments (EDIT) | 42 | 1 | 5 | 1 | 28/28 (100%) | 13/14 (93%) |
|
||||
| F. Edit panel — global adjustments (EDIT) | 42 | 2 | 4 | 1 | 28/28 (100%) | 13/14 (93%) |
|
||||
| G. Profiles (PROF) | 7 | 1 | 3 | 0 | 3/4 (75%) | 3/3 (100%) |
|
||||
| H. Crop & rotate (CROP) | 9 | 0 | 0 | 1 | 6/6 (100%) | 3/3 (100%) |
|
||||
| I. Remove / healing (REM) | 7 | 1 | 2 | 2 | 4/4 (100%) | 2/3 (67%) |
|
||||
@@ -48,9 +48,9 @@ and prints the summary; `cargo xtask parity --write` refreshes the summary table
|
||||
| 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 | 37 | 85 | 36 | 193/200 (97%) | 142/152 (93%) |
|
||||
| **Total** | 394 | 38 | 84 | 36 | 193/200 (97%) | 142/152 (93%) |
|
||||
|
||||
Weighted completion (✅ = 1, 🟡 = ½, 🚫 left out): **79.9%** of 516 in-scope rows — P0 98.2% of 200 · P1 95.7% of 152 · P2 42.6% of 163.
|
||||
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.
|
||||
<!-- /parity:summary -->
|
||||
|
||||
## Top gaps
|
||||
@@ -142,7 +142,7 @@ under the UI command that wraps it (a no-op error); the UI command now wins.
|
||||
| LR-IMP-PROFILES | Import profiles & presets | P1 | ✅ | `cmd:file.importPresets`, `cmd:preset.import`, `cmd:profile.import`, `crates/engine/src/preset_import.rs`, `crates/engine/src/preset_luminar.rs` | presets: .lcpreset, XMP, classic .lrtemplate, photos carrying edits ("DNG presets"), Luminar looks (.lmp, .mplumpack collections; sliders with a counterpart), .zip bundles, folders (folder → group), drag & drop; masks carried over; unmapped settings reported (fields that only describe the preset or photo, e.g. `Cluster`, `SortName`, `SupportsAmount2`, the as-shot white, empty Point Color slots, are not); profiles: .cube 3D LUTs; Adobe profile formats deliberately unsupported |
|
||||
| LR-IMP-LOCAL | Work on files in place | P0 | ✅ | `cmd:library.browse`, `cmd:photo.addToLibrary`, `cmd:library.import` (mode add), `cmd:local.addRoot`, `cmd:local.hide`, `cmd:local.restoreHidden`, `crates/engine/src/cmd/browse.rs`, `crates/ui-egui/src/panels/left.rs` (`local_section`), `crates/ui-egui/src/panels/grid.rs` (`folder_header`) | Local: browse Pictures / Desktop / Downloads / Home or any folder without adding it (breadcrumb, Include subfolders, Add N to My Photos); browsed photos stay out of All Photos, albums and counts; folders picked with Browse Folder… (or Keep in Local / Add to Local on a folder row) stay listed across restarts, and browsing a folder inside a listed one highlights it inside that tree (opened down to it, siblings reachable) instead of adding a root; edits go to XMP sidecars; importing promotes them; right-click a top-level location → Remove from Local hides the shortcut only (nothing on disk or in the catalog changes), saved with the UI state, and “Show N hidden locations” (or Browse Folder… on it) brings it back; a folder is one location however its path is spelled (`/` or `\`, trailing separator, `.`/`..`, drive-letter case; `folder_key` in `crates/catalog/src/query.rs`); browsed photos nobody changed are forgotten once their folder has not been browsed for 30 days (Settings → Performance → Local folders, 0 = never; checked when the library opens, or `cmd:library.forgetLocal` with `dryRun`; files and sidecars stay, browsing again brings them back; see `crates/catalog/src/local.rs` for what counts as a change) |
|
||||
| LR-IMP-SIDECAR-SPLIT | Separate XMP sidecar variants | P2 | ⬜ | `cmd:library.xmpPreferences` | sidecar naming option exists (stem/full), no split sidecars |
|
||||
| LR-IMP-FORMATS | Supported formats | P0 | 🟡 | `crates/codecs/src/lib.rs`, `crates/raw/src/lib.rs`, `crates/raw/src/jxl.rs` | JPEG, PNG, TIFF (1- to 64-bit integer, 16- to 64-bit float, incl. bilevel and CCITT fax), WebP, JXL, PSD, GIF, BMP; DNG (incl. lossy / Smart Preview DNG and DNG 1.7 JPEG XL tiles/strips: lossless ones decode bit-exactly on synthetic CFA and LinearRaw files, lossy ones are restored to the declared linear primaries without clipping or gamut mapping, checked on one real file with 108 lossy tiles, and any tile that fails to decode makes the file preview-only; a single-chunk JPEG XL preview IFD is offered as the embedded preview), CR2 (colour-filter layout from the file's `CR2CFAPattern` tag, issue #85), ARW (pre-2017 bodies: white balance and black level from enciphered maker-note / `SR2SubIFD` data, issue #148; downsized Sony lossless YCbCr 4:2:0 / 4:2:2 tiles decoded to linear RGB, verified on private ILCE-7M4 3:2 & 4:3 examples including 16-bit full-resolution export), NEF (uncompressed, lossless and lossy compressed 12/14-bit, `crates/raw/src/vendor/nefc.rs`), RAF (uncompressed, lossless/lossy compressed Bayer and X-Trans; `crates/raw/src/vendor/rafc.rs`, full sensor verification on 13 bodies, `docs/raf-compression.md`), RW2 / Leica RWL / Panasonic RAW in every raw format (compressed 4 and 6, the prefix-coded strips of format 8 from the GH6 on, packed 2/5/7, the 16-bit words of the 2005–2007 bodies; black levels, in-camera aspect crops and mapped-out defect pixels; 178 CC0 files from 118 bodies checked, `crates/raw/src/vendor/rw2.rs`), PEF, ORF (uncompressed; colour-filter layout from the file's Exif `CFAPattern`). 12-bit NEF black level: maker note `0x003d` is in 14-bit units (D750, D780, D850, D7500, Z 50 checked). Lossy type 2 NEF curves of version `0x44 0x40` (D780, D6, Z 5, Z 6, Z 7, Z 7_2) span a quarter of the sample range, read from the table's own point spacing. CR3 lossless CRX Bayer and version 0x100/0x200 C-RAW decodes to sensor pixels with timed white balance, black/white levels and file crop; full sensor samples match the reference on M50/R100/R8 (`crates/raw/tests/cr3_corpus.rs`, `docs/cr3.md`). Unsupported coding variants retain metadata and use the embedded JPEG when available; HEVC preview decoding remains unsupported. Missing: CR3 unverified CRX variants, compressed ORF, NEFs labelled compressed (34713) but stored uncompressed without a `0x0096` table (Z 6 packed 14-bit, D850 12-bit uncompressed), NEF "lossy after split" (preview only: imported with `preview_only` = the decoder's reason, rendered as a rendered JPEG, flagged by a grid badge and loupe/Edit/Info notices, `previewOnly` in `catalog.query`; Reload clears it once the file decodes), TIFF-based raws whose raw data sits in a private block (Phase One / Leaf IIQ, Canon EOS-1D / 1Ds and Kodak DCS TIFFs: IFD0 is only a reduced RGB image next to a much larger declared picture) are recognised from that structure and import as `preview_only` with the reason, instead of opening as a thumbnail-sized ordinary image (`thumbnail_shell` in `crates/raw/src/lib.rs`), Recognised raws LightCraft cannot decode open as `preview_only` with any usable embedded JPEG, also Canon CRW, Minolta MRW and Sigma X3F (containers recognised by magic, preview found by a structural JPEG scan), Kodak KDC (IFD0 without `ImageWidth`), Leaf MOS and Epson ERF (scan), and any decode failure of a recognised raw, not only an unsupported variant; ones with no JPEG still fail with "without an embedded preview", AVIF decode (HEIC decodes only behind the non-default `heif` feature: `crates/heif`, heic-rs, same policy as PhotoCraft; 8/10-bit, alpha, irot/imir/clap, ICC/EXIF/XMP), palette (indexed-colour) TIFF (refused by the `tiff` crate) |
|
||||
| LR-IMP-FORMATS | Supported formats | P0 | 🟡 | `crates/codecs/src/lib.rs`, `crates/raw/src/lib.rs`, `crates/raw/src/jxl.rs` | JPEG, PNG, TIFF (1- to 64-bit integer, 16- to 64-bit float, incl. bilevel and CCITT fax), WebP, JXL, PSD, GIF, BMP; DNG (incl. lossy / Smart Preview DNG and DNG 1.7 JPEG XL tiles/strips: lossless ones decode bit-exactly on synthetic CFA and LinearRaw files, lossy ones are restored to the declared linear primaries without clipping or gamut mapping, checked on one real file with 108 lossy tiles, and any tile that fails to decode makes the file preview-only; a single-chunk JPEG XL preview IFD is offered as the embedded preview), CR2 (colour-filter layout from the file's `CR2CFAPattern` tag, issue #85), ARW (pre-2017 bodies: white balance and black level from enciphered maker-note / `SR2SubIFD` data, issue #148; downsized Sony lossless YCbCr 4:2:0 / 4:2:2 tiles decoded to linear RGB, verified on private ILCE-7M4 3:2 & 4:3 examples including 16-bit full-resolution export), NEF (uncompressed, lossless and lossy compressed 12/14-bit, `crates/raw/src/vendor/nefc.rs`), RAF (uncompressed, lossless/lossy compressed Bayer and X-Trans; `crates/raw/src/vendor/rafc.rs`, full sensor verification on 13 bodies, `docs/raf-compression.md`), RW2 / Leica RWL / Panasonic RAW in every raw format (compressed 4 and 6, the prefix-coded strips of format 8 from the GH6 on, packed 2/5/7, the 16-bit words of the 2005–2007 bodies; black levels, in-camera aspect crops and mapped-out defect pixels; 178 CC0 files from 118 bodies checked, `crates/raw/src/vendor/rw2.rs`), PEF, ORF (uncompressed; colour-filter layout from the file's Exif `CFAPattern`). 12-bit NEF black level: maker note `0x003d` is in 14-bit units (D750, D780, D850, D7500, Z 50 checked). Lossy type 2 NEF curves of version `0x44 0x40` (D780, D6, Z 5, Z 6, Z 7, Z 7_2) span a quarter of the sample range, read from the table's own point spacing. CR3 lossless CRX Bayer and version 0x100/0x200 C-RAW decodes to sensor pixels with timed white balance, black/white levels and file crop (the shot's aspect ratio from the maker note's `AspectInfo`); full sensor samples match the reference on M50/R100/R8 (`crates/raw/tests/cr3_corpus.rs`, `docs/cr3.md`). Unsupported coding variants retain metadata and use the embedded JPEG when available; HEVC preview decoding remains unsupported. Missing: CR3 unverified CRX variants, compressed ORF, NEFs labelled compressed (34713) but stored uncompressed without a `0x0096` table (Z 6 packed 14-bit, D850 12-bit uncompressed), NEF "lossy after split" (preview only: imported with `preview_only` = the decoder's reason, rendered as a rendered JPEG, flagged by a grid badge and loupe/Edit/Info notices, `previewOnly` in `catalog.query`; Reload clears it once the file decodes), TIFF-based raws whose raw data sits in a private block (Phase One / Leaf IIQ, Canon EOS-1D / 1Ds and Kodak DCS TIFFs: IFD0 is only a reduced RGB image next to a much larger declared picture) are recognised from that structure and import as `preview_only` with the reason, instead of opening as a thumbnail-sized ordinary image (`thumbnail_shell` in `crates/raw/src/lib.rs`), Recognised raws LightCraft cannot decode open as `preview_only` with any usable embedded JPEG, also Canon CRW, Minolta MRW and Sigma X3F (containers recognised by magic, preview found by a structural JPEG scan), Kodak KDC (IFD0 without `ImageWidth`), Leaf MOS and Epson ERF (scan), and any decode failure of a recognised raw, not only an unsupported variant; ones with no JPEG still fail with "without an embedded preview", AVIF decode (HEIC decodes only behind the non-default `heif` feature: `crates/heif`, heic-rs, same policy as PhotoCraft; 8/10-bit, alpha, irot/imir/clap, ICC/EXIF/XMP), palette (indexed-colour) TIFF (refused by the `tiff` crate) |
|
||||
| LR-IMP-CAMERA-COVERAGE | Camera coverage, verified per model | P0 | 🟡 | `crates/raw/tests/corpus.rs`, `xtask/src/main.rs` | a supported container is not the same as every camera that writes it decoding correctly: ~55 CC0 corpus files plus 6 exact-sensor CR3 cases (plus ~60 NEFs checked by hand and 178 Panasonic / Leica RW2, RWL and RAW files from 118 bodies, checked against each file's own JPEG) against the >1,000 models Lightroom lists; per-model differences still surface (e.g. the CR2 colour-filter layout differed by model until it was read from the file's own tag, issue #85, checked on 35 CR2 bodies; pre-2017 Sony ARWs opened green until their scrambled white balance / black level were read, issue #148, checked on 16 ARWs from 15 Sony bodies; Canon PowerShot and EOS M CR2s whose `ColorData` is stored as bytes opened green until the bytes were read as 16-bit words, 25 files from 25 bodies checked). Compressed RAF sensor arrays verified on 13 Fujifilm bodies (`docs/raf-compression.md`). Grow the CC0 corpus per model and test each sample decodes with plausible colour. Nikon CropArea and untagged-preview colour-space regression tests; private Z 8 crop verified. |
|
||||
| LR-IMP-CULL-AT-IMPORT | Culling analysis at import | P2 | 🟡 | `cmd:photo.analyze` | run Assisted Culling on the imported photos (they're selected after an import); not automatic |
|
||||
| LR-IMP-MOVE | Move on import [Classic] | P1 | ✅ | `cmd:library.import` (`mode` move), `crates/engine/src/import_move.rs`, `crates/engine/src/tests_import_move.rs`, `crates/ui-egui/src/import.rs` | Transfer ▸ Move with Copy's destination, folders (day / month / one folder / custom template), rename template and example destination; XMP sidecars (both namings) move along; each source is removed only after its destination is written (hard link on the same volume, else copied, synced and compared byte for byte) and its catalog record is saved; failed, duplicate, unchecked files keep their sources, taken names get -1, -2…, never overwritten; files already in the destination/library are added in place; sources that can't be removed (read-only card) are kept and reported (`kept`). Undo removes the photos from the library but leaves the moved files at the destination. No Copy as DNG while moving |
|
||||
@@ -155,8 +155,8 @@ under the UI command that wraps it (a no-op error); the UI command now wins.
|
||||
| LR-LIB-ALLPHOTOS | All photos | P0 | ✅ | `cmd:library.source`, `crates/ui-egui/src/panels/left.rs` | |
|
||||
| LR-LIB-RECENT-ADDED | Recently added | P1 | ✅ | `cmd:library.source` (`recentlyAdded`), `crates/engine/src/view.rs` (`RECENT_DAYS`) | photos imported in the 30 days up to the latest import, newest import first, grouped by import day |
|
||||
| LR-LIB-BYDATE | Browse by date | P1 | ✅ | `cmd:library.filter` (`date`), `crates/ui-egui/src/panels/left.rs` (`date_row`), `crates/catalog/src/query.rs` (`date_groups`) | year → month → day tree with library-wide counts; a row filters the current source by that date. Active filters show as removable chips under the grid header (`crates/ui-egui/src/panels/chips.rs`, with “+N more” and Clear all), the header counts “N of M photos” (matching of the source's total), a badge on the top-bar filter icon counts them, and an empty result says no photos match the active filters |
|
||||
| LR-LIB-ALBUM | Albums | P0 | ✅ | `cmd:album.create`, `cmd:album.rename`, `cmd:album.delete`, `cmd:album.addPhotos`, `cmd:album.removePhotos`, `cmd:dialog.newAlbum` | no drag photos onto album (LR-BEHAV-DRAGDROP), no album sort |
|
||||
| LR-LIB-FOLDER | Folders of albums | P0 | ✅ | `cmd:album.create` (`folder`), `cmd:album.move` | moving is command-only (no drag, no "Move to" menu) |
|
||||
| LR-LIB-ALBUM | Albums | P0 | ✅ | `cmd:album.create`, `cmd:album.rename`, `cmd:album.delete`, `cmd:album.addPhotos`, `cmd:album.removePhotos`, `cmd:dialog.newAlbum` | photos dragged from the grid onto an album row are added to it (LR-BEHAV-DRAGDROP) |
|
||||
| LR-LIB-FOLDER | Folders of albums | P0 | ✅ | `cmd:album.create` (`folder`, `parent`), `cmd:album.move`, `cmd:album.reorder`, `cmd:album.sort`, `cmd:dialog.newAlbum` / `cmd:dialog.newFolder` / `cmd:dialog.smartAlbum` / `cmd:dialog.newSmartAlbum` (`parent`), `crates/ui-egui/src/panels/left.rs` (`albums_tree`, `folder_menu`, `disclosure_triangle`), `crates/ui-egui/src/tests_panels.rs` | folders nest and fold with the same disclosure triangle as Local / By Date / Folders / Keywords (`albumToggle:<id>`; the row folds them too; open by default); right-click a folder ▸ New ▸ Create Album… / Create Smart Album… / Create Smart Album from Filter… / Create Folder… creates inside it (the `dialog.new*` commands take `parent`), opening the folders down to the new row; creating is only allowed inside folders; moving: drag an album or folder row onto a folder (outlined when allowed; a closed folder opens after 0.6 s under the pointer; the "Albums" header takes an item that sits in a folder back to the top level while it is dragged), the Move to menu or `album.move` — one undo step each; not onto a plain album, itself or something inside it; drop on the top / bottom half of an album or the edge of a folder to place it there by hand (`cmd:album.reorder`, a line shows the place; ordered per folder, folders stay first, new albums go after the ordered ones; right-click ▸ Sort Contents A–Z or + ▸ Sort Albums A–Z = `cmd:album.sort`) |
|
||||
| LR-LIB-SMARTALBUM | Smart albums | P1 | ✅ | `cmd:album.createSmart`, `cmd:album.setRules`, `crates/catalog/src/query.rs`, `crates/ui-egui/src/panels/filterbar.rs`, `crates/ui-egui/src/lib.rs` (`album_counts`) | saved filters (rating/flag/label/kind/edited/keyword/camera/lens/date range/text/album), live; match-all only (no any/none rule groups, no rule editor dialog — rules come from the filter bar or `album.setRules`); sidebar counts are cached per catalog revision (and per minute while an “in the last…” rule is used), counted without building id lists |
|
||||
| LR-LIB-SHARED-ALBUM | Shared albums | P2 | ⬜ | | needs a sharing service |
|
||||
| LR-LIB-OFFLINE | Keep album offline | P2 | 🚫 | | not applicable: local-first library |
|
||||
@@ -267,7 +267,7 @@ under the UI command that wraps it (a no-op error); the UI command now wins.
|
||||
| LR-EDIT-DETAIL-SHARPEN | Sharpening | P0 | ✅ | `ctl:detail.sharpenAmount`, `ctl:detail.sharpenRadius`, `ctl:detail.sharpenDetail`, `ctl:detail.sharpenMasking` | no Alt-drag mask preview |
|
||||
| LR-EDIT-DETAIL-NR | Luminance noise reduction | P0 | ✅ | `ctl:detail.nrLuminance`, `ctl:detail.nrDetail`, `ctl:detail.nrContrast` | |
|
||||
| LR-EDIT-DETAIL-CNR | Colour noise reduction | P0 | ✅ | `ctl:detail.nrColor`, `ctl:detail.nrColorDetail`, `ctl:detail.nrColorSmoothness` | |
|
||||
| LR-EDIT-DETAIL-DENOISE | AI denoise | P2 | ⬜ | | settings field reserved, not rendered |
|
||||
| LR-EDIT-DETAIL-DENOISE | AI denoise | P2 | 🟡 | cmd:denoise.toggle, cmd:denoise.models.install, cmd:denoise.queue, ctl:enhance.denoise, `crates/denoise/`, `docs/denoise.md` | non-destructive Bayer RAW, opt-in weights, pure-Rust CPU/GPU; X-Trans/linear RGB, model policy and Lightroom fidelity remain |
|
||||
| LR-EDIT-DETAIL-RAWDETAILS | Improved demosaic toggle | P2 | ⬜ | | |
|
||||
| LR-EDIT-DETAIL-SUPERRES | Super resolution | P2 | ⬜ | | |
|
||||
| LR-EDIT-DETAIL-AISHARPEN | AI sharpen | OOS | 🚫 | | |
|
||||
@@ -518,7 +518,7 @@ under the UI command that wraps it (a no-op error); the UI command now wins.
|
||||
| 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-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`) | files → app (import); grid photos → an album row (adds the selection, with a count badge while dragging) |
|
||||
| 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 |
|
||||
| LR-BEHAV-PANEL-RESIZE | Resizable side panels | P1 | ✅ | `crates/ui-egui/src/panels/mod.rs` (`resizable_side`), `crates/ui-egui/src/state.rs` (`LEFT_WIDTH`, `RIGHT_WIDTH`), `crates/ui-egui/src/tests_panels.rs` | drag the left sidebar's right edge (200–480 pt) or the right panel's left edge (250–520 pt); the photo area keeps ≥ 360 pt; widths are kept across panels, views and restarts (`leftWidth` / `rightWidth` in the UI state); the Presets column stays fixed |
|
||||
@@ -628,7 +628,7 @@ visible menu bar yet: the menu model is only exposed through the control channel
|
||||
| MENU-HELP-SYSINFO | System info | P2 | ✅ | `cmd:app.systemInfo`, `cmd:library.info` | Help ▸ System Info…: version, OS, CPU threads, GPU, memory budget, preview size, library, timings; Copy to Clipboard; JSON for agents (`open: false`) |
|
||||
| MENU-CTX-GRID | Photo context menu | P0 | ✅ | `crates/ui-egui/src/panels/grid.rs` (`context_menu`) | rate, flag, label, add to / remove from album, rename, virtual copy, version, stack, copy / paste / paste selected, reset, merge, rotate, show in Finder, export / export with preset, set as album cover (in an album), delete (restore / delete permanently in Recently Deleted) |
|
||||
| MENU-CTX-DETAIL | Loupe context menu | P1 | ✅ | `crates/ui-egui/src/panels/detail.rs` | Zoom submenu (fit, 100%, in, out), then the photo menu |
|
||||
| MENU-CTX-ALBUM | Album / folder row menu | P0 | ✅ | `crates/ui-egui/src/panels/left.rs` (`folder_menu`), `cmd:album.move`, `cmd:dialog.export` | add selected, export album (dialog / preset), move to a folder or the top level, rename, delete; smart albums: update rules |
|
||||
| MENU-CTX-ALBUM | Album / folder row menu | P0 | ✅ | `crates/ui-egui/src/panels/left.rs` (`folder_menu`), `cmd:album.move`, `cmd:dialog.export` | add selected, export album (dialog / preset), New ▸ Create Album / Smart Album / Smart Album from Filter / Folder (on a folder: made inside it), move to a folder or the top level, rename, delete; smart albums: update rules |
|
||||
| MENU-CTX-MASK | Mask / component menu | P0 | ✅ | `crates/ui-egui/src/panels/masking.rs` (`mask_menu`, `component_row_menu`), `cmd:mask.component` | mask rows: duplicate (and invert), invert, show/hide, move, rename, delete; component rows (right-click or "…"): invert, duplicate, mode (add / subtract / intersect), intersect with / subtract a new component, rename (also double-click), delete (the last one deletes the mask) |
|
||||
| MENU-CTX-PRESET | Preset menu | P1 | ✅ | `crates/ui-egui/src/panels/presets.rs` | favourite, update with current settings, rename, move to group, delete; group: export |
|
||||
| MENU-CTX-PROFILE | Profile favourites | P2 | ✅ | `cmd:profile.favorite`, `crates/ui-egui/src/panels/profiles.rs` | star in the profile browser; Favorites group first |
|
||||
|
||||
+2
-2
@@ -122,8 +122,8 @@ are the same in every version, so serve them with `Cache-Control: no-cache` (not
|
||||
a reload); a notice says why.
|
||||
- One tab at a time: the page holds a Web Lock (`navigator.locks`, `lightcraft-library`); a
|
||||
second tab or window shows "LightCraft is already open in another tab" instead of loading
|
||||
its own copy (two copies would overwrite each other's saves). Browsers without Web Locks
|
||||
aren't protected.
|
||||
its own copy (two copies would overwrite each other's saves). Browsers without Web Locks, or
|
||||
that refuse the lock request, aren't protected: the app starts without the guard.
|
||||
- If the stored library can't be opened, a notice says the session is temporary and nothing
|
||||
is saved; the stored library is left as it was.
|
||||
- A panic (wasm is built with `panic = "abort"`) replaces the page with a message saying the
|
||||
|
||||
@@ -2,6 +2,23 @@
|
||||
|
||||
## October 2026
|
||||
|
||||
### Albums tree
|
||||
- Album folders fold with the same disclosure triangle as Local, By Date, Folders and Keywords.
|
||||
- Right-click a folder ▸ New ▸ Create Album… / Create Smart Album… / Create Smart Album from Filter… / Create Folder…
|
||||
makes it inside that folder (`dialog.newAlbum`, `dialog.newFolder`, `dialog.smartAlbum`, `dialog.newSmartAlbum` take `parent`).
|
||||
- Drag an album or folder onto a folder to move it there (a closed folder opens after 0.6 s under the pointer; a
|
||||
"Albums" header takes it back to the top level; Esc cancels; holding a dragged album or photos near the top or bottom edge of the sidebar scrolls it). Drop on the top or bottom half of another album,
|
||||
or the edge of another folder, to place it before or after it: that folder is then ordered by hand. Right-click ▸
|
||||
Sort Contents A–Z (or + ▸ Sort Albums A–Z at the top level) goes back to by name. To put a folder after an open
|
||||
folder, fold that folder first (the bottom of an open folder means "inside"). Agents use `album.reorder`
|
||||
(`id`, `parent?`, `before?`) and `album.sort`.
|
||||
- The catalog format is now version 3 (`Album.order`, `Op::SetAlbumOrder`). Once this version has opened a library,
|
||||
older versions refuse it, as with every format change.
|
||||
|
||||
### AI RAW denoise
|
||||
- Detail has a per-photo AI Denoise switch and Amount. Bayer RAW photos use a disposable cache, with matching previews and exports and pure-Rust CPU/GPU inference.
|
||||
- Models are installed separately after accepting their terms; no weights are bundled. See [setup and limitations](denoise.md).
|
||||
|
||||
### Library keyboard culling
|
||||
- Colour labels tint thumbnail surrounds in Square Grid and the Detail filmstrip, and a translucent footer along the bottom of the photo in Photo Grid. Label confirmations use a matching pale colour.
|
||||
- Setting or clearing a colour label shows a brief bottom confirmation, like rating a photo; custom label names appear in the message too.
|
||||
@@ -33,6 +50,8 @@
|
||||
are unchanged; photos imported before this change get it when imported again.
|
||||
- Canon CR3 raws now develop from their sensor data: lossless RAW and C-RAW, checked sample for sample on the EOS
|
||||
M50, R100 and R8. CR3 files the decoder can't read yet still open from their embedded JPEG, as before.
|
||||
- Canon EOS R7 C-RAW files develop from their sensor data too, instead of opening from their embedded JPEG. For R7
|
||||
C-RAWs imported earlier, Photo ▸ Reload from Disk picks up the raw data.
|
||||
- Canon CRW, Minolta MRW, Sigma X3F, Kodak KDC, Leaf MOS and Epson ERF files that LightCraft can't decode yet
|
||||
now import as "preview only" with their embedded JPEG instead of failing. A raw whose data is damaged but whose
|
||||
preview is intact does the same.
|
||||
@@ -133,6 +152,9 @@
|
||||
ignored before) chooses the graphics backend; `off` renders on the CPU. The GPU now starts after the window
|
||||
is up, and only when Settings ▸ Performance ▸ Use the GPU for rendering is on; if LightCraft ever dies while
|
||||
starting the GPU, the next launch starts with GPU rendering off and says how to turn it back on.
|
||||
- The Windows app no longer quits at launch with "Parent device is lost" when another program's `dxcompiler.dll` is
|
||||
on the DLL search path without its `dxil.dll` (issue #471): DirectX 12 shaders now always compile with the
|
||||
compiler built into Windows (FXC). `WGPU_DX12_COMPILER=dxc` uses a `dxcompiler.dll` instead.
|
||||
- Exports and renders never write over a photo's original (issue #93): exporting into the photo's own folder with
|
||||
the same name and "Overwrite" (or Export with Previous repeating it), an exact output path from the control
|
||||
channel or MCP, a merge preview path or `lightcraft-cli render IMG.jpg -o IMG.jpg` is refused with a clear
|
||||
@@ -248,6 +270,9 @@
|
||||
lost everything at quit; a temporary session shows a banner the whole time and never writes to your library.
|
||||
|
||||
### Editing
|
||||
- Optional remote SAM 3: keep the native editor local and run Object, Describe, and detail
|
||||
inference on a Mac through SSH. Saved masks still render and export offline.
|
||||
See [remote Metal inference](ai-masks.md#remote-metal-inference).
|
||||
- AI masks with SAM 3 (Object and Describe in the Masking panel): click an object to select it (⌥-click leaves a
|
||||
part out), or type what to select ("sky", "the red car", "car, road"); both combine with other masks, have an
|
||||
Edge setting, and get a sharper zoomed-in pass in the background. The model runs inside LightCraft in pure Rust
|
||||
|
||||
Reference in New Issue
Block a user