mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-26 19:17:02 -06:00
Merge upstream/main into pr/canvas-keyboard
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -191,6 +191,11 @@ Therefore:
|
||||
| `segment.model.cancel` | `{}` | stop it (it resumes next time) → `{cancelled}` |
|
||||
|
||||
`remote` reports the configured endpoint, not its health. `installed` describes local weights only.
|
||||
`download` is `{running, done, total, file, error, cancelled, finished}`: `cancelled` is true when the last
|
||||
download was cancelled (its `error` says so in words; decide on the flag). An AI mask command refused because
|
||||
the model is not installed (or still downloading) fails with an error that starts with "The SAM 3 model is
|
||||
not installed"; inside LightCraft it is `ErrorKind::ModelMissing(Sam3)`, which is what the app offers the
|
||||
download on.
|
||||
|
||||
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
|
||||
|
||||
@@ -28,6 +28,7 @@ An export can hand the files it wrote to Apple Photos, optionally into an album
|
||||
An import can take minutes, and the first one waits for the user to answer macOS's permission prompt. Each
|
||||
import is a job: `{job, running, requested, album}`, plus, once Photos is done, `{imported, ids, warning?}`,
|
||||
`{error}` or `{skipped}` (`exporting: true` while an export that will add its files is still writing them).
|
||||
`skipped` says why Photos wasn't asked, in words; `cancelled: true` comes with it when the export was cancelled.
|
||||
|
||||
While Photos works, the activity stack ([background-tasks.md](background-tasks.md)) shows it. An export adding its
|
||||
files keeps its own row, which reads "Adding to Apple Photos…" and loses its ✕, since Photos can't be stopped once
|
||||
|
||||
+18
-1
@@ -62,6 +62,10 @@ control. The exact inputs and reference hashes are:
|
||||
| Canon R100 C-RAW, raw.pixls.us 7897 | `0b66842b2fe00329ebc05fe8ab9357ddbbe1a0a2d2e69a2893102de7fdf5c942` | `341c706b37c38bbf` |
|
||||
| Canon R8 RAW, raw.pixls.us 6585 | `7d5c6dbb11ff7e6ee58715d90a20f5d801e8b103f4711ff6ea329ceafcef254c` | `63c1c6d8d1eb2312` |
|
||||
| Canon R8 C-RAW, raw.pixls.us 6587 | `df33cf394573645ce03dea1b2e9f0b5cc2b7734e9e3391ee7114151414cf2812` | `f836a663a795a375` |
|
||||
| Canon RP RAW, raw.pixls.us 4674 | `dcbd0f6f9929de2e2331b0fd10a9d01e7cfd04c5a5e4038456de1647d714cdf2` | `5cb0f46404b8b663` |
|
||||
| Canon RP C-RAW, raw.pixls.us 4672 | `bd3270610593d37ff8a399908bb9406a4e32f9151d55bf19ac1cf8f10d787b46` | `f48df06ca1d67382` |
|
||||
| Canon RP RAW, 1.6× crop, raw.pixls.us 4673 | `7d4ba45dc9264d598336133593c677b09774bcdf13b602ee770e72415a652a16` | `46808a9de3b3dcc7` |
|
||||
| Canon RP C-RAW, 1.6× crop, raw.pixls.us 4671 | `4a859ab137f792aedaf3416b0aad1497ebaab02052a49453902e226699154aa7` | `39a60c4efed4e1f4` |
|
||||
|
||||
The EOS R5 Mark II files (raw.pixls.us 7881–7884, CC0) are pinned to LightCraft's own decode; no external
|
||||
decoder was run on them:
|
||||
@@ -77,7 +81,11 @@ Reference pixels were observed from the DNGLab 0.8.0 macOS arm64 release
|
||||
binary; no decoder source was read or copied, and it is not shipped or linked.
|
||||
The release ZIP SHA-256 was
|
||||
`ee70805cb60f18d5ed62548c4e595f6cee0a33407e15358eb1766a46afc1c16e`.
|
||||
All six sensor comparisons pass. R8 HEVC preview tracks are distinguished from
|
||||
The RP references came from the same release's Windows x64 binary (ZIP SHA-256
|
||||
`d3add6b71568bf5f7938468b0b40a0ee1eaec01e0057fe78dcc453188010be9a`), after it
|
||||
reproduced the M50 RAW hash above. The RP's 1.6× crop mode writes a single
|
||||
4032×2656 tile, where full frame (6384×4224) uses two 3192-wide horizontal
|
||||
tiles. All ten sensor comparisons pass. R8 HEVC preview tracks are distinguished from
|
||||
the primary Bayer RAW; the HEVC preview itself is not decoded. Missing corpus
|
||||
files are reported as skipped by the tests.
|
||||
|
||||
@@ -129,6 +137,15 @@ tile count, marker headers and temporary allocations. The full sensor buffer is
|
||||
allocated only after the first plane decodes, and plane buffers grow row by row,
|
||||
so a small crafted file fails before reserving memory for the image it claims.
|
||||
|
||||
## White level
|
||||
|
||||
The white level is Canon's specular white from `ColorData` (the word after the
|
||||
normal white level), not the sensor's saturation point. On the RP at ISO 100
|
||||
it is 14558 while samples clip at 16383. At the −⅓-stop ISOs, which pull a
|
||||
higher analog ISO down digitally, it drops further: 11062 at ISO 5000, where
|
||||
red saturates near 13668. Samples above the white level render as clipped.
|
||||
Whether Lightroom recovers them has not been measured.
|
||||
|
||||
## Colour limits
|
||||
|
||||
CR3 carries as-shot white balance but no measured camera matrix is imported
|
||||
|
||||
@@ -292,6 +292,37 @@ shot), so check a model on your own photos before trusting it:
|
||||
(ignoring shots taken within a few seconds of it, which would make it too easy) and reports, for each threshold, how many
|
||||
suggestions it would make and how many were right.
|
||||
|
||||
## Face pictures in the People view
|
||||
|
||||
Each face on a person card, in Unnamed faces and on a person's page is a square around the face, about 1.7 times the
|
||||
face box, cut out of the photo with the photo's own look (`Session::face_job`). Where the pixels come from:
|
||||
|
||||
- **An unedited raw** (it still looks the way it was imported, the case in which its grid thumbnail is the camera's
|
||||
embedded JPEG too) is cut from its **largest embedded preview** (`crates/engine/src/face_tiles.rs`). The preview is
|
||||
read the planned way ([raw-preview-reads](raw-preview-reads.md)): the file's header and the JPEG, never the sensor
|
||||
data, and the raw is not developed. The preview is used when it holds the square at **at least ¾ of the pixels the
|
||||
tile asks for** (up to the 2,560 px the develop path would use); the square is then enlarged by at most 4⁄3, which
|
||||
is not visible at tile size (the camera's JPEG is sharpened, so its pixels carry more detail than a binned raw's),
|
||||
and is the bound the raw path already accepts when it bins an X-Trans sensor for a preview. A smaller preview, one of
|
||||
another shape than the photo (letterboxed, another aspect ratio), one that does not decode, or a file that is offline
|
||||
leaves the face to the develop path below.
|
||||
- **Everything else** (an edited raw, whose edits the camera's JPEG can't take the way the raw does; JPEG, HEIC and
|
||||
other files; a raw whose preview is too small) is a develop render of the photo with its crop replaced by the square,
|
||||
from the smallest source level that keeps the face sharp, as before.
|
||||
|
||||
Measured on two Sony raws on an external 5400 rpm disk (file cache dropped before each face, a centred face box of 4 %
|
||||
and 10 % of the frame, 176 and 352 px tiles): an α1 ARW (74 MB, 8640 × 5760 Adobe RGB preview) read 70.6 MiB in
|
||||
0.77–0.92 s per face before and reads 7.8 MiB in 0.20–0.26 s; an A7S III ARW (25 MB, full-size 4240 × 2832 preview)
|
||||
read 24.3 MiB in 0.38–0.56 s and reads 1.1 MiB in 0.04–0.09 s. The tiles differ from the develop path's by 2–6 levels
|
||||
of 255 on average (the camera's rendering against LightCraft's camera look, which is fitted to it).
|
||||
|
||||
Rendered faces are kept like other thumbnails: in memory, and in the library's thumbnail cache on disk (`thumbs/`,
|
||||
within the Settings ▸ Performance cache budget, least recently used files go first). The key is the photo's content
|
||||
hash, its develop settings with the face's square as the crop, the tile size, the renderer version and the camera
|
||||
profiles, so an edit, another square or a new renderer makes a new picture, and the next launch shows the faces
|
||||
without reading the originals. Tile sizes are rounded up to a multiple of 16 px, so moving the thumbnail slider
|
||||
reuses the pictures of its step instead of rendering every face again.
|
||||
|
||||
## Commands
|
||||
|
||||
All of this is reachable from the control channel, the CLI and MCP: `faces.models.list`, `faces.models.inspect {path}`,
|
||||
|
||||
+5
-4
@@ -1,6 +1,6 @@
|
||||
# Gaps: where LightCraft falls short of Lightroom
|
||||
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-12 · **Change:** minor (gap 23: canvas tools by keyboard, 4–8 h → 2–4 h) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-13 · **Change:** patch (gap 8: Nikon Z 6II / Z 8 / Z f in-file distortion; gap 23: canvas tools by keyboard, 4–8 h → 2–4 h; gaps 10, 22 and 23 listed once again: merges had left two copies of each, and `cargo xtask parity` now fails on a gap listed twice)
|
||||
|
||||
Every known shortfall, one entry each, ranked by user impact (how many Lightroom users hit it, and
|
||||
how hard it blocks them), then by effort. **Agents pick work from the top** (the first entry nobody is
|
||||
@@ -22,10 +22,9 @@ Hours are Opus 5.5 agent wall-clock hours (see [target-app-parity.md](target-app
|
||||
| 5 B | **AI masks: Subject, Sky, People, Background, Landscape** are heuristics; Object / Describe run SAM 3 but its download has no mirror configured | `crates/segment`; ai-masks.md; MASK rows 67% | masking is how most Lightroom users edit today | 40–70 + model decision | [target-app-parity](target-app-parity.md#ai) |
|
||||
| 6 B | **Compressed ORF** (every Olympus / OM body since ~2008) | measured: 66 of 78 Olympus / OM models preview-only; `orf.rs` refuses; OM bucket 3% of photographers, 3% of them fully work | +2.6 points; best gain per hour of any raw gap (~0.2) | 8–15 | [raw-parity](raw-parity.md#compressed-orf-a-clean-room-starting-point) (the two Olympus patents that describe the method) |
|
||||
| 7 | **Nikon High Efficiency NEF** (HE / HE★ on Z 8, Z 9, Z 6III, Z f, Z50II, Z5II when set to HE) | named and preview only (#193); needs a pure-Rust JPEG XS decoder | +1.1 points (only shots taken in HE; lossless works on the same bodies); low gain per hour (~0.04), so not a beta blocker | 20–40 | [raw-parity](raw-parity.md) |
|
||||
| 8 B | **No lens profile database** (LR-EDIT-OPTICS-PROFILE): only DNG opcodes, RW2 in-file distortion and Sony ILCE-7RM4A distortion | Adobe ships 3,635 lens profiles; we never use them | distortion / vignetting correction is a default step for many | 40–80 + calibration shots | [raw-parity](raw-parity.md#lens-profiles) |
|
||||
| 8 B | **No lens profile database** (LR-EDIT-OPTICS-PROFILE): only DNG opcodes, RW2 in-file distortion, Sony ILCE-7RM4A distortion and Nikon Z 6II / Z 8 / Z f distortion (`docs/nikon-lens-corrections.md`) | Adobe ships 3,635 lens profiles; we never use them | distortion / vignetting correction is a default step for many | 40–80 + calibration shots | [raw-parity](raw-parity.md#lens-profiles) |
|
||||
| 9 B | **Tests don't run in CI on macOS, Windows or Linux x64**: only FreeBSD runs `cargo test`; the others build packages | `.github/workflows/` (freebsd, packaging-lint, release, windows-arm64); the linux-riscv64 release job added in 0.6.0 (#844) runs only `lightcraft-cli --version` under QEMU | regressions reach users on the main platforms; `cargo xtask ci` runs only locally | 6–12 | [target-app-parity](target-app-parity.md#by-dimension) |
|
||||
| 10 B | **Per-model verification thin, plus cheap per-body decode fixes**: 236 of 1,446 Adobe models verified (16%), ~42% of photographers on a verified body; Nikon 5 bodies verified, Canon 12, Leica 14 (the Q3 by a recorded check of 38 private DNGs). Cheap fixes: Z 6II packed 14-bit NEF (#769, PR #671), Sony A100 / F828 regression (#702), DSLR-A200…A390 / A850 12-bit ARW (#535) | measured: [raw/coverage.tsv](raw/coverage.tsv) | per-body bugs (levels, crops, WB) surface only from users | 25–45 | [raw-parity](raw-parity.md#by-maker-measured) |
|
||||
| 10 B | **Per-model verification thin, plus cheap per-body decode fixes**: 236 of 1,446 Adobe models verified (16%), ~42% of photographers on a verified body; Nikon 5 bodies verified, Canon 12. Cheap fixes: Z 6II packed 14-bit NEF (#769, PR #671), Sony A100 / F828 regression (#702), DSLR-A200…A390 / A850 12-bit ARW (#535) | measured: [raw/coverage.tsv](raw/coverage.tsv) | per-body bugs (levels, crops, WB) surface only from users | 25–45 | [raw-parity](raw-parity.md#by-maker-measured) |
|
||||
| 11 | **Enhance: Raw Details, Super Resolution; AI denoise without shippable weights or X-Trans** | ENH rows 0%; denoise Bayer only, RawNIND weights GPL and opt-in | Fujifilm users get no AI denoise; nobody gets it out of the box | 30–60 + model decision | [raw-parity](raw-parity.md#demosaic-levels-crops-and-lens-metadata) |
|
||||
| 12 | **Content-aware / generative remove, detect distractions, reflections removal** | REM rows 75%; patch synthesis missing | cleanup work goes back to Photoshop | 25–45 (+ generative model) | [target-app-parity](target-app-parity.md#ai) |
|
||||
| 13 | **Lightroom catalog migration is one-way and approximate**: unsupported Adobe profiles / AI settings archived, history kept as data | lightroom-catalog-import.md | switching users lose some looks | 8–12 | [file-format-parity](file-format-parity.md) |
|
||||
@@ -37,8 +36,8 @@ Hours are Opus 5.5 agent wall-clock hours (see [target-app-parity.md](target-app
|
||||
| 19 | **Plug-in ecosystem**: no Lightroom SDK equivalent (export / publish plug-ins, metadata plug-ins) | ⬜ | pro workflows built on plug-ins (LR/Enfuse, Jeffrey's exporters, Negative Lab Pro) | 40–80 + owner decision | [target-app-parity](target-app-parity.md#by-dimension) |
|
||||
| 20 | **Long-tail raw formats**: compressed SRW, ARQ pixel shift, IIQ, Hasselblad compression, X3F, CRW, MRW, ERF, KDC, DCR, MEF, GPR, old FinePix | raw-parity.md | small user share each | 45–85 | [raw-parity](raw-parity.md) |
|
||||
| 21 | **Localization**: 9 of 15 shipped languages partial (74–99% of catalog messages), lower-layer errors English; Hindi, Arabic, Indonesian, Korean, Vietnamese absent; no RTL or complex shaping | measured from catalogs | non-English users see mixed English | 55–90 (shipped to full); 145–255 (five new) | [localization-parity](localization-parity.md) |
|
||||
| 22 | **Pen pressure**, OS monitor profiles on Windows (Linux reads colord's, macOS the screen's), follow-window colour on Linux / Windows | hardware-parity.md | brush users with tablets; multi-monitor setups | 11–22 | [hardware-parity](hardware-parity.md) |
|
||||
| 23 | **Accessibility**: AccessKit tree audited by a test; canvas handles, spots and mask pins by keyboard; left: brush and red-eye by keyboard, a session with a real screen reader | LR-BEHAV-ACCESS 🟡 | blind and low-vision users | 2–4 | [ui-parity](ui-parity.md) |
|
||||
| 22 | **Pen pressure**, OS monitor profiles on Windows (Linux reads colord's, macOS the screen's), follow-window colour on Linux / Windows | hardware-parity.md | brush users with tablets; multi-monitor setups | 11–22 | [hardware-parity](hardware-parity.md) |
|
||||
| 24 | **Brush live preview and masking feel** (#517, #518); Feather / Edge mask refinement (15.5) | issues | masking feels slower than Lightroom | 8–12 | [ui-parity](ui-parity.md) |
|
||||
| 25 | **File writes**: lossy DNG; AVIF import; Render to DNG as a catalog command (the export format landed) (PSD export landed with Edit in PhotoCraft, #953) | file-format-parity.md | interchange edge cases | 19–34 | [file-format-parity](file-format-parity.md) |
|
||||
| 26 | **Performance at scale**: 85k-photo library opens in 1.7–4.7 s; 100k grid test pending; colour NR at half resolution | ROADMAP log, gpu-pipeline.md | large-catalog users | 15–30 | [target-app-parity](target-app-parity.md#by-dimension) |
|
||||
@@ -73,6 +72,7 @@ Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform w
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-13 | patch | Gaps 10, 22 and 23 each had two rows (two branches edited them; the merges kept both): the older copies are removed, and `cargo xtask parity` (in `ci`) now fails when a gap number is listed twice |
|
||||
| 2026-10-11 | minor | Gap 10 shrinks: Leica Q3 verified by a recorded check of 38 private DNGs (236 models verified; Leica 14); gap not closed |
|
||||
| 2026-10-12 | minor | Camera-look deep shadows follow the JPEG toe (#1307); camera calibration remains partial |
|
||||
| 2026-10-12 | minor | Gap 23 (accessibility): crop handles, spots, gradient handles and mask pins by keyboard; 4–8 h → 2–4 h; gap not closed |
|
||||
@@ -91,3 +91,4 @@ Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform w
|
||||
| 2026-10-10 | minor | RAW gaps re-measured model by model (raw-parity.md): CR3, compressed ORF and verification entries carry measured shares; HE NEF no longer a beta blocker (+1.1 points for 20–40 h); verification entry absorbs the cheap per-body fixes (#769, #702, A200 series) |
|
||||
| 2026-10-10 | minor | Beta total raised to 340–600 h to include the ~92 open core-path user issues |
|
||||
| 2026-10-10 | major | Created from the checklist's Top gaps (whose items 1–2 had been duplicated by a merge) and ROADMAP's Where we stand; re-ranked; added evidence, impact, hours and beta flags; added platform-CI, lens-database, HE NEF and localization gaps |
|
||||
| 2026-10-13 | minor | Gap 8 narrows: Nikon Z 6II / Z 8 / Z f NEFs use the camera's in-file distortion correction (`docs/nikon-lens-corrections.md`) |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Lens profiles from a lens database
|
||||
|
||||
Raws that carry no lens correction of their own (DNG `OpcodeList3`, or a camera correction a raw reader converts,
|
||||
see [sony-lens-corrections.md](sony-lens-corrections.md)) can be corrected from a **lens database in the lensfun
|
||||
see [sony-lens-corrections.md](sony-lens-corrections.md) and [nikon-lens-corrections.md](nikon-lens-corrections.md)) can be corrected from a **lens database in the lensfun
|
||||
XML format** that you supply. The app does not ship any lens database: lensfun's data is licensed CC-BY-SA 3.0 and
|
||||
stays a separate download. Adobe LCP profiles are never used.
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ Select photos in Photo Grid or Square Grid, then use:
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `Cmd+Shift+D` (macOS) / `Ctrl+Shift+D` (Windows/Linux) | Select only the active photo |
|
||||
| `0` | Clear the star rating |
|
||||
| `1`–`5` | Set the star rating |
|
||||
| `[` / `]` | Decrease / increase each selected photo's rating by one, within 0–5 |
|
||||
@@ -13,6 +14,10 @@ Select photos in Photo Grid or Square Grid, then use:
|
||||
| `U` | Clear the flag |
|
||||
| `Shift` + `0`–`9`, `P`, `X` or `U` | Apply the action and select the next photo |
|
||||
|
||||
Select Only Active Photo (Edit menu) narrows the selection to the active photo. It does not change
|
||||
the photo, its edits or the last edit Undo step, and it works in any view that has an active photo.
|
||||
It can be reassigned or disabled in Help → Keyboard Shortcuts; text fields and dialogs keep the key.
|
||||
|
||||
Purple and clearing a colour label are available in Photo → Set Color Label and the photo context
|
||||
menu. Purple has no default number key, matching Lightroom Classic. Rejection marks a photo for
|
||||
culling; it does not delete the original or remove it from the library.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Localization parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (first per-language measurement from the catalogs) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (the interface no longer decides on error text, a prerequisite for translating lower-layer errors) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
|
||||
|
||||
How far each interface language is from `full`. How to add or maintain a language is in
|
||||
[localization.md](localization.md); per-language notes are in `localization-<code>.md`
|
||||
@@ -73,7 +73,9 @@ Target languages LightCraft lacks: Italian, Korean, Dutch, Polish, Swedish, Norw
|
||||
## Shared work before any language is `full` (counted once, ~20–35 h)
|
||||
|
||||
- Route lower-layer error messages (catalog, raw, codecs, engine) through translatable message ids
|
||||
instead of English `format!` strings (~12–20 h).
|
||||
instead of English `format!` strings (~12–20 h). Translating them must not change what the app does:
|
||||
since #1046 the interface decides on an error's kind (`lightcraft_engine::ErrorKind`, a download's or
|
||||
Apple Photos import's `cancelled` flag), not its words; new decisions on an error go through a kind too.
|
||||
- A check in `cargo xtask ci` that every user-visible literal reaches a catalog (~4–8 h).
|
||||
- A user manual / help pages beyond What's New and the shortcut sheet (Lightroom links to online
|
||||
help; ~4–8 h for a translatable in-app help index).
|
||||
@@ -89,4 +91,5 @@ Target languages LightCraft lacks: Italian, Korean, Dutch, Polish, Swedish, Norw
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-11 | minor | Shared work: the interface decides on error kinds, not English error text (#1046) |
|
||||
| 2026-10-10 | major | Created: per-language counts measured from the catalogs, the twelve key languages, script support, estimates |
|
||||
|
||||
+4
-1
@@ -22,6 +22,9 @@ it open, `--library` on the same folder fails with "This library is already open
|
||||
`--demo` starts the headless session with the procedurally generated demo library;
|
||||
`--compact` lists only the helper tools (see below). In connect mode the server starts even when
|
||||
the app is not running yet and connects on the first call (and reconnects if the app restarts).
|
||||
A request is sent again only when it never reached the app. If the connection drops after a request
|
||||
was sent but before its reply, the tool fails with "the outcome is unknown" and nothing is resent, so a
|
||||
command such as undo can't run twice: check the app's state before retrying.
|
||||
|
||||
Logs go to stderr; stdout carries only protocol messages.
|
||||
|
||||
@@ -285,7 +288,7 @@ lightcraft-cli calibrate --max 300 ~/Pictures/2026 # camera colour profiles (d
|
||||
`app.export` (MCP `export` tool, `lightcraft-cli run`, the desktop app) takes `addToPhotos: true` and
|
||||
`photosAlbum` to hand the files it wrote to Apple Photos, into that album (a top-level album of exactly that
|
||||
name, made when missing). The result's `applePhotos` is the import job: `{job, running, requested, album}`
|
||||
plus `{imported, ids, warning?}`, `{error}` or `{skipped}` once Photos is done; the files are written either
|
||||
plus `{imported, ids, warning?}`, `{error}` or `{skipped}` (with `cancelled: true` when the export was cancelled) once Photos is done; the files are written either
|
||||
way. Headless (this server, `lightcraft-cli`) waits for Photos by default; the desktop app (connect mode)
|
||||
answers with the running job and `export.photosImports {job}` gives the outcome. `export.addToPhotos {paths,
|
||||
album?, wait?}` does the same for files already on disk (`wait` defaults to true headless, false in the desktop
|
||||
|
||||
+8
-1
@@ -27,6 +27,11 @@ homography (the simplest model that explains ≥ 97 % of the matches). Exposure
|
||||
Radiance is a weighted mean in linear light, weight `k · (1 − smoothstep(0.8·clip, 0.96·clip, max
|
||||
channel))`. Deghost: a per-pixel consistency test against the reference radiance (with a noise
|
||||
floor), dilated and feathered masks; the union is the deghost overlay. Output: 16-bit float DNG.
|
||||
Lens corrections: frames are merged uncorrected, and the DNG's `OpcodeList3` carries the reference frame's
|
||||
(`WarpRectilinear`, `WarpRectilinear2`, `FixVignetteRadial`: a DNG's own, or a camera's correction a raw reader
|
||||
converts, e.g. Sony, Panasonic), re-expressed for its default crop (`Opcode::cropped`). So Optics ▸ Enable Lens
|
||||
Corrections works on the result as on the sources; a vertical bracket keeps its pixels unturned and its
|
||||
corrections.
|
||||
|
||||
**Panorama** (`pano/`): MOPS-style features, pairwise RANSAC homographies with Brown & Lowe's
|
||||
verification, focal from homographies (or EXIF 35 mm), rotations chained along a maximum spanning
|
||||
@@ -54,7 +59,9 @@ scale in `BaselineExposure`; raw sources keep their DNG colour tags (camera RGB)
|
||||
## Limits (known gaps)
|
||||
|
||||
- No lens-distortion model in the panorama bundle adjustment (rectilinear lenses assumed); no
|
||||
360° wrap-around; flat (translating-camera) scans are fitted with the rotation model.
|
||||
360° wrap-around; flat (translating-camera) scans are fitted with the rotation model. Panoramas and HDR
|
||||
panoramas carry no lens corrections, nor does an HDR whose frames don't share an orientation (they are turned
|
||||
before merging).
|
||||
- No camera response calibration for JPEG brackets (sRGB decoding is taken as linear light).
|
||||
- Boundary Warp is a separable stretch, not a content-preserving mesh warp; Fill Edges is diffusion,
|
||||
not patch synthesis.
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
# Nikon embedded distortion corrections
|
||||
|
||||
Nikon writes its own distortion correction into NEFs from its mirrorless bodies, in the raw SubIFD tag `0xc7d5`
|
||||
(NikonNEFInfo). LightCraft reads it for the NIKON Z 6_2 (Z 6II), the NIKON Z 8 and the NIKON Z f and turns it into an
|
||||
`OpcodeList3` `WarpRectilinear`, so it goes through the same profile correction control, optics path, orientation
|
||||
handling and DNG export as the other embedded corrections. Other bodies are left alone for now even if they have the
|
||||
same tag, because I've only checked files from these three.
|
||||
|
||||
This is distortion only. The vignetting block (`0x0006`) and what looks like lateral CA (`0x0007`) aren't applied
|
||||
yet. Like the Sony, Fujifilm and Panasonic corrections it's on by default and can be turned off under Lens
|
||||
Corrections.
|
||||
|
||||
## Where the data comes from
|
||||
|
||||
The [ExifTool Nikon tag names](https://exiftool.org/TagNames/Nikon.html) give the names of NikonNEFInfo and its
|
||||
DistortionInfo entry (`0x0005`): DistortionCorrection and RadialDistortionCoefficient1 to 3. Nothing else was taken
|
||||
from other software. The rest was worked out by comparing LightCraft's uncorrected render of each NEF with the
|
||||
full-size JPEG the camera embeds in it. That JPEG is always distortion-corrected with Z lenses ("On (Required)"), and
|
||||
with other lenses when Auto distortion control is on. The comparison matched textured patches between the two images
|
||||
(normalised cross-correlation on the green channel at quarter size).
|
||||
|
||||
NikonNEFInfo starts with `"Nikon\0"` and a 4-byte version, followed by a small TIFF stream. The two entries used here:
|
||||
|
||||
| Entry | Bytes | What it holds |
|
||||
|---|---|---|
|
||||
| `0x0005` DistortionInfo | 0–3 | `"0100"` |
|
||||
| | 4 | correction mode: 1 "On (Optional)", 3 "On (Required)", 0 none |
|
||||
| | 8–15 | normalisation radius in mm, a signed rational. It's 21.6 (the FX half diagonal) in every file I looked at |
|
||||
| | 16–19 | number of terms, 4 |
|
||||
| | 20, 28, 36 | the three coefficients, called `k1`, `k2`, `k3` here after ExifTool's names |
|
||||
| | 64–71 | the zoom `q` the camera uses to fill the frame |
|
||||
| `0x0008` | 0–3, 4–11, 12–19 | `"0100"`, then the optical center's offset from the center of the image area in pixels (x right, y down) |
|
||||
|
||||
The `0x0008` offset is where the in-body stabilization left the sensor. It's zero with a VR lens, since the lens does
|
||||
the stabilizing, and up to about 100 px with the Z lenses. The center of the distortion measured in the JPEG follows
|
||||
it closely, for example (−90.1, +18.6) recorded and (−91.2, +17.5) measured.
|
||||
|
||||
From an output pixel to the raw, around the optical center, the mapping is
|
||||
|
||||
```
|
||||
rho = r_out / half diagonal of the image area
|
||||
r_src = r_out * (1 + k3*rho^2 + k2*rho^4 + k1*rho^6) / (1 + q)
|
||||
```
|
||||
|
||||
So the stored order goes from the highest power down. Reading them the other way round (`k1` with `rho^2`) doesn't fit
|
||||
the JPEG at all. `q` really is the zoom: fitting a separate zoom for every file gives `1 / (1 + q)` to four decimals
|
||||
(0.9675 fitted and 0.9676 from `q` on one of them), so nothing is fitted per file. The decoder turns this into DNG
|
||||
terms the same way `docs/sony-lens-corrections.md` describes for Sony, with the center in active-area coordinates and
|
||||
the coefficients rescaled to the farthest corner of the active area.
|
||||
|
||||
## How well it matches
|
||||
|
||||
I checked it on 83 of my own Z 6II photos with three lenses between 20 and 200 mm, 45 Z 8 photos (below), plus the
|
||||
CC0 Z f sample in the corpus. The numbers are the radial error against the camera JPEG at full size (6048 px wide), out to 0.95 half
|
||||
diagonals:
|
||||
|
||||
| Lens | Focal length | Frames | Uncorrected median / p95 | Corrected median / p95 |
|
||||
|---|---:|---:|---:|---:|
|
||||
| VR 70-200mm f/2.8G (F-mount, FTZ) | 70 mm | 11 | 10.9 / 15.9 px | 0.19 / 0.57 px |
|
||||
| VR 70-200mm f/2.8G (F-mount, FTZ) | 105–140 mm | 15 | 34–42 / 38–46 px | 0.13–0.18 / 0.59–0.90 px |
|
||||
| VR 70-200mm f/2.8G (F-mount, FTZ) | 175–200 mm | 21 | 45–46 / 50–51 px | 0.10–0.18 / 0.48–0.95 px |
|
||||
| NIKKOR Z 24-70mm f/4 S | 70 mm | 8 | 126.4 / 141.2 px | 0.48 / 4.38 px |
|
||||
| NIKKOR Z 24-70mm f/4 S | 38–59 mm | 6 | 59–101 / 69–121 px | 0.36–0.76 / 4.6–6.8 px |
|
||||
| NIKKOR Z 24-70mm f/4 S | 33–34 mm | 4 | 32–44 / 43–53 px | 1.0–1.3 / 8.5–9.6 px |
|
||||
| NIKKOR Z 24-70mm f/4 S | 24 mm | 7 | 16.0 / 105.6 px | 6.90 / 23.56 px |
|
||||
| NIKKOR Z 20mm f/1.8 S | 20 mm | 5 | 8.2 / 53.9 px | 1.49 / 5.46 px |
|
||||
| Tamron 35-150mm (Z f, CC0 corpus) | 150 mm | 1 | 70.4 px median | 0.22 px median |
|
||||
|
||||
On the Z 8 (8256 px wide JPEGs) it looks the same. The 70-200 f/2.8G on the FTZ matches within 0.14 to 0.21 px median
|
||||
at 70, 135 and 200 mm. The NIKKOR Z 24-120mm f/4 S is within 0.6 to 1.0 px median from 36 to 120 mm and 1.5 px at 24
|
||||
to 28 mm, with the same kind of error toward the edges at the wide end as the Z lenses on the Z 6II. A few 14-24mm
|
||||
f/2.8G frames had Auto distortion control off, so their JPEGs aren't corrected and match about the same either way.
|
||||
|
||||
I also rendered a few with `lightcraft-cli render` at default settings and measured those against the camera JPEG.
|
||||
The median error was 0.10 px for the 70-200 at 200 mm and 0.12 px for the Z f sample. The 24-70 was 0.32 px at 70 mm,
|
||||
on a frame with a 92 px sensor shift, and 8.4 px at 24 mm. The 20 mm came out at 1.3 px. On the Z 8 it was 0.14 px for the 70-200 at 200 mm, 0.45 and 0.68 px for the 24-120
|
||||
at 68 and 120 mm, 1.8 px for the Z 20 mm and 3.7 px for the 24-120 at 24 mm. That checks the
|
||||
center, crop and DNG conversion along with the model itself.
|
||||
|
||||
The F-mount lens on the FTZ matches about as well as I can measure. The native Z lenses still have a smooth error
|
||||
toward the edges that doesn't depend on the sensor shift and is the same in every frame at a given focal length: about
|
||||
+22 px at 0.7 to 0.9 half diagonals at 24 mm, +8 px at 33 mm and +3.5 px at 70 mm. It seems the camera applies an
|
||||
extra correction for Z lenses that isn't in the readable coefficients. NikonNEFInfo `0x000e` looks encrypted and I
|
||||
didn't try to decode it. Even so, the readable correction takes most of the distortion out. At 24 mm the error at 0.7
|
||||
half diagonals goes from −82 px to +25 px, and at 70 mm from about −137 px to under 4 px.
|
||||
|
||||
## What's left out
|
||||
|
||||
Only `NIKON Z 6_2`, `NIKON Z 8` and `NIKON Z f` are corrected, matched exactly. A matching tag on another body doesn't tell us its
|
||||
pixel pitch or image area, so other bodies can be added once someone checks their files. Only the full-frame 3:2
|
||||
image area is corrected, because the stored 21.6 mm radius is the FX half diagonal, so DX and other crops are skipped.
|
||||
The decoder also skips data it doesn't recognise (another layout, radius or term count, or no correction mode),
|
||||
values that are clearly wrong (coefficients past ±1, a zoom past ±0.5, a sensor shift over 5% of the frame) and any
|
||||
mapping that folds back on itself before the farthest corner.
|
||||
|
||||
The tests in `nef_lens` cover the polynomial and zoom, the sensor-shift center and refusing other bodies, layouts
|
||||
and crops. `nikon_in_file_distortion_becomes_a_warp` runs a synthetic Z 6II NEF through the header and full decode
|
||||
and a DNG export. `nikon_in_file_distortion_is_limited_to_validated_bodies` checks the corpus: the Z f sample gets the
|
||||
warp and the D5100, D7000, D7500 and Coolpix B700 samples don't.
|
||||
|
||||
None of my photos, crops or reference images are committed, only the numbers above.
|
||||
+27
-18
File diff suppressed because one or more lines are too long
+4
-3
@@ -1,12 +1,12 @@
|
||||
# RAW parity: cameras, formats, demosaic, colour and lens profiles
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (Leica Q3 verified by a recorded check of 38 private DNGs; EOS R5 Mark II decoded and verified: CRX QP-map escape, odd-height planes) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 (Camera Raw 18.7)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (Nikon Z 6II / Z 8 / Z f in-file distortion correction; Leica Q3 verified by a recorded check of 38 private DNGs; EOS R5 Mark II decoded and verified: CRX QP-map escape, odd-height planes) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 (Camera Raw 18.7)
|
||||
|
||||
Raw processing is the deepest single dimension of Lightroom parity: a photographer whose camera
|
||||
does not decode, or decodes with the wrong colour, cannot switch. This document measures it model by
|
||||
model. The numbers feed [target-app-parity.md](target-app-parity.md); individual shortfalls are
|
||||
ranked in [gaps.md](gaps.md). Details per format: [cr3.md](cr3.md),
|
||||
[raf-compression.md](raf-compression.md), [sony-lens-corrections.md](sony-lens-corrections.md),
|
||||
[raf-compression.md](raf-compression.md), [sony-lens-corrections.md](sony-lens-corrections.md), [nikon-lens-corrections.md](nikon-lens-corrections.md),
|
||||
[camera-preview-colour.md](camera-preview-colour.md), [denoise.md](denoise.md).
|
||||
|
||||
## Headline
|
||||
@@ -219,7 +219,7 @@ essentials user (the default look is what they keep).
|
||||
| Item | Adobe | LightCraft |
|
||||
|---|---|---|
|
||||
| Profile database | 3,635 profile files, 56 makers, auto-selected | **0** (Adobe LCPs never used; no database of our own) |
|
||||
| In-file corrections | ✅ | DNG opcodes (WarpRectilinear, FixVignetteRadial, GainMap…) applied; Panasonic / Leica RW2 distortion (79 photos, 13 bodies, 11 lenses); Sony tag 0x7037 distortion for the ILCE-7RM4A (2 lenses) |
|
||||
| In-file corrections | ✅ | DNG opcodes (WarpRectilinear, FixVignetteRadial, GainMap…) applied; Panasonic / Leica RW2 distortion (79 photos, 13 bodies, 11 lenses); Sony tag 0x7037 distortion for the ILCE-7RM4A (2 lenses); Nikon NikonNEFInfo distortion (tag `0xc7d5`, about the sensor-shifted optical centre) for the Z 6II and Z f: F-mount lenses on the FTZ within 0.2 px of the camera JPEG, Z lenses within 0.5–1.5 px except the 24-70's wide end (7 px median; `docs/nikon-lens-corrections.md`) |
|
||||
| Vignetting / lateral CA from maker notes | ✅ | ⬜ |
|
||||
| Manual distortion, vignetting, CA, defringe | ✅ | ✅ |
|
||||
|
||||
@@ -280,6 +280,7 @@ Beyond decoding:
|
||||
| 2026-10-12 | minor | Hasselblad Lunar verified (a Sony NEX-7 ARW under Make HASSELBLAD, now decoded): 236 verified, 232 preview; an uncompressed Leaf MOS is recognised as a raw (preview only) instead of opening as a greyscale image of its mosaic (#1275) |
|
||||
| 2026-10-10 | minor | Compressed ORF: pointer to the two Olympus patents that describe the method, as a clean-room starting point |
|
||||
| 2026-10-12 | minor | PEF without area tags: masked border rows and columns trimmed against the BlackPoint (issue #1271; KP, KF, K-3 Mark III, K-70, K-1, K-1 Mark II, K-S1, K-S2, 645Z); counts unchanged |
|
||||
| 2026-10-13 | minor | Lens profiles: Nikon in-file distortion (NikonNEFInfo `0xc7d5`) for the Z 6II, Z 8 and Z f, validated on 83 frames from three lenses and the CC0 Z f sample (`docs/nikon-lens-corrections.md`); lens score unchanged |
|
||||
| 2026-10-11 | minor | EOS R5 Mark II verified (CRX QP-map first literal and limited-length escape, odd-height Bayer planes; 4 CC0 files pinned, QP maps of 33,665 private files): 235 verified, 942 unverified; use-weighted 85% (42% on verified bodies); decoding 77%, RAW readiness unchanged at ~56% |
|
||||
| 2026-10-10 (later) | major | Model-by-model measurement against Adobe's published list (1,446 models: 234 verified, 943 unverified, 233 preview, 36 unsupported); `docs/raw/` data and re-measure script; use-weighted share 84% (40% on verified bodies); variant matrix; colour and lens sections with scores; ranked next cameras; RAW readiness ~56% from written parts |
|
||||
| 2026-10-10 | major | Created: formats, makers, demosaic, colour and lens checklists; target counts from the installed Lightroom 9.6 bundle listing |
|
||||
|
||||
+35
-10
@@ -60,11 +60,13 @@ those:
|
||||
header probe reads samples and whose header plan is `Whole`, which is where that plan was untested
|
||||
before;
|
||||
- the quick path must be able to show what the whole-file path would. Two container facts are not in
|
||||
the JPEG, so a file that states one where the bounded header cannot see it declines and the whole
|
||||
file keeps the preview: a preview colour space of **Adobe RGB** (the whole-file path applies it
|
||||
through `decode_raw_preview`; a JPEG-only decode has the JPEG's own space, else sRGB — a container
|
||||
that says sRGB does not decline, since that is what the decode assumes anyway), and the file's own
|
||||
EXIF orientation when the preview JPEG carries none of its own;
|
||||
the JPEG. A preview colour space of **Adobe RGB** is read from the planned buffer and handed on
|
||||
(`PreviewRead::adobe_rgb`), and the quick decode applies it as `decode_raw_preview` does (issue
|
||||
#1440: a Sony α1 writes Adobe RGB previews, which until then sent every one of them to the
|
||||
whole-file read — 70 MB for an 8 MB JPEG); a container that says sRGB needs nothing, since that is
|
||||
what the decode assumes anyway. The file's own EXIF orientation, when the preview JPEG carries none
|
||||
of its own and the bounded header cannot see it, still declines and the whole file keeps the
|
||||
preview;
|
||||
- a format the planner cannot describe — NEF, ORF and PEF probe from samples, CRW/MRW/X3F are found
|
||||
by scanning — a plan that does not fit the buffer or wants more than 60 % of the file (the bound the
|
||||
import path's own partial read uses), or a window that does not decode all answer `None`, and the
|
||||
@@ -75,8 +77,8 @@ route**, reading a median **7.8 %** of their bytes (0.9 %–27.7 %) — by forma
|
||||
4.1 %, ARW 7.5 %, CR3 8.4 %, DNG 9.7 %, CR2 14.3 % — while the 21 RAFs stay on the header route
|
||||
(median 4.5 %). The remaining 38 read whole: NEF, NRW, ORF, PEF and Samsung SRW because the planner
|
||||
does not describe them (the first four say so; an SRW plans to the whole file, and the 60 % budget
|
||||
then keeps the whole-file read), and the ARWs, CR2s, CR3s and DNGs that declare Adobe RGB or whose
|
||||
plan wants more than that budget. Across the whole corpus the plan handed on **the same preview as
|
||||
then keeps the whole-file read), and the ARWs, CR2s, CR3s and DNGs that declared Adobe RGB (these take
|
||||
the plan route since #1440; the counts here are from before) or whose plan wants more than that budget. Across the whole corpus the plan handed on **the same preview as
|
||||
the whole file, every time** — and wrote it down: `cargo test -p lightcraft-raw --release --test
|
||||
sparse_corpus -- --nocapture` prints these numbers. `LIGHTCRAFT_PROFILE=1` says which route answered:
|
||||
|
||||
@@ -101,6 +103,9 @@ loupe render of a cold photo finished 174 s after the click, most of it spent be
|
||||
- `grid::skips_the_real_render(embedded_is_final, from_embedded)` is the decision, unit-tested:
|
||||
a **cached render** as the stand-in, an **edited photo** and a **raw with no embedded JPEG** all
|
||||
still render, because their thumbnail must show the pipeline's output rather than the camera's.
|
||||
- Whether the embedded JPEG can actually be read is only known when the quick job runs, so
|
||||
`Session::quick_thumb_job` carries the real thumbnail job as its last resort: a raw whose preview
|
||||
the loader can't read renders the raw instead of leaving the cell empty (issue #1448).
|
||||
- The thumbnail key still changes with the settings hash, so editing a photo re-renders its
|
||||
thumbnail through the ordinary background path.
|
||||
|
||||
@@ -108,17 +113,37 @@ Measured on the real folder: idle for a minute, the app read 12 previews (**26.9
|
||||
**zero** raws for thumbnails, against 638 MB of whole-file reads for the same 12 photos before. The
|
||||
render queue stayed empty.
|
||||
|
||||
## The smallest JPEG that covers the box
|
||||
|
||||
Many TIFF-based raws keep more than one embedded JPEG. A Sony A7R VI ARW holds a 1616 × 1080 one
|
||||
(0.3 MB) beside the 9984 × 6656 one (5.6 MB), and Nikon NEF and Panasonic RW2 files have mid-sized
|
||||
copies too. The loupe stand-in and the grid thumbnail (`embedded_preview_srgb`) decode the smallest
|
||||
JPEG whose long edge is at least the size asked for (`lightcraft_raw::embedded_preview_covering`).
|
||||
They take the largest one when no other JPEG is big enough. The plan route picks the same way
|
||||
(`preview_window` takes the size asked for), so a raw read through the plan gets the JPEG the whole
|
||||
file would give. A smaller JPEG only counts as a copy of the picture when its aspect ratio is within
|
||||
1 % of the largest one's and it has the same number of colour components. That rules out letterboxed
|
||||
thumbnails of another shape and the grey masks an iPhone ProRAW DNG keeps at the picture's shape.
|
||||
Sources for editing (an unsupported raw's JPEG, `load_embedded_preview`) and the camera-look fit still
|
||||
use the largest JPEG, so their geometry and colour do not depend on the size level.
|
||||
|
||||
## Tests
|
||||
|
||||
- `crates/raw/src/preview.rs`: the header-only range matches `embedded_preview` byte for byte; a
|
||||
clamped prefix, a padded extent, a late pointer, a lying pointer and a non-JPEG window are all
|
||||
covered.
|
||||
covered. `a_stand_in_takes_the_smallest_covering_jpeg_of_the_same_shape` covers the picker: a
|
||||
smaller JPEG that covers the box is taken, the largest when none does, and a thumbnail of another
|
||||
shape or a grey mask never is.
|
||||
- `crates/engine/src/files.rs`: on a synthetic RAF with a 200 KB tail, the quick path and the
|
||||
whole-file path produce identical pixels, and the read stays under half the file. On a synthetic
|
||||
TIFF/DNG whose preview only its tags locate — the plan path — the same identity holds and the CFA
|
||||
strip is not read; the same structure without the tag that makes it a raw declines instead of
|
||||
guessing, and the caller keeps reading it whole. So do a file whose own orientation or Adobe RGB
|
||||
colour space only the whole-file path can show, while one that states sRGB stays on the fast path.
|
||||
guessing, and the caller keeps reading it whole. So does a file whose own orientation only the
|
||||
whole-file path can show. One that states Adobe RGB takes the plan route and shows the same pixels
|
||||
as the whole-file path (the space changes them: decoded as sRGB they differ), and one that states
|
||||
sRGB stays on the fast path. A synthetic DNG with a full-size and a half-size JPEG gets, on the plan
|
||||
path, the half-size one for a box it covers and the full-size one otherwise, with the same pixels as
|
||||
the whole-file path (`preview_loading_plans_the_smallest_covering_jpeg`).
|
||||
- `crates/raw/tests/sparse_corpus.rs`: `preview_plan` is asked for **every** corpus file, not only
|
||||
the ones whose header plan is `Ready`, and the preview it yields must equal the whole file's with
|
||||
the skipped bytes `0` and `0xFF`. The test prints what each format reads.
|
||||
|
||||
+117
-1
@@ -1,13 +1,129 @@
|
||||
# What's new in LightCraft
|
||||
|
||||
## 2026-10-14
|
||||
|
||||
### Hard-edged Heal and Clone spots
|
||||
- A spot with Feather at 0 no longer draws black pixels exactly on its radius. This also fixes spots at 0% Opacity
|
||||
changing pixels along their edge. The hard edge now leaves pixels on the radius unchanged.
|
||||
|
||||
## 2026-10-13
|
||||
|
||||
### HDR merges keep the lens correction
|
||||
- An HDR made with Photo ▸ Photo Merge ▸ HDR now keeps the lens correction the photos carry (Sony's, Panasonic's
|
||||
and Leica's, and the one some DNGs carry, e.g. from drones): Optics ▸ Enable Lens Corrections straightens it as it
|
||||
does the photos it was made from. HDRs merged before this don't have it; merge them again.
|
||||
|
||||
### Portable mode
|
||||
- A file named `portable.txt` next to the LightCraft program makes it keep its settings, logs and downloaded
|
||||
models in a `LightCraftData` folder beside it instead of the per-user settings folder (issue #1200), as
|
||||
PhotoCraft does. If that folder can't be written, the usual folder is used and the log says why.
|
||||
|
||||
### Reliability
|
||||
- A photo whose Defringe amount was stored far past the sliders' 20 (in a damaged catalog or sidecar) no longer
|
||||
crashes LightCraft each time it renders: the amount acts as 20.
|
||||
|
||||
### Reliability
|
||||
- Importing a preset zip that unpacks to far more than any preset (a "zip bomb") is refused with a message naming
|
||||
the file, instead of asking for gigabytes of memory, which could end LightCraft.
|
||||
|
||||
### Reliability
|
||||
- A crop of no size (from an agent or a sidecar) no longer gives the Detail view a photo of no size to lay out
|
||||
(its width over its height was 0 / 0, which stopped debug builds); it shows as a single pixel's colour.
|
||||
|
||||
### Reliability
|
||||
- A crop a hair wide (or of no size), set by an agent or read from a sidecar, no longer crashes or hangs the
|
||||
render with Clarity, Dehaze, Texture or Highlights / Shadows on: their blurs are held to the picture's size.
|
||||
- A crop of no size with healing spots or masks no longer crashes the render: samples outside the picture read its
|
||||
edge.
|
||||
|
||||
### Reliability
|
||||
- A library whose catalog file was damaged (or edited by hand) in a way that still reads no longer crashes
|
||||
LightCraft when it shows a stack, or hangs it when a folder is moved: on opening, records are put back under
|
||||
their own ids, stacks keep only photos that exist (at least two), folders inside themselves move to the top,
|
||||
and new photos never reuse an id in use. What was repaired is written to the log.
|
||||
|
||||
### Nikon lens corrections
|
||||
- Raws from the Nikon Z 6II, Z 8 and Z f now open with the camera's own distortion correction, as Lightroom applies a
|
||||
built-in profile: straight lines near the edges stay straight and the framing matches the camera's JPEG. It works
|
||||
with Z lenses and with F-mount lenses on the FTZ adapter, follows the in-body stabilisation's shift of the optical
|
||||
centre, and can be turned off under Lens Corrections. Vignetting correction from the file comes later.
|
||||
|
||||
### Thumbnail cache stays in its folder
|
||||
- The thumbnail cache no longer follows a shard folder (`thumbs/ab/`) that is a symlink or junction to somewhere
|
||||
else (issue #1504): trimming or clearing the cache can't delete files outside it, and thumbnails are never read
|
||||
from or written through such a link.
|
||||
|
||||
### Large libraries: Synchronize Folder, import and sidecars start sooner
|
||||
- Opening Synchronize Folder, an import review or an import no longer makes a table of every photo in the library
|
||||
before it looks at the first file. In a library of 3.5 million photos that held the window for about 6 s before
|
||||
a Synchronize scan began (now under 1 s), 2.6 s before an import review (now 0.05 s) and 1.7 s before an import
|
||||
(now 0.05 s). The scan looks up only the files it finds and the contents it reads, on its own thread.
|
||||
- Writing one photo's XMP sidecar (Save Metadata to File, or automatically after an edit) no longer works out who
|
||||
owns every shared sidecar name in the library: 2.4 s a write in that library, now under 1 s.
|
||||
- Nothing else changes: the same files are found new, already there, moved or missing, and sidecars get the same
|
||||
names.
|
||||
|
||||
### Faces load faster in the People view
|
||||
- Faces on unedited raws are cut from the camera's preview inside the file instead of developing the raw (issue
|
||||
#1440): a Sony α1 face reads 8 MB instead of 70 MB, an A7S III face about 1 MB instead of 24 MB, several times
|
||||
faster on an external hard disk. A face whose preview is too small, and any edited photo, is still developed from
|
||||
the raw, so it keeps its look.
|
||||
- Raws whose preview is in Adobe RGB (Sony α1 among others) no longer read the whole file for their grid thumbnail:
|
||||
only the header and the preview are read, as for other raws.
|
||||
- Faces are kept in the thumbnail cache at a few fixed sizes, so moving the thumbnail slider no longer renders them
|
||||
all again.
|
||||
|
||||
### Reliability
|
||||
- A raw file whose black level is stored as "not a number" (or infinity), or a floating-point DNG with such
|
||||
samples, no longer crashes LightCraft at 1:1 or on export: the bad values read as black.
|
||||
|
||||
### Reliability
|
||||
- Exporting a photo more than 65,535 times as long as it is wide (a one-pixel strip), or opening the Export dialog's
|
||||
watermark preview for it, no longer crashes LightCraft; the export is capped at the encoders' 65,535 pixels.
|
||||
|
||||
### Reliability
|
||||
- Dragging a Tone Curve point that sits very close to its neighbours (two clicks side by side, or a curve from a
|
||||
sidecar, a preset or an agent) no longer crashes LightCraft; the point stays between them. Curve points sent to
|
||||
`develop.curve` are kept in order, and a sidecar curve with a "NaN" or "inf" point is ignored.
|
||||
|
||||
### Reliability
|
||||
- A demo photo whose stored size was damaged (a width of 4294967295) is rendered at its own size instead of
|
||||
one that overflows.
|
||||
|
||||
### DNG camera identity
|
||||
- DNG export and Convert to DNG no longer duplicate the camera make when the model already
|
||||
contains it: `Canon EOS R6` stays `Canon EOS R6`, rather than `Canon Canon EOS R6` (the camera
|
||||
identity subissue in #840). The separate Make and Model tags keep their original values.
|
||||
This does not change colour matrices, camera calibration or RAW noise rendering.
|
||||
### Building from source
|
||||
- The English and Simplified Chinese build guides now describe command-local job and release
|
||||
profile overrides for memory-constrained builders (issue #1196), including why packaging
|
||||
recipes can override inherited settings. Default release profiles and application behavior
|
||||
are unchanged; the guidance does not guarantee a build will fit every 16 GB machine.
|
||||
### Lightroom catalog import
|
||||
- A Lightroom Classic catalog whose changes are all still in its `.lrcat-wal` file (a new catalog Lightroom has
|
||||
open and hasn't written back yet) imports instead of failing with "unsupported SQLite schema format".
|
||||
|
||||
### Reliability
|
||||
- An export enlarged far past the photo's size (20000 typed for 2000 with Don't Enlarge off) no longer ends
|
||||
LightCraft: an enlargement stops at 512 megapixels, as in Lightroom, and one that needs more memory than the
|
||||
computer has is refused with the size it would take, before it starts.
|
||||
|
||||
### Slider drags on large raws
|
||||
- Dragging a slider no longer re-renders the edited photo's filmstrip thumbnail on every frame; it follows once you
|
||||
let go. On a 64 MP raw the first of those renders decoded the whole file again, which held up the loupe's
|
||||
previews for up to a second.
|
||||
- A thumbnail of the photo open in Detail is made from its already decoded preview instead of decoding the file
|
||||
again.
|
||||
### Dehaze while zoomed in
|
||||
- With Dehaze on, panning a photo zoomed to 1:1 or moving a slider while zoomed no longer samples the whole
|
||||
original again to find the haze colour. On a 64 MP raw (Sony A7R VI) that took ~0.2 s of CPU per pan and per
|
||||
slider step; a slider step now takes ~5 ms on the GPU.
|
||||
### Thumbnails of raw files appear sooner
|
||||
- Grid thumbnails and the first picture in the loupe of an unedited raw now come from the smallest JPEG the camera
|
||||
stored that is big enough, not always from the largest one. On a Sony A7R VI file a 512 px thumbnail decodes
|
||||
0.3 MB of JPEG instead of 5.6 MB and is ready several times sooner; Nikon and Panasonic raws with a mid-sized
|
||||
JPEG get faster too.
|
||||
## 2026-10-12
|
||||
|
||||
### Canon CR2 highlights
|
||||
@@ -526,7 +642,7 @@
|
||||
12–32 mm kit zoom was off by about 5 % of the image width at the corners before. Files shot with the correction off
|
||||
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.
|
||||
M50, R100, R8 and RP (on the RP also in 1.6× crop mode). 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
|
||||
|
||||
Reference in New Issue
Block a user