Merge main into ka-update

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
This commit is contained in:
Davit Matchakhelidze
2026-10-24 22:28:38 +04:00
co-authored by Claude Opus 5.5
341 changed files with 20175 additions and 3386 deletions
+1 -1
View File
@@ -238,7 +238,7 @@ wait and return the result (when built with the `sam` feature; the default CLI h
the model on the CPU from the start.
- Hostile documents: a selection keeps at most 4 detail patches (when read and when rendered),
each at most 1024² logits; more than 64 clicks are never sent to the model.
- Accuracy: `crates/segment/tests/reference.rs` compares every stage against the Python
- Accuracy: `crates/segment/tests/it/reference.rs` compares every stage against the Python
reference (`tools/sam3_reference.py`); on Metal the backbone matches to 7e-5 relative error
and masks to ~5e-5. f16 was tried and rejected (overflow, ~7 % faster).
- The engine feature `sam` (on in the desktop app, off for web and CLI) gates the model; without
+12 -1
View File
@@ -1,6 +1,6 @@
# Architecture
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (`cargo xtask ci` reports the machine and skipped tests; `testkit`) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-23 · **Change:** minor (`cargo xtask ci`: one integration-test binary per crate, doctests apart, one WASM check) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
How LightCraft is built today. The longer design document (`plan/architecture.md`) is local-only
(`plan/` is gitignored); this page is the committed, current summary. Sizes are Rust lines counted
@@ -63,6 +63,16 @@ dispatch by id, and headless snapshots render the UI without a window.
`assets` (every asset attributed, no Adobe formats) and the WASM build. Other workflows build and
test FreeBSD and Windows ARM64, lint packaging, and build releases.
The tests run under `cargo test --workspace --lib --bins --tests`, or `cargo nextest run` with the
same targets when `LIGHTCRAFT_NEXTEST=1` is set and cargo-nextest is installed; doctests run after
them with `cargo test --workspace --doc`. `cargo test` is the default because the demo library's
generated scenes are cached per process, and nextest's process per test regenerates them.
Examples are left out of the test build (clippy `--all-targets` still
checks them). Each crate links its integration tests into one binary, `tests/it/main.rs` with one
module per file (`cargo test -p <crate> --test it <module>::`); a test that changes state for its
whole process (a global allocator, closing the GPU) keeps a binary of its own in `tests/`. The WASM
build is one `cargo check --target wasm32-unknown-unknown --keep-going` over the wasm-safe crates.
`ci` also probes the machine: OS, CPUs, RAM, the GPU adapters of each backend (from the ignored
`gpu_probe` test of `lightcraft-gpu`, run after the workspace tests so it reuses their build; xtask
has no wgpu), the corpus and `CRAFT_FONTS_DIR`. Tests
@@ -77,5 +87,6 @@ the `lightcraft-gpu` tests once per backend with a hardware adapter (`LIGHTCRAFT
| Date | Change | Summary |
|---|---|---|
| 2026-10-23 | minor | Quality gates: one integration-test binary per crate, doctests apart, nextest on request, one WASM check |
| 2026-10-11 | minor | Quality gates: the CI machine probe, visible skips (`testkit`) and `target/ci/report.json` |
| 2026-10-10 | major | Created from the code (crate sizes, layers, data model, rendering, control, gates) |
+56 -3
View File
@@ -90,6 +90,7 @@ filmstrip is visible.
| `ui.set` | partial UI state, e.g. `{"view": "detail"}` | Resulting UI state |
| `ui.dialog.confirm` / `ui.dialog.cancel` | — | Close the open dialog (confirm runs its action; Synchronize Folder on a folder that is up to date answers `{"upToDate": true}` and starts nothing) |
| `ui.resize` | `{width, height}` | Resize the window |
| `ui.dropFiles` | `{paths: [..], hover?}` | Files dragged from the file manager. `hover: true` makes every following frame report `paths` as hovered over the window (the drop card appears, `ui.widgets` lists `dropOverlay`) until another `ui.dropFiles` replaces them; `{paths: [], hover: true}` ends the hover. Without `hover` the files are dropped: the hover ends and the next frame receives them as dropped files, exactly as an OS drop arrives (photos and folders open the import review, or import at once with Settings ▸ Import ▸ Import dropped files without review). An empty path stands for a file whose path the platform doesn't tell |
| `ui.zoomFactor` | `{factor}` | Set the interface scale (0.5–3; egui's zoom keys are off, Cmd+= / Cmd+- zoom the photo) |
| `ui.screenshot` | `{path?, headless?}` | `{path, width, height}` once the frame (with finished renders) is captured. `headless: true` draws the UI on the CPU (no compositor needed); a windowed capture that gets no frame within 2 s falls back to headless automatically. While the loupe still waits for a render, `ui.widgets {filter: "loupe:rendering"}` lists its "Rendering…" pill; `loupe:renderFailed` is the pill saying a render failed behind a stand-in; Compare and Reference panes have their own (`compare:rendering:<pane>`, `compare:renderFailed:<pane>`) |
| `engine.execute {command: "app.export", params}` | export params (see `docs/mcp.md`), plus `preset`, `dir` / `path`, `ids`, `background`; `destinationMode: "sameAsOriginal"` writes each photo into its original's folder (+ `subfolder`) instead of `dir` | Writes the files and returns `{files}` (one `{path, width, height, bytes, sidecars}` per file, plus `gpuFallback` — why that render fell back from the GPU to the CPU — when it did; see `docs/gpu-pipeline.md`); with `background: true` (what the Export dialog and menus use) it returns `{background: true, total}` at once and the batch runs on a worker thread — poll `ui.inspect` → `export` |
@@ -165,7 +166,42 @@ catalog, a missing drive), the session starts empty and in memory — never with
to do: `ui.inspect` → `libraryProblem` is `{path, error, temporarySession, pendingImport}` (else `null`); its buttons
are `button:libraryRetry`, `button:libraryChoose`, `button:libraryTemporary` (Continue Without Saving) and
`button:libraryQuit`. A temporary session shows a banner (`indicator:temporarySession`, `button:libraryReopen`) and
writes nothing. `app.openLibrary` opening a library ends it.
writes nothing. `app.openLibrary` opening a library ends it. `pendingImport` counts the files waiting for a
library: those given on the command line, and photos dropped on the window while it (or the launch list below) is
up (desktop app); they are imported into the library that opens, or into the temporary session. Presets dropped
then are refused with a toast.
### Choosing the library at launch
With Settings ▸ General ▸ At launch set to Ask me (`ui.set {"settings": {"atLaunch": "ask"}}`; `"last"`, the
default, opens the last library), or Alt/Option held during the first 2 s after the window appears, the desktop app
shows a window listing the recent libraries (issue #1290). `--library` and `LIGHTCRAFT_LIBRARY` skip it, and so does
`--memory`; `lightcraft-cli` never shows it. `ui.inspect` → `libraryProblem.chooser` is `{libraries: [{path,
lastOpened, available, checking, reason}], selected, libraryOpen}` (else `null`; `libraryOpen: true` when it was opened
over the last library with Alt). Each library is checked read-only on a worker thread of its own, never on the UI
thread (the folder and its catalog file are looked at, never locked or written, so a library another program has
open shows as available and Open reports who has it in `libraryProblem.error`). `checking: true` until its check
answers (the row says *Checking…*, then that the drive isn't answering after 5 s); `available` is `false` only
once a check found a `reason`, so a row still checking can be opened. The selection starts on the last library and
moves to the first one that can be opened once every check has answered, unless it was moved meanwhile. Each row is the widget `libraryRecent:<index>`; the buttons are `button:libraryOpen`,
`button:libraryOpenAnother` (the folder dialog of `app.openLibrary`), `button:libraryNew` (`app.newLibrary`),
`button:libraryForget` (Remove from List) and `button:libraryQuit`; `ui.key` ArrowUp / ArrowDown move the selection,
Enter opens it and Escape quits (over an open library: closes the window and keeps it). `app.openLibrary {path}`
opening a library ends it. While it shows, the settings are saved as usual (a removal is kept).
`app.recentLibraries` returns `[{path, name, exists, checking, open, lastOpened}]`. `exists` is what the last
background check found (`false`: moved, on a disconnected drive, not a folder or no longer a library; `null`: not
checked yet, or the check couldn't tell, e.g. a folder that can't be listed, an unreadable catalog or a lookup that
failed) and `checking` whether one
is running: the command never looks at a folder itself. A failed open never marks a library missing (another program
may have it). Recent libraries are checked in the
background when the app starts (the first 8) and when the launch window opens; one not found is looked for again every
30 s (a drive plugged back in), the others only when the window comes back to the front (a timer would wake external
drives and shares). File ▸ Open Recent Library greys out only an entry a check found missing (not there, not a folder,
no library in it); an unreadable catalog, a folder that refuses to be listed, a failed open or a lookup that failed
(a share that timed out) doesn't grey it out. `lastOpened`: ISO 8601, `null` when not known; kept in `ui.json` →
`settings.recentLibrariesOpened`. `{remove: path}` forgets one entry (an error if the list
doesn't hold it) and `{clear: true}` (or `app.clearRecentLibraries`) all of them; nothing on disk is touched.
### Open Library on a Lightroom catalog
@@ -173,12 +209,29 @@ writes nothing. `app.openLibrary` opening a library ends it.
folder (it holds a `*.lrcat`), one of its `*.lrdata` / `*.lrcat-data` folders or `Backups/`, the `.lrcat` itself or
its `-wal` / `-shm` / `.lock` files, it keeps the library open now and returns `{opened: false, lightroomCatalog:
<the .lrcat found, or null>, path}`; the window shows a dialog whose `button:dialogOk` (Import Lightroom Catalog…, or
`ui.dialog.confirm`) runs `file.importLightroom` with that catalog (no catalog found: it asks for one) and
`button:dialogCancel` (`ui.dialog.cancel`) does nothing. A folder that is a LightCraft library (`catalog.snap` / `catalog.log`) always
`ui.dialog.confirm`) runs `file.importLightroom` with that catalog (no catalog found: it asks for one), which then
asks where the catalog goes when `settings.lightroomImport` is `"ask"` (next section), and `button:dialogCancel` (`ui.dialog.cancel`) does nothing. A folder that is a LightCraft library (`catalog.snap` / `catalog.log`) always
opens as one. With no library open (the library problem window) it is an error instead. The engine refuses the same
places (`EngineError::LightroomCatalog`, so `lightcraft-cli --library` and `mcp --library` too). `library.importLightroom
{status: true}` reports the catalog's `path` after the task ends as well as while it runs.
### Import Lightroom Catalog: where the catalog goes
`file.importLightroom {path?, into?, name?, location?}` (File ▸ Import Lightroom Catalog…, issue #2672). With `into`
it acts without asking: `"open"` imports into the library open now (`library.importLightroom`); `"new"` creates the
library `name` (default: the catalog's file name without `.lrcat`, or `Travel 2`… when taken) in the folder
`location` (a full path; default: the open library's parent folder) under `app.newLibrary`'s rules, opens it and
imports there, returning the import's start with `library: {path, name, photos}`. A catalog file that isn't there is
refused before anything is made; so is a new library while an import runs. Without `into`, a library on disk with
`settings.lightroomImport` `"ask"` (the default) opens a window and returns `{asking: true, path, into}` (`into`: the
choice it opens with, `"open"` when the library is empty, else `"new"`). Its widgets:
`radio:lightroomDestination-open`, `radio:lightroomDestination-new`, `field:lightroomLibraryName`,
`field:lightroomLibraryLocation`, `button:lightroomLibraryChoose` (where there is a folder dialog), `check:lightroomDontAsk` (with Merge only; sets
`"alwaysMerge"`), `button:dialogOk` / `ui.dialog.confirm` (which stays open on an error such as a folder that isn't
empty) and `button:dialogCancel`. With `"alwaysMerge"` (`ui.set {"settings": {"lightroomImport": "alwaysMerge"}}`,
or Settings ▸ General `button:settingsLightroomImport-0|1`), or in a session in memory, it imports into the open
library without asking.
## External controllers
External apps can use this channel to send commands and poll the running desktop app for feedback
+3 -3
View File
@@ -39,7 +39,7 @@ decoded.
## Variant status
The supported and rejected CRX coding variants. Every rejection is an explicit `RawError::Unsupported` whose
text is part of the contract: `crates/raw/tests/cr3_variants.rs` rebuilds each variant as a synthetic CR3
text is part of the contract: `crates/raw/tests/it/cr3_variants.rs` rebuilds each variant as a synthetic CR3
fixture and asserts the exact string, and that header probing (`probe_info`, the import path) and full
decoding fail with the same error, because the engine shows that reason when it falls back to the embedded
preview. The same tests decode a control fixture at both edges of each accepted range, so a rejection can
@@ -87,7 +87,7 @@ CMP1 bit depth. A lossless sample outside the bit depth is still corrupt data.
Other unverified coding variants return explicit unsupported errors (the table above lists them all). Header probing validates the same coding variants and
tile/plane layout as full decoding.
`crates/raw/tests/cr3_corpus.rs` checks every full-sensor sample against a
`crates/raw/tests/it/cr3_corpus.rs` checks every full-sensor sample against a
black-box reference, before cropping or colour processing. Public CC0 files
are downloaded by `cargo xtask corpus --download` and kept outside version
control. The exact inputs and reference hashes are:
@@ -194,7 +194,7 @@ All 110 CR3 files on raw.pixls.us (2026-10-12, every one CC0, 31 folders) decode
falls back to the preview, so none of them uses a CRX variant the decoder refuses (vertical C-RAW tiles,
partial-subband or rounded-plane coding, multi-tile 0x200, adaptive low-frequency gains) or a CR3
sRAW / mRAW. HEIF stills are separate `.HIF` files, not CR3. One file per body is pinned in
`canon_cr3_bodies_decode_like_the_camera_jpeg` (`crates/raw/tests/cr3_corpus.rs`; URLs and SHA-256 in
`canon_cr3_bodies_decode_like_the_camera_jpeg` (`crates/raw/tests/it/cr3_corpus.rs`; URLs and SHA-256 in
`RAW_SAMPLES`, `xtask/src/main.rs`): EOS-1D X Mark III, 90D, 250D, 850D, M200, M50 Mark II, M6 Mark II,
R, R3, R5, R6, R6 Mark II, R6 Mark III, R7, R10, RP, R50, R50 V and PowerShot G5 X Mark II, G7 X Mark III,
SX70 HS and V1, C-RAW where the body has a CC0 C-RAW sample. No external decoder was run: the sensor
+2 -2
View File
@@ -117,8 +117,8 @@ 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
LC_DENOISE_MODEL=<model.onnx> LC_DENOISE_RAW=<corpus/raw> cargo test --release -p lightcraft-engine --features rawnind-model --test it denoise_cpu:: -- --ignored --nocapture
LC_DENOISE_MODEL=<model.onnx> LC_DENOISE_RAW=<corpus/raw> cargo test --release -p lightcraft-engine --features rawnind-model --test it denoise_real:: -- --ignored --nocapture
```
## Another computer's graphics card
+1 -1
View File
@@ -6,7 +6,7 @@ zooms to it, so checking whether the subject is sharp takes one key while cullin
## Using it
- **View ▸ Show Focus Point** (⌥F; `view.focusPoint {on?}`) draws the camera's AF frame as green corner brackets in
- **View ▸ Focus Point** (⌥F; `view.focusPoint {on?}`) draws the camera's AF frame as green corner brackets in
Detail, Compare and Survey. It follows Rotate, flips and the crop; a point cropped away is not drawn. Off by
default; the choice is saved with the app's settings.
- **View ▸ Zoom to Focus Point** (`view.zoomToFocus {on?}`, on by default): Toggle Zoom (Z or Space) zooms in
+1 -1
View File
@@ -161,5 +161,5 @@ before demosaicing would be needed.
vignette gain on the X-T4's real arrays, and the X-T4 geometry against its measured free fit.
- `crates/raw/src/vendor/raf.rs`: a synthetic RAF through header and full decode, only for validated models, and a DNG
round trip.
- `crates/raw/tests/corpus.rs` `fuji_embedded_lens_tables_follow_the_validated_models`: the corpus' `raf-fuji-*` files,
- `crates/raw/tests/it/corpus.rs` `fuji_embedded_lens_tables_follow_the_validated_models`: the corpus' `raf-fuji-*` files,
or any folder of RAFs in `LIGHTCRAFT_FUJI` (the pixls set: 129 decoded, 88 warps, 98 vignette gains).
+22 -6
View File
@@ -1,6 +1,6 @@
# Gaps: where LightCraft falls short of Lightroom
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-21 · **Change:** minor (gap 28: F8 shows / hides the right panel; import review album hierarchy, #2362; gap 20: Minolta MRW decodes; gap 10: 551 models verified; gap 15: Print Page Options — page numbers, page info, crop marks; gap 15: Print Page Options — page numbers, page info, crop marks; gap 28: `\` shows / hides the Filter Bar in the grids; gap 21: Indonesian interface shipped, every message but What's New; gap 27: the Windows x64 installer verified on Windows 11; the Settings → Apps uninstall confirmation remains)
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-23 · **Change:** minor (gap 13: before a Lightroom catalog import the app asks merge or new library, #2672; gap 28: F8 shows / hides the right panel; import review album hierarchy, #2362; gap 20: Minolta MRW decodes; gap 10: 551 models verified; gap 15: Print Page Options — page numbers, page info, crop marks; gap 28: `\` shows / hides the Filter Bar in the grids; gap 21: Indonesian interface shipped, every message but What's New; gap 27: the Windows x64 installer verified on Windows 11; the Settings → Apps uninstall confirmation remains; gap 28: ⌘L Enable Filters switches the filter off and on; gap 15: Japanese photo and postcard paper sizes in the Print page setup and PDF)
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
@@ -23,11 +23,11 @@ Hours are Opus 5.5 agent wall-clock hours (see [target-app-parity.md](target-app
| 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, Sony ILCE-7RM4A, full-frame ILCE-7CR distortion (Auto only) and ILCE-7RM6 distortion (`docs/sony-ilce7rm6-lens-corrections.md`) 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) |
| 9 B | **Tests don't run in CI on macOS or Windows**: Linux x64 runs `cargo xtask ci` on every PR and push to main (`.github/workflows/ci.yml`) and FreeBSD runs `cargo test`; the others build packages | `.github/workflows/` (ci, freebsd, packaging-lint, release, windows-arm64); the linux-riscv64 release job added in 0.6.0 (#844) runs only `lightcraft-cli --version` under QEMU; macOS / Windows jobs: #1148 | regressions in `cfg(target_os)` code reach macOS and Windows users | 6–12 | [target-app-parity](target-app-parity.md#by-dimension) |
| 10 B | **Per-model verification thin, plus cheap per-body decode fixes**: 551 of 1,446 Adobe models verified (38%), ~85% of photographers on a verified body; Nikon 5 bodies verified, Canon 34, 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 regression (#702); F828 SRF now discovered and imported as preview-only, sensor decoding still missing, 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; main-file pages read on demand up to 16 GiB, with dense-table and WAL bounds retained (#798); offline record counts and destination are now shown in a lasting import result | lightroom-catalog-import.md | switching users lose some looks | 8–12 | [file-format-parity](file-format-parity.md) |
| 13 | **Lightroom catalog migration is one-way and approximate**: unsupported Adobe profiles / AI settings archived, history kept as data; main-file pages read on demand up to 16 GiB, with dense-table and WAL bounds retained (#798); offline record counts and destination are now shown in a lasting import result; before an import the app asks whether the catalog merges into the open library or goes into a new library (opt-out in Settings, #2672) | lightroom-catalog-import.md | switching users lose some looks | 8–12 | [file-format-parity](file-format-parity.md) |
| 14 | **HDR display** (EDR / Windows HDR); the headroom limit and SDR rendition from `crs` XMP (the HDR switch, `HDREditMode`, is read). HLG AVIF export done; the display's SDR/HDR decision is tested, blocked on eframe's 8-bit surface | HDR rows 70%; LR-VIEW-HDR-DISPLAY ⬜ | HDR editing works blind on HDR monitors | 12–25 | [hardware-parity](hardware-parity.md) |
| 15 | **Print module**: identity plate / watermark / photo info overlays, Cut Guides for the packages, Custom Package Auto Layout, printer-driver Page Setup and direct printing, JPEG file output (the Print dialog writes a colour-managed PDF) | LRC-PRINT rows | Classic users who print | 15–25 | [target-app-parity](target-app-parity.md#features) |
| 16 | **Map view, Book, Slideshow module, publish services** | LRC-MAP / BOOK / SS rows ⬜ | Classic users; publish services matter for Flickr / SmugMug workflows | 60–100 | [target-app-parity](target-app-parity.md#features) |
@@ -35,7 +35,7 @@ Hours are Opus 5.5 agent wall-clock hours (see [target-app-parity.md](target-app
| 18 | **Video**: playback, trim, global edits, export | VID rows 0% | users who mix stills and clips | 25–45 | [target-app-parity](target-app-parity.md#features) |
| 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, ERF, KDC, DCR, MEF, GPR, old FinePix (MRW decodes since 2026-10-18) | raw-parity.md | small user share each | 40–80 | [raw-parity](raw-parity.md) |
| 21 | **Localization**: 10 of 16 shipped languages partial (de, es, fr, ja, uk now 97–99% of catalog messages and id every message but What's New, awaiting native review; pt-BR, ru, zh-hans, zh-hant 74–92%), lower-layer errors English; Hindi, Arabic, Korean, Vietnamese absent; no RTL or complex shaping | measured from catalogs | non-English users see mixed English | 40–70 (shipped to full); 135–239 (four new) | [localization-parity](localization-parity.md) |
| 21 | **Localization**: 10 of 16 shipped languages partial (de, es, fr, ja, uk now 97–99% of catalog messages and id every message but What's New, awaiting native review; pt-BR, ru, zh-hans, zh-hant 74–92%), lower-layer errors English; Hindi, Arabic, Korean, Vietnamese absent; no RTL or complex shaping; exports add Thai glyphs only (#2052), with horizontal mark positioning still incomplete and Arabic fallback deferred until joining and bidi exist | measured from catalogs | non-English users see mixed English | 40–70 (shipped to full); 135–239 (four new) | [localization-parity](localization-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 on macOS / Linux tablets** (brush size and flow follow pressure where winit reports a pen force), 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) |
@@ -43,7 +43,8 @@ Hours are Opus 5.5 agent wall-clock hours (see [target-app-parity.md](target-app
| 26 | **Performance at scale**: 85k-photo library opens in 1.7–4.7 s; 100k grid test pending; colour NR at half resolution; at 100k photos stack and album lookups are indexed (an "In a Stack" or "Album" smart album counts in ~14 ms, was 0.6–2.7 s) | ROADMAP log, gpu-pipeline.md | large-catalog users | 15–30 | [target-app-parity](target-app-parity.md#by-dimension) |
| 27 | **Windows / Linux runtime coverage**: the Windows x64 installer is verified on Windows 11 (0.9.1: install and upgrade end on the completion page, a custom folder is kept, Launch works, removal is clean), but uninstalling from Settings → Apps ends with no confirmation (#892, #931); GPU path proven on Apple and on one AMD GPU on Linux (Radeon 8060S, Vulkan); NVIDIA, Intel and DX12 unverified; the Linux riscv64 tarball (new in 0.6.0, #844) is cross-compiled and never run beyond a QEMU smoke test | ROADMAP, issues, `release.yml` | non-Mac users find bugs first | 13–27 | [hardware-parity](hardware-parity.md) |
| 29 | **HDR merge leftovers** (LR-MERGE-HDR): AI Denoise doesn't run on merged (linear) DNGs; JPEG brackets are treated as linear (no camera response curve); every frame is held as 32-bit float, ~0.86 GB per 67 MP frame (half-float frames would halve it) | tested on Sony A7R VI brackets 2026-10-14 ([merge.md](merge.md)); Lightroom's Denoise supports HDR DNGs | HDR shooters who denoise their merges, merge JPEGs, or merge 7+ frames of high-resolution raws on 16 GB machines | 10–20 (+ denoise linear-RGB contract, gap 11) | [merge](merge.md) |
| 28 | **Small Classic shortcuts and keys**: ⌘L (Enable Filters), Tab / ⇧Tab (hide the side panels / all panels; Tab moves the keyboard focus here) and F5. Rating `[` `]`, flag cycling (`` ` ``), `\` (Filter Bar), F1 help, F6 / F7 / F8 (filmstrip, left panel, right panel) are bound | [ui-parity](ui-parity.md#shortcuts) | Classic muscle memory | 2–4 | [ui-parity](ui-parity.md) |
| 28 | **Small Classic shortcuts and keys**: Tab / ⇧Tab (hide the side panels / all panels; Tab moves the keyboard focus here) and F5. Rating `[` `]`, flag cycling (`` ` ``), `\` (Filter Bar), ⌘L (Enable Filters), F1 help, F6 / F7 / F8 (filmstrip, left panel, right panel) are bound; the grid-cycle key is shown beside both grid rows in View (#2500) | [ui-parity](ui-parity.md#shortcuts) | Classic muscle memory | 2–4 | [ui-parity](ui-parity.md) |
| 30 | **Multi-select metadata editing in the UI**: `photo.setMeta` already iterates all selected photos, so the engine applies changes to every target, but the Info panel and All Metadata dialog always show one photo's values with no "mixed" indication when selected photos differ — users cannot tell whether a field is uniform or mixed across the selection before editing | standard in Lightroom Classic's multi-select Info panel | anyone editing metadata on a batch (copyright, keywords, custom fields) | 4–8 | [ui-parity](ui-parity.md) |
Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform work they need around them,
**~340–600 h** to beta (incl. 40–80 h for the ~92 open core-path user issues) (see [target-app-parity.md](target-app-parity.md#stage)).
@@ -51,7 +52,7 @@ Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform w
## Gaps by kind
- **Feature:** 5, 8, 11, 12, 13, 15, 16, 18, 19, 29
- **UI / UX:** 4, 23, 24, 28
- **UI / UX:** 4, 23, 24, 28, 30
- **File format:** 2, 6, 7, 10, 13, 20, 25 (raw ones in [raw-parity.md](raw-parity.md))
- **Hardware:** 14, 17, 22, 27
- **Localization:** 21
@@ -60,6 +61,10 @@ Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform w
## Closed recently (kept for calibration)
- 2026-10-22: saving XMP preserves foreign namespaces even when they use `lc:` or `xmp:` prefixes; importing them no longer interprets their properties as native settings or ratings. Parser, merge and save-command regressions cover the correction; no ranked gap or readiness estimate changes.
- 2026-10-22: PSD/PSB imports and metadata readers now expose embedded EXIF resource 1058 and XMP resource 1060 for capture dates, GPS and All Metadata (#2519); imported PSD edit copies retain their capture date, and the `removeLocation` Original-export guard sees embedded PSD/PSB GPS. No ranked gap or headline feature count closes.
- 2026-10-20: the import review album picker follows the sidebar folder hierarchy and searches album paths (#2362, LR-IMP-ADD-DIALOG), retaining None/New album and existing destination IDs. This closes a usability subgap; no ranked gap or headline feature count closes.
- 2026-10-12: original-pixel detail at 100% (gap 4, LR-VIEW-ZOOM, #323): soft proofing gets zoom windows like
@@ -79,13 +84,24 @@ Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform w
| Date | Change | Summary |
|---|---|---|
| 2026-10-23 | minor | Gap 13: before a Lightroom catalog import the app asks whether it merges into the open library or goes into a new one (opt-out in Settings, #2672); hours unchanged |
| 2026-10-22 | patch | Gap 28: View shows the current grid-cycle key beside Photo Grid and Square Grid (#2500); no new binding and no change to the remaining shortcut gap or hours |
| 2026-10-22 | minor | Gap 15: the Print page setup and PDF gain the Japanese photo and postcard paper sizes (L判 89×127, 2L判 127×178, KG判 102×152, はがき 100×148, 往復はがき 148×200, 六切 203×254, 四切 254×305, 名刺 55×91 mm), by their customary names; gap not closed |
| 2026-10-22 | minor | #2052: Thai glyphs added to shared export text; gap 21 retains Thai mark positioning, Arabic joining and bidi limits. No ranked gap closes; estimates unchanged |
| 2026-10-24 | minor | LR-LIB-SMARTALBUM: relative-date views and source totals expire on the session-clock minute, including nested smart albums; ranked gaps and readiness counts unchanged |
| 2026-10-22 | patch | XMP namespace-prefix collisions no longer overwrite foreign metadata; ranked gaps and estimates unchanged |
| 2026-10-22 | patch | Issue #2519: PSD/PSB EXIF resource 1058 and XMP resource 1060 now use the common metadata extractor, and `.psb` files are accepted by import; no ranked gap or readiness count changes |
| 2026-10-22 | minor | Gap 30 added: multi-select metadata editing — Info panel and All Metadata dialog show one photo's values with no mixed-value indication |
| 2026-10-21 | minor | Gap 28 shrinks: F8 shows / hides the right panel (`view.rightPanel`); rating `[` `]`, flag cycling and F1 help were already bound, so the entry lists only filter-bar keys, Tab / ⇧Tab and F5; 2–4 h (was 4–8) |
| 2026-10-20 | minor | #2362: import destination albums use the shared hierarchy/search picker instead of a flat name list; existing import workflows retained; ranked gaps/readiness counts unchanged |
| 2026-10-19 | minor | Gap 9 shrinks: `.github/workflows/ci.yml` runs `cargo xtask ci` on Linux x64 for every PR and push to main; macOS and Windows remain |
| 2026-10-19 | minor | Gap 21: Indonesian (`id`) shipped with every catalog message but What's New; four new languages left (Hindi, Arabic, Korean, Vietnamese), 135–239 h |
| 2026-10-19 | minor | Gap 27: the released 0.9.1 x64 MSI is tested on Windows 11 Pro 24H2 (a fresh install and an upgrade from 0.8.0 end on the completion page, so #790 is fixed; the upgrade keeps a custom folder; Launch starts the app; removal leaves nothing behind); uninstalling from Settings → Apps still ends with no confirmation (#892, #931); hours unchanged |
| 2026-10-18 | patch | Gap 1: six X-Trans CMOS 4 Fujifilm bodies (X-T30 …) take the X-T3's spectral matrices instead of the neutral matrix |
| 2026-10-15 | minor | Gap 15 shrinks: the Page panel's Page Options (Page Numbers, Page Info, Crop Marks) print on Single Image and Contact Sheet pages; identity plate, watermark, photo info, Cut Guides, direct printing and JPEG output remain |
| 2026-10-14 | minor | Gap 4 follow-up: the diagnostic overlays (Visualize Spots, Point Color range, HDR range) render zoom windows too; soft proofing runs on the GPU |
| 2026-10-12 | minor | Gap 10: SR2/SRF folder discovery fixed; F828 imports as preview-only. Sensor decoding remains a gap; readiness scores unchanged |
| 2026-10-23 | minor | Gap 28: ⌘L / Ctrl+L (Library ▸ Enable Filters) puts the filter aside and brings it back as it was; the entry drops ⌘L; hours unchanged |
| 2026-10-15 | minor | Gap 28: `\` shows / hides the Filter Bar in the grids; the entry drops `[` `]` (#509), flag cycling (#1490) and F1 (Help), already on main; ⌘L is the filter-bar key left; hours unchanged |
| 2026-10-14 | minor | Gap 26: at 100k photos stack and album lookups are indexed, so an "In a Stack" or "Album" smart album counts in ~14 ms (was 0.6–2.7 s); open time unchanged |
| 2026-10-14 | minor | Gap 10: Camera-coverage sweep (#1327): 30 models verified on 40 CC0 files (coverage.py 515 → 545 verified); sensor verification does not establish colour parity (#1277) |
+18 -4
View File
@@ -4,6 +4,10 @@ The CPU pipeline (`crates/pipeline`) is the reference ("oracle"). `lightcraft-gp
evaluates the same stages with wgpu compute shaders (WGSL) on Metal / Vulkan / DX12. Everything is
pure Rust (wgpu, naga); the drivers are the system's. No GL backend is compiled in.
CPU fast guided-filter allocation measurements and reproduction commands are documented in
[guided-filter-memory.md](guided-filter-memory.md). That optimization retains the full-size base
and does not change the GPU's coefficient buffers.
Warped geometry uses a CPU-computed coverage bit mask for the source boundary.
The CPU reference evaluates that boundary in double precision; GPU float
rounding otherwise can turn a blank edge pixel into a photo pixel. The mask
@@ -101,7 +105,8 @@ or worked around with `LIGHTCRAFT_GPU_BACKEND`.
**The window's renderer and its fallback (Windows).** Before the window opens, each renderer is probed in
order: DX12, Vulkan, GL, then eframe's OpenGL renderer (glow). A probe creates a device and builds egui's
pipeline inside wgpu error scopes, so a driver that cannot compile egui's shaders ("The parameter is
incorrect. (0x80070057)" in `egui_pipeline`) is logged and skipped instead of killing the app. The renderer
incorrect. (0x80070057)" in `egui_pipeline`) is logged and skipped instead of killing the app; no pop-up
unless that renderer was forced (#1624). The renderer
that worked is remembered in the settings folder (`window-renderer`); a start that dies before the window is up
leaves `window-init.marker`, and the next start (relaunched automatically, at most 4 times) tries that
renderer last. `LIGHTCRAFT_GPU_BACKEND=dx12 | vulkan | gl | glow` (or `WGPU_BACKEND`) forces one; DX12 stays
@@ -161,6 +166,8 @@ walks the drive.
(log luminance, dark channel, guided-filter steps, white balance, luminance / colour NR, airlight
sampling), `mask` (linear / radial / luminance / colour range / brush shapes, combine, finalize),
`finish` (the whole per-pixel stage) — each mirrors a named CPU function.
- Soft proofing against an output space (`RenderRequest::proof`, with its destination and display gamut
warnings) runs in `finish`; a proof against an output-device profile (its 3D table) renders on the CPU.
- Per-stage hybrid: defringe and spot removal (rare, CPU-only for now) download the resampled image,
run on the CPU and upload; heuristic mask shapes (Sky, Subject, Background, …) and brushes of more
than 4096 dabs are evaluated on the CPU and uploaded; sources over the buffer limit are resampled
@@ -229,7 +236,7 @@ passes on as `RenderResult::gpu_fallback`; an export carries it as `Exported::gp
each file of an `app.export` batch result has `gpuFallback` when its render fell back (issue #675).
**Reproducing a smaller GPU:** `LIGHTCRAFT_GPU_LIMITS=webgpu` (or `downlevel`) creates the device
with 128 MiB storage bindings / 256 MiB buffers, `LIGHTCRAFT_GPU_LIMITS=<n>` with n MiB / 2n MiB.
Tests inject failures with `lightcraft_gpu::inject_fault` (`crates/gpu/tests/fallback.rs`,
Tests inject failures with `lightcraft_gpu::inject_fault` (`crates/gpu/tests/it/fallback.rs`,
`export_falls_back_to_the_cpu_when_gpu_work_is_lost`). The fault is thread-local, and so are its
consequences: a simulated fatal failure stops GPU use on the injecting thread only (`available()`
false, `unavailable_reason()` set there), until `reset_failures()`, which undoes nothing else — a
@@ -237,7 +244,7 @@ real failure still stops the whole process. Such a test asserts on its own rende
`last_fallback()`, so it can run beside other tests rendering on the GPU (issue #675).
## Correctness: CPU oracle and equivalence tests
`crates/gpu/tests/equivalence.rs` renders the same settings on both and compares 8-bit sRGB:
`crates/gpu/tests/it/equivalence.rs` renders the same settings on both and compares 8-bit sRGB:
bounds **mean |Δ| < 0.5 LSB and max |Δ| ≤ 3 LSB** per channel. Measured (Apple M4 Pro, Metal):
**max 1 LSB, mean ≤ 0.0003 LSB** for every case — 26 settings cases (all tone/colour/effects tools,
3 vignette styles, grain, curves, grading, B&W, WB, NR, crop/straighten/flip, lens + perspective,
@@ -254,12 +261,19 @@ cut back after the readback. The tests skip (pass with a note) when no adapter e
non-Apple GPU verified: all 16 tests pass, 135 compared cases. 124 differ by max 1 LSB, 10 are
bit-identical, and one (the point colour range overlay) has max 2 LSB on 0.0002% of pixels. Worst
mean 0.0003 LSB. That is the same picture as on Metal, inside the bounds
(`cargo test -p lightcraft-gpu --release --test equivalence -- --nocapture`).
(`cargo test -p lightcraft-gpu --release --test it equivalence:: -- --nocapture`).
Remaining differences come from f32 vs f64 coordinate math, fast-math transcendental functions on
Metal and summation order in the box filters — all far below one 8-bit step.
## Measurements
**In the app:** Help ▸ Debug ▸ Frame Rate (`view.frameRate`, `{show?}`; `LIGHTCRAFT_FPS=1`
starts with it on, never saved) draws frames per second, each frame's UI CPU time against its
time spent waiting (for vsync, or for the GPU), the latest whole-frame and zoom-window render
times, the render queue, renders thrown away as superseded, the GPU adapter and its memory, and a
graph of the last frames with a tick where a render arrived. Once a second it logs the same
numbers (`fps-hud:`), so a scripted drag (`ui.dragWidget`) can be measured from the log.
`render_bench` on `corpus/raw/arw-sony-a7m3-compressed.arw` (24 MP; loupe = 2560 px preview →
1920×1280), min of 7 runs, wall clock, on a heavily shared machine (load average 60–110 on 14 cores,
so CPU numbers are pessimistic; the GPU numbers are less affected):
+53
View File
@@ -0,0 +1,53 @@
# CPU fast guided-filter memory
`local::guided_fast` computes blurred coefficients on the same reduced grid as before, then
samples them while applying `a * p + b`. It keeps the raster resizer's weights, boundary
normalization and horizontal-then-vertical accumulation order. Odd dimensions therefore use
their actual reduced/full dimension ratios. Four coefficient row pairs and one bias row are cached
per active 32-row band; adjacent output rows reuse their horizontal samples.
The two full-resolution coefficient planes and their resize intermediates are gone. The returned
full-resolution base remains: downstream CPU tone mapping and the spatial-plane cache need it.
The step-1 filter, Box reduction, Gaussian coefficients, V2's median input, process selection and
Draft sigma cap are unchanged. The GPU implementation is unchanged; this reduces CPU memory only.
## Reproduce
The dev-only example includes the previous materialized algorithm and uses the workspace's
existing `dhat` allocator. No allocator or profiling dependency is added to product builds.
```sh
cargo build --release -p lightcraft-pipeline --example guided_memory
RAYON_NUM_THREADS=1 target/release/examples/guided_memory old 6000 4000 120 15
RAYON_NUM_THREADS=1 target/release/examples/guided_memory new 6000 4000 120 15
RAYON_NUM_THREADS=1 target/release/examples/guided_memory old 6000 4000 120 1 --heap
RAYON_NUM_THREADS=1 target/release/examples/guided_memory new 6000 4000 120 1 --heap
```
If `CARGO_TARGET_DIR` is set, use that directory instead of `target` for the executable.
Arguments are mode, width, height, integer sigma and timing iterations. The example warms up
before measurement; `--heap` counts one filter call with the source allocated beforehand.
Time the runs without `--heap`: allocation profiling significantly changes timings. It reports
the minimum wall time; on a shared machine repeat when other builds/renders have stopped.
Use `LIGHTCRAFT_PROFILE=1` and `cargo xtask bench` for broader pipeline measurements.
## Observed allocation and time
Measured 2026-10-16 UTC, macOS aarch64, Rust 1.96.0, release build, one Rayon thread. Bytes are
requested heap bytes counted by dhat, not process RSS. Timings are minimums of 15 unprofiled
calls on a shared machine with concurrent builds; they are indicative, not a speed guarantee.
| Dimensions / sigma / step | Old peak bytes | New peak bytes | Old total allocated bytes | New total allocated bytes | Old / new wall ms |
|---|---:|---:|---:|---:|---:|
| 6000 × 4000 / 120 / 16 | 289,125,000 | 97,780,856 | 313,083,488 | 135,643,856 | 42.135 / 45.131 |
| 1601 × 1067 / 24 / 8 | 20,822,412 | 7,331,172 | 25,102,604 | 11,562,560 | 3.359 / 3.121 |
The 24 MP result saves about 191 MB of peak allocation (66%). Its **96,000,000-byte output is
included**; its **96,000,000-byte input is excluded**. Application RGB images, log luminance,
V2 median input and its construction temporaries, other spatial planes and caches also remain
outside this isolated measurement. More Rayon threads can hold more band scratch concurrently.
This is not a bound on total pipeline memory.
Regression tests compare the sampled result exactly against materialized bilinear resizes for
empty/singleton/one-dimensional, odd/even and multiple-band images; flat fields, signed texture,
edges and impulses; small/full and reduced filters; and median tone bases.
+1
View File
@@ -17,6 +17,7 @@ Select photos in Photo Grid or Square Grid, then use:
| `Cmd`/`Ctrl` + `↑` / `↓` | Raise / lower flag status (reject < unflagged < pick; stops at either end) |
| `Shift` + `0`–`9`, `P`, `X` or `U` | Apply the action and select the next photo |
| `\` (backslash) | Show / hide the Filter Bar |
| ⌘L / Ctrl+L | Enable Filters: switch the filter off and on |
Enter opens the active photo without changing the selection. Text fields, dialogs and open popups keep the key; a custom Enter binding takes precedence. Enter has no additional action in Detail.
+37 -3
View File
@@ -6,7 +6,41 @@ and a C SQLite runtime are not required. The source database is read-only, inclu
File → Open Library… given a Lightroom catalog folder (or an `.lrdata` / `.lrcat-data` folder, or the `.lrcat`)
doesn't make a LightCraft library there: it offers this import instead, with the catalog found
(`crates/engine/src/lightroom_folder.rs`, issue #891). `lightcraft-cli --library` refuses those places too.
(`crates/engine/src/lightroom_folder.rs`, issue #891), and then asks where the catalog goes as described next
(unless the question is turned off, below).
`lightcraft-cli --library` refuses those places too.
**Where the catalog goes.** Before the import starts, the desktop app asks where the catalog goes (issue #2672),
unless Settings says to always merge (*Turning the question off*, below):
- **Merge into the open library "<name>" (N photos)**: the catalog's photos, collections and edits join the ones
already in the library open now (the library's name is its folder's name).
- **Create a new library**: named after the catalog file (`Travel.lrcat` → "Travel", or "Travel 2" when a
"Travel" folder is already there) and placed next to the open library (in its parent folder); both can be
changed (Choose… picks the folder). LightCraft creates and opens it as File → New Library… does (the folder
must be new or empty; it is refused while another import runs), then imports the catalog into it. The library
left behind stays in File → Open Recent Library.
- **Cancel** does nothing.
The window opens on *Merge* when the open library has no photos (there is nothing to merge with) and on *Create a
new library* when it has some.
**Turning the question off.** Settings → General → Library → *When importing a Lightroom catalog*: *Ask where to
import it* (the default) or *Always merge into the open library* (how imports always worked before). The window's
*Don't ask again (always merge into the open library)* checkbox, offered with *Merge*, picks the second; the window
says where to turn the question back on. The choice is an app setting (`ui.json`, `settings.lightroomImport`:
`"ask"` or `"alwaysMerge"`); a settings file without it asks.
A session with no library on disk (in memory, e.g. after Continue Without Saving) has nothing to put a new library
next to: the catalog merges into it without asking, as before.
**Scripts and agents.** The app command `file.importLightroom {path?, into?, name?, location?}` (control channel,
`lightcraft-cli mcp --connect`) chooses without the window: `into: "open"` merges, `into: "new"` creates the library
`name` (default: the catalog's name) in the folder `location` (default: the open library's parent folder; a full
path), opens it and imports. Without `into` it shows the window (and returns `{asking: true, path, into}`, `into`
being the choice the window opens with) or merges when the setting says so. A catalog path that doesn't exist is
refused before a new library is made. The engine command `library.importLightroom` always imports into the library
open now.
Equivalent commands:
@@ -15,8 +49,8 @@ lightcraft-cli run --library "LightCraft Library" library.inspectLightroom path=
lightcraft-cli run --library "LightCraft Library" library.importLightroom path="Lightroom Catalog.lrcat"
```
Import adds records to the currently open LightCraft library; it does not create a separate
folder named after the Lightroom catalog. The desktop result stays open until dismissed and
The import itself (`library.importLightroom`, `lightcraft-cli run`) adds records to the currently open LightCraft
library; only the desktop window above can make a new library for it first. The desktop result stays open until dismissed and
shows the destination library, the number of records added, missing originals and warnings.
**Show in Library** opens All Photos, clears filters and selects an imported record. Errors stay
readable in the same result window. If another dialog is open, the result waits until that dialog closes. The result
+3 -2
View File
@@ -46,8 +46,9 @@ ani anglické zdrojové řetězce. Rozložení panelů a seznamu jazyků je beze
## Ověření a revize
Testy `i18n::tests` ověřují registraci příkazu, přepínání přes nabídku a řídicí kanál,
uložení jazyka, pokrytí katalogů, placeholdery, neutrální formulace počtů, datum, čas
a české glyfy. Překladové šablony kontroluje také Rust při sestavení.
uložení jazyka, placeholdery, datum, čas a české glyfy. Překladové šablony kontroluje také
Rust při sestavení. Chybějící překlady se zobrazují anglicky a vypisuje je
`i18n::tests::translation_gaps_report`; žádný test kvůli nim neselže.
Při jazykové korektuře doporučujeme zkontrolovat „asistované třídění“, „sada“ pro Stack,
„bodová barva“, „měkký nátisk“ a názvy kreativních přednastavení.
+29 -3
View File
@@ -16,6 +16,32 @@ werden nicht übersetzt. Die vorhandenen Inter-Schriften enthalten die deutschen
für Deutsch ist kein zusätzlicher Font-Download nötig. Beschreibungen für SAM 3 werden
unverändert an das Modell übergeben; die Beispiele verwenden dessen englische Begriffe.
## Begriffe
Eine Durchsicht aller Oberflächentexte und Meldungen (2026-10-24, mit einem Agenten erstellt und
von einem Muttersprachler stichprobenartig geprüft; die Versionshinweise nicht) hat diese Begriffe
festgelegt. Neue Texte verwenden sie ebenfalls:
| Englisch | Deutsch | Anmerkung |
|---|---|---|
| Panel | Bedienfeld | wie in Lightroom; **Bereich** bleibt für Abschnitte (Sections) der Seitenleiste |
| Sidecar | Begleitdatei (XMP-Begleitdatei) | |
| Slide | Dia | passend zu „Diashow“ |
| Crop | Zuschnitt, zuschneiden | |
| Flag / Pick / Reject | Markierung / ausgewählt / abgelehnt | |
| Label (Color Label) | Farbmarkierung | nie nur „Markierung“, das ist die Ausgewählt/Abgelehnt-Kennzeichnung |
| Tint | Tönung | wie in Lightroom; „Tonung“ nur für Tonungsvorgaben wie Sepiatonung |
| Clipping | Beschneidung | wie Lightroom („Lichter-/Tiefenbeschneidung“); „Beschnitt“ ist ein Druckbegriff |
| Shutter Speed | Verschlusszeit | |
| Upright | Perspektivkorrektur | |
| Smart Album | Smart-Album | |
| Activity stack | Aktivitätsstapel | |
Typografie: Gedankenstrich mit Leerzeichen als Halbgeviertstrich (` – `), Auslassungspunkte
ohne Leerzeichen direkt am Wort (`Importieren…`), deutsche Anführungszeichen („…“). „Ihre“
nur, wo es eindeutig „von ihnen“ heißt; die Anrede ist *du*, ein Satz mit „Ihre Fotos“ am
Anfang liest sich sonst wie die Sie-Form.
## Wartung und Prüfung
Feste Texte: `crates/ui-egui/locales/de.json`. Meldungen mit Werten:
@@ -29,8 +55,8 @@ cargo test -p lightcraft-ui-egui i18n::tests
LIGHTCRAFT_LANGUAGE=de lightcraft-cli snapshot --demo -o deutsch.png --size 1600x1000
```
Die Tests prüfen Katalogabdeckung, Platzhalter, Sprachwechsel per Menü und Steuerkanal,
Speicherung, Datumsformate, unveränderte Benutzernamen und gerenderte Texte sowie Umlaute
und ß. Ein weiterer Render-Test prüft die Schaltflächenbreiten; der Importtest prüft
Die Tests prüfen Platzhalter, Sprachwechsel per Menü und Steuerkanal, Speicherung,
Datumsformate, unveränderte Benutzernamen und gerenderte Texte sowie Umlaute und ß. Fehlende
Übersetzungen erscheinen auf Englisch; `i18n::tests::translation_gaps_report` listet sie auf. Ein weiterer Render-Test prüft die Schaltflächenbreiten; der Importtest prüft
auch den Sprachwechsel nach der Hintergrundprüfung der Quellen. Akzeptierte Sprachangaben umfassen `de`, `de-DE`, `de_AT.UTF-8` und `de-CH`;
gespeichert wird stets `de`. Weitere Hinweise stehen in [localization.md](localization.md).
+3 -2
View File
@@ -52,8 +52,9 @@ englanniksi. Käännös ei muuta kirjastotietoja eikä englanninkielisiä lähde
## Testaus
`i18n::tests` tarkistaa komennon rekisteröinnin, vaihdon valikosta ja ohjauskanavasta,
kielen tallennuksen, luetteloiden kattavuuden, paikkamerkit, määrämuodot, päivämäärän, ajan ja
suomen kirjaimet. Rust tarkistaa muotoilumallit käännösvaiheessa.
kielen tallennuksen, paikkamerkit, päivämäärän, ajan ja suomen kirjaimet. Rust tarkistaa
muotoilumallit käännösvaiheessa. Puuttuvat käännökset näkyvät englanniksi, ja
`i18n::tests::translation_gaps_report` luettelee ne; mikään testi ei epäonnistu niiden takia.
Kielentarkistuksessa kannattaa katsoa erityisesti: ”Väriluokittelu” (Color Grading),
”Valoalueet / Valkoiset” (Highlights / Whites), ”Näyttövedos” (Soft Proofing),
+4 -4
View File
@@ -19,7 +19,7 @@ not translated. Descriptions for SAM 3 go to the model unchanged.
## Georgian script
Mkhedruli has no upper and lower case: `i18n::upper_first` leaves Georgian letters alone, and the
interface never uses Mtavruli (a test checks the whole catalog). Commands are verbal nouns. Counts
interface never uses Mtavruli. Commands are verbal nouns. Counts
are count-neutral, noun first (`იმპორტირებულია ფოტო: 3`); English plural endings are dropped with
`{:.0}`. Dates read `კვირა, 20.09.2026` (`{weekday}, {day:02}.{month:02}.{year}`), months `09.2026`.
@@ -73,7 +73,7 @@ cargo test -p lightcraft-ui-egui i18n::tests -- --nocapture
LIGHTCRAFT_LANGUAGE=ka lightcraft-cli snapshot --demo -o georgian.png --size 1600x1000
```
The tests check catalog coverage, placeholders, switching language by menu and control channel,
saving, the `ka` locale tags, dates, count wording, the absence of Mtavruli and that sentence case
keeps Georgian lowercase. More notes are in
The tests check placeholders, switching language by menu and control channel, saving, the `ka`
locale tags, dates and that sentence case keeps Georgian lowercase. Messages Georgian lacks show in
English; `i18n::tests::translation_gaps_report` lists them. More notes are in
[localization.md](localization.md).
+11 -8
View File
@@ -1,12 +1,12 @@
# Localization parity
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-20 · **Change:** minor (German form of address: *du*; Indonesian and Finnish interface catalogs added; Italian: every interface string translated, native-speaker corrections; Vietnamese shipped; previously minor: de, es, fr, ja, uk catalogs filled; re-measured; Italian and Georgian interface catalogs added) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-24 · **Change:** minor (German: interface strings and formats messages reviewed, terms fixed in localization-de.md; German form of address: *du*; Indonesian and Finnish interface catalogs added; Italian: every interface string translated, native-speaker corrections; Vietnamese shipped; previously minor: de, es, fr, ja, uk catalogs filled; re-measured; Italian and Georgian interface catalogs added) · **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`
([zh-hans](localization-zh-hans.md), [zh-hant](localization-zh-hant.md), [ja](localization-ja.md),
[pt-br](localization-pt-br.md), [es](localization-es.md), [de](localization-de.md),
[ru](localization-ru.md), [fi](localization-fi.md), [ka](localization-ka.md), [id](localization-id.md), [vi](localization-vi.md)).
[ru](localization-ru.md), [fi](localization-fi.md), [ka](localization-ka.md), [id](localization-id.md), [vi](localization-vi.md), [pl](localization-pl.md)).
## The target
@@ -37,7 +37,7 @@ scripts `Latn`, `Cyrl`, `Hans`, `Hant`, `Jpan` only. The UI is egui, which has n
and no complex-script shaping, so Arabic (RTL, joining) and Devanagari (conjuncts, reordering) cannot
render correctly today.
Native-speaker review: no catalog has had a formal review. Several catalogs have edits from community
Native-speaker review: no catalog has had a full formal review; German's interface strings and formats messages have (2026-10-24, What's New not). Several catalogs have edits from community
contributors whose commits only touch that language (ja, zh-hans, zh-hant, fr, de, ru, uk, pt-br);
that is promising but is not recorded as review, so the column says `no (community edits)`.
@@ -58,11 +58,11 @@ string follows that.
| 7 | Portuguese (Brazil) | `pt-br` | 1,966 / 2,304 (85%) | as zh-hans | Latin | no (community edits) | partial | 6–10 h |
| 8 | Indonesian | `id` | 2,555 / 2,555 (100%; 2026-10-19: every plain and formats message any catalog has, plus 69 command labels; What's New left in English) | as zh-hans | Latin (Inter covers it) | no (written with an agent; native-speaker pass pending) | partial (target: not shipped) | 2–4 h (native review, lower-layer errors) |
| 9 | Japanese | `ja` | 2,383 / 2,398 (99%; 2026-10-12) | as zh-hans; vertical export watermarks | Jpan via craft-fonts (BIZ UDPGothic / UDMincho); IME through winit | no (community edits) | partial | 2–4 h (native review, lower-layer errors) |
| 10 | German | `de` | 3,644 / 3,754 (97%; 2026-10-16: every interface message and every What's New line except 59: the distinct What's New lines whose English text has a ▸ menu path have no German entry yet and wait for #2016; the rest are words spelled the same) | as zh-hans; What's New translated line by line | Latin | no (community edits) | partial | 2–4 h (native review, lower-layer errors) |
| 10 | German | `de` | 3,644 / 3,754 (97%; 2026-10-16: every interface message and every What's New line except 59: the distinct What's New lines whose English text has a ▸ menu path have no German entry yet and wait for #2016; the rest are words spelled the same) | as zh-hans; What's New translated line by line | Latin | partly (2026-10-24: every interface string and formats message, reviewed with an agent and spot-checked by a native speaker; What's New lines not reviewed) | partial | 1–2 h (What's New review, lower-layer errors) |
| 11 | Korean | `ko` | 0 (0%) | none | **no Korean (Hangul) face**: needs a non-Adobe OFL face in craft-fonts (Noto CJK is allowed only for Chinese) | no | none | 14–22 h |
| 12 | Vietnamese | `vi` | 2,471 / 2,471 (100% key coverage of current union; intentional English like `ISO`/`OK` kept) | menus, panels, dialogs, presets, profiles, dates, formats; lower-layer errors English | Latin with stacked diacritics (Inter) | no (community contribution) | partial (catalog complete; lower-layer errors English) | 1–3 h |
## Other languages LightCraft ships (4)
## Other languages LightCraft ships (7)
| Language | Code | UI strings translated | Script | Native review | Status | To `full` |
|---|---|---:|---|---|---|---:|
@@ -72,8 +72,9 @@ string follows that.
| Italian | `it` | 2,628 / 3,278 (80%, measured 2026-10-15; every interface string is translated, the other 650 are What's New lines, left in English) | Latin (Inter) | partly (written with an agent; corrections and the 2026-10-15 additions by a native speaker) | partial (lower-layer errors English) | 3–5 h |
| Georgian | `ka` | 2,398 / 2,398 (100%: every plain and formats message any catalog has on 2026-10-12, plus the 27 display labels and 397 What's New lines no other catalog translates) | Geor: no craft-fonts face yet (boxes until one is available) | yes (native speaker) | partial (lower-layer errors English) | 2–4 h (+ a Georgian face) |
| Finnish | `fi` | 2,610 / 3,308 (79%, measured 2026-10-17: 2,313 plain + 297 formats; the rest is What's New) | Latin (Inter) | yes (written with an agent; native-speaker review by @roopeg) | partial (lower-layer errors English) | 3–5 h |
| Polish | `pl` | 2,159 messages + 291 formats; every format message translated (2026-10-15) | Latin (Inter) | no (AI-drafted; follow-up by a native speaker) | partial (lower-layer errors English) | 2–4 h (full native review) |
Target languages LightCraft lacks: Korean, Dutch, Polish, Swedish, Norwegian Bokmål, Thai
Target languages LightCraft lacks: Korean, Dutch, Norwegian Bokmål, Thai
(Thai needs shaping and line breaking without spaces). Each Latin-script one is ~10–16 h; Thai
~20–35 h on top of the shaping work.
@@ -90,9 +91,9 @@ Target languages LightCraft lacks: Korean, Dutch, Polish, Swedish, Norwegian Bok
## Totals
- Bring the 10 shipped non-English catalogs to `full`: ~55–90 h (incl. the shared work).
- Bring the shipped non-English catalogs to `full` (17 on 2026-10-23; the estimate was made for 10): ~55–90 h (incl. the shared work).
- Add the 5 missing key languages: ~145–255 h, most of it RTL and complex shaping in egui.
- Reach the target's 16 languages: + ~50–85 h (Dutch, Polish, Swedish, Norwegian, Thai).
- Reach the target's 16 languages: + ~30–55 h (Dutch, Norwegian, Thai; Polish and Swedish ship).
## Revision history
@@ -100,6 +101,7 @@ Target languages LightCraft lacks: Korean, Dutch, Polish, Swedish, Norwegian Bok
|---|---|---|
| 2026-10-19 | minor | Indonesian interface catalog (`id`): every plain and formats message any catalog has, plus 69 command labels; What's New left in English |
| 2026-10-17 | minor | Finnish interface catalog (`fi`): 2,610 of 3,308 messages (2,313 plain + 297 formats), What's New left in English |
| 2026-10-15 | minor | Polish row added: the format messages still in English translated, counts made right for every number, dates day.month.year ([localization-pl.md](localization-pl.md)) |
| 2026-10-15 | trivial | Italian (`it`): 339 interface messages and 60 format strings that were missing or still in English translated (2,628 of 3,278; only What's New left in English); native-speaker corrections: Exposure → *Esposizione*, Sharpening → *Nitidezza*, Preset → *Preset*, blurry → *sfocate* |
| 2026-10-13 | minor | Remeasured denominator (2,222 plain + 249 formats = 2,471); Vietnamese catalog complete at 2,471/2,471 (status partial: lower-layer errors still English) |
| 2026-10-12 | minor | Italian interface catalog (`it`): 2,201 of 2,398 messages, What's New left in English |
@@ -108,4 +110,5 @@ Target languages LightCraft lacks: Korean, Dutch, Polish, Swedish, Norwegian Bok
| 2026-10-10 | major | Created: per-language counts measured from the catalogs, the twelve key languages, script support, estimates |
| 2026-10-12 | minor | de, es, fr, ja and uk catalogs filled to every message another catalog knows (agent translations, native review welcome); those rows re-measured against the current 2,398 messages. Other rows keep their 2026-10-10 counts (open PRs for pt-BR, ru, zh) |
| 2026-10-16 | minor | German (`de`): 217 interface strings and 862 What's New lines added (agent translations, native review welcome); re-measured 2,572 → 3,644 of 3,754 messages (80% → 97%). The denominator grew because German now translates What's New lines no other catalog has; other rows keep their earlier counts |
| 2026-10-24 | minor | German (`de`): interface strings and formats messages reviewed (agent-prepared, spot-checked by a native speaker); 108 messages corrected (Tönung, Beschneidung, Farbmarkierung vs. Markierung, Bedienfeld, Begleitdatei, Dia, en dash and ellipsis spacing, ambiguous *Ihre*); terms listed in localization-de.md; native review column `partly`, estimate 1–2 h |
| 2026-10-20 | minor | German (`de`) speaks to the user with *du*; the 11 messages that used *Sie* changed, no count changes |
+56
View File
@@ -0,0 +1,56 @@
# Polski
Język polski (`pl`, English name **Polish**, nazwa natywna **Polski**) włącza się przez **Edytuj → Język →
Polski** albo **Ustawienia → Ogólne → Język**. Zmiana działa od razu i jest zapamiętywana przy kolejnym
uruchomieniu. Polecenie to `app.language.polish`; kanał sterowania przyjmuje `ui.set {"language":"pl"}`
oraz znaczniki `pl-PL` i `pl_PL.UTF-8`. Bez okna można ustawić `LIGHTCRAFT_LANGUAGE=pl`.
Katalogi `crates/ui-egui/locales/pl.json` i `pl-formats.json` dodał #1100 (tłumaczenie przygotowane
z pomocą AI). Uzupełnienie opisane niżej również przygotowano z pomocą AI; przygotował je rodzimy
użytkownik języka polskiego (@lewandowskit). Profesjonalny tłumacz nie robił jeszcze korekty.
## Konwencje
- Menu: Plik, Edytuj, Widok, Zdjęcie, Okno, Pomoc.
- Library = Biblioteka, Develop = Wywoływanie, Preset = Ustawienie wstępne, Stack = Stos,
Masking = Maskowanie, Virtual Copy = Kopia wirtualna, Filmstrip = Pasek zdjęć.
- Clarity = Przejrzystość, Dehaze = Usuń zamglenie, Vibrance = Dynamika, Saturation = Nasycenie.
- Highlights = Światła, Shadows = Cienie, Whites = Biele, Blacks = Czernie.
- Cudzysłowy polskie: „…”. Ścieżki menu w tekstach: `Edytuj → Cofnij`.
- Data ma postać `20.09.2026` (dzień.miesiąc.rok), nagłówek dnia `Niedziela, 20.09.2026`,
miesiąca `09.2026`; czas 24-godzinny (`16:04:05`).
- **Liczby i rzeczowniki.** Polski ma trzy formy liczby mnogiej (1 zdjęcie, 2 zdjęcia, 5 zdjęć),
a mechanizm katalogów rozróżnia tylko „jeden” i „wiele”. Dlatego liczba stoi po rzeczowniku:
„Zaimportowane zdjęcia: 22”, „Usuń zdjęcia (3)”, „Oczekujące zdjęcia: 2”. Taka forma jest
poprawna dla każdej liczby. Angielska końcówka liczby mnogiej jest pochłaniana przez `{:.0}`
(jak w [localization.md](localization.md)). Po przyimku z dopełniaczem („z 10 zdjęć”, „dla 3
zaznaczonych zdjęć”) liczba może stać przed rzeczownikiem, bo forma jest wtedy zawsze ta sama.
- Komunikaty `{n} {noun}` (np. w panelu Odszumianie AI) mają postać `{noun}: {n}`, a obie formy
rzeczownika (`photo waiting` / `photos waiting`) tłumaczą się tak samo.
- Font Inter dołączony do aplikacji zawiera wszystkie polskie litery i cudzysłowy; inny font
nie jest potrzebny.
## Uzupełnienie (2026-10-15)
- Przetłumaczono 40 komunikatów `pl-formats.json`, które były jeszcze po angielsku (ustawienia
efektów masek, szablony pokazu slajdów, import katalogu Lightroom, słowa kluczowe, osoby,
scalanie panoram, komunikat o nieoczekiwanym zakończeniu programu) i 2, w których zmieniono
tylko strzałkę.
- Poprawiono 25 komunikatów, w których liczba stała przed rzeczownikiem w dopełniaczu i dla
2–4, 22–24… dawała błędną formę („Usuń 2 zdjęć”), oraz `{} failed`, gdzie `{}` jest nazwą
zadania, a nie liczbą.
- Daty: `{month} {year}` i `{weekday}, {day} {month} {year}` mają postać dzień.miesiąc.rok
zamiast „20 9 2026”.
Katalog ma 2 159 zwykłych komunikatów i 291 szablonów formatów. Komunikaty, których nie ma w
katalogu, oraz techniczne błędy niższych warstw wyświetlają się po angielsku.
## Weryfikacja
Test `i18n::tests::polish_switches_formats_dates_and_reads_every_count` sprawdza przełączanie
z menu i kanału sterowania, zapamiętanie języka, znaczniki `pl-*`, daty, czas, liczby 0–111
(w tym 2–4 i 22–25) w liczonych komunikatach, nazwy użytkownika pozostawione bez zmian oraz
polskie znaki w fontach interfejsu. Zgodność placeholderów sprawdza
`catalogs_agree_on_placeholders_and_report_gaps`.
Postęp dodawania języków opisuje [localization.md](localization.md).
+3 -2
View File
@@ -66,8 +66,9 @@ LIGHTCRAFT_LANGUAGE=vi lightcraft-cli snapshot --demo -o vi.png --size 1600x1000
LIGHTCRAFT_LANGUAGE=vi lightcraft-cli snapshot --demo -o vi-narrow.png --size 1280x800
```
Các test tiếng Việt bắt buộc phủ catalog union, nhãn lệnh/control/Settings/Smart Album,
đổi ngôn ngữ, lưu `vi`, formats, ngày, dữ liệu người dùng, và glyph dấu.
Các test tiếng Việt kiểm tra đổi ngôn ngữ, lưu `vi`, formats, ngày, dữ liệu người dùng và glyph
dấu. Chuỗi còn thiếu hiện bằng tiếng Anh và được `i18n::tests::translation_gaps_report` liệt kê;
không test nào thất bại vì thiếu bản dịch.
## Nguồn dịch (clean-room)
+2 -1
View File
@@ -34,5 +34,6 @@ LightCraft 的界面支持英语、简体中文、繁体中文(台湾)和日
`LIGHTCRAFT_LANGUAGE=zh-hans lightcraft-cli snapshot ...` 可以渲染中文界面用于检查。
翻译是否完整、两种目录的键与占位符是否一致、字形是否齐全,都由
两种目录的占位符是否一致、字形是否齐全,由
`cargo test -p lightcraft-ui-egui i18n::tests` 验证(字形检查只在指定 `CRAFT_FONTS_DIR` 时实际执行)。
缺少的译文显示为英文,由 `i18n::tests::translation_gaps_report` 列出,不会让测试失败。
+18 -14
View File
@@ -2,21 +2,20 @@
The interface ships in the language it is written in (English) plus every language in the table in
`crates/ui-egui/src/i18n.rs`. Today that is English, Simplified Chinese (`zh-hans`), Traditional
Chinese (`zh-hant`, Taiwan), Japanese (`ja`), Brazilian Portuguese (`pt-br`), Spanish (`es`), German (`de`), Russian (`ru`), French (`fr`), Ukrainian (`uk`), Czech (`cs`), Georgian (`ka`), Swedish (`sv`), Finnish (`fi`), Indonesian (`id`) and Vietnamese (`vi`). This file is the reference for **adding or maintaining a language**; the per-language notes
Chinese (`zh-hant`, Taiwan), Japanese (`ja`), Brazilian Portuguese (`pt-br`), Spanish (`es`), German (`de`), Russian (`ru`), French (`fr`), Ukrainian (`uk`), Czech (`cs`), Italian (`it`), Georgian (`ka`), Swedish (`sv`), Finnish (`fi`), Indonesian (`id`), Vietnamese (`vi`) and Polish (`pl`). This file is the reference for **adding or maintaining a language**; the per-language notes
are in [`localization-zh-hans.md`](localization-zh-hans.md), [`localization-zh-hant.md`](localization-zh-hant.md),
[`localization-ja.md`](localization-ja.md), [`localization-pt-br.md`](localization-pt-br.md), [`localization-es.md`](localization-es.md),
[`localization-de.md`](localization-de.md), [`localization-ru.md`](localization-ru.md),
[`localization-cs.md`](localization-cs.md), [`localization-fi.md`](localization-fi.md), [`localization-ka.md`](localization-ka.md), [`localization-id.md`](localization-id.md) and [`localization-vi.md`](localization-vi.md).
[`localization-cs.md`](localization-cs.md), [`localization-fi.md`](localization-fi.md), [`localization-ka.md`](localization-ka.md), [`localization-id.md`](localization-id.md), [`localization-vi.md`](localization-vi.md) and [`localization-pl.md`](localization-pl.md).
## Adding a language
1. **Translate the two catalogs.** Copy `locales/ja.json` and `locales/ja-formats.json` to
`locales/<code>.json` and `locales/<code>-formats.json`, then translate the values. Keys are the
English source strings and must stay byte-for-byte identical. The plain catalog may grow at its
own pace: a message it lacks shows in English, and `cargo test -p lightcraft-ui-egui i18n::tests
-- --nocapture` lists the gaps (it fails on an empty entry or mismatched placeholders). The
formats catalog must carry every message (the build fails otherwise; a message copied in English,
value equal to its key, is an untranslated placeholder until someone translates it), including the date
English source strings and must stay byte-for-byte identical. Both catalogs may grow at their
own pace: a message a catalog lacks, or translates as an empty string, shows in English, and
nothing fails. The translation gaps report lists what each language lacks (see
[Testing and looking at it](#testing-and-looking-at-it)). The formats catalog also holds the date
patterns (`{year}`, `{month} {year}`, `{weekday}, {day} {month} {year}`…) that date headings and
capture times use in every language but English; weekday names go in the plain catalog.
2. **Add the table entry** in `crates/ui-egui/src/i18n.rs`:
@@ -81,18 +80,23 @@ tr_format!("Added {n} photo{} to “{}”", if n == 1 { "" } else { "s" }, album
`"已将 {n} 张照片添加到“{1}”{0:.0}"`. Keep the source's spec (`{:.1}`, `{d:+}`, `{:.0}%`).
- `format!` rejects an unused argument, so a value a language does not need — the English plural
suffix (`"s"` / `""`) — is consumed as `{:.0}` (a string at precision 0 prints nothing).
- Every formats catalog carries the same messages (`build.rs` fails the build otherwise), and a
`tr_format!` call whose message is in no catalog does not compile: add it to every
`*-formats.json` file.
- A language whose formats catalog lacks a message shows the English source there, and a
`tr_format!` call whose message is in no catalog formats the English source (the macro's last
arm). A new message builds with no catalog entry.
## Testing and looking at it
```sh
cargo test -p lightcraft-ui-egui i18n::tests # catalogs, keys, placeholders, commands
cargo test -p lightcraft-ui-egui i18n::tests # catalogs load, placeholders, switching
CRAFT_FONTS_DIR=../craft-fonts cargo test -p lightcraft-ui-egui i18n::tests # + glyph coverage
cargo test -p lightcraft-ui-egui --lib -- --exact i18n::tests::translation_gaps_report --nocapture
```
The glyph tests are skipped without `CRAFT_FONTS_DIR` (there are no CJK faces to check then).
The tests fail on a broken catalog (malformed JSON, a repeated key, placeholders that differ from
the source), never on a missing translation. The last command is the translation gaps report: per
language, the count and list of plain and format messages it lacks and the number of untranslated
What's New lines. It always passes, and `cargo xtask ci` prints it after its test step. The glyph
tests are skipped without `CRAFT_FONTS_DIR` (there are no CJK faces to check then).
To look at a language, render it headless:
```sh
@@ -114,5 +118,5 @@ when a region has no dedicated entry.
Help ▸ What's New shows `docs/whats-new.md`, and each of its lines (a heading, or one wrapped line of a
bullet, without the `- ` or `### `) is looked up like any message, so a catalog can translate the notes
line by line (German does). A line that is re-wrapped or edited no longer matches its translation and
shows in English until the catalog is updated; untranslated lines are reported by
`display_label_gaps_are_reported`, never failed.
shows in English until the catalog is updated; the translation gaps report counts the untranslated
lines.
+3 -3
View File
@@ -170,7 +170,7 @@ per-request `_meta` receive `resultType: complete` and list/read cache hints; re
|---|---|
| `list_commands {filter?}` | Every command: id, label, menu, shortcut, parameter doc, enabled now |
| `run_command {command, params?}` | Run any command by id |
| `import {paths, album?, mode?, destination?, organize?, rename?}` | Import files/folders (folders are scanned recursively); the first new photo becomes active. `mode: "copy"` copies into `destination` (default: the library's Originals/), `mode: "move"` moves there (each original and its XMP sidecars are removed from the source only after the copy is verified and catalogued; duplicates and failures keep their sources; the result's `moved` / `kept` list what moved and what stayed, and why; undo leaves the moved files at the destination), filed by `organize`: `date` (YYYY/YYYY-MM-DD), `month`, `flat` or a folder template such as `{date:%Y}/{date:%Y%m%d}` (→ `2026/20260114`; capture date, else the import date; always inside the destination), and named by the `rename` template (`run_command photo.renameTokens` lists the tags) |
| `import {paths, album?, mode?, destination?, organize?, rename?}` | Import files/folders (folders are scanned recursively); the first new photo becomes active. `mode: "copy"` copies into `destination` (default: the library's Originals/), `mode: "move"` moves there (each original and its XMP sidecars are removed from the source only after the copy is verified and catalogued; duplicates and failures keep their sources; the result's `moved` / `kept` list what moved and what stayed, and why; undo leaves the moved files at the destination), filed by `organize`: `date` (YYYY/YYYY-MM-DD), `month`, `flat` or a folder template such as `{date:%Y}/{date:%Y%m%d}` (→ `2026/20260114`) or `{date:%Y}/{date:%B}` (→ `2026/January`; capture date, else the import date; always inside the destination), and named by the `rename` template (`run_command photo.renameTokens` lists the tags) |
| `query_photos {filter?, sort?, offset?, limit?}` | Photos in the current view (or matching a catalog `Filter`) |
| `select_photos {ids, active?, mode?}` | Set the selection / active photo. Every id (and `active`) must be in the library: an unknown id is a tool error (`no such photo 9999`) and the selection and active photo are left as they were |
| `list_controls {section?}` | Every develop slider: id (`light.exposure`…), range, default, current value |
@@ -316,10 +316,10 @@ lightcraft-cli calibrate --max 300 ~/Pictures/2026 # camera colour profiles (d
## Tests
- `crates/mcp/tests/e2e.rs` — M0.9 acceptance: over the stdio framing, set exposure and render;
- `crates/mcp/tests/it/e2e.rs` — M0.9 acceptance: over the stdio framing, set exposure and render;
checks the decoded PNG gets brighter/darker. Runs headless and through the TCP transport
(`Remote`) against a stand-in control server; also import → render → JPEG export of a real file.
- `apps/lightcraft-cli/tests/cli.rs` — spawns `lightcraft-cli mcp` with real pipes; `render`;
- `apps/lightcraft-cli/tests/it/cli.rs` — spawns `lightcraft-cli mcp` with real pipes; `render`;
`commands`.
## Add to Apple Photos (macOS)
+2 -1
View File
@@ -8,7 +8,8 @@ All algorithms are our own implementations from published papers (cited in the m
- **App:** select the photos, then *Photo Merge ▸ HDR…* (`Ctrl+H`), *Panorama…* (`Ctrl+M`) or
*HDR Panorama…* from the photo context menu (or the `dialog.merge*` commands). The dialog previews
the merge at ≤ 1024 px and re-runs the preview when an option changes; *Merge* runs the full merge
the merge at ≤ 1024 px and re-runs the preview when an option changes; as in Lightroom, a click on the preview
zooms in on that point (drag to pan, click again to fit), and it stays there while options change; *Merge* runs the full merge
in the background (a row in the activity stack shows its progress, with ✕ to stop it). The result is written next to the first photo as
`<name>-HDR.dng`, `-Pano.dng` or `-HDR-Pano.dng`, imported and selected. *Photo Merge ▸ HDR for Each Bracket Set*
merges many brackets in one go with the last HDR options: one HDR per stack among the selected photos, then per
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -46,11 +46,11 @@ unsupported to preview-only in the counts; sensor-decoding readiness is unchange
version. The year a model was added is approximated from that Camera Raw major version.
- **What we decode**: the decoders and their refusals in `crates/raw/src` (`lib.rs` routing, `vendor/*.rs`
coding variants, `crx.rs` CRX limits, `orf.rs` / `srw.rs` compressed refusals), the import list
`lightcraft_engine::import::EXTENSIONS`, and `KNOWN_UNSUPPORTED` in `crates/raw/tests/corpus.rs`. Doc
`lightcraft_engine::import::EXTENSIONS`, and `KNOWN_UNSUPPORTED` in `crates/raw/tests/it/corpus.rs`. Doc
claims were not used.
- **Verified** means a real file from that body decoded from sensor data in: the corpus test over the
`RAW_SAMPLES` manifest (`xtask/src/main.rs`, 105 CC0 files; bodies whose sample is in
`KNOWN_UNSUPPORTED` excluded), `crates/raw/tests/cr3_corpus.rs` (3 bodies exact against an external decoder;
`KNOWN_UNSUPPORTED` excluded), `crates/raw/tests/it/cr3_corpus.rs` (3 bodies exact against an external decoder;
the EOS R5 Mark II pinned, with a QP-map check of 33,665 private files; 22 more bodies pinned on one CC0
file each and checked against the camera JPEG, [cr3.md](cr3.md#further-bodies-one-cc0-file-each)), the RAF reference
check ([raf-compression.md](raf-compression.md)), and the raw.pixls.us bulk checks recorded in issue
@@ -64,7 +64,7 @@ unsupported to preview-only in the counts; sensor-decoding readiness is unchange
decoded on main `25ef65c8`; [raw/coverage-sweep.tsv](raw/coverage-sweep.tsv) records the URLs,
SHA-256, dimensions, FNV-1a sensor hashes and render/reference luminance correlations (0.6801
to 0.9998). The hashes pin LightCraft's own output. The regression test is
`crates/raw/tests/coverage_sweep_corpus.rs`; downloads are in `RAW_SAMPLES`. Ten references are
`crates/raw/tests/it/coverage_sweep_corpus.rs`; downloads are in `RAW_SAMPLES`. Ten references are
embedded TIFF images; the rest are JPEGs. FFF TIFF previews are not display-ready and check
image structure only. Eleven candidate models were excluded for identity, provenance or
missing/mismatched reference images. D6 / Z 6 / Z 7 verification covers compressed NEF only;
+3 -3
View File
@@ -144,15 +144,15 @@ use the largest JPEG, so their geometry and colour do not depend on the size lev
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
- `crates/raw/tests/it/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.
- `crates/engine/tests/preview_read.rs` (opt-in, `#[ignore]`): the same measurement on a **real**
- `crates/engine/tests/it/preview_read.rs` (opt-in, `#[ignore]`): the same measurement on a **real**
file, typically on a share:
```sh
LC_PREVIEW_RAW="/mnt/nas/…/DSCF0001.RAF" \
cargo test --release -p lightcraft-engine --test preview_read -- --ignored --nocapture
cargo test --release -p lightcraft-engine --test it preview_read:: -- --ignored --nocapture
```
No raw fixture is committed (media never is, see `AGENTS.md`).
+2 -2
View File
@@ -29,7 +29,7 @@ HERE = os.path.dirname(os.path.abspath(__file__))
# Olympus ORF bodies that write uncompressed / packed data (crates/raw/src/vendor/orf.rs). Everything else
# writes compressed ORF, which is preview-only (the E-M5 II / PEN-F high-res mode alone is decoded).
ORF_DECODED = re.compile(r"^(E-1|E-10|E-20|EVOLT E-300|EVOLT E-330|EVOLT E-400|EVOLT E-500|E-300|E-330|E-500|Camedia C-\S+.*|Stylus XZ-2 iHS)$")
ORF_DECODED = re.compile(r"^(E-1|E-10|E-20|EVOLT E-300|EVOLT E-330|EVOLT E-400|EVOLT E-500|E-300|E-330|E-500|Camedia C-\S.*|Stylus XZ-2 iHS)$")
# Samsung SRW bodies with compressed raw data (crates/raw/src/vendor/srw.rs refuses 32770 / 32772 / 32773).
SRW_COMPRESSED = re.compile(r"(^|\W)(NX1|NX30|NX300|NX300M|NX500|NX2000|NX3000|NX3300|NX mini|EK-GN\d+|EK-KN\d+)(\W|$)")
# Fujifilm bodies before the X series whose RAF has no raw IFD (crates/raw/src/vendor/raf.rs).
@@ -123,7 +123,7 @@ def main():
classes = ["verified", "unverified", "preview", "unsupported"]
by_maker = {}
for r in rows:
by_maker.setdefault(r["maker"], {c: 0 for c in classes})[r["class"]] += 1
by_maker.setdefault(r["maker"], dict.fromkeys(classes, 0))[r["class"]] += 1
print("| Maker | Adobe models | verified | unverified | preview | unsupported |")
print("|---|---:|---:|---:|---:|---:|")
for m, c in sorted(by_maker.items(), key=lambda kv: -sum(kv[1].values())):
+354 -354
View File
File diff suppressed because it is too large Load Diff
+354 -354
View File
@@ -1,119 +1,119 @@
# Per-model evidence that overrides the format rules in coverage.py. status: verified | preview | unsupported.
# "verified" means a real file from this body decoded from sensor data in a corpus test or a recorded bulk check (source column).
maker camera status source
Apple iPhone 12 Pro Rear Wide Camera verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Canon EOS 40D verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Canon EOS 550D (EOS Digital Rebel T2i, EOS Kiss X4 Digital) verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Canon EOS 5D Mark II verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Canon EOS 5DS R verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Canon EOS 6D verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Canon EOS 7D verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Canon EOS 80D verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Canon EOS M50 (EOS Kiss M) verified corpus: crates/raw/tests/cr3_corpus.rs (exact sensor hashes, docs/cr3.md)
Canon EOS R100 verified corpus: crates/raw/tests/cr3_corpus.rs (exact sensor hashes, docs/cr3.md)
Canon EOS R5 Mark II verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hashes, docs/cr3.md); bulk: QP maps of 33,665 C-RAW files
Canon EOS R8 verified corpus: crates/raw/tests/cr3_corpus.rs (exact sensor hashes, docs/cr3.md)
Canon EOS-1D X Mark III verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS 90D verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS 250D (EOS Rebel SL3, EOS 200D II, EOS Kiss X10) verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS 850D (EOS Kiss X10i, EOS Rebel T8i) verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS M200 verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS M50 Mark II verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS M6 Mark II verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS R (** see note) verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS R3 verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS R5 verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS R6 verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS R6 Mark II verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md; C-RAW with adaptive QP above 168 still opens as the preview)
Canon EOS R6 Mark III verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS R7 verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md; lossless only, C-RAW unverified)
Canon EOS R10 verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS RP verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS R50 verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS R50 V verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon PowerShot G5 X Mark II verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon PowerShot G7 X Mark III verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon PowerShot SX70 HS verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon PowerShot V1 verified corpus: crates/raw/tests/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS-5D Mark III verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Canon EOS 70D verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 800D (EOS Digital Rebel T7i, EOS Kiss X9i) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G9X verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 3000D (EOS Rebel T100, EOS 4000D) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 1000D (EOS Digital Rebel XS, EOS Kiss F) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G1 X Mark II verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 500D (EOS Digital Rebel T1i, EOS Kiss X3 Digital) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 400D (EOS Digital Rebel XTi, EOS Kiss Digital X) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot S110 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 1300D (EOS Digital Rebel T6, EOS Kiss X80) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 600D (EOS Digital Rebel T3i, EOS Kiss X5 Digital) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G1 X verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G16 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G3 X verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS M verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS-1D X Mark II verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 750D (EOS Digital Rebel T6i, EOS Kiss X8i) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 5DS verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot S100V verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 350D (EOS Digital Rebel XT, EOS Kiss Digital N) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 1200D (EOS Digital Rebel T5, EOS Kiss X70) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G11 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 100D (EOS Digital Rebel SL1, EOS Kiss X7) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G15 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS-1D Mark IV verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS M5 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot S100 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 650D (EOS Digital Rebel T4i, EOS Kiss X4i) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot SX50 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS M2 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS M3 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G12 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 6D Mark II verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS M10 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 700D (EOS Digital Rebel T5i, EOS Kiss X7i) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 1100D (EOS Digital Rebel T3, EOS Kiss X50 Digital) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 77D (EOS 9000D) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 1500D (EOS Rebel T7, EOS Kiss X90, EOS 2000D) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS-5D Mark IV (** see note) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS-1D Mark III verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 60D verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS-1D Mark II N verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS M100 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G5 X verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G9 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 5D verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS M6 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G7 X Mark II verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 7D Mark II verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot S120 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot S95 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G1 X Mark III verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 50D verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 20D verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G7 X verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 450D (EOS Digital Rebel XSi, EOS Kiss Digital X2) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot SX60 HS verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 200D (EOS Kiss X9, EOS Rebel SL2) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G9 X Mark II verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 760D (EOS Digital Rebel T6s, EOS Kiss 8000D) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 30D verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm GFX 100 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Fujifilm GFX 100RF (Final Support) verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Fujifilm GFX 100S verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Fujifilm GFX 50S verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-A2 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-A5 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-E1 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-E5 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-H2 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Apple iPhone 12 Pro Rear Wide Camera verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Canon EOS 40D verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Canon EOS 550D (EOS Digital Rebel T2i, EOS Kiss X4 Digital) verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Canon EOS 5D Mark II verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Canon EOS 5DS R verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Canon EOS 6D verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Canon EOS 7D verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Canon EOS 80D verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Canon EOS M50 (EOS Kiss M) verified corpus: crates/raw/tests/it/cr3_corpus.rs (exact sensor hashes, docs/cr3.md)
Canon EOS R100 verified corpus: crates/raw/tests/it/cr3_corpus.rs (exact sensor hashes, docs/cr3.md)
Canon EOS R5 Mark II verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hashes, docs/cr3.md); bulk: QP maps of 33,665 C-RAW files
Canon EOS R8 verified corpus: crates/raw/tests/it/cr3_corpus.rs (exact sensor hashes, docs/cr3.md)
Canon EOS-1D X Mark III verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS 90D verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS 250D (EOS Rebel SL3, EOS 200D II, EOS Kiss X10) verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS 850D (EOS Kiss X10i, EOS Rebel T8i) verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS M200 verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS M50 Mark II verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS M6 Mark II verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS R (** see note) verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS R3 verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS R5 verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS R6 verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS R6 Mark II verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md; C-RAW with adaptive QP above 168 still opens as the preview)
Canon EOS R6 Mark III verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS R7 verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md; lossless only, C-RAW unverified)
Canon EOS R10 verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS RP verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS R50 verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS R50 V verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon PowerShot G5 X Mark II verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon PowerShot G7 X Mark III verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon PowerShot SX70 HS verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon PowerShot V1 verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md)
Canon EOS-5D Mark III verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Canon EOS 70D verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 800D (EOS Digital Rebel T7i, EOS Kiss X9i) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G9X verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 3000D (EOS Rebel T100, EOS 4000D) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 1000D (EOS Digital Rebel XS, EOS Kiss F) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G1 X Mark II verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 500D (EOS Digital Rebel T1i, EOS Kiss X3 Digital) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 400D (EOS Digital Rebel XTi, EOS Kiss Digital X) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot S110 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 1300D (EOS Digital Rebel T6, EOS Kiss X80) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 600D (EOS Digital Rebel T3i, EOS Kiss X5 Digital) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G1 X verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G16 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G3 X verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS M verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS-1D X Mark II verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 750D (EOS Digital Rebel T6i, EOS Kiss X8i) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 5DS verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot S100V verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 350D (EOS Digital Rebel XT, EOS Kiss Digital N) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 1200D (EOS Digital Rebel T5, EOS Kiss X70) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G11 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 100D (EOS Digital Rebel SL1, EOS Kiss X7) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G15 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS-1D Mark IV verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS M5 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot S100 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 650D (EOS Digital Rebel T4i, EOS Kiss X4i) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot SX50 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS M2 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS M3 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G12 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 6D Mark II verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS M10 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 700D (EOS Digital Rebel T5i, EOS Kiss X7i) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 1100D (EOS Digital Rebel T3, EOS Kiss X50 Digital) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 77D (EOS 9000D) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 1500D (EOS Rebel T7, EOS Kiss X90, EOS 2000D) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS-5D Mark IV (** see note) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS-1D Mark III verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 60D verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS-1D Mark II N verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS M100 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G5 X verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G9 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 5D verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS M6 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G7 X Mark II verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 7D Mark II verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot S120 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot S95 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G1 X Mark III verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 50D verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 20D verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G7 X verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 450D (EOS Digital Rebel XSi, EOS Kiss Digital X2) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot SX60 HS verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 200D (EOS Kiss X9, EOS Rebel SL2) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon PowerShot G9 X Mark II verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 760D (EOS Digital Rebel T6s, EOS Kiss 8000D) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Canon EOS 30D verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm GFX 100 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Fujifilm GFX 100RF (Final Support) verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Fujifilm GFX 100S verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Fujifilm GFX 50S verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-A2 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-A5 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-E1 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-E5 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-H2 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-H2S verified docs/raf-compression.md (supplied files, exact sensor arrays)
Fujifilm X-M5 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-T2 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-T20 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-T4** verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-T5 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-T50 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Google Pixel 2 XL verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-M5 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-T2 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-T20 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-T4** verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-T5 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Fujifilm X-T50 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Google Pixel 2 XL verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Hasselblad Lunar verified issue #1275: raw.pixls.us 4051 (a Sony NEX-7 ARW under Make HASSELBLAD) decodes from sensor data; black 512 and SR2SubIFD WB as on a NEX-7
Leica C (Typ 112) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
Leica D-Lux (Typ 109) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
@@ -129,147 +129,147 @@ Leica V-LUX (Typ 114) verified PR #215: 178 raw.pixls.us files from 118 Panasoni
Leica V-LUX 1 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
Leica V-LUX 4 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
Leica V-LUX 5 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
Leica M Monochrom verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash)
Leica CL verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Leica M10 verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Leica C-Lux verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Leica M Monochrom (Typ 246) verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash)
Leica M8 verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash)
Leica X2 verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Leica Q2 verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Leica SL (Typ 601) verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Leica M10-R verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Leica SL2 verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Leica M9 verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash)
Leica Q (Typ 116) verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon COOLPIX B700 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Nikon D5100 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Nikon D7000 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Nikon D7500 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Nikon Z f verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Nikon D700 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm XF10 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-A10 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D5300 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-T100 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-Pro1 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon 1 V1 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon 1 J4 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm GFX 100S II verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D610 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D5 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D2H verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon Z 6 II verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon COOLPIX P1000 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix X100 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X30 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix X10 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D2Xs verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D4 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D800 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D780 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D850 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-E2S verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D500 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm GFX 50R verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon Df verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-S10 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon 1 V2 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon 1 V3 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D3s verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon Z fc verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D810 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-A3 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D40 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D800E verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D3X verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X100V** verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D4S verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-T30 III verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm XQ2 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-T10 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D2X verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X70 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D80 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-H1 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon 1 J5 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon 1 J1 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D7200 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-T30 II verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon Z 50 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D5000 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm XQ1 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix HS30 EXR verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D70S verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D5200 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon COOLPIX P1100 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-S20 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon Z 30 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-E2 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D60 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon 1 J2 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D5600 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D3 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D300 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix SL1000 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-Pro3** verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-Pro2 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon 1 S1 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X100VI verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-T200 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D3200 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-T3 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix F550 EXR verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D5500 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-A7 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D3300 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon Z 5 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon 1 J3 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon Z 9 (*See note) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon COOLPIX A1000 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X100S verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D2Hs verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D90 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D50 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D3000 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X100T verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon Z 50 II verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D600 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm XF1 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D7100 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-E3 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-A1 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D3500 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix HS50 EXR verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm GFX 100 II verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X100F verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D40x verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon COOLPIX A verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X20 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-E4 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm GFX 50S II verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D1H verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D200 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix HS33 EXR verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix S1 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon Z 7 II verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D70 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D300s verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix X-S1 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D750 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D3100 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-T1 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D3400 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix X-M1 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-T30 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon COOLPIX P950 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix F770 EXR verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Leica M Monochrom verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash)
Leica CL verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Leica M10 verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Leica C-Lux verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Leica M Monochrom (Typ 246) verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash)
Leica M8 verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash)
Leica X2 verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Leica Q2 verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Leica SL (Typ 601) verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Leica M10-R verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Leica SL2 verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Leica M9 verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash)
Leica Q (Typ 116) verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon COOLPIX B700 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Nikon D5100 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Nikon D7000 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Nikon D7500 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Nikon Z f verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Nikon D700 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm XF10 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-A10 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D5300 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-T100 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-Pro1 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon 1 V1 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon 1 J4 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm GFX 100S II verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D610 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D5 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D2H verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon Z 6 II verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon COOLPIX P1000 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix X100 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X30 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix X10 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D2Xs verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D4 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D800 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D780 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D850 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-E2S verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D500 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm GFX 50R verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon Df verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-S10 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon 1 V2 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon 1 V3 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D3s verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon Z fc verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D810 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-A3 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D40 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D800E verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D3X verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X100V** verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D4S verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-T30 III verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm XQ2 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-T10 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D2X verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X70 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D80 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-H1 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon 1 J5 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon 1 J1 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D7200 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-T30 II verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon Z 50 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D5000 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm XQ1 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix HS30 EXR verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D70S verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D5200 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon COOLPIX P1100 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-S20 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon Z 30 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-E2 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D60 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon 1 J2 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D5600 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D3 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D300 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix SL1000 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-Pro3** verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-Pro2 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon 1 S1 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X100VI verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-T200 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D3200 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-T3 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix F550 EXR verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D5500 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-A7 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D3300 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon Z 5 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon 1 J3 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon Z 9 (*See note) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon COOLPIX A1000 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X100S verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D2Hs verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D90 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D50 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D3000 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X100T verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon Z 50 II verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D600 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm XF1 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D7100 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-E3 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-A1 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D3500 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix HS50 EXR verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm GFX 100 II verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X100F verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D40x verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon COOLPIX A verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X20 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-E4 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm GFX 50S II verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D1H verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D200 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix HS33 EXR verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix S1 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon Z 7 II verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D70 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D300s verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix X-S1 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D750 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D3100 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-T1 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon D3400 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix X-M1 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm X-T30 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon COOLPIX P950 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Fujifilm FinePix F770 EXR verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Nikon COOLPIX 5700 unsupported bulk: raw.pixls.us CC0 file decodes wrongly (two stacked half-height fields, CMYG mosaic read as RGB; green vs camera JPEG r 0.12-0.36)
Nikon COOLPIX 8400 unsupported bulk: raw.pixls.us CC0 file decodes wrongly (two stacked half-height fields, CMYG mosaic read as RGB; green vs camera JPEG r 0.12-0.36)
Nikon COOLPIX 8800 unsupported bulk: raw.pixls.us CC0 file decodes wrongly (two stacked half-height fields, CMYG mosaic read as RGB; green vs camera JPEG r 0.12-0.36)
Olympus E-1 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Olympus EVOLT E-400 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Olympus Stylus XZ-2 iHS verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Olympus E-1 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Olympus EVOLT E-400 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Olympus Stylus XZ-2 iHS verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Panasonic LUMIX DC - G97 (DC-G99M2) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
Panasonic LUMIX DC-FZ1000M2 (DC-FZ10002) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
Panasonic LUMIX DC-FZ80 (DC-FZ82, DC-FZ85) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
@@ -363,47 +363,47 @@ Panasonic LUMIX DMC-ZS40 (DMC-TZ60, DMC-TZ61) verified PR #215: 178 raw.pixls.us
Panasonic LUMIX DMC-ZS50 (DMC-TZ70, DMC-TZ71) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
Panasonic LUMIX DMC-ZS60 (DMC-TZ80, DMC-TZ81, DMC-TZ85) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
Panasonic LUMIX ZS200 (DC-TX2, DC-TZ200, DC-TZ202, DC-TZ220, DC-ZS220) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
Pentax K-3 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Pentax K-5 IIs verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Pentax K10D verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Ricoh GR III verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Pentax K-1 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax *ist DL verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Ricoh GX200 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash)
Pentax KP verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-3 Mark III Monochrome verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash)
Pentax K-70 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Ricoh GR IIIx verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K20D verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax *ist DS verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-3 Mark III verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-01 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K110D verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax Q7 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-S1 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax *ist D verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-30 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K200D verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-5 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-x verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Ricoh GR verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax 645D verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K100D verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-500 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K2000 K-m verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-50 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax 645Z verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-5 II verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax Q verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax MX-1 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K100D Super verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Ricoh GR II verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-S2 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-r verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax KF verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-3 II († see note) verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-7 verified corpus: crates/raw/tests/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Samsung TL500 (EX1) verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Pentax K-3 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Pentax K-5 IIs verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Pentax K10D verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Ricoh GR III verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Pentax K-1 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax *ist DL verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Ricoh GX200 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash)
Pentax KP verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-3 Mark III Monochrome verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash)
Pentax K-70 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Ricoh GR IIIx verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K20D verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax *ist DS verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-3 Mark III verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-01 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K110D verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax Q7 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-S1 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax *ist D verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-30 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K200D verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-5 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-x verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Ricoh GR verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax 645D verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K100D verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-500 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K2000 K-m verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-50 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax 645Z verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-5 II verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax Q verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax MX-1 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K100D Super verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Ricoh GR II verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-S2 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-r verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax KF verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-3 II († see note) verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Pentax K-7 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check)
Samsung TL500 (EX1) verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Sony A100 (DSLR-A100) preview issue #702 fixed (#774): opens from its embedded JPEG; its raw (no CFA image IFD) is not decoded
Sony A200 (DSLR-A200) preview issue #535: preview-only on the raw.pixls.us sample
Sony A230 (DSLR-A230) preview issue #535: preview-only on the raw.pixls.us sample
@@ -502,70 +502,70 @@ Sony RX1R II (DSC-RX1RM2) verified issue #535: raw.pixls.us bulk check of all 10
Sony ZV-1 (DCZV1/B) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
Sony ZV-E1 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
Sony ZV-E10 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
Sony ZV-1M2 verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Sony ZV-E10M2 verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Sony RX1R III (DSC-RX1RM3) verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Sony A1 II (ILCE-1M2) (** see note) verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Sony DSC-RX100M7A verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Sony A7 V (ILCE-7M5) (** see note) verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Sony FX2 (ILMEFX2) (** see note) verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Sony ILCE - 6400A verified corpus: crates/raw/tests/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus SP-550 UZ verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus E-10 verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash)
Olympus Camedia C-5060 Zoom verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus SP-565 UZ verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus OM-D E-M5 Mark II verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus EVOLT E-300 verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus Stylus SH-2 verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus Camedia C-7070 Wide Zoom verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus Stylus XZ-10 verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus EVOLT E-330 verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus SP-570 UZ verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus SP-510 UZ verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus PEN-F verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus EVOLT E-500 verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Samsung TL350 (WB2000) verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Samsung NX1000 verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Samsung NX1100 verified corpus: crates/raw/tests/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Sony ZV-1M2 verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Sony ZV-E10M2 verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Sony RX1R III (DSC-RX1RM3) verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Sony A1 II (ILCE-1M2) (** see note) verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Sony DSC-RX100M7A verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Sony A7 V (ILCE-7M5) (** see note) verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Sony FX2 (ILMEFX2) (** see note) verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Sony ILCE - 6400A verified corpus: crates/raw/tests/it/sony_leica_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus SP-550 UZ verified corpus: crates/raw/tests/it/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus E-10 verified corpus: crates/raw/tests/it/olympus_samsung_corpus.rs (pinned sensor hash)
Olympus Camedia C-5060 Zoom verified corpus: crates/raw/tests/it/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus SP-565 UZ verified corpus: crates/raw/tests/it/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus OM-D E-M5 Mark II verified corpus: crates/raw/tests/it/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus EVOLT E-300 verified corpus: crates/raw/tests/it/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus Stylus SH-2 verified corpus: crates/raw/tests/it/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus Camedia C-7070 Wide Zoom verified corpus: crates/raw/tests/it/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus Stylus XZ-10 verified corpus: crates/raw/tests/it/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus EVOLT E-330 verified corpus: crates/raw/tests/it/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus SP-570 UZ verified corpus: crates/raw/tests/it/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus SP-510 UZ verified corpus: crates/raw/tests/it/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus PEN-F verified corpus: crates/raw/tests/it/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Olympus EVOLT E-500 verified corpus: crates/raw/tests/it/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Samsung TL350 (WB2000) verified corpus: crates/raw/tests/it/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Samsung NX1000 verified corpus: crates/raw/tests/it/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Samsung NX1100 verified corpus: crates/raw/tests/it/olympus_samsung_corpus.rs (pinned sensor hash, camera-JPEG check)
Sony A100 (DSLR-A100) unsupported issue #702: regression, refused as not a readable photo
Sony DSC-F828 preview 2026-10-12 release CLI: raw.pixls.us DSC06227.SRF imports as preview-only through recursive and direct import (docs/raw-parity.md)
Epson R-D1 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Epson R-D1s verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Epson R-D1x verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Mamiya ZD verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Apple iPhone 6s Plus verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Apple iPhone 7 Plus verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Apple iPhone XS verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
DJI DJI Mavic 2 Pro (Hasselblad L1D-20c) verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
DJI DJI Mavic 3 (Hasselblad L2D-20c) verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
DJI DJI Mavic 4 Pro (DJI FC9287) verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
DJI DJI Mini 2 verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Google Pixel 4 XL verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Google Pixel 4a Rear Wide Camera verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Google Pixel 6 Pro Rear Main Camera verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Google Pixel 8 Pro Wide Camera* verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Hasselblad CFV-50c verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); decode verified, not colour calibration
Hasselblad H5D-40 verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); decode verified, not colour calibration
Hasselblad H5D-50c verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); decode verified, not colour calibration
Hasselblad X1D verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); decode verified, not colour calibration
Hasselblad X1D II 50C verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); decode verified, not colour calibration
Hasselblad X2D 100C verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); decode verified, not colour calibration
Nikon D6 verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); checked compressed NEF only; uncompressed remains preview-only (#671)
Nikon Z 6 verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); checked compressed NEF only; uncompressed remains preview-only (#671)
Nikon Z 7 verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); checked compressed NEF only; uncompressed remains preview-only (#671)
Nikon Z 8 verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); HE / HE* remain preview-only
Parrot Anafi verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Samsung Galaxy S21 Ultra Rear Main Camera verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Samsung Galaxy S22 Ultra Rear Wide Camera verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Samsung Galaxy S23 Rear Wide Camera verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Samsung Galaxy S7 Edge verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Samsung Galaxy S8 verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Samsung Galaxy S8+ verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Sigma fp verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Sigma fp L verified corpus: crates/raw/tests/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Konica Minolta DiMAGE 7Hi verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Konica Minolta DiMAGE A1 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Konica Minolta DiMAGE A200 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Konica Minolta Maxxum 7D Dynax 7D verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
Epson R-D1 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Epson R-D1s verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Epson R-D1x verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Mamiya ZD verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Apple iPhone 6s Plus verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Apple iPhone 7 Plus verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Apple iPhone XS verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
DJI DJI Mavic 2 Pro (Hasselblad L1D-20c) verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
DJI DJI Mavic 3 (Hasselblad L2D-20c) verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
DJI DJI Mavic 4 Pro (DJI FC9287) verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
DJI DJI Mini 2 verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Google Pixel 4 XL verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Google Pixel 4a Rear Wide Camera verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Google Pixel 6 Pro Rear Main Camera verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Google Pixel 8 Pro Wide Camera* verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Hasselblad CFV-50c verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); decode verified, not colour calibration
Hasselblad H5D-40 verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); decode verified, not colour calibration
Hasselblad H5D-50c verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); decode verified, not colour calibration
Hasselblad X1D verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); decode verified, not colour calibration
Hasselblad X1D II 50C verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); decode verified, not colour calibration
Hasselblad X2D 100C verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); decode verified, not colour calibration
Nikon D6 verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); checked compressed NEF only; uncompressed remains preview-only (#671)
Nikon Z 6 verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); checked compressed NEF only; uncompressed remains preview-only (#671)
Nikon Z 7 verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); checked compressed NEF only; uncompressed remains preview-only (#671)
Nikon Z 8 verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check); HE / HE* remain preview-only
Parrot Anafi verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Samsung Galaxy S21 Ultra Rear Main Camera verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Samsung Galaxy S22 Ultra Rear Wide Camera verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Samsung Galaxy S23 Rear Wide Camera verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Samsung Galaxy S7 Edge verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Samsung Galaxy S8 verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Samsung Galaxy S8+ verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Sigma fp verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Sigma fp L verified corpus: crates/raw/tests/it/coverage_sweep_corpus.rs (pinned sensor hashes); bulk: docs/raw/coverage-sweep.tsv (CC0, SHA-256, camera-image check)
Konica Minolta DiMAGE 7Hi verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Konica Minolta DiMAGE A1 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Konica Minolta DiMAGE A200 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Konica Minolta Maxxum 7D Dynax 7D verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES)
Konica Minolta DiMAGE 7 verified raw.pixls.us check: CC0 sample decoded and its WB / CFA matched to the embedded preview (vendor/mrw.rs)
Konica Minolta DiMAGE A2 verified raw.pixls.us check: CC0 sample decoded and its WB / CFA matched to the embedded preview (vendor/mrw.rs)
Can't render this file because it contains an unexpected character in line 2 and column 3.
+27 -4
View File
@@ -1,16 +1,39 @@
#!/usr/bin/env python3
"""lcb.py FILE.jsonl — run control requests from a JSON-lines file ({"method":..., "params":...} or {"sleep": s})."""
import json, socket, sys, time
from pathlib import Path
# Largest control reply we accept (a screenshot response can carry a base64 image), after twice
# that the connection is closed instead of buffering further.
MAX_RESPONSE = 128 << 20
def _jsonl_path(arg):
"""Resolve the CLI file argument; require an existing readable regular file."""
path = Path(arg).resolve()
if not path.is_file():
sys.exit(f"not a readable file: {arg}")
try:
with path.open("rb"):
pass
except OSError:
sys.exit(f"not a readable file: {arg}")
return path
def call(method, params):
s = socket.create_connection(("127.0.0.1", 7980), timeout=180)
s.sendall((json.dumps({"id": 1, "method": method, "params": params}) + "\n").encode())
buf = b""
while not buf.endswith(b"\n"):
buf = bytearray()
while not (len(buf) and buf[-1:] == b"\n"):
c = s.recv(1 << 20)
if not c: break
buf += c
# a hostile server must not make a client buffer or pay quadratic copies forever (CWE-407)
if len(buf) + len(c) > MAX_RESPONSE:
s.close()
return {"ok": False, "error": f"response exceeds {MAX_RESPONSE} bytes"}
buf.extend(c)
return json.loads(buf)
for line in open(sys.argv[1]):
for line in open(_jsonl_path(sys.argv[1])):
line = line.strip()
if not line or line.startswith("#"): continue
r = json.loads(line)
+2 -2
View File
@@ -181,7 +181,7 @@ profile correction control when an existing edit has it disabled.
## Local inventory check
`crates/raw/tests/sony_lens_inventory.rs` is opt-in and does nothing in CI. With `LIGHTCRAFT_SONY_LENS_DIR` naming a
`crates/raw/tests/it/sony_lens_inventory.rs` is opt-in and does nothing in CI. With `LIGHTCRAFT_SONY_LENS_DIR` naming a
directory of ARWs it decodes each from its headers and prints its record (settings, sources, copies' agreement,
sample ranges, statuses); `LIGHTCRAFT_SONY_LENS_FULL=1` also compares header and full decode. With
`LIGHTCRAFT_SONY_LENS_INVENTORY` naming a CSV inventory it checks every file's table counts, sizes, sample counts,
@@ -300,7 +300,7 @@ The current `cargo xtask corpus --download` manifest contains 25 Sony CC0 sample
including samples 4822 (A7R IVA / 24–105 mm) and 3989 (A9 II / 200–600 mm), each with a pinned SHA-256 that the
download checks. `sony_embedded_distortion_is_limited_to_validated_models` checks 23 of them (the two ILCE-7CM2
codings have their own test), and `sony_lens_records_of_the_public_samples` verifies all 25 against their pins
(`SONY_SAMPLES` in `crates/raw/tests/corpus.rs`, itself anchored by a pinned digest and compared with the manifest)
(`SONY_SAMPLES` in `crates/raw/tests/it/corpus.rs`, itself anchored by a pinned digest and compared with the manifest)
before decoding. **No CI job fetches the corpus**: these fixture tests run only where it has been downloaded, and
otherwise skip each absent file. `LIGHTCRAFT_CORPUS_REQUIRED=1` makes `sony_lens_records_of_the_public_samples`
fail on any absent pinned file instead.
+2 -1
View File
@@ -158,7 +158,7 @@ essentials replaces its guessed 45% raw look with 84% × 0.75 (+1 point).
| Discount | Mainstream | Essentials | Evidence |
|---|---:|---:|---|
| Interaction fidelity / UI clarity | ×0.95 | ×0.95 | brush live preview gaps and feathering (#517, #518); crop artefacts and limits (#731, #742, #770, #771); Compare white-balance selector (#743–#745); low-res slider previews (#330); dialogs taller than the window (#781); mouse-wheel scroll (#714) |
| Stability on real machines | ×0.92 | ×0.92 | launch and quit crashes or silent startup failures (#250 macOS 12 Intel, #315 Windows Intel GPU, #431 Intel Mac, #260, #462, #783 Linux / Nouveau, #790 Windows installer, fixed in 0.9.1); tests run in CI on FreeBSD only (gap 9) |
| Stability on real machines | ×0.92 | ×0.92 | launch and quit crashes or silent startup failures (#250 macOS 12 Intel, #315 Windows Intel GPU, #431 Intel Mac, #260, #462, #783 Linux / Nouveau, #790 Windows installer, fixed in 0.9.1); tests run in CI on Linux x64 and FreeBSD only (gap 9) |
| File exchange with Lightroom users (mainstream) / opening files people send (essentials) | ×0.92 | ×0.95 | Adobe XMP sidecars change colours (#290), XMP CameraProfile and Upright ignored (#204, #205), catalog import fails (#599), same Light values render differently (#523, #209), no write-back to `.lrcat`; for casual users: CR3 beyond verified bodies, compressed ORF, HE NEF open as previews (#473, #485, #556), HEIC trouble (#693) |
| **Product** | **×0.804** | **×0.830** | |
@@ -300,6 +300,7 @@ P2 items 12–25 h; look tuning, performance, packaging, hardening 25–45 h.
| Date | Change | Summary |
|---|---|---|
| 2026-10-19 | patch | Stability note: tests run in CI on Linux x64 (every PR) as well as FreeBSD; macOS and Windows remain (gap 9) |
| 2026-10-19 | minor | Localization ~55% → ~64% (mean of the 12 key languages): Indonesian (`id`) catalog, every message but What's New; full target 61.2% → 61.5% (still ~61%) |
| 2026-10-19 | patch | Platforms: the Windows installer is no longer listed as unverified (the 0.9.1 x64 MSI is tested on Windows 11; a confirmation after uninstalling from Settings → Apps, #892, remains); #790 noted as fixed in 0.9.1; percentages and hours unchanged |
| 2026-10-14 | minor | Localization ~52% → ~55% (mean of the 12 key languages): de, es, fr, ja and uk catalogs filled |
+1 -1
View File
@@ -12,7 +12,7 @@ Run the evidence without private samples:
```sh
cargo test -p lightcraft-pipeline tint_ -- --nocapture
cargo test -p lightcraft-gpu --test equivalence tint_directions -- --nocapture
cargo test -p lightcraft-gpu --test it equivalence::tint_directions -- --nocapture
cargo test -p lightcraft-color cct
```
+6 -4
View File
@@ -1,6 +1,6 @@
# UI parity
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-21 · **Change:** minor (context menus: a right-click elsewhere on the same widget moves its open menu instead of flashing it, issue #1877; Reference Escape, catalog-local pins and deleted-photo handling verified on Windows; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; earlier: zoom row soft proofing sharp at 1:1, canvas tools by keyboard, focus-order test, ~50 unnamed hand-drawn widgets named) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-23 · **Change:** minor (KEYC-RATING: ⌘L Enable Filters; context menus: a right-click elsewhere on the same widget moves its open menu instead of flashing it, issue #1877; Reference Escape, catalog-local pins and deleted-photo handling verified on Windows; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; earlier: zoom row soft proofing sharp at 1:1, canvas tools by keyboard, focus-order test, ~50 unnamed hand-drawn widgets named) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
How LightCraft's interaction compares with Lightroom's: views, panels, tools and handles, modifier
keys, shortcuts, zoom, context menus and feel. Rows for individual menu items and shortcuts live in
@@ -27,7 +27,7 @@ assessment and the notes that don't fit a row. Visual reference for agents: `pla
| Crop, Upright, transform handles | handles, aspect lock, angle, overlays (O / ⇧O), guided Upright | ✅ all of them; overlay cycle on ⇧O | 85% | crop opacity of cut-out area (15.5) |
| Masking tools | brush (A / B, flow, density, auto mask, pressure), linear / radial, range masks, AI masks, pins | brush, linear, radial, colour / luminance range, add / subtract / intersect / invert, masks panel, pins | 60% | brush live preview (#517 / #518), no pen pressure, no depth range, AI masks, Feather / Edge controls (15.5) |
| Remove / heal | content-aware remove, heal, clone, visualize spots | heal, clone, auto source, visualize spots, red / pet eye | 65% | content-aware (patch synthesis) remove, generative remove, detect distractions |
| Zoom and navigation | Fit / Fill / 1:1 / 2:1 … 11:1, Navigator, spacebar pan | Fit to 800%, pinch zoom, pan, Navigator-like | 60% | diagnostic overlays (Visualize Spots, Point Color range, HDR range) render the whole frame instead of a zoom window; everything else, mask overlays and soft proofing included, is sharp at 1:1–8:1 |
| Zoom and navigation | Fit / Fill / 1:1 / 2:1 … 11:1, Navigator, spacebar pan | Fit to 800%, pinch zoom, pan, Navigator-like; every view, mask and diagnostic overlays (Visualize Spots, Point Color range, HDR range) and soft proofing included, is sharp at 1:1–8:1 | 60% | zoom stops at 800 % (Adobe's reference goes to 11:1) |
| Library culling and metadata | ratings, flags, labels with keys, painter, keywording, metadata panel, filter bar | ratings / flags / labels and advance, keyword list and sets, keywording box, metadata fields, smart albums, filter bar | 85% | painter tool, quick collection, some Classic keys |
| Shortcuts | Lightroom desktop and Classic keymaps | Lightroom-desktop keymap with Classic additions, editable | 85% | deliberate differences below; no Classic keymap layer |
| Context menus | everywhere | 31 sites (grid, filmstrip, albums, folders, keywords, masks, presets, text fields…), all through `widgets::context_menu` (`crates/ui-egui/src/widgets.rs`): a right-click elsewhere while a menu is open closes it and opens the menu there, also on the same widget (issue #1877: that one flashed at the new spot and closed, as egui counts the opening click as a click while open) | 70% | unaudited against Lightroom's menus item by item |
@@ -66,7 +66,7 @@ under the UI command that wraps it (a no-op error); the UI command now wins.
| Create Version | ⌘⇧S (+ ⇧M) | ⇧M (Windows: Ctrl+⇧S) | — |
| Select None | ⌘⇧A (+ ⌘D) | ⌘D | — |
**Library culling (KEYC-RATING):** `0–5`, `6–9` and `P/X/U` reach egui on macOS even when displayed in the native menu (adapted from PR #261; issue #283). `Shift+0–9` applies and advances once; `Shift+P` picks and advances in Photo Grid / Square Grid and retains Presets elsewhere. See [library shortcuts](library-shortcuts.md) for the verified Classic mapping and regression coverage. F6, F7 and F8 show / hide the filmstrip, the left panel and the right panel (F8 brings back the panel it hid; `view.rightPanel`). `` ` `` cycles the flag (unflagged → pick → reject → unflagged) and ⌘↑ / ⌘↓ raise / lower it; the native menu leaves ⌘↑ / ⌘↓ to egui so Compare/Survey act on the active photo. `\` shows / hides the Filter Bar in Photo Grid / Square Grid and keeps Show Original in Detail. Remaining Classic gaps: ⌘L (Enable Filters).
**Library culling (KEYC-RATING):** `0–5`, `6–9` and `P/X/U` reach egui on macOS even when displayed in the native menu (adapted from PR #261; issue #283). `Shift+0–9` applies and advances once; `Shift+P` picks and advances in Photo Grid / Square Grid and retains Presets elsewhere. See [library shortcuts](library-shortcuts.md) for the verified Classic mapping and regression coverage. F6, F7 and F8 show / hide the filmstrip, the left panel and the right panel (F8 brings back the panel it hid; `view.rightPanel`). `` ` `` cycles the flag (unflagged → pick → reject → unflagged) and ⌘↑ / ⌘↓ raise / lower it; the native menu leaves ⌘↑ / ⌘↓ to egui so Compare/Survey act on the active photo. `\` shows / hides the Filter Bar in Photo Grid / Square Grid and keeps Show Original in Detail. ⌘L / Ctrl+L (Enable Filters) puts the filter aside and shows every photo, and the next press brings back the same filter, stars, flag and text together; setting or clearing a filter while it is off drops the one put aside (not documented by Adobe, our choice).
**Grid thumbnail size (KEYC-COMPARE):** Classic's `=` and `-` increase and decrease the Grid thumbnail size (same keys on macOS and Windows; `+` also works for the larger size here, as Shift+`=` or the keypad). They step the bottom-bar slider by 30 pt inside its 90–480 pt range, in Photo Grid, Square Grid and People only; Detail, Compare, Survey and Reference ignore them, and ⌘= / ⌘− stay Zoom In / Out. See [library shortcuts](library-shortcuts.md).
@@ -80,7 +80,7 @@ under the UI command that wraps it (a no-op error); the UI command now wins.
## Accessibility audit
`crates/ui-egui/tests/accessibility.rs` walks the AccessKit tree egui hands to VoiceOver, Narrator and
`crates/ui-egui/tests/it/accessibility.rs` walks the AccessKit tree egui hands to VoiceOver, Narrator and
AT-SPI, headless, in Photo Grid, Square Grid, Detail, Compare, Survey and Reference, in each right
panel (Edit, Profiles, Crop, Remove, Masking, Red Eye, Versions, Activity, Keywords, Info) and in 13
dialogs (Export, Copy / Paste Settings, Create Preset, Rename, New Album, New Smart Album, Capture
@@ -149,7 +149,9 @@ establish OS keyboard delivery or cross-platform acceptance.
| Date | Change | Summary |
|---|---|---|
| 2026-10-15 | patch | Context menus: a right-click elsewhere on the same widget while its menu is open moves the menu to the pointer; before it flashed at the new spot and closed (issue #1877); every site uses `widgets::context_menu`; 70% unchanged |
| 2026-10-23 | patch | KEYC-RATING: ⌘L / Ctrl+L (Library ▸ Enable Filters) switches the filter off and on, restoring it as it was |
| 2026-10-15 | patch | KEYC-RATING: `\` shows / hides the Filter Bar in Photo Grid and Square Grid (Show Original in Detail); ⌘L is the filter-bar key left |
| 2026-10-14 | minor | Zoom row: diagnostic overlays render zoom windows too |
| 2026-10-14 | patch | Shortcuts: Visualize HDR loses its default ⇧H, which cycles the Tool Overlay (issue #224); both PRs had claimed it |
| 2026-10-12 | minor | Accessibility: crop handles, spots, gradient handles and mask pins operable by keyboard with AccessKit names and values; Tab order test for the right panels; hand-drawn icons and rows named; 55% → 75%; UI remaining 34–58 h → 32–54 h |
| 2026-10-13 | minor | Shortcuts: Tool Overlay modes for spot and mask pins on ⇧H (H stays Remove; issue #224), listed with the deliberate differences; dropped from the missing list |
+3 -3
View File
@@ -9,12 +9,12 @@ Double-click a photo in the grid, then press `E` to open the **Edit** panel. Dra
## Steps
1. In **Photo Grid** or **Square Grid**, double-click the photo. It opens in the **Detail** view.
2. Press `E`, or choose **Window** > **Edit**, to open the **Edit** panel.
1. In **Photo Grid** or **Square Grid**, double-click the photo. It opens in the **Detail View**.
2. Press `E`, or choose **Window** > **Edit Panel**, to open the **Edit** panel.
3. Click **Auto** for a first pass, or skip this step and start with the sliders.
4. Open the **Light** section and set **Exposure**, **Contrast**, **Highlights**, **Shadows**, **Whites** and **Blacks**.
5. Open the **Color** section to change **Vibrance** and **Saturation**. Open **Effects** for **Texture**, **Clarity** and **Dehaze**. The panel also has **Detail** and **Optics** sections.
6. Check your work against the original. Hold `\` to peek at the original. Press `Y` (**View** > **Compare Before and After**) to see before and after side by side.
6. Check your work against the original. Hold `\` to peek at the original. Press `Y` (**View** > **Before/After Side by Side**) to see before and after side by side.
7. To go back a step, choose **Edit** > **Undo**. To drop all edits on the photo, click **Reset** at the top of the **Edit** panel.
## Black and white
+8 -1
View File
@@ -158,7 +158,14 @@ are the same in every version, so serve them with `Cache-Control: no-cache` (not
`write` service hands each file to the browser as a download instead of writing it to disk.
- **Automation.** `await lightcraft.command("library.info", "{}")` runs any engine or UI command
by id on the next frame and resolves to the JSON result (`web.stats` reports storage, workers
and the render queue). This is how the headless-Chrome checks drive the page.
and the render queue). The params are a JSON object; empty text and JSON `null` mean `{}`
(no params: a command that takes photo ids then acts on the selection). Any other text that
isn't an object rejects the promise and runs nothing, as the control channel and MCP do, so a
typo can't run a command on the selection. This is how the headless-Chrome checks drive the page.
- **The page title names the open photo**: `<file name> — LightCraft`, with ` •` after the name while
changes are not yet written to browser storage (`LightcraftApp::page_title`). eframe's web runner
ignores the window title the desktop app sets, and a host that embeds the page reads this title to
tell which photo a drop opened and whether it is saved (issue #2397).
## Not yet
+185 -2
View File
@@ -1,7 +1,162 @@
# What's new in LightCraft
## 2026-10-24
### Relative-date filters and smart albums follow the clock
- A current view or source count using “in the last” rules now refreshes when the session clock moves into a new
minute, even when no catalog edit occurred. This fixes stale photos remaining in the grid and stale counts for
direct or nested smart albums. Held views keep photos that expired until Refresh View while admitting photos that
newly match; expiry itself does not create an undo step.
### The histogram's tonal zones adjust the Basic tones
- The histogram is a control surface again, as in Lightroom Classic: hovering one of its five zones (Blacks, Shadows,
Exposure, Highlights, Whites) tints it and shows its name and value under the plot ("Exposure: +0.45 EV"). Dragging one
sideways moves that control — the zone you pressed stays locked while the pointer crosses the others, Shift drags a
tenth as far, and the whole drag is one undo step. Double-clicking a zone puts its control back to its default.
### Drag the parametric Tone Curve
- In the Tone Curve's parametric mode the graph is now drawn from the renderer's own curve, so it always matches the
photo, and the line can be dragged: pressing it picks the region under the pointer (Highlights, Lights, Darks or
Shadows) and moving up or down changes that slider, as one undo step. Hovering the line names the region first.
## 2026-10-23
### Tab leaves the name field in New Album, New Smart Album and the name prompts
- In New Album, New Smart Album, Save Metadata Preset and the other dialogs with one name field, the field has the
keyboard focus when the dialog opens, and Tab now moves on to the check box, Cancel and OK (Shift+Tab goes back).
Before, the field took the focus back every frame, so only Return and Esc got you out of it. Return still
confirms and Esc still cancels.
### Foreign XMP metadata survives namespace-prefix collisions
- Saving a sidecar keeps another application's properties even when its namespace uses prefixes such as `lc`
or `xmp`. Those foreign properties no longer become LightCraft settings or ratings when imported.
### Find Missing Photos finds the files of a library made on Windows
- A library made on Windows records its originals with Windows paths. Opened on macOS or Linux, its photos are
missing until relinked, and Find Missing Photos found them only by their content, so it missed every photo it had
no fingerprint for (such as photos brought in from a Lightroom catalog) or that had been edited since, even in a
folder holding every file under the same name. It now matches them by name as it does any other photo: the name
after the last `/` or `\`, in any letter case, checked against the size when the library has one. Several files
of that name are still reported rather than guessed, and undo puts back the paths as they were.
### Auto straighten leaves straight photos alone
- Auto straighten (Auto in the crop panel, or a double-click with the Straighten tool) no longer turns a photo by
more than 10°, and acts only when at least three lines agree. Before, it often turned photos that were straight:
the edges of wooden planks, a branch or a bird's wing were taken for the horizon, sometimes for a 25° turn.
- When it finds no horizon or vertical lines, it says so instead of doing nothing without a word.
### PSD and PSB metadata stays available
- LightCraft now imports PSD and PSB files with EXIF and XMP image resources. Capture dates, GPS and descriptive fields
stay available in All Metadata; PSD edit copies keep their capture date. Original exports with Remove Location Info enabled
are refused when a PSD or PSB embeds GPS, since LightCraft cannot strip metadata from a byte-for-byte original copy.
### Choose where a Lightroom catalog goes
- File ▸ Import Lightroom Catalog… now asks first: merge the catalog's photos, collections and edits into the
library open now, or create a new library for them, named after the catalog and placed next to the open library
(both can be changed). LightCraft opens the new library, then imports the catalog into it. Cancel does nothing.
Open Library… on a Lightroom catalog goes through the same choice.
- To import as before without the question, choose Settings ▸ General ▸ When importing a Lightroom catalog ▸
Always merge into the open library, or tick Don't ask again in the window. Ask where to import it brings the
question back.
### Choose which library opens at launch
- Settings ▸ General ▸ **At launch**: *Open the last library* (as before) or *Ask me*. With *Ask me*, LightCraft
starts with a window listing your recent libraries: each one's name, the folder it is in and when you last opened
it. One that is gone (moved, renamed, on a disconnected drive), can't be read or no longer holds a library is
greyed out with the reason. Each one is checked in the background, showing *Checking…* until it answers (and that
the drive isn't answering after 5 s), so the list never waits on a network share that is offline or a disk that
has to wake up; File ▸ Open Recent Library uses the same answers (it greys out only libraries that are gone). Making the list only looks: it never locks or writes anything in your libraries, so one
that another program has open is reported, with who has it, when you press **Open**, and the window stays. The
last library is selected; **Open** (or Return, or a double-click) opens it,
**Open Another…** asks for a folder, **New Library…** makes one, **Quit** (or Esc) quits. ↑ and ↓ move through
the list.
- Holding Alt (Option on a Mac) as the window appears shows the same list once, over the library that opened: open
another one from it, or press Esc to keep working where you were. On a Mac, an Option key held from before
LightCraft starts is only noticed once it changes, so press it as the window appears.
- `--library` and `LIGHTCRAFT_LIBRARY` still decide the library when given, and `lightcraft-cli` never asks.
- File ▸ Open Recent Library ▸ **Remove from List** forgets one library, and the launch window's **Remove from
List** does the same. Only the entry goes: the library and its photos stay where they are. Agents use
`app.recentLibraries {remove: path}`; its list now also says when each library was last opened (`lastOpened`).
- Photos dropped on the window while the list (or the window of a library that couldn't open) is up wait for the
library you open and are imported into it, as files given on the command line are; presets dropped then are
refused with a note, since they belong to a library.
## 2026-10-22
### Reliability
- Opening a library no longer writes through a link left at the lock's owner note (`catalog.lock.owner`): the link
is removed and a new note made in its place, so the file it pointed to is never changed (as for the catalog's
temporary files, issue #1502).
### Keys reach text fields and the shortcut editor on macOS
- While a text field has focus, the native menu now lets go of the keys the field needs (`G`, the digits `0`–`9`,
`P`, `U` and the like), and while Keyboard Shortcuts is recording a key it lets go of all of them (⌘, and ⌘Q in
the LightCraft menu too), except the system's own items (Hide LightCraft ⌘H, Hide Others ⌥⌘H, Close Window ⌘W
and Minimize ⌘M) and the two keys of the Help menu (⌘/ and F1). Before, the menu item kept its key on macOS, so the
key could still run the menu command instead of reaching the field or the editor. The keys come back on the menu
items when the field loses focus or recording ends; the menu's order, checkmarks and enabled state are unchanged.
### Find the grid shortcut in View
- Photo Grid and Square Grid now show the grid-cycle shortcut (G by default) in the View menu. The hints follow
shortcut changes; clicking a menu row still selects that grid (issue #2500).
## 2026-10-21
### Settings window
- Settings is one size on every tab and stays where it is as you go through them; before, each tab made its own
size and the tabs moved under the pointer. The tabs stay in place while a long page scrolls (in a very short
LightCraft window, where the page has too little room to scroll by itself, the tabs and the page scroll together), a
page that goes on below shows a scroll handle without the pointer on it, and each page remembers where it was
scrolled to, also when Settings is closed and opened again.
- Nothing in Settings runs off the side any more. A row with more choices than fit (the languages on General) wraps
onto further lines and a long label wraps in its column, where both used to widen the window: in a small
LightCraft window the labels and the Close button of General ended up off screen.
- The Slideshow tab is one column like the other tabs, with the slide preview first, instead of a column beside the
preview that made Settings much wider on that tab. The preview stays above the page while it scrolls, so pointing
at a template further down still shows it there (in a very short window it scrolls with the page instead). Pointing
at a template no longer pushes its rows out from under the pointer, and the Export buttons wrap.
- A long folder (General ▸ Library ▸ Location, the folder Auto Import watches, the smart previews folder, a monitor
profile's file) is shortened to its last folders ("…/Pictures/Library") on one line; rest the pointer on it for
the whole path.
### One star rating control
- The stars in the bottom bar, the Info panel and the filter bar are now one shared control, and the badges on grid
cells, filmstrip thumbnails and the Compare caption use the same drawing. Hovering a star in the filter bar now
previews the rating like the others do, and with a star focused (Tab), ← and → lower and raise the rating.
### Progress looks the same everywhere
- Download, scan, denoise and activity progress now use one bar that fills the width it is given, with its text on
it, and a gentle sweep while the amount of work isn't known yet. Waits that only said "Selecting…", "Waiting to
start…" or "Installing…" now show a small turning arc beside the text, and a thumbnail still being made shows a
quiet placeholder that fits the theme in the grid, the filmstrip and Compare. Screen readers hear a progress
indicator with its percentage.
### Menu names that say what they are
- Switches in the menus are named for what they show, with the check mark saying whether it is on: Filmstrip ✓,
Focus Point ✓, Clipping ✓, Photo Counts ✓, Mask Overlay ✓ instead of Show Focus Point ✓. No two commands share a
name any more: View ▸ Detail is now Detail View (the Edit section Detail keeps its name), Window ▸ Edit is Edit
Panel, and Compare Before and After is Before/After Side by Side, next to Split and Top/Bottom.
- The preview cache command has one name, Discard Preview Cache, in File ▸ Previews and in the toast that confirms
it. Keyboard shortcuts are unchanged.
### Dropping files shows what will happen
- While files are dragged over the window, it dims and a card says what dropping will do: review the photos and
folders for import, import the presets, or open the face model. If a dialog is open, the card asks you to close it
first, and a drop is refused with a message instead of replacing the dialog. The same goes while an import is
running, and in the web version.
- The import review of a finished scan (a drop, or File ▸ Import) no longer replaces a dialog you opened in the
meantime; it opens as soon as that dialog is closed.
- Photos and folders dropped on the window now open the same import review as File ▸ Import, so you pick add, copy or
move, the destination and a preset before anything is imported. Settings ▸ Import ▸ "Import dropped files without
review" brings back the old behaviour of importing at once.
- In the web version, a dropped file the browser can't read, or that can't be imported, now shows a message instead of
failing silently.
- For scripts: `ui.dropFiles` hovers files over the window or drops them, as the operating system does.
### Counting many smart albums is faster
- Counting every smart album at once (the numbers in the Albums list, and the `albums.list` command) now works out an
album that other smart albums test (“album is …”) once for the whole library, instead of again for every album
that tests it. On a synthetic library of 312,000 photos with 77 smart albums, 51 of them testing one shared album,
the full count took about 14 s instead of about 23 s on a heavily loaded machine; the rest is the text rules of
the albums themselves. The counts are the same as before, including for albums that loop through each other.
### Sharper resampling in HDR and panorama merges
- Merged photos (HDR and panorama) are resampled with a sharper filter (Lanczos-3 instead of bilinear). Moving a
photo by half a pixel used to lose a third of its finest detail; now it keeps most of it, without halos around
@@ -39,6 +194,20 @@
- `photo.setReference` refuses an `id` that is not a photo id (a string, a negative or fractional
number) instead of pinning the active photo; only an absent or null `id` means the active photo.
### Edit Capture Time leaves a capture time it can't read as it was
- A capture time that is cut short, has seconds that aren't digits or has text after it (a Lightroom catalog's capture
time is copied as it is) is no longer shifted into a clean-looking wrong time: Edit Capture Time leaves the photo as
it was and says so, and its preview says the photo's time can't be read instead of previewing a wrong one.
`photo.setCaptureTime` counts those photos as `unreadable`. Set date & time still repairs such a photo selected on
its own; with other photos selected it says why it can't move them by the same amount. When the dialog can't make a
change (a time that isn't a date, a shift past year 9999), it now says why and stays open, instead of closing.
### Month names in folder and file name templates
- Date templates take the month by name: `%B` gives January, `%b` gives Jan. Import ▸ Folders ▸ Custom template…
`{date:%Y}/{date:%B}` files photos into 2026/January, 2026/February…, the layout many existing libraries already
use. The names are English in every interface language, so folders don't change when the language does. Rename
Photos and export file names take them too.
## 2026-10-20
### Smart albums check File Path rules faster
@@ -47,7 +216,6 @@
or "doesn't contain" rule took about 17 ms per pass instead of about 62 ms, around 3.5 times faster, with the same
photos matched; about 6 ms of that is only reading the paths. Paths and rule text with letters beyond A–Z are
compared as before.
## 2026-10-19
### AI Denoise on DirectX 12 with NVIDIA GTX 10-series cards
@@ -70,6 +238,13 @@
- With several locations removed from Local, "Show N hidden locations" now lists them, so each comes back on its own
(Restore All is there too). The row used to bring every hidden location back at once (issue #2114).
### Rating in a filtered view keeps your place
- When a filter shows only one-star photos and you take the star off a photo, it leaves the view and the photo after
it is now selected, so you carry on from where you were (issue #2312). The same goes for flags and colour labels,
in Picks and in smart albums. Before, the photo stayed selected although the grid no longer showed it, and → or
Auto Advance jumped back to the first photo of the view. With Auto Advance on, or with Shift+0–5, you land on the
photo after it: no photo is skipped.
## 2026-10-18
### Thumbnails of Lightroom HDR and panorama merges in a second
@@ -141,7 +316,6 @@
one (issue #1507). LightCraft used to open the library without it, showing an incomplete catalog that the next save
would write over the stored copy. It now leaves the library unopened, saves nothing and says which file couldn't be
read; reload the page to try again.
## 2026-10-14
### Model downloads time out on a name that doesn't resolve
@@ -387,6 +561,15 @@
screen and nearly as tall counts as zoomed.
- For scripts: a size set with `ui.resize` is remembered like any other, so a script that needs a size sets it.
### Keyword List: a keyword shows that one inside it is applied
- In the Keyword List (right panel ▸ Keywords), a keyword whose box is empty now carries a dot in it when the
selected photos have a keyword inside it, at any depth: with `Places › Italy › Rome` on a photo, `Places` and
`Italy` show the dot and `Rome` the tick. A folded keyword no longer hides that something in it is applied.
Resting on the box names what is inside.
- The tick and the dash are as they were, and are about the keyword itself: all of the selected photos have it, or
some do. A keyword the photos have themselves shows that, not the dot. A click on the box still gives the
keyword itself to the selected photos.
### 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
+3
View File
@@ -81,6 +81,9 @@ the only copy of those edits outside that application's catalog. Saving never dr
ones), `xmp:CreatorTool`, comments, the packet wrapper and padding. LightCraft's properties go into one
`rdf:Description` of their own; owned properties written by another application (in attribute or element form) are
removed from its description, which otherwise stays as it was.
- Ownership follows the namespace URI, not the prefix spelling. A foreign namespace bound to `lc:` or `xmp:`
stays foreign: its properties are preserved and do not become LightCraft settings or ratings. Genuine namespace
aliases remain interchangeable, including namespace declarations containing XML character references.
- A sidecar that can't be read as XMP (not well-formed, not UTF-8, no `rdf:RDF`) is first copied to
`<name>.xmp.bak-<date><time>` (`-1`, `-2`… if taken — a backup is never overwritten), then replaced. A sidecar that
can't be read at all (permissions) is left alone and the save fails.