From 06c1b9ae0775464ce83ef0a9588be770337600f1 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 2 Nov 2026 18:41:34 -0700 Subject: [PATCH] Use crw craftlib (#3503) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * CRW-LIB: decode Canon CRW through the shared craft-crw crate LightCraft decodes Canon CRW (the CIFF raws of the EOS D30, D60, 10D, 300D and the PowerShot G1/G6) through craft-libs' `craft-crw`, the clean-room decoder derived and verified on the `clean-room-crw-format-decode` branch and moved there with its frozen tables and derivation record. `crates/raw/src/vendor/crw.rs` is now the adapter: it maps the crate's header and samples onto `RawImage` (CFA, levels, active area, orientation, as-shot white balance, the make/model/time records a CRW keeps instead of Exif) and converts the capture time through chrono. The dependency is a local path (`../craft-libs/crates/crw`) until the craft-libs branch merges; then it becomes a git revision. Everything LightCraft-side from that branch comes along unchanged: `probe_bytes` fills metadata gaps from the decoder's records so CRW imports name their camera; the EOS D60 bundled camera profile and its chart-measured dual-illuminant matrices (`chart_table.rs`, `spectral::Camera` carries its illuminant pair) give the D60 a Kelvin white-balance scale; CRW joins the JPEG-fitted starting look, the catalog's raw formats and `lightcraft-cli calibrate`; `RENDER_CACHE_VERSION` 71; the CC0 CRW corpus entries for `cargo xtask corpus`; coverage, parity, gaps, raw-parity, target-app-parity and ROADMAP updates. The G5 stays preview-only (the crate refuses it with the reason) and the robustness test keeps its CRW sample. What does not come along: the decoder, its tables and `crw_oracle.rs` (now craft-crw's digest-pinned sample test) and the derivation record, which lives in craft-libs `crates/crw/clean-room/`. `clean-room/crw/` here keeps only the EOS D60 chart-calibration document and its capture list, with a README pointing at the record. Gates: fmt, clippy -D warnings (raw all targets; engine, catalog, cli), lightcraft-raw tests (475 + 134, the corpus files decoding through the adapter), catalog tests, engine lib tests (1,300 pass; the two failures, `tests_import::recursive_discovery_…` and `originals::tests::write_targets_pin_absent_paths_…`, are in files unchanged from main), `cargo xtask parity`, `assets`, `layers`, `coverage.py --check`. Co-Authored-By: Claude Fable 5.1 * no message * no message * Update documentation after moving code to craft-libs * Review coment fixes * CRW: pin craft-crw f3b62ec, a truncated entropy stream is Corrupt craft-libs #472 makes craft_crw::decode refuse a stream that ends more than Limits::max_stream_end_fill (64 samples, one block) before the frame does, instead of filling the rest with predictor values and returning success. The adapter already maps craft_crw::Error::Corrupt to RawError::Corrupt; a regression test proves a truncated CRW now fails at this boundary (it decoded as a flat frame on the old pin) while its header still reads. Only craft-crw moves; craft-ui/docpath/update/window stay on c5a1cbf. Co-Authored-By: Claude Opus 5.5 * Code review comment fix * Add some tests --------- Co-authored-by: Robert Woldberg <314159+rwoldberg@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 --- Cargo.lock | 10 + Cargo.toml | 2 + README.md | 2 +- ROADMAP.md | 11 +- apps/lightcraft-cli/src/main.rs | 7 +- assets/ATTRIBUTION.md | 2 + assets/camera-profiles/Canon_EOS_D60.json | 1 + clean-room/crw/10-chart-calibration.md | 97 ++++++++ clean-room/crw/README.md | 18 ++ clean-room/crw/logs/chart-captures.tsv | 39 ++++ crates/catalog/src/model.rs | 4 +- crates/engine/src/camera_preview/colour.rs | 1 + crates/engine/src/camera_profiles.rs | 6 +- crates/engine/src/files/probe.rs | 7 + crates/engine/src/files/tests.rs | 35 +++ crates/engine/src/lightroom_catalog.rs | 2 +- crates/engine/src/media/source.rs | 2 +- crates/raw/Cargo.toml | 2 + crates/raw/src/chart_table.rs | 21 ++ crates/raw/src/lib.rs | 5 +- crates/raw/src/preview/tests.rs | 4 +- crates/raw/src/spectral.rs | 69 ++++-- crates/raw/src/spectral_table.rs | 52 +++++ crates/raw/src/tests.rs | 7 +- crates/raw/src/vendor.rs | 1 + crates/raw/src/vendor/crw.rs | 255 +++++++++++++++++++++ crates/raw/tests/it/corpus.rs | 9 +- crates/raw/tests/it/crw_corpus.rs | 71 ++++++ crates/raw/tests/it/main.rs | 1 + crates/raw/tests/it/vendor_robust.rs | 44 +++- docs/camera-preview-colour.md | 6 +- docs/gaps.md | 10 +- docs/parity-checklist.md | 12 +- docs/raw-parity.md | 42 ++-- docs/raw/coverage.py | 13 +- docs/raw/coverage.tsv | 42 ++-- docs/raw/evidence.tsv | 7 + docs/target-app-parity.md | 8 +- tools/spectral_camera_table.py | 1 + xtask/src/raw_samples.rs | 57 +++++ 40 files changed, 893 insertions(+), 92 deletions(-) create mode 100644 assets/camera-profiles/Canon_EOS_D60.json create mode 100644 clean-room/crw/10-chart-calibration.md create mode 100644 clean-room/crw/README.md create mode 100644 clean-room/crw/logs/chart-captures.tsv create mode 100644 crates/raw/src/chart_table.rs create mode 100644 crates/raw/src/vendor/crw.rs create mode 100644 crates/raw/tests/it/crw_corpus.rs diff --git a/Cargo.lock b/Cargo.lock index 4115a749a..037865f87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1181,6 +1181,14 @@ dependencies = [ "libc", ] +[[package]] +name = "craft-crw" +version = "0.1.0" +source = "git+https://github.com/storytold/craft-libs?rev=f3b62ec61f45534392376492fc5ad33047375205#f3b62ec61f45534392376492fc5ad33047375205" +dependencies = [ + "thiserror 2.0.21", +] + [[package]] name = "craft-docpath" version = "0.1.0" @@ -3902,6 +3910,8 @@ dependencies = [ name = "lightcraft-raw" version = "0.18.0" dependencies = [ + "chrono", + "craft-crw", "dhat", "jpeg-encoder", "jxl-oxide", diff --git a/Cargo.toml b/Cargo.toml index eb363f491..4cfd43f3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,8 @@ repository = "https://github.com/storytold/lightcraft" [workspace.dependencies] craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", features = ["serde"] } craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7" } +# Canon CRW decoder shared with the other Craft apps +craft-crw = { git = "https://github.com/storytold/craft-libs", rev = "f3b62ec61f45534392376492fc5ad33047375205" } # craft-update: app-specific pretend versions apply to startup and manual checks. craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", features = ["egui"] } craft-window = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", features = ["egui"] } diff --git a/README.md b/README.md index e6ac8db59..1b64265b3 100644 --- a/README.md +++ b/README.md @@ -349,7 +349,7 @@ LightCraft is young and moving fast: **alpha**. **Where we honestly stand** (det casual use.** It's great for JPEG/DNG and most Sony / Panasonic / Fujifilm / Nikon / older-Canon raws on one machine; see [docs/raw-parity.md](docs/raw-parity.md) for your camera. - **The biggest gaps:** - - **camera colour calibration:** Sony, Nikon, Panasonic, Fujifilm and Canon CR3 raws have guarded estimates from their camera JPEGs, with built-in ILCA-99M2, ILCE-7CR, ILCE-7M4, ILCE-7RM5, X-H2S, X-T4, Nikon Z 6II, Z 8 and D5300 profiles; measured calibration is missing, and other raws or rejected fits retain a neutral matrix; + - **camera colour calibration:** Sony, Nikon, Panasonic, Fujifilm and Canon CR3 and CRW raws have guarded estimates from their camera JPEGs, with built-in Canon EOS D60, ILCA-99M2, ILCE-7CR, ILCE-7M4, ILCE-7RM5, X-H2S, X-T4, Nikon Z 6II, Z 8 and D5300 profiles; measured calibration covers one model so far (chart-measured matrices for the Canon EOS D60), and other raws or rejected fits retain a neutral matrix; - **compressed Olympus raws and unsupported CR3 variants:** these use embedded JPEG previews when present. Fujifilm lossless/lossy compressed RAF now decodes sensor data; [verification and existing-library reload instructions](docs/raf-compression.md); - **AI quality and model setup:** Subject and Sky selection are classical heuristics; Object/Describe use an optional SAM 3 model. AI Denoise supports Bayer RAW with separately installed weights, while X-Trans and diff --git a/ROADMAP.md b/ROADMAP.md index 0571abfb9..471496ecd 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **Stage: alpha** · next: beta, ~14 points and ~340–600 h away -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-24 · **Change:** minor (gap 9: CI runs the tests on Linux x64 since #2667; macOS, Windows and the GPU equivalence tests still don't; the lens profile database priority links its tracking issue #329; Print: Japanese photo and postcard paper sizes — L判, 2L判, KG判, はがき, 往復はがき, 六切, 四切, 名刺; Minolta MRW decoded, 6 bodies verified; RAW: 83% of Adobe's models decode, 38% verified after the camera-coverage sweep #1327; German catalog 91% → 97%: interface strings and What's New lines translated) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-24 · **Change:** minor (Canon CRW decoded clean-room, 6 bodies verified, 557 models verified; gap 9: CI runs the tests on Linux x64 since #2667; macOS, Windows and the GPU equivalence tests still don't; the lens profile database priority links its tracking issue #329; Print: Japanese photo and postcard paper sizes — L判, 2L判, KG判, はがき, 往復はがき, 六切, 四切, 名刺; Minolta MRW decoded, 6 bodies verified; RAW: 85% of Adobe's models decode, 39% verified after the camera-coverage sweep #1327; German catalog 91% → 97%: interface strings and What's New lines translated) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 One-page summary of how close LightCraft is to Adobe Lightroom. Agents: read this, then pick work from [docs/gaps.md](docs/gaps.md). The full assessment, method and evidence are in @@ -45,7 +45,7 @@ Hours are calibrated on this repo's PR history; see | Dimension | Ready | Breadth | Remaining | Doc | |---|---:|---:|---:|---| | Features | ~61% | ~77% (checklist 81.1%) | 600–1,100 h | [target-app-parity](docs/target-app-parity.md#features) | -| ↳ RAW cameras, colour, lens profiles | ~60% | 83% of 1,446 Adobe models decode, 38% verified | 255–480 h | [raw-parity](docs/raw-parity.md) | +| ↳ RAW cameras, colour, lens profiles | ~60% | 85% of 1,446 Adobe models decode, 39% verified | 255–480 h | [raw-parity](docs/raw-parity.md) | | UI/UX fidelity | ~70% | ~85% | 32–54 h | [ui-parity](docs/ui-parity.md) | | File formats (non-raw) | ~70% | ~85% | 30–55 h | [file-format-parity](docs/file-format-parity.md) | | Hardware | ~40% | ~50% | 40–80 h | [hardware-parity](docs/hardware-parity.md) | @@ -119,6 +119,12 @@ Ranked toward beta; detail in [docs/roadmap.md](docs/roadmap.md), gaps in [docs/ 六切 (203 × 254), 四切 (254 × 305) and 名刺 (55 × 91) — at their exact metric sizes. The Print dialog and `print.set` take their customary names, and names are matched case-, space- and punctuation-insensitively (`L判`, `l`, `L-size` are one paper). +- 2026-10-13: Canon CRW (the 2000–2004 CIFF bodies) decodes from sensor data: compression + derived clean-room against a black-box reference and frozen (`craft-libs crates/crw/clean-room/02-crw-format.md`); + EOS D30/D60/10D/300D and PowerShot G1/G6 verified pixel-exact on 28 of 29 corpus files (18 of + them the maintainer's D60 shots); as-shot white balance, black level, rotation and capture time + from the CIFF records; the PowerShot G5's anomalous coding stays preview-only. With the + camera-coverage sweep and MRW merged, 557 models verified; Canon bucket 95% → 98%. - 2026-10-14: Camera-coverage sweep verifies 30 more models on 40 CC0 files, with file and sensor hashes pinned (`docs/raw/coverage.py`: 515 → 545 verified). Eleven candidates excluded after rechecking identity, provenance and camera-image agreement. Phone and drone DNG tone differences remain tracked in #1277. @@ -194,6 +200,7 @@ Ranked toward beta; detail in [docs/roadmap.md](docs/roadmap.md), gaps in [docs/ | 2026-10-24 | patch | Alpha rationale: tests run in CI on Linux x64 (#2667) and FreeBSD nightly, not on macOS or Windows; ranked item 8 now reads "Tests in CI on macOS and Windows, and the GPU tests in CI"; percentages and hours unchanged | | 2026-10-22 | minor | Print: Japanese photo and postcard paper sizes (L判, 2L判, KG判, はがき, 往復はがき, 六切, 四切, 名刺) in the page setup, Print dialog and PDF output; headline numbers unchanged | | 2026-10-19 | minor | Indonesian interface (`id`): every plain and formats message, plus 69 command labels, locale, menu command and tests; What's New left in English; Localization ~55% → ~64% (mean of the 12 key languages), 8 of 12 present | +| 2026-10-13 | minor | Canon CRW decoded (clean-room): 6 bodies verified pixel-exact, G5 preview; 551 models verified after merging the coverage sweep | | 2026-10-12 | minor | Italian interface (`it`, #1192): 2,201 of 2,398 messages, locale, menu command and tests; headline numbers unchanged | | 2026-10-13 | minor | Sony ILCA-99M2 camera colour profile in the log; numbers unchanged | | 2026-10-14 | minor | Localization ~52% → ~55% (mean of the 12 key languages): de, es, fr, ja and uk catalogs filled | diff --git a/apps/lightcraft-cli/src/main.rs b/apps/lightcraft-cli/src/main.rs index dcb5280f6..01d6f23b3 100644 --- a/apps/lightcraft-cli/src/main.rs +++ b/apps/lightcraft-cli/src/main.rs @@ -106,7 +106,7 @@ USAGE: lightcraft-cli controls [--json] list every develop control id with its range lightcraft-cli calibrate [--max N] [--out DIR] FOLDERS/FILES… Fit a colour profile per camera model from raw files and their embedded camera JPEGs - (Sony ARW, Nikon NEF, Fujifilm RAF): up to N files spread over the folders (default 300; 0 = all), pooled per + (Sony ARW, Nikon NEF, Fujifilm RAF, Canon CRW): up to N files spread over the folders (default 300; 0 = all), pooled per model, written as .json to DIR (default: the profiles folder LightCraft reads, /camera-profiles, or $LIGHTCRAFT_CAMERA_PROFILES). Raws of a profiled model then take their colour from the profile and only their tone from their own JPEG. @@ -197,8 +197,9 @@ fn main() -> ExitCode { /// Raw files below `path` (or `path` itself), skipping hidden and NAS metadata folders. fn raw_files(path: &Path, out: &mut Vec, depth: usize) { - let is_raw = - |p: &Path| p.extension().and_then(|e| e.to_str()).is_some_and(|e| ["arw", "nef", "nrw", "raf"].iter().any(|x| e.eq_ignore_ascii_case(x))); + let is_raw = |p: &Path| { + p.extension().and_then(|e| e.to_str()).is_some_and(|e| ["arw", "nef", "nrw", "raf", "crw"].iter().any(|x| e.eq_ignore_ascii_case(x))) + }; if path.is_file() { if is_raw(path) { out.push(path.to_path_buf()); diff --git a/assets/ATTRIBUTION.md b/assets/ATTRIBUTION.md index d95e95b41..f0c7d15ca 100644 --- a/assets/ATTRIBUTION.md +++ b/assets/ATTRIBUTION.md @@ -19,11 +19,13 @@ covers a whole directory), or if a licence file referenced in the Licence column | `assets/camera-profiles/ILCA-99M2.json` | Sony ILCA-99M2 camera colour profile (matrix + hue/saturation/value table), built into LightCraft | LightCraft contributors; source photographs supplied by @PiMinusMax | original work: fitted by `lightcraft-cli calibrate` to 144 usable ILCA-99M2 ARWs (of 150, seven shooting days, Standard creative style) and their in-camera JPEGs; aggregate colour statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 | 2026-10-11 | none | | `assets/camera-profiles/ILCE-7CR.json` | Sony ILCE-7CR camera colour profile (matrix + hue/saturation/value table), built into LightCraft | LightCraft contributors; source photographs supplied by @AlexChambers | original work: fitted by `lightcraft-cli calibrate` to 125 usable ILCE-7CR ARWs (of 131, September 2025 to September 2026, Standard creative style) and their in-camera JPEGs; aggregate colour statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 | 2026-10-09 | none | | `assets/camera-profiles/X-H2S.json`, `assets/camera-profiles/X-T4.json` | Fujifilm camera colour profiles (matrix + hue/saturation/value table), built into LightCraft | LightCraft contributors; source photographs supplied by Sebastian Pfluegelmeier | original work: fitted by `lightcraft-cli calibrate` to 201 X-H2S and 545 X-T4 RAFs and their in-camera JPEGs; aggregate colour statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 | 2026-10-08 | none | +| `assets/camera-profiles/Canon_EOS_D60.json` | Canon EOS D60 camera colour profile (matrix + hue/saturation/value table), built into LightCraft | LightCraft contributors; source photographs supplied by Robert Woldberg | original work: fitted by `lightcraft-cli calibrate` to 1,424 usable EOS D60 CRWs (of 1,520, 2005 to 2014, Auto white balance) and their in-camera JPEGs; aggregate colour statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 | 2026-10-13 | none | | `assets/camera-profiles/NIKON_Z_6_2.json` | Nikon Z 6II camera colour profile (matrix + hue/saturation/value table), built into LightCraft | Michael Anderson (@endthestart) | original work: fitted by `lightcraft-cli calibrate` to 296 usable Z 6II NEFs of my own (of 299, 2024 to 2026, Standard and Auto picture controls; NIKKOR Z 20mm f/1.8 S, Z 24-70mm f/4 S and AF-S 70-200mm f/2.8G on the FTZ) and their in-camera JPEGs; aggregate colour statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 | 2026-10-13 | none | | `assets/camera-profiles/NIKON_Z_8.json` | Nikon Z 8 camera colour profile (matrix + hue/saturation/value table), built into LightCraft | Michael Anderson (@endthestart) | original work: fitted by `lightcraft-cli calibrate` to 261 usable Z 8 NEFs of my own (of 278, 2023 and 2026, nearly all Standard picture control, lossless 14-bit; NIKKOR Z 24-120mm f/4 S and AF-S 70-200mm f/2.8G on the FTZ) and their in-camera JPEGs; aggregate colour statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 | 2026-10-13 | none | | `assets/camera-profiles/NIKON_D5300.json` | Nikon D5300 camera colour profile (matrix + hue/saturation/value table), built into LightCraft | Michael Anderson (@endthestart) | original work: fitted by `lightcraft-cli calibrate` to 264 usable D5300 NEFs of my own (of 273, 2014 to 2017, mostly Standard picture control, lossy 12-bit, mostly the AF-S DX 35mm f/1.8G) and their in-camera JPEGs; aggregate colour statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 | 2026-10-13 | none | | `assets/camera-profiles/ILCE-7RM5.json` | Sony ILCE-7RM5 camera colour profile (matrix + hue/saturation/value table), built into LightCraft | Yule (Brian) Wang (@peaperfish) | original work: fitted by `lightcraft-cli calibrate` to 220 usable ILCE-7RM5 ARWs of my own (of 241, 2025 to 2026, Standard creative style; Sigma 24-70mm F2.8 DG DN II Art and Sony FE 200-600mm F5.6-6.3 G OSS) and their in-camera JPEGs; aggregate colour statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 | 2026-10-26 | none | | `crates/raw/src/spectral_table.rs` | Camera colour matrices (DNG ColorMatrix1/2 and ForwardMatrix1/2 for Standard Illuminant A and D65) for 52 camera models, and the model alias list | Derived by LightCraft contributors from rawtoaces-data (Academy Software Foundation): camera spectral sensitivities measured by Weta Digital (44 cameras), scitech (7) and Jonathon Irons at the National Physical Laboratory (Nikon D5100); training reflectances; CIE 1931 colour-matching functions (CIE, CC-BY-4.0, as shipped in rawtoaces-data) | https://github.com/AcademySoftwareFoundation/rawtoaces-data, tag v1.1.0, commit 5cfa53707b053594b2d0d48e8019fb56e6582d6c | Apache-2.0 (`LICENSE-APACHE`); the colour-matching functions CC-BY-4.0 | 2026-10-08 | generated by `tools/spectral_camera_table.py`: a 3×3 matrix per illuminant fitted to the spectral data (no measurement copied verbatim); aliases copied from `data/aliases.json`; no Adobe or third-party camera matrix | +| `crates/raw/src/chart_table.rs` | Chart-measured camera colour matrices (DNG ColorMatrix1/2 and ForwardMatrix1/2 for Standard Illuminant A and D50) for the Canon EOS D60 | LightCraft contributors; ColorChecker photographs taken by Robert Woldberg on the maintainer's EOS D60 | original work: fitted to our own CRWs of a ColorChecker Classic (post-November-2014 formulation) under a ~2,900 K tungsten bulb (11 frames) and direct sun (~4,850 K, 20 frames); reference colours: X-Rite's published L*a*b* (D50) for the chart, as packaged by [colour-science](https://github.com/colour-science/colour) (BSD-3-Clause), used for fitting only and not shipped; method in `clean-room/crw/10-chart-calibration.md` | MIT OR Apache-2.0 | 2026-10-25 | kept by hand: a 3×3 matrix per illuminant fitted on chromaticity with neutral held at D50; no Adobe or third-party camera matrix, no number from a GPL raw decoder | | `assets/camera-tone/sony-dro-off.json` | Sony global tone curve with the Dynamic Range Optimizer off (32 knots), built into LightCraft for ARWs shot with DRO on | LightCraft contributors; source photographs by raw.pixls.us contributors | original work: tone curve quantile-matched over the pooled pixel pairs of 12 CC0 raw.pixls.us ARWs (DRO off, Standard creative style; ILCE-3500, -5000, -6600, -7CR, -7M3 ×3, -7S, -9M2, ILCA-77M2, SLT-A57, NEX-F3) and their embedded camera JPEGs; the file lists each source URL and SHA-256 and how the curve was derived (see also `docs/camera-preview-colour.md`); aggregate tone statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 (sources CC0) | 2026-10-09 | none | | `crates/scenes/` (generated images) | Procedural demo photographs | LightCraft contributors | original work (generated at runtime, no source imagery) | MIT OR Apache-2.0 | 2026-09-30 | n/a | | `crates/ui-egui/src/icons.rs` | UI icons drawn as vector paths in code | LightCraft contributors | original work | MIT OR Apache-2.0 | 2026-09-30 | n/a | diff --git a/assets/camera-profiles/Canon_EOS_D60.json b/assets/camera-profiles/Canon_EOS_D60.json new file mode 100644 index 000000000..c5d0b08cf --- /dev/null +++ b/assets/camera-profiles/Canon_EOS_D60.json @@ -0,0 +1 @@ +{"version":1,"model":"Canon EOS D60","files":1424,"samples":5140322,"matrix":[[1.13012,-0.360203,0.234919],[-0.0483286,1.21254,-0.166746],[-0.0238897,-0.834382,1.86577]],"hue_sat":{"hue_divisions":72,"sat_divisions":5,"val_divisions":5,"data":[[0.0,1.0,1.0],[8.29241,1.23189,1.0],[6.24278,1.25426,1.0],[5.85238,1.20774,1.0],[3.78008,1.10233,1.0],[0.0,1.0,1.0],[7.49695,1.19919,1.0],[4.58151,1.21364,1.0],[3.60262,1.15474,1.0],[2.74306,1.07068,1.0],[0.0,1.0,1.0],[6.97455,1.16634,1.0],[3.67004,1.18558,1.0],[1.9923,1.12524,1.0],[0.245554,0.982395,1.0],[0.0,1.0,1.0],[6.42589,1.13738,1.0],[2.66266,1.14834,1.0],[0.226251,1.09372,1.0],[-2.23194,0.930707,1.0],[0.0,1.0,1.0],[6.09352,1.1248,1.0],[1.65699,1.12876,1.0],[-1.52059,1.07635,1.0],[-3.87766,0.926268,1.0],[0.0,1.0,1.0],[6.2146,1.10729,1.0],[1.13044,1.11602,1.0],[-2.4179,1.08135,1.0],[-4.69366,0.96409,1.0],[0.0,1.0,1.0],[6.73255,1.09443,1.0],[1.15502,1.09451,1.0],[-2.95449,1.05376,1.0],[-5.86916,0.954201,1.0],[0.0,1.0,1.0],[7.0143,1.0883,1.0],[1.25698,1.07945,1.0],[-3.3853,1.03275,1.0],[-6.46471,0.9465,1.0],[0.0,1.0,1.0],[7.44031,1.08414,1.0],[1.9515,1.06401,1.0],[-3.08108,1.00026,1.0],[-6.03475,0.917114,1.0],[0.0,1.0,1.0],[8.29527,1.07925,1.0],[2.86018,1.03974,1.0],[-2.25228,0.96454,1.0],[-4.98936,0.860481,1.0],[0.0,1.0,1.0],[9.14683,1.08392,1.0],[3.8335,1.01361,1.0],[-1.06889,0.935075,1.0],[-4.04616,0.821455,1.0],[0.0,1.0,1.0],[9.19479,1.10285,1.0],[4.68727,1.00463,1.0],[0.536387,0.911486,1.0],[-2.795,0.795914,1.0],[0.0,1.0,1.0],[8.67599,1.12913,1.0],[5.14131,1.01389,1.0],[1.77075,0.910094,1.0],[-0.965505,0.813756,1.0],[0.0,1.0,1.0],[7.95628,1.12916,1.0],[5.32373,1.0083,1.0],[2.91716,0.905281,1.0],[1.05035,0.834887,1.0],[0.0,1.0,1.0],[6.83544,1.13214,1.0],[4.95639,1.00769,1.0],[3.81032,0.903303,1.0],[2.8937,0.82685,1.0],[0.0,1.0,1.0],[5.89142,1.1369,1.0],[4.55603,1.00893,1.0],[4.04927,0.902735,1.0],[3.59749,0.8313,1.0],[0.0,1.0,1.0],[5.07797,1.14498,1.0],[4.14717,1.01358,1.0],[3.95316,0.909713,1.0],[4.03064,0.834461,1.0],[0.0,1.0,1.0],[3.93584,1.15444,1.0],[3.29081,1.02539,1.0],[3.28057,0.922408,1.0],[3.7798,0.840035,1.0],[0.0,1.0,1.0],[2.45431,1.17195,1.0],[2.1184,1.04565,1.0],[2.31804,0.94015,1.0],[2.84532,0.862496,1.0],[0.0,1.0,1.0],[0.914128,1.1919,1.0],[0.658994,1.06124,1.0],[1.06903,0.95232,1.0],[1.60007,0.890999,1.0],[0.0,1.0,1.0],[-0.709287,1.20875,1.0],[-0.970683,1.0793,1.0],[-0.571268,0.969075,1.0],[-0.0688885,0.893104,1.0],[0.0,1.0,1.0],[-2.56506,1.23369,1.0],[-2.97984,1.10163,1.0],[-2.79742,0.983848,1.0],[-2.01335,0.892256,1.0],[0.0,1.0,1.0],[-4.47609,1.26079,1.0],[-4.91221,1.12068,1.0],[-4.5471,1.00002,1.0],[-2.72325,0.931381,1.0],[0.0,1.0,1.0],[-6.07811,1.27839,1.0],[-6.49836,1.12664,1.0],[-5.47092,0.99826,1.0],[-1.83561,0.972328,1.0],[0.0,1.0,1.0],[-8.37879,1.29932,1.0],[-8.93662,1.13395,1.0],[-7.32093,1.02059,1.0],[-0.98037,0.994534,1.0],[0.0,1.0,1.0],[-10.2729,1.25707,1.0],[-11.0599,1.09129,1.0],[-9.32287,1.00616,1.0],[-1.21689,0.994275,1.0],[0.0,1.0,1.0],[-11.9931,1.22624,1.0],[-12.4979,1.05021,1.0],[-10.2083,0.969313,1.0],[-1.172,0.991243,1.0],[0.0,1.0,1.0],[-13.5442,1.18901,1.0],[-13.5809,1.00396,1.0],[-10.7308,0.93446,1.0],[-1.1717,0.98724,1.0],[0.0,1.0,1.0],[-14.1202,1.16045,1.0],[-14.1451,0.982771,1.0],[-10.5188,0.911345,1.0],[-1.10721,0.983353,1.0],[0.0,1.0,1.0],[-14.5581,1.16209,1.0],[-13.8126,0.986909,1.0],[-8.30721,0.90898,1.0],[-0.995621,0.97911,1.0],[0.0,1.0,1.0],[-14.4013,1.1613,1.0],[-13.2596,0.979666,1.0],[-8.95671,0.890826,1.0],[-2.10024,0.967003,1.0],[0.0,1.0,1.0],[-15.1417,1.16939,1.0],[-13.2246,0.977241,1.0],[-9.7885,0.871629,1.0],[-2.78003,0.957243,1.0],[0.0,1.0,1.0],[-16.0188,1.15745,1.0],[-14.0864,0.974714,1.0],[-10.4183,0.871201,1.0],[-2.37086,0.964302,1.0],[0.0,1.0,1.0],[-16.2546,1.14581,1.0],[-13.912,0.987045,1.0],[-8.19204,0.901434,1.0],[-1.1791,0.982516,1.0],[0.0,1.0,1.0],[-16.3253,1.14274,1.0],[-13.1974,0.996884,1.0],[-7.37707,0.909881,1.0],[-0.796766,0.988333,1.0],[0.0,1.0,1.0],[-17.4833,1.13741,1.0],[-13.1058,0.977386,1.0],[-5.85941,0.919339,1.0],[-0.352557,0.99447,1.0],[0.0,1.0,1.0],[-17.5235,1.10838,1.0],[-13.7099,0.974525,1.0],[-5.10075,0.930882,1.0],[-0.176921,0.995738,1.0],[0.0,1.0,1.0],[-17.5967,1.05115,1.0],[-13.6048,0.938325,1.0],[-5.27843,0.910606,1.0],[-0.167657,0.994507,1.0],[0.0,1.0,1.0],[-17.4696,0.996524,1.0],[-13.115,0.908347,1.0],[-4.95967,0.899867,1.0],[-0.174963,0.992617,1.0],[0.0,1.0,1.0],[-16.5982,0.955417,1.0],[-11.0254,0.879437,1.0],[-3.31212,0.863198,1.0],[-0.175046,0.977174,1.0],[0.0,1.0,1.0],[-15.5952,0.929258,1.0],[-8.31794,0.86823,1.0],[-0.528212,0.846517,1.0],[0.380461,0.923401,1.0],[0.0,1.0,1.0],[-14.4398,0.910204,1.0],[-8.05514,0.876305,1.0],[-0.57323,0.868118,1.0],[0.470409,0.948896,1.0],[0.0,1.0,1.0],[-13.0585,0.905534,1.0],[-7.90773,0.889057,1.0],[-0.976436,0.905932,1.0],[0.542039,0.983099,1.0],[0.0,1.0,1.0],[-11.876,0.908437,1.0],[-7.06937,0.903432,1.0],[-0.539651,0.916127,1.0],[0.779286,0.983595,1.0],[0.0,1.0,1.0],[-10.0702,0.929172,1.0],[-5.39408,0.937425,1.0],[-0.2061,0.920171,1.0],[1.17455,0.972633,1.0],[0.0,1.0,1.0],[-8.19665,0.965734,1.0],[-3.79008,0.995854,1.0],[0.735503,0.977993,1.0],[2.47582,0.981799,1.0],[0.0,1.0,1.0],[-6.81135,0.995992,1.0],[-2.14588,1.04353,1.0],[2.66441,1.05104,1.0],[3.71856,1.02802,1.0],[0.0,1.0,1.0],[-6.09325,1.01737,1.0],[-1.16553,1.07761,1.0],[3.22399,1.09269,1.0],[3.60392,1.06285,1.0],[0.0,1.0,1.0],[-5.67983,1.00236,1.0],[-1.59948,1.08043,1.0],[1.772,1.1166,1.0],[1.50147,1.05603,1.0],[0.0,1.0,1.0],[-4.93455,0.948406,1.0],[-2.02886,1.03546,1.0],[0.440492,1.10114,1.0],[0.44623,1.03065,1.0],[0.0,1.0,1.0],[-4.05699,0.914275,1.0],[-1.90483,0.982593,1.0],[-0.604556,1.0474,1.0],[-0.0390689,1.00817,1.0],[0.0,1.0,1.0],[-3.08093,0.903908,1.0],[-1.98686,1.00641,1.0],[-1.94429,1.08972,1.0],[-0.352449,1.01615,1.0],[0.0,1.0,1.0],[-2.73077,0.91307,1.0],[-2.44839,1.03758,1.0],[-2.63185,1.10403,1.0],[-0.49201,1.01805,1.0],[0.0,1.0,1.0],[-2.38881,0.934744,1.0],[-2.57865,1.0511,1.0],[-3.04775,1.10985,1.0],[-0.586879,1.01897,1.0],[0.0,1.0,1.0],[-1.27415,0.944475,1.0],[-1.86686,1.05548,1.0],[-2.23726,1.08935,1.0],[-0.228978,1.00814,1.0],[0.0,1.0,1.0],[-0.101355,0.961184,1.0],[-0.934238,1.06758,1.0],[-1.13534,1.06534,1.0],[-0.0561888,1.00339,1.0],[0.0,1.0,1.0],[0.598274,0.984285,1.0],[-1.06223,1.07669,1.0],[-0.945264,1.05627,1.0],[0.00985705,1.00242,1.0],[0.0,1.0,1.0],[2.04951,0.986024,1.0],[-0.267861,1.07427,1.0],[-0.73815,1.05684,1.0],[0.00245078,1.00274,1.0],[0.0,1.0,1.0],[2.08883,1.01304,1.0],[0.151963,1.08506,1.0],[-0.241507,1.05953,1.0],[0.00193501,1.00292,1.0],[0.0,1.0,1.0],[1.83224,1.04552,1.0],[0.122697,1.08099,1.0],[-0.443897,1.03705,1.0],[0.00525946,1.00164,1.0],[0.0,1.0,1.0],[2.955,1.06865,1.0],[2.40293,1.10632,1.0],[1.21487,1.03978,1.0],[0.116631,1.00179,1.0],[0.0,1.0,1.0],[4.34913,1.07975,1.0],[3.83869,1.11183,1.0],[2.71629,1.04953,1.0],[0.273693,1.00284,1.0],[0.0,1.0,1.0],[4.74482,1.08215,1.0],[4.72065,1.10431,1.0],[3.77532,1.04093,1.0],[0.493566,1.00304,1.0],[0.0,1.0,1.0],[5.25777,1.07169,1.0],[5.32614,1.09942,1.0],[5.06916,1.04884,1.0],[0.993296,1.00533,1.0],[0.0,1.0,1.0],[5.88163,1.07401,1.0],[6.07869,1.10391,1.0],[5.83284,1.05936,1.0],[1.42654,1.0069,1.0],[0.0,1.0,1.0],[6.90312,1.08396,1.0],[6.94645,1.10811,1.0],[6.19738,1.06095,1.0],[1.82831,1.00748,1.0],[0.0,1.0,1.0],[7.44771,1.07582,1.0],[6.65837,1.09613,1.0],[6.27648,1.05517,1.0],[2.12399,1.0076,1.0],[0.0,1.0,1.0],[7.31406,1.06821,1.0],[6.34729,1.08126,1.0],[6.19796,1.04675,1.0],[2.07171,1.00504,1.0],[0.0,1.0,1.0],[8.19827,1.08887,1.0],[6.47189,1.0915,1.0],[6.167,1.04997,1.0],[1.80019,1.00482,1.0],[0.0,1.0,1.0],[9.41006,1.12278,1.0],[8.03371,1.13711,1.0],[7.38094,1.09474,1.0],[2.10397,1.01352,1.0],[0.0,1.0,1.0],[9.65947,1.16005,1.0],[8.6464,1.18328,1.0],[8.37811,1.14689,1.0],[2.88382,1.03414,1.0],[0.0,1.0,1.0],[9.04497,1.20326,1.0],[7.86209,1.22425,1.0],[8.18499,1.19391,1.0],[4.23855,1.08138,1.0],[0.0,1.0,1.0],[6.45737,1.22953,1.0],[5.83506,1.24649,1.0],[6.04989,1.19909,1.0],[5.62245,1.14814,1.0],[0.0,1.0,1.0],[5.52039,1.20235,1.0],[3.96584,1.21201,1.0],[3.63405,1.1421,1.0],[3.12661,1.07917,1.0],[0.0,1.0,1.0],[4.86834,1.16794,1.0],[2.70659,1.19194,1.0],[1.48174,1.10776,1.0],[-0.12763,0.979172,1.0],[0.0,1.0,1.0],[4.31491,1.1454,1.0],[1.70696,1.16806,1.0],[-0.477717,1.10476,1.0],[-2.56979,0.966709,1.0],[0.0,1.0,1.0],[3.83196,1.12956,1.0],[0.728633,1.14769,1.0],[-1.93909,1.1025,1.0],[-4.26165,0.977716,1.0],[0.0,1.0,1.0],[3.82294,1.11173,1.0],[0.155235,1.12776,1.0],[-2.84614,1.09368,1.0],[-5.03384,1.00496,1.0],[0.0,1.0,1.0],[4.33193,1.09533,1.0],[0.0691161,1.10426,1.0],[-3.42321,1.06232,1.0],[-6.33353,0.975445,1.0],[0.0,1.0,1.0],[4.90996,1.08498,1.0],[0.292905,1.08392,1.0],[-3.73154,1.03591,1.0],[-6.76969,0.956434,1.0],[0.0,1.0,1.0],[5.22443,1.07254,1.0],[0.918505,1.06375,1.0],[-3.25514,1.01021,1.0],[-6.27866,0.936284,1.0],[0.0,1.0,1.0],[5.71992,1.05618,1.0],[1.63799,1.04045,1.0],[-2.16294,0.981528,1.0],[-4.85659,0.891864,1.0],[0.0,1.0,1.0],[6.24525,1.05283,1.0],[2.15152,1.01757,1.0],[-1.23229,0.950332,1.0],[-4.04973,0.837257,1.0],[0.0,1.0,1.0],[6.48731,1.06455,1.0],[2.95282,1.00357,1.0],[-0.13344,0.918371,1.0],[-3.26727,0.795304,1.0],[0.0,1.0,1.0],[6.43304,1.08659,1.0],[3.60322,1.00417,1.0],[1.1116,0.908953,1.0],[-1.17154,0.803847,1.0],[0.0,1.0,1.0],[6.08314,1.08502,1.0],[3.98195,0.996781,1.0],[2.13832,0.903213,1.0],[0.601765,0.815392,1.0],[0.0,1.0,1.0],[5.38634,1.08306,1.0],[3.96838,0.99318,1.0],[3.08024,0.902371,1.0],[2.33057,0.816005,1.0],[0.0,1.0,1.0],[4.70801,1.08804,1.0],[3.6389,0.992919,1.0],[3.14261,0.902246,1.0],[2.80019,0.823245,1.0],[0.0,1.0,1.0],[3.9373,1.09765,1.0],[3.15718,0.9982,1.0],[2.90997,0.906114,1.0],[2.98089,0.826701,1.0],[0.0,1.0,1.0],[2.93423,1.10659,1.0],[2.43397,1.00625,1.0],[2.3901,0.914244,1.0],[2.81925,0.833616,1.0],[0.0,1.0,1.0],[1.75735,1.11926,1.0],[1.4578,1.0158,1.0],[1.62288,0.922963,1.0],[2.14895,0.847463,1.0],[0.0,1.0,1.0],[0.467054,1.13504,1.0],[0.269281,1.02753,1.0],[0.580391,0.936603,1.0],[1.10631,0.873195,1.0],[0.0,1.0,1.0],[-0.931699,1.15249,1.0],[-1.16467,1.04976,1.0],[-0.864673,0.957196,1.0],[-0.288895,0.875365,1.0],[0.0,1.0,1.0],[-2.62064,1.18076,1.0],[-2.93156,1.08253,1.0],[-2.60906,0.974414,1.0],[-1.91468,0.861015,1.0],[0.0,1.0,1.0],[-4.33073,1.2089,1.0],[-4.69807,1.10591,1.0],[-4.24579,0.967835,1.0],[-3.39933,0.845043,1.0],[0.0,1.0,1.0],[-6.04384,1.23424,1.0],[-6.39864,1.11931,1.0],[-5.6838,0.948674,1.0],[-4.78474,0.83504,1.0],[0.0,1.0,1.0],[-8.10562,1.2573,1.0],[-8.63972,1.12984,1.0],[-8.00675,0.959973,1.0],[-5.52745,0.869055,1.0],[0.0,1.0,1.0],[-10.0076,1.2248,1.0],[-10.654,1.09686,1.0],[-10.1383,0.961011,1.0],[-5.74516,0.90956,1.0],[0.0,1.0,1.0],[-11.3225,1.18168,1.0],[-11.8645,1.05002,1.0],[-11.0067,0.921538,1.0],[-5.53332,0.907742,1.0],[0.0,1.0,1.0],[-12.3989,1.15066,1.0],[-12.6428,1.00856,1.0],[-11.5574,0.889656,1.0],[-4.47558,0.915526,1.0],[0.0,1.0,1.0],[-13.0847,1.13038,1.0],[-13.0968,0.980698,1.0],[-11.5589,0.845667,1.0],[-6.06387,0.865438,1.0],[0.0,1.0,1.0],[-13.2421,1.12309,1.0],[-12.6262,0.959127,1.0],[-10.2846,0.813384,1.0],[-7.36689,0.82578,1.0],[0.0,1.0,1.0],[-12.7795,1.11392,1.0],[-11.566,0.934485,1.0],[-10.4237,0.792341,1.0],[-9.31722,0.804662,1.0],[0.0,1.0,1.0],[-12.6769,1.10245,1.0],[-10.7995,0.914334,1.0],[-11.4312,0.776062,1.0],[-11.6932,0.779856,1.0],[0.0,1.0,1.0],[-12.8136,1.09092,1.0],[-10.714,0.913308,1.0],[-11.8132,0.780711,1.0],[-11.3633,0.79714,1.0],[0.0,1.0,1.0],[-12.9893,1.09121,1.0],[-11.3863,0.942534,1.0],[-10.8763,0.810494,1.0],[-6.38202,0.877641,1.0],[0.0,1.0,1.0],[-12.9058,1.09063,1.0],[-10.6735,0.956993,1.0],[-9.53278,0.832118,1.0],[-2.99673,0.934202,1.0],[0.0,1.0,1.0],[-13.1017,1.0857,1.0],[-9.73913,0.958602,1.0],[-7.38057,0.856439,1.0],[-1.46525,0.955774,1.0],[0.0,1.0,1.0],[-13.4686,1.06833,1.0],[-10.4883,0.951945,1.0],[-7.18973,0.852331,1.0],[-1.33411,0.939021,1.0],[0.0,1.0,1.0],[-13.5707,1.01915,1.0],[-10.3816,0.919458,1.0],[-6.11786,0.821507,1.0],[-1.47186,0.898169,1.0],[0.0,1.0,1.0],[-13.7086,0.974874,1.0],[-9.45884,0.897761,1.0],[-3.68031,0.820758,1.0],[-0.858757,0.879703,1.0],[0.0,1.0,1.0],[-13.1972,0.944081,1.0],[-7.74305,0.885551,1.0],[-1.5697,0.837698,1.0],[0.165719,0.871773,1.0],[0.0,1.0,1.0],[-11.8739,0.929549,1.0],[-4.3559,0.882459,1.0],[1.06891,0.844377,1.0],[1.67435,0.846731,1.0],[0.0,1.0,1.0],[-10.6799,0.924428,1.0],[-4.06136,0.906987,1.0],[1.52214,0.86951,1.0],[2.40795,0.868667,1.0],[0.0,1.0,1.0],[-9.5752,0.931471,1.0],[-4.20953,0.935923,1.0],[2.29417,0.909633,1.0],[4.29582,0.903491,1.0],[0.0,1.0,1.0],[-8.7303,0.938297,1.0],[-3.73491,0.954183,1.0],[2.87868,0.934722,1.0],[5.31219,0.92055,1.0],[0.0,1.0,1.0],[-7.52354,0.951013,1.0],[-2.979,0.977569,1.0],[3.17883,0.9614,1.0],[6.27338,0.941609,1.0],[0.0,1.0,1.0],[-5.61763,0.983581,1.0],[-1.54163,1.02516,1.0],[3.60014,1.01671,1.0],[7.19602,0.984591,1.0],[0.0,1.0,1.0],[-4.22545,1.01662,1.0],[0.140206,1.06957,1.0],[4.17481,1.06953,1.0],[6.1315,1.04731,1.0],[0.0,1.0,1.0],[-3.78345,1.03385,1.0],[1.05038,1.0995,1.0],[4.1045,1.10205,1.0],[5.21719,1.09109,1.0],[0.0,1.0,1.0],[-3.80075,1.02051,1.0],[0.352142,1.11178,1.0],[2.74513,1.13811,1.0],[3.42962,1.13077,1.0],[0.0,1.0,1.0],[-3.47037,0.970882,1.0],[-0.599396,1.09168,1.0],[1.13462,1.16315,1.0],[1.31077,1.15075,1.0],[0.0,1.0,1.0],[-2.70432,0.954215,1.0],[-1.59434,1.03591,1.0],[-1.19418,1.11839,1.0],[-1.10938,1.09685,1.0],[0.0,1.0,1.0],[-2.14495,0.960338,1.0],[-1.94064,1.05783,1.0],[-2.53886,1.14425,1.0],[-2.2552,1.1214,1.0],[0.0,1.0,1.0],[-2.25488,0.963303,1.0],[-2.55399,1.09327,1.0],[-3.56191,1.1645,1.0],[-3.51432,1.1431,1.0],[0.0,1.0,1.0],[-2.49663,0.984053,1.0],[-2.98283,1.11472,1.0],[-4.15624,1.17788,1.0],[-3.96794,1.14962,1.0],[0.0,1.0,1.0],[-1.63829,0.988561,1.0],[-2.32553,1.11066,1.0],[-3.29053,1.16474,1.0],[-2.03215,1.10047,1.0],[0.0,1.0,1.0],[-0.591005,0.997344,1.0],[-1.73238,1.11203,1.0],[-2.54204,1.14303,1.0],[-0.840567,1.05701,1.0],[0.0,1.0,1.0],[0.106161,1.00732,1.0],[-1.64833,1.11011,1.0],[-2.63137,1.12835,1.0],[0.000417006,1.03827,1.0],[0.0,1.0,1.0],[1.18957,1.01322,1.0],[-0.709562,1.11028,1.0],[-1.54299,1.13823,1.0],[0.341615,1.04609,1.0],[0.0,1.0,1.0],[1.26161,1.0302,1.0],[-0.130317,1.11515,1.0],[-0.760396,1.14654,1.0],[-0.377267,1.05055,1.0],[0.0,1.0,1.0],[1.34901,1.0613,1.0],[0.856557,1.11955,1.0],[1.15235,1.12433,1.0],[0.632698,1.03424,1.0],[0.0,1.0,1.0],[1.80693,1.09089,1.0],[2.41036,1.14014,1.0],[3.95601,1.11942,1.0],[1.61517,1.03991,1.0],[0.0,1.0,1.0],[2.8421,1.08909,1.0],[3.73284,1.13423,1.0],[6.61016,1.11057,1.0],[3.69954,1.04818,1.0],[0.0,1.0,1.0],[3.31884,1.08192,1.0],[4.40875,1.12051,1.0],[8.17687,1.08499,1.0],[6.46302,1.04773,1.0],[0.0,1.0,1.0],[4.07688,1.07618,1.0],[5.35406,1.11161,1.0],[9.80713,1.07613,1.0],[8.93626,1.04866,1.0],[0.0,1.0,1.0],[4.81023,1.08203,1.0],[6.14305,1.11396,1.0],[10.4516,1.07015,1.0],[10.3146,1.04211,1.0],[0.0,1.0,1.0],[5.60871,1.09277,1.0],[6.62807,1.11447,1.0],[10.8092,1.05775,1.0],[11.1733,1.03124,1.0],[0.0,1.0,1.0],[5.91878,1.09833,1.0],[6.90828,1.11233,1.0],[11.1597,1.04582,1.0],[11.7704,1.01867,1.0],[0.0,1.0,1.0],[6.18104,1.09833,1.0],[7.08648,1.11,1.0],[11.1675,1.03715,1.0],[11.8516,1.00733,1.0],[0.0,1.0,1.0],[7.00814,1.10579,1.0],[7.29881,1.11849,1.0],[10.523,1.04893,1.0],[11.0842,1.00497,1.0],[0.0,1.0,1.0],[7.8262,1.12744,1.0],[7.96552,1.14585,1.0],[10.0515,1.09656,1.0],[10.6741,1.0343,1.0],[0.0,1.0,1.0],[8.07519,1.159,1.0],[8.29985,1.17598,1.0],[9.87129,1.14577,1.0],[10.3288,1.09887,1.0],[0.0,1.0,1.0],[7.37598,1.19494,1.0],[7.56967,1.21752,1.0],[8.94372,1.19239,1.0],[9.06854,1.16421,1.0],[0.0,1.0,1.0],[3.68338,1.08531,1.0],[4.38971,1.12863,1.0],[4.73027,1.11585,1.0],[4.54314,1.09538,1.0],[0.0,1.0,1.0],[2.94821,1.06131,1.0],[2.97489,1.1026,1.0],[3.03927,1.08071,1.0],[2.63529,1.04305,1.0],[0.0,1.0,1.0],[2.57351,1.03554,1.0],[1.89741,1.07813,1.0],[1.2687,1.03162,1.0],[0.226707,0.961594,1.0],[0.0,1.0,1.0],[2.36462,1.02063,1.0],[1.33289,1.06092,1.0],[-0.269328,1.02454,1.0],[-1.87251,0.951141,1.0],[0.0,1.0,1.0],[2.14722,1.01376,1.0],[0.916703,1.05471,1.0],[-1.16994,1.03138,1.0],[-3.47602,0.955454,1.0],[0.0,1.0,1.0],[2.1531,1.00339,1.0],[0.599137,1.04111,1.0],[-1.42547,1.02755,1.0],[-3.53331,0.964634,1.0],[0.0,1.0,1.0],[2.56896,0.981761,1.0],[0.379733,1.02159,1.0],[-1.87368,1.00098,1.0],[-3.73593,0.940611,1.0],[0.0,1.0,1.0],[3.34281,0.956887,1.0],[0.562745,0.990625,1.0],[-2.00992,0.972546,1.0],[-4.03612,0.916146,1.0],[0.0,1.0,1.0],[3.70201,0.932035,1.0],[1.03031,0.958944,1.0],[-1.43057,0.936191,1.0],[-3.48989,0.878391,1.0],[0.0,1.0,1.0],[4.01699,0.909684,1.0],[1.47515,0.932379,1.0],[-0.684378,0.900747,1.0],[-2.17011,0.831407,1.0],[0.0,1.0,1.0],[4.20769,0.903911,1.0],[1.58117,0.914789,1.0],[-0.204523,0.875306,1.0],[-1.74439,0.781114,1.0],[0.0,1.0,1.0],[4.26209,0.91174,1.0],[1.95006,0.901154,1.0],[0.146487,0.838802,1.0],[-1.50293,0.73827,1.0],[0.0,1.0,1.0],[4.14572,0.928888,1.0],[2.17201,0.892381,1.0],[0.720994,0.815512,1.0],[-0.557276,0.737431,1.0],[0.0,1.0,1.0],[3.83083,0.930908,1.0],[2.22797,0.883923,1.0],[1.1286,0.809516,1.0],[0.313424,0.742822,1.0],[0.0,1.0,1.0],[3.22442,0.931112,1.0],[2.17251,0.884451,1.0],[1.58433,0.81271,1.0],[1.11012,0.743416,1.0],[0.0,1.0,1.0],[2.65218,0.937936,1.0],[1.85422,0.890641,1.0],[1.5073,0.823901,1.0],[1.23189,0.757209,1.0],[0.0,1.0,1.0],[2.1485,0.950147,1.0],[1.47256,0.898305,1.0],[1.10932,0.834142,1.0],[0.929015,0.771719,1.0],[0.0,1.0,1.0],[1.46417,0.966505,1.0],[0.892508,0.907199,1.0],[0.677007,0.84201,1.0],[0.782876,0.784345,1.0],[0.0,1.0,1.0],[0.671556,0.975774,1.0],[0.255539,0.912658,1.0],[0.306344,0.849958,1.0],[0.581909,0.791799,1.0],[0.0,1.0,1.0],[-0.232216,0.988441,1.0],[-0.603134,0.921144,1.0],[-0.460117,0.862507,1.0],[-0.107263,0.814403,1.0],[0.0,1.0,1.0],[-1.34957,1.00596,1.0],[-1.76213,0.939168,1.0],[-1.61403,0.87008,1.0],[-1.17209,0.80566,1.0],[0.0,1.0,1.0],[-2.72758,1.02624,1.0],[-3.03897,0.956089,1.0],[-2.57537,0.849434,1.0],[-2.04494,0.774777,1.0],[0.0,1.0,1.0],[-3.99996,1.04342,1.0],[-4.18515,0.958758,1.0],[-3.31966,0.816493,1.0],[-3.00571,0.765035,1.0],[0.0,1.0,1.0],[-5.35434,1.06437,1.0],[-5.34074,0.958166,1.0],[-4.18959,0.805923,1.0],[-4.129,0.769208,1.0],[0.0,1.0,1.0],[-6.98613,1.08028,1.0],[-6.77624,0.961994,1.0],[-5.50517,0.815479,1.0],[-5.09191,0.787004,1.0],[0.0,1.0,1.0],[-8.27537,1.04965,1.0],[-8.09453,0.944809,1.0],[-6.8017,0.806179,1.0],[-5.18158,0.814935,1.0],[0.0,1.0,1.0],[-9.04833,1.00809,1.0],[-8.7844,0.909025,1.0],[-6.93936,0.791674,1.0],[-4.03433,0.845474,1.0],[0.0,1.0,1.0],[-9.43181,0.98407,1.0],[-9.09251,0.890722,1.0],[-7.0911,0.804798,1.0],[-2.45967,0.906586,1.0],[0.0,1.0,1.0],[-9.45648,0.962051,1.0],[-8.89996,0.870864,1.0],[-7.59422,0.790714,1.0],[-3.89725,0.874013,1.0],[0.0,1.0,1.0],[-9.1551,0.952512,1.0],[-7.99781,0.856023,1.0],[-6.56106,0.777842,1.0],[-4.52665,0.84456,1.0],[0.0,1.0,1.0],[-8.63291,0.939113,1.0],[-6.66401,0.83436,1.0],[-6.12567,0.759722,1.0],[-5.8122,0.805741,1.0],[0.0,1.0,1.0],[-8.07018,0.924489,1.0],[-5.72503,0.825439,1.0],[-6.55667,0.755673,1.0],[-7.60626,0.766193,1.0],[0.0,1.0,1.0],[-7.71402,0.914648,1.0],[-5.55096,0.827839,1.0],[-6.1182,0.767114,1.0],[-6.56151,0.798221,1.0],[0.0,1.0,1.0],[-7.73036,0.918568,1.0],[-6.34513,0.845252,1.0],[-5.84789,0.791564,1.0],[-3.21326,0.898598,1.0],[0.0,1.0,1.0],[-7.60352,0.921108,1.0],[-5.75152,0.857972,1.0],[-4.2223,0.808735,1.0],[-0.777749,0.951292,1.0],[0.0,1.0,1.0],[-7.59302,0.921877,1.0],[-5.07845,0.866725,1.0],[-1.91403,0.825463,1.0],[0.313248,0.951153,1.0],[0.0,1.0,1.0],[-7.95059,0.913408,1.0],[-5.31852,0.857203,1.0],[-2.09618,0.808571,1.0],[-0.36965,0.939769,1.0],[0.0,1.0,1.0],[-8.04501,0.884124,1.0],[-4.00064,0.815801,1.0],[-0.552985,0.758992,1.0],[-0.290257,0.849646,1.0],[0.0,1.0,1.0],[-8.31768,0.866341,1.0],[-3.33357,0.813293,1.0],[0.157563,0.769212,1.0],[0.343628,0.817142,1.0],[0.0,1.0,1.0],[-8.30951,0.858061,1.0],[-3.22756,0.836749,1.0],[0.621114,0.809609,1.0],[0.978062,0.84691,1.0],[0.0,1.0,1.0],[-7.63892,0.855642,1.0],[-1.78416,0.856328,1.0],[1.71414,0.839884,1.0],[2.00564,0.852348,1.0],[0.0,1.0,1.0],[-6.98256,0.855364,1.0],[-1.90937,0.87686,1.0],[2.08805,0.862082,1.0],[2.68608,0.867942,1.0],[0.0,1.0,1.0],[-6.22476,0.86076,1.0],[-1.76697,0.892517,1.0],[2.75581,0.869282,1.0],[3.64017,0.861139,1.0],[0.0,1.0,1.0],[-5.58461,0.866272,1.0],[-1.16949,0.911281,1.0],[3.51652,0.901504,1.0],[4.73018,0.89384,1.0],[0.0,1.0,1.0],[-4.63899,0.874735,1.0],[-0.898044,0.924947,1.0],[3.92745,0.924477,1.0],[5.54917,0.914903,1.0],[0.0,1.0,1.0],[-3.15305,0.905493,1.0],[-0.412746,0.967576,1.0],[3.28203,0.983881,1.0],[6.10256,0.959483,1.0],[0.0,1.0,1.0],[-2.32765,0.933308,1.0],[0.518624,1.01081,1.0],[3.19822,1.03526,1.0],[4.94074,1.02466,1.0],[0.0,1.0,1.0],[-1.8871,0.94285,1.0],[1.33377,1.04564,1.0],[3.12132,1.06374,1.0],[3.97632,1.05853,1.0],[0.0,1.0,1.0],[-1.8999,0.93121,1.0],[0.969048,1.05804,1.0],[2.46869,1.0949,1.0],[2.79413,1.08503,1.0],[0.0,1.0,1.0],[-1.97726,0.894039,1.0],[0.0822964,1.02906,1.0],[1.23635,1.12734,1.0],[1.19178,1.11584,1.0],[0.0,1.0,1.0],[-1.32251,0.907478,1.0],[-0.732392,0.976431,1.0],[-0.769014,1.0757,1.0],[-0.833215,1.0751,1.0],[0.0,1.0,1.0],[-1.40729,0.925752,1.0],[-1.21162,1.00368,1.0],[-1.73984,1.09073,1.0],[-1.89858,1.09279,1.0],[0.0,1.0,1.0],[-1.81031,0.926354,1.0],[-1.71811,1.04366,1.0],[-2.54731,1.12158,1.0],[-2.84006,1.12445,1.0],[0.0,1.0,1.0],[-1.82418,0.940786,1.0],[-2.10252,1.0643,1.0],[-3.06982,1.13859,1.0],[-2.85592,1.12341,1.0],[0.0,1.0,1.0],[-1.11867,0.946146,1.0],[-1.631,1.0569,1.0],[-2.12014,1.12189,1.0],[-1.0948,1.08358,1.0],[0.0,1.0,1.0],[-0.160143,0.949616,1.0],[-1.1426,1.04576,1.0],[-1.82633,1.09789,1.0],[-0.888133,1.0567,1.0],[0.0,1.0,1.0],[0.276782,0.953904,1.0],[-0.878393,1.03139,1.0],[-1.52091,1.07285,1.0],[0.0984381,1.02982,1.0],[0.0,1.0,1.0],[0.600961,0.962362,1.0],[-0.465934,1.02903,1.0],[-1.45788,1.08222,1.0],[-0.654393,1.03748,1.0],[0.0,1.0,1.0],[0.501916,0.97851,1.0],[-0.560871,1.03624,1.0],[-1.53518,1.09508,1.0],[-1.14336,1.04042,1.0],[0.0,1.0,1.0],[0.284415,1.00531,1.0],[-0.279351,1.04784,1.0],[-0.0110145,1.08033,1.0],[0.0939488,1.03245,1.0],[0.0,1.0,1.0],[0.121363,1.02361,1.0],[0.19487,1.0638,1.0],[2.03154,1.08677,1.0],[1.16244,1.04173,1.0],[0.0,1.0,1.0],[0.478025,1.01306,1.0],[1.20917,1.05049,1.0],[3.97838,1.06899,1.0],[2.70163,1.0445,1.0],[0.0,1.0,1.0],[0.660392,1.00367,1.0],[1.64103,1.03741,1.0],[5.53692,1.05185,1.0],[5.13526,1.04052,1.0],[0.0,1.0,1.0],[1.20646,0.99897,1.0],[2.73304,1.03354,1.0],[6.67188,1.04331,1.0],[6.79749,1.0354,1.0],[0.0,1.0,1.0],[1.7191,1.00525,1.0],[3.8326,1.0395,1.0],[7.61762,1.03572,1.0],[7.7932,1.02698,1.0],[0.0,1.0,1.0],[2.40321,1.01122,1.0],[4.16662,1.0354,1.0],[7.94702,1.02485,1.0],[8.40517,1.0166,1.0],[0.0,1.0,1.0],[2.70014,1.01829,1.0],[4.68291,1.03542,1.0],[8.21642,1.01253,1.0],[8.89084,1.00328,1.0],[0.0,1.0,1.0],[2.79955,1.0167,1.0],[4.95658,1.03421,1.0],[8.31755,1.00221,1.0],[8.94719,0.992123,1.0],[0.0,1.0,1.0],[3.60667,1.02372,1.0],[5.13614,1.04037,1.0],[7.91937,0.999985,1.0],[8.71567,0.984859,1.0],[0.0,1.0,1.0],[4.30411,1.03045,1.0],[5.29894,1.0538,1.0],[7.10184,1.02433,1.0],[8.33043,0.991609,1.0],[0.0,1.0,1.0],[4.39741,1.04119,1.0],[5.5808,1.06804,1.0],[7.20042,1.05636,1.0],[8.38559,1.03146,1.0],[0.0,1.0,1.0],[4.06608,1.06103,1.0],[5.5966,1.10289,1.0],[7.05727,1.1009,1.0],[7.5007,1.09021,1.0],[0.0,1.0,1.0],[1.80714,0.862232,1.0],[2.57435,0.96193,1.0],[2.69156,0.993122,1.0],[2.35223,0.985233,1.0],[0.0,1.0,1.0],[1.66212,0.841201,1.0],[2.13529,0.939288,1.0],[2.14828,0.968632,1.0],[1.92574,0.942215,1.0],[0.0,1.0,1.0],[1.73126,0.829669,1.0],[1.61753,0.886622,1.0],[1.64417,0.899945,1.0],[1.92043,0.873032,1.0],[0.0,1.0,1.0],[1.84088,0.822886,1.0],[1.78624,0.866582,1.0],[1.58812,0.902691,1.0],[1.72774,0.874351,1.0],[0.0,1.0,1.0],[2.04344,0.818351,1.0],[2.08533,0.85138,1.0],[2.25818,0.856605,1.0],[0.768175,0.865097,1.0],[0.0,1.0,1.0],[2.19654,0.804209,1.0],[2.03698,0.840288,1.0],[2.44899,0.839691,1.0],[1.61015,0.838124,1.0],[0.0,1.0,1.0],[2.67957,0.772528,1.0],[1.99182,0.820889,1.0],[1.53375,0.851328,1.0],[0.782918,0.852971,1.0],[0.0,1.0,1.0],[3.39254,0.72266,1.0],[2.26683,0.776186,1.0],[1.65109,0.811055,1.0],[1.27951,0.811585,1.0],[0.0,1.0,1.0],[3.79197,0.69998,1.0],[2.48564,0.75141,1.0],[1.26637,0.790085,1.0],[0.9583,0.776468,1.0],[0.0,1.0,1.0],[3.75679,0.707483,1.0],[2.39234,0.749819,1.0],[1.26065,0.777822,1.0],[1.23194,0.743045,1.0],[0.0,1.0,1.0],[3.68285,0.721718,1.0],[1.99529,0.763244,1.0],[1.28744,0.759051,1.0],[1.41618,0.697818,1.0],[0.0,1.0,1.0],[3.55451,0.734759,1.0],[1.93232,0.74931,1.0],[1.21702,0.70825,1.0],[0.993047,0.662823,1.0],[0.0,1.0,1.0],[3.21034,0.749757,1.0],[1.6257,0.732309,1.0],[0.955119,0.685328,1.0],[0.454537,0.660033,1.0],[0.0,1.0,1.0],[2.49107,0.756638,1.0],[1.22866,0.732036,1.0],[0.687176,0.687395,1.0],[0.32915,0.666885,1.0],[0.0,1.0,1.0],[1.75801,0.760552,1.0],[0.927357,0.736289,1.0],[0.468821,0.686011,1.0],[0.365004,0.663181,1.0],[0.0,1.0,1.0],[1.01564,0.767492,1.0],[0.524494,0.745355,1.0],[0.246019,0.695487,1.0],[0.0703193,0.667101,1.0],[0.0,1.0,1.0],[0.490832,0.779727,1.0],[0.115268,0.755236,1.0],[-0.20347,0.71657,1.0],[-0.486117,0.686975,1.0],[0.0,1.0,1.0],[-0.0574273,0.792251,1.0],[-0.416134,0.767277,1.0],[-0.647007,0.73117,1.0],[-0.770428,0.700426,1.0],[0.0,1.0,1.0],[-0.757924,0.80457,1.0],[-0.908808,0.777163,1.0],[-0.940719,0.737997,1.0],[-0.979114,0.706816,1.0],[0.0,1.0,1.0],[-1.60192,0.81767,1.0],[-1.58307,0.78978,1.0],[-1.41954,0.758826,1.0],[-1.2767,0.733849,1.0],[0.0,1.0,1.0],[-2.18358,0.829051,1.0],[-2.4119,0.800002,1.0],[-2.18405,0.768265,1.0],[-1.71947,0.734251,1.0],[0.0,1.0,1.0],[-3.24689,0.84405,1.0],[-3.17718,0.806693,1.0],[-2.51133,0.748202,1.0],[-2.14749,0.712745,1.0],[0.0,1.0,1.0],[-4.11575,0.853054,1.0],[-3.5877,0.79966,1.0],[-2.7728,0.734278,1.0],[-2.57275,0.711773,1.0],[0.0,1.0,1.0],[-4.94706,0.861959,1.0],[-4.15591,0.798372,1.0],[-3.18771,0.74273,1.0],[-2.96267,0.750222,1.0],[0.0,1.0,1.0],[-5.64839,0.865694,1.0],[-4.71823,0.797056,1.0],[-3.65506,0.758248,1.0],[-2.48082,0.83958,1.0],[0.0,1.0,1.0],[-6.82874,0.843687,1.0],[-5.52966,0.782379,1.0],[-3.96698,0.747026,1.0],[-1.75201,0.867586,1.0],[0.0,1.0,1.0],[-7.52278,0.806458,1.0],[-6.40324,0.765556,1.0],[-3.86767,0.771171,1.0],[-0.90734,0.928701,1.0],[0.0,1.0,1.0],[-7.36489,0.780066,1.0],[-6.4182,0.748836,1.0],[-3.42398,0.813074,1.0],[-0.199601,0.985392,1.0],[0.0,1.0,1.0],[-7.06767,0.769338,1.0],[-6.11923,0.736279,1.0],[-2.80801,0.789549,1.0],[-0.173737,0.981272,1.0],[0.0,1.0,1.0],[-6.98719,0.761335,1.0],[-5.60103,0.72847,1.0],[-2.32286,0.763431,1.0],[-0.206355,0.972705,1.0],[0.0,1.0,1.0],[-7.19227,0.753611,1.0],[-5.11719,0.725632,1.0],[-2.22272,0.739503,1.0],[-0.394778,0.950964,1.0],[0.0,1.0,1.0],[-6.78646,0.751239,1.0],[-4.81011,0.730982,1.0],[-2.25718,0.749349,1.0],[-0.564096,0.931121,1.0],[0.0,1.0,1.0],[-6.14383,0.743367,1.0],[-4.74665,0.733849,1.0],[-2.10399,0.761636,1.0],[-0.20149,0.940535,1.0],[0.0,1.0,1.0],[-6.01653,0.73872,1.0],[-5.00084,0.736255,1.0],[-1.85464,0.824293,1.0],[-0.0843571,0.987363,1.0],[0.0,1.0,1.0],[-6.06608,0.741158,1.0],[-4.75158,0.734706,1.0],[-0.418244,0.816448,1.0],[0.0738295,0.989608,1.0],[0.0,1.0,1.0],[-6.1513,0.745672,1.0],[-3.24496,0.716605,1.0],[1.29638,0.744335,1.0],[0.263942,0.980017,1.0],[0.0,1.0,1.0],[-5.44565,0.731198,1.0],[-0.495605,0.681438,1.0],[1.99363,0.683864,1.0],[0.288026,0.954665,1.0],[0.0,1.0,1.0],[-4.20029,0.703329,1.0],[0.747765,0.678139,1.0],[1.68012,0.683816,1.0],[0.691829,0.85211,1.0],[0.0,1.0,1.0],[-5.59689,0.701871,1.0],[-0.30376,0.700292,1.0],[1.02081,0.71003,1.0],[0.57095,0.83763,1.0],[0.0,1.0,1.0],[-7.00129,0.695238,1.0],[-2.80194,0.723937,1.0],[0.26524,0.753078,1.0],[0.290073,0.905559,1.0],[0.0,1.0,1.0],[-6.76136,0.694525,1.0],[-2.51014,0.743115,1.0],[0.669437,0.788888,1.0],[0.509218,0.905682,1.0],[0.0,1.0,1.0],[-5.76779,0.696908,1.0],[-1.74829,0.747574,1.0],[0.64116,0.772713,1.0],[0.705368,0.846673,1.0],[0.0,1.0,1.0],[-5.23204,0.696789,1.0],[-1.38688,0.751786,1.0],[1.04421,0.780137,1.0],[1.29981,0.816658,1.0],[0.0,1.0,1.0],[-4.80973,0.701173,1.0],[-1.49155,0.765564,1.0],[1.48641,0.825689,1.0],[1.8829,0.856319,1.0],[0.0,1.0,1.0],[-3.98407,0.711682,1.0],[-1.55931,0.772302,1.0],[1.48491,0.840475,1.0],[2.16826,0.863431,1.0],[0.0,1.0,1.0],[-2.96718,0.729664,1.0],[-1.45556,0.785341,1.0],[0.658648,0.856963,1.0],[1.93779,0.887031,1.0],[0.0,1.0,1.0],[-2.41039,0.739762,1.0],[-0.895293,0.822435,1.0],[0.524603,0.90053,1.0],[1.74269,0.941794,1.0],[0.0,1.0,1.0],[-2.04427,0.746417,1.0],[-0.347337,0.8665,1.0],[0.82932,0.938689,1.0],[1.65256,0.970226,1.0],[0.0,1.0,1.0],[-1.42486,0.757715,1.0],[-0.000267224,0.869143,1.0],[1.31949,0.982208,1.0],[1.55689,0.996697,1.0],[0.0,1.0,1.0],[-0.848812,0.734082,1.0],[0.0114361,0.804928,1.0],[0.395743,0.977226,1.0],[0.206862,1.0105,1.0],[0.0,1.0,1.0],[-0.0803961,0.736191,1.0],[-0.127857,0.812698,1.0],[-0.476354,0.950943,1.0],[-0.570623,0.991673,1.0],[0.0,1.0,1.0],[-0.556175,0.764348,1.0],[-0.429436,0.867389,1.0],[-0.628538,0.969159,1.0],[-0.51369,0.99939,1.0],[0.0,1.0,1.0],[-0.688107,0.797483,1.0],[-0.666583,0.932097,1.0],[-1.0186,1.00479,1.0],[-0.822168,1.01685,1.0],[0.0,1.0,1.0],[-0.289872,0.809868,1.0],[-0.812629,0.938043,1.0],[-1.45898,1.03583,1.0],[-0.795228,1.02689,1.0],[0.0,1.0,1.0],[0.133498,0.814392,1.0],[-0.69441,0.921026,1.0],[-1.77182,1.0374,1.0],[-0.82135,1.01854,1.0],[0.0,1.0,1.0],[0.844359,0.810612,1.0],[-0.33815,0.899531,1.0],[-1.57377,0.998981,1.0],[-0.548347,1.00352,1.0],[0.0,1.0,1.0],[1.04141,0.820516,1.0],[-0.185627,0.89876,1.0],[-1.04561,0.998034,1.0],[-0.309933,1.00361,1.0],[0.0,1.0,1.0],[0.35477,0.839484,1.0],[-0.86123,0.910014,1.0],[-1.75893,1.00497,1.0],[-0.433708,1.004,1.0],[0.0,1.0,1.0],[0.0667524,0.854932,1.0],[-1.03097,0.913445,1.0],[-0.78467,1.00204,1.0],[-0.0517986,1.00241,1.0],[0.0,1.0,1.0],[0.306667,0.883005,1.0],[-0.578596,0.925849,1.0],[-0.588702,1.00264,1.0],[-0.0686681,1.00274,1.0],[0.0,1.0,1.0],[-0.873341,0.893873,1.0],[-1.25905,0.933786,1.0],[0.200431,1.00432,1.0],[0.146782,1.00325,1.0],[0.0,1.0,1.0],[-0.919506,0.88949,1.0],[-1.0804,0.926157,1.0],[0.651799,0.990702,1.0],[0.233688,1.00193,1.0],[0.0,1.0,1.0],[-0.858609,0.878817,1.0],[-0.768885,0.920294,1.0],[2.10662,0.995863,1.0],[1.42343,1.00568,1.0],[0.0,1.0,1.0],[-1.03588,0.867559,1.0],[-0.0930043,0.921591,1.0],[2.87823,0.991301,1.0],[2.47573,1.00262,1.0],[0.0,1.0,1.0],[-0.531328,0.869942,1.0],[0.819605,0.926484,1.0],[3.2611,0.992084,1.0],[3.02627,1.00026,1.0],[0.0,1.0,1.0],[0.206652,0.873395,1.0],[1.18531,0.927259,1.0],[3.77087,0.988596,1.0],[3.92548,0.996875,1.0],[0.0,1.0,1.0],[0.80955,0.867401,1.0],[1.55215,0.934242,1.0],[3.98773,0.981408,1.0],[4.51641,0.988098,1.0],[0.0,1.0,1.0],[1.01735,0.856204,1.0],[2.01152,0.92966,1.0],[4.08073,0.968357,1.0],[4.46964,0.974428,1.0],[0.0,1.0,1.0],[1.71918,0.859677,1.0],[2.24653,0.928946,1.0],[3.7394,0.958318,1.0],[4.40235,0.966491,1.0],[0.0,1.0,1.0],[1.99937,0.861921,1.0],[2.94313,0.954898,1.0],[3.44516,0.961366,1.0],[3.95849,0.960993,1.0],[0.0,1.0,1.0],[1.43869,0.84255,1.0],[3.05803,0.956206,1.0],[3.76042,0.97624,1.0],[3.41303,0.967518,1.0],[0.0,1.0,1.0],[1.48723,0.844662,1.0],[2.64368,0.926592,1.0],[3.26043,0.958098,1.0],[2.76276,0.967112,1.0],[0.0,1.0,1.0],[0.0855342,0.700066,1.0],[-1.9237,0.708879,1.0],[-2.78949,0.718593,1.0],[-2.34799,0.790328,1.0],[0.0,1.0,1.0],[1.05637,0.680044,1.0],[0.82885,0.686378,1.0],[0.844709,0.71335,1.0],[2.41146,0.753575,1.0],[0.0,1.0,1.0],[1.95218,0.664727,1.0],[2.4404,0.687557,1.0],[5.31906,0.758155,1.0],[6.49187,0.789878,1.0],[0.0,1.0,1.0],[3.03417,0.634878,1.0],[3.89064,0.641277,1.0],[6.88372,0.750011,1.0],[7.766,0.784769,1.0],[0.0,1.0,1.0],[3.66006,0.6177,1.0],[5.65008,0.60974,1.0],[11.9503,0.585009,1.0],[12.933,0.633394,1.0],[0.0,1.0,1.0],[3.76755,0.61906,1.0],[6.18175,0.614775,1.0],[11.8745,0.582563,1.0],[13.9328,0.578158,1.0],[0.0,1.0,1.0],[4.19052,0.610445,1.0],[5.39299,0.620521,1.0],[8.41895,0.635778,1.0],[9.78372,0.659453,1.0],[0.0,1.0,1.0],[4.80778,0.583936,1.0],[5.18478,0.602629,1.0],[7.88045,0.627236,1.0],[9.8036,0.645707,1.0],[0.0,1.0,1.0],[4.83847,0.572987,1.0],[4.8768,0.589749,1.0],[6.32586,0.611775,1.0],[8.69008,0.609703,1.0],[0.0,1.0,1.0],[4.34984,0.596177,1.0],[4.07081,0.60799,1.0],[5.14482,0.622675,1.0],[7.80759,0.60006,1.0],[0.0,1.0,1.0],[4.02009,0.618601,1.0],[3.07622,0.647895,1.0],[4.2903,0.634044,1.0],[6.77634,0.598976,1.0],[0.0,1.0,1.0],[3.7232,0.629488,1.0],[2.61576,0.644909,1.0],[3.12738,0.622353,1.0],[4.00518,0.607437,1.0],[0.0,1.0,1.0],[2.80417,0.648748,1.0],[1.58566,0.650354,1.0],[1.78385,0.633291,1.0],[1.58031,0.629079,1.0],[0.0,1.0,1.0],[1.51484,0.66637,1.0],[0.584125,0.662797,1.0],[0.680099,0.642402,1.0],[0.429029,0.646365,1.0],[0.0,1.0,1.0],[0.332074,0.666594,1.0],[-0.00972684,0.665929,1.0],[-0.0428133,0.643195,1.0],[0.0283791,0.645326,1.0],[0.0,1.0,1.0],[-0.761362,0.665741,1.0],[-0.530909,0.673999,1.0],[-0.326823,0.646967,1.0],[-0.403237,0.641567,1.0],[0.0,1.0,1.0],[-1.15064,0.681169,1.0],[-0.974953,0.685852,1.0],[-1.01162,0.667857,1.0],[-1.18996,0.659085,1.0],[0.0,1.0,1.0],[-1.93238,0.705908,1.0],[-1.34358,0.701093,1.0],[-1.44249,0.682112,1.0],[-1.55416,0.678645,1.0],[0.0,1.0,1.0],[-2.7408,0.725915,1.0],[-1.85278,0.706956,1.0],[-1.76106,0.686999,1.0],[-1.93208,0.691768,1.0],[0.0,1.0,1.0],[-3.45851,0.746801,1.0],[-2.31604,0.726976,1.0],[-1.94127,0.711508,1.0],[-1.52566,0.760718,1.0],[0.0,1.0,1.0],[-2.83512,0.754194,1.0],[-2.28629,0.735078,1.0],[-2.08969,0.717271,1.0],[-1.5865,0.754468,1.0],[0.0,1.0,1.0],[-3.61196,0.775247,1.0],[-2.42242,0.745545,1.0],[-1.78826,0.732347,1.0],[-1.49177,0.768371,1.0],[0.0,1.0,1.0],[-3.72532,0.781387,1.0],[-1.86171,0.751674,1.0],[-1.34387,0.744707,1.0],[-1.1189,0.806285,1.0],[0.0,1.0,1.0],[-4.26267,0.793536,1.0],[-1.99383,0.771457,1.0],[-0.890465,0.794552,1.0],[-0.292623,0.936899,1.0],[0.0,1.0,1.0],[-4.21751,0.806125,1.0],[-2.02534,0.789889,1.0],[-0.581378,0.850651,1.0],[-0.0253143,0.984482,1.0],[0.0,1.0,1.0],[-5.08533,0.812077,1.0],[-1.6133,0.820185,1.0],[-0.259989,0.910701,1.0],[-0.0586713,0.989555,1.0],[0.0,1.0,1.0],[-5.07063,0.779668,1.0],[-1.63652,0.835421,1.0],[-0.0567593,0.95639,1.0],[-0.0142179,0.997651,1.0],[0.0,1.0,1.0],[-4.77975,0.739459,1.0],[-2.19599,0.820505,1.0],[-0.208329,0.968352,1.0],[-0.00224836,0.999526,1.0],[0.0,1.0,1.0],[-4.45449,0.737173,1.0],[-1.44549,0.802374,1.0],[0.0161031,0.953461,1.0],[0.00316718,0.999053,1.0],[0.0,1.0,1.0],[-3.89432,0.719224,1.0],[-0.601417,0.772428,1.0],[0.220387,0.931693,1.0],[0.00538454,0.998363,1.0],[0.0,1.0,1.0],[-4.77552,0.710353,1.0],[-1.46004,0.791953,1.0],[0.00282181,0.940541,1.0],[0.000668639,0.998309,1.0],[0.0,1.0,1.0],[-4.34901,0.711378,1.0],[-1.32515,0.797679,1.0],[0.0227745,0.950445,1.0],[0.00183562,0.998598,1.0],[0.0,1.0,1.0],[-5.32497,0.702745,1.0],[-2.23197,0.811713,1.0],[-0.106095,0.962117,1.0],[0.000774817,0.998715,1.0],[0.0,1.0,1.0],[-5.04468,0.691325,1.0],[-2.17082,0.824815,1.0],[-0.0538191,0.980496,1.0],[0.00180051,0.999645,1.0],[0.0,1.0,1.0],[-4.63088,0.679205,1.0],[-1.77209,0.796756,1.0],[0.192721,0.948099,1.0],[0.0122462,0.997987,1.0],[0.0,1.0,1.0],[-4.6158,0.675196,1.0],[0.306872,0.706387,1.0],[1.1143,0.85192,1.0],[0.0428345,0.994454,1.0],[0.0,1.0,1.0],[-3.34765,0.652181,1.0],[1.49239,0.644148,1.0],[1.7052,0.745098,1.0],[0.0750734,0.98784,1.0],[0.0,1.0,1.0],[-2.32353,0.629431,1.0],[1.49431,0.634761,1.0],[1.7081,0.682639,1.0],[0.162418,0.965372,1.0],[0.0,1.0,1.0],[-4.97365,0.624568,1.0],[0.203427,0.659424,1.0],[0.935012,0.729539,1.0],[0.0809343,0.975177,1.0],[0.0,1.0,1.0],[-6.58448,0.605647,1.0],[-2.85875,0.669595,1.0],[-0.0947285,0.815362,1.0],[0.014458,0.98534,1.0],[0.0,1.0,1.0],[-6.64917,0.59994,1.0],[-3.2762,0.655727,1.0],[-0.302513,0.847914,1.0],[0.0151315,0.990293,1.0],[0.0,1.0,1.0],[-5.77614,0.599647,1.0],[-1.84747,0.660109,1.0],[-0.0325114,0.732173,1.0],[0.0552754,0.928256,1.0],[0.0,1.0,1.0],[-5.31798,0.599723,1.0],[-1.50551,0.673713,1.0],[0.142132,0.732059,1.0],[0.127878,0.892765,1.0],[0.0,1.0,1.0],[-4.66564,0.59949,1.0],[-2.17359,0.659343,1.0],[-1.6744,0.719999,1.0],[-1.9808,0.849478,1.0],[0.0,1.0,1.0],[-4.10527,0.591371,1.0],[-2.93901,0.634836,1.0],[-3.54904,0.699533,1.0],[-4.82776,0.752715,1.0],[0.0,1.0,1.0],[-3.45506,0.585623,1.0],[-3.19151,0.600036,1.0],[-3.88164,0.65431,1.0],[-4.2901,0.759322,1.0],[0.0,1.0,1.0],[-3.26092,0.587924,1.0],[-3.97341,0.61119,1.0],[-5.02722,0.631102,1.0],[-4.64551,0.706705,1.0],[0.0,1.0,1.0],[-3.28463,0.585785,1.0],[-5.4167,0.605524,1.0],[-6.11549,0.618712,1.0],[-5.03231,0.683674,1.0],[0.0,1.0,1.0],[-1.2888,0.599668,1.0],[-2.93207,0.630276,1.0],[-3.90318,0.703056,1.0],[-1.95753,0.852358,1.0],[0.0,1.0,1.0],[0.584038,0.591575,1.0],[0.119869,0.635598,1.0],[-1.10532,0.805416,1.0],[-0.837605,0.930957,1.0],[0.0,1.0,1.0],[1.41123,0.612198,1.0],[0.525198,0.697601,1.0],[-0.084616,0.855263,1.0],[0.281798,0.959624,1.0],[0.0,1.0,1.0],[0.90464,0.64252,1.0],[0.204828,0.749899,1.0],[0.132037,0.89252,1.0],[0.95185,0.964429,1.0],[0.0,1.0,1.0],[1.5538,0.669261,1.0],[0.0878431,0.814625,1.0],[-0.54754,0.936992,1.0],[-0.0352334,0.982044,1.0],[0.0,1.0,1.0],[1.81385,0.68885,1.0],[0.626367,0.781457,1.0],[-0.231532,0.955163,1.0],[-0.0360823,0.998455,1.0],[0.0,1.0,1.0],[1.43438,0.719251,1.0],[0.701568,0.802556,1.0],[0.0235685,0.97262,1.0],[-0.00891454,0.999663,1.0],[0.0,1.0,1.0],[1.80045,0.732676,1.0],[0.903036,0.855378,1.0],[-0.206474,0.975566,1.0],[-0.0264155,0.997856,1.0],[0.0,1.0,1.0],[2.06827,0.75276,1.0],[1.79507,0.872885,1.0],[-0.0970723,0.981708,1.0],[-0.0387983,0.998746,1.0],[0.0,1.0,1.0],[0.625386,0.80525,1.0],[-0.212439,0.918002,1.0],[-0.206799,0.995518,1.0],[-0.0169261,0.999958,1.0],[0.0,1.0,1.0],[0.225662,0.796163,1.0],[-0.242337,0.914862,1.0],[-0.0619648,0.996788,1.0],[-0.00307031,0.999985,1.0],[0.0,1.0,1.0],[1.45977,0.858321,1.0],[0.783917,0.950581,1.0],[0.0389577,0.997767,1.0],[-0.00036868,0.999989,1.0],[0.0,1.0,1.0],[-0.00585478,0.849167,1.0],[-0.000863034,0.907505,1.0],[-0.136772,0.983267,1.0],[-0.00745626,0.9995,1.0],[0.0,1.0,1.0],[-0.823514,0.838048,1.0],[-0.73422,0.880373,1.0],[-0.53514,0.956036,1.0],[-0.0607511,0.996663,1.0],[0.0,1.0,1.0],[-0.209549,0.819245,1.0],[-0.768871,0.877592,1.0],[-0.525896,0.962725,1.0],[-0.0652702,0.997194,1.0],[0.0,1.0,1.0],[-1.65692,0.796907,1.0],[-3.78341,0.78821,1.0],[-2.9151,0.877646,1.0],[-0.381098,0.986643,1.0],[0.0,1.0,1.0],[-1.3262,0.784397,1.0],[-4.37347,0.773366,1.0],[-3.20877,0.87257,1.0],[-0.275568,0.988733,1.0],[0.0,1.0,1.0],[-0.321925,0.816727,1.0],[-2.08024,0.844289,1.0],[-1.06573,0.928401,1.0],[-0.0586658,0.993194,1.0],[0.0,1.0,1.0],[-0.0661225,0.800429,1.0],[-1.7824,0.859799,1.0],[-0.690445,0.941361,1.0],[-0.00968662,0.993002,1.0],[0.0,1.0,1.0],[-0.0252066,0.772944,1.0],[-2.45319,0.823328,1.0],[-1.82779,0.912295,1.0],[-0.277157,0.984417,1.0],[0.0,1.0,1.0],[-1.65043,0.727703,1.0],[-4.769,0.737336,1.0],[-3.77497,0.85187,1.0],[-0.758286,0.971868,1.0],[0.0,1.0,1.0],[-3.82536,0.671065,1.0],[-6.80853,0.671393,1.0],[-4.66756,0.804813,1.0],[-0.962573,0.95621,1.0],[0.0,1.0,1.0],[-2.82235,0.663717,1.0],[-5.84894,0.654476,1.0],[-4.11993,0.781383,1.0],[-0.814404,0.957541,1.0],[0.0,1.0,1.0],[-1.37721,0.681457,1.0],[-3.57748,0.708946,1.0],[-2.7702,0.786335,1.0],[-1.02829,0.919022,1.0]],"srgb_value":true}} diff --git a/clean-room/crw/10-chart-calibration.md b/clean-room/crw/10-chart-calibration.md new file mode 100644 index 000000000..8816a427d --- /dev/null +++ b/clean-room/crw/10-chart-calibration.md @@ -0,0 +1,97 @@ +# EOS D60 colour matrices from a ColorChecker (2026-10-25) + +The Canon EOS D60 has no published spectral sensitivities, so it is not in `spectral_table.rs`. +These are its DNG-style matrices (`ColorMatrix1/2`, `ForwardMatrix1/2`), fitted to our own +photographs of a ColorChecker Classic under two lights. They are in +`crates/raw/src/chart_table.rs`. Measurements come from the maintainer's own captures (names, +sizes and SHA-256 in `logs/chart-captures.tsv`; no image content recorded) and the chart's +published reference colours (craft-libs `crates/crw/clean-room/sources.md`). Scripts and measurements are local-only, in +`plan/crw/chart/`. + +## Captures + +| Set | Frames used | Light | Notes | +|---|---:|---|---| +| `SunlightColorCard/` | 20 of 22 | Direct afternoon sun, ISO 100 | Calibrite ColorChecker Classic (50th-anniversary edition, post-2014 formulation) leaning on a wall, filling most of the frame. Auto and Daylight white balance, a shutter bracket. Two frames dropped: a white-patch sample ≥ 3900 (clip ≈ 4000 at ISO 100, craft-libs `crates/crw/clean-room/04-color.md` §2). | +| `TungstonColorChart/` 2696–2706 | 11 | One household tungsten bulb, ISO 100 | Chart flat on a wall, a shutter bracket. Brightest white patch 2657 of ~4000. | +| `TungstonColorChart/` 2688–2691 | not used | Same bulb, 8 minutes earlier | Steady within the set, but its neutral reads ~3,040 K against ~2,900 K for the later set: the light changed (window light or bulb warm-up). The later, warmer and better-exposed set is used. | + +All 37 frames decode sample-exact against the black-box reference (`craft-crw`'s digest-pinned sample test, formerly `crw_oracle.rs` here). + +An earlier set on a ColorChecker-layout clone (Pixel Perfect "24 Standard Color Calibration +Chart", 2026-10-15) was not used. The maker publishes no reference values, and its grey patches +are not neutral: against an 18 % grey board they read R/G 0.88–0.94 in shade and B/G up to +1.29 under warm LED light, the darker patches drifting most. That is the sign of greys printed +from coloured inks. + +## Measurement + +- Patches are located on the embedded JPEG (four corner patch centres, bilinear grid), tracked + per frame by phase correlation, and checked visually on drawn boxes. The tungsten-set chart + was upside down in the first four frames; patch order is mapped accordingly. +- Each patch is measured in a central box of about 40 % of its width in the linear raw (black + from the far masked border per CFA site; R, mean of both G, B per 2×2 site). Uniformity + across the box (3×3 sub-blocks): ≤ 4 % in sun, ≤ 7 % under the bulb (light falloff across + the closer chart). +- Frames are normalised by the four middle neutrals (patches 20–23), averaged, and white + balanced on those neutrals. White balance (R, B multipliers, G = 1): sun 2.362, 1.298; bulb + 1.353, 2.213. The neutrals agree on it to ±2 %. + +## Fit + +- The reference is X-Rite's published L\*a\*b\* (D50) for charts made after November 2014. +- The model is a 3×3 matrix from white-balanced camera RGB to XYZ (D50), with neutral (1, 1, 1) + held exactly at the engine's D50 white (`lightcraft_color::D50`): the third column is D50 + minus the first two. That is a DNG `ForwardMatrix`. +- **Chromaticity only.** Each prediction is scaled to the patch's reference luminance before + the CIEDE2000 residual. In the sun the chart warped as it heated: the grey row's brightness + slope swung from +4 % to −7 % across the 70 s of shooting, in time order, and the per-patch + brightness needed was not smooth across the chart. A fit including lightness would have + absorbed that into the matrix. Chromaticity still determines the whole 3×3 up to scale, + which the white constraint fixes. The cost is that luminance of saturated blues and purples + rests on the model, not the measurement. +- `ColorMatrix` per light = diag(1 / multipliers) · ForwardMatrix⁻¹ · Bradford(light → D50), + scaled so the light's white maps to camera values with maximum 1. By construction it maps + the filed illuminant's white to the measured camera neutral. + +| Light | CIEDE2000 on the 24 patches | Leaving each patch out | Worst | +|---|---|---|---| +| Sun (filed as D50) | mean 1.70, median 1.46 | mean 1.97 | white 4.5 | +| Bulb (filed as A) | mean 2.42, median 1.79 | mean 2.74 | bluish green 6.7 | + +- One matrix does not serve both lights: the sun's matrix on the bulb's chart gives mean 3.6 + (cyan 9.0), the bulb's on the sun's 3.4 (red 7.7). Hence the dual-illuminant entry. +- The neutral row reads a\* within ±0.4 for the middle greys; white and the darkest patches + are off by up to 2 (white patch sheen, flare). + +## Which illuminants + +The two lights' colour temperatures come from the camera's own white-balance presets +(ColorBalance slots, craft-libs `crates/crw/clean-room/04-color.md` §3) at the temperatures Canon's manuals give them (Daylight +5200 K, Tungsten 3200 K, Fluorescent 4000 K), interpolated in mired on ln(R/B): sun ~4,850 K, +bulb ~2,900 K. They are filed under the nearest DNG illuminants the engine knows, **D50 +(5003 K) and Standard Illuminant A (2856 K)**, so `Camera` now carries its own illuminant +pair (spectral rows: A and D65). + +## Checks in the app + +- `spectral::tests::the_chart_measured_eos_d60_reads_its_presets_in_kelvin`: the camera's + presets, which the fit never saw, read through the matrices as Tungsten 3,264 K, Daylight + 5,603 K, Cloudy 6,613 K, Flash 7,085 K, near the locus. Canon's nominal values are 3200, 5200, + 6000 and 6000 K. Order and spacing hold; the daylight presets read 8–15 % high, as Sony's + dial does against its spectral matrices (#730). +- Real files through the headless app (As Shot): WBModes Fluorescent 4,103 K, tint +26 (green + compensation); LED-lit room, Auto 3,761 K; open shade, Auto 6,596 K; sunlit chart, Auto + 4,762 K. +- `files::tests::crw_probes_name_their_camera_from_the_ciff_records`: CRW imports now take make, + model and capture time from the decoder's CIFF records (no Exif in a CRW; the catalog had an + empty camera, so a D60 stayed on the relative white-balance scale). + +## Limits + +- One body, one bulb, one afternoon. The chart is 24 patches; a 3×3 matrix cannot follow the + sensor's departures from the CIE observer (the bulb fit's worst patches are 5–7 ΔE). +- The lights are filed under standard illuminants they only approximate (~50 K for the bulb, + ~150 K for the sun). +- The D60's look is still the per-file JPEG fit and the pooled profile; these matrices give the + white-balance scale and the fallback colour only. diff --git a/clean-room/crw/README.md b/clean-room/crw/README.md new file mode 100644 index 000000000..6e4bba726 --- /dev/null +++ b/clean-room/crw/README.md @@ -0,0 +1,18 @@ +# Canon CRW: where the record is + +The Canon CRW (CIFF) decoder and its clean-room record (the frozen format, the verification +summary, the colour, geometry and metadata notes, the private-corpus summary and the source log) +live in craft-libs: +[`crates/crw/clean-room/`](https://github.com/storytold/craft-libs/tree/main/crates/crw/clean-room). +The derivation trail and the frozen table-generation materials stay in LightCraft PR #3021. +LightCraft uses the crate `craft-crw` through the adapter `crates/raw/src/vendor/crw.rs`. + +What stays here is LightCraft's own colour work on these cameras: + +- [`10-chart-calibration.md`](10-chart-calibration.md): the EOS D60 colour matrices fitted to + ColorChecker captures, implemented in `crates/raw/src/chart_table.rs`. +- [`logs/chart-captures.tsv`](logs/chart-captures.tsv): names, sizes and SHA-256 of the chart + frames (no image content). + +The capture statistics those documents cite (`04-color.md`, `05-private-corpus.md`, `sources.md`) +are in the craft-libs record. diff --git a/clean-room/crw/logs/chart-captures.tsv b/clean-room/crw/logs/chart-captures.tsv new file mode 100644 index 000000000..c80309901 --- /dev/null +++ b/clean-room/crw/logs/chart-captures.tsv @@ -0,0 +1,39 @@ +# EOS D60 ColorChecker Classic captures (maintainer, local only; never committed). Names, sizes, SHA-256; no image content. Generated 2026-10-25. +file bytes sha256 +SunlightColorCard/CRW_2666.CRW 7791628 371d8e18c12801c72754dd7b277163a866afc422c8edf6ad1ea822009e4170f8 +SunlightColorCard/CRW_2667.CRW 6638578 188d625c31ab44475a6cf54a5dae00fd861e26aafc90f4b5d18a1905c0a3cef5 +SunlightColorCard/CRW_2668.CRW 6832770 1189b8dc79642c57a8c9496b5629e36a1741c545d2b0b864d6e6e20d901510c1 +SunlightColorCard/CRW_2669.CRW 7079102 baf1686b27a9e0a04bf87c0b6cf55168f398d85247f8115a0af69b5e9ea1884c +SunlightColorCard/CRW_2670.CRW 7225298 067ba3f4d3026b8009cfe4babb5e4c3b28f988d95c48024a75c6715de32afccf +SunlightColorCard/CRW_2671.CRW 7504700 cf81cbb96389624318e890b5506221d49f5fba8e2d45447d87e0f3ff3e9bdc72 +SunlightColorCard/CRW_2672.CRW 7830852 7a59446bfb1f00d6eabd0115b8e5451c34f5d3d48f483cb27608ce9508e43ee5 +SunlightColorCard/CRW_2673.CRW 8053180 feeb8f818325d6ddb4e88b5eabe4a74f4cb5e1da9f70135ae3f85938c86695dc +SunlightColorCard/CRW_2674.CRW 8312060 45e7402c888582adadb607599eae80644d657d61c9607b426c411ee306e42daf +SunlightColorCard/CRW_2675.CRW 8565950 be28d2fa049aa4158ae9e3ec49dd28eb56fcd0ffa7af060235b8bda0d89dfd0a +SunlightColorCard/CRW_2676.CRW 7795508 76f7418a82e45e1034b3adacacf4bc756cb65a0d683e5bb3631992784e654f93 +SunlightColorCard/CRW_2677.CRW 7747450 a5e7bc618e6b80ea70f9bad6a1bf41ee63eadbc51df28b82d0864a73434f7964 +SunlightColorCard/CRW_2678.CRW 6815054 28824f0f64b087488d8dc5a66a8bdf717e83a4d4e1660676d07991bf32dffb96 +SunlightColorCard/CRW_2679.CRW 7050606 b5c915fa89542265deb4fa16da3ca7ffda01b1d961903836b0059d024316673a +SunlightColorCard/CRW_2680.CRW 7231410 a5780dfe99a2df2a3ed9de1888e91baa614b2e009a2b6e0a84d442b6bf1af9fd +SunlightColorCard/CRW_2681.CRW 7438088 eb4475919c2770f816d47787656ab7ecb5396f30d2c03628c3ab167ebbc47c65 +SunlightColorCard/CRW_2682.CRW 7584262 a6fe417b90ce41b03552963dee6b21e4f02b59a5c51148edc04af7224e8c59e9 +SunlightColorCard/CRW_2683.CRW 7811474 5402ad40e2689d78ade083d8855ecced5caea9368b9d77ecc4a61a16dc55aa36 +SunlightColorCard/CRW_2684.CRW 8043372 bbef5da457c0b6d6c45c74a99f3e318e6c80a7d3a0ad5c7cb7e13d24205f64fa +SunlightColorCard/CRW_2685.CRW 8242344 8d68d592a5c8933f0f35630114fe7017be808b007867beaf1a8d6d2bd1441f70 +SunlightColorCard/CRW_2686.CRW 8462380 79f7db32b18a834d5408500185c65d316387d1440aa61e26a11be85883fcc382 +SunlightColorCard/CRW_2687.CRW 7690732 0333d8d18390e77128f186239e6de71c0432ab6ee9669a71c210255e4d915d19 +TungstonColorChart/CRW_2688.CRW 6269390 d9c4334edfe3636a8a7e69f29c1d1e89f820b7b704697684d7996faee41f5d43 +TungstonColorChart/CRW_2689.CRW 6001348 1e8b47645d76a71fd9fb4111c4dce805d6dcd41ac82e6b4ce52debbf71565455 +TungstonColorChart/CRW_2690.CRW 6114056 f63fe49a9ed13af33ddb8d8ed8d75de4e304a3bc2c1f1d4275ba7edbc4f459f3 +TungstonColorChart/CRW_2691.CRW 6299730 ad8a2844c00965fb6a12e8fc85a6e8bfbdf1924e6d1a68b55472701cb8ffa115 +TungstonColorChart/CRW_2696.CRW 6169684 105ee515c94da78dedc1f8d777982c4a1e62781b160428524fbe922f62b3ef5c +TungstonColorChart/CRW_2697.CRW 5565982 327eebf702185127feb8a5f4bfac5d7ebad5532f7417b302997b19b97d7ff4a8 +TungstonColorChart/CRW_2698.CRW 5709092 f0fca20f8dcd51046e53bebf9fb9f624677ee674ad739622cdb2578977669705 +TungstonColorChart/CRW_2699.CRW 5873492 57724a1ff507cf4e78c80a34cd4e0b77eac397322228d32e5215fc3869853dd1 +TungstonColorChart/CRW_2700.CRW 5988146 6c97ae2c72114034e2fbe6aed0ef97f475696e43b56ed62273eccbfbba4cbaa0 +TungstonColorChart/CRW_2701.CRW 6202756 c4032e46b753a718d8748e269a9da5d124891d07912542368ea867529143aa62 +TungstonColorChart/CRW_2702.CRW 6409582 89b65c1c07e0685841046db85923b0bf80e747fed141ddf0335ba12dc75f3f3c +TungstonColorChart/CRW_2703.CRW 6407270 50179d0a330c967a74d76bc8342eea1052689c5c544aa1a913769b241ac6f736 +TungstonColorChart/CRW_2704.CRW 6483154 6bac4d8229a8d0fff1445d212ca63e53911dc7bda988743215009257b87787fa +TungstonColorChart/CRW_2705.CRW 7170488 629f94131e7f598964d2635e3039a20c441563201c4f3cbf177d83ad6b061f78 +TungstonColorChart/CRW_2706.CRW 6383026 67657e7a21f6fd8580012fccd2210afccf285e747bc4d39b8ea775811528ce83 diff --git a/crates/catalog/src/model.rs b/crates/catalog/src/model.rs index ed1c190c5..5bd037184 100644 --- a/crates/catalog/src/model.rs +++ b/crates/catalog/src/model.rs @@ -332,7 +332,9 @@ pub struct Analysis { /// file's white balance is edited relative to its as-shot look or in Kelvin depends on the camera: /// [`relative_wb_camera`]. pub fn relative_wb_format(format: &str) -> bool { - ["ARW", "NEF", "NRW", "RW2", "RWL", "RAW", "RAF", "CR3", "CR2", "PEF", "SRW", "ORF", "ERF", "MRW"].iter().any(|f| format.eq_ignore_ascii_case(f)) + ["ARW", "NEF", "NRW", "RW2", "RWL", "RAW", "RAF", "CR3", "CR2", "PEF", "SRW", "ORF", "ERF", "MRW", "CRW"] + .iter() + .any(|f| format.eq_ignore_ascii_case(f)) } /// Whether a raw of `format` shot on `camera` (Exif make and model, as [`Meta::camera`] keeps them) is developed diff --git a/crates/engine/src/camera_preview/colour.rs b/crates/engine/src/camera_preview/colour.rs index 6befe7ce1..742b37c48 100644 --- a/crates/engine/src/camera_preview/colour.rs +++ b/crates/engine/src/camera_preview/colour.rs @@ -27,6 +27,7 @@ pub(crate) fn file_local_look(format: RawFormat) -> bool { | RawFormat::Pef | RawFormat::Srw | RawFormat::Orf + | RawFormat::Crw | RawFormat::Mrw | RawFormat::Erf ) diff --git a/crates/engine/src/camera_profiles.rs b/crates/engine/src/camera_profiles.rs index 9e6d2094e..5f2c92f17 100644 --- a/crates/engine/src/camera_profiles.rs +++ b/crates/engine/src/camera_profiles.rs @@ -1,6 +1,6 @@ //! Camera colour profiles of our own, pooled from many photos of one camera model. //! -//! A raw file without colour matrices (Sony ARW, Nikon NEF, Fujifilm RAF) gets its look fitted to its own embedded camera +//! A raw file without colour matrices (Sony ARW, Nikon NEF, Fujifilm RAF, Canon CRW) gets its look fitted to its own embedded camera //! JPEG (`camera_preview`), but one photo shows too little of some colours: a lime shirt covering a //! few dozen proxy pixels next to a hillside of foliage at the same hue. `lightcraft-cli calibrate` //! pools the colour pairs of many photos per model and fits one matrix and hue/saturation/value @@ -25,6 +25,7 @@ const MAX_FILE: u64 = 4 << 20; /// Profiles built into LightCraft (`assets/camera-profiles/`, see `assets/ATTRIBUTION.md`): /// `(model, JSON)`. pub const BUNDLED: &[(&str, &str)] = &[ + ("Canon EOS D60", include_str!("../../../assets/camera-profiles/Canon_EOS_D60.json")), ("ILCA-99M2", include_str!("../../../assets/camera-profiles/ILCA-99M2.json")), ("ILCE-7CR", include_str!("../../../assets/camera-profiles/ILCE-7CR.json")), ("ILCE-7M4", include_str!("../../../assets/camera-profiles/ILCE-7M4.json")), @@ -295,6 +296,9 @@ mod tests { assert_eq!(bundled(model), Some(p)); } assert!(bundled("No Such Camera").is_none()); + // a model name with spaces is stored under its safe file name + assert!(bundled("Canon EOS D60").is_some_and(|p| p.files > 1000)); + assert_eq!(file_name("Canon EOS D60"), "Canon_EOS_D60.json"); } #[test] diff --git a/crates/engine/src/files/probe.rs b/crates/engine/src/files/probe.rs index 12913bc6f..41a0854f7 100644 --- a/crates/engine/src/files/probe.rs +++ b/crates/engine/src/files/probe.rs @@ -149,6 +149,13 @@ pub fn probe_bytes(name: &str, bytes: &[u8]) -> Result { } Err(Err(e)) => return Err(e), }; + // what the Exif reader can't see, the decoder may have read from the raw's own records (a CRW keeps make, + // model and capture time in CIFF records, no Exif): it fills the gaps, so the catalog knows the camera + let (meta, captured) = { + let mut m = m.clone(); + m.fill_missing(&raw.metadata); + meta_of(&m) + }; let (w, h) = raw.developed_size(); let (mut w, mut h) = (w.max(1) as u32, h.max(1) as u32); if raw.orientation.swaps_axes() { diff --git a/crates/engine/src/files/tests.rs b/crates/engine/src/files/tests.rs index 1b1b380de..33ad692d4 100644 --- a/crates/engine/src/files/tests.rs +++ b/crates/engine/src/files/tests.rs @@ -44,6 +44,30 @@ fn psd_probe_reads_embedded_capture_time_gps_and_title() { use super::*; use lightcraft_codecs::{ChromaSubsampling, EncodeImage, EncodeMeta, Samples, encode_jpeg}; +#[test] +fn crw_probes_name_their_camera_from_the_ciff_records() { + // no Exif in a CRW: the camera and capture time come from the decoder's records, and the catalog's camera + // string decides the white-balance scale (the D60 has chart-measured matrices, the 10D none) + let dir = std::env::var_os("LIGHTCRAFT_CORPUS") + .map(std::path::PathBuf::from) + .unwrap_or_else(|| std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../corpus")) + .join("raw"); + for (name, camera, kelvin) in [("crw-canon-eos-d60.crw", "Canon Canon EOS D60", true), ("crw-canon-eos-10d-a.crw", "Canon Canon EOS 10D", false)] + { + let Ok(bytes) = std::fs::read(dir.join(name)) else { + lightcraft_testkit::skip!(Corpus, "{name} absent"); + continue; + }; + assert!(lightcraft_meta::extract(&bytes).model.is_none(), "{name}: no Exif to read"); + let info = probe_bytes(name, &bytes).unwrap(); + assert_eq!(info.meta.camera, camera, "{name}"); + assert!(info.captured.is_some(), "{name}"); + assert_eq!(lightcraft_catalog::relative_wb_camera(&info.format, &info.meta.camera), !kelvin, "{name}"); + let (t, _) = info.as_shot_wb.unwrap(); + assert_eq!(t != 6500.0, kelvin, "{name}: {t}"); + } +} + /// Which white-balance scale a raw gets (issue #730): a file with matrices of its own is absolute from them; a /// fitted-look file is absolute through its camera's measured matrices, which only read the as-shot /// multipliers as Kelvin; any other raw stays relative (6500 / 0 = as shot). The catalog's decision from the @@ -110,6 +134,17 @@ fn white_balance_scale_follows_the_files_matrices_then_the_cameras() { assert!(other.relative && other.scale_white.is_none()); let unknown = WbScaleOf::new(RawFormat::Nef, &ColorData::default(), mul, &lightcraft_meta::Metadata::default()); assert!(unknown.relative && unknown.as_shot(d65) == (6500.0, 0.0)); + // CRW: no matrices in the file. The EOS D60 has chart-measured matrices: Kelvin through them, its multipliers + // for the sun its D50 matrix was measured under reading as D50; another body stays relative, so its + // JPEG-fitted look applies + let d60 = WbScaleOf::new(RawFormat::Crw, &ColorData::default(), Some([2.362, 1.0, 1.298]), &meta("Canon", "Canon EOS D60")); + assert!(!d60.own_matrix && !d60.relative && d60.legacy_relative && d60.scale_white.is_some()); + let ((t, tint), (t50, tint50)) = (d60.as_shot(d65), xy_to_temp_tint(lightcraft_color::D50)); + assert!((t - t50).abs() < 50.0 && (tint - tint50).abs() < 2.0, "{t} {tint} vs D50 {t50} {tint50}"); + assert!(!lightcraft_catalog::relative_wb_camera("CRW", "Canon Canon EOS D60")); + let crw = WbScaleOf::new(RawFormat::Crw, &ColorData::default(), mul, &meta("Canon", "Canon EOS 10D")); + assert!(crw.relative && !crw.legacy_relative && crw.scale_white.is_none()); + assert!(lightcraft_catalog::relative_wb_camera("CRW", "Canon Canon EOS 10D")); // a file with matrices of its own (DNG): absolute from them, no matter whose camera let own = ColorData { illuminant: [21, 0], color_matrix: [Some(lightcraft_color::Mat3::IDENTITY), None], ..Default::default() }; let dng = WbScaleOf::new(RawFormat::Dng, &own, mul, &meta("SONY", "ILCE-7M3")); diff --git a/crates/engine/src/lightroom_catalog.rs b/crates/engine/src/lightroom_catalog.rs index c7bf8611b..add37f904 100644 --- a/crates/engine/src/lightroom_catalog.rs +++ b/crates/engine/src/lightroom_catalog.rs @@ -1139,7 +1139,7 @@ pub(crate) fn apply_prepared(s: &mut crate::Session, mut data: CatalogImport, co number(&src.image, "fileHeight").max(0) as u32, &now, ); - if ["RAW", "DNG", "ARW", "CR2", "CR3", "NEF", "NRW", "RAF", "ORF", "RW2", "PEF"].contains(&p.format.to_uppercase().as_str()) { + if ["RAW", "DNG", "ARW", "CR2", "CR3", "CRW", "NEF", "NRW", "RAF", "ORF", "RW2", "PEF"].contains(&p.format.to_uppercase().as_str()) { p.kind = lightcraft_catalog::MediaKind::Raw; } p diff --git a/crates/engine/src/media/source.rs b/crates/engine/src/media/source.rs index 3373e12b5..08ba80617 100644 --- a/crates/engine/src/media/source.rs +++ b/crates/engine/src/media/source.rs @@ -1,7 +1,7 @@ use super::{Arc, Deserialize, DevelopSettings, MediaKind, Photo, Rgb32f, Rgba8, Serialize, SourceInfo}; /// Bump when the pipeline's output changes, to invalidate cached thumbnails. -pub const RENDER_CACHE_VERSION: u64 = 72; +pub const RENDER_CACHE_VERSION: u64 = 73; /// Thumbnails render at one of these long edges (so window/cell size changes reuse the cache). pub const THUMB_SIZES: [usize; 4] = [128, 256, 384, 512]; diff --git a/crates/raw/Cargo.toml b/crates/raw/Cargo.toml index 3cb2003b2..50ecfc371 100644 --- a/crates/raw/Cargo.toml +++ b/crates/raw/Cargo.toml @@ -15,6 +15,8 @@ lightcraft-raster = { workspace = true } lightcraft-color = { workspace = true } lightcraft-geom = { workspace = true } serde = { workspace = true } +chrono = { workspace = true } +craft-crw = { workspace = true } thiserror = { workspace = true } rayon = { workspace = true } miniz_oxide = { workspace = true } diff --git a/crates/raw/src/chart_table.rs b/crates/raw/src/chart_table.rs new file mode 100644 index 000000000..bb87ec73e --- /dev/null +++ b/crates/raw/src/chart_table.rs @@ -0,0 +1,21 @@ +// Camera colour matrices fitted to our own photographs of a ColorChecker Classic (post-November-2014 formulation), +// for cameras with no published spectral sensitivities. Kept by hand: each row's capture, fit and checks are in +// clean-room/crw/10-chart-calibration.md (scripts local-only). Reference colours: X-Rite's published L*a*b* (D50) for +// the chart, as packaged by colour-science (BSD-3-Clause). No number comes from Adobe or from a GPL raw decoder. +use super::Camera; +use lightcraft_color::Mat3; + +#[rustfmt::skip] +pub(super) const CAMERAS: &[Camera] = &[ + // Canon EOS D60 (CRW), the maintainer's body, ISO 100. Matrix 1: one ~2,900 K tungsten bulb, 11 frames, filed as + // Standard Illuminant A; matrix 2: direct afternoon sun, ~4,850 K, 20 frames, filed as D50 (both temperatures from + // the camera's own white-balance presets). Fitted on chromaticity with neutral held at D50 (the sunlit chart was + // unevenly lit): CIEDE2000 mean 2.42 / 1.70 on the 24 patches, 2.74 / 1.97 leaving each patch out. + Camera { + make: "Canon", + model: "EOS D60", + illuminant: [17, 23], + color_matrix: [Mat3([[0.950961, -0.345297, 0.112165], [-0.545579, 1.400621, 0.558365], [-0.123107, 0.222004, 1.026236]]), Mat3([[0.609694, -0.127035, -0.045387], [-0.461201, 1.16644, 0.337283], [-0.115992, 0.229363, 0.791647]])], + forward_matrix: [Mat3([[0.763921, 0.183562, 0.016813], [0.268545, 0.944022, -0.212567], [0.100742, -0.431647, 1.15601]]), Mat3([[0.757277, 0.190573, 0.016446], [0.291782, 1.009128, -0.300909], [0.026419, -0.264451, 1.063137]])], + }, +]; diff --git a/crates/raw/src/lib.rs b/crates/raw/src/lib.rs index 17152e80f..b0398cd07 100644 --- a/crates/raw/src/lib.rs +++ b/crates/raw/src/lib.rs @@ -15,7 +15,8 @@ //! //! Formats: DNG (uncompressed, lossless JPEG, lossy JPEG (Smart Previews), Deflate incl. floating point, JPEG XL (DNG 1.7, //! `jxl` feature, on by default), tiled/stripped, CFA and LinearRaw), -//! Canon CR2 / CR3 (lossless CRX Bayer and version 0x100/0x200 C-RAW), Nikon NEF/NRW (uncompressed, Huffman lossless / lossy compressed), Sony ARW (uncompressed, ARW2, lossless), Fujifilm RAF (uncompressed Bayer +//! Canon CR2 / CR3 (lossless CRX Bayer and version 0x100/0x200 C-RAW) / CRW (the CIFF bodies of 2000–2004; clean-room-derived +//! compression, `vendor::crw`; the PowerShot G5's anomalous coding is refused), Nikon NEF/NRW (uncompressed, Huffman lossless / lossy compressed), Sony ARW (uncompressed, ARW2, lossless), Fujifilm RAF (uncompressed Bayer //! and X-Trans, lossless and lossy compressed), Panasonic RW2 / Leica RWL / Panasonic RAW (every raw format: compressed 4 and 6, the prefix-coded strips of 8, //! packed 2/5/7, the 16-bit words of the oldest bodies), Pentax PEF (uncompressed, Huffman), Olympus ORF (uncompressed: 16-bit words, 12-bit XZ-2 words, 12-bit 16-byte blocks of the E-300/E-330/E-500). //! [`embedded_preview`] covers these containers' JPEG previews. Variants we can't decode yet (compressed ORF, CR3 unverified marker families / C-RAW configurations) return [`RawError::Unsupported`]; each vendor module documents its sources @@ -140,6 +141,7 @@ impl RawFormat { RawFormat::Dng | RawFormat::Cr2 | RawFormat::Cr3 + | RawFormat::Crw | RawFormat::Nef | RawFormat::Nrw | RawFormat::Arw @@ -413,6 +415,7 @@ fn decode_with(bytes: &[u8], mode: Mode) -> Result { RawFormat::Dng => dng::decode(bytes, mode).map(lift_clipped), RawFormat::Cr2 => vendor::cr2::decode(bytes, mode), RawFormat::Cr3 => vendor::cr3::decode(bytes, mode), + RawFormat::Crw => vendor::crw::decode(bytes, mode), RawFormat::Nef | RawFormat::Nrw => vendor::nef::decode(bytes, mode), RawFormat::Arw => vendor::arw::decode(bytes, mode), RawFormat::Raf => vendor::raf::decode(bytes, mode), diff --git a/crates/raw/src/preview/tests.rs b/crates/raw/src/preview/tests.rs index 7eb9b8dc1..aa2dc0da7 100644 --- a/crates/raw/src/preview/tests.rs +++ b/crates/raw/src/preview/tests.rs @@ -485,8 +485,8 @@ fn scanned_containers_give_the_largest_colour_jpeg() { f.extend_from_slice(part); f.extend_from_slice(&[0u8; 7]); } - // MRW decodes; its preview is still found by the scan - assert!(crate::probe(&f).is_some_and(|p| !p.is_supported() || p == crate::RawFormat::Mrw), "{head:?}"); + // CRW and MRW decode; their previews are still found by the scan, the others stay preview-only containers + assert!(crate::probe(&f).is_some_and(|p| !p.is_supported() || p == crate::RawFormat::Crw || p == crate::RawFormat::Mrw), "{head:?}"); assert_eq!(embedded_preview(&f).as_deref(), Some(&big[..]), "{head:?}"); for n in (0..f.len()).step_by(11) { let _ = embedded_preview(&f[..n]); diff --git a/crates/raw/src/spectral.rs b/crates/raw/src/spectral.rs index 3a3e74262..6c4e7c46c 100644 --- a/crates/raw/src/spectral.rs +++ b/crates/raw/src/spectral.rs @@ -1,28 +1,34 @@ -//! Camera colour matrices from measured spectral sensitivities, for raws that carry no colour matrix. +//! Measured camera colour matrices, for raws that carry no colour matrix. //! -//! [`find`] looks a camera up by its Exif make and model in a table generated by -//! `tools/spectral_camera_table.py` from rawtoaces-data (Academy Software Foundation, Apache-2.0): per model, DNG -//! `ColorMatrix1/2` and `ForwardMatrix1/2` for Standard Illuminant A and D65, fitted to the camera's measured spectral -//! sensitivities. [`Camera::fill`] writes them into a file's [`ColorData`], and [`crate::color`]'s DNG model does the -//! rest. Other names of a covered camera (Rebel T3i for EOS 600D) come from rawtoaces-data's alias list, and other -//! bodies built on a covered camera's sensor from [`SAME_SENSOR`]. No number +//! [`find`] looks a camera up by its Exif make and model in two tables, per model DNG `ColorMatrix1/2` and +//! `ForwardMatrix1/2` for two calibration illuminants: +//! - generated by `tools/spectral_camera_table.py` from rawtoaces-data (Academy Software Foundation, Apache-2.0), +//! fitted to the camera's measured spectral sensitivities, for Standard Illuminant A and D65. Other names of a +//! covered camera (Rebel T3i for EOS 600D) come from rawtoaces-data's alias list, and other bodies built on a +//! covered camera's sensor from [`SAME_SENSOR`]; +//! - `chart_table.rs`: fitted to our own photographs of a ColorChecker Classic under two lights, for cameras with no +//! published sensitivities (`clean-room/crw/10-chart-calibration.md`). +//! +//! [`Camera::fill`] writes them into a file's [`ColorData`], and [`crate::color`]'s DNG model does the rest. No number //! comes from Adobe or from a GPL raw decoder. Which colour a raw starts from is the engine's choice //! (`docs/camera-preview-colour.md`). use crate::ColorData; use lightcraft_color::{Mat3, Xy}; +#[path = "chart_table.rs"] +mod chart; #[path = "spectral_table.rs"] mod table; -/// DNG `CalibrationIlluminant` codes of the two matrices: Standard Illuminant A, D65. -const ILLUMINANTS: [u16; 2] = [17, 21]; - /// One camera's colour matrices. #[derive(Clone, Copy, Debug, PartialEq)] pub struct Camera { pub make: &'static str, pub model: &'static str, - /// XYZ → camera under Standard Illuminant A and D65 (`ColorMatrix1/2`). + /// DNG `CalibrationIlluminant` codes of the two matrices: Standard Illuminant A (17) and D65 (21) for the spectral + /// table, A and D50 (23) for a chart-measured camera. + pub illuminant: [u16; 2], + /// XYZ → camera under the two illuminants (`ColorMatrix1/2`). pub color_matrix: [Mat3; 2], /// White-balanced camera → XYZ D50 (`ForwardMatrix1/2`). pub forward_matrix: [Mat3; 2], @@ -34,7 +40,7 @@ impl Camera { if crate::color::has_matrix(color) { return false; } - color.illuminant = ILLUMINANTS; + color.illuminant = self.illuminant; color.color_matrix = self.color_matrix.map(Some); color.forward_matrix = self.forward_matrix.map(Some); true @@ -88,7 +94,7 @@ pub fn find(make: Option<&str>, model: &str) -> Option<&'static Camera> { }; match table::ALIASES.iter().chain(SAME_SENSOR).find(|(m, alias, _)| same(m, alias)) { Some((m, _, target)) => table::CAMERAS.iter().find(|c| brand(c.make) == brand(m) && c.model == *target), - None => table::CAMERAS.iter().find(|c| same(c.make, c.model)), + None => cameras().find(|c| same(c.make, c.model)), } } @@ -120,9 +126,9 @@ pub fn as_shot_white_through(camera: &Camera, color: &ColorData, wb_multipliers: Some(crate::color::as_shot_white_of(&c, wb_multipliers)) } -/// Every camera in the table. -pub fn cameras() -> &'static [Camera] { - table::CAMERAS +/// Every camera in the tables: the spectral one, then the chart-measured one. +pub fn cameras() -> impl Iterator { + table::CAMERAS.iter().chain(chart::CAMERAS) } #[cfg(test)] @@ -240,7 +246,7 @@ mod tests { #[test] fn every_row_is_a_usable_dng_profile() { - assert_eq!(cameras().len(), 52); + assert_eq!(cameras().count(), 53); for c in cameras() { assert_eq!(find(Some(c.make), c.model), Some(c), "{} {}", c.make, c.model); for m in c.color_matrix.iter().chain(&c.forward_matrix) { @@ -253,7 +259,36 @@ mod tests { let d50 = lightcraft_color::D50.to_xyz(); assert!((0..3).all(|i| (w[i] - d50[i]).abs() < 1e-4), "{} {w:?}", c.model); } + assert!(c.illuminant.iter().all(|&i| crate::color::illuminant_temperature(i).is_some()), "{}", c.model); } + assert!(table::CAMERAS.iter().all(|c| c.illuminant == [17, 21])); + } + + #[test] + fn the_chart_measured_eos_d60_reads_its_presets_in_kelvin() { + // Exif "Canon" / "Canon EOS D60" (CRW), and the catalog's joined string; not the Nikon D60 + let c = find(Some("Canon"), "Canon EOS D60").unwrap(); + assert_eq!((c.model, c.illuminant), ("EOS D60", [17, 23])); + assert_eq!(find_camera("Canon Canon EOS D60"), Some(c)); + assert_eq!(find(Some("NIKON CORPORATION"), "NIKON D60"), None); + // the camera's own white-balance presets (ColorBalance multipliers of a D60 file), which the fit never saw, + // land near the temperatures Canon names for them, on the locus + let kelvin = |r: f32, b: f32| { + let xy = as_shot_white_through(c, &ColorData::default(), Some([r, 1.0, b])).unwrap(); + lightcraft_color::cct::xy_to_temp_tint(xy) + }; + let (tungsten, tint) = kelvin(1.568, 1.918); + assert!((2900.0..3500.0).contains(&tungsten) && tint.abs() < 15.0, "Tungsten {tungsten} {tint}"); + let (daylight, tint) = kelvin(2.541, 1.219); + assert!((4800.0..5800.0).contains(&daylight) && tint.abs() < 15.0, "Daylight {daylight} {tint}"); + let (cloudy, _) = kelvin(2.799, 1.131); + let (flash, _) = kelvin(2.911, 1.103); + eprintln!("EOS D60 presets: Tungsten {tungsten:.0} K, Daylight {daylight:.0} K, Cloudy {cloudy:.0} K, Flash {flash:.0} K"); + assert!(cloudy > daylight + 300.0 && flash > daylight + 300.0, "{daylight} {cloudy} {flash}"); + // the two calibration lights read as the illuminants they were filed under + let (bulb, _) = kelvin(1.353, 2.213); + let (sun, _) = kelvin(2.362, 1.298); + assert!((bulb - 2856.0).abs() < 30.0 && (sun - 5003.0).abs() < 50.0, "{bulb} {sun}"); } #[test] diff --git a/crates/raw/src/spectral_table.rs b/crates/raw/src/spectral_table.rs index e8703e471..f4ce3f882 100644 --- a/crates/raw/src/spectral_table.rs +++ b/crates/raw/src/spectral_table.rs @@ -9,312 +9,364 @@ pub(super) const CAMERAS: &[Camera] = &[ Camera { make: "ARRI", model: "D21", + illuminant: [17, 21], color_matrix: [Mat3([[0.933065, -0.03997, 0.042177], [-0.321253, 0.916394, 0.250116], [-0.047929, 0.258088, 0.770888]]), Mat3([[0.841118, 0.11685, -0.05716], [-0.416513, 1.06087, 0.169491], [-0.121062, 0.361236, 0.692345]])], forward_matrix: [Mat3([[0.913988, -0.102353, 0.152661], [0.315506, 0.849769, -0.165276], [-0.134707, -0.698389, 1.6582]]), Mat3([[1.01361, -0.108757, 0.059444], [0.405694, 0.811177, -0.216871], [-0.027612, -0.326433, 1.17915]])], }, Camera { make: "Arashi Vision", model: "Insta360 X5", + illuminant: [17, 21], color_matrix: [Mat3([[1.0899, -0.436896, 0.023525], [-0.482767, 1.39879, 0.369626], [-0.056659, 0.169421, 0.72407]]), Mat3([[0.700283, -0.158934, -0.077847], [-0.543955, 1.30291, 0.196622], [-0.148964, 0.25165, 0.516509]])], forward_matrix: [Mat3([[0.710808, 0.160362, 0.093126], [0.231838, 0.891164, -0.123002], [0.020454, -0.498248, 1.3029]]), Mat3([[0.706099, 0.209581, 0.048616], [0.287186, 0.8971, -0.184286], [0.044158, -0.279999, 1.06095]])], }, Camera { make: "Canon", model: "Digital Rebel XTi", + illuminant: [17, 21], color_matrix: [Mat3([[0.984721, -0.317618, -0.03823], [-0.536205, 1.41139, 0.499196], [-0.019493, 0.0898, 0.993219]]), Mat3([[0.654378, -0.152128, -0.077165], [-0.504141, 1.17532, 0.279049], [-0.071499, 0.132862, 0.672873]])], forward_matrix: [Mat3([[0.735947, 0.130604, 0.097745], [0.271991, 0.870186, -0.142177], [-0.013289, -0.229681, 1.06808]]), Mat3([[0.693623, 0.247958, 0.022715], [0.295094, 0.978904, -0.273998], [0.00951, -0.117049, 0.932644]])], }, Camera { make: "Canon", model: "EOS-1D X Mark II", + illuminant: [17, 21], color_matrix: [Mat3([[0.841821, -0.109388, -0.029826], [-0.469467, 1.34341, 0.484215], [-0.074495, 0.204633, 0.843881]]), Mat3([[0.559829, 0.012974, -0.076841], [-0.474068, 1.17573, 0.252424], [-0.137973, 0.263217, 0.557436]])], forward_matrix: [Mat3([[0.867342, -0.069209, 0.166163], [0.282942, 0.879178, -0.162119], [0.042308, -0.560464, 1.34326]]), Mat3([[0.866644, -0.0379, 0.135551], [0.340507, 0.913959, -0.254466], [0.035971, -0.324047, 1.11318]])], }, Camera { make: "Canon", model: "EOS-1Ds Mark III", + illuminant: [17, 21], color_matrix: [Mat3([[0.933464, -0.245919, -0.026807], [-0.571496, 1.49286, 0.379177], [-0.048229, 0.100118, 0.927528]]), Mat3([[0.623037, -0.073582, -0.085177], [-0.54689, 1.28786, 0.213006], [-0.105037, 0.156191, 0.659429]])], forward_matrix: [Mat3([[0.785005, 0.07525, 0.10404], [0.285685, 0.799015, -0.084701], [0.044611, -0.231741, 1.01223]]), Mat3([[0.764142, 0.106772, 0.093381], [0.318478, 0.835999, -0.154477], [0.03106, -0.128764, 0.922809]])], }, Camera { make: "Canon", model: "EOS-1Ds Mark II", + illuminant: [17, 21], color_matrix: [Mat3([[0.945891, -0.245875, -0.023592], [-0.58518, 1.49586, 0.412994], [-0.049647, 0.124624, 1.04996]]), Mat3([[0.633076, -0.080316, -0.073882], [-0.552992, 1.27833, 0.227086], [-0.10768, 0.180208, 0.730732]])], forward_matrix: [Mat3([[0.78531, 0.073316, 0.10567], [0.29637, 0.804671, -0.101041], [0.021036, -0.254656, 1.05872]]), Mat3([[0.780605, 0.119757, 0.063934], [0.332784, 0.851961, -0.184745], [0.021321, -0.137163, 0.940946]])], }, Camera { make: "Canon", model: "EOS 100D", + illuminant: [17, 21], color_matrix: [Mat3([[0.823379, -0.153284, -0.011827], [-0.451822, 1.34611, 0.422153], [-0.063154, 0.17914, 0.866791]]), Mat3([[0.537713, -0.001778, -0.071702], [-0.479729, 1.20511, 0.230384], [-0.135862, 0.245077, 0.608091]])], forward_matrix: [Mat3([[0.839709, -0.013024, 0.137611], [0.260264, 0.869968, -0.130232], [0.040567, -0.473274, 1.25781]]), Mat3([[0.852444, -0.005311, 0.117164], [0.33135, 0.880249, -0.211599], [0.038232, -0.260159, 1.04703]])], }, Camera { make: "Canon", model: "EOS 200D", + illuminant: [17, 21], color_matrix: [Mat3([[0.882798, -0.11491, -0.016841], [-0.443662, 1.31235, 0.491834], [-0.062151, 0.206192, 0.798346]]), Mat3([[0.570466, 0.029473, -0.079371], [-0.461712, 1.15876, 0.257222], [-0.118587, 0.24972, 0.538517]])], forward_matrix: [Mat3([[0.867592, -0.074026, 0.17073], [0.27684, 0.904905, -0.181745], [0.007997, -0.615366, 1.43247]]), Mat3([[0.884048, -0.067045, 0.147293], [0.347584, 0.917256, -0.26484], [0.021454, -0.322825, 1.12647]])], }, Camera { make: "Canon", model: "EOS 200D II", + illuminant: [17, 21], color_matrix: [Mat3([[0.820814, -0.08112, -0.025232], [-0.449049, 1.31524, 0.500348], [-0.063522, 0.20839, 0.807016]]), Mat3([[0.534782, 0.039491, -0.075304], [-0.461016, 1.15287, 0.26203], [-0.117011, 0.248633, 0.539767]])], forward_matrix: [Mat3([[0.879874, -0.096427, 0.180849], [0.283378, 0.900453, -0.183831], [0.006798, -0.61219, 1.4305]]), Mat3([[0.897644, -0.089573, 0.156225], [0.354938, 0.914467, -0.269405], [0.020307, -0.323555, 1.12835]])], }, Camera { make: "Canon", model: "EOS 5DS", + illuminant: [17, 21], color_matrix: [Mat3([[0.784071, -0.14278, -0.018109], [-0.493528, 1.36672, 0.49299], [-0.067845, 0.178082, 0.792129]]), Mat3([[0.501948, -0.005161, -0.069927], [-0.500698, 1.19504, 0.25793], [-0.125608, 0.228657, 0.525639]])], forward_matrix: [Mat3([[0.838414, -0.019978, 0.145859], [0.285256, 0.874752, -0.160008], [0.038705, -0.507361, 1.29376]]), Mat3([[0.840997, -0.003166, 0.126464], [0.342942, 0.904091, -0.247033], [0.034419, -0.289165, 1.07985]])], }, Camera { make: "Canon", model: "EOS 5D", + illuminant: [17, 21], color_matrix: [Mat3([[0.961758, -0.26807, -0.006664], [-0.565407, 1.47679, 0.405541], [-0.067833, 0.12912, 0.951998]]), Mat3([[0.616895, -0.064661, -0.085183], [-0.554321, 1.28625, 0.22097], [-0.137616, 0.197613, 0.656267]])], forward_matrix: [Mat3([[0.79062, 0.078103, 0.095573], [0.287453, 0.812941, -0.100394], [0.068474, -0.279714, 1.03635]]), Mat3([[0.77434, 0.091394, 0.098562], [0.324069, 0.841224, -0.165293], [0.044245, -0.169979, 0.950838]])], }, Camera { make: "Canon", model: "EOS 5D Mark III", + illuminant: [17, 21], color_matrix: [Mat3([[0.798227, -0.156522, -0.024001], [-0.479014, 1.37953, 0.412187], [-0.067815, 0.178094, 0.795854]]), Mat3([[0.536462, -0.020759, -0.074508], [-0.493492, 1.22724, 0.222071], [-0.136505, 0.242956, 0.547031]])], forward_matrix: [Mat3([[0.829015, -0.007278, 0.142558], [0.266702, 0.857593, -0.124295], [0.048011, -0.488746, 1.26584]]), Mat3([[0.818929, 0.023372, 0.121994], [0.319742, 0.879191, -0.198933], [0.040465, -0.281513, 1.06615]])], }, Camera { make: "Canon", model: "EOS 5D Mark II", + illuminant: [17, 21], color_matrix: [Mat3([[0.765281, -0.126067, -0.028016], [-0.539891, 1.44802, 0.407637], [-0.058008, 0.128461, 0.861097]]), Mat3([[0.508976, -0.002163, -0.072663], [-0.524812, 1.25489, 0.224018], [-0.11344, 0.183978, 0.599963]])], forward_matrix: [Mat3([[0.84396, -0.009524, 0.12986], [0.296711, 0.799464, -0.096175], [0.052873, -0.321603, 1.09383]]), Mat3([[0.8407, 0.001511, 0.122085], [0.343565, 0.82753, -0.171095], [0.035331, -0.182192, 0.971966]])], }, Camera { make: "Canon", model: "EOS 5D Mark IV", + illuminant: [17, 21], color_matrix: [Mat3([[0.763394, -0.085035, -0.016567], [-0.442907, 1.31347, 0.486351], [-0.080725, 0.247335, 0.884784]]), Mat3([[0.49672, 0.034424, -0.068025], [-0.461551, 1.16055, 0.255439], [-0.154521, 0.307914, 0.579555]])], forward_matrix: [Mat3([[0.880649, -0.094084, 0.177732], [0.27666, 0.906137, -0.182797], [0.024429, -0.659213, 1.45989]]), Mat3([[0.898336, -0.087734, 0.153694], [0.347611, 0.924078, -0.271689], [0.035769, -0.378779, 1.16812]])], }, Camera { make: "Canon", model: "EOS 600D", + illuminant: [17, 21], color_matrix: [Mat3([[0.800214, -0.150575, -0.010842], [-0.462694, 1.36091, 0.41413], [-0.054076, 0.17327, 0.853835]]), Mat3([[0.528239, -0.007001, -0.067784], [-0.485979, 1.21691, 0.225002], [-0.13144, 0.244539, 0.592949]])], forward_matrix: [Mat3([[0.836952, -0.011114, 0.138458], [0.267123, 0.859868, -0.12699], [0.017395, -0.463764, 1.27147]]), Mat3([[0.849461, 0.005947, 0.108888], [0.332065, 0.875561, -0.207627], [0.033552, -0.262503, 1.05405]])], }, Camera { make: "Canon", model: "EOS M", + illuminant: [17, 21], color_matrix: [Mat3([[0.858133, -0.188655, -0.015316], [-0.477476, 1.37657, 0.415737], [-0.066593, 0.173487, 0.870331]]), Mat3([[0.559337, -0.024286, -0.076489], [-0.498439, 1.22646, 0.227107], [-0.13663, 0.237788, 0.612213]])], forward_matrix: [Mat3([[0.818185, 0.015661, 0.130449], [0.263684, 0.85776, -0.121444], [0.046738, -0.442459, 1.22083]]), Mat3([[0.820106, 0.031315, 0.112875], [0.32549, 0.874009, -0.199499], [0.03847, -0.243276, 1.02991]])], }, Camera { make: "Canon", model: "EOS R10", + illuminant: [17, 21], color_matrix: [Mat3([[0.900395, -0.120204, -0.023257], [-0.450784, 1.32079, 0.490112], [-0.060508, 0.207987, 0.757971]]), Mat3([[0.586274, 0.022629, -0.081629], [-0.465757, 1.16444, 0.255537], [-0.114546, 0.247787, 0.505639]])], forward_matrix: [Mat3([[0.860416, -0.07424, 0.178119], [0.279245, 0.90614, -0.185385], [-0.005252, -0.64772, 1.47808]]), Mat3([[0.874599, -0.057155, 0.146852], [0.346428, 0.920831, -0.267259], [0.018292, -0.341711, 1.14852]])], }, Camera { make: "Canon", model: "EOS R5", + illuminant: [17, 21], color_matrix: [Mat3([[0.906792, -0.181028, -0.039982], [-0.497007, 1.37995, 0.46653], [-0.056558, 0.135904, 0.790883]]), Mat3([[0.599617, -0.03146, -0.086837], [-0.500836, 1.20715, 0.246929], [-0.122485, 0.204473, 0.529195]])], forward_matrix: [Mat3([[0.825544, 0.000874, 0.137878], [0.27921, 0.851443, -0.130653], [0.053855, -0.393303, 1.16455]]), Mat3([[0.80269, 0.040879, 0.120727], [0.321664, 0.899552, -0.221215], [0.039913, -0.245426, 1.03062]])], }, Camera { make: "Canon", model: "EOS R5m2", + illuminant: [17, 21], color_matrix: [Mat3([[0.925997, -0.201878, -0.03247], [-0.514263, 1.39694, 0.472057], [-0.067213, 0.159605, 0.932813]]), Mat3([[0.603973, -0.037874, -0.08705], [-0.520173, 1.22523, 0.247204], [-0.147012, 0.240988, 0.619754]])], forward_matrix: [Mat3([[0.816853, 0.016859, 0.130583], [0.284699, 0.847793, -0.132492], [0.049678, -0.384186, 1.15961]]), Mat3([[0.796684, 0.049994, 0.117618], [0.326624, 0.8894, -0.216024], [0.040655, -0.243309, 1.02776]])], }, Camera { make: "Canon", model: "EOS R6", + illuminant: [17, 21], color_matrix: [Mat3([[0.887235, -0.176902, -0.018642], [-0.462429, 1.33965, 0.473061], [-0.070097, 0.19962, 0.862666]]), Mat3([[0.571385, -0.014759, -0.079163], [-0.485367, 1.18708, 0.251862], [-0.145333, 0.265796, 0.580543]])], forward_matrix: [Mat3([[0.830203, -0.011614, 0.145706], [0.268315, 0.891706, -0.16002], [0.03391, -0.533608, 1.3248]]), Mat3([[0.829826, 0.011021, 0.123449], [0.329107, 0.920067, -0.249174], [0.037653, -0.307101, 1.09455]])], }, Camera { make: "Canon", model: "EOS R6m2", + illuminant: [17, 21], color_matrix: [Mat3([[0.904651, -0.185945, -0.032292], [-0.490303, 1.37329, 0.46456], [-0.076482, 0.19622, 0.939437]]), Mat3([[0.595351, -0.032062, -0.086354], [-0.499687, 1.20659, 0.246445], [-0.151602, 0.266204, 0.633694]])], forward_matrix: [Mat3([[0.822515, -0.0028, 0.14458], [0.275534, 0.86495, -0.140483], [0.04532, -0.468236, 1.24802]]), Mat3([[0.80207, 0.038555, 0.123671], [0.322259, 0.905003, -0.227262], [0.03728, -0.271382, 1.05921]])], }, Camera { make: "Canon", model: "EOS RP", + illuminant: [17, 21], color_matrix: [Mat3([[0.843269, -0.158656, -0.026198], [-0.497933, 1.37758, 0.476056], [-0.070882, 0.179269, 0.839769]]), Mat3([[0.548578, -0.014251, -0.077893], [-0.504763, 1.20691, 0.250579], [-0.139008, 0.243991, 0.560854]])], forward_matrix: [Mat3([[0.836529, -0.017241, 0.145008], [0.285362, 0.860694, -0.146057], [0.049163, -0.481077, 1.25702]]), Mat3([[0.825922, 0.011858, 0.126516], [0.334357, 0.898585, -0.232942], [0.038621, -0.283664, 1.07015]])], }, Camera { make: "Canon", model: "EOS R", + illuminant: [17, 21], color_matrix: [Mat3([[0.732886, -0.080769, -0.010303], [-0.454757, 1.32829, 0.481287], [-0.083062, 0.251827, 0.895746]]), Mat3([[0.473635, 0.038459, -0.063761], [-0.474141, 1.17757, 0.250797], [-0.157456, 0.313293, 0.589901]])], forward_matrix: [Mat3([[0.884185, -0.093978, 0.174089], [0.284166, 0.893986, -0.178152], [0.023242, -0.654769, 1.45663]]), Mat3([[0.908841, -0.095987, 0.151442], [0.356864, 0.902145, -0.259009], [0.034581, -0.371735, 1.16226]])], }, Camera { make: "Canon", model: "PowerShot S90", + illuminant: [17, 21], color_matrix: [Mat3([[1.37542, -0.592063, 0.037432], [-0.219062, 1.12456, 0.326212], [-0.021707, 0.09788, 0.58587]]), Mat3([[0.90732, -0.244506, -0.087152], [-0.314903, 1.06475, 0.215416], [-0.11223, 0.179775, 0.439924]])], forward_matrix: [Mat3([[0.67794, 0.203129, 0.083227], [0.105177, 1.01192, -0.1171], [0.055684, -0.464622, 1.23404]]), Mat3([[0.655349, 0.321862, -0.012915], [0.180096, 1.10102, -0.281111], [0.064028, -0.26917, 1.03025]])], }, Camera { make: "Fujifilm", model: "GFX 100", + illuminant: [17, 21], color_matrix: [Mat3([[1.37208, -0.633889, 0.081365], [-0.430548, 1.32101, 0.427017], [-0.039908, 0.114944, 0.782345]]), Mat3([[0.834968, -0.233255, -0.07589], [-0.475844, 1.18948, 0.241344], [-0.126239, 0.194812, 0.541708]])], forward_matrix: [Mat3([[0.673446, 0.25104, 0.039809], [0.200798, 0.950696, -0.151494], [0.031013, -0.340478, 1.13457]]), Mat3([[0.679917, 0.304542, -0.020163], [0.261808, 1.00662, -0.268426], [0.044444, -0.214012, 0.994673]])], }, Camera { make: "Fujifilm", model: "X-T3", + illuminant: [17, 21], color_matrix: [Mat3([[1.29933, -0.598524, 0.075033], [-0.403354, 1.28265, 0.450876], [-0.037817, 0.112326, 0.810909]]), Mat3([[0.785701, -0.221043, -0.071522], [-0.456611, 1.15627, 0.255052], [-0.124341, 0.191243, 0.562458]])], forward_matrix: [Mat3([[0.671025, 0.255703, 0.037568], [0.190434, 0.97306, -0.163493], [0.03182, -0.334086, 1.12737]]), Mat3([[0.676828, 0.316449, -0.028982], [0.256753, 1.03712, -0.293871], [0.04319, -0.207191, 0.989106]])], }, Camera { make: "Fujifilm", model: "X-T4", + illuminant: [17, 21], color_matrix: [Mat3([[1.27406, -0.587718, 0.074639], [-0.371617, 1.24808, 0.450058], [-0.038746, 0.113215, 0.780014]]), Mat3([[0.771728, -0.215706, -0.070731], [-0.439625, 1.14183, 0.253491], [-0.125229, 0.192512, 0.542394]])], forward_matrix: [Mat3([[0.668116, 0.258486, 0.037694], [0.175163, 0.992049, -0.167213], [0.040145, -0.355105, 1.14006]]), Mat3([[0.675162, 0.317733, -0.028599], [0.248581, 1.05006, -0.298641], [0.04702, -0.219918, 0.998002]])], }, Camera { make: "Hasselblad", model: "L1D-20c", + illuminant: [17, 21], color_matrix: [Mat3([[0.697908, -0.225515, 0.018865], [-0.403519, 1.29608, 0.413637], [-0.048508, 0.167167, 0.74727]]), Mat3([[0.466971, -0.076456, -0.040872], [-0.471572, 1.21777, 0.21163], [-0.133006, 0.246905, 0.516655]])], forward_matrix: [Mat3([[0.754419, 0.110524, 0.099352], [0.21061, 0.943846, -0.154456], [0.016488, -0.523808, 1.33242]]), Mat3([[0.780313, 0.168783, 0.0152], [0.295267, 0.942833, -0.2381], [0.041, -0.301041, 1.08515]])], }, Camera { make: "Hasselblad", model: "L2D-20c", + illuminant: [17, 21], color_matrix: [Mat3([[0.787283, -0.249155, 0.044752], [-0.415734, 1.29586, 0.451949], [-0.060535, 0.144901, 0.709626]]), Mat3([[0.506449, -0.071884, -0.038243], [-0.435817, 1.14056, 0.251334], [-0.129637, 0.209508, 0.474062]])], forward_matrix: [Mat3([[0.776836, 0.120396, 0.067064], [0.218196, 0.951802, -0.169998], [0.071832, -0.4598, 1.21307]]), Mat3([[0.807201, 0.167509, -0.010414], [0.291699, 1.01528, -0.306978], [0.062694, -0.296911, 1.05932]])], }, Camera { make: "Nikon", model: "D200", + illuminant: [17, 21], color_matrix: [Mat3([[1.37569, -0.560621, 0.01677], [-0.606594, 1.52996, 0.38327], [-0.024329, 0.069398, 0.99135]]), Mat3([[0.898945, -0.249527, -0.086342], [-0.568281, 1.29839, 0.222009], [-0.098369, 0.139456, 0.687094]])], forward_matrix: [Mat3([[0.708545, 0.18748, 0.068271], [0.274935, 0.818921, -0.093855], [0.012224, -0.169279, 0.98216]]), Mat3([[0.685079, 0.274473, 0.004744], [0.294469, 0.898619, -0.193087], [0.026107, -0.10149, 0.900488]])], }, Camera { make: "Nikon", model: "D3300", + illuminant: [17, 21], color_matrix: [Mat3([[1.01047, -0.365965, 0.032301], [-0.473579, 1.3513, 0.474736], [-0.075188, 0.164522, 1.02012]]), Mat3([[0.644589, -0.136221, -0.056505], [-0.47509, 1.16499, 0.26318], [-0.15352, 0.237111, 0.681108]])], forward_matrix: [Mat3([[0.738138, 0.162349, 0.063809], [0.234836, 0.925124, -0.15996], [0.059989, -0.351996, 1.11711]]), Mat3([[0.742449, 0.23561, -0.013763], [0.290358, 1.00587, -0.29623], [0.04507, -0.217376, 0.99741]])], }, Camera { make: "Nikon", model: "D5100", + illuminant: [17, 21], color_matrix: [Mat3([[1.34772, -0.48474, 0.012024], [-0.466856, 1.29899, 0.44042], [-0.076248, 0.161652, 0.981853]]), Mat3([[0.929942, -0.213704, -0.081538], [-0.481178, 1.17186, 0.262186], [-0.14942, 0.233631, 0.699016]])], forward_matrix: [Mat3([[0.732398, 0.159354, 0.072543], [0.240376, 0.912366, -0.152741], [0.06111, -0.349037, 1.11303]]), Mat3([[0.727572, 0.254229, -0.017505], [0.287762, 1.00467, -0.292432], [0.040205, -0.206009, 0.990908]])], }, Camera { make: "Nikon", model: "D5300", + illuminant: [17, 21], color_matrix: [Mat3([[0.943297, -0.302977, 0.012055], [-0.489398, 1.36679, 0.480036], [-0.079912, 0.172098, 1.02217]]), Mat3([[0.613661, -0.114638, -0.052652], [-0.475392, 1.16153, 0.266621], [-0.147109, 0.232899, 0.680179]])], forward_matrix: [Mat3([[0.75961, 0.127618, 0.077067], [0.24786, 0.911605, -0.159465], [0.062139, -0.362458, 1.12542]]), Mat3([[0.765013, 0.208344, -0.009061], [0.301196, 0.998195, -0.299391], [0.042143, -0.217084, 1.00005]])], }, Camera { make: "Nikon", model: "D7000", + illuminant: [17, 21], color_matrix: [Mat3([[1.0579, -0.370378, 0.024968], [-0.50908, 1.38998, 0.475636], [-0.075079, 0.158561, 1.00866]]), Mat3([[0.675661, -0.143226, -0.053077], [-0.488456, 1.16985, 0.270384], [-0.137868, 0.21334, 0.681191]])], forward_matrix: [Mat3([[0.744976, 0.150244, 0.069076], [0.251735, 0.900835, -0.15257], [0.058406, -0.336086, 1.10278]]), Mat3([[0.755556, 0.237153, -0.028413], [0.304411, 0.998446, -0.302857], [0.039148, -0.192875, 0.978832]])], }, Camera { make: "Nikon", model: "D700", + illuminant: [17, 21], color_matrix: [Mat3([[1.25876, -0.491229, 0.017162], [-0.619342, 1.54791, 0.372191], [-0.050936, 0.098145, 1.01122]]), Mat3([[0.835285, -0.217528, -0.074624], [-0.583051, 1.32613, 0.209425], [-0.121368, 0.168895, 0.708639]])], forward_matrix: [Mat3([[0.722062, 0.17127, 0.070964], [0.279306, 0.809472, -0.088779], [0.042476, -0.200528, 0.983157]]), Mat3([[0.707886, 0.252255, 0.004155], [0.304416, 0.875438, -0.179854], [0.032977, -0.118624, 0.910752]])], }, Camera { make: "Nikon", model: "D70", + illuminant: [17, 21], color_matrix: [Mat3([[0.95752, -0.326533, -0.000865], [-0.654123, 1.5628, 0.437699], [-0.01723, 0.05324, 0.899754]]), Mat3([[0.621434, -0.137207, -0.067353], [-0.59081, 1.28888, 0.250406], [-0.066923, 0.098053, 0.633702]])], forward_matrix: [Mat3([[0.74958, 0.134109, 0.080606], [0.313007, 0.787492, -0.100498], [0.005453, -0.15183, 0.971482]]), Mat3([[0.722318, 0.215673, 0.026305], [0.326263, 0.879351, -0.205614], [0.017282, -0.077471, 0.885294]])], }, Camera { make: "Nikon", model: "D800E", + illuminant: [17, 21], color_matrix: [Mat3([[0.981723, -0.320813, 0.025417], [-0.500428, 1.37669, 0.486261], [-0.07946, 0.169419, 1.04548]]), Mat3([[0.629114, -0.117451, -0.046422], [-0.483155, 1.16337, 0.27171], [-0.147278, 0.230496, 0.696276]])], forward_matrix: [Mat3([[0.760723, 0.133423, 0.07015], [0.25382, 0.906695, -0.160515], [0.059962, -0.34837, 1.11351]]), Mat3([[0.780492, 0.212338, -0.028534], [0.311949, 0.998057, -0.310006], [0.041771, -0.208304, 0.991637]])], }, Camera { make: "Nikon", model: "D810", + illuminant: [17, 21], color_matrix: [Mat3([[0.983274, -0.318832, 0.030267], [-0.51358, 1.39278, 0.481653], [-0.076666, 0.176366, 1.07197]]), Mat3([[0.625688, -0.110113, -0.045912], [-0.497828, 1.18171, 0.267671], [-0.149244, 0.239807, 0.71615]])], forward_matrix: [Mat3([[0.763991, 0.129679, 0.070625], [0.262526, 0.897486, -0.160012], [0.045849, -0.35404, 1.13329]]), Mat3([[0.791151, 0.196615, -0.023471], [0.322368, 0.977225, -0.299593], [0.038389, -0.209045, 0.995761]])], }, Camera { make: "Nikon", model: "D850", + illuminant: [17, 21], color_matrix: [Mat3([[1.17359, -0.461087, -0.011934], [-0.607581, 1.52285, 0.4063], [-0.096127, 0.196063, 1.00005]]), Mat3([[0.773836, -0.196466, -0.097677], [-0.609077, 1.35526, 0.205388], [-0.190117, 0.290252, 0.676129]])], forward_matrix: [Mat3([[0.713588, 0.158895, 0.091813], [0.273041, 0.838318, -0.111359], [0.055293, -0.367196, 1.13701]]), Mat3([[0.669707, 0.227388, 0.0672], [0.291703, 0.871502, -0.163205], [0.042827, -0.229884, 1.01216]])], }, Camera { make: "Nikon", model: "Z f", + illuminant: [17, 21], color_matrix: [Mat3([[1.16951, -0.415422, 0.033906], [-0.516721, 1.41065, 0.441119], [-0.080466, 0.180954, 1.07672]]), Mat3([[0.749962, -0.151755, -0.063228], [-0.504317, 1.20913, 0.248149], [-0.162888, 0.256933, 0.729432]])], forward_matrix: [Mat3([[0.744024, 0.151119, 0.069153], [0.253649, 0.888649, -0.142297], [0.049891, -0.351314, 1.12653]]), Mat3([[0.756117, 0.216534, -0.008355], [0.304737, 0.961046, -0.265784], [0.041615, -0.212408, 0.995897]])], }, Camera { make: "Panasonic", model: "DC-GX9", + illuminant: [17, 21], color_matrix: [Mat3([[0.820911, -0.254983, 0.035979], [-0.372553, 1.25333, 0.438192], [-0.054294, 0.146795, 0.747319]]), Mat3([[0.534195, -0.07728, -0.038219], [-0.400358, 1.10647, 0.251688], [-0.12982, 0.216241, 0.50214]])], forward_matrix: [Mat3([[0.772645, 0.116685, 0.074966], [0.196365, 0.972505, -0.16887], [0.06062, -0.463162, 1.22765]]), Mat3([[0.805529, 0.178696, -0.01993], [0.275218, 1.04725, -0.322465], [0.060973, -0.29745, 1.06158]])], }, Camera { make: "Sony", model: "DSC-RX100M4", + illuminant: [17, 21], color_matrix: [Mat3([[0.783848, -0.284096, 0.031033], [-0.411895, 1.30629, 0.410807], [-0.047532, 0.165091, 0.725564]]), Mat3([[0.507376, -0.094837, -0.042497], [-0.488786, 1.23246, 0.213166], [-0.137082, 0.247475, 0.503834]])], forward_matrix: [Mat3([[0.733625, 0.143965, 0.086705], [0.210364, 0.945779, -0.156144], [0.012886, -0.528963, 1.34118]]), Mat3([[0.767183, 0.193058, 0.004054], [0.296288, 0.94455, -0.240838], [0.043397, -0.304922, 1.08663]])], }, Camera { make: "Sony", model: "ILCE-6400", + illuminant: [17, 21], color_matrix: [Mat3([[0.686447, -0.204525, 0.026311], [-0.43013, 1.31387, 0.445779], [-0.061279, 0.132669, 0.791917]]), Mat3([[0.44557, -0.064452, -0.027734], [-0.437513, 1.13079, 0.261784], [-0.122323, 0.190304, 0.552427]])], forward_matrix: [Mat3([[0.782555, 0.106676, 0.075064], [0.224677, 0.924591, -0.149268], [0.078407, -0.377222, 1.12392]]), Mat3([[0.821757, 0.17479, -0.032251], [0.30346, 1.00862, -0.31208], [0.052261, -0.224433, 0.997277]])], }, Camera { make: "Sony", model: "ILCE-7CM2", + illuminant: [17, 21], color_matrix: [Mat3([[0.758033, -0.221503, 0.036455], [-0.492549, 1.36195, 0.503357], [-0.065665, 0.148645, 0.906293]]), Mat3([[0.482991, -0.070139, -0.027429], [-0.478804, 1.14702, 0.282926], [-0.119226, 0.194381, 0.616628]])], forward_matrix: [Mat3([[0.788283, 0.10759, 0.068422], [0.261923, 0.909809, -0.171732], [0.053026, -0.362705, 1.13478]]), Mat3([[0.83437, 0.173346, -0.043421], [0.336938, 0.996333, -0.333271], [0.037073, -0.203664, 0.991696]])], }, Camera { make: "Sony", model: "ILCE-7M3", + illuminant: [17, 21], color_matrix: [Mat3([[0.759047, -0.22787, 0.042531], [-0.502563, 1.37732, 0.491076], [-0.066877, 0.150947, 0.900094]]), Mat3([[0.482867, -0.070899, -0.024574], [-0.489531, 1.16667, 0.274244], [-0.123988, 0.201218, 0.610148]])], forward_matrix: [Mat3([[0.787201, 0.113043, 0.064052], [0.265374, 0.902211, -0.167585], [0.052744, -0.364541, 1.1369]]), Mat3([[0.840001, 0.174345, -0.05005], [0.340881, 0.981246, -0.322127], [0.039232, -0.209817, 0.995689]])], }, Camera { make: "Sony", model: "ILCE-7M4", + illuminant: [17, 21], color_matrix: [Mat3([[0.763892, -0.227658, 0.033049], [-0.512687, 1.38412, 0.503225], [-0.063774, 0.141899, 0.91715]]), Mat3([[0.485445, -0.074907, -0.026973], [-0.491937, 1.15934, 0.283071], [-0.115422, 0.185993, 0.622862]])], forward_matrix: [Mat3([[0.784064, 0.112194, 0.068037], [0.2698, 0.896428, -0.166229], [0.050008, -0.338826, 1.11392]]), Mat3([[0.830062, 0.181159, -0.046925], [0.341401, 0.985843, -0.327244], [0.034749, -0.188826, 0.979182]])], }, Camera { make: "Sony", model: "ILCE-7RM2", + illuminant: [17, 21], color_matrix: [Mat3([[0.775308, -0.25358, 0.026979], [-0.524545, 1.40154, 0.490876], [-0.060608, 0.132591, 0.920838]]), Mat3([[0.492753, -0.089554, -0.036484], [-0.505928, 1.18284, 0.273705], [-0.116121, 0.182519, 0.626106]])], forward_matrix: [Mat3([[0.76369, 0.133034, 0.067571], [0.267238, 0.888036, -0.155274], [0.048003, -0.313698, 1.0908]]), Mat3([[0.787687, 0.201628, -0.025019], [0.326839, 0.971594, -0.298433], [0.034173, -0.177854, 0.968786]])], }, Camera { make: "Sony", model: "ILCE-7RM3", + illuminant: [17, 21], color_matrix: [Mat3([[0.742519, -0.22996, 0.032883], [-0.500197, 1.37662, 0.485744], [-0.063378, 0.142467, 0.898759]]), Mat3([[0.473388, -0.075142, -0.031025], [-0.487453, 1.16732, 0.271829], [-0.119917, 0.192619, 0.610537]])], forward_matrix: [Mat3([[0.776802, 0.119664, 0.06783], [0.260471, 0.900308, -0.160779], [0.051869, -0.347156, 1.12039]]), Mat3([[0.813753, 0.181916, -0.031373], [0.32902, 0.979656, -0.308676], [0.037764, -0.198487, 0.985828]])], }, Camera { make: "Sony", model: "ILCE-7RM4", + illuminant: [17, 21], color_matrix: [Mat3([[0.732028, -0.220808, 0.020118], [-0.501296, 1.37502, 0.49365], [-0.061615, 0.13838, 0.885461]]), Mat3([[0.472151, -0.07908, -0.031322], [-0.483014, 1.15528, 0.279019], [-0.111192, 0.18072, 0.604588]])], forward_matrix: [Mat3([[0.775623, 0.114594, 0.074079], [0.260863, 0.901608, -0.162471], [0.050735, -0.342927, 1.1173]]), Mat3([[0.806038, 0.193021, -0.034763], [0.32686, 0.992356, -0.319216], [0.033831, -0.189102, 0.980375]])], }, Camera { make: "Sony", model: "ILCE-7SM2", + illuminant: [17, 21], color_matrix: [Mat3([[0.615775, -0.137144, 0.06099], [-0.408209, 1.28402, 0.462018], [-0.084205, 0.196387, 0.85663]]), Mat3([[0.395982, -0.016098, -0.005517], [-0.420931, 1.11058, 0.265879], [-0.162165, 0.267589, 0.576945]])], forward_matrix: [Mat3([[0.854583, 0.041834, 0.067878], [0.234125, 0.948414, -0.182539], [0.097857, -0.530195, 1.25744]]), Mat3([[0.948357, 0.092307, -0.076369], [0.33668, 1.02973, -0.366408], [0.073834, -0.330955, 1.08223]])], }, Camera { make: "Sony", model: "ILCE-7SM3", + illuminant: [17, 21], color_matrix: [Mat3([[0.749715, -0.228299, 0.030188], [-0.512062, 1.38442, 0.500457], [-0.072776, 0.169469, 0.933545]]), Mat3([[0.476058, -0.07383, -0.031403], [-0.495874, 1.16904, 0.277606], [-0.134473, 0.222664, 0.625349]])], forward_matrix: [Mat3([[0.779691, 0.113899, 0.070706], [0.268632, 0.905927, -0.174559], [0.04639, -0.390929, 1.16964]]), Mat3([[0.816745, 0.179015, -0.031464], [0.334824, 0.988848, -0.323672], [0.037938, -0.229363, 1.01653]])], }, Camera { make: "Sony", model: "ILCE-9", + illuminant: [17, 21], color_matrix: [Mat3([[0.747492, -0.216385, 0.049086], [-0.499768, 1.37142, 0.499037], [-0.071324, 0.164705, 0.951496]]), Mat3([[0.473639, -0.063703, -0.016557], [-0.488372, 1.1591, 0.280181], [-0.129154, 0.213406, 0.651]])], forward_matrix: [Mat3([[0.796409, 0.106147, 0.061739], [0.26822, 0.906732, -0.174952], [0.050574, -0.378333, 1.15286]]), Mat3([[0.868551, 0.167059, -0.071314], [0.354491, 0.985971, -0.340462], [0.037779, -0.211149, 0.998474]])], }, diff --git a/crates/raw/src/tests.rs b/crates/raw/src/tests.rs index 865099cc9..e58991ccc 100644 --- a/crates/raw/src/tests.rs +++ b/crates/raw/src/tests.rs @@ -258,9 +258,12 @@ fn private_containers_are_recognised_by_their_magic() { assert_eq!(probe(b"II\x1a\0\0\0HEAPCCDR\0\0"), Some(RawFormat::Crw)); assert_eq!(probe(b"\0MRM\0\x01\0\0"), Some(RawFormat::Mrw)); assert_eq!(probe(b"FOVb\x02\0\x02\0"), Some(RawFormat::X3f)); - for f in [RawFormat::Crw, RawFormat::X3f] { - assert!(!f.is_supported()); + for f in [RawFormat::Crw, RawFormat::Mrw] { + assert!(f.is_supported()); } + assert!(!RawFormat::X3f.is_supported()); + // a CRW signature with no heap behind it is corrupt, not a panic + assert!(matches!(decode(b"II\x1a\0\0\0HEAPCCDR\0\0"), Err(RawError::Corrupt(_)))); assert!(matches!(decode(b"FOVb\x02\0\x02\0"), Err(RawError::Unsupported(w)) if w.contains("X3f"))); // too short for the CIFF signature assert_eq!(probe(b"II\x1a\0\0\0HEAP"), None); diff --git a/crates/raw/src/vendor.rs b/crates/raw/src/vendor.rs index 1610f20ee..a44262a2c 100644 --- a/crates/raw/src/vendor.rs +++ b/crates/raw/src/vendor.rs @@ -9,6 +9,7 @@ mod arw_lens; pub(crate) mod arw_lens_rm6; pub mod cr2; pub mod cr3; +pub mod crw; pub mod crx; pub(crate) mod crx_wavelet; pub mod erf; diff --git a/crates/raw/src/vendor/crw.rs b/crates/raw/src/vendor/crw.rs new file mode 100644 index 000000000..d48e1f229 --- /dev/null +++ b/crates/raw/src/vendor/crw.rs @@ -0,0 +1,255 @@ +//! Canon CRW (CIFF heap) raw decoding through the shared `craft-crw` crate (craft-libs, +//! `crates/crw`): the bodies before the CR2 era (EOS D30/D60/10D/300D, PowerShot G-series). +//! +//! The decoder, its frozen code tables and its clean-room record live in +//! craft-libs (`crates/crw/clean-room/`: the frozen format `02-crw-format.md`, the verification +//! `03-verification.md`, 28 of 29 corpus files and 1,516 EOS D60 files sample-exact against a +//! black-box reference). This module maps its output onto [`RawImage`]: CFA phase, black and white +//! levels, active area, orientation, as-shot white balance and the make, model and capture-time +//! records (a CRW carries no Exif block, so this is where the catalog learns the camera). +//! +//! Known gaps (documented in the freeze): the PowerShot G5's entropy coding diverges from the +//! model and is refused (its embedded JPEG is shown instead); PowerShot bodies without CC0 samples +//! (G2/G3/Pro1/S-series) decode through the same model unverified. Colour matrices for these +//! bodies are LightCraft's own (`chart_table`, the EOS D60); the file carries none. + +use crate::{BlackLevel, Cfa, ColorData, Metadata, Mode, OpcodeLists, Orientation, RawData, RawError, RawFormat, RawImage, Rect, Result}; +use lightcraft_meta::DateTime; + +fn map_err(e: craft_crw::Error) -> RawError { + match e { + craft_crw::Error::NotCrw => RawError::NotRaw, + craft_crw::Error::Corrupt(why) => RawError::Corrupt(format!("CRW: {why}")), + craft_crw::Error::Unsupported(why) => RawError::Unsupported(format!("CRW: {why}")), + craft_crw::Error::LimitExceeded(what) => RawError::Limit(what), + } +} + +pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { + // the crate's own cap (2²³ samples; the largest CRW frame is 7.4 million): its decode holds + // about three `u16` buffers of the declared size, so the raw crate's general `MAX_SAMPLES` + // (2³⁰) would let a malformed file ask for gigabytes. No sample may be filled after the + // entropy stream ends (the crate allows one 64-sample block by default): every corpus file + // ends exactly on its frame, so a shorter stream is a truncated file and is `Corrupt`. + let mut limits = craft_crw::Limits::default(); + limits.max_stream_end_fill = 0; + let full = mode == Mode::Full; + let (h, samples, black, white) = if full { + let img = craft_crw::decode(bytes, &limits).map_err(map_err)?; + let black = BlackLevel { repeat_rows: 2, repeat_cols: 2, values: img.black.values.to_vec(), delta_h: vec![], delta_v: vec![] }; + (img.header, img.samples, black, img.white) + } else { + let h = craft_crw::header(bytes, &limits).map_err(map_err)?; + let white = ((1u32 << h.bits.clamp(1, 16)) - 1) as f32; + (h, Vec::new(), BlackLevel::uniform(0.0), white) + }; + let active = Rect::new(h.active_area.x, h.active_area.y, h.active_area.width, h.active_area.height); + let orientation = Orientation::from_exif(h.exif_orientation as _); + let metadata = Metadata { + make: h.make, + model: h.model, + capture_time: h.capture_unix.and_then(datetime_from_unix), + width: Some(active.width as u32), + height: Some(active.height as u32), + orientation: Some(orientation), + ..Metadata::default() + }; + let img = RawImage { + format: RawFormat::Crw, + width: h.width, + height: h.height, + cpp: 1, + data: RawData::U16(samples), + cfa: Some(Cfa::bayer_static(h.cfa.name())), + bits: h.bits, + black, + white: vec![white], + active_area: active, + crop: Rect::new(0, 0, active.width, active.height), + orientation, + color: ColorData::default(), + wb_multipliers: h.wb_multipliers, + linearized: false, + pixel_aspect: 1.0, + opcodes: OpcodeLists::default(), + metadata, + lens_metadata: Default::default(), + }; + img.validate_for(mode)?; + Ok(img) +} + +/// Civil date-time from the TimeStamp record's Unix seconds (the camera's local clock, no UTC +/// offset; inverse of [`DateTime::unix_seconds`]). Before 1970 or after 2200 is a garbage clock, +/// not a capture time. +fn datetime_from_unix(t: i64) -> Option { + use chrono::{Datelike, Timelike}; + let c = chrono::DateTime::from_timestamp(t, 0)?.naive_utc(); + let dt = DateTime { + year: u16::try_from(c.year()).ok()?, + month: u8::try_from(c.month()).ok()?, + day: u8::try_from(c.day()).ok()?, + hour: u8::try_from(c.hour()).ok()?, + minute: u8::try_from(c.minute()).ok()?, + second: u8::try_from(c.second()).ok()?, + millis: 0, + offset_minutes: None, + }; + ((1970..=2200).contains(&dt.year)).then_some(dt) +} + +#[cfg(test)] +mod tests { + use super::*; + + const W: usize = 48; + const H: usize = 20; + + /// A CIFF file with the records the decoder reads and a pseudo-random payload: enough for the + /// header path; the entropy data decodes or is refused, never panics. + fn synthetic_crw(model: &str) -> Vec { + synthetic_crw_sized(model, W, H) + } + + fn synthetic_crw_sized(model: &str, w: usize, h: usize) -> Vec { + let payload: Vec = (0..514 + 256).map(|i| (i * 41 % 253) as u8).collect(); + synthetic_crw_with_payload(model, w, h, payload) + } + + fn synthetic_crw_with_payload(model: &str, w: usize, h: usize, payload: Vec) -> Vec { + let le16 = |v: u16| v.to_le_bytes(); + let le32 = |v: u32| v.to_le_bytes(); + let heap = |records: &[(u16, Vec)]| -> Vec { + let mut b = Vec::new(); + let mut entries = Vec::new(); + for (tc, v) in records { + entries.push((*tc, v.len() as u32, b.len() as u32)); + b.extend_from_slice(v); + if b.len() % 2 == 1 { + b.push(0); + } + } + let tbl = b.len() as u32; + b.extend_from_slice(&le16(entries.len() as u16)); + for (tc, l, off) in entries { + b.extend_from_slice(&le16(tc)); + b.extend_from_slice(&le32(l)); + b.extend_from_slice(&le32(off)); + } + b.extend_from_slice(&le32(tbl)); + b + }; + let sensor: Vec = [34u16, w as u16, h as u16, 0, 0, 2, 2, w as u16 - 3, h as u16 - 3].iter().flat_map(|&v| le16(v)).collect(); + let dtable: Vec = [0u32, 0, 514, 0].iter().flat_map(|&v| le32(v)).collect(); + let image_info: Vec = [w as u32, h as u32, 0, 270, 12, 12, 0].iter().flat_map(|&v| le32(v)).collect(); + let mut color_balance: Vec = vec![66]; + color_balance.extend([2000u16, 800, 800, 960]); + color_balance.extend((0..7).flat_map(|_| [1500u16, 900, 900, 1500])); + let color_balance: Vec = color_balance.iter().flat_map(|&v| le16(v)).collect(); + let mut make_model = b"Canon\0".to_vec(); + make_model.extend_from_slice(model.as_bytes()); + make_model.push(0); + let props = heap(&[ + (0x1031, sensor), + (0x1835, dtable), + (0x080a, make_model), + (0x1810, image_info), + (0x180e, [1083410638u32, 0, 0].iter().flat_map(|&v| le32(v)).collect()), + (0x10a9, color_balance), + ]); + let root = heap(&[(0x2005, payload), (0x300a, props)]); + let mut f = b"II".to_vec(); + f.extend_from_slice(&le32(26)); + f.extend_from_slice(b"HEAPCCDR"); + f.extend_from_slice(&le32(0x0001_0002)); + f.extend_from_slice(&[0; 8]); + f.extend_from_slice(&root); + f + } + + #[test] + fn header_mode_maps_the_records_onto_raw_image() { + let f = synthetic_crw("Canon EOS D60"); + let img = decode(&f, Mode::Header).expect("header decodes"); + assert_eq!((img.format, img.width, img.height, img.bits, img.cpp), (RawFormat::Crw, W, H, 10, 1)); + assert_eq!(img.data.len(), 0); + assert_eq!(img.active_area, Rect::new(2, 2, W - 4, H - 4)); + assert_eq!(img.crop, Rect::new(0, 0, W - 4, H - 4)); + assert_eq!(img.cfa.as_ref().map(|c| c.name()).as_deref(), Some("RGGB")); + assert_eq!(img.orientation, Orientation::from_exif(8)); + assert_eq!(img.metadata.make.as_deref(), Some("Canon")); + assert_eq!(img.metadata.model.as_deref(), Some("Canon EOS D60")); + assert_eq!(img.metadata.orientation, Some(Orientation::from_exif(8))); + assert_eq!(img.metadata.capture_time.map(|t| t.unix_seconds()), Some(1083410638)); + let wb = img.wb_multipliers.expect("ColorBalance slot 0"); + assert!((wb[0] - 2.5).abs() < 1e-3 && wb[1] == 1.0 && (wb[2] - 1.2).abs() < 1e-3, "{wb:?}"); + assert_eq!(img.white, vec![1023.0]); + assert_eq!(img.black, BlackLevel::uniform(0.0)); + // the entropy data is pseudo-random: decodes or errors, never panics + let _ = decode(&f, Mode::Full); + let g1 = decode(&synthetic_crw("Canon PowerShot G1"), Mode::Header).expect("header decodes"); + assert_eq!(g1.cfa.as_ref().map(|c| c.name()).as_deref(), Some("GBRG")); + } + + #[test] + fn errors_map_onto_raw_error() { + assert!(matches!(decode(b"II*\0\x08\0\0\0", Mode::Full), Err(RawError::NotRaw))); + assert!(matches!(decode(b"II HEAPCCDR ", Mode::Full), Err(RawError::Corrupt(w)) if w.starts_with("CRW: "))); + let Err(RawError::Unsupported(why)) = decode(&synthetic_crw("Canon PowerShot G5"), Mode::Header) else { panic!("expected Unsupported") }; + assert!(why.contains("PowerShot G5"), "{why}"); + assert_eq!(map_err(craft_crw::Error::LimitExceeded("image too large")), RawError::Limit("image too large")); + } + + /// A file declaring more samples than any CRW camera has is refused before anything is + /// allocated: 4096 × 4096 (16.8 million) passed when the adapter raised the crate's cap of 2²³ + /// to the raw crate's 2³⁰. + #[test] + fn an_oversized_crw_is_refused_by_the_crates_own_limit() { + let f = synthetic_crw_sized("Canon EOS D60", 4096, 4096); + for mode in [Mode::Header, Mode::Full] { + assert_eq!(decode(&f, mode).err(), Some(RawError::Limit("image too large")), "{mode:?}"); + } + // the largest real frame (the 10D's 3152 × 2068) is within it + assert!(decode(&synthetic_crw_sized("Canon EOS D60", 3152, 2068), Mode::Header).is_ok()); + } + + /// A stream cut short is `Corrupt`, not a smeared image: the crate once filled every sample + /// after the entropy stream ended with its predictor value and returned success, so a CRW + /// truncated just past its header rendered and exported as a flat frame. + #[test] + fn a_truncated_entropy_stream_is_corrupt() { + // the 514-byte header and two bytes of entropy: almost all of the 960-sample frame unread + let f = synthetic_crw_with_payload("Canon EOS D60", W, H, vec![0; 516]); + let Err(RawError::Corrupt(why)) = decode(&f, Mode::Full) else { panic!("a truncated stream must be refused") }; + assert!(why.starts_with("CRW: ") && why.contains("before the frame does"), "{why}"); + // the header alone still reads, so the library can list the file and show its preview + assert!(decode(&f, Mode::Header).is_ok()); + } + + /// A stream that ends a few samples short is `Corrupt` too: the crate's default limit fills up + /// to one 64-sample block silently, and no real CRW fills any. + #[test] + fn a_stream_a_few_samples_short_is_corrupt() { + // an all-zero entropy stream of 842 bytes decodes the 960-sample frame exactly + let payload = |len: usize| vec![0u8; 514 + len]; + let whole = synthetic_crw_with_payload("Canon EOS D60", W, H, payload(842)); + assert!(decode(&whole, Mode::Full).is_ok()); + // six bytes fewer leave 10 samples unread, within the crate's default of 64 + let short = synthetic_crw_with_payload("Canon EOS D60", W, H, payload(836)); + let filled = craft_crw::decode(&short, &craft_crw::Limits::default()).map(|i| i.stream_end_fill); + assert_eq!(filled, Ok(10)); + let Err(RawError::Corrupt(why)) = decode(&short, Mode::Full) else { panic!("a stream 10 samples short must be refused") }; + assert!(why.contains("10 samples before the frame does"), "{why}"); + } + + #[test] + fn unix_datetime_inverse_matches_unix_seconds() { + for t in [0i64, 1, 86399, 86400, 951867295, 1083410638, 1600000000, 4102444799] { + let dt = datetime_from_unix(t).expect("in range"); + assert_eq!(dt.unix_seconds(), t, "{t}"); + } + // Before 1970 or after 2200 is a garbage clock, not a capture time. + assert!(datetime_from_unix(-1).is_none()); + assert!(datetime_from_unix(i64::MAX).is_none()); + } +} diff --git a/crates/raw/tests/it/corpus.rs b/crates/raw/tests/it/corpus.rs index 85792c79a..eb8feb7da 100644 --- a/crates/raw/tests/it/corpus.rs +++ b/crates/raw/tests/it/corpus.rs @@ -17,10 +17,11 @@ fn corpus_root() -> PathBuf { /// Variants known not to decode yet (see the crate docs): matched against the lower-case file name. const KNOWN_UNSUPPORTED: &[&str] = &[ - "cr3-", // Unverified CRX coding variants; exact supported cases live in cr3_corpus.rs. - "orf-olympus-em", // Olympus compressed ORF - "-he.nef", // Nikon High Efficiency (HE / HE★) NEF, issue #193 - "sraw", // Canon sRAW / mRAW + "cr3-", // Unverified CRX coding variants; exact supported cases live in cr3_corpus.rs. + "orf-olympus-em", // Olympus compressed ORF + "-he.nef", // Nikon High Efficiency (HE / HE★) NEF, issue #193 + "sraw", // Canon sRAW / mRAW + "crw-canon-powershot-g5.crw", // Canon PowerShot G5 CRW: entropy coding anomaly (craft-libs crates/crw/clean-room/02-crw-format.md §4) ]; #[test] diff --git a/crates/raw/tests/it/crw_corpus.rs b/crates/raw/tests/it/crw_corpus.rs new file mode 100644 index 000000000..9de297a71 --- /dev/null +++ b/crates/raw/tests/it/crw_corpus.rs @@ -0,0 +1,71 @@ +//! Canon CRW through the shared craft-crw decoder, on the CC0 corpus files (URLs and SHA-256 in +//! `xtask/src/raw_samples.rs`; downloaded into the gitignored corpus/raw by `cargo xtask corpus --download`). +//! +//! The FNV-1a hashes over the decoded u16 samples (row-major, little-endian, masked borders +//! included) were computed from LightCraft's earlier in-tree clean-room CRW decoder (commit +//! dd02f12f, branch `clean-room-crw-format-decode`) before it moved to craft-libs: the shared +//! decoder reproduces it bit for bit on all ten files, six bodies. Black and white levels are not +//! pinned: craft-crw changed both on purpose (border padding no longer lowers the black level; the +//! white level follows these bodies' low-ISO clip near 4000), so they are only checked for sanity. + +use lightcraft_raw::{RawData, RawError, decode}; +use std::path::{Path, PathBuf}; + +fn corpus() -> PathBuf { + std::env::var_os("LIGHTCRAFT_CORPUS").map(PathBuf::from).unwrap_or_else(|| Path::new(env!("CARGO_MANIFEST_DIR")).join("../../corpus")) +} + +fn fnv1a(samples: &[u16]) -> u64 { + samples.iter().flat_map(|v| v.to_le_bytes()).fold(0xcbf2_9ce4_8422_2325u64, |h, byte| (h ^ u64::from(byte)).wrapping_mul(0x100_0000_01b3)) +} + +/// (file, sensor width × height, FNV-1a of the samples from the earlier in-tree decoder) +const CASES: &[(&str, (usize, usize), u64)] = &[ + ("crw-canon-eos-10d-a.crw", (3152, 2068), 0xb8a6_7781_fa37_b1df), + ("crw-canon-eos-10d-b.crw", (3152, 2068), 0xeeea_3c68_2fb5_62ae), + ("crw-canon-eos-10d-c.crw", (3152, 2068), 0x060d_d320_55aa_d79e), + ("crw-canon-eos-300d-a.crw", (3152, 2068), 0xa9a0_5cc0_ca19_01bd), + ("crw-canon-eos-300d-b.crw", (3152, 2068), 0xf0c3_98e8_be78_58cc), + ("crw-canon-eos-d30.crw", (2224, 1456), 0x1394_ca5c_1cb8_816d), + ("crw-canon-eos-d60.crw", (3152, 2068), 0x63e2_4897_3fe4_1cef), + ("crw-canon-eos-digital-rebel.crw", (3152, 2068), 0x0db1_b751_a769_aa32), + ("crw-canon-powershot-g1.crw", (2144, 1560), 0x29e5_4008_46b7_a54b), + ("crw-canon-powershot-g6.crw", (3160, 2344), 0xa7fa_be95_3036_5b64), +]; + +#[test] +fn crw_samples_match_the_earlier_in_tree_decoder() { + let mut seen = 0; + for &(name, sensor, hash) in CASES { + let path = corpus().join("raw").join(name); + let Ok(bytes) = std::fs::read(&path) else { continue }; + let raw = decode(&bytes).unwrap_or_else(|e| panic!("{name}: {e}")); + raw.validate().unwrap(); + assert_eq!((raw.width, raw.height, raw.cpp), (sensor.0, sensor.1, 1), "{name}: sensor size"); + let RawData::U16(samples) = &raw.data else { panic!("{name}: integer sensor data expected") }; + assert_eq!(fnv1a(samples), hash, "{name}: sensor samples differ from the earlier decoder"); + assert!(raw.white_at(0) > raw.black.mean() && raw.black.mean() > 0.0, "{name}: invalid black/white levels"); + seen += 1; + } + if seen == 0 { + lightcraft_testkit::skip!(Corpus, "no CRW files in {}", corpus().join("raw").display()); + } +} + +/// A real CRW damaged two ways is an error, never a panic or a silently smeared frame. +#[test] +fn a_damaged_real_crw_is_an_error() { + let path = corpus().join("raw").join("crw-canon-eos-d60.crw"); + let Ok(bytes) = std::fs::read(&path) else { + lightcraft_testkit::skip!(Corpus, "{} absent", path.display()); + return; + }; + // cut in half: the CIFF heap's record table sits at the end, so the file no longer parses + let cut = &bytes[..bytes.len() / 2]; + assert!(matches!(decode(cut), Err(RawError::Corrupt(_) | RawError::NotRaw)), "{:?}", decode(cut).err()); + // the length kept but a run of the sensor data overwritten with an invalid code + let mut garbled = bytes.clone(); + let mid = garbled.len() / 2; + garbled[mid..mid + 4096].fill(0xff); + assert!(matches!(decode(&garbled), Err(RawError::Corrupt(_))), "{:?}", decode(&garbled).err()); +} diff --git a/crates/raw/tests/it/main.rs b/crates/raw/tests/it/main.rs index 8834ed0f0..88840b2ca 100644 --- a/crates/raw/tests/it/main.rs +++ b/crates/raw/tests/it/main.rs @@ -7,6 +7,7 @@ mod corpus; mod coverage_sweep_corpus; mod cr3_corpus; mod cr3_variants; +mod crw_corpus; mod crx_properties; mod dng; mod lossy_dng; diff --git a/crates/raw/tests/it/vendor_robust.rs b/crates/raw/tests/it/vendor_robust.rs index cb79c87da..446c1fb60 100644 --- a/crates/raw/tests/it/vendor_robust.rs +++ b/crates/raw/tests/it/vendor_robust.rs @@ -1,4 +1,4 @@ -//! Malformed vendor raw files (NEF, ARW, PEF, ORF, RW2 of every raw format, CR2, RAF) must decode to an error, never panic. +//! Malformed vendor raw files (NEF, ARW, PEF, ORF, RW2 of every raw format, CR2, RAF, CRW) must decode to an error, never panic. use lightcraft_tiff::tags as t; use lightcraft_tiff::{ByteOrder, IfdBuilder, ImageData, TiffWriter, Value}; @@ -226,8 +226,48 @@ fn raf() -> Vec { raf_file(W, H, 16, strip, Some(layout), b"\xff\xd8\xff\xd9") } +/// A Canon CRW: CIFF header, a raw-data payload of pseudo-random bytes, and a nested properties +/// heap with the records the decoder reads. +fn crw() -> Vec { + let le16 = |v: u16| v.to_le_bytes(); + let le32 = |v: u32| v.to_le_bytes(); + let heap = |records: &[(u16, Vec)]| -> Vec { + let mut b = Vec::new(); + let mut entries = Vec::new(); + for (tc, v) in records { + entries.push((*tc, v.len() as u32, b.len() as u32)); + b.extend_from_slice(v); + if b.len() % 2 == 1 { + b.push(0); + } + } + let tbl = b.len() as u32; + b.extend_from_slice(&le16(entries.len() as u16)); + for (tc, l, off) in entries { + b.extend_from_slice(&le16(tc)); + b.extend_from_slice(&le32(l)); + b.extend_from_slice(&le32(off)); + } + b.extend_from_slice(&le32(tbl)); + b + }; + let sensor: Vec = [34u16, W as u16, H as u16, 0, 0, 2, 2, W as u16 - 3, H as u16 - 3].iter().flat_map(|&v| le16(v)).collect(); + let dtable: Vec = [0u32, 0, 514, 0].iter().flat_map(|&v| le32(v)).collect(); + let props = heap(&[(0x1031, sensor), (0x1835, dtable), (0x080a, b"Canon\0Canon EOS D60\0".to_vec())]); + let payload: Vec = (0..514 + 256).map(|i| (i * 41 % 253) as u8).collect(); + let root = heap(&[(0x2005, payload), (0x300a, props)]); + let mut f = b"II".to_vec(); + f.extend_from_slice(&le32(26)); + f.extend_from_slice(b"HEAPCCDR"); + f.extend_from_slice(&le32(0x0001_0002)); + f.extend_from_slice(&[0; 8]); + f.extend_from_slice(&root); + f +} + fn samples() -> Vec> { vec![ + crw(), cfa_tiff("NIKON CORPORATION", 1, 12, ByteOrder::Big), cfa_tiff("NIKON CORPORATION", 34713, 14, ByteOrder::Big), cfa_tiff("SONY", 32767, 12, ByteOrder::Little), @@ -283,7 +323,7 @@ proptest! { #![proptest_config(ProptestConfig { cases: 2000, .. ProptestConfig::default() })] #[test] - fn mutated_vendor_files_never_panic(kind in 0usize..18, flips in proptest::collection::vec((any::(), any::()), 1..16), cut in any::()) { + fn mutated_vendor_files_never_panic(kind in 0usize..19, flips in proptest::collection::vec((any::(), any::()), 1..16), cut in any::()) { let mut data = samples().swap_remove(kind); let n = data.len(); for (i, v) in flips { diff --git a/docs/camera-preview-colour.md b/docs/camera-preview-colour.md index be50d23f8..41f9af0c1 100644 --- a/docs/camera-preview-colour.md +++ b/docs/camera-preview-colour.md @@ -1,6 +1,6 @@ -# Sony ARW, Nikon NEF, Panasonic RW2, Fujifilm RAF, Canon CR3/CR2, Pentax PEF, Samsung SRW, Olympus ORF, Epson ERF and Minolta MRW starting look +# Sony ARW, Nikon NEF, Panasonic RW2, Fujifilm RAF, Canon CR3/CR2/CRW, Pentax PEF, Samsung SRW, Olympus ORF, Epson ERF and Minolta MRW starting look -ARW, NEF, RW2, RAF, CR3, CR2, PEF, SRW, ORF, ERF and MRW decoding supply sensor data and camera white-balance multipliers, but currently no measured camera colour matrix. Treating camera RGB as linear sRGB produces a dark, muted default rendering. The loader now estimates a file-local starting look from that file's embedded camera JPEG, only when a usable camera matrix is absent. All output pixels still come from the RAW sensor data; there is no JPEG replacement or uniform saturation boost. +ARW, NEF, RW2, RAF, CR3, CR2, CRW, PEF, SRW, ORF, ERF and MRW decoding supply sensor data and camera white-balance multipliers (CRW only on the D60/10D/300D; on the other CRW bodies the fitted matrix absorbs the white balance), but currently no measured camera colour matrix — with one exception: the Canon EOS D60, whose matrices we measured on a ColorChecker (`crates/raw/src/chart_table.rs`). They give a D60 CRW its colour when its JPEG look cannot be fitted, and its white balance in Kelvin (below); an accepted fit still wins. Treating camera RGB as linear sRGB produces a dark, muted default rendering. The loader now estimates a file-local starting look from that file's embedded camera JPEG, only when a usable camera matrix is absent. All output pixels still come from the RAW sensor data; there is no JPEG replacement or uniform saturation boost. ## Colour and tone are separate @@ -48,7 +48,7 @@ Previously `SourceRef::load()` discarded the decoder's `SourceInfo`, and render Vendor RGB WB multipliers alone do **not** identify an absolute illuminant without camera calibration. The generic model inferred approximately 6829 K / −127 tint for one Sony sample; changing only temperature retained that spurious tint and caused a pink cast. Uncalibrated ARWs, NEFs, RW2s and RAFs keep the file's vendor WB at decode and expose **relative** WB controls around the as-shot look. 6500/0 is the internal neutral reference of that relative scale, not a claim about the capture's measured Kelvin temperature. Presets, picker/auto and manual controls use this reference consistently. Switching from As Shot to Custom first resolves both controls; changing one cannot inherit stale catalog tint. Reset restores the current as-shot reference. Old As Shot numbers in a catalog do not mark an otherwise unchanged photo as edited; Custom WB remains an edit. -**Kelvin through the camera's measured matrices (issue #730).** A raw of a camera in `lightcraft_raw::spectral` (52 bodies: ILCE-7M3, ILCE-7M4, ILCE-7RM3, D850, EOS 5D Mark III…) edits white balance in Kelvin like a DNG, whichever look it got: the matrices read its as-shot multipliers as a temperature and tint (`spectral::as_shot_white_through`, the DNG `AsShotNeutral` reading with the model's `ColorMatrix1/2`) and the Temp slider runs 2000–50000 K with As Shot at that reading. The matrices do nothing else: the colour look stays the file's JPEG fit (or the profile, or the spectral fallback), and the picture is adapted from the as-shot white to the chosen one with Bradford, as every fitted look is — not re-developed in camera space as a DNG is. That keeps one adaptation model per camera whether or not a photo's fit was accepted, and it is what makes the upgrade exact: white balance written on the relative scale before this (no `scale` in the settings' `wb`, `WbScale::Legacy`) renders as the composition of the two Bradford adaptations, which is again one Bradford adaptation (`lightcraft_pipeline::local::legacy_white`), so existing edits are pixel-identical; they are converted to Custom Kelvin values the first time they are edited (`normalize_wb`). The decision is one function on both sides — `lightcraft_catalog::relative_wb_camera(format, camera)` from the catalog's camera string and `files::WbScaleOf` from the file — and a test keeps them in step. A photo imported before this keeps the relative scale's 6500 / 0 as its recorded as-shot white until its file is next decoded for display or reloaded (`Op::SetAsShotWb`, bookkeeping); only the readout waits, not the look. Raws of cameras without measured matrices stay relative, unless their own white-balance presets give the scale (next). +**Kelvin through the camera's measured matrices (issue #730).** A raw of a camera in `lightcraft_raw::spectral` (52 bodies with spectral matrices: ILCE-7M3, ILCE-7M4, ILCE-7RM3, D850, EOS 5D Mark III…, and the Canon EOS D60, whose matrices we measured on a ColorChecker under tungsten and sun, `chart_table.rs`, `clean-room/crw/10-chart-calibration.md`) edits white balance in Kelvin like a DNG, whichever look it got: the matrices read its as-shot multipliers as a temperature and tint (`spectral::as_shot_white_through`, the DNG `AsShotNeutral` reading with the model's `ColorMatrix1/2`) and the Temp slider runs 2000–50000 K with As Shot at that reading. The matrices do nothing else: the colour look stays the file's JPEG fit (or the profile, or the spectral fallback), and the picture is adapted from the as-shot white to the chosen one with Bradford, as every fitted look is — not re-developed in camera space as a DNG is. That keeps one adaptation model per camera whether or not a photo's fit was accepted, and it is what makes the upgrade exact: white balance written on the relative scale before this (no `scale` in the settings' `wb`, `WbScale::Legacy`) renders as the composition of the two Bradford adaptations, which is again one Bradford adaptation (`lightcraft_pipeline::local::legacy_white`), so existing edits are pixel-identical; they are converted to Custom Kelvin values the first time they are edited (`normalize_wb`). The decision is one function on both sides — `lightcraft_catalog::relative_wb_camera(format, camera)` from the catalog's camera string and `files::WbScaleOf` from the file — and a test keeps them in step. A photo imported before this keeps the relative scale's 6500 / 0 as its recorded as-shot white until its file is next decoded for display or reloaded (`Op::SetAsShotWb`, bookkeeping); only the readout waits, not the look. Raws of cameras without measured matrices stay relative, unless their own white-balance presets give the scale (next). **Kelvin through the camera's white-balance presets.** Sony ARWs store, in the encrypted `SR2SubIFD`, the multipliers of every white-balance preset, five of them at a stated temperature (2500, 3200, 4500, 6000 and 8500 K). For a camera without measured matrices whose files carry them (`lightcraft_raw::kelvin_presets`: Sony ILCA-99M2, ILCA-77M2, SLT-A33, DSC-RX10, ILCE-3500, ILCE-7S) they take the matrices' place in everything above: the as-shot multipliers are placed on the curve through the five presets (mired linear along it, in log red/green and blue/green), an offset from the curve is the tint, and the decision, the Bradford adaptation, the pixel-identical upgrade and `Op::SetAsShotWb` are the same. The scale is Sony's own: files shot on the Kelvin setting read back what the dial said (ILCA-77M2 3400 K → 3396 K and 5200 K → 5193 K, SLT-A33 4700 K → 4708 K, the public ILCE-3500 sample's 5600 K → 5594 K; with the colour filter one step towards magenta, the SLT-A33's 3400 K reads 3473 K / +11), and the Shade preset reads 7493 K on the public ILCE-7S sample. Off the curve, two constants come from the 14 Sony bodies that have both measured matrices and presets: the tint is the offset scaled by the locus's own spacing (the matrices give 0.90–1.04 of it), and the isotherms lean against the curve's normal (`ISOTHERM_TILT` 0.74, measured 0.59–0.84). On 150 ILCE-7SM2 photos read both ways, the presets' temperature tracks the matrices' to ±4 mired around Sony's 8 % higher scale, and the tint to −9 ± 4. A listed camera whose file has no presets keeps the Kelvin scale with As Shot at D65, as a covered camera without multipliers does. Bodies with measured matrices keep reading through them. diff --git a/docs/gaps.md b/docs/gaps.md index 0e650528d..3ff39b54f 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Gaps: where LightCraft falls short of Lightroom -> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-29 · **Change:** minor (LR-LIB-VIRTUALCOPY: default number exhaustion is validated before copy creation; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; 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; typed GPS input now rejects malformed coordinates before metadata changes; LR-MAP-TRACKLOG decodes GPX character references in coordinates, elevations and timestamps; malformed references error; CDATA stays literal; ranked gaps unchanged; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; 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; import: checked rename-sequence exhaustion; import review: remembered thumbnail size and density-aware previews (#2237, part of #2056); pre-import loupe/keyboard culling remains open; no ranked gap closes; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; 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; gap 9: CI runs the tests on Linux x64 since #2667; macOS, Windows and the GPU equivalence tests still don't; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; 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; gap 8: Sony ILCE-7RM6 in-file distortion conversion extends to the APS-C ILCE-6700; LR-META-EXIF-FULL rejects extra EXIF clock components and preserves date fallbacks; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; 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; JPEG ICC APP2 sequence validation; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; 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; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; 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; gap 24: extreme brush segments skip the intermediate dabs; **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-20 · **Change:** minor (gap 15: Print Page Options — page numbers, page info, crop marks) +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-29 · **Change:** minor (gap 1: the Canon EOS D60 is the first chart-calibrated model, its matrices from our own ColorChecker shots give its white balance a Kelvin scale; gap 20: Canon CRW decodes; LR-LIB-VIRTUALCOPY: default number exhaustion is validated before copy creation; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; 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: 557 models verified with CRW and MRW; 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; typed GPS input now rejects malformed coordinates before metadata changes; LR-MAP-TRACKLOG decodes GPX character references in coordinates, elevations and timestamps; malformed references error; CDATA stays literal; ranked gaps unchanged; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; 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: 557 models verified with CRW and MRW; 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; import: checked rename-sequence exhaustion; import review: remembered thumbnail size and density-aware previews (#2237, part of #2056); pre-import loupe/keyboard culling remains open; no ranked gap closes; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; 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: 557 models verified with CRW and MRW; 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; gap 9: CI runs the tests on Linux x64 since #2667; macOS, Windows and the GPU equivalence tests still don't; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; 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: 557 models verified with CRW and MRW; 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; gap 8: Sony ILCE-7RM6 in-file distortion conversion extends to the APS-C ILCE-6700; LR-META-EXIF-FULL rejects extra EXIF clock components and preserves date fallbacks; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; 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: 557 models verified with CRW and MRW; 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; JPEG ICC APP2 sequence validation; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; 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: 557 models verified with CRW and MRW; 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; gap 8: lens profile work is tracked in #329; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; 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: 557 models verified with CRW and MRW; 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; gap 24: extreme brush segments skip the intermediate dabs; **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-20 · **Change:** minor (gap 15: Print Page Options — page numbers, page info, crop marks) 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 @@ -15,7 +15,7 @@ Hours are Opus 5.5 agent wall-clock hours (see [target-app-parity.md](target-app | # | Gap | Evidence | User impact | Hours | Doc | |---:|---|---|---|---:|---| -| 1 B | **Camera colour is estimated, not measured** (LR-PROF-CAMERACOLOR). Raws start from a per-file fit to the camera JPEG, 9 bundled profiles or 52 spectral matrices (plus same-sensor bodies, e.g. the X-T30 through the X-T3); others use a neutral matrix (a black-and-white camera JPEG gives its tone curve only, #577; dark JPEG tones also guide the toe, #1307) | ΔE vs camera JPEG 2.9–10 after fitting (raw-parity.md); 0 chart-calibrated models vs 1,500 Adobe Standard profiles | every raw shooter sees colour that is close but not Lightroom's; some get neutral-matrix colour | 50–90 | [raw-parity](raw-parity.md#colour) | +| 1 B | **Camera colour is estimated, not measured** (LR-PROF-CAMERACOLOR). Raws start from a per-file fit to the camera JPEG, 10 bundled profiles, 52 spectral matrices (plus same-sensor bodies, e.g. the X-T30 through the X-T3) or chart-measured matrices (the EOS D60); others use a neutral matrix (a black-and-white camera JPEG gives its tone curve only, #577; dark JPEG tones also guide the toe, #1307) | ΔE vs camera JPEG 2.9–10 after fitting (raw-parity.md); 1 chart-calibrated model (EOS D60: its Kelvin scale and fallback colour, the look stays the JPEG fit) vs 1,500 Adobe Standard profiles | every raw shooter sees colour that is close but not Lightroom's; some get neutral-matrix colour | 50–90 | [raw-parity](raw-parity.md#colour) | | 2 B | **CR3 variants no public file uses** (LR-IMP-FORMATS): `crx.rs` refuses vertical-tile C-RAW, partial-subband and other plane coding, multi-tile 0x200 and adaptive low-frequency gains, and those files fall back to the preview. All 110 CC0 CR3s on raw.pixls.us (31 folders) decode from sensor data, so none of these variants has a public sample; 26 CR3 bodies are verified (M50 / R100 / R8 exact, R5 Mark II pinned, 22 more pinned on one CC0 file each and checked against the camera JPEG, [cr3.md](cr3.md#further-bodies-one-cc0-file-each)). HEIF stills are `.HIF` files, not CR3; no CR3 sRAW / mRAW sample exists | measured: Canon's share whose camera fully works 73% → 94% (raw-parity.md); 18 of 22 popular Canon bodies write CR3, all 18 now verified | +0.3 points of photographers; the variants surface only as user files that open as a preview | 10–25 | [raw-parity](raw-parity.md#remaining-work-ranked-by-photographers-unlocked-per-hour) | | 3 B | **Render fidelity unmeasured** (LR-BEHAV-RENDER-FIDELITY): tone, highlights, texture / clarity / dehaze, NR and sharpening are tuned by eye | no side-by-side suite; a retune needs a new process version (only V1 exists) | edits imported from Lightroom look different; the look "isn't Lightroom's" | 30–60 | [raw-parity](raw-parity.md), [process-versions](process-versions.md) | | 4 | ~~Original-pixel detail at 100%~~ (LR-VIEW-ZOOM): closed 2026-10-12, see Closed recently | checklist row ✅ | — | 0 | [ui-parity](ui-parity.md) | @@ -24,7 +24,7 @@ Hours are Opus 5.5 agent wall-clock hours (see [target-app-parity.md](target-app | 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 and ILCE-6700 distortion (`docs/sony-ilce7rm6-lens-corrections.md`) and Nikon Z 6II / Z 8 / Z f distortion (`docs/nikon-lens-corrections.md`); tracked in #329 | 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 or Windows, and no CI job runs the GPU equivalence tests**: the CI gate (`ci.yml`, #2667) runs `cargo xtask ci`, which includes `cargo test`, on Linux x64 for every push to `main` and every pull request; FreeBSD runs `cargo test` nightly, on pushes to `release` and on PRs that change its workflow or `packaging/freebsd/`; the macOS and Windows jobs build packages | `.github/workflows/` (ci, freebsd, packaging-lint, release, sonarcloud, windows-arm64); the gate's runner has Mesa software Vulkan drivers installed, but `Gpu::new` refuses software adapters (`crates/gpu/src/ctx.rs`), so the CPU ↔ GPU equivalence tests skip; the linux-riscv64 release job added in 0.6.0 (#844) runs only `lightcraft-cli --version` under QEMU; #1148 (open, not merged; #2667 names it superseded) has the macOS and Windows test legs and a `LIGHTCRAFT_GPU_ALLOW_SOFTWARE` opt-in that runs the GPU tests on lavapipe, Metal and WARP | a regression that only shows on macOS or Windows, or only on a GPU, reaches users; `cargo xtask ci` runs only on Linux in CI | 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) | +| 10 B | **Per-model verification thin, plus cheap per-body decode fixes**: 557 of 1,446 Adobe models verified (39%), ~86% 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; catalog VIDEO records match the Videos filter, with older classification repaired on reimport; 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) | @@ -34,7 +34,7 @@ Hours are Opus 5.5 agent wall-clock hours (see [target-app-parity.md](target-app | 17 | **Tethered capture** | ⬜ | studio photographers | 25–45 + cameras | [hardware-parity](hardware-parity.md) | | 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) | +| 20 | **Long-tail raw formats**: compressed SRW, ARQ pixel shift, IIQ, Hasselblad compression, X3F, KDC, DCR, GPR, old FinePix (MRW decodes since 2026-10-18; ERF and MEF since 2026-10-12; CRW since 2026-10-13, the PowerShot G5 anomaly and pre-CIFF compacts remain preview) | raw-parity.md | small user share each | 35–75 | [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; 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) | @@ -98,6 +98,7 @@ Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform w | 2026-10-25 | patch | LR-META-EXIF-FULL rejects extra clock components rather than projecting a plausible prefix; valid DateTimeDigitized, IFD0 DateTime and XMP fallbacks remain available, raw tag values stay visible, and the status / feature counts do not change | | 2026-10-25 | patch | JPEG ICC APP2 chunks are validated as a complete, uniquely numbered sequence before colour conversion; ranked gaps and readiness counts unchanged | | 2026-10-29 | minor | Gap 5: Object / Describe masks are enlarged along the photo's edges, so a raised Exposure no longer outlines the subject (#3272); the gap (heuristic Subject / Sky / People, no model mirror) and its hours are unchanged | +| 2026-10-13 | minor | Gap 20: Canon CRW decoded (clean-room derivation; EOS and G1/G6 verified pixel-exact; G5 preview) — removed from the long-tail list; hours 40–80 → 35–75 after merging main (MRW) | | 2026-10-24 | minor | Gap 28: ⌘⌥⇧C / ⌘⌥⇧V (Ctrl+Alt+Shift+C / V on Windows and Linux) copy and paste metadata; no key leaves the entry; hours unchanged | | 2026-10-24 | minor | Gap 30 shrinks: the Info panel's Show Metadata For (Target Photo / Selected Photos, `view.infoSelectedPhotos`) shows the selected photos' shared values and marks fields that differ as Mixed; the All Metadata dialog remains; 1–3 h (was 4–8) | | 2026-10-24 | patch | Typed GPS input now validates the complete coordinate expression, rejecting unknown text, wrong-axis or conflicting hemispheres, and invalid DMS minutes/seconds before changing metadata; ranked gaps, estimates and readiness counts unchanged | @@ -176,3 +177,4 @@ Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform w | 2026-10-14 | minor | Gap 10: 515 of 1,446 models verified (36%), ~84% of photographers on a verified body (integration batch 36) | | 2026-10-24 | patch | LR-META-XMP: sidecar keywords are written as names in `dc:subject` and paths in `lr:hierarchicalSubject`, so other DAMs can search them; ranked gaps and estimates unchanged | | 2026-10-20 | minor | Opt-in Windows DirectComposition preference added; VRR hardware verification pending; parity counts unchanged | +| 2026-10-25 | minor | Gap 1: first chart-calibrated model, the Canon EOS D60 (ColorChecker Classic under tungsten and sun, `crates/raw/src/chart_table.rs`); the gap stays open, hours unchanged | diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index 46aabad73..600df0a08 100644 --- a/docs/parity-checklist.md +++ b/docs/parity-checklist.md @@ -1,6 +1,6 @@ # Lightroom parity checklist -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (LR-BEHAV-ACCESS: the Flatpak may talk to the accessibility bus; LR-LIB-ALBUM: album commands leave out photos the library no longer has and record nothing when they add nothing; LR-BEHAV-DRAGDROP: a right-click on a text field while photos are carried from the grid opens no Cut / Copy / Paste menu; LR-BEHAV-DRAGDROP: a right-click while photos are carried from the grid opens no context menu; LR-LIB-VIRTUALCOPY: default numbering is preflighted across the requested batch and explicit names bypass suffix allocation; LR-BEHAV-DRAGDROP: only the primary button drags photos out of the grid and drops them; KEYC-RATING: deleting a keyword while Enable Filters has the filter put aside no longer leaves an empty one there; LR-BEHAV-DRAGDROP: a drop's destination is named with its parent when another folder or album has the same name; LR-VIEW-FILMSTRIP: photos drag from the filmstrip to the same sidebar targets as from the grid; LRC-LIB-LIBFOLDERS: a deep row's count follows its name in a narrow panel; LR-FILT-FLAG / LRC-LIB-FILTERBAR: the flag toggles combine, Picked + Unflagged, #2588; LR-VIEW-PHOTOGRID, LR-VIEW-FILMSTRIP, LR-VIEW-COMPARE, LR-VIEW-SURVEY, LRC-LIB-REFVIEW, LRC-LIB-PEOPLE: the active photo's selection ring shows on light themes and in Compare, Survey and the face tiles, and needs a loaded thumbnail; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; LR-META-LOCATION: Info GPS text is fully validated before metadata changes; LR-MAP-TRACKLOG decodes GPX character references in point fields; malformed references error; CDATA stays literal; LR-BEHAV-ACCESS: the Thumbnail size control is a slider to screen readers; LR-BEHAV-ACCESS: the Detail photo is named with its file name; LR-VIEW-PHOTOGRID: the grid's empty-view message names its shortcut as this platform writes it and as it is bound; LR-BEHAV-ACCESS: the grid's date headers are named as they read; LR-BEHAV-ACCESS: the sidebar's disclosure triangles name their row; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌘⌥⇧K Set Keyword Shortcut and ⇧K; KEYC-METADATA: ⌘⇧K / Ctrl+Shift+K Edit Keywords; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; LRC-LIB-FOLDERS: Local's built-in places (Pictures, Desktop, Downloads, Home) offer no Rename Folder… or Move Folder To…, and `folder.rename` / `folder.move` refuse the home folder and its Pictures, Desktop and Downloads; LRC-LIB-CATALOG-SWITCH: a library's catalog.snap or folder given at launch opens that library; LR-IMP-ADD-DIALOG / LR-IMP-MOVE: checked rename-sequence exhaustion; LR-IMP-ADD-DIALOG: an import asks the disk about the probes a scan left, and for each photo's sidecar, up to 16 files at a time; LRC-LIB-LIBFOLDERS: Don't ask again in the Move Photos question is kept only when the move started; a refused move leaves the question on; LR-IMP-ADD-DIALOG: remembered import thumbnail size, density-aware bounded previews and one job per candidate/size bucket (#2237, part of #2056); import loupe remains open; counts unchanged; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; LR-MERGE-HDR / LR-MERGE-PANO / LR-MERGE-HDRPANO: the dialog makes a new preview each time it opens, also after a failed one, #2708; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; LRC-LIB-MISSING: `library.missing` from the control channel checks the files on a worker thread instead of the UI thread; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; LR-CROP-AUTO: Auto straighten reads the vertical vanishing point and the horizon; LR-META-EXIF-FULL: reject extra EXIF clock components so invalid capture-time candidates fall through to valid metadata; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; LR-META-XMP / LRC-MAP-TRACKLOG: malformed ISO times are not projected or matched; LR-IMP-FORMATS: JPEG ICC APP2 chunk validation; LR-BEHAV-TITLEBAR: a dialog leaves the title bar undimmed, as a macOS sheet does; LR-BEHAV-ACCESS: the bars are toolbars and the panels landmarks for screen readers; LR-BEHAV-ACCESS: every dialog and prompt is exposed as a dialog (a modal one, an alert dialog for the prompts); LR-BEHAV-ACCESS: icon-only buttons are named by their label, with the shortcut as the description; MENU-HELP-HELP: on macOS the Help menu is registered with the application only once the menu bar is installed (muda ignores the call before), so AppKit knows which menu is Help; LR-BEHAV-ACCESS: a slider's value field is named ` value`, with how to use it as its description; MENU-BAR: every shortcut the app shows on macOS lists its modifiers ⌃⌥⇧⌘, as the menu bar does; MENU-BAR: on macOS two menu rows that run the same command each get their own native row and label (File ▸ Export… and File ▸ Export with Preset ▸ Custom…; File ▸ New Album… and Photo ▸ Add to Album ▸ New Album…); LR-BEHAV-DIALOGS: a one-line prompt that is refused stays open and says why, and a New Folder on Disk says it was made; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; LR-VIEW-HISTOGRAM: the shadow and highlight clipping buttons take their tile and chevron from the theme, so the chevron reads in every theme; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; LR-MASK-BRUSH: extreme brush segments skip the intermediate dabs; LR-MASK-BRUSH: with the mask overlay hidden, the stroke being painted no longer tints the photo) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (LR-IMP-FORMATS: the Canon CRW decoder is the shared craft-libs crate `craft-crw`; LR-PROF-CAMERACOLOR: chart-measured matrices for the Canon EOS D60, its white balance in Kelvin; CRW imports name their camera; LR-BEHAV-ACCESS: the Flatpak may talk to the accessibility bus; LR-LIB-ALBUM: album commands leave out photos the library no longer has and record nothing when they add nothing; LR-BEHAV-DRAGDROP: a right-click on a text field while photos are carried from the grid opens no Cut / Copy / Paste menu; LR-BEHAV-DRAGDROP: a right-click while photos are carried from the grid opens no context menu; LR-LIB-VIRTUALCOPY: default numbering is preflighted across the requested batch and explicit names bypass suffix allocation; LR-BEHAV-DRAGDROP: only the primary button drags photos out of the grid and drops them; KEYC-RATING: deleting a keyword while Enable Filters has the filter put aside no longer leaves an empty one there; LR-BEHAV-DRAGDROP: a drop's destination is named with its parent when another folder or album has the same name; LR-VIEW-FILMSTRIP: photos drag from the filmstrip to the same sidebar targets as from the grid; LRC-LIB-LIBFOLDERS: a deep row's count follows its name in a narrow panel; LR-FILT-FLAG / LRC-LIB-FILTERBAR: the flag toggles combine, Picked + Unflagged, #2588; LR-VIEW-PHOTOGRID, LR-VIEW-FILMSTRIP, LR-VIEW-COMPARE, LR-VIEW-SURVEY, LRC-LIB-REFVIEW, LRC-LIB-PEOPLE: the active photo's selection ring shows on light themes and in Compare, Survey and the face tiles, and needs a loaded thumbnail; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; LR-META-LOCATION: Info GPS text is fully validated before metadata changes; LR-MAP-TRACKLOG decodes GPX character references in point fields; malformed references error; CDATA stays literal; LR-BEHAV-ACCESS: the Thumbnail size control is a slider to screen readers; LR-BEHAV-ACCESS: the Detail photo is named with its file name; LR-VIEW-PHOTOGRID: the grid's empty-view message names its shortcut as this platform writes it and as it is bound; LR-BEHAV-ACCESS: the grid's date headers are named as they read; LR-BEHAV-ACCESS: the sidebar's disclosure triangles name their row; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌘⌥⇧K Set Keyword Shortcut and ⇧K; KEYC-METADATA: ⌘⇧K / Ctrl+Shift+K Edit Keywords; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; LRC-LIB-FOLDERS: Local's built-in places (Pictures, Desktop, Downloads, Home) offer no Rename Folder… or Move Folder To…, and `folder.rename` / `folder.move` refuse the home folder and its Pictures, Desktop and Downloads; LRC-LIB-CATALOG-SWITCH: a library's catalog.snap or folder given at launch opens that library; LR-IMP-ADD-DIALOG / LR-IMP-MOVE: checked rename-sequence exhaustion; LR-IMP-ADD-DIALOG: an import asks the disk about the probes a scan left, and for each photo's sidecar, up to 16 files at a time; LRC-LIB-LIBFOLDERS: Don't ask again in the Move Photos question is kept only when the move started; a refused move leaves the question on; LR-IMP-ADD-DIALOG: remembered import thumbnail size, density-aware bounded previews and one job per candidate/size bucket (#2237, part of #2056); import loupe remains open; counts unchanged; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; LR-MERGE-HDR / LR-MERGE-PANO / LR-MERGE-HDRPANO: the dialog makes a new preview each time it opens, also after a failed one, #2708; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; LRC-LIB-MISSING: `library.missing` from the control channel checks the files on a worker thread instead of the UI thread; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; LR-CROP-AUTO: Auto straighten reads the vertical vanishing point and the horizon; LR-META-EXIF-FULL: reject extra EXIF clock components so invalid capture-time candidates fall through to valid metadata; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; LR-META-XMP / LRC-MAP-TRACKLOG: malformed ISO times are not projected or matched; LR-IMP-FORMATS: JPEG ICC APP2 chunk validation; LR-BEHAV-TITLEBAR: a dialog leaves the title bar undimmed, as a macOS sheet does; LR-BEHAV-ACCESS: the bars are toolbars and the panels landmarks for screen readers; LR-BEHAV-ACCESS: every dialog and prompt is exposed as a dialog (a modal one, an alert dialog for the prompts); LR-BEHAV-ACCESS: icon-only buttons are named by their label, with the shortcut as the description; MENU-HELP-HELP: on macOS the Help menu is registered with the application only once the menu bar is installed (muda ignores the call before), so AppKit knows which menu is Help; LR-BEHAV-ACCESS: a slider's value field is named ` value`, with how to use it as its description; MENU-BAR: every shortcut the app shows on macOS lists its modifiers ⌃⌥⇧⌘, as the menu bar does; MENU-BAR: on macOS two menu rows that run the same command each get their own native row and label (File ▸ Export… and File ▸ Export with Preset ▸ Custom…; File ▸ New Album… and Photo ▸ Add to Album ▸ New Album…); LR-BEHAV-DIALOGS: a one-line prompt that is refused stays open and says why, and a New Folder on Disk says it was made; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; LR-VIEW-HISTOGRAM: the shadow and highlight clipping buttons take their tile and chevron from the theme, so the chevron reads in every theme; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; LR-FILT-SEARCH-META: `>=` / `<=` in fielded search tokens; MENU-FILE-CLOSE, KEY-CLOSEWINDOW: the notes say Close Window quits the app, after the same check as Quit; MENU-EDIT-SELECTNONE, MENU-EDIT-PASTESELECTED: Edit lists Select Only Active Photo after Deselect All and Paste Settings from Previous after Paste Selected Settings…; on macOS no doubled separators are left where Settings… moved to the app menu; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; LR-MASK-BRUSH: extreme brush segments skip the intermediate dabs; LR-MASK-BRUSH: with the mask overlay hidden, the stroke being painted no longer tints the photo) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 The measured, row-by-row checklist: one row per Lightroom feature, menu item and shortcut, what we have and what is missing. It answers *does the feature exist?* (feature breadth). Whether it is good @@ -80,8 +80,8 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts). | LR-IMP-PROFILES | Import profiles & presets | P1 | ✅ | `cmd:file.importPresets`, `cmd:preset.import`, `cmd:profile.import`, `crates/engine/src/preset_import.rs`, `crates/engine/src/preset_luminar.rs` | presets: .lcpreset, XMP, classic .lrtemplate (local adjustment templates become mask effects, LR-MASK-EFFECTS), photos carrying edits ("DNG presets"), Luminar looks (.lmp, .mplumpack collections; sliders with a counterpart), .zip bundles, folders (folder → group), drag & drop (an `.xmp` dropped together with its photo is that photo's sidecar and is not imported as a preset, issue #936); masks carried over; unmapped settings reported (fields that only describe the preset or photo, e.g. `Cluster`, `SortName`, `SupportsAmount2`, the as-shot white, empty Point Color slots, are not); profiles: .cube 3D LUTs (one toast reports both halves; a LUT that fails is named with its reason); a preset that names a profile (`crs:Look`) applies the imported profile of that name, imported before or after it, and Lightroom's basic profiles map to ours (`crates/engine/src/profile_refs.rs`, issue #1001); Adobe profile formats deliberately unsupported (so `.xmp` creative profiles' colour tables aren't read) | | LR-IMP-LOCAL | Work on files in place | P0 | ✅ | `cmd:library.browse`, `cmd:photo.addToLibrary`, `cmd:library.import` (mode add), `cmd:local.addRoot`, `cmd:local.hide`, `cmd:local.restoreHidden`, `crates/engine/src/cmd/browse.rs`, `crates/ui-egui/src/panels/left.rs` (`local_section`), `crates/ui-egui/src/panels/grid.rs` (`folder_header`) | Local: browse Pictures / Desktop / Downloads / Home or any folder without adding it (breadcrumb, Include subfolders, Add N to My Photos); browsed photos stay out of All Photos, albums and counts (album commands skip them and the album menus say to Add to My Photos first, issue #684); folders picked with Browse Folder… (or Keep in Local / Add to Local on a folder row) stay listed across restarts, and browsing a folder inside a listed one highlights it inside that tree (opened down to it, siblings reachable) instead of adding a root; edits go to XMP sidecars; importing promotes them; right-click a top-level location → Remove from Local hides the shortcut only (nothing on disk or in the catalog changes), saved with the UI state, and “Show N hidden locations” (or Browse Folder… on it) brings it back, each on its own from the row's list when several are hidden, or all at once with Restore All (issue #2114); a folder is one location however its path is spelled (`/` or `\`, trailing separator, `.`/`..`, drive-letter case; `folder_key` in `crates/catalog/src/query.rs`); browsed photos nobody changed are forgotten once their folder has not been browsed for 30 days (Settings → Performance → Local folders, 0 = never; checked when the library opens, or `cmd:library.forgetLocal` with `dryRun`; files and sidecars stay, browsing again brings them back; see `crates/catalog/src/local.rs` for what counts as a change); browsing a folder again forgets at once the untouched records of its files that another app deleted or moved, so they no longer fail to load on every visit (issue #1392; changed ones stay, shown as missing); browsing a folder keeps the active photo when the folder shows it, else makes the folder's first photo active as it arrives, the same rule as albums and the other sources (`Session::keep_active_in_view`; issue #1447: before, a library photo stayed active, so Photo Grid marked nothing and Detail, the histogram and the info showed a photo the folder doesn't hold) | | LR-IMP-SIDECAR-SPLIT | Separate XMP sidecar variants | P2 | ⬜ | `cmd:library.xmpPreferences` | sidecar naming option exists (stem/full), no split sidecars | -| LR-IMP-FORMATS | Supported formats | P0 | 🟡 | `crates/codecs/src/lib.rs`, `crates/raw/src/lib.rs`, `crates/raw/src/jxl.rs` | JPEG (ICC APP2 sequences require unique complete chunks with consistent totals; malformed profiles use the existing unsupported-profile sRGB fallback; a Samsung panorama whose scan data runs into its SEF trailer with no end-of-image marker reads as the image before the trailer, while a JPEG cut short is still refused, issue #3350, `crates/codecs/src/jpeg.rs`), PNG, TIFF (1- to 64-bit integer, 16- to 64-bit float, incl. bilevel and CCITT fax), WebP, JXL, PSD, GIF, BMP (lossy wide-gamut JXL decodes to the right colours: jxl-oxide desaturated XYB rendered to P3/Rec. 2020, worked around in `crates/codecs/src/jxl.rs`); DNG (incl. lossy / Smart Preview DNG and DNG 1.7 JPEG XL tiles/strips: lossless ones decode bit-exactly on synthetic CFA and LinearRaw files, lossy ones are restored to the declared primaries and transfer curve (linear, sRGB, BT.709 or a power; PQ / HLG stay preview-only) without clipping or gamut mapping, checked on one real file with 108 lossy tiles and on 2 Lightroom Classic 15.1 lossy LinearRaw DNGs (ILCE-7CM2) whose tiles declare Rec. 2100 primaries with the sRGB curve, which were preview-only before, and any tile that fails to decode makes the file preview-only; DNG 1.7 row/column-interleaved rasters (`RowInterleaveFactor` / `ColumnInterleaveFactor`) are undone before the mosaic is read, so a file from Adobe's converter with a 2×2 interleave no longer decodes as four scaled-down copies in the wrong colours; a dimension the factor does not divide decodes with short trailing fields; verified on 4 real ILCE-6400 JPEG XL DNGs rendering bit-identical to their non-interleaved twins, factors above 2 rest on the plain reading of the tag; a single-chunk JPEG XL preview IFD is offered as the embedded preview; a DNG whose samples pile up at one value well below its stated `WhiteLevel`, as a camera app's 12-bit Bayer DNG that states 4095 and clips at 3567, has those samples raised to the white level on decode, so highlight reconstruction sees the clipped areas instead of white balance tinting them magenta, issue #548, `crates/raw/src/saturation.rs`), CR2 (colour-filter layout from the file's `CR2CFAPattern` tag, issue #85), ARW (pre-2017 bodies: white balance and black level from the encrypted `SR2SubIFD`, issue #148, its `WB_RGGBLevels` being the white balance the camera applied, issue #535; the DSLR-A900's packed 12-bit strip, two pixels per three bytes, read only when the strip is exactly 1.5 bytes per pixel, with its camera-JPEG window centred, checked on one CC0 file (A850 unmeasured); downsized Sony lossless YCbCr 4:2:0 / 4:2:2 tiles decoded to linear RGB, verified on private ILCE-7M4 3:2 & 4:3 examples including 16-bit full-resolution export; Sony DSC-R1 .SR2 decodes, its big-endian 16-bit words read in the order that fits the sample range, checked on the CC0 3221 sample; its crop and white balance live in the SR2SubIFD and are not read yet; ARW6 Compressed / Compressed (HQ) of the ILCE-7RM6 (`crates/raw/src/vendor/arw6.rs`, `crates/raw/src/llvc.rs`; 12-bit codes exact against a black-box oracle on 8 CC0 files, FF over the full frame, APS-C in the interior (LibRaw's own edge rows differ); output through our own companding curve, measured from CC0 lossless / ARW6 pairs; geometry checked against the camera JPEG, `docs/arw6-compression.md`; the ILCE-7M5 writes the same codec, unverified)), NEF (uncompressed, lossless and lossy compressed 12/14-bit, `crates/raw/src/vendor/nefc.rs`), RAF (uncompressed, lossless/lossy compressed Bayer and X-Trans; `crates/raw/src/vendor/rafc.rs`, full sensor verification on 13 bodies, `docs/raf-compression.md`), RW2 / Leica RWL / Panasonic RAW in every raw format (compressed 4 and 6, the prefix-coded strips of format 8 from the GH6 on, packed 2/5/7, the 16-bit words of the 2005–2007 bodies; black levels, in-camera aspect crops and mapped-out defect pixels; 178 CC0 files from 118 bodies checked, `crates/raw/src/vendor/rw2.rs`), PEF, ORF (uncompressed; colour-filter layout from the file's Exif `CFAPattern`; 12.8 bit/px 16-byte blocks of ten 12-bit samples + a zero pad byte, E-300 / E-330 / E-500 verified on CC0 files, E-M5 Mark II / PEN-F high-res files decode smooth but CFA and black unverified), Kodak DCS520C TIFF (a two-component 12-bit lossless JPEG strip, the two components the even and odd columns; the file's `GrayResponseCurve` table linearises the companded samples to 12 bits, black measured per CFA position, white balance from the `KodakIFD`; `RawFormat::KodakDcs`, `crates/raw/src/vendor/kodak.rs`, checked on the one CC0 sample against its IFD0 thumbnail), Hasselblad 3FR and FFF (a TIFF without a DNG version tag that describes its CFA IFD, levels, crop and white balance with DNG's tags; `RawFormat::CfaTiff`, read by `dng::decode_as`, RGGB when the file states no `CFAPattern`, checked against the camera preview on the CC0 H4D-40 FFF and CFV 3FR; an FFF has no colour matrix of its own and takes the camera's from the original 3FR header it embeds in tag `0xC51B`; neither carries a JPEG, so their uncompressed 8-bit RGB preview (1217 × 913 in the H4D-40 FFF, 218 × 218 in the CFV 3FR) is the embedded preview, handed on as a baseline TIFF, `crates/raw/src/vendor/hasselblad.rs`): uncompressed (X1D, X1D II, X2D, CFV 100C) and Hasselblad's lossless JPEG with scan selection value 8 (every FFF; H3D, H4D and CFV-50c 3FR), `ljpeg::decode_hasselblad`, its rule measured on 14 CC0 files with the ljpeg-explorer tool, Minolta / Konica Minolta MRW (the `\0MRM` block container: `PRD` sensor size, 12-bit packed or 16-bit-word storage and the RGGB / GBRG cell, `WBG` gains in cell order, the `TTW` TIFF for Exif and orientation, black 0 and white from the saturation plateau; `crates/raw/src/vendor/mrw.rs`, DiMAGE 7, 7Hi, A1, A2, A200 and Dynax / Maxxum 7D checked on CC0 files against their previews), Samsung SRW without compression (NX5, NX10, NX11 and the NX20-generation bodies NX20, NX200, NX210, NX1000 and NX1100 as 12-bit packed samples in either bit order, EX1 and WB2000 as 16-bit words; layout, camera-JPEG framing, levels and white balance from the maker note, `crates/raw/src/vendor/srw.rs`; median ΔE vs the camera JPEG 2.2–7.6 on 11 of the 12 files from 10 bodies checked, 10.7 (5.7 registered) on the backlit WB2000 sample). 12-bit NEF black level: maker note `0x003d` is in 14-bit units (D750, D780, D850, D7500, Z 50 checked). Lossy type 2 NEF curves of version `0x44 0x40` (D780, D6, Z 5, Z 6, Z 7, Z 7_2) span a quarter of the sample range, read from the table's own point spacing. Import probes (`probe_info`) of compressed NEFs decode only the first 128 rows, the ones the masked-trailing-column test reads in both the probe and the full decode (the D5100 and D7000 write 8 masked columns at 0 with no maker-note tag for them, compressed and uncompressed alike), instead of the whole Huffman stream: ~3 ms instead of the full decode time per file (386 ms on a 24 MP D750 file), issue #708. CR3 lossless CRX Bayer and version 0x100/0x200 C-RAW decodes to sensor pixels with timed white balance, black/white levels and file crop (EOS R6 Mark III `ColorData` version 66: as-shot white balance and black level from the file, white level from the data, `docs/cr3.md`) (the shot's aspect ratio from the maker note's `AspectInfo`); full sensor samples match the reference on M50/R100/R8/RP, incl. RP 1.6× crop mode (`crates/raw/tests/it/cr3_corpus.rs`, `docs/cr3.md`). EOS R5 Mark II C-RAW and RAW decode too (the QP map's first-symbol literal and 23-zero escape, odd-height Bayer planes): 4 CC0 files pinned, every QP map of 33,665 private C-RAW files decodes exactly, and full-frame images match the camera JPEG. Unsupported coding variants retain metadata and use the embedded JPEG when available; HEVC preview decoding remains unsupported. Missing: CR3 unverified CRX variants, compressed ORF, compressed Samsung SRW (NX1, NX30, NX300, NX500, NX2000, NX3000, NX3300, NX mini: preview only with the reason), Nikon High Efficiency NEF (HE / HE★ from the Z 8, Z 9, Z 6III and Z f: compression 34713 with a JPEG XS codestream and no `0x0096` table; no public specification, so preview only with the reason "Nikon High Efficiency NEF", issue #193; Lossless compressed NEFs from the same bodies decode), NEF "lossy after split" (preview only: imported with `preview_only` = the decoder's reason, rendered as a rendered JPEG, flagged by a grid badge and loupe/Edit/Info notices, `previewOnly` in `catalog.query`; Reload clears it once the file decodes, and the notices carry a Reload from Disk button; each flag is stamped with the app version that set it, and a library opened in another version rechecks its preview-only raws in the background, so a raw a newer release decodes is cleared without a manual Reload, issue #662, `library.recheckPreviewOnly`), TIFF-based raws whose raw data sits in a private block (Phase One / Leaf IIQ, Canon EOS-1D / 1Ds and the Kodak DCS Pro / DCS760C TIFFs (compression 65000): IFD0 is only a reduced RGB image next to a much larger declared picture) are recognised from that structure and import as `preview_only` with the reason, instead of opening as a thumbnail-sized ordinary image (`thumbnail_shell` in `crates/raw/src/lib.rs`), Recognised raws LightCraft cannot decode open as `preview_only` with any usable embedded JPEG, also Canon CRW and Sigma X3F (containers recognised by magic, preview found by a structural JPEG scan), Kodak KDC (IFD0 without `ImageWidth`), Leaf MOS and Epson ERF (scan; ones with no JPEG still fail with "without an embedded preview", AVIF decode (HEIC decodes only behind the non-default `heif` feature, which the release packages enable: `crates/heif`, heic-rs for the container and HEVC (bit-exact against libde265 on iPhone tiles) plus our own colour conversion, matching libheif within 1 code value on iPhone-style grids (VUI full range, Display P3 ICC, nclx, 10-bit, irot/imir/clap, gain map/depth ignored); 4:2:0/monochrome only, 4:2:2/4:4:4 Canon/Sony HIF refused; same policy as PhotoCraft), palette (indexed-colour) TIFF (refused by the `tiff` crate); folder discovery and file pickers include Sony SR2 and SRF, with the same extension list used by CLI/MCP: DSC-R1 SR2 decodes from sensor data; DSC-F828 SRF stays preview-only; transparency is flattened onto white, in linear light, where the working image is built (`to_working` in `crates/codecs/src/lib.rs`): renders, previews, exports and contact sheets no longer turn transparent areas black, issue #1212; median ΔE vs the camera JPEG 2.2–7.6 on 11 of the 12 files from 10 bodies checked, 10.7 (5.7 registered) on the backlit WB2000 sample). 12-bit NEF black level: maker note `0x003d` is in 14-bit units (D750, D780, D850, D7500, Z 50 checked). Lossy type 2 NEF curves of version `0x44 0x40` (D780, D6, Z 5, Z 6, Z 7, Z 7_2) span a quarter of the sample range, read from the table's own point spacing. Import probes (`probe_info`) of compressed NEFs decode only the first 128 rows, the ones the masked-trailing-column test reads in both the probe and the full decode (the D5100 and D7000 write 8 masked columns at 0 with no maker-note tag for them, compressed and uncompressed alike), instead of the whole Huffman stream: ~3 ms instead of the full decode time per file (386 ms on a 24 MP D750 file), issue #708. CR3 lossless CRX Bayer and version 0x100/0x200 C-RAW decodes to sensor pixels with timed white balance, black/white levels and file crop (the shot's aspect ratio from the maker note's `AspectInfo`); the Hasselblad Lunar, a Sony NEX-7 under Make HASSELBLAD, decodes as an ARW: a HASSELBLAD file with Sony's raw coding (compression 32767 or tag `0x7010`) goes to the ARW reader, and the body for its `Tag2010` layout comes from the maker note's `SonyModelID`, checked on the CC0 4051 file, issue #1275; an uncompressed MOS, whose IFD0 is the 16-bit mosaic with no Make tag, is recognised by Leaf's data block `0x8606` instead of opening as a greyscale image of its mosaic, issue #1275), and any decode failure of a recognised raw, not only an unsupported variant Nikon 12-bit small RAW (D810 / D4S): packed Y0/Y1/Cb/Cr rows decode to linear RGB; header probes retain geometry without decoding pixels, and truncated strips are refused. Chroma gains are approximate. | -| LR-IMP-CAMERA-COVERAGE | Camera coverage, verified per model | P0 | 🟡 | `crates/raw/tests/it/corpus.rs`, `crates/raw/tests/it/coverage_sweep_corpus.rs`, `docs/raw/evidence.tsv`, `docs/raw/coverage-sweep.tsv`, `xtask/src/main.rs` | a supported container is not the same as every camera that writes it decoding correctly: ~55 CC0 corpus files plus 6 exact-sensor CR3 cases and 4 pinned EOS R5 Mark II files (plus ~60 NEFs checked by hand and 178 Panasonic / Leica RW2, RWL and RAW files from 118 bodies, checked against each file's own JPEG) against the >1,000 models Lightroom lists; per-model differences still surface (e.g. the CR2 colour-filter layout differed by model until it was read from the file's own tag, issue #85, checked on 35 CR2 bodies; CR2 black levels were read from border columns light already reaches, left of `SensorInfo`'s border, so CR2s rendered dark with crushed shadows (EOS 6D: 2591 instead of 2047), now measured from the masked columns only, checked on 8 corpus bodies; pre-2017 Sony ARWs opened green until their scrambled white balance / black level were read, issue #148, checked on 16 ARWs from 15 Sony bodies; their white balance now comes from the encrypted `SR2SubIFD`'s `WB_RGGBLevels`, the gains the camera applied, which 14 bodies (DSLR-A450 … A700, NEX-3/5/C3, SLT-A33/A35/A55, ILCE-6001, UMC-R10C) had no other source for and which the maker-note estimate read before missed by up to 100 % on preset shots, issue #535, checked against ExifTool on 100 decoded ARWs from 87 Sony bodies; the encrypted SR2 black level is now read from its directory entry (the DSLR-A450/A500/A550/A700 read 354–975 instead of 512), checked on 126 ARWs from 97 Sony bodies, and downsized lossless M/S ARWs use their measured black level 1024 instead of 512, checked on 12 files from 4 bodies, issue #535, and on a fifth, the ILCE-7CM2 (issue #697: 0.5.0 rendered its lossless M file with that 512 pedestal, the darkest half of a dusk scene +11 L* and blue, the lamplight white; its darkest 0.1 % per channel sits at 1017/1020/1024, and with 1024 the render is within +3 L* of the camera JPEG; the body's compressed and uncompressed "M-size" files are Bayer at 4736×3132 with black 512, two public ones in the corpus); pre-2017 Sony bodies shot in the camera's 16:9 mode (ILCE-7SM2, DSLR-A580) now get the 16:9 crop of their camera JPEG instead of the whole 3:2 frame with padding columns, issue #535; Canon PowerShot and EOS M CR2s whose `ColorData` is stored as bytes opened green until the bytes were read as 16-bit words, 25 files from 25 bodies checked; private Z 8 crop verified; the Nikon D4, which records no CropArea, gets its image area and white level measured on 148 NEFs against the camera JPEG (optical-black stripes and magenta clipped highlights gone); the D7100, whose data holds picture beyond the camera's frame, develops at Lightroom's frame (the camera JPEG's size) in full frame and 1.3x crop, so Lightroom crops line up (#1248).; CR2 white levels came from masked rows holding samples at 16383, so on the EOS 50D, 60D, 450D (Rebel XSi), 500D (Rebel T1i), 550D (Rebel T2i), 1200D (Rebel T5) and 100D (Rebel SL1) clipped highlights sat at 81–98% of the range and were not treated as clipped, now taken from the active area only, issue #1272). Compressed RAF sensor arrays verified on 13 Fujifilm bodies (`docs/raf-compression.md`). ILCE-7RM6 ARW6 12-bit codes verified on 8 CC0 files (FF and APS-C, both compression modes). Sony ILCE-7CR (61 MP): lossless compressed L and M and compressed ARW2 samples of one scene in the corpus (black level, default crop, as-shot gains, lossless vs compressed mosaic agreement, fitted camera look), and all nine public codings (also uncompressed, small and APS-C crop) decode and fit. Grow the CC0 corpus per model and test each sample decodes with plausible colour. Nikon CropArea and untagged-preview colour-space regression tests; the Nikon D4, which records no CropArea, gets its image area and white level measured on 148 NEFs against the camera JPEG (optical-black stripes and magenta clipped highlights gone). Pentax PEFs without area tags (KP, KF, K-3 Mark III, K-70, K-1, K-1 Mark II, K-S1, K-S2, 645Z) no longer keep their masked border: black, empty and saturated lines are trimmed against the maker-note BlackPoint, issue #1271, measured on 38 CC0 PEFs from 30 bodies.; Canon PowerShot and EOS M CR2s whose `ColorData` is stored as bytes opened green until the bytes were read as 16-bit words, 25 files from 25 bodies checked). Compressed RAF sensor arrays verified on 13 Fujifilm bodies (`docs/raf-compression.md`). ILCE-7RM6 ARW6 12-bit codes verified on 8 CC0 files (FF and APS-C, both compression modes). Sony ILCE-7CR (61 MP): lossless compressed L and M and compressed ARW2 samples of one scene in the corpus (black level, default crop, as-shot gains, lossless vs compressed mosaic agreement, fitted camera look), and all nine public codings (also uncompressed, small and APS-C crop) decode and fit. Grow the CC0 corpus per model and test each sample decodes with plausible colour. Nikon CropArea and untagged-preview colour-space regression tests; the Nikon D4, which records no CropArea, gets its image area and white level measured on 148 NEFs against the camera JPEG (optical-black stripes and magenta clipped highlights gone). The Canon PowerShot G9, which writes no `ColorData`, takes its as-shot white balance from the WBInfo row (`0x0029`) of the preset ShotInfo names, issue #1273, checked on 1 CC0 file against neutral areas of the camera JPEG (within 0.1 %).; the Nikon D4, which records no CropArea, gets its image area and white level measured on 148 NEFs against the camera JPEG (optical-black stripes and magenta clipped highlights gone). The Olympus E-10 (no ImageProcessing directory) takes its as-shot gains and its black level (`0x1012` in `0x102c` ValidBits units: 128 at 12 bits) from the main maker note, issue #1273: its render against the camera thumbnail goes from ΔE76 18.5 with a rejected fit to 11.5 with the fit accepted (measured by AlexChambers).; the Nikon D4, which records no CropArea, gets its image area and white level measured on 148 NEFs against the camera JPEG (optical-black stripes and magenta clipped highlights gone), and the Nikon D90 likewise on 10 of 550 NEFs (42 filler columns on the right that developed as a pink band; green clips below red and blue).; the Nikon D4, which records no CropArea, gets its image area and white level measured on 148 NEFs against the camera JPEG (optical-black stripes and magenta clipped highlights gone). Public raw.pixls.us coverage sweep (#1327): 30 more models verified on 40 CC0 files, with SHA-256 and FNV-1a sensor hashes pinned (`crates/raw/tests/it/coverage_sweep_corpus.rs`, `docs/raw/coverage-sweep.tsv`; `docs/raw/coverage.py`: 515 → 545 verified rows). Render/reference luminance correlation is 0.6801 to 0.9998; eleven candidates excluded for identity, provenance or missing/mismatched reference images. Nikon D6 / Z 6 / Z 7 uncompressed and Z 8 HE variants remain preview-only. Own-matrix DNG tone differs from the camera JPEG (#1277); sensor verification is not colour calibration. | +| LR-IMP-FORMATS | Supported formats | P0 | 🟡 | `crates/codecs/src/lib.rs`, `crates/raw/src/lib.rs`, `crates/raw/src/jxl.rs` | JPEG (ICC APP2 sequences require unique complete chunks with consistent totals; malformed profiles use the existing unsupported-profile sRGB fallback; a Samsung panorama whose scan data runs into its SEF trailer with no end-of-image marker reads as the image before the trailer, while a JPEG cut short is still refused, issue #3350, `crates/codecs/src/jpeg.rs`), PNG, TIFF (1- to 64-bit integer, 16- to 64-bit float, incl. bilevel and CCITT fax), WebP, JXL, PSD, GIF, BMP (lossy wide-gamut JXL decodes to the right colours: jxl-oxide desaturated XYB rendered to P3/Rec. 2020, worked around in `crates/codecs/src/jxl.rs`); DNG (incl. lossy / Smart Preview DNG and DNG 1.7 JPEG XL tiles/strips: lossless ones decode bit-exactly on synthetic CFA and LinearRaw files, lossy ones are restored to the declared primaries and transfer curve (linear, sRGB, BT.709 or a power; PQ / HLG stay preview-only) without clipping or gamut mapping, checked on one real file with 108 lossy tiles and on 2 Lightroom Classic 15.1 lossy LinearRaw DNGs (ILCE-7CM2) whose tiles declare Rec. 2100 primaries with the sRGB curve, which were preview-only before, and any tile that fails to decode makes the file preview-only; DNG 1.7 row/column-interleaved rasters (`RowInterleaveFactor` / `ColumnInterleaveFactor`) are undone before the mosaic is read, so a file from Adobe's converter with a 2×2 interleave no longer decodes as four scaled-down copies in the wrong colours; a dimension the factor does not divide decodes with short trailing fields; verified on 4 real ILCE-6400 JPEG XL DNGs rendering bit-identical to their non-interleaved twins, factors above 2 rest on the plain reading of the tag; a single-chunk JPEG XL preview IFD is offered as the embedded preview; a DNG whose samples pile up at one value well below its stated `WhiteLevel`, as a camera app's 12-bit Bayer DNG that states 4095 and clips at 3567, has those samples raised to the white level on decode, so highlight reconstruction sees the clipped areas instead of white balance tinting them magenta, issue #548, `crates/raw/src/saturation.rs`), CR2 (colour-filter layout from the file's `CR2CFAPattern` tag, issue #85), CRW (CIFF, the 2000–2004 Canon bodies: clean-room-derived decode frozen in `craft-libs crates/crw/clean-room/02-crw-format.md`, `crates/raw/src/vendor/crw.rs`; EOS D30/D60/10D/300D, PowerShot G1 (10-bit GBRG) and G6 pixel-exact against a black-box reference on 28 of 29 corpus files incl. 18 private D60s; as-shot white balance from the ColorBalance record's ShotInfo-selected slot; black level from the darkest flat masked-border strip; rotation from ImageInfo; starting look fitted from the file's own embedded JPEG like CR2/CR3 (`camera_preview::file_local_look`), accepted on all 10 decodable public corpus files (holdout RMS 0.009–0.047), with the fitted matrix absorbing the white balance on the bodies without a ColorBalance record (D30, PowerShots); the PowerShot G5's anomalous entropy coding and the pre-CIFF compacts stay preview-only, and G2/G3/G4/Pro1/S-series decode unverified for lack of redistributable samples), ARW (pre-2017 bodies: white balance and black level from the encrypted `SR2SubIFD`, issue #148, its `WB_RGGBLevels` being the white balance the camera applied, issue #535; the DSLR-A900's packed 12-bit strip, two pixels per three bytes, read only when the strip is exactly 1.5 bytes per pixel, with its camera-JPEG window centred, checked on one CC0 file (A850 unmeasured); downsized Sony lossless YCbCr 4:2:0 / 4:2:2 tiles decoded to linear RGB, verified on private ILCE-7M4 3:2 & 4:3 examples including 16-bit full-resolution export; Sony DSC-R1 .SR2 decodes, its big-endian 16-bit words read in the order that fits the sample range, checked on the CC0 3221 sample; its crop and white balance live in the SR2SubIFD and are not read yet; ARW6 Compressed / Compressed (HQ) of the ILCE-7RM6 (`crates/raw/src/vendor/arw6.rs`, `crates/raw/src/llvc.rs`; 12-bit codes exact against a black-box oracle on 8 CC0 files, FF over the full frame, APS-C in the interior (LibRaw's own edge rows differ); output through our own companding curve, measured from CC0 lossless / ARW6 pairs; geometry checked against the camera JPEG, `docs/arw6-compression.md`; the ILCE-7M5 writes the same codec, unverified)), NEF (uncompressed, lossless and lossy compressed 12/14-bit, `crates/raw/src/vendor/nefc.rs`), RAF (uncompressed, lossless/lossy compressed Bayer and X-Trans; `crates/raw/src/vendor/rafc.rs`, full sensor verification on 13 bodies, `docs/raf-compression.md`), RW2 / Leica RWL / Panasonic RAW in every raw format (compressed 4 and 6, the prefix-coded strips of format 8 from the GH6 on, packed 2/5/7, the 16-bit words of the 2005–2007 bodies; black levels, in-camera aspect crops and mapped-out defect pixels; 178 CC0 files from 118 bodies checked, `crates/raw/src/vendor/rw2.rs`), PEF, ORF (uncompressed; colour-filter layout from the file's Exif `CFAPattern`; 12.8 bit/px 16-byte blocks of ten 12-bit samples + a zero pad byte, E-300 / E-330 / E-500 verified on CC0 files, E-M5 Mark II / PEN-F high-res files decode smooth but CFA and black unverified), Kodak DCS520C TIFF (a two-component 12-bit lossless JPEG strip, the two components the even and odd columns; the file's `GrayResponseCurve` table linearises the companded samples to 12 bits, black measured per CFA position, white balance from the `KodakIFD`; `RawFormat::KodakDcs`, `crates/raw/src/vendor/kodak.rs`, checked on the one CC0 sample against its IFD0 thumbnail), Hasselblad 3FR and FFF (a TIFF without a DNG version tag that describes its CFA IFD, levels, crop and white balance with DNG's tags; `RawFormat::CfaTiff`, read by `dng::decode_as`, RGGB when the file states no `CFAPattern`, checked against the camera preview on the CC0 H4D-40 FFF and CFV 3FR; an FFF has no colour matrix of its own and takes the camera's from the original 3FR header it embeds in tag `0xC51B`; neither carries a JPEG, so their uncompressed 8-bit RGB preview (1217 × 913 in the H4D-40 FFF, 218 × 218 in the CFV 3FR) is the embedded preview, handed on as a baseline TIFF, `crates/raw/src/vendor/hasselblad.rs`): uncompressed (X1D, X1D II, X2D, CFV 100C) and Hasselblad's lossless JPEG with scan selection value 8 (every FFF; H3D, H4D and CFV-50c 3FR), `ljpeg::decode_hasselblad`, its rule measured on 14 CC0 files with the ljpeg-explorer tool, Minolta / Konica Minolta MRW (the `\0MRM` block container: `PRD` sensor size, 12-bit packed or 16-bit-word storage and the RGGB / GBRG cell, `WBG` gains in cell order, the `TTW` TIFF for Exif and orientation, black 0 and white from the saturation plateau; `crates/raw/src/vendor/mrw.rs`, DiMAGE 7, 7Hi, A1, A2, A200 and Dynax / Maxxum 7D checked on CC0 files against their previews), Samsung SRW without compression (NX5, NX10, NX11 and the NX20-generation bodies NX20, NX200, NX210, NX1000 and NX1100 as 12-bit packed samples in either bit order, EX1 and WB2000 as 16-bit words; layout, camera-JPEG framing, levels and white balance from the maker note, `crates/raw/src/vendor/srw.rs`; median ΔE vs the camera JPEG 2.2–7.6 on 11 of the 12 files from 10 bodies checked, 10.7 (5.7 registered) on the backlit WB2000 sample). 12-bit NEF black level: maker note `0x003d` is in 14-bit units (D750, D780, D850, D7500, Z 50 checked). Lossy type 2 NEF curves of version `0x44 0x40` (D780, D6, Z 5, Z 6, Z 7, Z 7_2) span a quarter of the sample range, read from the table's own point spacing. Import probes (`probe_info`) of compressed NEFs decode only the first 128 rows, the ones the masked-trailing-column test reads in both the probe and the full decode (the D5100 and D7000 write 8 masked columns at 0 with no maker-note tag for them, compressed and uncompressed alike), instead of the whole Huffman stream: ~3 ms instead of the full decode time per file (386 ms on a 24 MP D750 file), issue #708. CR3 lossless CRX Bayer and version 0x100/0x200 C-RAW decodes to sensor pixels with timed white balance, black/white levels and file crop (EOS R6 Mark III `ColorData` version 66: as-shot white balance and black level from the file, white level from the data, `docs/cr3.md`) (the shot's aspect ratio from the maker note's `AspectInfo`); full sensor samples match the reference on M50/R100/R8/RP, incl. RP 1.6× crop mode (`crates/raw/tests/it/cr3_corpus.rs`, `docs/cr3.md`). EOS R5 Mark II C-RAW and RAW decode too (the QP map's first-symbol literal and 23-zero escape, odd-height Bayer planes): 4 CC0 files pinned, every QP map of 33,665 private C-RAW files decodes exactly, and full-frame images match the camera JPEG. Unsupported coding variants retain metadata and use the embedded JPEG when available; HEVC preview decoding remains unsupported. Missing: CR3 unverified CRX variants, compressed ORF, compressed Samsung SRW (NX1, NX30, NX300, NX500, NX2000, NX3000, NX3300, NX mini: preview only with the reason), Nikon High Efficiency NEF (HE / HE★ from the Z 8, Z 9, Z 6III and Z f: compression 34713 with a JPEG XS codestream and no `0x0096` table; no public specification, so preview only with the reason "Nikon High Efficiency NEF", issue #193; Lossless compressed NEFs from the same bodies decode), NEF "lossy after split" (preview only: imported with `preview_only` = the decoder's reason, rendered as a rendered JPEG, flagged by a grid badge and loupe/Edit/Info notices, `previewOnly` in `catalog.query`; Reload clears it once the file decodes, and the notices carry a Reload from Disk button; each flag is stamped with the app version that set it, and a library opened in another version rechecks its preview-only raws in the background, so a raw a newer release decodes is cleared without a manual Reload, issue #662, `library.recheckPreviewOnly`), TIFF-based raws whose raw data sits in a private block (Phase One / Leaf IIQ, Canon EOS-1D / 1Ds and the Kodak DCS Pro / DCS760C TIFFs (compression 65000): IFD0 is only a reduced RGB image next to a much larger declared picture) are recognised from that structure and import as `preview_only` with the reason, instead of opening as a thumbnail-sized ordinary image (`thumbnail_shell` in `crates/raw/src/lib.rs`), Recognised raws LightCraft cannot decode open as `preview_only` with any usable embedded JPEG, also Sigma X3F (container recognised by magic, preview found by a structural JPEG scan), the Canon CRWs the decoder refuses (PowerShot G5, pre-CIFF compacts), Kodak KDC (IFD0 without `ImageWidth`), Leaf MOS and Epson ERF (scan; ones with no JPEG still fail with "without an embedded preview", AVIF decode (HEIC decodes only behind the non-default `heif` feature, which the release packages enable: `crates/heif`, heic-rs for the container and HEVC (bit-exact against libde265 on iPhone tiles) plus our own colour conversion, matching libheif within 1 code value on iPhone-style grids (VUI full range, Display P3 ICC, nclx, 10-bit, irot/imir/clap, gain map/depth ignored); 4:2:0/monochrome only, 4:2:2/4:4:4 Canon/Sony HIF refused; same policy as PhotoCraft), palette (indexed-colour) TIFF (refused by the `tiff` crate); folder discovery and file pickers include Sony SR2 and SRF, with the same extension list used by CLI/MCP: DSC-R1 SR2 decodes from sensor data; DSC-F828 SRF stays preview-only; transparency is flattened onto white, in linear light, where the working image is built (`to_working` in `crates/codecs/src/lib.rs`): renders, previews, exports and contact sheets no longer turn transparent areas black, issue #1212; median ΔE vs the camera JPEG 2.2–7.6 on 11 of the 12 files from 10 bodies checked, 10.7 (5.7 registered) on the backlit WB2000 sample). 12-bit NEF black level: maker note `0x003d` is in 14-bit units (D750, D780, D850, D7500, Z 50 checked). Lossy type 2 NEF curves of version `0x44 0x40` (D780, D6, Z 5, Z 6, Z 7, Z 7_2) span a quarter of the sample range, read from the table's own point spacing. Import probes (`probe_info`) of compressed NEFs decode only the first 128 rows, the ones the masked-trailing-column test reads in both the probe and the full decode (the D5100 and D7000 write 8 masked columns at 0 with no maker-note tag for them, compressed and uncompressed alike), instead of the whole Huffman stream: ~3 ms instead of the full decode time per file (386 ms on a 24 MP D750 file), issue #708. CR3 lossless CRX Bayer and version 0x100/0x200 C-RAW decodes to sensor pixels with timed white balance, black/white levels and file crop (the shot's aspect ratio from the maker note's `AspectInfo`); the Hasselblad Lunar, a Sony NEX-7 under Make HASSELBLAD, decodes as an ARW: a HASSELBLAD file with Sony's raw coding (compression 32767 or tag `0x7010`) goes to the ARW reader, and the body for its `Tag2010` layout comes from the maker note's `SonyModelID`, checked on the CC0 4051 file, issue #1275; an uncompressed MOS, whose IFD0 is the 16-bit mosaic with no Make tag, is recognised by Leaf's data block `0x8606` instead of opening as a greyscale image of its mosaic, issue #1275), and any decode failure of a recognised raw, not only an unsupported variant Nikon 12-bit small RAW (D810 / D4S): packed Y0/Y1/Cb/Cr rows decode to linear RGB; header probes retain geometry without decoding pixels, and truncated strips are refused. Chroma gains are approximate. | +| LR-IMP-CAMERA-COVERAGE | Camera coverage, verified per model | P0 | 🟡 | `crates/raw/tests/it/corpus.rs`, `crates/raw/tests/it/coverage_sweep_corpus.rs`, `docs/raw/evidence.tsv`, `docs/raw/coverage-sweep.tsv`, `xtask/src/main.rs` | a supported container is not the same as every camera that writes it decoding correctly: ~55 CC0 corpus files plus 6 exact-sensor CR3 cases and 4 pinned EOS R5 Mark II files (plus ~60 NEFs checked by hand and 178 Panasonic / Leica RW2, RWL and RAW files from 118 bodies, checked against each file's own JPEG) against the >1,000 models Lightroom lists; per-model differences still surface (e.g. the CR2 colour-filter layout differed by model until it was read from the file's own tag, issue #85, checked on 35 CR2 bodies; CR2 black levels were read from border columns light already reaches, left of `SensorInfo`'s border, so CR2s rendered dark with crushed shadows (EOS 6D: 2591 instead of 2047), now measured from the masked columns only, checked on 8 corpus bodies; pre-2017 Sony ARWs opened green until their scrambled white balance / black level were read, issue #148, checked on 16 ARWs from 15 Sony bodies; their white balance now comes from the encrypted `SR2SubIFD`'s `WB_RGGBLevels`, the gains the camera applied, which 14 bodies (DSLR-A450 … A700, NEX-3/5/C3, SLT-A33/A35/A55, ILCE-6001, UMC-R10C) had no other source for and which the maker-note estimate read before missed by up to 100 % on preset shots, issue #535, checked against ExifTool on 100 decoded ARWs from 87 Sony bodies; the encrypted SR2 black level is now read from its directory entry (the DSLR-A450/A500/A550/A700 read 354–975 instead of 512), checked on 126 ARWs from 97 Sony bodies, and downsized lossless M/S ARWs use their measured black level 1024 instead of 512, checked on 12 files from 4 bodies, issue #535, and on a fifth, the ILCE-7CM2 (issue #697: 0.5.0 rendered its lossless M file with that 512 pedestal, the darkest half of a dusk scene +11 L* and blue, the lamplight white; its darkest 0.1 % per channel sits at 1017/1020/1024, and with 1024 the render is within +3 L* of the camera JPEG; the body's compressed and uncompressed "M-size" files are Bayer at 4736×3132 with black 512, two public ones in the corpus); pre-2017 Sony bodies shot in the camera's 16:9 mode (ILCE-7SM2, DSLR-A580) now get the 16:9 crop of their camera JPEG instead of the whole 3:2 frame with padding columns, issue #535; Canon PowerShot and EOS M CR2s whose `ColorData` is stored as bytes opened green until the bytes were read as 16-bit words, 25 files from 25 bodies checked; private Z 8 crop verified; the Nikon D4, which records no CropArea, gets its image area and white level measured on 148 NEFs against the camera JPEG (optical-black stripes and magenta clipped highlights gone); the D7100, whose data holds picture beyond the camera's frame, develops at Lightroom's frame (the camera JPEG's size) in full frame and 1.3x crop, so Lightroom crops line up (#1248).; CR2 white levels came from masked rows holding samples at 16383, so on the EOS 50D, 60D, 450D (Rebel XSi), 500D (Rebel T1i), 550D (Rebel T2i), 1200D (Rebel T5) and 100D (Rebel SL1) clipped highlights sat at 81–98% of the range and were not treated as clipped, now taken from the active area only, issue #1272). Canon CRW verified pixel-exact on 6 bodies (D30, D60, 10D, 300D, G1, G6; 10 CC0 files in the corpus, `crates/raw/tests/it/corpus.rs`, plus 18 maintainer D60 files via the digest-pinned sample test of craft-crw in craft-libs (crates/crw/tests/samples.rs there); `craft-libs crates/crw/clean-room/03-verification.md`). Compressed RAF sensor arrays verified on 13 Fujifilm bodies (`docs/raf-compression.md`). ILCE-7RM6 ARW6 12-bit codes verified on 8 CC0 files (FF and APS-C, both compression modes). Sony ILCE-7CR (61 MP): lossless compressed L and M and compressed ARW2 samples of one scene in the corpus (black level, default crop, as-shot gains, lossless vs compressed mosaic agreement, fitted camera look), and all nine public codings (also uncompressed, small and APS-C crop) decode and fit. Grow the CC0 corpus per model and test each sample decodes with plausible colour. Nikon CropArea and untagged-preview colour-space regression tests; the Nikon D4, which records no CropArea, gets its image area and white level measured on 148 NEFs against the camera JPEG (optical-black stripes and magenta clipped highlights gone). Pentax PEFs without area tags (KP, KF, K-3 Mark III, K-70, K-1, K-1 Mark II, K-S1, K-S2, 645Z) no longer keep their masked border: black, empty and saturated lines are trimmed against the maker-note BlackPoint, issue #1271, measured on 38 CC0 PEFs from 30 bodies.; Canon PowerShot and EOS M CR2s whose `ColorData` is stored as bytes opened green until the bytes were read as 16-bit words, 25 files from 25 bodies checked). Compressed RAF sensor arrays verified on 13 Fujifilm bodies (`docs/raf-compression.md`). ILCE-7RM6 ARW6 12-bit codes verified on 8 CC0 files (FF and APS-C, both compression modes). Sony ILCE-7CR (61 MP): lossless compressed L and M and compressed ARW2 samples of one scene in the corpus (black level, default crop, as-shot gains, lossless vs compressed mosaic agreement, fitted camera look), and all nine public codings (also uncompressed, small and APS-C crop) decode and fit. Grow the CC0 corpus per model and test each sample decodes with plausible colour. Nikon CropArea and untagged-preview colour-space regression tests; the Nikon D4, which records no CropArea, gets its image area and white level measured on 148 NEFs against the camera JPEG (optical-black stripes and magenta clipped highlights gone). The Canon PowerShot G9, which writes no `ColorData`, takes its as-shot white balance from the WBInfo row (`0x0029`) of the preset ShotInfo names, issue #1273, checked on 1 CC0 file against neutral areas of the camera JPEG (within 0.1 %).; the Nikon D4, which records no CropArea, gets its image area and white level measured on 148 NEFs against the camera JPEG (optical-black stripes and magenta clipped highlights gone). The Olympus E-10 (no ImageProcessing directory) takes its as-shot gains and its black level (`0x1012` in `0x102c` ValidBits units: 128 at 12 bits) from the main maker note, issue #1273: its render against the camera thumbnail goes from ΔE76 18.5 with a rejected fit to 11.5 with the fit accepted (measured by AlexChambers).; the Nikon D4, which records no CropArea, gets its image area and white level measured on 148 NEFs against the camera JPEG (optical-black stripes and magenta clipped highlights gone), and the Nikon D90 likewise on 10 of 550 NEFs (42 filler columns on the right that developed as a pink band; green clips below red and blue).; the Nikon D4, which records no CropArea, gets its image area and white level measured on 148 NEFs against the camera JPEG (optical-black stripes and magenta clipped highlights gone). Public raw.pixls.us coverage sweep (#1327): 30 more models verified on 40 CC0 files, with SHA-256 and FNV-1a sensor hashes pinned (`crates/raw/tests/it/coverage_sweep_corpus.rs`, `docs/raw/coverage-sweep.tsv`; `docs/raw/coverage.py`: 515 → 545 verified rows). Render/reference luminance correlation is 0.6801 to 0.9998; eleven candidates excluded for identity, provenance or missing/mismatched reference images. Nikon D6 / Z 6 / Z 7 uncompressed and Z 8 HE variants remain preview-only. Own-matrix DNG tone differs from the camera JPEG (#1277); sensor verification is not colour calibration. | | LR-IMP-PREVIEW-READ | Preview reads the header, not the raw | P0 | ✅ | `crates/raw/src/preview.rs` (`bounded_preview`, `embedded_preview_from_range`, `embedded_preview_covering`), `crates/engine/src/files.rs` (`preview_window`), `docs/raw-preview-reads.md`, `crates/engine/src/files.rs` (`preview_window`, `planned_preview`), `crates/raw/src/sparse.rs` (`preview_plan`), `crates/raw/src/dng.rs` (`decode_reduced`) | a grid thumbnail and a loupe preview of an unedited raw show the camera's embedded JPEG, which sits near the start of the file (a 24 MP Fuji RAF keeps its 5.6 MB JPEG in a 56 MB file), so the preview loader reads the header and that JPEG instead of the whole file: 56.5 MB → 5.9 MB and ~10 s → 0.96 s measured on one real RAF over a GVFS/SMB Wi-Fi share (byte-identical preview pixels; `LIGHTCRAFT_PROFILE=1` reports the bytes); the RAF pointer is read from its fixed header fields, a CR3's boxes and a TIFF raw whose preview tag sits past the prefix are read through `lightcraft_raw::sparse::preview_plan`, which names the ranges `embedded_preview` reads and nothing else; a cached render as stand-in, an edited photo and a raw without an embedded JPEG still render; the formats it cannot describe (CRW/MRW/X3F are found by scanning, NEF/ORF/PEF probe from samples) keep the whole-file path, and a lying pointer, a short read or a file that changes underneath us falls back to it rather than losing the preview. An unedited raw's **grid thumbnail is that camera JPEG itself**, so no sensor decode is scheduled behind it (`Session::embedded_thumb_is_final`, `grid::skips_the_real_render`): drawing a 512 px thumbnail used to decode the whole raw — 45 s a photo at 1.2 MB/s, 8.3 GB for a 149-raw folder — and the queue it built delayed the render the user was waiting for. Stand-ins and grid thumbnails decode the smallest embedded JPEG that covers the requested size and is a copy of the largest (aspect ratio within 1 %, same component count), not always the largest, on the whole-file and the plan route alike: an A7R VI's 1616 × 1080 JPEG (0.3 MB) instead of its 9984 × 6656 one (5.6 MB), and mid-sized NEF / RW2 JPEGs; a DNG that keeps reduced-resolution copies of its LinearRaw image (Lightroom HDR/panorama merges) develops a thumbnail or preview from the smallest copy that covers it (512 px of a 23836 × 7078 pano: 38 s → 1.1 s) | | LR-IMP-CULL-AT-IMPORT | Culling analysis at import | P2 | 🟡 | `cmd:photo.analyze` | run Assisted Culling on the imported photos (they're selected after an import); not automatic | | LR-IMP-MOVE | Move on import [Classic] | P1 | ✅ | `cmd:library.import` (`mode` move), `crates/engine/src/import_move.rs`, `crates/engine/src/tests/import_move.rs`, `crates/ui-egui/src/dialogs/import.rs` | Transfer ▸ Move with Copy's destination, folders (day / month / one folder / custom template), rename template and example destination; XMP sidecars (both namings) move along; each source is removed only after its destination is written (hard link on the same volume, else copied, synced and compared byte for byte) and its catalog record is saved; failed, duplicate, unchecked files keep their sources, taken names get -1, -2…, never overwritten; if the rename sequence runs out, later files are reported as failed and earlier completed moves remain catalogued; files already in the destination/library are added in place; sources that can't be removed (read-only card) are kept and reported (`kept`). Undo removes the photos from the library but leaves the moved files at the destination. No Copy as DNG while moving | @@ -236,7 +236,7 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts). | LR-PROF-ADOBE | Standard raw looks (own equivalents) | P0 | ✅ | `crates/engine/src/presets.rs` (`PROFILES`), `crates/pipeline/src/profiles.rs` | six own looks: Color, Neutral, Vivid, Landscape, Portrait, Monochrome | | LR-PROF-ADAPTIVE | Adaptive profiles | P2 | ⬜ | | | | LR-PROF-CAMERA | Camera-matching looks | P2 | ⬜ | | | -| LR-PROF-CAMERACOLOR | Camera colour calibration (own) | P0 | 🟡 | `crates/raw/src/color.rs`, `crates/raw/src/profile.rs`, `crates/raw/src/gaintable.rs`, `crates/engine/src/camera_preview.rs` | DNG files use the colour matrices and the camera-profile look they carry (`ProfileHueSatMap`, `ProfileLookTable`, `ProfileToneCurve`, read from the file at run time per DNG spec ch. 6 — Lightroom-converted DNGs rendered flat and muted without them, issue #138). A DNG whose profile defines no tone curve (absent or identity `ProfileToneCurve`, with or without a `LookTable`, as Adobe Standard in Lightroom-written DNGs) gets its tone fitted to its embedded preview when that is at least 10% of the raw's long edge and 384 px (Lightroom's 1024 px previews qualify): two Lightroom Classic 15.1 lossy ILCE-7CM2 DNGs went from about 0.8 EV darker and flat to within 3% of Lightroom's mean linear RGB. A DNG's `ProfileGainTableMap` / `ProfileGainTableMap2` (Apple ProRAW's local tone map; both versions, DNG 1.7 precedence, the file's byte order with a big-endian fallback) is read and written back by DNG export and not rendered by default, as Lightroom Classic renders ProRAW without it (measured on one CC0 iPhone 12 Pro ProRAW); the Profile option "Camera local tone mapping" (`profile.camera_local_tone`, per photo, off by default, `crates/pipeline/src/local_tone.rs`) renders it as the camera does, per output pixel in the scene-linear stage (CPU, also under the GPU path); Sony ARW, Nikon NEF, Panasonic RW2, Fujifilm RAF, Canon CR3 and CR2, Pentax PEF, Samsung SRW and Olympus ORF get a guarded file-local fit to their own embedded JPEG — chromaticity matrix + hue/saturation/value table + tone and chroma curves, relative WB — or, with a bundled or local per-model profile pooled from many photos (`lightcraft-cli calibrate`, ARW, NEF and RAF so far, `crates/engine/src/camera_profiles.rs`), the profile's colour and only their own tone/chroma curves (on 29 held-out ILCE-7M4 photos mean ΔE vs the camera JPEG 3.54 → 3.10; bundled ILCE-7CR from 125 photos: 26 held-out 4.07 → 3.29, and every one of the 131 photos accepted, 8 of which fell back to neutral without it; bundled ILCA-99M2 from 144 photos: 30 held-out 5.11 → 4.87, 25 of them closer); the tone fit also uses dark JPEG samples excluded from colour training, so deep shadows follow the camera toe (#1307); near-black frames with too few colour pairs can take a guarded tone-only fallback on their existing source colour (#1587); Sony ARWs shot with DRO on (maker note `0xb025`, on by default) keep that colour but their tone curve is lowered to Sony's curve without DRO, whose lifted shadows are in the camera JPEG only (issue #244: 8 ILCE-7CR photos +6.4 → +0.8 L* from Lightroom's default, median ΔE 7.5 → 3.3); 52 models without a profile whose JPEG fit fails (or that have no usable JPEG) start from DNG-style matrices fitted to their measured spectral sensitivities instead of the neutral fallback (`crates/raw/src/spectral.rs`, rawtoaces-data v1.1.0, Apache-2.0, aliases included; not yet measured against the camera JPEG); ARW: on 10 public raw.pixls.us samples from 8 bodies 7 fits were accepted, mean ΔE vs the camera JPEG 17–26 → 3–10; NEF: 13 of 13 decodable samples (D750, D780, D850, D7500, Z 50 and local D7500 shots) accepted, ΔE 13–46 → 3–8 (issue #150); RW2 / RWL: 140 of 174 public samples accepted, from 93 of the 114 bodies with a preview, median ΔE 5.0 with matching lightness (13 of the 14 corpus files with a preview: ΔE 9.6–27 → 2.8–8.9; most rejections are compacts and kit zooms whose camera JPEG is distortion-corrected); a fit that fails the gates on all pixels is retried away from edges, where a lens-corrected camera JPEG still lines up (issue #232: the six distortion-corrected public ILCE-7RM2 samples ΔE 16.6–16.9 → 3.7–3.8, the corpus GX80 19.1 → 7.9); RAF: bundled X-H2S/X-T4 profiles from 201/545 photos; on 150 withheld files mean ΔE improves slightly over per-file fitting (X-H2S 1.70 → 1.62, X-T4 2.43 → 2.34); 23 of 24 references from 13 bodies accepted, including the dark X-H2S samples (8.74 → 2.25); ORF: the 3 decodable corpus files (E-1, E-400, XZ-2) accepted (`docs/camera-preview-colour.md`); a raw whose camera JPEG is black and white (a monochrome picture style) takes only the JPEG's tone curve, on the camera profile's, spectral or neutral colour (issue #577); this is not measured camera calibration; other raws and rejected fits use a neutral fallback (camera RGB ≈ linear sRGB, `matrix_is_fallback`) with as-shot white balance, so colours are muted and not accurate. Needs our own per-camera calibration: matrices the files carry themselves (e.g. Olympus `ColorMatrix`), fitting each camera to its own embedded JPEG, then chart shots. Adobe matrices / DCPs are never used. Biggest image-quality gap today; ILCE-7RM6: per-file fit now runs on previews above 64 MP, a pooled profile (46 photos) was tried and not bundled (withheld ΔE 5.71 → 6.40, 11 of 15 regressed), kept with smart previews (see docs/camera-preview-colour.md); the fit pairs the sensor moved into the file's lens-warp geometry when that lines up better with a lens-corrected camera JPEG (29 CC0 raws: alignment +0.13, mean ΔE 3.73 → 3.61); bundled NIKON Z 6_2 profile (296 photos; 59 held-back photos mean ΔE 2.73 → 2.54) NIKON Z 8 profile (261 photos; 55 held back 2.55 → 2.33) and NIKON D5300 profile (264 photos; 53 held back 2.86 → 2.66) | +| LR-PROF-CAMERACOLOR | Camera colour calibration (own) | P0 | 🟡 | `crates/raw/src/color.rs`, `crates/raw/src/profile.rs`, `crates/raw/src/gaintable.rs`, `crates/engine/src/camera_preview.rs` | DNG files use the colour matrices and the camera-profile look they carry (`ProfileHueSatMap`, `ProfileLookTable`, `ProfileToneCurve`, read from the file at run time per DNG spec ch. 6 — Lightroom-converted DNGs rendered flat and muted without them, issue #138). A DNG whose profile defines no tone curve (absent or identity `ProfileToneCurve`, with or without a `LookTable`, as Adobe Standard in Lightroom-written DNGs) gets its tone fitted to its embedded preview when that is at least 10% of the raw's long edge and 384 px (Lightroom's 1024 px previews qualify): two Lightroom Classic 15.1 lossy ILCE-7CM2 DNGs went from about 0.8 EV darker and flat to within 3% of Lightroom's mean linear RGB. A DNG's `ProfileGainTableMap` / `ProfileGainTableMap2` (Apple ProRAW's local tone map; both versions, DNG 1.7 precedence, the file's byte order with a big-endian fallback) is read and written back by DNG export and not rendered by default, as Lightroom Classic renders ProRAW without it (measured on one CC0 iPhone 12 Pro ProRAW); the Profile option "Camera local tone mapping" (`profile.camera_local_tone`, per photo, off by default, `crates/pipeline/src/local_tone.rs`) renders it as the camera does, per output pixel in the scene-linear stage (CPU, also under the GPU path); Sony ARW, Nikon NEF, Panasonic RW2, Fujifilm RAF, Canon CR3 and CR2, Pentax PEF, Samsung SRW and Olympus ORF get a guarded file-local fit to their own embedded JPEG — chromaticity matrix + hue/saturation/value table + tone and chroma curves, relative WB — or, with a bundled or local per-model profile pooled from many photos (`lightcraft-cli calibrate`, ARW, NEF and RAF so far, `crates/engine/src/camera_profiles.rs`), the profile's colour and only their own tone/chroma curves (on 29 held-out ILCE-7M4 photos mean ΔE vs the camera JPEG 3.54 → 3.10; bundled ILCE-7CR from 125 photos: 26 held-out 4.07 → 3.29, and every one of the 131 photos accepted, 8 of which fell back to neutral without it; bundled ILCA-99M2 from 144 photos: 30 held-out 5.11 → 4.87, 25 of them closer); the tone fit also uses dark JPEG samples excluded from colour training, so deep shadows follow the camera toe (#1307); near-black frames with too few colour pairs can take a guarded tone-only fallback on their existing source colour (#1587); Sony ARWs shot with DRO on (maker note `0xb025`, on by default) keep that colour but their tone curve is lowered to Sony's curve without DRO, whose lifted shadows are in the camera JPEG only (issue #244: 8 ILCE-7CR photos +6.4 → +0.8 L* from Lightroom's default, median ΔE 7.5 → 3.3); 52 models without a profile whose JPEG fit fails (or that have no usable JPEG) start from DNG-style matrices fitted to their measured spectral sensitivities instead of the neutral fallback (`crates/raw/src/spectral.rs`, rawtoaces-data v1.1.0, Apache-2.0, aliases included; not yet measured against the camera JPEG); the Canon EOS D60 (CRW) has DNG-style matrices of our own, fitted to ColorChecker Classic shots under a tungsten bulb and direct sun (`crates/raw/src/chart_table.rs`, `clean-room/crw/10-chart-calibration.md`; CIEDE2000 on the chart mean 2.4 / 1.7, 2.7 / 2.0 leaving each patch out), which give its white balance a Kelvin scale (the camera's own presets read Tungsten 3,264 K, Daylight 5,603 K, Cloudy 6,612 K) while its look stays the JPEG fit; ARW: on 10 public raw.pixls.us samples from 8 bodies 7 fits were accepted, mean ΔE vs the camera JPEG 17–26 → 3–10; NEF: 13 of 13 decodable samples (D750, D780, D850, D7500, Z 50 and local D7500 shots) accepted, ΔE 13–46 → 3–8 (issue #150); RW2 / RWL: 140 of 174 public samples accepted, from 93 of the 114 bodies with a preview, median ΔE 5.0 with matching lightness (13 of the 14 corpus files with a preview: ΔE 9.6–27 → 2.8–8.9; most rejections are compacts and kit zooms whose camera JPEG is distortion-corrected); a fit that fails the gates on all pixels is retried away from edges, where a lens-corrected camera JPEG still lines up (issue #232: the six distortion-corrected public ILCE-7RM2 samples ΔE 16.6–16.9 → 3.7–3.8, the corpus GX80 19.1 → 7.9); RAF: bundled X-H2S/X-T4 profiles from 201/545 photos; on 150 withheld files mean ΔE improves slightly over per-file fitting (X-H2S 1.70 → 1.62, X-T4 2.43 → 2.34); 23 of 24 references from 13 bodies accepted, including the dark X-H2S samples (8.74 → 2.25); ORF: the 3 decodable corpus files (E-1, E-400, XZ-2) accepted (`docs/camera-preview-colour.md`); a raw whose camera JPEG is black and white (a monochrome picture style) takes only the JPEG's tone curve, on the camera profile's, spectral or neutral colour (issue #577); this is not measured camera calibration; other raws and rejected fits use a neutral fallback (camera RGB ≈ linear sRGB, `matrix_is_fallback`) with as-shot white balance, so colours are muted and not accurate. Needs our own per-camera calibration: matrices the files carry themselves (e.g. Olympus `ColorMatrix`), fitting each camera to its own embedded JPEG, then chart shots. Adobe matrices / DCPs are never used. Biggest image-quality gap today; ILCE-7RM6: per-file fit now runs on previews above 64 MP, a pooled profile (46 photos) was tried and not bundled (withheld ΔE 5.71 → 6.40, 11 of 15 regressed), kept with smart previews (see docs/camera-preview-colour.md); the fit pairs the sensor moved into the file's lens-warp geometry when that lines up better with a lens-corrected camera JPEG (29 CC0 raws: alignment +0.13, mean ΔE 3.73 → 3.61); bundled NIKON Z 6_2 profile (296 photos; 59 held-back photos mean ΔE 2.73 → 2.54) NIKON Z 8 profile (261 photos; 55 held back 2.55 → 2.33) and NIKON D5300 profile (264 photos; 53 held back 2.86 → 2.66) | | LR-PROF-CREATIVE | Creative profiles (own) | P2 | ✅ | `cmd:develop.profile`, `crates/pipeline/src/profiles.rs` | 16 own looks in Film / Cinematic / Muted / B&W (tone + point-curve fades, colour grading, mixer / B&W mix); scale with `ctl:profile.amount`; sliders untouched | | LR-PROF-LEGACY | Legacy profiles | P2 | ⬜ | | | | LR-PROF-NONRAW | Profiles for non-raw files | P0 | ✅ | `cmd:develop.profile` | same looks apply to JPEG/TIFF | @@ -815,6 +815,9 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove | 2026-10-27 | patch | MENU-BAR: every shortcut the app shows (a menu row, a button's tooltip, a hint, the empty-album line, the Keyboard Shortcuts dialog) lists its modifiers on macOS in the menu bar's order, ⌃⌥⇧⌘ (`⇧⌘I`, not `⌘⇧I`); the empty-album line follows the keymap; status counts unchanged | | 2026-10-25 | patch | LR-BEHAV-TITLEBAR: the top bar's title-bar gestures come from craft-window's `title_bar`: a double-click follows the system setting, and a press on a button, the search field or a panel floating over the bar never moves the window; status counts unchanged | | 2026-10-25 | patch | LR-IMP-FORMATS: JPEG ICC APP2 chunk sequences are validated before colour conversion; valid reordered chunks remain supported and malformed profiles use the existing sRGB fallback; status unchanged (🟡) | +| 2026-10-13 | minor | LR-IMP-FORMATS / LR-IMP-CAMERA-COVERAGE: Canon CRW decodes (clean-room; 6 bodies pixel-exact, G5 preview-only) | +| 2026-10-25 | patch | LR-BEHAV-TITLEBAR: the top bar's title-bar gestures come from craft-window's `title_bar`: a double-click follows the system setting, and a press on a button, the search field or a panel floating over the bar never moves the window; status counts unchanged | +| 2026-10-26 | minor | LR-IMP-FORMATS: the Canon CRW decoder moved to craft-libs as `craft-crw` (path dependency until that branch merges); `crates/raw/src/vendor/crw.rs` is the adapter, the clean-room record lives in craft-libs (crates/crw/clean-room there) | | 2026-10-24 | minor | KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 (`keyword.nextSet`, `keyword.previousSet`) go to the next / previous keyword set; status counts unchanged (KEYC-METADATA stays 🟡) | | 2026-10-24 | minor | KEYC-METADATA: ⌘K / Ctrl+K (`panel.addKeywords`, Add Keywords) shows the Keywords panel, opens Keywording and puts the cursor in the Add keyword box; status counts unchanged (KEYC-METADATA stays 🟡) | | 2026-10-24 | patch | LR-META-LOCATION: Info GPS text validates the complete decimal/DMS expression, axis markers and component ranges before metadata changes; readiness and feature counts unchanged | @@ -941,6 +944,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove | 2026-10-12 | minor | LR-IMP-FORMATS and LR-IMP-CAMERA-COVERAGE: 22 more CR3 bodies verified on one CC0 file each (all 110 CC0 CR3s decode); EOS R6 Mark III `ColorData` 66 white balance; status unchanged (🟡: CRX variants without public samples remain refused) | | 2026-10-11 | minor | Model setup folder action, opener error and retry (LR-MASK-OBJECTS). | | 2026-10-13 | minor | LR-PROF-CAMERACOLOR: the camera look follows the lens warp of lens-corrected camera JPEGs; status unchanged (🟡) | +| 2026-10-25 | minor | LR-PROF-CAMERACOLOR: chart-measured matrices for the Canon EOS D60 (first chart-calibrated model), white balance in Kelvin for its CRWs; CRW imports take make, model and capture time from the CIFF records (the catalog had no camera for them); status unchanged (🟡) | | 2026-10-13 | minor | LR-PROF-CAMERACOLOR: bundled Nikon Z 6II camera profile; status unchanged (🟡) | | 2026-10-13 | minor | LR-PROF-CAMERACOLOR: bundled Nikon Z 8 camera profile; status unchanged (🟡) | | 2026-10-13 | minor | LR-PROF-CAMERACOLOR: bundled Nikon D5300 camera profile; status unchanged (🟡) | diff --git a/docs/raw-parity.md b/docs/raw-parity.md index 0c849b63c..32c401669 100644 --- a/docs/raw-parity.md +++ b/docs/raw-parity.md @@ -1,6 +1,6 @@ # RAW parity: cameras, formats, demosaic, colour and lens profiles -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-23 · **Change:** minor (Sony ILCE-6700 in-file distortion; Minolta MRW decoded, 6 bodies verified; lossy JPEG XL DNG tiles with a non-linear declared transfer curve decode; Canon ColorData Kelvin; X-Trans CMOS 4 bodies take the X-T3's spectral matrices; AHD no longer turns flat noise into a maze pattern) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 (Camera Raw 18.7) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-26 · **Change:** minor (Canon CRW decoder shared via craft-libs `craft-crw`; Canon EOS D60: chart-measured colour matrices, white balance in Kelvin; Canon CRW merged with MRW: 557 verified; CRW variant rows split by verification status; Sony ILCE-6700 in-file distortion; Minolta MRW decoded, 6 bodies verified; lossy JPEG XL DNG tiles with a non-linear declared transfer curve decode; Canon ColorData Kelvin; X-Trans CMOS 4 bodies take the X-T3's spectral matrices; AHD no longer turns flat noise into a maze pattern) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 (Camera Raw 18.7) Raw processing is the deepest single dimension of Lightroom parity: a photographer whose camera does not decode, or decodes with the wrong colour, cannot switch. This document measures it model by @@ -15,14 +15,14 @@ ranked in [gaps.md](gaps.md). Details per format: [cr3.md](cr3.md), | Measure | Value | Kind | |---|---:|---| | Adobe-listed camera models | **1,446** | measured: [raw/adobe-cameras.tsv](raw/adobe-cameras.tsv) | -| Decoded from sensor data and **verified** on a real file | **551** (38%) | measured: [raw/evidence.tsv](raw/evidence.tsv) | -| Decoder path should handle it, **unverified** | **656** (45%) | measured from the format rules (631 of them DNG, mostly phones) | -| **Preview-only** (embedded JPEG) | **201** (14%) | measured | -| **Unsupported** (not imported, refused or decodes wrongly) | **38** (3%) | measured | +| Decoded from sensor data and **verified** on a real file | **557** (39%) | measured: [raw/evidence.tsv](raw/evidence.tsv) | +| Decoder path should handle it, **unverified** | **665** (46%) | measured from the format rules (631 of them DNG, mostly phones) | +| **Preview-only** (embedded JPEG) | **187** (13%) | measured | +| **Unsupported** (not imported, refused or decodes wrongly) | **37** (3%) | measured | | Models added since 2017 (Camera Raw 10+): verified / unverified / preview / unsupported | 182 / 542 / 22 / 8 of 754 | measured | | **Share of photographers whose camera fully works** (decodes from sensor data), use-weighted | **~94%** | estimated: measured classes × written usage weights × per-format confidence | -| …of which on a body we have verified | **~85%** | estimated, same weights | -| Camera colour: Adobe profiles vs ours | 1,500 Adobe Standard profiles, 481 models with Camera Matching profiles vs **9 bundled profiles** + per-file fits + 52 spectral matrices (plus same-sensor aliases) | measured (bundle listing, Adobe list, `assets/camera-profiles`) | +| …of which on a body we have verified | **~86%** | estimated, same weights | +| Camera colour: Adobe profiles vs ours | 1,500 Adobe Standard profiles, 481 models with Camera Matching profiles vs **9 bundled profiles** + per-file fits + 52 spectral matrices (plus same-sensor aliases) + chart-measured matrices (1 model, the EOS D60) | measured (bundle listing, Adobe list, `assets/camera-profiles`) | | Lens profiles: Adobe vs ours | **3,635 vs 0** (in-file corrections only) | measured | | **RAW readiness** (decoding 60%, colour 25%, lens 15%) | **~60%** (range 54–64%) | estimated, see [Scores](#scores) | @@ -86,7 +86,7 @@ unsupported to preview-only in the counts; sensor-decoding readiness is unchange | Maker | Adobe models | verified | unverified | preview | unsupported | Main reason for preview / unsupported | |---|---:|---:|---:|---:|---:|---| | Sony | 149 | 94 | 43 | 10 | 2 | DSLR-A200…A390 and A850 12-bit compressed ARW (preview, #535); A100 remains classified unsupported in the recorded evidence; DSC-F828 SRF imports as preview-only; DSC-V3 preview not verified | -| Canon | 134 | 95 | 16 | 21 | 2 | CRW (2000–2004 bodies) preview; EOS-1D / 1Ds TIFF raws not imported | +| Canon | 134 | 101 | 25 | 6 | 2 | PowerShot G5 CRW anomaly and pre-CIFF compacts preview; EOS-1D / 1Ds TIFF raws not imported | | Samsung | 128 | 10 | 109 | 9 | 0 | compressed SRW (NX1, NX30, NX300, NX500, NX2000, NX3000, NX3300, NX mini, Galaxy NX) | | Apple | 113 | 4 | 109 | 0 | 0 | — (DNG / ProRAW) | | Panasonic | 104 | 93 | 11 | 0 | 0 | — | @@ -103,13 +103,13 @@ unsupported to preview-only in the counts; sensor-decoding readiness is unchange | DJI | 21 | 4 | 17 | 0 | 0 | DNG | | OM Digital Solutions | 7 | 0 | 0 | 7 | 0 | **compressed ORF** | | Kodak, Konica Minolta, Epson, Sigma, GoPro, Contax, others | 56 | 12 | 16 | 13 | 15 | DCR, GPR not imported; KDC, X3F preview (Epson ERF and Minolta MRW decode) | -| **All** | **1,446** | **551 (38%)** | **656 (45%)** | **201 (14%)** | **38 (3%)** | | +| **All** | **1,446** | **557 (39%)** | **665 (46%)** | **187 (13%)** | **37 (3%)** | | ## By use (estimated) | Bucket | Weight | Camera fully works | On a verified body | Why | |---|---:|---:|---:|---| -| Canon | 33% | 95% | 93% | 18 of the 22 popular bodies write CR3; 26 CR3 bodies verified (raw.pixls.us has a CC0 file for each), the rest at 0.6 | +| Canon | 33% | 98% | 94% | 18 of the 22 popular bodies write CR3; 26 CR3 bodies verified (raw.pixls.us has a CC0 file for each), the rest at 0.6; CRW decoded | | Sony | 27% | 98% | 92% | bulk-checked | | Nikon | 16% | 98% | 89% | decoders cover every NEF coding but HE; 77 bodies verified | | Fujifilm | 10% | 94% | 92% | | @@ -120,9 +120,9 @@ unsupported to preview-only in the counts; sensor-decoding readiness is unchange | Leica | 1% | 99% | 72% | the Q3 verified, 13 more bodies pinned on CC0 files | | DJI | 1% | 96% | 19% | | | Medium format and others | 0.5% | 29% | 8% | IIQ, 3FR, X3F | -| **Weighted** | 100% | **94%** | **85%** | | +| **Weighted** | 100% | **94%** | **86%** | | -About 19 in 20 raw photographers' cameras decode, and about 5 in 6 use a body we have actually +About 19 in 20 raw photographers' cameras decode, and about 6 in 7 use a body we have actually checked (the per-model CC0 sweeps of October 2026); compressed ORF (OM / Olympus) is the largest gap left. ## Variant matrix (depth per format) @@ -141,7 +141,9 @@ checked (the per-model CC0 sweeps of October 2026); compressed ORF (OM / Olympus | CR3 | sRAW / mRAW | ⬜ | 4-plane Bayer only | | CR3 | HEIF / HDR PQ | ⬜ | HEVC tracks recognised, not decoded | | CR3 | dual-pixel raw | ⬜ | never substituted | -| CRW | all | ⬜ | preview | +| CRW | CIFF: EOS D30/D60/10D/300D, PowerShot G1/G6 | ✅ | clean-room derivation frozen in `craft-libs crates/crw/clean-room/02-crw-format.md`; 28 of 29 files pixel-exact vs a black-box reference (8 CC0 EOS, G1, G6, 18 private D60); as-shot WB from ColorBalance; EOS D60 colour matrices measured on a ColorChecker (Kelvin white balance) | +| CRW | CIFF: PowerShot G2/G3/G4, Pro1, S-series | 🟡 | same decoder and tables; no sample file checked | +| CRW | CIFF: PowerShot G5 | ⬜ | refused (entropy anomaly, `craft-libs crates/crw/clean-room/02-crw-format.md`); preview | | NEF | uncompressed, lossless, lossy type 1 / 2, lossy after split | ✅ | D5100, D7000, D7500, Z f corpus; ~60 NEFs by hand; #633; D4, D90, D5000, D600 and D610 image area measured against the camera JPEG (white level too for the D4, D90 and D5000) | | NEF | packed 14-bit stored under a compressed tag (Z 6II uncompressed) | ⬜ | #769; fix in PR #671 (not merged) | | NEF | High Efficiency / HE★ (JPEG XS) | ⬜ | named, preview (#193); needs a JPEG XS decoder | @@ -214,11 +216,11 @@ sample files. A pointer for contributors, not legal advice. | Item | Adobe | LightCraft | |---|---|---| -| Default profile per model | Adobe Standard for every model (1,500 profile names in the bundle) | the DNG's own matrices; else a pooled profile (**9 bundled**: ILCA-99M2, ILCE-7CR, ILCE-7M4, ILCE-7RM5, X-H2S, X-T4, Nikon Z 6II, Z 8 and D5300); else a guarded per-file fit to the camera's embedded JPEG; else spectral matrices (**52 models**, rawtoaces-data, and the bodies sharing their sensors: 6 Fujifilm bodies take the X-T3's (X-T30, X-T30 II, X-Pro3, X100V, X-E4, X-M5) and 2 take the X-T4's (X-S10, X-S20)); else a neutral matrix | +| Default profile per model | Adobe Standard for every model (1,500 profile names in the bundle) | the DNG's own matrices; else a pooled profile (**10 bundled**: EOS D60, ILCA-99M2, ILCE-7CR, ILCE-7M4, ILCE-7RM5, X-H2S, X-T4, Nikon Z 6II, Z 8 and D5300); else a guarded per-file fit to the camera's embedded JPEG; else spectral matrices (**52 models**, rawtoaces-data, and the bodies sharing their sensors: 6 Fujifilm bodies take the X-T3's (X-T30, X-T30 II, X-Pro3, X100V, X-E4, X-M5) and 2 take the X-T4's (X-S10, X-S20)) or chart-measured ones (**1 model**, the EOS D60); else a neutral matrix | | Camera Matching looks | 481 models (plus phone-maker looks) | none (the per-file JPEG fit imitates the camera's own look) | | Creative profiles | Adobe Color, Portrait, Landscape, Vivid, Monochrome… | our own looks (Color, Neutral, Vivid, Landscape, Portrait, Monochrome) | | DCP / dual-illuminant model | ✅ | dual-illuminant DNG colour (ColorMatrix / ForwardMatrix 1-2, CameraCalibration, AnalogBalance), HueSatMap, LookTable, ProfileToneCurve; external `.dcp` never loaded (clean-room) | -| White balance in Kelvin | ✅ for every model | absolute Kelvin where the camera has colour matrices (its own, or one of the 52 spectral rows), also for files whose look is a per-file fit (#730), or where the camera's white-balance presets in the file give the scale (6 Sony bodies and the Canon PowerShot G1 X Mark III, `kelvin_presets`), or the neutral pair a Fujifilm X-Trans III RAF stores (6 bodies, #730); other raws keep the relative scale (#296) | +| White balance in Kelvin | ✅ for every model | absolute Kelvin where the camera has colour matrices (its own, one of the 52 spectral rows or the chart-measured EOS D60), also for files whose look is a per-file fit (#730), or where the camera's white-balance presets in the file give the scale (6 Sony bodies and the Canon PowerShot G1 X Mark III, `kelvin_presets`), or the neutral pair a Fujifilm X-Trans III RAF stores (6 bodies, #730); other raws keep the relative scale (#296) | **How far the default rendering is from Adobe's** (CIE ΔE; measured where stated): @@ -280,9 +282,8 @@ works, from [raw/usage-weights.tsv](raw/usage-weights.tsv). | 5 | Sony DSLR-A200…A390 / A850 12-bit compressed ARW | +0.4 | 3–6 | ~0.08 | samples on raw.pixls.us | | 6 | Pre-X FinePix RAF | +0.6 | 4–8 | ~0.08 | samples | | 7 | Sony A100 regression (#702) and DSC-F828 sensor decoding (preview-only) | +0.1 | 1–2 | ~0.07 | samples on raw.pixls.us | -| 8 | Canon CRW (2000–2004 bodies) | +1.2 by the uniform tail weight, far less by real use | 6–12 | ≤ 0.1 | samples | -| 9 | Nikon HE / HE★ NEF (Z 8, Z 9, Z 6III, Z f, Z50II, Z5II set to HE) | +1.1 | 20–40 | ~0.04 | a pure-Rust JPEG XS decoder | -| 10 | Compressed SRW, Hasselblad 3FR, IIQ, X3F, KDC, DCR, GPR (ERF, MEF, MRW done) | +0.6 together | 45–85 | ~0.01 | samples; low use | +| 8 | Nikon HE / HE★ NEF (Z 8, Z 9, Z 6III, Z f, Z50II, Z5II set to HE) | +1.1 | 20–40 | ~0.04 | a pure-Rust JPEG XS decoder | +| 9 | Compressed SRW, Hasselblad 3FR, IIQ, X3F, KDC, DCR, GPR (ERF, MEF, MRW done) | +0.6 together | 45–85 | ~0.01 | samples; low use | Beyond decoding: @@ -299,6 +300,8 @@ Beyond decoding: |---|---|---| | 2026-10-26 | patch | Bundled camera profile count 5 → 9 (Nikon Z 6II, Z 8 and D5300, ILCE-7RM5); counts only | | 2026-10-23 | minor | Lens profiles: Sony ILCE-6700 (APS-C) in-file distortion through the ILCE-7RM6 conversion, knots spanning its own crop; lens score unchanged (~10%: one more body) | +| 2026-10-25 | patch | CRW variant rows split by verification status (EOS and G1/G6 verified; G2/G3/G4/Pro1/S-series unverified; G5 preview); the CIFF heap walk is bounded by a per-file record budget, so a block of sub-heap records pointing back at their parent no longer hangs decode | +| 2026-10-13 | minor | Canon CRW decoded (clean-room derivation, craft-libs `crates/crw/clean-room/`): EOS D30/D60/10D/300D, PowerShot G1/G6 verified pixel-exact (28 of 29 corpus files); G5 preview (entropy anomaly); as-shot WB from ColorBalance 0x10a9; 243 verified, 950 unverified, 217 preview; Canon bucket 76% (21% verified) | | 2026-10-18 | patch | X-T30, X-T30 II, X-Pro3, X100V, X-S10 and X-E4 take the X-T3's spectral matrices (same 26.1 MP X-Trans CMOS 4 sensor): an X-T30 raw with a black-and-white (Acros) camera JPEG no longer opens in the muted neutral fallback | | 2026-10-16 | minor | Kelvin white balance from Canon CR2 `ColorData` presets (PowerShot G1 X Mark III) | | 2026-10-15 | minor | DNGs with a look table and no tone curve (Lightroom-written, Adobe Standard) fit their tone to the embedded preview, also Lightroom's 1024 px one: two lossy ILCE-7CM2 DNGs no longer render 0.8 EV dark and flat | @@ -330,6 +333,9 @@ Beyond decoding: | 2026-10-14 | minor | 97 Canon CR2, Pentax and Ricoh models verified (one CC0 raw.pixls.us file each, pinned, checked against the camera JPEG; `canon_pentax_corpus.rs`): 334 verified, 844 unverified, 232 preview; 50% of photographers on a verified body; use-weighted 85% unchanged | | 2026-10-14 | minor | Integration batch 36: CC0 per-model verification for 117 Nikon/Fujifilm, 97 Canon CR2/Pentax/Ricoh, 21 Sony/Leica and 23 Olympus/Samsung bodies, 22 more CR3 bodies and 30 more models; Epson ERF and Mamiya MEF decode; measured Nikon image areas (#1053, #1770): 515 verified, 675 unverified, 218 preview, 38 unsupported (re-measured with `coverage.py`) | | 2026-10-15 | minor | By-maker and by-use tables re-measured with `coverage.py` after batches 36–39 (545 verified, 651 unverified, 212 preview, 38 unsupported; ~94% of photographers' cameras fully work, ~85% on a verified body); rows the per-model PRs had updated are current again | +| 2026-10-15 | minor | Canon CRW merged with batches 36–39 and re-measured with `coverage.py`: 551 verified, 660 unverified, 197 preview, 38 unsupported; Canon 98% (94% on verified bodies); use-weighted 94% and 86% | +| 2026-10-25 | minor | Canon EOS D60: dual-illuminant matrices fitted to our own ColorChecker Classic shots (tungsten, sun; `crates/raw/src/chart_table.rs`), the first chart-calibrated model: its CRWs edit white balance in Kelvin; counts unchanged | | 2026-10-18 | minor | Minolta MRW decoded (`vendor/mrw.rs`): DiMAGE 7, 7Hi, A1, A2, A200 and Dynax / Maxxum 7D verified; 551 verified, 656 unverified, 201 preview (re-measured with `coverage.py`) | | 2026-10-23 | minor | AHD blends its horizontal and vertical candidates where their homogeneity is close, so flat noise no longer becomes a maze pattern; Bayer demosaic row notes the pre-white-balance demosaic (#1328); scores unchanged | | 2026-10-30 | patch | RAF camera look fitted on the vignetting-corrected sensor (`LOOK_VERSION` 13): renders were up to 0.3 EV too bright in midtones and 0.6 EV in shadows; X-S10 mean ΔE00 vs the camera JPEG 2.71 → 2.55 on 60 photos | +| 2026-10-26 | minor | Canon CRW decoding moved to the shared craft-libs crate `craft-crw` (local path dependency until that branch merges); `vendor/crw.rs` is now the adapter onto `RawImage`, and the clean-room derivation record lives in craft-libs `crates/crw/clean-room/`; counts unchanged | diff --git a/docs/raw/coverage.py b/docs/raw/coverage.py index 3db93ce81..24f6ddd34 100644 --- a/docs/raw/coverage.py +++ b/docs/raw/coverage.py @@ -32,6 +32,10 @@ HERE = os.path.dirname(os.path.abspath(__file__)) 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|$)") + +# CIFF-container CRW bodies (crates/raw/src/vendor/crw.rs): every CRW EOS and the CIFF PowerShots +# (the G and S series and the Pro 1). +CRW_DECODED = re.compile(r"^EOS |^PowerShot (G[1-6]$|Pro ?1$|S[34567]0$|S45$)") # Fujifilm bodies before the X series whose RAF has no raw IFD (crates/raw/src/vendor/raf.rs). RAF_OLD = re.compile(r"^(FinePix (E|F|S\d|S\d{3,4}|IS)|IS Pro|S5Pro|S3Pro|S2Pro)", re.I) @@ -89,9 +93,16 @@ def classify(maker, camera, ext_field): return "unverified", "MEF", "MEF decoder (uncompressed 12-bit)" if e == "ERF": return "unverified", "ERF", "ERF decoder (12-bit 16-byte blocks)" + if e == "CRW": + # CIFF-era bodies decode (craft-libs crates/crw/clean-room/02-crw-format.md): EOS D30/D60/10D/300D and + # the CIFF PowerShots; the pre-CIFF compacts (PowerShot 600, A5, A50, Pro70) and the + # unprobed Pro90 IS stay preview. The G5 is preview by evidence (entropy anomaly). + if CRW_DECODED.search(camera): + return "unverified", "CRW", "CRW decoder (CIFF; selector 0-2 tables)" + return "preview", "CRW", "pre-CIFF CRW is not decoded" if e == "MRW": return "unverified", "MRW", "MRW decoder (12-bit packed, 16-bit words)" - if e in ("CRW", "3FR", "FFF", "IIQ", "MOS", "KDC", "X3F", "RAW", "ORI"): + if e in ("3FR", "FFF", "IIQ", "MOS", "KDC", "X3F", "RAW", "ORI"): return "preview", e, f"{e} is opened from its embedded JPEG only" return "unsupported", e, f"{e or 'unknown'} is not imported" diff --git a/docs/raw/coverage.tsv b/docs/raw/coverage.tsv index c062ea95e..cad79853a 100644 --- a/docs/raw/coverage.tsv +++ b/docs/raw/coverage.tsv @@ -135,7 +135,7 @@ Canon EOS 6D CR2 2012 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_ Canon EOS 6D Mark II CR2 2015 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) Canon EOS 7D CR2 2008 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES) Canon EOS 7D Mark II CR2 2013 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) -Canon EOS 10D CRW 2003 preview CRW is opened from its embedded JPEG only +Canon EOS 10D CRW 2003 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES; 3 files) Canon EOS 20D CR2 2003 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) Canon EOS 20Da CR2 2005 unverified CR2 lossless JPEG decoder Canon EOS 30D CR2 2005 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) @@ -149,9 +149,9 @@ Canon EOS 80D CR2 2015 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW Canon EOS 90D CR3 2018 verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md) Canon EOS 200D (EOS Kiss X9, EOS Rebel SL2) CR2 2015 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) Canon EOS 250D (EOS Rebel SL3, EOS 200D II, EOS Kiss X10) CR3 2018 verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md) -Canon EOS D30 CRW 2003 preview CRW is opened from its embedded JPEG only -Canon EOS D60 CRW 2003 preview CRW is opened from its embedded JPEG only -Canon EOS 300D (EOS Digital Rebel, EOS Kiss Digital) CRW 2003 preview CRW is opened from its embedded JPEG only +Canon EOS D30 CRW 2003 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES) +Canon EOS D60 CRW 2003 verified corpus: crates/raw/tests/it/corpus.rs; 18 maintainer files exact (craft-libs crates/crw/clean-room/03-verification.md) +Canon EOS 300D (EOS Digital Rebel, EOS Kiss Digital) CRW 2003 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES; 3 files) Canon EOS 100D (EOS Digital Rebel SL1, EOS Kiss X7) CR2 2012 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) CR2 2008 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) Canon EOS 550D (EOS Digital Rebel T2i, EOS Kiss X4 Digital) CR2 2010 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES) @@ -202,20 +202,20 @@ Canon EOS R8 CR3 2022 verified corpus: crates/raw/tests/it/cr3_corpus.rs (exact Canon EOS R50 CR3 2022 verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md) Canon EOS R50 V CR3 2024 verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md) Canon EOS R100 CR3 2022 verified corpus: crates/raw/tests/it/cr3_corpus.rs (exact sensor hashes, docs/cr3.md) -Canon PowerShot 600 CRW 2003 preview CRW is opened from its embedded JPEG only -Canon PowerShot A5 CRW 2003 preview CRW is opened from its embedded JPEG only -Canon PowerShot A50 CRW 2003 preview CRW is opened from its embedded JPEG only -Canon PowerShot G1 CRW 2003 preview CRW is opened from its embedded JPEG only +Canon PowerShot 600 CRW 2003 preview pre-CIFF CRW is not decoded +Canon PowerShot A5 CRW 2003 preview pre-CIFF CRW is not decoded +Canon PowerShot A50 CRW 2003 preview pre-CIFF CRW is not decoded +Canon PowerShot G1 CRW 2003 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES) Canon PowerShot G1 X CR2 2012 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) Canon PowerShot G1 X Mark II CR2 2013 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) Canon PowerShot G1 X Mark III CR2 2017 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) -Canon PowerShot G2 CRW 2003 preview CRW is opened from its embedded JPEG only -Canon PowerShot G3 CRW 2003 preview CRW is opened from its embedded JPEG only +Canon PowerShot G2 CRW 2003 unverified CRW decoder (CIFF; selector 0-2 tables) +Canon PowerShot G3 CRW 2003 unverified CRW decoder (CIFF; selector 0-2 tables) Canon PowerShot G3 X CR2 2015 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) -Canon PowerShot G5 CRW 2003 preview CRW is opened from its embedded JPEG only +Canon PowerShot G5 CRW 2003 preview CRW entropy anomaly (craft-libs crates/crw/clean-room/02-crw-format.md section 4); embedded JPEG Canon PowerShot G5 X CR2 2015 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) Canon PowerShot G5 X Mark II CR3 2018 verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md) -Canon PowerShot G6 CRW 2003 preview CRW is opened from its embedded JPEG only +Canon PowerShot G6 CRW 2003 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES) Canon PowerShot G7 X CR2 2013 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) Canon PowerShot G7 X Mark II CR2 2015 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) Canon PowerShot G7 X Mark III CR3 2018 verified corpus: crates/raw/tests/it/cr3_corpus.rs (pinned sensor hash, camera-JPEG check, docs/cr3.md) @@ -227,17 +227,17 @@ Canon PowerShot G11 CR2 2008 verified corpus: crates/raw/tests/it/body_corpus.rs Canon PowerShot G12 CR2 2010 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) Canon PowerShot G15 CR2 2012 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) Canon PowerShot G16 CR2 2013 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) -Canon PowerShot Pro 1 CRW 2003 preview CRW is opened from its embedded JPEG only -Canon PowerShot Pro70 CRW 2003 preview CRW is opened from its embedded JPEG only -Canon PowerShot Pro90 IS CRW 2003 preview CRW is opened from its embedded JPEG only -Canon PowerShot S30 CRW 2003 preview CRW is opened from its embedded JPEG only -Canon PowerShot S40 CRW 2003 preview CRW is opened from its embedded JPEG only -Canon PowerShot S45 CRW 2003 preview CRW is opened from its embedded JPEG only -Canon PowerShot S50 CRW 2003 preview CRW is opened from its embedded JPEG only -Canon PowerShot S60 CRW 2003 preview CRW is opened from its embedded JPEG only +Canon PowerShot Pro 1 CRW 2003 unverified CRW decoder (CIFF; selector 0-2 tables) +Canon PowerShot Pro70 CRW 2003 preview pre-CIFF CRW is not decoded +Canon PowerShot Pro90 IS CRW 2003 preview pre-CIFF CRW is not decoded +Canon PowerShot S30 CRW 2003 unverified CRW decoder (CIFF; selector 0-2 tables) +Canon PowerShot S40 CRW 2003 unverified CRW decoder (CIFF; selector 0-2 tables) +Canon PowerShot S45 CRW 2003 unverified CRW decoder (CIFF; selector 0-2 tables) +Canon PowerShot S50 CRW 2003 unverified CRW decoder (CIFF; selector 0-2 tables) +Canon PowerShot S60 CRW 2003 unverified CRW decoder (CIFF; selector 0-2 tables) Canon PowerShot S90 CR2 2008 unverified CR2 lossless JPEG decoder Canon PowerShot S95 CR2 2010 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) -Canon PowerShot S70 CRW 2003 preview CRW is opened from its embedded JPEG only +Canon PowerShot S70 CRW 2003 unverified CRW decoder (CIFF; selector 0-2 tables) Canon PowerShot S100 CR2 2010 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) Canon PowerShot S100V CR2 2010 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) Canon PowerShot S110 CR2 2012 verified corpus: crates/raw/tests/it/body_corpus.rs (pinned sensor hash, camera-JPEG check) diff --git a/docs/raw/evidence.tsv b/docs/raw/evidence.tsv index 621255858..1e6e7399a 100644 --- a/docs/raw/evidence.tsv +++ b/docs/raw/evidence.tsv @@ -569,3 +569,10 @@ Konica Minolta DiMAGE A200 verified corpus: crates/raw/tests/it/corpus.rs (xtask 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) +Canon EOS D30 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES) +Canon EOS D60 verified corpus: crates/raw/tests/it/corpus.rs; 18 maintainer files exact (craft-libs crates/crw/clean-room/03-verification.md) +Canon EOS 10D verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES; 3 files) +Canon EOS 300D (EOS Digital Rebel, EOS Kiss Digital) verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES; 3 files) +Canon PowerShot G1 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES) +Canon PowerShot G6 verified corpus: crates/raw/tests/it/corpus.rs (xtask RAW_SAMPLES) +Canon PowerShot G5 preview CRW entropy anomaly (craft-libs crates/crw/clean-room/02-crw-format.md section 4); embedded JPEG diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 757ed9055..c54e4884a 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1,6 +1,6 @@ # Target-app parity: LightCraft vs Adobe Lightroom -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-24 · **Change:** minor (gap 9: CI runs the tests on Linux x64 since #2667; macOS, Windows and the GPU equivalence tests still don't; Minolta MRW decoded, 6 bodies verified; RAW: 83% of Adobe's models decode, 38% verified after the camera-coverage sweep #1327; Platforms: the Windows x64 installer verified on Windows 11) · **Target:** Adobe Lightroom Classic 15.5 (August 2026) and Lightroom 9.6 (desktop) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-26 · **Change:** minor (Canon CRW decoder shared via craft-libs `craft-crw`; Canon CRW decoded clean-room, 6 bodies verified; gap 9: CI runs the tests on Linux x64 since #2667; macOS, Windows and the GPU equivalence tests still don't; Minolta MRW decoded, 6 bodies verified; RAW: 85% of Adobe's models decode, 39% verified after the camera-coverage sweep #1327; Platforms: the Windows x64 installer verified on Windows 11) · **Target:** Adobe Lightroom Classic 15.5 (August 2026) and Lightroom 9.6 (desktop) The authoritative assessment of how close LightCraft is to Lightroom. The row-by-row checklist is [parity-checklist.md](parity-checklist.md); the ranked work list is [gaps.md](gaps.md); deep areas @@ -47,7 +47,7 @@ phase #85, duplicate Local entries #22, black GPU exports on an Intel iGPU #78). | Dimension | Breadth | Ready for real work | Remaining | Doc | |---|---:|---:|---:|---| | Features (weighted areas below) | ~77% (checklist 81.1%, measured) | ~61% | 600–1,100 h | [Features](#features) | -| ↳ RAW cameras, colour and lens profiles (part of features) | 83% of Adobe's 1,446 models decode (38% verified), measured | ~56% | 255–480 h | [raw-parity.md](raw-parity.md) | +| ↳ RAW cameras, colour and lens profiles (part of features) | 85% of Adobe's 1,446 models decode (39% verified), measured | ~56% | 255–480 h | [raw-parity.md](raw-parity.md) | | UI/UX fidelity | ~85% | ~70% | 32–54 h | [ui-parity.md](ui-parity.md) | | File formats (non-raw: images, sidecars, presets, catalogs) | ~85% | ~70% | 30–55 h | [file-format-parity.md](file-format-parity.md) | | Hardware | ~50% | ~40% | 40–80 h (+ tethering and printing, counted under Classic modules) | [hardware-parity.md](hardware-parity.md) | @@ -68,7 +68,7 @@ from the checklist sections and the deep-dive documents. |---|---:|---:|---:|---:|---| | Import and library (import, folders, albums, smart albums, keywords, metadata, search, faces) | 15% | 90% | 80% | 25–45 h | checklist A–E 83–92%; robust journal; 85k-photo libraries | | RAW decoding and camera coverage | 15% | 81% | 77% | 120–220 h | [raw-parity.md](raw-parity.md), measured model by model: 85% of photographers' cameras decode (use-weighted), × 0.9 for per-body bugs; CR3 variants, compressed ORF, HE NEF | -| Colour, camera profiles, render fidelity | 10% | 65% | 35% | 80–150 h | 9 bundled profiles vs Adobe's 1,500; per-file fits median ΔE ~5 vs the camera JPEG, ~10% far off; no fidelity suite ([raw-parity.md](raw-parity.md#colour)) | +| Colour, camera profiles, render fidelity | 10% | 65% | 35% | 80–150 h | 10 bundled profiles vs Adobe's 1,500; per-file fits median ΔE ~5 vs the camera JPEG, ~10% far off; no fidelity suite ([raw-parity.md](raw-parity.md#colour)) | | Global develop adjustments (light, colour, curves, grading, effects, detail) | 15% | 93% | 70% | 30–60 h | checklist F 91.7%; character tuned by eye | | Optics, lens profiles, crop and geometry | 6% | 80% | 50% | 40–80 h | no lens database; crop / Upright complete | | Masking | 8% | 67% | 45% | 60–110 h | checklist K 67.4%; AI masks heuristic or SAM 3 without mirror | @@ -301,6 +301,7 @@ P2 items 12–25 h; look tuning, performance, packaging, hardening 25–45 h. | Date | Change | Summary | |---|---|---| | 2026-10-24 | patch | Stability, the stability discount, the Beta-needs list and the estimate history: tests run in CI on Linux x64 since #2667 (FreeBSD nightly), not on macOS or Windows (gap 9); percentages and hours unchanged | +| 2026-10-13 | minor | Canon CRW decoded (clean-room, craft-libs `crates/crw/clean-room/`): 551 models verified with the coverage sweep (557 with MRW after merging main), Canon preview rows 21 → 6; RAW decoding unchanged | | 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 | @@ -320,3 +321,4 @@ P2 items 12–25 h; look tuning, performance, packaging, hardening 25–45 h. | 2026-10-10 | minor | Stage re-checked against the core-workflow alpha gate: passes, stays alpha | | 2026-10-10 | major | Created as progress docs: full re-measure (checklist recount, installed Lightroom 9.6 bundle listing, code counts, catalogs, CI), feature areas with weights, dimensions, AI, stage, calibration; merged ROADMAP's *Where we stand* and *Parity estimate* | | 2026-10-14 | minor | RAW: 82% of Adobe's models decode, 36% verified (integration batch 36) | +| 2026-10-26 | minor | Canon CRW decoder moved to craft-libs as `craft-crw` (shared with the other Craft apps); LightCraft keeps the adapter, the EOS D60 colour work and its records; numbers unchanged | diff --git a/tools/spectral_camera_table.py b/tools/spectral_camera_table.py index d936983b4..f77d25eca 100644 --- a/tools/spectral_camera_table.py +++ b/tools/spectral_camera_table.py @@ -129,6 +129,7 @@ def main(root, out): print(f"{f}: training dE76 A {e1:.3f}, D65 {e2:.3f}", file=sys.stderr) rows.append( f' Camera {{\n make: "{rust_lit(header["manufacturer"])}",\n model: "{rust_lit(header["model"])}",\n' + " illuminant: [17, 21],\n" f" color_matrix: [{mat(cm1)}, {mat(cm2)}],\n forward_matrix: [{mat(fm1)}, {mat(fm2)}],\n }}," ) aliases = json.load(open(os.path.join(root, "data/aliases.json")))["data"]["camera"]["model"] diff --git a/xtask/src/raw_samples.rs b/xtask/src/raw_samples.rs index c9cbe7e42..ae386352d 100644 --- a/xtask/src/raw_samples.rs +++ b/xtask/src/raw_samples.rs @@ -926,6 +926,63 @@ pub(crate) const RAW_SAMPLES: &[(&str, &str, &str)] = &[ "https://raw.pixls.us/getfile.php/4204/nice/Leica%20-%20D-Lux%207%20-%204:3.RWL", "4f6db6492da83f7f2a5012591e370557249d1ed44d4c948784016605b799327c", ), + // Canon CRW (CIFF): the clean-room decode's verification corpus (craft-libs crates/crw/clean-room/03-verification.md); + // the PowerShot G5 is the documented anomaly the decoder refuses (embedded JPEG preview) + ( + "crw-canon-eos-d30.crw", + "https://raw.pixls.us/getfile.php/1307/nice/Canon%20-%20EOS%20D30%20-%20RAW%20(3:2).CRW", + "4ee482f2b6f9f1a8d370703ea04e9f2abaa49356973a7f56b229af93a78e8a2e", + ), + ( + "crw-canon-eos-d60.crw", + "https://raw.pixls.us/getfile.php/2010/nice/Canon%20-%20EOS%20D60%20-%20RAW%20(3:2).CRW", + "2b98659ecff170af1dfe44f89b493139e75e995b2979686f6ff0847cfb56b97f", + ), + ( + "crw-canon-eos-10d-a.crw", + "https://raw.pixls.us/getfile.php/2027/nice/Canon%20-%20EOS%2010D%20-%20RAW%20(3:2).CRW", + "b0730eb3a67cc2281a32e0b2ee104199246928d954a68b7e5b72366514c73d02", + ), + ( + "crw-canon-eos-10d-b.crw", + "https://raw.pixls.us/getfile.php/3164/nice/Canon%20-%20EOS%2010D%20-%20RAW%20(3:2).crw", + "e879c94afdc64dabc88b01ce2d02628aa225259303ec71dd61f7fb0322bfea2d", + ), + ( + "crw-canon-eos-10d-c.crw", + "https://raw.pixls.us/getfile.php/3165/nice/Canon%20-%20EOS%2010D%20-%20RAW%20(3:2).crw", + "58144118854657df5cd2185df40fecf6b665fc6754fef0416c2f222b68b025c6", + ), + ( + "crw-canon-eos-300d-a.crw", + "https://raw.pixls.us/getfile.php/1983/nice/Canon%20-%20EOS%20300D%20-%20RAW%20(3:2).crw", + "47da16e1668af5500eea63cf42cf2992d9cb3d32180b8d786f17f1e2a57f39f4", + ), + ( + "crw-canon-eos-300d-b.crw", + "https://raw.pixls.us/getfile.php/3162/nice/Canon%20-%20EOS%20300D%20-%20RAW%20(3:2).CRW", + "9e7f62239fe728046af28d0d4a8a722d35d40146a79794a5bda1775306af4383", + ), + ( + "crw-canon-eos-digital-rebel.crw", + "https://raw.pixls.us/getfile.php/1308/nice/Canon%20-%20EOS%20Digital%20Rebel%20-%20RAW%20(3:2).CRW", + "d6a3131bfac390fde01b96882ec378211f04570c3252708b58f0d05b33a9fdfc", + ), + ( + "crw-canon-powershot-g1.crw", + "https://raw.pixls.us/getfile.php/2073/nice/Canon%20-%20PowerShot%20G1%20-%20RAW%20(4:3).crw", + "f39be5acf14057ce04527fd8d0574e57ec8ed5fed082d7bfaff763075a44ee8a", + ), + ( + "crw-canon-powershot-g5.crw", + "https://raw.pixls.us/getfile.php/1746/nice/Canon%20-%20PowerShot%20G5%20-%20RAW%20(4:3).CRW", + "bb084223d714871b99de4f549eaf00513e30a7080feeead700252c2008060196", + ), + ( + "crw-canon-powershot-g6.crw", + "https://raw.pixls.us/getfile.php/4226/nice/Canon%20-%20PowerShot%20G6%20-%20RAW%20(4:3).CRW", + "244378e1f5660b6cc6cefc93a75ed4928d0b28aa6304ff327b61c748538f4fef", + ), ( "mrw-minolta-dimage7hi-words16.mrw", "https://raw.pixls.us/data/Minolta/DiMAGE%207Hi/RAW_MINOLTA_DIMAGE_7HI.MRW",