diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c81bf0507..b7e3036d6 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -5,8 +5,9 @@
# The signing jobs (macOS, Windows) and the draft-release job run in the `release` environment
# (restricted to the `release` branch), which holds the signing secrets. Every secret is optional:
# missing ones produce unsigned artifacts and a warning, never a failed build. The jobs that sign
-# nothing (version, Linux, Flatpak, FreeBSD, web) run without it, so a dispatch on any branch is
-# a dry run of them; the release job needs the signed jobs, so a branch run never drafts a release.
+# nothing (version, Linux, Linux riscv64, Flatpak, FreeBSD, web) run without it, so a dispatch on
+# any branch is a dry run of them; the release job needs the signed jobs, so a branch run never
+# drafts a release.
name: Release
on:
@@ -272,6 +273,79 @@ jobs:
path: dist/release/*.flatpak
if-no-files-found: error
+ linux-riscv64:
+ name: Linux riscv64
+ needs: version
+ # Cross-compiled on x86_64 (no hosted RISC-V runners) against the runner's own Ubuntu release
+ # from ports.ubuntu.com, so the binaries need that release's glibc (24.04: 2.39). 22.04's
+ # riscv64 ports lag its amd64 updates, which breaks the multiarch install. tar.gz only; the
+ # CLI is smoke-tested under QEMU.
+ runs-on: ubuntu-24.04
+ # No `environment: release`: this job signs nothing, so it gets no release secrets.
+ env:
+ LIGHTCRAFT_VERSION: ${{ needs.version.outputs.version }}
+ LIGHTCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ # Fonts (optional build input; required for releases): see craftrules standards/fonts.md.
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ repository: storytold/craft-fonts
+ ref: 8dcdacd5153e64560d109541a47d806f26f048c0 # bump deliberately
+ path: craft-fonts
+ - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
+ with:
+ targets: riscv64gc-unknown-linux-gnu
+ - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
+ with:
+ key: release-linux-riscv64
+ - name: Apply version override
+ if: inputs.version != ''
+ run: cargo xtask version set "$LIGHTCRAFT_VERSION"
+ - name: Install cross-compilation dependencies
+ run: |
+ . /etc/os-release
+ sudo dpkg --add-architecture riscv64
+ # Pin the existing (archive.ubuntu.com) sources to amd64; riscv64 comes from ports.
+ if [ -f /etc/apt/sources.list.d/ubuntu.sources ]; then
+ sudo sed -i '/^Types: deb/a Architectures: amd64' /etc/apt/sources.list.d/ubuntu.sources
+ fi
+ if [ -f /etc/apt/sources.list ]; then
+ sudo sed -i 's/^deb /deb [arch=amd64] /g' /etc/apt/sources.list
+ sudo sed -i 's/^deb-src /deb-src [arch=amd64] /g' /etc/apt/sources.list
+ fi
+ for suite in "$VERSION_CODENAME" "$VERSION_CODENAME-updates" "$VERSION_CODENAME-security"; do
+ echo "deb [arch=riscv64] http://ports.ubuntu.com/ubuntu-ports $suite main universe restricted multiverse"
+ done | sudo tee /etc/apt/sources.list.d/riscv64.list
+ sudo apt-get update
+ sudo apt-get install -y --no-install-recommends \
+ gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu libc6-dev-riscv64-cross libc6-dev:riscv64 qemu-user \
+ libxkbcommon-dev:riscv64 libwayland-dev:riscv64 libx11-dev:riscv64 libxrandr-dev:riscv64 \
+ libxi-dev:riscv64 libgl1-mesa-dev:riscv64
+ - name: Build and package (tar.gz)
+ env:
+ CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts
+ CRAFT_FONTS_REQUIRED: "1"
+ CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER: riscv64-linux-gnu-gcc
+ PKG_CONFIG_SYSROOT_DIR: /
+ PKG_CONFIG_PATH: /usr/lib/riscv64-linux-gnu/pkgconfig:/usr/share/pkgconfig
+ PKG_CONFIG_ALLOW_CROSS: "1"
+ CROSS_ARCH: riscv64
+ CROSS_TARGET: riscv64gc-unknown-linux-gnu
+ CROSS_COMPILE: riscv64-linux-gnu-
+ EMULATOR: qemu-riscv64
+ QEMU_LD_PREFIX: /usr/riscv64-linux-gnu
+ run: packaging/linux/package.sh --formats tar
+ - name: Check the tarball
+ run: |
+ tar -tzf dist/release/lightcraft-*-linux-riscv64.tar.gz \
+ | grep -E '/bin/lightcraft$|/bin/lightcraft-cli$|/share/applications/ai\.storyteller\.lightcraft\.desktop$'
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
+ with:
+ name: release-linux-riscv64
+ path: dist/release/*.tar.gz
+ if-no-files-found: error
+
freebsd:
name: FreeBSD x86_64
needs: version
@@ -368,7 +442,7 @@ jobs:
release:
name: Draft GitHub Release
- needs: [version, macos, windows, linux, flatpak, freebsd, web]
+ needs: [version, macos, windows, linux, linux-riscv64, flatpak, freebsd, web]
runs-on: ubuntu-latest
environment: release
permissions:
diff --git a/AGENTS.md b/AGENTS.md
index b199c7f96..8cd199ff6 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -5,12 +5,18 @@ LightCraft is a clean-room, open-source, pure-Rust photo library + non-destructi
## Start every session here
1. Read `plan/STATUS.md` (current milestone, next unchecked task, blockers).
2. Read the task in `plan/execution-plan.md` §3, the relevant section of `plan/architecture.md`, and the README/docs of the crate you touch. Behaviour/visual reference: `plan/lightroom/` (incl. `10-observed-ui.md` + screenshots).
-3. **Know where we stand:** read [`ROADMAP.md`](ROADMAP.md) → *Where we stand* (honest assessment by dimension and
- by kind of user) and *Where we're going* (ordered priorities). The checklist counts features that *exist*; the real
- gaps are quality and coverage: camera colour calibration, CR3 / compressed raws, per-model verification, render
- fidelity against Lightroom, AI models, HDR / video / Classic modules. A ✅ row is not proof of parity — if you find a
- ✅ feature that is wrong or incomplete, downgrade it to 🟡 with a note.
-4. **Pick work** from [`docs/parity.md`](docs/parity.md) → *Top gaps* (ordered by user impact; the tracker has one row per feature, menu item and shortcut). When you land a feature, update its row(s) and the gap list in the same commit, and the *Where we stand* / *Where we're going* sections of `ROADMAP.md` when a listed gap closes; `cargo xtask parity` (part of `ci`) checks that every `cmd:`/`ctl:` id and path the tracker cites still exists, and `cargo xtask parity --write` refreshes its summary.
+3. **Know where we stand:** read [`ROADMAP.md`](ROADMAP.md) (stage, headline numbers, every dimension) and, for
+ method and evidence, [`docs/target-app-parity.md`](docs/target-app-parity.md). The checklist counts features that
+ *exist*; the real gaps are quality and coverage: camera colour calibration, CR3 / compressed raws, per-model
+ verification, render fidelity against Lightroom, AI models, HDR / video / Classic modules (raw detail:
+ [`docs/raw-parity.md`](docs/raw-parity.md)). A ✅ row is not proof of parity — if you find a ✅ feature that is
+ wrong or incomplete, downgrade it to 🟡 with a note.
+4. **Pick work** from [`docs/gaps.md`](docs/gaps.md) (ranked by user impact; `B` marks beta blockers). The row-by-row
+ tracker is [`docs/parity-checklist.md`](docs/parity-checklist.md) (one row per feature, menu item and shortcut).
+ When you land a feature, update its row(s) and the gap entry in the same commit, and the numbers in `ROADMAP.md` /
+ `docs/target-app-parity.md` when a listed gap closes (bump each doc's *Last updated* line and revision history, per
+ craftrules `standards/progress-docs.md`); `cargo xtask parity` (part of `ci`) checks that every `cmd:`/`ctl:` id and
+ path the tracker cites still exists, and `cargo xtask parity --write` refreshes its summary.
5. Follow the autonomous operation protocol (`plan/execution-plan.md` §7). Don't stop to ask unless §7 lists the decision as the user's.
`plan/` is gitignored (local-only).
diff --git a/CLAUDE.md b/CLAUDE.md
index 814bc5cf8..38f5bde4a 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -5,12 +5,18 @@ LightCraft is a clean-room, open-source, pure-Rust photo library + non-destructi
## Start every session here
1. Read `plan/STATUS.md` (current milestone, next unchecked task, blockers).
2. Read the task in `plan/execution-plan.md` §3, the relevant section of `plan/architecture.md`, and the README/docs of the crate you touch. Behaviour/visual reference: `plan/lightroom/` (incl. `10-observed-ui.md` + screenshots).
-3. **Know where we stand:** read [`ROADMAP.md`](ROADMAP.md) → *Where we stand* (honest assessment by dimension and
- by kind of user) and *Where we're going* (ordered priorities). The checklist counts features that *exist*; the real
- gaps are quality and coverage: camera colour calibration, CR3 / compressed raws, per-model verification, render
- fidelity against Lightroom, AI models, HDR / video / Classic modules. A ✅ row is not proof of parity — if you find a
- ✅ feature that is wrong or incomplete, downgrade it to 🟡 with a note.
-4. **Pick work** from [`docs/parity.md`](docs/parity.md) → *Top gaps* (ordered by user impact; the tracker has one row per feature, menu item and shortcut). When you land a feature, update its row(s) and the gap list in the same commit, and the *Where we stand* / *Where we're going* sections of `ROADMAP.md` when a listed gap closes; `cargo xtask parity` (part of `ci`) checks that every `cmd:`/`ctl:` id and path the tracker cites still exists, and `cargo xtask parity --write` refreshes its summary.
+3. **Know where we stand:** read [`ROADMAP.md`](ROADMAP.md) (stage, headline numbers, every dimension) and, for
+ method and evidence, [`docs/target-app-parity.md`](docs/target-app-parity.md). The checklist counts features that
+ *exist*; the real gaps are quality and coverage: camera colour calibration, CR3 / compressed raws, per-model
+ verification, render fidelity against Lightroom, AI models, HDR / video / Classic modules (raw detail:
+ [`docs/raw-parity.md`](docs/raw-parity.md)). A ✅ row is not proof of parity — if you find a ✅ feature that is
+ wrong or incomplete, downgrade it to 🟡 with a note.
+4. **Pick work** from [`docs/gaps.md`](docs/gaps.md) (ranked by user impact; `B` marks beta blockers). The row-by-row
+ tracker is [`docs/parity-checklist.md`](docs/parity-checklist.md) (one row per feature, menu item and shortcut).
+ When you land a feature, update its row(s) and the gap entry in the same commit, and the numbers in `ROADMAP.md` /
+ `docs/target-app-parity.md` when a listed gap closes (bump each doc's *Last updated* line and revision history, per
+ craftrules `standards/progress-docs.md`); `cargo xtask parity` (part of `ci`) checks that every `cmd:`/`ctl:` id and
+ path the tracker cites still exists, and `cargo xtask parity --write` refreshes its summary.
5. Follow the autonomous operation protocol (`plan/execution-plan.md` §7). Don't stop to ask unless §7 lists the decision as the user's.
`plan/` is gitignored (local-only).
diff --git a/Cargo.lock b/Cargo.lock
index 813afecdd..9ecb9c70d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -988,6 +988,16 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+[[package]]
+name = "colored"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
+dependencies = [
+ "lazy_static",
+ "windows-sys 0.59.0",
+]
+
[[package]]
name = "combine"
version = "4.6.8"
@@ -1062,6 +1072,15 @@ dependencies = [
"libc",
]
+[[package]]
+name = "craft-ui"
+version = "0.1.0"
+source = "git+https://github.com/storytold/craft-libs?rev=5c874d51eeb3de70e735c8ce87a59a7dedd47675#5c874d51eeb3de70e735c8ce87a59a7dedd47675"
+dependencies = [
+ "egui",
+ "serde",
+]
+
[[package]]
name = "crc32fast"
version = "1.5.2"
@@ -1202,6 +1221,19 @@ dependencies = [
"thousands",
]
+[[package]]
+name = "dify"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ce0fb972943b4e88cd03b8f92953df0c71bb05e0bde8e5b684895d808013cc"
+dependencies = [
+ "anyhow",
+ "colored",
+ "getopts",
+ "image",
+ "rayon",
+]
+
[[package]]
name = "digest"
version = "0.10.7"
@@ -1447,6 +1479,27 @@ dependencies = [
"winit",
]
+[[package]]
+name = "egui_kittest"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5eb3f3effb2726fee23449293e67acbf6cc6ce35525b1722bd5743bb348114d5"
+dependencies = [
+ "dify",
+ "eframe",
+ "egui",
+ "egui-wgpu",
+ "image",
+ "kittest",
+ "log",
+ "open",
+ "pollster 1.0.1",
+ "serde",
+ "tempfile",
+ "toml",
+ "wgpu",
+]
+
[[package]]
name = "either"
version = "1.18.0"
@@ -2230,6 +2283,15 @@ dependencies = [
"windows-link",
]
+[[package]]
+name = "getopts"
+version = "0.2.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
+dependencies = [
+ "unicode-width",
+]
+
[[package]]
name = "getrandom"
version = "0.2.17"
@@ -2588,6 +2650,25 @@ dependencies = [
"syn 2.0.119",
]
+[[package]]
+name = "is-docker"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "is-wsl"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
+dependencies = [
+ "is-docker",
+ "once_cell",
+]
+
[[package]]
name = "itertools"
version = "0.14.0"
@@ -2909,6 +2990,16 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
+[[package]]
+name = "kittest"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ceaa75eb0036a32b6b9833962eb18137449e9817e2e586006471925b727fd5"
+dependencies = [
+ "accesskit",
+ "accesskit_consumer 0.35.0",
+]
+
[[package]]
name = "kurbo"
version = "0.13.1"
@@ -2976,7 +3067,7 @@ dependencies = [
[[package]]
name = "lightcraft"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"eframe",
"egui",
@@ -2997,7 +3088,7 @@ dependencies = [
[[package]]
name = "lightcraft-catalog"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"crc32fast",
"lightcraft-develop",
@@ -3013,7 +3104,7 @@ dependencies = [
[[package]]
name = "lightcraft-cli"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"dhat",
"lightcraft-codecs",
@@ -3028,7 +3119,7 @@ dependencies = [
[[package]]
name = "lightcraft-codecs"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"avif-serialize",
"bytemuck",
@@ -3056,7 +3147,7 @@ dependencies = [
[[package]]
name = "lightcraft-color"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"lightcraft-geom",
"proptest",
@@ -3065,7 +3156,7 @@ dependencies = [
[[package]]
name = "lightcraft-denoise"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"faer-core",
"lightcraft-denoise-core",
@@ -3082,7 +3173,7 @@ dependencies = [
[[package]]
name = "lightcraft-denoise-core"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"lightcraft-raster",
"miniz_oxide 0.8.9",
@@ -3097,7 +3188,7 @@ dependencies = [
[[package]]
name = "lightcraft-develop"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"lightcraft-color",
"lightcraft-geom",
@@ -3109,7 +3200,7 @@ dependencies = [
[[package]]
name = "lightcraft-engine"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"ab_glyph",
"harfrust",
@@ -3146,7 +3237,7 @@ dependencies = [
[[package]]
name = "lightcraft-faces"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"matrixmultiply",
"ndarray",
@@ -3160,7 +3251,7 @@ dependencies = [
[[package]]
name = "lightcraft-fetch"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"log",
"rustls",
@@ -3171,7 +3262,7 @@ dependencies = [
[[package]]
name = "lightcraft-geom"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"proptest",
"serde",
@@ -3179,7 +3270,7 @@ dependencies = [
[[package]]
name = "lightcraft-gpu"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"bytemuck",
"lightcraft-color",
@@ -3197,7 +3288,7 @@ dependencies = [
[[package]]
name = "lightcraft-heif"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"heic-rs",
"rayon",
@@ -3205,7 +3296,7 @@ dependencies = [
[[package]]
name = "lightcraft-mcp"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"lightcraft-codecs",
"lightcraft-engine",
@@ -3216,7 +3307,7 @@ dependencies = [
[[package]]
name = "lightcraft-merge"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"lightcraft-codecs",
"lightcraft-color",
@@ -3232,7 +3323,7 @@ dependencies = [
[[package]]
name = "lightcraft-meta"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"lightcraft-geom",
"lightcraft-tiff",
@@ -3244,7 +3335,7 @@ dependencies = [
[[package]]
name = "lightcraft-pipeline"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"image",
"lightcraft-color",
@@ -3261,7 +3352,7 @@ dependencies = [
[[package]]
name = "lightcraft-preview"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"jpeg-encoder",
"lightcraft-raster",
@@ -3273,7 +3364,7 @@ dependencies = [
[[package]]
name = "lightcraft-raster"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"libc",
"lightcraft-color",
@@ -3285,7 +3376,7 @@ dependencies = [
[[package]]
name = "lightcraft-raw"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"jpeg-encoder",
"jxl-oxide",
@@ -3307,7 +3398,7 @@ dependencies = [
[[package]]
name = "lightcraft-scenes"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"image",
"lightcraft-color",
@@ -3318,7 +3409,7 @@ dependencies = [
[[package]]
name = "lightcraft-segment"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"candle-core",
"candle-nn",
@@ -3331,11 +3422,11 @@ dependencies = [
[[package]]
name = "lightcraft-sysmem"
-version = "0.5.0"
+version = "0.6.0"
[[package]]
name = "lightcraft-tiff"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"proptest",
"serde",
@@ -3344,9 +3435,11 @@ dependencies = [
[[package]]
name = "lightcraft-ui-egui"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
+ "craft-ui",
"egui",
+ "egui_kittest",
"lightcraft-catalog",
"lightcraft-codecs",
"lightcraft-color",
@@ -3369,7 +3462,7 @@ dependencies = [
[[package]]
name = "lightcraft-web"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"crc32fast",
"eframe",
@@ -4172,6 +4265,16 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+[[package]]
+name = "open"
+version = "5.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc"
+dependencies = [
+ "is-wsl",
+ "libc",
+]
+
[[package]]
name = "orbclient"
version = "0.3.55"
@@ -6829,7 +6932,7 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
[[package]]
name = "xtask"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"brotli",
"flate2",
diff --git a/Cargo.toml b/Cargo.toml
index ddd1c138f..c558caceb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,13 +3,14 @@ resolver = "3"
members = ["crates/*", "apps/*", "xtask"]
[workspace.package]
-version = "0.5.0"
+version = "0.6.0"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.90"
repository = "https://github.com/storytold/lightcraft"
[workspace.dependencies]
+craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "5c874d51eeb3de70e735c8ce87a59a7dedd47675", features = ["serde"] }
# internal
lightcraft-denoise-core = { path = "crates/denoise-core" }
lightcraft-denoise = { path = "crates/denoise", default-features = false }
diff --git a/README.md b/README.md
index 217092fbf..1a7ca07f8 100644
--- a/README.md
+++ b/README.md
@@ -297,18 +297,20 @@ lightcraft --control 7980 ~/Pictures/trip
## Feature status
-LightCraft is young and moving fast. **Where we honestly stand** (details in the [roadmap](ROADMAP.md#where-we-stand)):
+LightCraft is young and moving fast: **alpha**. **Where we honestly stand** (details in the [roadmap](ROADMAP.md) and
+[docs/target-app-parity.md](docs/target-app-parity.md)):
-- **By feature count we're at ~79%** of Lightroom (core features 98%), tracked row by row in
- [docs/parity.md](docs/parity.md).
-- **As a day-to-day Lightroom replacement we're nearer 60–70%.** It's great for JPEG/DNG and most Nikon / Sony /
- older-Canon raws on one machine.
+- **By feature count we're at ~81%** of Lightroom (core features 98%), tracked row by row in
+ [docs/parity-checklist.md](docs/parity-checklist.md).
+- **As a day-to-day Lightroom replacement we're nearer 61% for the whole product, ~49% for a typical working photographer and ~64% for
+ 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 ILCE-7CR, ILCE-7M4, X-H2S and X-T4 profiles; measured calibration is missing, 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 masks and denoise:** subject and sky selection are classical heuristics;
- **on-screen HDR display, video and the Classic Print / Book / Map modules.**
-- **What's next:** see [where we're going](ROADMAP.md#where-were-going).
+- **What's next:** see [the ranked gaps](docs/gaps.md) and [the roadmap](docs/roadmap.md).
| Area | Status |
|---|---|
@@ -318,10 +320,10 @@ LightCraft is young and moving fast. **Where we honestly stand** (details in the
| Masking: brush, linear, radial, luminance/colour range, add/subtract/intersect | ✅ (AI subject/sky use classical heuristics for now) |
| Crop, straighten tool + auto straighten, flip, rotate, aspect ratios, overlays | ✅ |
| Profiles (Color, Neutral, Vivid, Landscape, Portrait, Monochrome: our own looks), presets, versions, history, copy/paste/sync settings | ✅ |
-| Camera colour: DNG files use their own matrices | ✅ DNG · 🟡 own Sony/Fujifilm profiles; measured calibration database missing |
+| Camera colour: DNG files use their own matrices | ✅ DNG · 🚧 own Sony/Fujifilm profiles; measured calibration database missing |
| Native macOS menu bar (generated from the command registry), control channel + every widget addressable, headless UI snapshots | ✅ |
-| RAW: DNG, CR2, CR3 (lossless CRX Bayer and version 0x100/0x200 C-RAW), ARW, NEF (uncompressed + lossless/lossy compressed), Fujifilm RAF (uncompressed + lossless/lossy compressed, Bayer + X-Trans), Panasonic RW2 / Leica RWL / Panasonic RAW (every raw format, DMC-LX1 to DC-S1RM2), Pentax PEF, Olympus ORF (uncompressed), Samsung SRW (uncompressed); embedded previews for every format incl. CR3 | 🟡 · CR3, compressed ORF and compressed SRW decode ⬜ |
-| Detail: sharpening, luminance + colour noise reduction | ✅ · AI Denoise, Super Resolution ⬜ |
+| RAW: DNG, CR2, CR3 (lossless CRX Bayer and version 0x100/0x200 C-RAW), ARW, NEF (uncompressed + lossless/lossy compressed, incl. lossy after split), Fujifilm RAF (uncompressed + lossless/lossy compressed, Bayer + X-Trans), Panasonic RW2 / Leica RWL / Panasonic RAW (every raw format, DMC-LX1 to DC-S1RM2), Pentax PEF, Olympus ORF (uncompressed, 12.8 bpp), Samsung SRW (uncompressed); embedded previews for every format incl. CR3 | 🚧 · other CR3 variants, compressed ORF / SRW and Nikon HE NEF decode ⬜ ([raw parity](docs/raw-parity.md)) |
+| Detail: sharpening, luminance + colour noise reduction | ✅ · AI Denoise 🚧 (Bayer, user-installed weights), Super Resolution ⬜ |
| Remove / Heal / Clone spots (auto source), Visualize Spots, Red Eye and Pet Eye (auto pupil detection, catchlight) | ✅ · content-aware fill, spot pin editing 🚧 |
| Export: JPEG / PNG / TIFF / WebP / AVIF / DNG / original, sizing, file-size limit, output sharpening, naming templates, batch, metadata policy, text or image watermark | ✅ |
| HDR: HDR editing with a headroom limit, SDR rendition, Visualize HDR; export as ISO 21496-1 gain map JPEG, PQ AVIF or 32-bit float TIFF | ✅ · HDR display ⬜ |
@@ -332,7 +334,7 @@ LightCraft is young and moving fast. **Where we honestly stand** (details in the
| Optics (distortion, vignetting, auto + manual CA, defringe, lens corrections embedded in DNG files and Panasonic / Leica RW2 / RWL distortion data), Geometry (transforms, Constrain Crop), Upright (Auto/Level/Vertical/Full/Guided) | ✅ · camera lens profiles (our own) ⬜ |
| Photo Merge: HDR (auto-align, deghost), Panorama (spherical/cylindrical/perspective, boundary warp, auto crop), HDR Panorama → DNG | ✅ |
| GPU pipeline (wgpu compute, CPU-exact within 1/255), CPU fallback on device limits / errors | ✅ · WebGPU in the browser 🚧 |
-| AI: segmentation masks, AI denoise, super resolution, faces; video | ⬜ (see [roadmap](ROADMAP.md#where-were-going)) |
+| AI: segmentation masks, AI denoise, super resolution, faces; video | 🚧 (see [gaps](docs/gaps.md)) |
| Web build (same UI in the browser via WASM): persistent library in OPFS/IndexedDB, Web Worker rendering, export downloads | ✅ · WebGPU, Safari/Firefox testing 🚧 |
✅ works today · 🚧 in progress · ⬜ not started
@@ -341,6 +343,8 @@ LightCraft is young and moving fast. **Where we honestly stand** (details in the
## Quick start
+Workspace builds, including the desktop, web and CLI applications, require Rust 1.95 or newer.
+
```sh
git clone https://github.com/storytold/lightcraft && cd lightcraft
cargo run --release -p lightcraft # opens your library (~/Pictures/LightCraft Library; a new one starts with demo photos)
@@ -481,6 +485,8 @@ option to start it. For unattended installs: `msiexec /i lightcraft--window
| Fedora/RHEL/openSUSE | `lightcraft--linux-x86_64.rpm` | `lightcraft--linux-aarch64.rpm` | |
| Tarball | `lightcraft--linux-x86_64.tar.gz` | `lightcraft--linux-aarch64.tar.gz` | Unpack anywhere |
+RISC-V (riscv64): `lightcraft--linux-riscv64.tar.gz`, a tarball only (glibc 2.39 or newer, e.g. Ubuntu 24.04+).
+
### FreeBSD
| Build | File |
@@ -567,3 +573,5 @@ Forks and modified versions must remove them.
## Star history
[](https://www.star-history.com/?repos=storytold%2Flightcraft&type=date&legend=top-left)
+
+Library, Tools, and Presets panels can be regrouped, split, floated within the app window, closed, and reopened. Tools keeps the current Edit/Crop/Masking mode; the photo view and filmstrip stay together. Drag a panel header to a target edge or another header. Its context menu offers Float panel, Move to group, and Close panel. Existing panel toggles reopen panels at their saved location; Reset Panel Layout restores the app layout. Floating panels remain inside the current app window.
diff --git a/ROADMAP.md b/ROADMAP.md
index 9bb4744b9..c8062d539 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,242 +1,167 @@
# LightCraft roadmap
-Milestones toward full Adobe Lightroom parity (cloud Lightroom first, then every Lightroom Classic module), with wall-clock
-estimates for continuous (24/7) agent-driven development with 4–6 parallel agents. Estimates are calibrated on the sibling
-projects (DrawCraft reached its first four milestones in ≈ 4½ h) and are revised as milestones land.
+**Stage: alpha** · next: beta, ~14 points and ~340–600 h away
-## Where we stand
+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (full re-measure against Lightroom Classic 15.5 and the installed Lightroom 9.6; restructured to the progress-docs standard) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
-*Honest assessment, 2026-10-08. Agents: read this before picking work. The checklist in
-[`docs/parity.md`](docs/parity.md) counts features that **exist**; this section is about whether a photographer can
-**switch** from Lightroom. Update it when a gap below closes.*
+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
+[docs/target-app-parity.md](docs/target-app-parity.md).
-**In one line:** the checklist says **79.7%** (P0 98.2%, P1 95.6%, P2 41.6%), but measured by whether a working
-photographer could replace Lightroom without noticing, we are at roughly **60–70%**. The remaining gap is mostly
-**quality of results and camera coverage**, not missing buttons.
+## Headline numbers
-Caveat on the checklist: ✅ is set by whoever lands a feature, and nobody has systematically checked rows against
-Lightroom's behaviour or output. Bugs keep turning up in ✅ areas (CR2 colour-filter phase on some Canon models #85,
-duplicate Local entries #22, black GPU exports on an Intel iGPU #78).
-
-### By dimension
-
-| Dimension | Estimate | What's true today | Biggest gaps |
-|---|---:|---|---|
-| **Feature checklist** | 79.7% | P0 core and P1 nearly complete: import (Add / Copy / Move, templates, devices), library, grid/loupe/compare/survey, every Edit slider, curves, colour grading, masking tools, crop/Upright, heal/clone, presets/profiles, versions/history, sync, export, menus, shortcuts | P0: full-resolution zoom detail; P1: lens-profile database, content-aware fill (patch synthesis), video playback/trim |
-| **RAW coverage** (formats people shoot) | ~55% | DNG (all kinds), CR2, CR3 lossless Bayer and version 0x100/0x200 C-RAW (M50/R100/R8 pixel-exact corpus), ARW, NEF (uncompressed + Huffman lossless/lossy), RAF (uncompressed, lossless/lossy compressed Bayer and X-Trans), uncompressed ORF, uncompressed Samsung SRW (NX5 to NX1100, EX1, WB2000), RW2 / RWL / Panasonic RAW (every raw format, checked on 178 files from 118 Panasonic and Leica bodies), PEF; every container's embedded preview (incl. CR3) | **CR3** (every Canon since ~2018), compressed ORF, compressed Samsung SRW (NX1, NX30, NX300, NX500, NX2000, NX3000, NX3300, NX mini), Nikon lossy-after-split, Canon sRAW, HEIC/AVIF. NEFs labelled compressed but stored uncompressed (Z 6 packed 14-bit, D850 12-bit uncompressed). Nikon High Efficiency NEF (HE / HE★: Z 8, Z 9, Z 6III, Z f; JPEG XS-based, preview only, #193). Per-model verification is thin (~55 corpus files vs >1,000 models) |
-| **Colour & image quality** | ~55–65% | Pipeline is complete and fast; GPU path CPU-exact within 1/255 | **No measured camera calibration database**: ARW, NEF, RW2, RAF, CR3, CR2 and PEF have guarded camera-JPEG colour estimates, with bundled ILCE-7CR, ILCE-7M4, X-H2S and X-T4 profiles (docs/camera-preview-colour.md); 52 models whose JPEG fit is rejected start from matrices fitted to measured spectral sensitivities; other non-DNG raws and rejected estimates use a neutral matrix. Colour fidelity remains incomplete. No lens-profile database. No measured fidelity against Lightroom (tone, highlights, texture/clarity, NR, sharpening are tuned by eye) |
-| **AI & computational** | ~15–20% | Assisted culling (focus, bursts), auto tone, HDR/panorama merge; subject/sky/background masks as classical heuristics, or the photo's own segmentation mattes when it ships them (DNG semantic masks, e.g. iPhone ProRAW's sky matte); non-destructive Bayer RAW denoise with opt-in weights on pure-Rust CPU/GPU; **faces (MVP, [docs/faces.md](docs/faces.md))**: a pure-Rust detector (a 232 KB opt-in download), opt-in recognition models (one-click download or add a file; they run on the shared pure-Rust CPU interpreter; pinned SFace/AuraFace embeddings agree with an independent reference runtime on 40 public crops; one-thread medians 34–36 / 233–252 ms (AuraFace remains expensive)), a background scan that finds and embeds faces, name suggestions, a person's page with look-alike faces to confirm, an Unnamed faces section (look-alikes together, select a group and name it at once) | Real segmentation masks (subject, sky, people, objects, landscape, depth), denoise model policy / linear RGB / quality verification, super resolution, lens blur, generative remove, natural-language search; for faces: clusters with separators between groups, small faces in large photos (the detector looks at 640 px), thresholds calibrated on live faces, writing face regions back to XMP. **Blocked on a model strategy** (licensable weights or our own training; pure-Rust inference is feasible) |
-| **Workflow & library** | ~85% (single machine) | Robust catalog (journal + snapshots, background compaction, crash-tested), 85k-photo libraries stay responsive, Local browsing with automatic cleanup, XMP interop, keywords, smart albums, Move import | Opening an 85k library takes 1.7–4.7 s; no cloud sync (out of scope), no tablet companion (#74, roadmap), shared albums, publish services, tethering |
-| **Classic modules** | ~30% | Geotagging from GPX track logs, soft proofing (partial), slideshow (basic), paginated contact-sheet PDF export (A4/Letter, layout/captions, current edits, 150 dpi sRGB) | **Map view, Book, full Print module, Slideshow module, Web, publish services**: contact sheets work, custom print packages and printer colour management remain missing |
-| **HDR & video** | ~25% | HDR editing (headroom limit, SDR rendition, Visualize HDR); gain map JPEG, PQ AVIF and float TIFF export | On-screen HDR display, HLG, HDR settings from crs XMP; video play/trim/edit/export |
-| **Platform & robustness** | ~70% | macOS native; Windows/Linux builds; web via WASM; no-panic lints workspace-wide, `unsafe` confined to `crates/sysmem`; failed saves are reported; GPU errors fall back to CPU | Windows installer UI unverified on Windows (PR #79); GPU path proven only on Apple + user reports; English, Simplified/Traditional Chinese, Japanese, Brazilian Portuguese, Spanish, German, Russian and Ukrainian UI (see docs/localization.md); remaining technical errors; Traditional Chinese uses the Simplified Chinese font until craft-fonts has a TC face; accessibility partial; headless UI tests time out under machine load |
-
-### By kind of user
-
-| User | Readiness | What blocks them |
+| | Value | Kind |
|---|---:|---|
-| JPEG / DNG shooter, single machine | ~85% | Fidelity polish, AI masks |
-| Nikon / Sony / Panasonic / older-Canon raw shooter | ~65% | Camera colour fidelity and coverage (ARW, NEF and RW2 preview estimates are only a starting point); embedded distortion is supported for Panasonic / Leica RW2 and the Sony ILCE-7RM4A (Bayer ARW, 24–105 / 200–600 tested); other lens metadata needs coverage and validation |
-| Fujifilm raw shooter | ~65% | Uncompressed and lossless/lossy compressed RAF decoded; camera colour calibration remains missing. Verified on 13 bodies, including 14/16-bit GFX; see `docs/raf-compression.md` |
-| Canon CR3 / Olympus compressed-raw shooter | ~45% | CR3 lossless Bayer and version 0x100/0x200 C-RAW develop from sensor data (M50, R100, R8 verified pixel for pixel); other CRX variants and compressed ORF open as embedded JPEG previews. Camera colour and broader model verification remain |
-| Lightroom Classic power user | ~45% | Print, Book, Map, publish, tethering |
-| Relies on AI (masks, denoise) | ~25% | Object / Describe masks via optional SAM 3 (download not yet hosted); Bayer AI denoise needs separately installed weights; X-Trans/linear RGB, model policy and quality parity remain open |
+| **Feature breadth** (checklist) | **81.1%** of 517 rows (P0 98.0%, P1 95.1%, P2 46.9%) | measured: [parity-checklist.md](docs/parity-checklist.md) recount |
+| **Feature breadth** (weighted by use) | **~77%** | estimated |
+| **Ready for real work** (full target) | **~61%** (56–66%) | estimated: weighted sum over the dimensions |
+| **Mainstream practitioner** | **~49%** (44–54%) | estimated |
+| **Essentials user** | **~64%** (59–69%) | estimated |
+| Remaining to **beta** | **~340–600 h** (Opus 5.5 agent hours, ~60% parallel) | estimated |
+| Remaining to **full parity** | **~1,000–1,800 h** (~70% parallel) | estimated |
-## Where we're going
+Each audience with its own hours (Opus 5.5 agent wall-clock hours to ~95%):
-Priorities, in order. Each points at tracker rows in [`docs/parity.md`](docs/parity.md) → *Top gaps*.
+| Audience | Ready | Opus 5.5 agent wall-clock hours to ~95% | Work that dominates |
+|---|---:|---:|---|
+| Full target (ready for real work) | **~61%** | **1,000–1,800 h** (~70% parallelizes) | raw colour, lens and camera data; AI models; Classic output modules; localization; plug-in ecosystem |
+| Mainstream practitioner | **~49%** | **450–840 h** (~60% parallelizes) | camera colour and render fidelity; CR3 / ORF / HE NEF; lens database; subject / sky masks; Lightroom XMP exchange; stability |
+| Essentials user | **~64%** | **160–300 h** (~50% parallelizes) | default raw look for popular cameras; launch stability; basic-slider feel; opening CR3 / HEIC files people send |
-1. **Camera colour calibration of our own** (LR-PROF-CAMERACOLOR, P0): fit each camera to its own embedded JPEG, use
- matrices the files carry themselves, then chart shots. Sony ARW, Nikon NEF, Panasonic RW2, Fujifilm RAF, Canon CR3/CR2,
- Pentax PEF, Samsung SRW and Olympus ORF have the file-local fit (matrix + tone curve from their own JPEG; CR2 and PEF
- since issue #310, not yet checked on the corpus); 52 models whose fit is rejected fall back to matrices fitted to
- measured spectral sensitivities (rawtoaces-data); then validate fidelity.
-2. **Raw formats, clean-room** (LR-IMP-FORMATS, P0): Remaining **unverified CR3 CRX** variants, compressed ORF, NEF
- lossy-after-split, sRAW. Decided 2026-10-05: write our own decoders from prose descriptions (never decoder source,
- no LGPL dependency); compressed NEF (#86) is the template.
-3. **Verified camera coverage** (LR-IMP-CAMERA-COVERAGE, P0): a CC0 sample per model in the corpus, each decoded and
- checked for plausible colour; fix per-model bugs (#85).
-4. **Render fidelity suite** (LR-BEHAV-RENDER-FIDELITY, P1): measure our output against Lightroom on the same CC0 raws
- (references stay in the local `plan/`), then tune against the numbers.
-5. **Lens profiles of our own** (LR-EDIT-OPTICS-PROFILE, P1): DNG, Panasonic / Leica RW2 and Sony ILCE-7RM4A Bayer ARW
- embedded distortion are supported; broaden validation and decode remaining vignetting / lateral CA metadata.
-6. **AI model strategy** (maintainer decision): which permissively licensed models (or our own training) for
- segmentation masks and denoise. Bayer denoise inference is implemented in pure Rust with an opt-in GPL model; release policy and independently licensed weights remain open. Unblocks M12 and Enhance. Face detection and recognition run on our own checked Rust engine. Object / Describe masks now
- run Meta's SAM 3 in pure Rust (optional, user-consented download under the non-OSI SAM License; CDN mirrors still
- to be set up, docs/ai-masks.md); Subject / Sky / People, Raw Details and Super Resolution are still open.
-7. **Then:** HDR (Q), the Classic output modules (expand contact-sheet PDF export into full Print, then Map view, Book, Slideshow), video (R), localisation
- and accessibility.
+The gap between breadth and readiness is quality and coverage, not missing buttons: camera colour is
+estimated rather than measured, CR3 decodes on three verified Canon bodies, compressed ORF and HE NEF
+open as previews, rendering is tuned by eye, AI masks are heuristics, there is no lens database, and
+tests run in CI on FreeBSD only. **Why alpha:** core workflows run end to end, but readiness is below
+the ~75% beta bar (it is above the ~40% alpha bar) and Lightroom's main inputs, raw files, still have blocking gaps (Canon, ~30% of
+raw users, decodes CR3 on three verified bodies). It passes the alpha gate: all six core workflows
+(import, cull, develop, save and reopen, batch looks, export) work end to end on macOS
+([docs/roadmap.md](docs/roadmap.md#alpha-gate)). The three readiness numbers and their weights and discounts are in
+[target-app-parity.md](docs/target-app-parity.md#ready-for-real-work-mainstream-practitioner-and-essentials-user).
+Hours are calibrated on this repo's PR history; see
+[target-app-parity.md](docs/target-app-parity.md#how-hours-are-calibrated).
-Already closed in the week of 2026-10-03: compressed NEF (#10), Move import (#29), per-library smart-preview folder,
-Local roots / cleanup, grid and catalog performance at 85k photos (#35, #37), failed-save reporting, GPU export
-hardening (#78), copyright metadata (#51), GPX geotagging (#60), import tag help and folder templates (#31, #32).
+## By dimension
-Library folders now have records of their own in the catalog (`Catalog.folder_records`, keyed by folder path; colour
-labels first). Photos still store full paths, so moving a whole disk or folder relinks each photo. If that becomes a
-problem (an "Update Folder Location" for a renamed or replaced drive, journal growth on large moves), the next step is
-the Classic model: photos point to a folder id (root + relative path), and the records keyed by path become those rows.
+| 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 | ~56% | 81% of 1,446 Adobe models decode, 16% verified | 255–480 h | [raw-parity](docs/raw-parity.md) |
+| UI/UX fidelity | ~70% | ~85% | 40–70 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) |
+| Localization | ~52% of the 12 key languages | 7 of 12 present | 130–210 h (+145–255 h new scripts) | [localization-parity](docs/localization-parity.md) |
+| Performance | ~70% | ~80% | 30–50 h | [target-app-parity](docs/target-app-parity.md#by-dimension) |
+| Stability | ~60% | — | 30–60 h | [target-app-parity](docs/target-app-parity.md#by-dimension) |
+| Platforms | ~60% | macOS, Windows, Linux, FreeBSD, web | 25–45 h | [target-app-parity](docs/target-app-parity.md#by-dimension) |
+| Ecosystem / plug-ins | ~15% | ~25% | 40–80 h | [target-app-parity](docs/target-app-parity.md#by-dimension) |
+| AI features | ~20% | ~30% | 120–220 h (overlaps features) | [target-app-parity](docs/target-app-parity.md#ai) |
-Image navigation now supports native pinch zoom and two-finger pan in Detail, Compare and Reference views.
-The whole-image preview still has a configurable size cap (2,560 px default), so 100% on larger photos does not
-yet guarantee original-pixel detail; visible-region rendering remains a gap (LR-VIEW-ZOOM).
+## Features
-## Milestones
-
-**Status legend:** ✅ done · 🚧 in progress · ⬜ not started
-
-| # | Milestone | Scope (summary) | Estimate (h) | Status |
-|---|---|---|---|---|
-| M0 | Skeleton + visual shell | workspace, xtask CI + layering, geom/color/raster, develop model, pipeline v0, catalog v0, engine commands, Lightroom-look UI (grid, loupe, filmstrip, Edit panel), control channel, MCP, web build | 3–5 | ✅ |
-| M1 | Library core | import (JPEG/PNG/TIFF/WebP), EXIF/XMP, persistent catalog (op log + snapshots), albums, ratings/flags/labels, filter/search/sort, thumbnail cache, 100k-photo grid | 6–10 | ✅ (100k-photo grid scale test pending) |
-| M2 | Pipeline v1 (quality) | WB temp/tint, profiles, local tone mapping (highlights/shadows), curves, HSL, point colour, colour grading, texture/clarity/dehaze, vignette, grain, B&W, auto tone/WB, histogram, before/after | 10–15 | ✅ (look tuning vs our references ongoing) |
-| M3 | RAW I | TIFF/DNG (LJ92, deflate, tiles, opcodes), demosaic (AHD/PPG/bilinear), highlight recovery, DNG colour model, CR2, NEF, ARW, embedded previews | 10–15 | 🚧 (DNG, CR2, ARW, NEF uncompressed + Huffman lossless/lossy, embedded previews ✅; NEF lossy-after-split ⬜) |
-| M4 | Crop, geometry, optics | crop tool + overlays, straighten, Upright (auto/level/vertical/full/guided), manual transforms, CA, defringe, manual lens corrections | 6–10 | ✅ |
-| M5 | Performance | source pyramids, wgpu compute pipeline (CPU oracle), draft/full renders, prefetch, budgets (16 ms slider updates on 24 MP) | 10–15 | 🚧 (stage cache, source pyramid, wgpu pipeline, prefetch, memory budget ✅; colour NR at half resolution, GPU histogram ⬜) |
-| M6 | Masking | brush, linear/radial gradients, colour/luminance/depth range, add/subtract/intersect/invert, all local adjustments, masks panel | 8–12 | 🚧 (brush/linear/radial/colour/luminance range, add/subtract/intersect, masks panel ✅; depth range, AI masks ⬜) |
-| M7 | Detail | sharpening + masking preview, luminance/colour NR, Denoise, Raw Details, Super Resolution | 6–10 | 🚧 (sharpening, luminance/colour NR ✅; AI Denoise 🟡 (Bayer, optional weights; policy/fidelity open); Raw Details, Super Resolution ⬜) |
-| M8 | Heal / Remove | content-aware remove (PatchMatch), heal, clone, brush spots, visualize spots, red/pet eye | 6–10 | 🚧 (heal, clone, auto source, visualize spots, red/pet eye ✅; PatchMatch remove ⬜) |
-| M9 | Presets, profiles, versions, sync | preset browser + amount, create/import presets, profile browser, versions, history, copy/paste/sync settings | 5–8 | ✅ |
-| M10 | Export & share | export dialog (JPEG/PNG/TIFF/DNG/AVIF/JXL/original), sizing, sharpening, metadata, watermark, naming, batch jobs, XMP sidecars, HDR export | 6–10 | 🚧 (all formats incl. DNG/original, sizing, presets, background jobs, HDR gain map JPEG / PQ AVIF ✅; JXL encode ⬜) |
-| M11 | RAW II | CR3, RAF (X-Trans), ORF, RW2, PEF, SRW, 3FR, IIQ + long tail; camera calibration DB; HEIC/AVIF/JXL import | 20–35 | 🚧 (CR3 lossless CRX and C-RAW, RAF uncompressed and lossless/lossy compressed, RW2 (every raw format), PEF, ORF uncompressed ✅; **camera colour calibration** 🚧 (guarded ARW, NEF, RW2 and RAF preview fitting; measured database still missing), CR3, compressed ORF ⬜) |
-| M12 | AI & smart features | subject/sky/background/people/object masks, semantic search, faces/People (permissively licensed models, pure-Rust inference) | 20–40 | ⬜ |
-| M13 | Merge | HDR merge (deghost), panorama (projections, boundary warp, fill edges), HDR panorama | 10–15 | ✅ |
-| M14 | Video | import/playback/trim via FilmCraft crates, global edits + presets on video, video export | 6–10 | ⬜ |
-| M15 | Classic modules | Map, Book, Slideshow, Print, Web; smart collections, stacks, virtual copies, publish services, tethering | 25–40 | 🚧 (smart albums, stacks, virtual copies, compare/survey ✅; Map/Book/Slideshow/Print/Web ⬜) |
-| M16 | 1.0 polish | preferences, shortcut editor, accessibility, localization, packaging (dmg/msi/AppImage/web), hardening | 10–20 | 🚧 (settings, keyboard shortcuts sheet, packaging basics ✅; English/Chinese (Simplified, Traditional)/Japanese/Brazilian Portuguese/Spanish/German/Russian/Ukrainian localisation 🟡; accessibility and further locales ⬜) |
-
-## Parity estimate (feature count updated 2026-10-08; effort estimate from 2026-10-02)
-
-**By feature count** — from `docs/parity.md` (one row per Lightroom feature, menu item and shortcut; `cargo xtask
-parity` prints this line on every run, so it stays current):
-
-| Scope | Weighted completion | Rows | 2026-10-02 |
+| Area | Ready | Breadth | Remaining |
|---|---:|---:|---:|
-| P0 (core) | **98.2%** | 200 | 98.7% |
-| P1 (important parity) | **95.6%** | 149 | 94.4% |
-| P2 (later / AI / niche, incl. Classic modules) | **41.6%** | 160 | 29.4% |
-| **All in-scope rows** | **79.7%** | 509 | 75.6% |
+| Import and library | 80% | 90% | 25–45 h |
+| RAW decoding and camera coverage | 76% | 81% | 120–220 h |
+| Colour, camera profiles, render fidelity | 35% | 65% | 80–150 h |
+| Global develop adjustments | 70% | 93% | 30–60 h |
+| Optics, lens profiles, crop and geometry | 50% | 80% | 40–80 h |
+| Masking | 45% | 67% | 60–110 h |
+| Remove, heal, Enhance | 30% | 45% | 70–130 h |
+| Presets, profiles, versions, sync | 85% | 95% | 8–15 h |
+| Export | 75% | 89% | 15–30 h |
+| Merge and HDR | 60% | 85% | 20–35 h |
+| Views, compare, zoom, culling | 70% | 90% | 15–30 h |
+| Classic output modules (Map, Book, Slideshow, Print, publish, tethering) | 10% | 15% | 90–150 h |
+| Video | 0% | 0% | 25–45 h |
-The 2026-10-05 count includes three new 🟡 rows that make quality gaps visible (camera colour calibration, verified
-camera coverage, render fidelity), which is why P0 dipped slightly.
+Weights and evidence: [target-app-parity.md](docs/target-app-parity.md#features).
-✅ counts 1, 🟡 ½, ⬜ 0; out-of-scope rows (cloud sharing, Adobe accounts…) are left out.
+## Languages
-**By remaining effort** — rows are not equal: a shortcut and the whole Book module are one row each, and what is
-left is the heavy part (AI, video, Classic output modules, undocumented raw codecs). Remaining work in **Opus agent-hours**
-(one agent working continuously; calibrated on this project — a single lead agent closed ≈ 45 tracker rows of
-UI/feature work in ≈ 5 h on 2026-10-01, and ≈ 60 more (preset import incl. `.lrtemplate` / DNG / zip and masks, smart-album
-rule editor, auto sync, keyword and label sets, import options and DNG conversion, smart previews, external-editor round
-trip, slideshow, auto import…) in ≈ 12 h on 2026-10-02; four to six parallel agents built M0–M13 in ≈ 25 active hours):
+Measured from `crates/ui-egui/locales` (share of 2,304 catalog messages translated); none is `full`
+while lower-layer errors stay English. Detail: [localization-parity.md](docs/localization-parity.md).
-| Work package | Tracker rows | Agent-hours | Risk |
+| Language | Code | Translated | Status |
|---|---|---:|---|
-| Remaining P0/P1 UI and library features (folder rename/move, keyword painter, people view…) | ≈ 8 | 5–10 | low |
-| Raw codecs: CR3 (CRX), compressed ORF, NEF lossy-after-split, HEIC/AVIF decode | LR-IMP-FORMATS | 40–80 | **high** — clean-room black-box analysis, no permissive specs |
-| Lens-profile database of our own (calibration targets, fitting, data) | LR-EDIT-OPTICS-PROFILE | 15–30 | data collection |
-| Video: playback, trim, edits, export (pure-Rust decode, ideally shared with FilmCraft) | R. Video | 20–40 | medium |
-| AI: subject / sky / background / people / object masks, object-aware remove, AI denoise, super resolution, lens blur, people & faces, natural-language search, culling | ≈ 30 | 80–150 | **high** — permissively licensed weights, pure-Rust inference, maybe training |
-| HDR editing, display, visualisation and export | Q. HDR, LR-EXP-HDR | 15–25 | medium |
-| Classic modules: Map, Book, Slideshow module, Print, publish services, tethering, soft proofing | ≈ 50 | 60–100 | medium (large, well-understood) |
-| Smaller P2 items: Enhance dialog, export to Photos, help / what's new, accessibility, localisation, sidecar variants | ≈ 15 | 12–25 | low |
-| Look tuning, performance budgets (colour NR at half resolution on CPU + GPU, 100k-photo library), packaging, hardening | — | 25–45 | medium |
-| **Total remaining** | | **≈ 270–505** | |
+| English | en | 100% | full (source) |
+| Simplified Chinese | zh-hans | 87% | partial |
+| Spanish | es | 86% | partial |
+| Hindi | hi | 0% | none (no Devanagari shaping) |
+| Arabic | ar | 0% | none (no RTL / shaping) |
+| French | fr | 88% | partial |
+| Portuguese (Brazil) | pt-br | 85% | partial |
+| Indonesian | id | 0% | none |
+| Japanese | ja | 87% | partial |
+| German | de | 91% | partial |
+| Korean | ko | 0% | none (no Hangul face) |
+| Vietnamese | vi | 0% | none |
-Spent so far ≈ 105–145 agent-hours, so **by effort the project is ≈ 20–35% of the way to complete Lightroom + Classic
-parity**, and ≈ 45–60% of the way for cloud-Lightroom parity without AI and the Classic modules (remaining ≈ 130–255 h,
-most of it the raw codecs, lens data, video and HDR).
+Also shipped: Traditional Chinese (87%), Russian (74%), Ukrainian (99%).
-**Wall clock:** ≈ 270–505 h for one agent working alone; with 4–6 parallel agents (≈ 70% parallel efficiency, merges and
-CI under load cost the rest) **≈ 65–125 h of continuous work**. The AI package and the raw codecs carry most of the
-uncertainty: they can finish faster if suitable permissive models / documentation turn up, or stall on licensing.
+## Upcoming
-## Totals
+Ranked toward beta; detail in [docs/roadmap.md](docs/roadmap.md), gaps in [docs/gaps.md](docs/gaps.md).
-Remaining from 2026-10-02 evening (see *Parity estimate* above for the breakdown):
+1. Camera colour of our own: measured calibration for the popular models (50–90 h)
+2. CR3 for every Canon body (25–45 h)
+3. Render-fidelity suite against Lightroom, then tuning (30–60 h)
+4. Full-resolution zoom (8–15 h)
+5. AI masks: Subject / Sky / People (40–70 h + model decision)
+6. Compressed ORF and HE NEF (28–55 h)
+7. Lens profile database of our own (40–80 h)
+8. Tests in CI on macOS, Windows and Linux (6–12 h)
+9. Per-model raw verification (20–35 h)
-| Target | Remaining agent-hours | Wall clock, 4–6 parallel agents |
-|---|---:|---:|
-| Cloud-Lightroom parity without AI or Classic modules | ≈ 130–255 h | ≈ 35–65 h |
-| Full parity incl. AI, video and the Classic modules | ≈ 270–505 h | ≈ 65–125 h |
+## Progress log
-The milestone estimates in the table above were made before work started and are kept for calibration (M0–M13 took
-≈ 25 active hours with parallel agents against an estimate of ≈ 110–170 h for those milestones).
+- **2026-10-10 (UI docking):** Library, Tools, and Presets panels can be regrouped, split, floated within the app window, closed, and reopened. Tools keeps the current Edit/Crop/Masking mode; the photo view and filmstrip stay together. Panel moves and reset are available as UI commands, and saved UI preferences retain arrangements. Widget state styling uses the shared `craft-ui` theme helper, retaining the dark palette, typography, spacing and per-state borders. No readiness percentage change.
+- 2026-10-10 (RAW): RAW measured model by model against Adobe's 1,446-model list (234 verified, 943
+ unverified, 233 preview-only, 36 unsupported; ~84% of photographers' cameras decode, ~40% on a
+ verified body). Full ~61%, mainstream ~49%, essentials ~64%; beta ~14 points away.
+- 2026-10-10 (final): full ready is a weighted sum over the dimensions (~59%; method aligned with
+ the standard); mainstream practitioner ~51% (450–840 h), essentials user ~63% (160–300 h);
+ alpha gate passes.
+- 2026-10-10: full re-measure for the progress-docs standard: stage alpha, ready ~55%, checklist
+ 81.1%; new docs (target-app-parity, gaps, roadmap, architecture, raw / file-format / hardware / UI
+ / localization parity); `docs/parity.md` renamed `docs/parity-checklist.md`. Landed today: Nikon
+ lossy-after-split NEF (#633), ORF 12.8 bpp (#651), HDR gain map JPEG / PQ AVIF / float TIFF export
+ (#646), Lightroom-like Auto (#628), activity stack.
+- 2026-10-09: Sony ILCE-7CR camera colour: bundled profile from 125 photos; on 26 withheld photos mean
+ ΔE vs the camera JPEG 4.07 → 3.29. HEIC in release builds (#609), Samsung uncompressed SRW,
+ contact-sheet PDF export, Ukrainian and French UI, release v0.5.0.
+- 2026-10-08: independent Rust CR3 lossless and version 0x100/0x200 C-RAW decoding, six exact
+ full-sensor regressions on M50 / R100 / R8; guarded CR3 camera-JPEG colour fitting
+ ([cr3.md](docs/cr3.md)).
+- 2026-10-08: Fujifilm lossless / lossy compressed RAF: pure-Rust striped decoder, full sensor arrays
+ verified on 17 CC0 and 7 supplied files (13 bodies). Fujifilm camera colour: bundled X-H2S / X-T4
+ profiles from 201 / 545 photos; on 150 withheld files mean ΔE X-H2S 1.70 → 1.62, X-T4 2.43 → 2.34.
+ AI RAW denoise (#384), Lightroom catalog import, German, Spanish and Russian UI.
+- 2026-10-07: Panasonic RW2 / Leica RWL / Panasonic RAW decode in every raw format, established on
+ 178 CC0 files from 118 bodies; RW2 file-local camera look (140 of 174 files). Faces: recognition in
+ pure Rust and People view.
+- 2026-10-03 – 10-05: community PRs (#42–#51, #60) and 25+ issues: compressed NEF, Move import,
+ folder templates, Local roots and cleanup, 85k-photo grid and catalog performance, background
+ compaction, failed-save errors, GPU export hardening. Added the honest *Where we stand* assessment
+ and tracker rows for camera colour, camera coverage and render fidelity.
+- 2026-10-02: parity estimate added (`cargo xtask parity` prints weighted completion); milestones
+ refreshed.
+- 2026-10-01: RAW II formats: RAF (uncompressed Bayer + X-Trans), RW2 (packed), PEF (incl. Huffman),
+ ORF (uncompressed); embedded previews for every container incl. CR3; raw corpus test with 37 CC0
+ samples.
+- 2026-09-30 (later): app running with the full Lightroom-style UI; pipeline v0; DNG / CR2 / ARW;
+ README showcase. ≈ 8 h elapsed.
+- 2026-09-30: roadmap created; M0 in progress; research docs (Lightroom reference, Rust imaging
+ ecosystem) complete.
-## Risks that coding hours alone don't retire
+## Revision history
-- **AI features** (subject/sky/people masks, generative remove) need model weights with licences we can ship; classical
- fallbacks first. No permissively licensed sky-segmentation or raw-denoise model was found — we may need to train our own.
-- **Camera colour and lens data** is a data problem: we never use Adobe's matrices, DCPs or LCPs. DNG-embedded data first,
- then our own calibration; long-tail camera/lens coverage grows over time.
-- **Raw-format sources:** decided 2026-10-05: decoders are written from *prose* format descriptions (even ones
- published alongside GPL code); decoder source is never read. Still open: freedom-to-operate review for local
- Laplacian filters, PatchMatch and HEVC (HEIC).
-- **Look parity** with Adobe's default rendering is tuned by eye today; the planned fidelity suite (LR-BEHAV-RENDER-FIDELITY)
- turns it into measured comparisons against local-only Lightroom references. Retuning changes existing edits, so it
- ships as a new process version (`docs/process-versions.md`): photos keep their look until they are updated.
-
-## Raw format coverage and known gaps
-
-Sony ILCE-7M4 downsized lossless ARW (subsampled YCbCr 4:2:0 / 4:2:2 tiles) now decodes to linear RGB;
-private 3:2 & 4:3 examples verified through native rendering & full-resolution 16-bit export.
-
-CR3 lossless Bayer and version 0x100/0x200 C-RAW sensor decoding is verified pixel-exactly on Canon M50, R100 and R8; any other CR3, or one that fails to decode, opens from its embedded JPEG as before. See [`docs/cr3.md`](docs/cr3.md) for coding limits, reference provenance and colour limitations.
-
-Decoded (CC0 corpus from raw.pixls.us, `cargo xtask corpus --download`, `crates/raw/tests/corpus.rs`): DNG (uncompressed,
-LJ92, lossy JPEG / Smart Previews, Deflate, float, linear, DNG 1.7 JPEG XL tiles: lossless ones bit-exact on synthetic files,
-lossy ones unclipped in the declared linear primaries, checked on one real file), CR2, ARW (uncompressed, ARW2, LJ92; as-shot white balance and black level of pre-2017 bodies from the encrypted
-`SR2SubIFD`, decrypted with a keystream recovered by black-box analysis, `crates/raw/src/vendor/sr2.rs`, `crates/raw/src/vendor/arw.rs`), NEF/NRW uncompressed and Huffman-compressed (lossless, lossy type 1/2, 12/14-bit), RAF uncompressed and lossless/lossy compressed (Bayer and
-X-Trans, 14/16-bit real-file verification), RW2 / Leica RWL / Panasonic RAW in every raw format (compressed formats 4 and 6, the prefix-coded strips of format 8,
-packed formats 2, 5 and 7, the 16-bit words of the 2005–2007 bodies; all recovered by black-box analysis of 178 CC0 files from
-118 bodies, `crates/raw/src/vendor/rw2.rs`), PEF (uncompressed and Huffman), ORF uncompressed (16-bit and 12-bit packed). Supported containers yield their embedded JPEG preview when present (including CR3). The engine uses it for RAW variants it cannot decode yet; HEVC-only CR3 previews are not decoded.
-
-Not decoded yet — preview only (no permissively licensed description; black-box analysis incomplete):
-- **Olympus compressed ORF**, **Canon CR3 unverified CRX variants** (M11.1), **Canon sRAW/mRAW**.
-
-**Camera colour matrices:** ARW, NEF, RW2, RAF, CR3, CR2, PEF, Samsung SRW and Olympus ORF files can use guarded, separate chromaticity and tone estimates from their own embedded JPEG (see `docs/camera-preview-colour.md`); this is a per-file camera-look estimate with relative WB, not measured calibration or absolute-Kelvin WB. 52 models whose fit is rejected (or that have no usable JPEG) start from matrices fitted to their measured spectral sensitivities instead (rawtoaces-data, Apache-2.0; `crates/raw/src/spectral.rs`). Other non-DNG raws and rejected fits use the documented neutral fallback (camera RGB ≈ linear sRGB, flagged
-`matrix_is_fallback`) with the file's as-shot white-balance multipliers. Clean sources to evaluate next: manufacturer
-matrices stored in the files themselves (Olympus ImageProcessing `ColorMatrix`, Pentax equivalents; Panasonic RW2 files carry none) and our
-own chart-based calibration (M11.4). Adobe matrices are never used.
-
-## Lightroom Classic catalog migration
-
-File → Import Lightroom Catalog… reads `.lrcat` plus committed WAL pages directly through a pure-Rust
-SQLite reader. Originals stay in place; ratings, flags, labels, XMP metadata, hierarchical keywords,
-collections/sets, virtual copies and supported develop settings migrate into LightCraft. Existing edits
-are preserved by default, reimport identities are persistent, and source settings/history/snapshots are
-archived before catalog mutation. Missing originals remain available for relinking.
-
-Rendering is approximate: unsupported Adobe profiles/AI/process settings are reported and archived;
-history/snapshots remain source data, and smart collections import current membership. The Lightroom
-database is read-only; LightCraft owns subsequent edits. See `docs/lightroom-catalog-import.md`.
-
-## Log
-- 2026-09-30: roadmap created; M0 in progress; research docs (Lightroom reference, Rust imaging ecosystem) complete.
-- 2026-09-30 (later): app running with the full Lightroom-style UI; pipeline v0; DNG/CR2/ARW; README showcase. ≈ 8 h elapsed.
-- 2026-10-02: parity estimate added (xtask parity prints weighted completion); milestone statuses refreshed.
-- 2026-10-08: Fujifilm camera colour: guarded JPEG matching and relative WB, plus bundled X-H2S/X-T4 profiles from 201/545 photos. On 150 withheld files the pooled profiles improve mean ΔE slightly over per-file fitting (X-H2S 1.70 → 1.62, X-T4 2.43 → 2.34); dark X-H2S samples 8.74 → 2.25. Measured calibration remains open.
-- 2026-10-08: Fujifilm lossless/lossy compressed RAF: pure-Rust striped decoder; full sensor arrays verified against a black-box reference on 17 CC0 files and 7 supplied X-H2S / X-T4 files (13 camera bodies, 14/16-bit Bayer and X-Trans). Camera colour calibration remains a separate gap.
-- 2026-10-01: RAW II formats: RAF (uncompressed Bayer + X-Trans), RW2 (packed), PEF (incl. Huffman), ORF (uncompressed); embedded previews for every container incl. CR3; raw corpus test with 37 CC0 samples.
-- 2026-10-03 – 10-05: community PRs (#42–#51, #60) and 25+ issues landed: compressed NEF, Move import, folder templates,
- Local roots and cleanup, 85k-photo grid and catalog performance, background compaction, failed-save errors, GPU
- export hardening, clippy 1.99. Added the honest *Where we stand* assessment and *Where we're going* priorities; added
- tracker rows for camera colour, camera coverage and render fidelity.
-- 2026-10-07: Panasonic RW2 / Leica RWL / Panasonic RAW decode in every raw format (compressed 4/6/8, packed 2/5/7,
- 16-bit words), black levels, in-camera aspect crops and mapped-out defects established on 178 CC0 files from 118
- bodies; RW2 gets the file-local camera look (accepted on 140 of 174 files with a preview).
-- 2026-10-08: independent Rust CR3 lossless and version 0x100/0x200 C-RAW decoding, six exact full-sensor regressions on M50/R100/R8; any CR3 decode error falls back to the embedded JPEG; guarded CR3 camera-JPEG colour fitting with independent framing validation (`docs/cr3.md`). Unverified CRX variants and broader model verification remain.
-- 2026-10-09: Sony ILCE-7CR camera colour: bundled profile from 125 photos. On 26 withheld photos mean ΔE vs the camera JPEG 4.07 → 3.29; all 131 photos accepted, where 8 fell back to the neutral matrix without it. Measured calibration remains open.
-
-## Chinese and Japanese interfaces, and text watermarks
-
-English, Simplified Chinese, Traditional Chinese (Taiwan) and Japanese interface languages are persisted in UI state, and the
-language table (`crates/ui-egui/src/i18n.rs`) drives the menus, settings and fonts, so another
-language is a table entry plus two catalogs (docs/localization.md). Core menus and panels are
-translated; untranslated text retains English. Ukrainian covers all existing catalog keys, command/control/rule labels
-and format messages, with count-neutral labels and numeric dates; technical errors from lower layers remain English.
-CJK glyphs (Chinese: Noto Sans CJK SC; Japanese UI:
-BIZ UDPGothic; watermarks: BIZ UDMincho) come from storytold/craft-fonts, embedded by builds made
-with the optional `CRAFT_FONTS_DIR` input (all releases), so no system fonts are needed. Text watermarks now accept
-`vertical: true` in export JSON/presets and expose an orientation selector. Japanese
-characters stay upright in top-to-bottom columns, with newlines starting columns to the
-left. This is basic lettering, without tate-chu-yoko, ruby, kinsoku, or general vertical
-OpenType shaping. The same coverage renderer serves 8/16/32-bit exports.
+| Date | Change | Summary |
+|---|---|---|
+| 2026-10-10 | minor | RAW measured model by model; RAW decode and colour as explicit rows: full 59% → 61%, mainstream 51% → 49%, essentials 63% → 64%; beta ~14 points |
+| 2026-10-10 | minor | Full ready restored to the additive weighted sum (~59%; method aligned with the standard, no new evidence); hours per audience; beta ~16 points away |
+| 2026-10-10 | minor | Mainstream practitioner and essentials user numbers; full ready 55% → 46% (method written down); beta 340–600 h |
+| 2026-10-10 | minor | Alpha gate added (core-workflow check): passes, stays alpha |
+| 2026-10-10 | major | Restructured to the progress-docs standard; re-measured; *Where we stand*, *Parity estimate* and *Totals* moved to docs/target-app-parity.md, milestones and risks to docs/roadmap.md, raw coverage to docs/raw-parity.md, catalog migration to docs/file-format-parity.md, CJK notes to docs/localization-parity.md |
+| 2026-10-08 | minor | *Where we stand* by dimension and by kind of user |
+| 2026-10-02 | major | Parity estimate and effort table |
+| 2026-09-30 | major | Created |
diff --git a/apps/lightcraft/src/main.rs b/apps/lightcraft/src/main.rs
index 7712b1b43..486d2333c 100644
--- a/apps/lightcraft/src/main.rs
+++ b/apps/lightcraft/src/main.rs
@@ -185,6 +185,23 @@ fn load_prefs(in_memory: bool) -> (Option, Option, bool) {
load_prefs_at(&path, !in_memory)
}
+/// Compatibility recovery is limited to persisted docking, not runtime UiState commands.
+fn decode_saved_ui(bytes: &[u8]) -> Result<(UiState, bool), String> {
+ let mut saved: serde_json::Value = serde_json::from_slice(bytes).map_err(|error| error.to_string())?;
+ let fields = saved.as_object_mut().ok_or("settings must be an object")?;
+ let recovered =
+ fields.get("docking").is_some_and(|value| match serde_json::from_value::>(value.clone()) {
+ Ok(Some(workspace)) => workspace.validate().is_err(),
+ Ok(None) => false,
+ Err(_) => true,
+ });
+ if recovered {
+ fields.remove("docking");
+ }
+ let ui = serde_json::from_value(saved).map_err(|error| error.to_string())?;
+ Ok((ui, recovered))
+}
+
fn load_prefs_at(path: &std::path::Path, set_aside_damaged: bool) -> (Option, Option, bool) {
let bytes = match std::fs::read(path) {
Ok(b) => b,
@@ -194,8 +211,15 @@ fn load_prefs_at(path: &std::path::Path, set_aside_damaged: bool) -> (Option(&bytes) {
- Ok(ui) => (Some(ui.sanitized()), None, false),
+ match decode_saved_ui(&bytes) {
+ Ok((ui, false)) => (Some(ui.sanitized()), None, false),
+ Ok((ui, true)) => {
+ let msg = format!(
+ "The saved panel layout in {} couldn't be restored. Other settings were loaded; the original file is preserved and settings won't be saved this session.",
+ path.display()
+ );
+ (Some(ui.sanitized()), Some(msg), true)
+ }
Err(e) if !set_aside_damaged => {
let msg = format!("The app settings ({}) are damaged ({e}). Defaults are used; nothing is saved in this session.", path.display());
(None, Some(msg), true)
@@ -272,6 +296,9 @@ impl PrefsWriter {
return Ok(());
}
let Some(path) = self.path.clone() else { return Ok(()) };
+ if let Some(workspace) = &ui.docking {
+ workspace.validate()?;
+ }
let bytes = serde_json::to_vec_pretty(ui).map_err(|e| e.to_string())?;
if bytes == self.written {
return Ok(());
@@ -615,6 +642,7 @@ OPTIONS:
--version, --help
ENVIRONMENT:
+ LIGHTCRAFT_CONFIG_DIR=DIR use this settings folder instead of the platform default (empty = no settings folder)
LIGHTCRAFT_GPU_BACKEND=dx12|vulkan|metal|auto|off graphics backend (default: DX12 on Windows, Metal on macOS,
Vulkan on Linux; off = render on the CPU); else WGPU_BACKEND. LIGHTCRAFT_GPU=0: CPU rendering.
LIGHTCRAFT_SAM3_DIR=DIR the SAM 3 model for Object / Describe masks (default: /models/sam3;
@@ -670,10 +698,16 @@ fn control_port_from(what: &str, value: Option) -> Option {
port
}
+/// An isolated profile keeps the emergency crash log with its other app data. With no
+/// override (or an empty override), retain the historical system-temp location.
+fn panic_log_path_with(env: impl Fn(&str) -> Option, temp: std::path::PathBuf) -> std::path::PathBuf {
+ env("LIGHTCRAFT_CONFIG_DIR").filter(|value| !value.is_empty()).map(std::path::PathBuf::from).unwrap_or(temp).join("lightcraft-panics.log")
+}
+
fn main() -> eframe::Result {
// First, so every start-up record is kept for the log file (`logging`).
let logger = logging::install();
- lightcraft_engine::guard::install_hook(std::env::temp_dir().join("lightcraft-panics.log"));
+ lightcraft_engine::guard::install_hook(panic_log_path_with(|name| std::env::var_os(name), std::env::temp_dir()));
if let Some(logger) = logger {
logging::record_panics(logger);
}
@@ -911,6 +945,59 @@ mod tests {
let _ = std::fs::remove_dir_all(&d);
}
+ #[test]
+ fn docking_survives_native_settings_file_reload() {
+ use lightcraft_ui_egui::docking::Panel;
+ let d = dir("docking-reload");
+ let path = d.join("ui.json");
+ let mut app = LightcraftApp::new(lightcraft_engine::Session::with_demo(), Services::default());
+ app.run("ui.dock", serde_json::json!({"operation":"float", "panel":"tools", "rect":[70,80,310,500]})).unwrap();
+ app.run("ui.dock", serde_json::json!({"operation":"close", "panel":"tools"})).unwrap();
+ let mut writer = PrefsWriter { path: Some(path.clone()), ..Default::default() };
+ writer.save_ui(&app.ui, false).unwrap();
+ let (restored, warning, keep) = load_prefs_at(&path, false);
+ assert!(warning.is_none());
+ assert!(!keep);
+ let mut restored_app = LightcraftApp::new(lightcraft_engine::Session::with_demo(), Services::default());
+ restored_app.ui = restored.unwrap();
+ restored_app.run("ui.dock", serde_json::json!({"operation":"open", "panel":"tools"})).unwrap();
+ assert_eq!(
+ restored_app.ui.docking.as_ref().unwrap().layout.floating.iter().find(|g| g.panels.contains(&Panel::Tools)).unwrap().rect,
+ [70.0, 80.0, 310.0, 500.0]
+ );
+ std::fs::remove_dir_all(d).unwrap();
+ }
+
+ #[test]
+ fn future_docking_preserves_library_settings_and_the_original_preferences_file() {
+ let d = dir("future-docking");
+ let path = d.join("ui.json");
+ let mut ui = UiState { thumb_size: 210.0, filmstrip: false, ..Default::default() };
+ ui.settings.library_path = "/Volumes/Photos/Synthetic Library".into();
+ for docking in [serde_json::json!({"layout":{"root":{"FutureLayout":{}}}}), serde_json::json!(false)] {
+ let mut saved = serde_json::to_value(&ui).unwrap();
+ saved["docking"] = docking;
+ assert!(serde_json::from_value::(saved.clone()).is_err(), "runtime state parsing stays strict");
+ let bytes = serde_json::to_vec(&saved).unwrap();
+ for set_aside in [false, true] {
+ std::fs::write(&path, &bytes).unwrap();
+ let (loaded, warning, keep) = load_prefs_at(&path, set_aside);
+ let loaded = loaded.unwrap();
+ assert_eq!(loaded.settings.library_path, ui.settings.library_path);
+ assert_eq!(loaded.thumb_size, ui.thumb_size);
+ assert!(!loaded.filmstrip);
+ assert!(loaded.docking.is_none());
+ assert!(warning.unwrap().contains("Other settings were loaded"));
+ assert!(keep);
+ let mut writer = PrefsWriter { path: Some(path.clone()), keep_file: keep, ..Default::default() };
+ writer.save_ui(&loaded, false).unwrap();
+ assert_eq!(std::fs::read(&path).unwrap(), bytes);
+ assert_eq!(std::fs::read_dir(&d).unwrap().count(), 1, "a future layout does not rename or replace the settings file");
+ }
+ }
+ std::fs::remove_dir_all(d).unwrap();
+ }
+
/// Issue #164: the writer saves changed settings to its file — and nothing at all without one
/// (`--memory`) or while the session is the temporary one offered when the library can't open.
#[test]
@@ -1063,3 +1150,16 @@ mod tests {
let _ = std::fs::remove_dir_all(&d);
}
}
+
+#[cfg(test)]
+mod config_path_tests {
+ use super::*;
+
+ #[test]
+ fn config_override_keeps_panic_log_in_isolated_profile() {
+ let temp = std::path::PathBuf::from("temp");
+ assert_eq!(panic_log_path_with(|_| Some("isolated".into()), temp.clone()), std::path::PathBuf::from("isolated/lightcraft-panics.log"));
+ assert_eq!(panic_log_path_with(|_| None, temp.clone()), temp.join("lightcraft-panics.log"));
+ assert_eq!(panic_log_path_with(|_| Some("".into()), temp.clone()), temp.join("lightcraft-panics.log"));
+ }
+}
diff --git a/crates/catalog/src/rules.rs b/crates/catalog/src/rules.rs
index be1fd867a..f17f12e01 100644
--- a/crates/catalog/src/rules.rs
+++ b/crates/catalog/src/rules.rs
@@ -363,7 +363,7 @@ pub fn rule_date(s: &str) -> Result {
_ => false,
}
};
- if !shaped(&s) && !(s.get(..19).is_some_and(shaped) && s.get(19..).is_some_and(tail_ok)) {
+ if !(shaped(&s) || s.get(..19).is_some_and(shaped) && s.get(19..).is_some_and(tail_ok)) {
return Err(Issue::NeedsDate);
}
let part = |a: usize, b: usize| s.get(a..b).and_then(|t| t.parse::().ok());
diff --git a/crates/codecs/src/gainmap.rs b/crates/codecs/src/gainmap.rs
index 13c3c0e55..c3ab2c6dc 100644
--- a/crates/codecs/src/gainmap.rs
+++ b/crates/codecs/src/gainmap.rs
@@ -411,7 +411,7 @@ pub fn compute(
if n == 0 || sdr.len() < n || hdr.len() < n {
return Err(Error::Encode("gain map: SDR and HDR renditions must match the image size".into()));
}
- if !matches!(o.scale, 1 | 2 | 4) || !(o.gamma > 0.0 && o.gamma.is_finite()) || !(o.offset > 0.0 && o.offset.is_finite()) {
+ if !(matches!(o.scale, 1 | 2 | 4) && o.gamma > 0.0 && o.gamma.is_finite() && o.offset > 0.0 && o.offset.is_finite()) {
return Err(Error::Encode("gain map: scale must be 1, 2 or 4, gamma and offset positive".into()));
}
let s = o.scale;
diff --git a/crates/engine/src/config.rs b/crates/engine/src/config.rs
index 7ab9b6531..2a4ccb6b4 100644
--- a/crates/engine/src/config.rs
+++ b/crates/engine/src/config.rs
@@ -6,21 +6,42 @@
//! face- or denoise-models folder until a host asks for one ([`Session::with_default_face_models`],
//! [`Session::with_default_denoise_models`]), so tests stay hermetic.
+use std::ffi::OsString;
use std::path::PathBuf;
use crate::Session;
-/// LightCraft's per-user config folder (`…/LightCraft`), if the platform tells us where.
+#[derive(Clone, Copy)]
+enum Platform {
+ Mac,
+ Windows,
+ Other,
+}
+
+/// LightCraft's settings folder. A nonempty `$LIGHTCRAFT_CONFIG_DIR` overrides the platform default.
+/// The override is the complete directory; no application-name suffix is appended. An explicitly
+/// empty value disables the settings directory instead of falling back to a personal profile.
pub fn config_dir() -> Option {
- if cfg!(target_os = "macos") {
- std::env::var_os("HOME").map(|h| PathBuf::from(h).join("Library/Application Support/LightCraft"))
+ let platform = if cfg!(target_os = "macos") {
+ Platform::Mac
} else if cfg!(windows) {
- std::env::var_os("APPDATA").map(|a| PathBuf::from(a).join("LightCraft"))
+ Platform::Windows
} else {
- std::env::var_os("XDG_CONFIG_HOME")
- .map(PathBuf::from)
- .or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".config")))
- .map(|c| c.join("lightcraft"))
+ Platform::Other
+ };
+ config_dir_with(platform, |name| std::env::var_os(name))
+}
+
+fn config_dir_with(platform: Platform, env: impl Fn(&str) -> Option) -> Option {
+ if let Some(dir) = env("LIGHTCRAFT_CONFIG_DIR") {
+ return (!dir.is_empty()).then(|| PathBuf::from(dir));
+ }
+ match platform {
+ Platform::Mac => env("HOME").map(|h| PathBuf::from(h).join("Library/Application Support/LightCraft")),
+ Platform::Windows => env("APPDATA").map(|a| PathBuf::from(a).join("LightCraft")),
+ Platform::Other => {
+ env("XDG_CONFIG_HOME").map(PathBuf::from).or_else(|| env("HOME").map(|h| PathBuf::from(h).join(".config"))).map(|c| c.join("lightcraft"))
+ }
}
}
@@ -56,3 +77,56 @@ impl Session {
self.denoise.touch();
}
}
+
+#[cfg(test)]
+mod config_tests {
+ use super::*;
+
+ fn resolve(platform: Platform, vars: &[(&str, &str)]) -> Option {
+ config_dir_with(platform, |name| vars.iter().find_map(|(key, value)| (*key == name).then(|| OsString::from(value))))
+ }
+
+ #[test]
+ fn config_override_wins_without_reading_user_profile() {
+ for platform in [Platform::Mac, Platform::Windows, Platform::Other] {
+ let actual = config_dir_with(platform, |name| {
+ assert_eq!(name, "LIGHTCRAFT_CONFIG_DIR");
+ Some(OsString::from("isolated/settings"))
+ });
+ assert_eq!(actual, Some(PathBuf::from("isolated/settings")));
+ }
+ }
+
+ #[test]
+ fn config_empty_override_disables_profile_without_reading_defaults() {
+ for platform in [Platform::Mac, Platform::Windows, Platform::Other] {
+ assert_eq!(
+ config_dir_with(platform, |name| {
+ assert_eq!(name, "LIGHTCRAFT_CONFIG_DIR");
+ Some(OsString::new())
+ }),
+ None
+ );
+ }
+ }
+
+ #[test]
+ fn config_unset_override_keeps_platform_defaults() {
+ let vars = [("HOME", "/user"), ("APPDATA", "/roaming"), ("XDG_CONFIG_HOME", "/xdg")];
+ assert_eq!(resolve(Platform::Mac, &vars), Some(PathBuf::from("/user/Library/Application Support/LightCraft")));
+ assert_eq!(resolve(Platform::Windows, &vars), Some(PathBuf::from("/roaming/LightCraft")));
+ assert_eq!(resolve(Platform::Other, &vars), Some(PathBuf::from("/xdg/lightcraft")));
+ assert_eq!(resolve(Platform::Other, &[("HOME", "/user")]), Some(PathBuf::from("/user/.config/lightcraft")));
+ for platform in [Platform::Mac, Platform::Windows, Platform::Other] {
+ assert_eq!(resolve(platform, &[]), None);
+ }
+ }
+
+ #[cfg(unix)]
+ #[test]
+ fn config_override_preserves_non_unicode_paths() {
+ use std::os::unix::ffi::OsStringExt as _;
+ let value = OsString::from_vec(b"isolated/\xff".to_vec());
+ assert_eq!(config_dir_with(Platform::Mac, |_| Some(value.clone())), Some(PathBuf::from(value)));
+ }
+}
diff --git a/crates/meta/src/xmp.rs b/crates/meta/src/xmp.rs
index b8a541096..2a4d753dc 100644
--- a/crates/meta/src/xmp.rs
+++ b/crates/meta/src/xmp.rs
@@ -418,7 +418,7 @@ fn parse_one_region(item: &XmpValue, px_dims: Option<(f64, f64)>) -> Option(v) {
Ok(mut u) => {
+ if let Err(error) = crate::docking::set_widths(&mut u, p) {
+ return err(error);
+ }
u.toast = app.ui.toast.clone();
u.dialog = app.ui.dialog.clone();
u.status = app.ui.status.clone();
+ crate::docking::cancel_replaced_gesture(app, u.docking.as_ref());
app.ui = u;
crate::i18n::set_language(app.ui.language);
ctx.request_repaint();
diff --git a/crates/ui-egui/src/docking.rs b/crates/ui-egui/src/docking.rs
new file mode 100644
index 000000000..639589429
--- /dev/null
+++ b/crates/ui-egui/src/docking.rs
@@ -0,0 +1,653 @@
+//! Application panel identities and persistence around the shared docking renderer.
+//! Document contents remain owned by their existing view implementations.
+
+use craft_ui::docking::{Action, DockArea, DockStyle, Layout, Location, Node, PanelLimits, Permissions, Placement, Zone};
+use craft_ui::layout::{SplitAxis, SplitSize};
+use serde::{Deserialize, Serialize};
+use serde_json::{Value, json};
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub enum Panel {
+ Canvas,
+ Library,
+ Tools,
+ Presets,
+}
+
+impl Panel {
+ pub const ALL: &[Self] = &[Self::Canvas, Self::Library, Self::Tools, Self::Presets];
+ pub fn id(self) -> &'static str {
+ match self {
+ Self::Canvas => "canvas",
+ Self::Library => "library",
+ Self::Tools => "tools",
+ Self::Presets => "presets",
+ }
+ }
+ fn label(self) -> &'static str {
+ match self {
+ Self::Canvas => "Photos",
+ Self::Library => "Library",
+ Self::Tools => "Tools",
+ Self::Presets => "Presets",
+ }
+ }
+ fn default_side(self) -> Zone {
+ match self {
+ Self::Canvas => Zone::Right,
+ Self::Library => Zone::Left,
+ Self::Tools => Zone::Right,
+ Self::Presets => Zone::Right,
+ }
+ }
+ fn parse(id: &str) -> Result {
+ Self::ALL.iter().copied().find(|panel| panel.id() == id).ok_or_else(|| format!("Unknown panel: {id}"))
+ }
+}
+
+fn permissions(panel: &Panel) -> Permissions {
+ if *panel == Panel::Canvas { Permissions::PROTECTED } else { Permissions::default() }
+}
+
+fn tabs(panel: Panel) -> Node {
+ Node::Tabs { panels: vec![panel], active: 0 }
+}
+
+fn split(axis: SplitAxis, size: SplitSize, first: Node, second: Node) -> Node {
+ Node::Split { axis, size, first: Box::new(first), second: Box::new(second) }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+#[serde(default)]
+pub struct Workspace {
+ pub layout: Layout,
+ /// Closed panels retain their most recent group, side, size or floating rectangle.
+ pub hidden: Vec<(Panel, Location)>,
+}
+
+impl Default for Workspace {
+ fn default() -> Self {
+ Self {
+ layout: Layout {
+ root: Some(split(
+ SplitAxis::Horizontal,
+ SplitSize::FixedFirst(crate::state::LEFT_WIDTH.default),
+ tabs(Panel::Library),
+ split(
+ SplitAxis::Horizontal,
+ SplitSize::FixedSecond(crate::state::RIGHT_WIDTH.default),
+ split(SplitAxis::Horizontal, SplitSize::FixedSecond(300.0), tabs(Panel::Canvas), tabs(Panel::Presets)),
+ tabs(Panel::Tools),
+ ),
+ )),
+ floating: Vec::new(),
+ },
+ hidden: Vec::new(),
+ }
+ }
+}
+
+impl Workspace {
+ pub fn validate(&self) -> Result<(), String> {
+ self.layout.validate().map_err(|error| error.to_string())?;
+ if !self.layout.contains(&Panel::Canvas) || self.hidden.len() > Panel::ALL.len() {
+ return Err("The panel layout must contain its document view and bounded hidden panels".into());
+ }
+ // Saved settings cannot turn the protected document view into a floating or closable tab.
+ if self.layout.floating.iter().any(|group| group.panels.contains(&Panel::Canvas)) {
+ return Err("The document view cannot float".into());
+ }
+ let mut nodes: Vec<_> = self.layout.root.iter().collect();
+ while let Some(node) = nodes.pop() {
+ match node {
+ Node::Split { first, second, .. } => {
+ nodes.push(first);
+ nodes.push(second);
+ }
+ Node::Tabs { panels, .. } if panels.contains(&Panel::Canvas) && panels.len() != 1 => {
+ return Err("The document view cannot share a tab group".into());
+ }
+ Node::Stack { entries } if entries.iter().any(|entry| entry.panel == Panel::Canvas) => {
+ return Err("The document view cannot be collapsed".into());
+ }
+ _ => {}
+ }
+ }
+ let mut seen = std::collections::HashSet::new();
+ for (panel, location) in &self.hidden {
+ if *panel == Panel::Canvas || self.layout.contains(panel) || !seen.insert(*panel) {
+ return Err("A hidden panel has an invalid or duplicate identity".into());
+ }
+ if location.anchor == Some(Panel::Canvas) && matches!(location.placement, Placement::Tab { .. } | Placement::Split(Zone::Center)) {
+ return Err("A hidden panel cannot restore over the document view".into());
+ }
+ let mut probe = self.layout.clone();
+ if let Err(error) = probe.restore(*panel, location)
+ && error != craft_ui::docking::DockError::MissingTarget
+ {
+ return Err(error.to_string());
+ }
+ }
+ Ok(())
+ }
+
+ pub fn apply(&mut self, action: Action) -> Result<(), String> {
+ self.validate()?;
+ let mut next = self.clone();
+ if let Action::Close { panel } = &action {
+ let location = next.layout.location(panel).map_err(|error| error.to_string())?;
+ next.hidden.retain(|(id, _)| id != panel);
+ next.hidden.push((*panel, location));
+ } else if let Action::Open { panel, .. } | Action::OpenAt { panel, .. } = &action {
+ next.hidden.retain(|(id, _)| id != panel);
+ }
+ next.layout.apply_with_permissions(action, permissions).map_err(|error| error.to_string())?;
+ next.validate()?;
+ *self = next;
+ Ok(())
+ }
+
+ pub fn set_visible(&mut self, panel: Panel, visible: bool) -> Result<(), String> {
+ if self.layout.contains(&panel) == visible {
+ return Ok(());
+ }
+ if !visible {
+ return self.apply(Action::Close { panel });
+ }
+ self.validate()?;
+ let mut next = self.clone();
+ let mut location = next.hidden.iter().find(|(id, _)| *id == panel).map(|(_, location)| location.clone());
+ next.hidden.retain(|(id, _)| *id != panel);
+ if let Some(saved) = &mut location
+ && saved.floating.is_some()
+ && saved.anchor.as_ref().is_some_and(|anchor| !next.layout.floating.iter().any(|group| group.panels.contains(anchor)))
+ {
+ // The other members of a floating group may have been closed too.
+ saved.anchor = None;
+ saved.placement = Placement::Tab { before: None };
+ }
+ let restored = location.as_ref().is_some_and(|location| next.layout.restore(panel, location).is_ok());
+ if !restored {
+ next.layout
+ .apply_with_permissions(
+ Action::OpenAt { panel, anchor: Panel::Canvas, placement: Placement::Split(panel.default_side()) },
+ permissions,
+ )
+ .map_err(|error| error.to_string())?;
+ }
+ next.validate()?;
+ *self = next;
+ Ok(())
+ }
+
+ /// Membership is passive state; an explicit command also exposes the panel's body.
+ pub fn reveal(&mut self, panel: Panel) -> Result<(), String> {
+ self.set_visible(panel, true)?;
+ self.apply(Action::Activate { panel })
+ }
+
+ pub fn exposed(&self, panel: Panel) -> bool {
+ if self.layout.floating.iter().any(|group| group.panels.get(group.active) == Some(&panel)) {
+ return true;
+ }
+ let mut nodes = self.layout.root.as_ref().map(|node| vec![node]).unwrap_or_default();
+ while let Some(node) = nodes.pop() {
+ match node {
+ Node::Tabs { panels, active } if panels.get(*active) == Some(&panel) => return true,
+ Node::Stack { entries } if entries.iter().any(|entry| entry.panel == panel && entry.open) => return true,
+ Node::Split { first, second, .. } => {
+ nodes.push(first);
+ nodes.push(second);
+ }
+ _ => {}
+ }
+ }
+ false
+ }
+
+ fn command(&mut self, params: &Value) -> Result<(), String> {
+ self.validate()?;
+ if let Some(action) = params.get("action") {
+ let action: Action = serde_json::from_value(action.clone()).map_err(|error| error.to_string())?;
+ return self.apply(action);
+ }
+ let operation = params.get("operation").and_then(Value::as_str).ok_or("operation is required")?;
+ if operation == "resizeSplit" {
+ let path: Vec =
+ serde_json::from_value(params.get("path").cloned().ok_or("path is required")?).map_err(|error| error.to_string())?;
+ let size: SplitSize =
+ serde_json::from_value(params.get("size").cloned().ok_or("size is required")?).map_err(|error| error.to_string())?;
+ return self.apply(Action::ResizeSplit { path, size });
+ }
+ let panel = Panel::parse(params.get("panel").and_then(Value::as_str).ok_or("panel is required")?)?;
+ let action = match operation {
+ "open" => return self.reveal(panel),
+ "close" => Action::Close { panel },
+ "activate" => Action::Activate { panel },
+ "setStackOpen" => Action::SetStackOpen { panel, open: params.get("open").and_then(Value::as_bool).ok_or("open must be a boolean")? },
+ "resizeStack" => {
+ let value = params.get("height").ok_or("height is required")?;
+ let height = if value.is_null() {
+ None
+ } else {
+ Some(
+ value
+ .as_f64()
+ .filter(|height| height.is_finite() && (0.0..=1_000_000.0).contains(height))
+ .ok_or("height must be finite and bounded")? as f32,
+ )
+ };
+ Action::ResizeStack { panel, height }
+ }
+ "float" | "moveFloating" => {
+ let rect = params.get("rect").and_then(Value::as_array).filter(|rect| rect.len() == 4).ok_or("rect must be [x,y,width,height]")?;
+ let mut values = [0.0f32; 4];
+ for (output, value) in values.iter_mut().zip(rect) {
+ *output = value
+ .as_f64()
+ .filter(|number| number.is_finite() && number.abs() <= 1_000_000.0)
+ .ok_or("rect values must be finite and bounded")? as f32;
+ }
+ if operation == "float" { Action::Float { panel, rect: values } } else { Action::MoveFloating { panel, rect: values } }
+ }
+ "move" => {
+ let anchor = Panel::parse(params.get("target").and_then(Value::as_str).ok_or("target is required")?)?;
+ let placement = match params.get("zone").and_then(Value::as_str).unwrap_or("center") {
+ "center" => Placement::Split(Zone::Center),
+ "left" => Placement::Split(Zone::Left),
+ "right" => Placement::Split(Zone::Right),
+ "top" => Placement::Split(Zone::Top),
+ "bottom" => Placement::Split(Zone::Bottom),
+ _ => return Err("zone must be center, left, right, top or bottom".into()),
+ };
+ let placement = if let Some(before) = params.get("before") {
+ if !matches!(placement, Placement::Split(Zone::Center)) {
+ return Err("before only applies to a center move".into());
+ }
+ Placement::Tab {
+ before: if before.is_null() {
+ None
+ } else {
+ Some(Panel::parse(before.as_str().ok_or("before must be a panel ID or null")?)?)
+ },
+ }
+ } else {
+ placement
+ };
+ Action::Move { panel, anchor, placement }
+ }
+ _ => return Err("operation must be open, close, activate, float, move, moveFloating, resizeSplit, setStackOpen or resizeStack".into()),
+ };
+ self.apply(action)
+ }
+}
+
+fn from_ui(app: &crate::LightcraftApp) -> Workspace {
+ let mut workspace = Workspace::default();
+ if let Some(Node::Split { size, second, .. }) = workspace.layout.root.as_mut() {
+ *size = SplitSize::FixedFirst(crate::state::LEFT_WIDTH.clamp(app.ui.left_width));
+ if let Node::Split { size, .. } = second.as_mut() {
+ *size = SplitSize::FixedSecond(crate::state::RIGHT_WIDTH.clamp(app.ui.right_width));
+ }
+ }
+ workspace
+}
+
+fn sync(workspace: &mut Workspace, app: &crate::LightcraftApp) -> Result<(), String> {
+ workspace.set_visible(Panel::Library, app.ui.left_panel)?;
+ workspace.set_visible(Panel::Tools, app.ui.right != crate::state::RightPanel::None)?;
+ workspace.set_visible(Panel::Presets, app.ui.presets)
+}
+
+fn publish_visibility(app: &mut crate::LightcraftApp, workspace: &Workspace) {
+ app.ui.left_panel = workspace.layout.contains(&Panel::Library);
+ app.ui.presets = workspace.layout.contains(&Panel::Presets);
+ if !workspace.layout.contains(&Panel::Tools) {
+ app.ui.right = crate::state::RightPanel::None;
+ } else if app.ui.right == crate::state::RightPanel::None {
+ app.ui.right = crate::state::RightPanel::Edit;
+ }
+ for (panel, width, limits) in
+ [(Panel::Library, &mut app.ui.left_width, crate::state::LEFT_WIDTH), (Panel::Tools, &mut app.ui.right_width, crate::state::RIGHT_WIDTH)]
+ {
+ let Ok(location) = workspace.layout.location(&panel) else {
+ continue;
+ };
+ let value = match (location.floating, location.placement, location.size) {
+ (Some([_, _, width, _]), _, _)
+ | (None, Placement::Split(Zone::Left), Some(SplitSize::FixedFirst(width)))
+ | (None, Placement::Split(Zone::Right), Some(SplitSize::FixedSecond(width))) => Some(width),
+ // A vertical split stores height, and the opposite fixed side sizes a sibling.
+ _ => None,
+ };
+ if let Some(value) = value {
+ *width = limits.clamp(value);
+ }
+ }
+}
+
+pub(crate) fn exposed(app: &crate::LightcraftApp, panel: Panel) -> bool {
+ app.ui.docking.as_ref().is_none_or(|workspace| workspace.exposed(panel))
+}
+
+pub(crate) fn reveal_command(app: &mut crate::LightcraftApp, id: &str) -> Result<(), String> {
+ let panel = if id == "panel.presets" && app.ui.presets {
+ Some(Panel::Presets)
+ } else if (id.starts_with("panel.") && id != "panel.close" && id != "panel.presets"
+ || id.starts_with("section.")
+ || id.starts_with("tool.") && id != "tool.none")
+ && app.ui.right != crate::state::RightPanel::None
+ {
+ Some(Panel::Tools)
+ } else {
+ None
+ };
+ if let Some(panel) = panel {
+ let mut workspace = app.ui.docking.clone().unwrap_or_else(|| from_ui(app));
+ sync(&mut workspace, app)?;
+ workspace.reveal(panel)?;
+ cancel_replaced_gesture(app, Some(&workspace));
+ app.ui.docking = Some(workspace);
+ }
+ Ok(())
+}
+
+/// Check the target's actual egui layer even while a different widget owns the drag.
+pub(crate) fn drop_target(ui: &egui::Ui, rect: egui::Rect, pointer: Option) -> bool {
+ pointer.is_some_and(|pos| rect.intersect(ui.clip_rect()).contains(pos) && ui.ctx().layer_id_at(pos) == Some(ui.layer_id()))
+}
+
+/// Explicit host commands replace the snapshot before rendering. Cancel only their old
+/// gestures; actions returned by the renderer must keep continuous resizing alive.
+pub(crate) fn cancel_replaced_gesture(app: &crate::LightcraftApp, workspace: Option<&Workspace>) {
+ let replaced = match (app.ui.docking.as_ref(), workspace) {
+ (Some(old), Some(next)) => old.layout != next.layout || old.hidden != next.hidden,
+ (None, None) => false,
+ _ => true,
+ };
+ if replaced && let Some(ctx) = &app.tasks.repaint {
+ craft_ui::docking::cancel_drag::(ctx, egui::Id::new("lightcraft-docking"));
+ }
+}
+
+pub fn command(app: &mut crate::LightcraftApp, reset: bool, params: &Value) -> Result {
+ command_impl(app, reset, params, false)
+}
+
+fn command_impl(app: &mut crate::LightcraftApp, reset: bool, params: &Value, renderer_action: bool) -> Result {
+ let mut workspace = app.ui.docking.clone().unwrap_or_else(|| from_ui(app));
+ if reset {
+ workspace = Workspace::default();
+ // The usual LightCraft starting workspace shows its current tool beside the photo.
+ workspace.set_visible(Panel::Library, false)?;
+ workspace.set_visible(Panel::Presets, false)?;
+ } else {
+ sync(&mut workspace, app)?;
+ workspace.command(params)?;
+ }
+ if !renderer_action {
+ cancel_replaced_gesture(app, Some(&workspace));
+ }
+ publish_visibility(app, &workspace);
+ app.ui.docking = Some(workspace);
+ Ok(json!({"docking": app.ui.docking}))
+}
+
+pub fn show(app: &mut crate::LightcraftApp, ui: &mut egui::Ui) {
+ let t = crate::theme::Tokens::get(ui.ctx());
+ let mut workspace = app.ui.docking.clone().unwrap_or_else(|| from_ui(app));
+ if let Err(error) = workspace.validate().and_then(|()| sync(&mut workspace, app)) {
+ app.ui.toast = Some((format!("Panel layout could not be restored: {error}"), ui.input(|input| input.time) + 8.0, None));
+ workspace = from_ui(app);
+ let _ = sync(&mut workspace, app);
+ }
+ // Legacy visibility commands may replace the layout before this render starts.
+ cancel_replaced_gesture(app, Some(&workspace));
+ // Tool/Canvas callbacks dispatch app commands and must see the real workspace.
+ app.ui.docking = Some(workspace.clone());
+ let mut style = DockStyle::from_ui(ui);
+ style.min_pane = 96.0;
+ style.float_label = crate::i18n::tr("Float panel").into();
+ style.close_label = crate::i18n::tr("Close panel").into();
+ style.move_label = crate::i18n::tr("Move to group").into();
+ style.panels_label = crate::i18n::tr("Panels").into();
+ style.resize_label = crate::i18n::tr("Resize panels").into();
+ style.resize_window_label = crate::i18n::tr("Resize panel window").into();
+ style.background = t.chrome;
+ style.tab_background = t.chrome;
+ style.active_background = t.canvas;
+ style.text = t.text;
+ style.inactive_text = t.text_dim;
+ style.border = egui::Stroke::new(1.0, t.divider);
+ style.accent = t.accent;
+ style.font = t.font(12.0);
+ // Tool and preset panels publish hover previews before the photo consumes them.
+ let mut canvas = None;
+ let output = DockArea::new(egui::Id::new("lightcraft-docking")).show_with_limits(
+ ui,
+ &workspace.layout,
+ &style,
+ |panel| crate::i18n::tr(panel.label()).into(),
+ permissions,
+ panel_limits,
+ |ui, panel| match panel {
+ Panel::Canvas => {
+ canvas = Some(ui.new_child(egui::UiBuilder::new().id_salt("photo-view").max_rect(ui.max_rect())));
+ }
+ Panel::Library => {
+ crate::widgets::register(ui.ctx(), "panel:left_panel", ui.max_rect());
+ crate::panels::left::show(app, ui);
+ }
+ Panel::Tools => {
+ crate::widgets::register(ui.ctx(), "panel:right_panel", ui.max_rect());
+ crate::panels::right::show(app, ui);
+ }
+ Panel::Presets => crate::panels::presets::show(app, ui),
+ },
+ );
+ if let Some(mut canvas) = canvas {
+ let ui = &mut canvas;
+ crate::panels::bottombar::show(app, ui);
+ let bg = if matches!(
+ app.ui.view,
+ crate::state::ViewMode::Detail | crate::state::ViewMode::Compare | crate::state::ViewMode::Survey | crate::state::ViewMode::Reference
+ ) {
+ t.canvas
+ } else {
+ t.grid_bg
+ };
+ egui::CentralPanel::default().frame(egui::Frame::NONE.fill(bg)).show(ui, |ui| match app.ui.view {
+ crate::state::ViewMode::PhotoGrid | crate::state::ViewMode::SquareGrid => crate::panels::grid::show(app, ui),
+ crate::state::ViewMode::Detail => crate::panels::detail::show(app, ui),
+ crate::state::ViewMode::Compare => crate::panels::compare::show_compare(app, ui),
+ crate::state::ViewMode::Survey => crate::panels::compare::show_survey(app, ui),
+ crate::state::ViewMode::Reference => crate::panels::compare::show_reference(app, ui),
+ crate::state::ViewMode::People => crate::panels::people::show(app, ui),
+ });
+ }
+
+ let rendered = workspace;
+ let mut workspace = app.ui.docking.clone().unwrap_or_else(|| from_ui(app));
+ let _ = sync(&mut workspace, app);
+ let stale = workspace.layout != rendered.layout || workspace.hidden != rendered.hidden;
+ if stale {
+ craft_ui::docking::cancel_drag::(ui.ctx(), egui::Id::new("lightcraft-docking"));
+ }
+ publish_visibility(app, &workspace);
+ app.ui.docking = Some(workspace);
+ if stale {
+ return;
+ }
+ for action in output.actions {
+ if let Err(error) = serde_json::to_value(action)
+ .map_err(|error| error.to_string())
+ .and_then(|action| command_impl(app, false, &json!({"action": action}), true))
+ {
+ app.ui.toast = Some((format!("Panel move was not applied: {error}"), ui.input(|input| input.time) + 8.0, None));
+ }
+ }
+}
+
+/// Keep the established ui.set width controls connected to the panel's current parent split.
+pub(crate) fn set_widths(state: &mut crate::UiState, params: &Value) -> Result<(), String> {
+ state.left_width = crate::state::LEFT_WIDTH.clamp(state.left_width);
+ state.right_width = crate::state::RIGHT_WIDTH.clamp(state.right_width);
+ let Some(workspace) = state.docking.as_mut() else {
+ return Ok(());
+ };
+ workspace.validate()?;
+ for (key, panel, width) in [("leftWidth", Panel::Library, state.left_width), ("rightWidth", Panel::Tools, state.right_width)] {
+ if params.get(key).is_none() {
+ continue;
+ }
+ if let Some((_, location)) = workspace.hidden.iter_mut().find(|(id, _)| *id == panel) {
+ if let Some(rect) = &mut location.floating {
+ rect[2] = width;
+ }
+ match location.placement {
+ Placement::Split(Zone::Left) => location.size = Some(SplitSize::FixedFirst(width)),
+ Placement::Split(Zone::Right) => location.size = Some(SplitSize::FixedSecond(width)),
+ _ => {} // Width controls do not rewrite a saved vertical split's height.
+ }
+ continue;
+ }
+ if let Some(group) = workspace.layout.floating.iter().find(|group| group.panels.contains(&panel)) {
+ let mut rect = group.rect;
+ rect[2] = width;
+ workspace.apply(Action::MoveFloating { panel, rect })?;
+ continue;
+ }
+ let mut nodes = workspace.layout.root.as_ref().map(|node| vec![(node, Vec::new())]).unwrap_or_default();
+ let mut change = None;
+ while let Some((node, path)) = nodes.pop() {
+ if let Node::Split { axis, first, second, .. } = node {
+ let contains = |node: &Node| match node {
+ Node::Tabs { panels, .. } => panels.contains(&panel),
+ Node::Stack { entries } => entries.iter().any(|entry| entry.panel == panel),
+ Node::Split { .. } => false,
+ };
+ if *axis == SplitAxis::Horizontal {
+ if contains(first) {
+ change = Some(Action::ResizeSplit { path: path.clone(), size: SplitSize::FixedFirst(width) });
+ }
+ if contains(second) {
+ change = Some(Action::ResizeSplit { path: path.clone(), size: SplitSize::FixedSecond(width) });
+ }
+ }
+ let mut second_path = path.clone();
+ second_path.push(true);
+ let mut first_path = path;
+ first_path.push(false);
+ nodes.push((second, second_path));
+ nodes.push((first, first_path));
+ }
+ }
+ if let Some(action) = change {
+ workspace.apply(action)?;
+ }
+ }
+ Ok(())
+}
+
+fn panel_limits(panel: &Panel) -> PanelLimits {
+ let (min, max_width) = match panel {
+ Panel::Canvas => (egui::vec2(crate::state::MIN_PHOTO_WIDTH, 160.0), 1_000_000.0),
+ Panel::Library => (egui::vec2(crate::state::LEFT_WIDTH.min, 120.0), crate::state::LEFT_WIDTH.max),
+ Panel::Tools => (egui::vec2(crate::state::RIGHT_WIDTH.min, 120.0), crate::state::RIGHT_WIDTH.max),
+ Panel::Presets => (egui::vec2(250.0, 120.0), 520.0),
+ };
+ PanelLimits { min, max: egui::vec2(max_width, 1_000_000.0) }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn app_with_widths() -> crate::LightcraftApp {
+ let mut app = crate::LightcraftApp::new(lightcraft_engine::Session::new(), crate::Services::default());
+ app.ui.left_width = 333.0;
+ app.ui.right_width = 410.0;
+ app
+ }
+
+ fn tools_split(axis: SplitAxis, size: SplitSize, first: bool) -> Workspace {
+ let rest = split(
+ SplitAxis::Horizontal,
+ SplitSize::FixedFirst(333.0),
+ tabs(Panel::Library),
+ split(SplitAxis::Horizontal, SplitSize::FixedSecond(300.0), tabs(Panel::Canvas), tabs(Panel::Presets)),
+ );
+ let root = if first { split(axis, size, tabs(Panel::Tools), rest) } else { split(axis, size, rest, tabs(Panel::Tools)) };
+ Workspace { layout: Layout { root: Some(root), floating: Vec::new() }, hidden: Vec::new() }
+ }
+
+ #[test]
+ fn explicit_reveal_opens_collapsed_tools_and_survives_reload() {
+ let mut app = app_with_widths();
+ app.ui.right = crate::state::RightPanel::Crop;
+ let entries = [Panel::Tools, Panel::Library, Panel::Presets]
+ .into_iter()
+ .map(|panel| craft_ui::docking::StackEntry { panel, open: panel != Panel::Tools, height: Some(180.0) })
+ .collect();
+ app.ui.docking = Some(Workspace {
+ layout: Layout {
+ root: Some(split(SplitAxis::Horizontal, SplitSize::FixedSecond(350.0), tabs(Panel::Canvas), Node::Stack { entries })),
+ floating: Vec::new(),
+ },
+ hidden: Vec::new(),
+ });
+ let undo = app.session.undo.len();
+ app.run("panel.crop", json!({})).unwrap();
+ assert_eq!(app.ui.right, crate::state::RightPanel::Crop);
+ let workspace = app.ui.docking.as_ref().unwrap();
+ assert!(workspace.exposed(Panel::Tools));
+ assert_eq!(app.session.undo.len(), undo);
+ let saved = serde_json::to_string(workspace).unwrap();
+ let reloaded: Workspace = serde_json::from_str(&saved).unwrap();
+ reloaded.validate().unwrap();
+ assert!(reloaded.exposed(Panel::Tools));
+ }
+
+ #[test]
+ fn legacy_width_publication_ignores_heights_and_sibling_widths() {
+ for (axis, size, first) in [
+ (SplitAxis::Vertical, SplitSize::FixedFirst(180.0), true),
+ (SplitAxis::Vertical, SplitSize::FixedSecond(180.0), false),
+ (SplitAxis::Horizontal, SplitSize::FixedSecond(180.0), true),
+ (SplitAxis::Horizontal, SplitSize::FixedFirst(180.0), false),
+ ] {
+ let workspace = tools_split(axis, size, first);
+ workspace.validate().unwrap();
+ let mut app = app_with_widths();
+ publish_visibility(&mut app, &workspace);
+ assert_eq!((app.ui.left_width, app.ui.right_width), (333.0, 410.0), "{axis:?} {size:?}, first={first}");
+ }
+ }
+
+ #[test]
+ fn legacy_width_publication_reads_own_horizontal_and_floating_widths() {
+ for (size, first) in [(SplitSize::FixedFirst(310.0), true), (SplitSize::FixedSecond(310.0), false)] {
+ let workspace = tools_split(SplitAxis::Horizontal, size, first);
+ let mut app = app_with_widths();
+ publish_visibility(&mut app, &workspace);
+ assert_eq!((app.ui.left_width, app.ui.right_width), (333.0, 310.0));
+ }
+ let mut workspace = Workspace::default();
+ workspace.apply(Action::Float { panel: Panel::Tools, rect: [12.0, 34.0, 390.0, 450.0] }).unwrap();
+ let mut app = app_with_widths();
+ publish_visibility(&mut app, &workspace);
+ assert_eq!(app.ui.right_width, 390.0);
+ }
+
+ #[test]
+ fn legacy_width_update_preserves_hidden_top_panel_height() {
+ let mut workspace = Workspace::default();
+ workspace.apply(Action::Move { panel: Panel::Tools, anchor: Panel::Canvas, placement: Placement::Split(Zone::Top) }).unwrap();
+ workspace.set_visible(Panel::Tools, false).unwrap();
+ let height = workspace.hidden.iter().find(|(panel, _)| *panel == Panel::Tools).unwrap().1.size;
+ let mut state = crate::UiState { docking: Some(workspace), right_width: 410.0, ..Default::default() };
+ set_widths(&mut state, &json!({"rightWidth":410.0})).unwrap();
+ assert_eq!(state.docking.as_ref().unwrap().hidden.iter().find(|(panel, _)| *panel == Panel::Tools).unwrap().1.size, height);
+ }
+}
diff --git a/crates/ui-egui/src/headless.rs b/crates/ui-egui/src/headless.rs
index 3aa0d4e1a..6d06f7645 100644
--- a/crates/ui-egui/src/headless.rs
+++ b/crates/ui-egui/src/headless.rs
@@ -2493,9 +2493,19 @@ mod tests {
h.request("engine.execute", json!({"command": "library.select", "params": {"ids": [id.0]}}), t);
h.request("ui.set", json!({"view": "detail", "right": "info"}), t);
let before = h.app.session.catalog.photo(id).unwrap().captured.clone().unwrap();
+ // The dock header leaves less vertical space; bring the metadata action into view.
+ let panel = h.app.widgets.iter().find(|(id, _)| id == "panel:right_panel").unwrap().1;
+ let clipped_action = h.app.widgets.iter().find(|(id, _)| id == "icon:editCaptureTime").unwrap().1;
+ assert!(!panel.contains_rect(clipped_action), "the capture-time action starts below the visible panel: {clipped_action:?}");
+ h.request("ui.move", json!({"x": panel.center().x, "y": panel.center().y}), t);
+ h.request("ui.scroll", json!({"dy": -160.0}), t);
+ h.settle(SETTLE);
+ let action = h.app.widgets.iter().find(|(id, _)| id == "icon:editCaptureTime").unwrap().1;
+ assert!(panel.contains_rect(action), "capture-time action must be visible before clicking: {action:?} in {panel:?}");
let r = h.request("ui.clickWidget", json!({"id": "icon:editCaptureTime"}), t);
assert_eq!(r["ok"], true, "{r}");
- h.request("ui.clickWidget", json!({"id": "button:captureMode-2"}), t);
+ let r = h.request("ui.clickWidget", json!({"id": "button:captureMode-2"}), t);
+ assert_eq!(r["ok"], true, "{r}");
if let Some(crate::state::Dialog::CaptureTime { zone, .. }) = &mut h.app.ui.dialog {
*zone = -3.0;
} else {
diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs
index 9e752bb82..db458c5c4 100644
--- a/crates/ui-egui/src/lib.rs
+++ b/crates/ui-egui/src/lib.rs
@@ -10,6 +10,7 @@ pub mod album_picker;
pub mod control;
pub mod credits;
pub mod date_picker;
+pub mod docking;
pub mod export_task;
pub mod headless;
pub mod i18n;
@@ -429,6 +430,9 @@ impl LightcraftApp {
return result;
}
if let Some(r) = menus::run_ui_command(self, id, ¶ms) {
+ if r.is_ok() {
+ docking::reveal_command(self, id)?;
+ }
return r;
}
let r = self.session.execute(id, ¶ms).map_err(|e| e.to_string());
@@ -1088,31 +1092,7 @@ impl LightcraftApp {
panels::topbar::show(self, ui);
panels::library_problem::banner(self, ui);
panels::strip::show(self, ui);
- if self.ui.right != state::RightPanel::None {
- panels::right::show(self, ui);
- }
- if self.ui.presets {
- panels::presets::show(self, ui);
- }
- if self.ui.left_panel {
- panels::left::show(self, ui);
- }
- panels::bottombar::show(self, ui);
- let t = theme::Tokens::get(&ctx);
- let bg = if matches!(self.ui.view, state::ViewMode::Detail | state::ViewMode::Compare | state::ViewMode::Survey | state::ViewMode::Reference)
- {
- t.canvas
- } else {
- t.grid_bg
- };
- egui::CentralPanel::default().frame(egui::Frame::NONE.fill(bg)).show(ui, |ui| match self.ui.view {
- state::ViewMode::PhotoGrid | state::ViewMode::SquareGrid => panels::grid::show(self, ui),
- state::ViewMode::Detail => panels::detail::show(self, ui),
- state::ViewMode::Compare => panels::compare::show_compare(self, ui),
- state::ViewMode::Survey => panels::compare::show_survey(self, ui),
- state::ViewMode::Reference => panels::compare::show_reference(self, ui),
- state::ViewMode::People => panels::people::show(self, ui),
- });
+ egui::CentralPanel::default().frame(egui::Frame::NONE).show(ui, |ui| docking::show(self, ui));
panels::second::show(self, &ctx);
panels::notices::show(self, &ctx);
panels::dialogs::show(self, &ctx);
@@ -1667,3 +1647,6 @@ mod cache_tests {
#[cfg(test)]
mod tests_model_setup;
+
+#[cfg(test)]
+mod tests_docking_acceptance;
diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs
index c9b397f60..81a46eb98 100644
--- a/crates/ui-egui/src/menus.rs
+++ b/crates/ui-egui/src/menus.rs
@@ -27,7 +27,7 @@ pub const LANGUAGE_COMMANDS: &[UiCommand] = &[
];
/// Every UI command: the languages, then everything else. `xtask parity` reads both tables from
-/// this file, so an id listed in `docs/parity.md` is checked wherever it is declared.
+/// this file, so an id listed in `docs/parity-checklist.md` is checked wherever it is declared.
pub fn ui_commands() -> impl Iterator- {
LANGUAGE_COMMANDS.iter().chain(UI_COMMANDS)
}
@@ -51,6 +51,8 @@ pub fn language_from_command(id: &str) -> Option
{
}
pub const UI_COMMANDS: &[UiCommand] = &[
+ ("ui.dock", "Arrange Panel", None, ""),
+ ("ui.dock.reset", "Reset Panel Layout", None, "Window"),
("modelSetup.cancel", "Cancel Pending Photo Action", None, ""),
("view.photoGrid", "Photo Grid", None, "View"),
("view.squareGrid", "Square Grid", None, "View"),
@@ -204,7 +206,7 @@ pub const UI_COMMANDS: &[UiCommand] = &[
];
fn panel(app: &mut LightcraftApp, ctx: &egui::Context, p: RightPanel, name: &str) {
- if app.ui.right == p {
+ if app.ui.right == p && crate::docking::exposed(app, crate::docking::Panel::Tools) {
app.ui.right = RightPanel::None;
app.toast(ctx, crate::i18n::tr_format!("{name} Off", name = crate::i18n::tr(name)));
} else {
@@ -266,6 +268,9 @@ pub fn parse_rgb(v: &Value) -> Option<[u8; 3]> {
/// Handle UI commands; `None` means "not a UI command — send it to the engine".
pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option> {
+ if matches!(id, "ui.dock" | "ui.dock.reset") {
+ return Some(crate::docking::command(app, id == "ui.dock.reset", p));
+ }
if matches!(id, "library.inspectLightroom" | "library.importLightroom") {
// the app's own context: a fresh one's repaints reach no window and its clock starts at zero
let ctx = app.tasks.repaint.clone().unwrap_or_default();
@@ -751,7 +756,11 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option {
// toggles between the profile browser and the Edit panel it belongs to
- app.ui.right = if app.ui.right == RightPanel::Profiles { RightPanel::Edit } else { RightPanel::Profiles };
+ app.ui.right = if app.ui.right == RightPanel::Profiles && crate::docking::exposed(app, crate::docking::Panel::Tools) {
+ RightPanel::Edit
+ } else {
+ RightPanel::Profiles
+ };
if !matches!(app.ui.view, ViewMode::Detail) {
app.ui.view = ViewMode::Detail;
}
@@ -793,7 +802,7 @@ pub fn run_ui_command(app: &mut LightcraftApp, id: &str, p: &Value) -> Option {
- app.ui.presets = !app.ui.presets;
+ app.ui.presets = !app.ui.presets || !crate::docking::exposed(app, crate::docking::Panel::Presets);
if app.ui.presets && app.ui.view != ViewMode::Detail {
app.ui.view = ViewMode::Detail;
}
diff --git a/crates/ui-egui/src/panels/bottombar.rs b/crates/ui-egui/src/panels/bottombar.rs
index de0409ec1..89de02631 100644
--- a/crates/ui-egui/src/panels/bottombar.rs
+++ b/crates/ui-egui/src/panels/bottombar.rs
@@ -10,6 +10,15 @@ use crate::state::{BeforeAfter, ViewMode, Zoom};
use crate::theme::Tokens;
use crate::widgets::{icon_button, register, stars};
+const VIEW_MODES: &[(&str, Icon, ViewMode, &str)] = &[
+ ("photoGrid", Icon::GridPhoto, ViewMode::PhotoGrid, "Photo Grid (G)"),
+ ("squareGrid", Icon::GridSquare, ViewMode::SquareGrid, "Square Grid (G toggles)"),
+ ("detail", Icon::Single, ViewMode::Detail, "Detail (D)"),
+ ("compare", Icon::Compare, ViewMode::Compare, "Compare (Shift+C)"),
+ ("survey", Icon::Survey, ViewMode::Survey, "Survey (N)"),
+ ("people", Icon::Subject, ViewMode::People, "People"),
+];
+
pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
let t = Tokens::get(ui.ctx());
egui::Panel::bottom("bottom_bar")
@@ -17,32 +26,52 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
.frame(egui::Frame::NONE.fill(t.chrome).inner_margin(egui::Margin { left: 12, right: 12, top: 0, bottom: 0 }))
.show(ui, |ui| {
let full = ui.max_rect();
- let left_end = ui
+ let compact_zoom = full.width() < 360.0;
+ let right_start = right_side(app, ui, full, compact_zoom);
+ let left_rect = Rect::from_min_max(full.min, pos2((right_start - 12.0).max(full.left()), full.bottom()));
+ let mut left_ui = ui.new_child(egui::UiBuilder::new().max_rect(left_rect));
+ let left_end = left_ui
.horizontal_centered(|ui| {
ui.spacing_mut().item_spacing.x = 2.0;
- for (id, icon, mode, tip) in [
- ("photoGrid", Icon::GridPhoto, ViewMode::PhotoGrid, "Photo Grid (G)"),
- ("squareGrid", Icon::GridSquare, ViewMode::SquareGrid, "Square Grid (G toggles)"),
- ("detail", Icon::Single, ViewMode::Detail, "Detail (D)"),
- ("compare", Icon::Compare, ViewMode::Compare, "Compare (Shift+C)"),
- ("survey", Icon::Survey, ViewMode::Survey, "Survey (N)"),
- ("people", Icon::Subject, ViewMode::People, "People"),
- ] {
- if icon_button(ui, id, icon, vec2(32.0, 32.0), app.ui.view == mode, true, tip).clicked() {
- let _ = app.run(&format!("view.{id}"), json!({}));
+ if left_rect.width() >= 270.0 {
+ for &(id, icon, mode, tip) in VIEW_MODES {
+ if icon_button(ui, id, icon, vec2(32.0, 32.0), app.ui.view == mode, true, tip).clicked() {
+ let _ = app.run(&format!("view.{id}"), json!({}));
+ }
}
+ ui.add_space(10.0);
+ let (sep, _) = ui.allocate_exact_size(vec2(1.0, 22.0), Sense::hover());
+ ui.painter().rect_filled(sep, 0.0, t.button_border);
+ ui.add_space(10.0);
+ let sort = icon_button(ui, "sort", Icon::Sort, vec2(30.0, 30.0), false, true, "Sort");
+ egui::Popup::menu(&sort).show(|ui| sort_menu(app, ui));
+ } else {
+ let current_icon = VIEW_MODES.iter().find(|(_, _, mode, _)| *mode == app.ui.view).map_or(Icon::More, |(_, icon, _, _)| *icon);
+ let more = icon_button(ui, "viewOptions", current_icon, vec2(40.0, 32.0), true, true, "View");
+ paint(
+ ui.painter(),
+ Rect::from_center_size(more.rect.right_bottom() - vec2(5.0, 5.0), vec2(7.0, 7.0)),
+ Icon::ChevronDown,
+ t.text,
+ );
+ egui::Popup::menu(&more).show(|ui| {
+ for &(id, _, mode, label) in VIEW_MODES {
+ if ui.selectable_label(app.ui.view == mode, crate::i18n::tr(label)).clicked() {
+ let _ = app.run(&format!("view.{id}"), json!({}));
+ ui.close();
+ }
+ }
+ ui.separator();
+ ui.menu_button(crate::i18n::tr("Sort"), |ui| sort_menu(app, ui));
+ if compact_zoom && matches!(app.ui.view, ViewMode::Detail | ViewMode::Compare) {
+ ui.menu_button(crate::i18n::tr_format!("Click {}:1", app.ui.click_zoom / 100), |ui| click_zoom_menu(app, ui));
+ }
+ });
}
- ui.add_space(10.0);
- let (sep, _) = ui.allocate_exact_size(vec2(1.0, 22.0), Sense::hover());
- ui.painter().rect_filled(sep, 0.0, t.button_border);
- ui.add_space(10.0);
- let sort = icon_button(ui, "sort", Icon::Sort, vec2(30.0, 30.0), false, true, "Sort");
- egui::Popup::menu(&sort).show(|ui| sort_menu(app, ui));
})
.response
.rect
.right();
- let right_start = right_side(app, ui, full);
centre(app, ui, full, left_end + 12.0, right_start - 12.0);
});
}
@@ -123,11 +152,11 @@ fn centre(app: &mut LightcraftApp, ui: &mut egui::Ui, full: Rect, from: f32, to:
}
/// The right-hand group (zoom, view toggles or thumbnail size); returns its left edge.
-fn right_side(app: &mut LightcraftApp, ui: &mut egui::Ui, full: Rect) -> f32 {
+fn right_side(app: &mut LightcraftApp, ui: &mut egui::Ui, full: Rect, compact_zoom: bool) -> f32 {
let t = Tokens::get(ui.ctx());
let mut child = ui.new_child(
egui::UiBuilder::new()
- .max_rect(Rect::from_min_max(pos2(full.right() - 360.0, full.top()), full.right_bottom()))
+ .max_rect(Rect::from_min_max(pos2((full.right() - 360.0).max(full.left()), full.top()), full.right_bottom()))
.layout(egui::Layout::right_to_left(egui::Align::Center)),
);
child.spacing_mut().item_spacing.x = 6.0;
@@ -191,21 +220,17 @@ fn right_side(app: &mut LightcraftApp, ui: &mut egui::Ui, full: Rect) -> f32 {
}
}
});
- child.add_space(6.0);
- let cz = crate::widgets::dropdown(
- &mut child,
- "clickZoom",
- &crate::i18n::tr_format!("Click {}:1", app.ui.click_zoom / 100),
- t.font(13.0),
- t.text_label,
- );
- egui::Popup::menu(&cz).show(|ui| {
- for pct in crate::state::CLICK_ZOOMS {
- if ui.selectable_label(app.ui.click_zoom == pct, format!("{}:1", pct / 100)).clicked() {
- let _ = app.run("view.clickZoom", json!({"ratio": pct / 100}));
- }
- }
- });
+ if !compact_zoom {
+ child.add_space(6.0);
+ let cz = crate::widgets::dropdown(
+ &mut child,
+ "clickZoom",
+ &crate::i18n::tr_format!("Click {}:1", app.ui.click_zoom / 100),
+ t.font(13.0),
+ t.text_label,
+ );
+ egui::Popup::menu(&cz).show(|ui| click_zoom_menu(app, ui));
+ }
} else {
// thumbnail size slider
let (r, resp) = child.allocate_exact_size(vec2(110.0, 20.0), Sense::click_and_drag());
@@ -225,6 +250,14 @@ fn right_side(app: &mut LightcraftApp, ui: &mut egui::Ui, full: Rect) -> f32 {
if child.min_rect().width() > 0.0 { child.min_rect().left() } else { full.right() }
}
+fn click_zoom_menu(app: &mut LightcraftApp, ui: &mut egui::Ui) {
+ for pct in crate::state::CLICK_ZOOMS {
+ if ui.selectable_label(app.ui.click_zoom == pct, format!("{}:1", pct / 100)).clicked() {
+ let _ = app.run("view.clickZoom", json!({"ratio": pct / 100}));
+ }
+ }
+}
+
fn sort_menu(app: &mut LightcraftApp, ui: &mut egui::Ui) {
use lightcraft_catalog::GroupBy;
use lightcraft_catalog::SortKey::*;
@@ -270,3 +303,156 @@ fn sort_menu(app: &mut LightcraftApp, ui: &mut egui::Ui) {
}
}
}
+
+#[cfg(test)]
+mod visual_regressions {
+ use super::*;
+
+ fn scaled_input(size: egui::Vec2, scale: f32) -> egui::RawInput {
+ let mut input = egui::RawInput { screen_rect: Some(Rect::from_min_size(egui::Pos2::ZERO, size)), ..Default::default() };
+ input.viewports.entry(egui::ViewportId::ROOT).or_default().native_pixels_per_point = Some(scale);
+ input
+ }
+
+ #[test]
+ fn capture_bottom_bar_visual_fixtures() {
+ let Some(directory) = std::env::var_os("CRAFT_UI_VISUAL_FIXTURES").map(std::path::PathBuf::from) else { return };
+ std::fs::create_dir_all(&directory).unwrap();
+ for width in [280.0, 400.0, 600.0, 1000.0] {
+ for view in [
+ ViewMode::PhotoGrid,
+ ViewMode::SquareGrid,
+ ViewMode::Detail,
+ ViewMode::Compare,
+ ViewMode::Survey,
+ ViewMode::Reference,
+ ViewMode::People,
+ ] {
+ for scale in [1.0, 1.5, 2.0] {
+ let mut app = LightcraftApp::new(lightcraft_engine::Session::new(), Default::default());
+ app.ui.view = view;
+ let mut ready = false;
+ let mut harness =
+ egui_kittest::Harness::builder().with_size(vec2(width, 100.0)).with_pixels_per_point(scale).wgpu().build_ui_state(
+ move |ui, app: &mut LightcraftApp| {
+ if !ready {
+ crate::theme::install_fonts(ui.ctx());
+ crate::theme::apply(ui.ctx());
+ ready = true;
+ return;
+ }
+ crate::widgets::take_registry(ui.ctx());
+ show(app, ui);
+ },
+ app,
+ );
+ harness.input_mut().max_texture_side = Some(8192);
+ harness.run_steps(4);
+ let stem = format!("lightcraft-bottom-{view:?}-{width}-{scale}x");
+ harness.render().unwrap().save(directory.join(format!("{stem}.png"))).unwrap();
+ let panel = egui::containers::panel::PanelState::load(&harness.ctx, egui::Id::new("bottom_bar")).unwrap().outer_rect;
+ let controls: serde_json::Map = crate::widgets::take_registry(&harness.ctx)
+ .into_iter()
+ .map(|(id, r)| (id, json!([r.left(), r.top(), r.width(), r.height()])))
+ .collect();
+ std::fs::write(
+ directory.join(format!("{stem}.json")),
+ serde_json::to_vec_pretty(&json!({
+ "screen": [width, 100.0], "scale": scale,
+ "regions": {"bottom-bar": [panel.left(), panel.top(), panel.width(), panel.height()]},
+ "controls": controls
+ }))
+ .unwrap(),
+ )
+ .unwrap();
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn bottom_bar_controls_do_not_overlap_in_narrow_canvas() {
+ for width in [280.0, 400.0, 600.0, 1000.0] {
+ for view in [
+ ViewMode::PhotoGrid,
+ ViewMode::SquareGrid,
+ ViewMode::Detail,
+ ViewMode::Compare,
+ ViewMode::Survey,
+ ViewMode::Reference,
+ ViewMode::People,
+ ] {
+ for scale in [1.0, 1.5, 2.0] {
+ let ctx = egui::Context::default();
+ crate::theme::install_fonts(&ctx);
+ crate::theme::apply(&ctx);
+ let mut app = LightcraftApp::new(lightcraft_engine::Session::new(), Default::default());
+ app.ui.view = view;
+ let mut output = ctx.run_ui(scaled_input(vec2(width, 100.0), scale), |ui| show(&mut app, ui));
+ output.textures_delta.clear();
+ assert_eq!(ctx.content_rect().size(), vec2(width, 100.0));
+ assert_eq!(ctx.pixels_per_point(), scale);
+ let controls = crate::widgets::take_registry(&ctx);
+ assert!(!controls.is_empty());
+ for (index, (id, rect)) in controls.iter().enumerate() {
+ assert!(rect.left() >= 0.0 && rect.right() <= width, "{view:?} at {width}: {id} outside viewport: {rect:?}");
+ for (other_id, other) in controls.iter().skip(index + 1) {
+ assert!(!rect.intersect(*other).is_positive(), "{view:?} at {width}: {id} overlaps {other_id}: {rect:?} / {other:?}");
+ }
+ }
+ }
+ }
+ }
+ }
+ #[test]
+ fn compact_view_menu_opens_and_changes_view_by_keyboard() {
+ use egui_kittest::kittest::Queryable;
+
+ for scale in [1.0, 1.5, 2.0] {
+ let app = LightcraftApp::new(lightcraft_engine::Session::new(), Default::default());
+ let mut ready = false;
+ let mut harness = egui_kittest::Harness::builder().with_size(vec2(280.0, 640.0)).with_pixels_per_point(scale).build_ui_state(
+ move |ui, app: &mut LightcraftApp| {
+ if !ready {
+ crate::theme::install_fonts(ui.ctx());
+ crate::theme::apply(ui.ctx());
+ ready = true;
+ return;
+ }
+ crate::shortcuts::handle(app, ui.ctx());
+ crate::widgets::take_registry(ui.ctx());
+ show(app, ui);
+ },
+ app,
+ );
+ harness.run_steps(4);
+ assert_eq!(harness.ctx.content_rect().size(), vec2(280.0, 640.0));
+ assert_eq!(harness.ctx.pixels_per_point(), scale);
+ let mut focused = false;
+ for _ in 0..12 {
+ harness.key_press(egui::Key::Tab);
+ harness.run_steps(3);
+ focused = harness.get_by_label("View").is_focused();
+ if focused {
+ break;
+ }
+ }
+ assert!(focused, "Tab reaches the compact View control at {scale}x");
+ harness.key_press(egui::Key::Space);
+ harness.run_steps(3);
+ let mut people_focused = false;
+ for _ in 0..20 {
+ harness.key_press(egui::Key::Tab);
+ harness.run_steps(3);
+ people_focused = harness.get_by_label("People").is_focused();
+ if people_focused {
+ break;
+ }
+ }
+ assert!(people_focused, "Tab reaches People at {scale}x");
+ harness.key_press(egui::Key::Space);
+ harness.run_steps(3);
+ assert_eq!(harness.state().ui.view, ViewMode::People, "keyboard activation dispatches the view command");
+ }
+ }
+}
diff --git a/crates/ui-egui/src/panels/keyword_list.rs b/crates/ui-egui/src/panels/keyword_list.rs
index f201359db..bf0daeb45 100644
--- a/crates/ui-egui/src/panels/keyword_list.rs
+++ b/crates/ui-egui/src/panels/keyword_list.rs
@@ -107,7 +107,7 @@ pub(crate) fn follow(app: &mut LightcraftApp, from: &str, to: &str) {
/// The pointer is on the part of `rect` that is shown: inside it and inside the panel's visible
/// (scrolled) area, so a drop never lands on a row or title hidden under the top bar.
fn shown_under(ui: &egui::Ui, rect: Rect, pointer: Option) -> bool {
- pointer.is_some_and(|p| rect.contains(p) && ui.clip_rect().contains(p))
+ crate::docking::drop_target(ui, rect, pointer)
}
/// A keyword being dragged, each frame (after the panels, which take the drop): its name follows
diff --git a/crates/ui-egui/src/panels/left.rs b/crates/ui-egui/src/panels/left.rs
index b435f6eda..4f1bae44c 100644
--- a/crates/ui-egui/src/panels/left.rs
+++ b/crates/ui-egui/src/panels/left.rs
@@ -180,8 +180,7 @@ fn sidebar_section_header(app: &mut LightcraftApp, ui: &mut egui::Ui, id: &str,
pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
let t = Tokens::get(ui.ctx());
let frame = egui::Frame::NONE.fill(t.chrome).stroke(egui::Stroke::new(1.0, t.divider));
- let width = app.ui.left_width;
- let resized = super::resizable_side(ui, true, "left_panel", frame, width, crate::state::LEFT_WIDTH, 0.0, |ui| {
+ frame.show(ui, |ui| {
ui.spacing_mut().item_spacing.y = 0.0;
let (hr, _) = ui.allocate_exact_size(vec2(ui.available_width(), 44.0), Sense::hover());
ui.painter().text(pos2(hr.left() + 18.0, hr.center().y), Align2::LEFT_CENTER, crate::i18n::tr("My Photos"), t.semibold(15.0), t.text);
@@ -300,9 +299,6 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
}
});
});
- if let Some(w) = resized {
- app.ui.left_width = w;
- }
}
/// How many library photos have no file (Local browse records are not checked; see
@@ -775,7 +771,7 @@ fn albums_tree(app: &mut LightcraftApp, ui: &mut egui::Ui, all: &AlbumKids, pare
/// Whether the pointer is over `rect` and over what the panel shows of it (a row scrolled out of
/// sight is not a drop target).
fn pointer_over(ui: &egui::Ui, rect: Rect) -> bool {
- ui.input(|i| i.pointer.latest_pos()).is_some_and(|p| rect.contains(p) && ui.clip_rect().contains(p))
+ crate::docking::drop_target(ui, rect, ui.input(|i| i.pointer.latest_pos()))
}
/// How close to the top or bottom edge of the sidebar (points) a dragged album starts scrolling it.
@@ -1010,7 +1006,7 @@ pub fn album_drag_feedback(app: &mut LightcraftApp, ctx: &egui::Context) {
/// release there adds them.
fn drop_target(app: &mut LightcraftApp, ui: &mut egui::Ui, resp: &egui::Response, a: &Album) {
let Some(ids) = app.ui.dragging_photos.clone() else { return };
- let over = ui.input(|i| i.pointer.latest_pos()).is_some_and(|p| resp.rect.contains(p));
+ let over = pointer_over(ui, resp.rect);
if !over {
return;
}
diff --git a/crates/ui-egui/src/panels/presets.rs b/crates/ui-egui/src/panels/presets.rs
index f60ddc5d6..0968ab67d 100644
--- a/crates/ui-egui/src/panels/presets.rs
+++ b/crates/ui-egui/src/panels/presets.rs
@@ -19,224 +19,206 @@ const THUMB_EDGE: usize = 128;
pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
let t = Tokens::get(ui.ctx());
- egui::Panel::right("presets_panel")
- .exact_size(t.panel_w)
- .resizable(false)
- .frame(egui::Frame::NONE.fill(t.chrome).stroke(egui::Stroke::new(1.0, t.divider)))
- .show(ui, |ui| {
- ui.spacing_mut().item_spacing.y = 0.0;
- let (hr, _) = ui.allocate_exact_size(vec2(ui.available_width(), 46.0), Sense::hover());
- ui.painter().text(pos2(hr.left() + 24.0, hr.center().y + 2.0), Align2::LEFT_CENTER, crate::i18n::tr("Presets"), t.semibold(15.0), t.text);
- let mut hdr = ui.new_child(
- egui::UiBuilder::new()
- .max_rect(Rect::from_min_max(pos2(hr.right() - 80.0, hr.top()), hr.right_bottom()))
- .layout(egui::Layout::right_to_left(egui::Align::Center)),
- );
- if icon_button(&mut hdr, "presetCreate", Icon::Plus, vec2(28.0, 28.0), false, app.session.active().is_some(), "Create Preset…").clicked()
- {
- app.ui.dialog = Some(crate::state::Dialog::create_preset());
+ egui::Frame::NONE.fill(t.chrome).stroke(egui::Stroke::new(1.0, t.divider)).show(ui, |ui| {
+ ui.spacing_mut().item_spacing.y = 0.0;
+ let (hr, _) = ui.allocate_exact_size(vec2(ui.available_width(), 46.0), Sense::hover());
+ ui.painter().text(pos2(hr.left() + 24.0, hr.center().y + 2.0), Align2::LEFT_CENTER, crate::i18n::tr("Presets"), t.semibold(15.0), t.text);
+ let mut hdr = ui.new_child(
+ egui::UiBuilder::new()
+ .max_rect(Rect::from_min_max(pos2(hr.right() - 80.0, hr.top()), hr.right_bottom()))
+ .layout(egui::Layout::right_to_left(egui::Align::Center)),
+ );
+ if icon_button(&mut hdr, "presetCreate", Icon::Plus, vec2(28.0, 28.0), false, app.session.active().is_some(), "Create Preset…").clicked() {
+ app.ui.dialog = Some(crate::state::Dialog::create_preset());
+ }
+ let more = icon_button(&mut hdr, "presetMore", Icon::More, vec2(28.0, 28.0), false, true, "More preset options");
+ egui::Popup::menu(&more).show(|ui| {
+ let r = ui.checkbox(&mut app.ui.preset_thumbs, crate::i18n::tr("Show Thumbnails"));
+ register(ui.ctx(), "presetMenu:thumbnails", r.rect);
+ ui.separator();
+ if ui.button(crate::i18n::tr("Import Presets…")).clicked() {
+ let _ = app.run("file.importPresets", json!({}));
}
- let more = icon_button(&mut hdr, "presetMore", Icon::More, vec2(28.0, 28.0), false, true, "More preset options");
- egui::Popup::menu(&more).show(|ui| {
- let r = ui.checkbox(&mut app.ui.preset_thumbs, crate::i18n::tr("Show Thumbnails"));
- register(ui.ctx(), "presetMenu:thumbnails", r.rect);
- ui.separator();
- if ui.button(crate::i18n::tr("Import Presets…")).clicked() {
- let _ = app.run("file.importPresets", json!({}));
- }
- let any_user = app.session.presets.iter().any(|p| !p.builtin);
- if ui.add_enabled(any_user, egui::Button::new(crate::i18n::tr("Export User Presets…"))).clicked() {
- let _ = app.run("file.exportPresets", json!({}));
- }
- });
- divider(ui);
- // amount slider (applies to the last applied preset)
- let amt_id = egui::Id::new("preset-amount");
- let last: Option<(String, f64)> = ui.data(|d| d.get_temp(amt_id));
- if let Some((pid, amount)) = last.clone() {
- let spec = ControlSpec {
- id: "presetAmount",
- label: "Amount",
- section: Section::Profile,
- min: 0.0,
- max: 200.0,
- default: 100.0,
- step: 1.0,
- decimals: 0,
- track: Track::Plain,
- };
- let out = slider(ui, &spec, amount, true, None);
- if let Some(v) = out.value {
- // re-apply from the pre-preset state: undo last preset step then apply with the new amount
- let _ = app.run("edit.undo", json!({}));
- let _ = app.run("preset.apply", json!({"id": pid, "amount": v}));
- ui.data_mut(|d| d.insert_temp(amt_id, (pid, v)));
- }
- divider(ui);
- }
- let active = app.session.active();
- let current = active.and_then(|id| app.session.develop_of(id)).map(|d| (*d).clone());
- let thumbs = app.ui.preset_thumbs && active.is_some();
- let row_h = if thumbs { 54.0 } else { 26.0 };
- let scroll = egui::ScrollArea::vertical().id_salt("presets-scroll").auto_shrink([false, false]).show(ui, |ui| {
- ui.spacing_mut().item_spacing.y = 0.0;
- let mut groups: BTreeMap> = BTreeMap::new();
- for p in &app.session.presets {
- groups.entry(p.group.clone()).or_default().push(p.clone());
- }
- for (g, items) in groups {
- // A group holding stock presets is a stock group (shown in the UI language), even
- // when user presets share its name; a group of user presets keeps its name.
- let builtin = items.iter().any(|p| p.builtin);
- let open_id = egui::Id::new(("preset-group", &g));
- let open: bool = ui.data(|d| d.get_temp(open_id)).unwrap_or(true);
- let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 32.0), Sense::click());
- register(ui.ctx(), format!("presetGroup:{g}"), r);
- paint(
- ui.painter(),
- Rect::from_center_size(pos2(r.left() + 22.0, r.center().y), vec2(12.0, 12.0)),
- if open { Icon::ChevronDown } else { Icon::ChevronRight },
- t.text_label,
- );
- ui.painter().text(
- pos2(r.left() + 36.0, r.center().y),
- Align2::LEFT_CENTER,
- crate::i18n::builtin_label(&g, builtin),
- t.semibold(13.0),
- t.text_label,
- );
- if resp.clicked() {
- ui.data_mut(|d| d.insert_temp(open_id, !open));
- }
- resp.context_menu(|ui| {
- if ui.button(crate::i18n::tr("Export Group…")).clicked() {
- let _ = app.run("file.exportPresets", json!({"group": g}));
- }
- });
- if !open {
- continue;
- }
- for pr in items {
- let (pid, name, fav) = (pr.id.clone(), crate::i18n::builtin_label(&pr.name, pr.builtin).to_string(), pr.favorite);
- let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), row_h), Sense::click());
- register(ui.ctx(), format!("preset:{pid}"), r);
- let is_last = last.as_ref().is_some_and(|(l, _)| *l == pid);
- if is_last {
- ui.painter().rect_filled(r, 0.0, t.tool_active);
- } else if resp.hovered() {
- ui.painter().rect_filled(r, 0.0, t.hover.gamma_multiply(0.7));
- }
- let look = if thumbs || resp.hovered() { current.as_ref().map(|d| pr.apply(d, 1.0)) } else { None };
- let mut text_x = r.left() + 40.0;
- if thumbs && let (Some(id), Some(s)) = (active, look.as_ref()) {
- let tr = Rect::from_min_size(pos2(r.left() + 36.0, r.top() + 5.0), vec2(66.0, row_h - 10.0));
- ui.painter().rect_filled(tr, 2.0, t.canvas);
- if let Some(job) = app.session.variant_job(id, s, THUMB_EDGE)
- && let Some(tex) = app.renderer.variant(job)
- {
- ui.painter().image(tex.tex.id(), tr, cover_uv(tr, tex.size), Color32::WHITE);
- }
- text_x = tr.right() + 10.0;
- }
- ui.painter().text(pos2(text_x, r.center().y), Align2::LEFT_CENTER, &name, t.font(13.0), t.text_label);
- if fav {
- paint(
- ui.painter(),
- Rect::from_center_size(pos2(r.right() - 20.0, r.center().y), vec2(12.0, 12.0)),
- Icon::StarFilled,
- t.star,
- );
- }
- // resting on a preset previews it in the loupe (no history entry)
- if resp.hovered()
- && let Some(s) = look
- {
- let label = crate::i18n::tr_format!("Preset: {name}", name = name);
- app.hover_preview = Some(HoverPreview { label: label.clone(), settings: s });
- if let Some(id) = active {
- ui.data_mut(|d| d.insert_temp(egui::Id::new("last-preset-hover"), (id, pr.clone(), label)));
- }
- }
- if resp.clicked() {
- let _ = app.run("preset.apply", json!({"id": pid, "amount": 100}));
- ui.data_mut(|d| d.insert_temp(amt_id, (pid.clone(), 100.0)));
- ui.data_mut(|d| d.remove::<(lightcraft_catalog::PhotoId, Preset, String)>(egui::Id::new("last-preset-hover")));
- app.toast(ui.ctx(), crate::i18n::tr_format!("Preset: {name}", name = name));
- }
- let (builtin, group) = (pr.builtin, pr.group.clone());
- resp.context_menu(|ui| {
- if ui.button(crate::i18n::tr(if fav { "Remove from Favorites" } else { "Add to Favorites" })).clicked() {
- let _ = app.run("preset.favorite", json!({"id": pid}));
- }
- if builtin {
- return;
- }
- ui.separator();
- if ui
- .add_enabled(app.session.active().is_some(), egui::Button::new(crate::i18n::tr("Update with Current Settings")))
- .clicked()
- {
- match app.run("preset.update", json!({"id": pid})) {
- Ok(_) => app.toast(ui.ctx(), crate::i18n::tr_format!("Updated “{name}”", name = name)),
- Err(e) => app.toast(ui.ctx(), e),
- }
- }
- if ui.button(crate::i18n::tr("Rename…")).clicked() {
- crate::panels::dialogs::prompt(
- app,
- "Rename Preset",
- "Preset name",
- &name,
- "preset.rename",
- json!({"id": pid}),
- "name",
- );
- }
- ui.menu_button(crate::i18n::tr("Move to Group"), |ui| {
- let mut groups: Vec = app.session.presets.iter().filter(|p| !p.builtin).map(|p| p.group.clone()).collect();
- groups.sort();
- groups.dedup();
- for g in groups.iter().filter(|g| **g != group) {
- if ui.button(g).clicked() {
- let _ = app.run("preset.move", json!({"id": pid, "group": g}));
- }
- }
- if ui.button(crate::i18n::tr("New Group…")).clicked() {
- crate::panels::dialogs::prompt(
- app,
- "Move Preset to New Group",
- "Group name",
- "",
- "preset.move",
- json!({"id": pid}),
- "group",
- );
- }
- });
- ui.separator();
- if ui.button(crate::i18n::tr("Delete Preset")).clicked() {
- let _ = app.run("preset.delete", json!({"id": pid}));
- }
- });
- }
- }
- ui.add_space(30.0);
- });
- let last_hover_id = egui::Id::new("last-preset-hover");
- let pointer_in_scroll = ui.ctx().input(|i| i.pointer.hover_pos()).is_some_and(|pos| scroll.inner_rect.contains(pos));
- if pointer_in_scroll {
- if app.hover_preview.is_none()
- && let (Some(id), Some(curr)) = (active, current.as_ref())
- && let Some((saved_id, saved_preset, saved_label)) =
- ui.data(|d| d.get_temp::<(lightcraft_catalog::PhotoId, Preset, String)>(last_hover_id))
- && saved_id == id
- {
- app.hover_preview = Some(HoverPreview { label: saved_label, settings: saved_preset.apply(curr, 1.0) });
- }
- } else {
- ui.data_mut(|d| d.remove::<(lightcraft_catalog::PhotoId, Preset, String)>(last_hover_id));
+ let any_user = app.session.presets.iter().any(|p| !p.builtin);
+ if ui.add_enabled(any_user, egui::Button::new(crate::i18n::tr("Export User Presets…"))).clicked() {
+ let _ = app.run("file.exportPresets", json!({}));
}
});
+ divider(ui);
+ // amount slider (applies to the last applied preset)
+ let amt_id = egui::Id::new("preset-amount");
+ let last: Option<(String, f64)> = ui.data(|d| d.get_temp(amt_id));
+ if let Some((pid, amount)) = last.clone() {
+ let spec = ControlSpec {
+ id: "presetAmount",
+ label: "Amount",
+ section: Section::Profile,
+ min: 0.0,
+ max: 200.0,
+ default: 100.0,
+ step: 1.0,
+ decimals: 0,
+ track: Track::Plain,
+ };
+ let out = slider(ui, &spec, amount, true, None);
+ if let Some(v) = out.value {
+ // re-apply from the pre-preset state: undo last preset step then apply with the new amount
+ let _ = app.run("edit.undo", json!({}));
+ let _ = app.run("preset.apply", json!({"id": pid, "amount": v}));
+ ui.data_mut(|d| d.insert_temp(amt_id, (pid, v)));
+ }
+ divider(ui);
+ }
+ let active = app.session.active();
+ let current = active.and_then(|id| app.session.develop_of(id)).map(|d| (*d).clone());
+ let thumbs = app.ui.preset_thumbs && active.is_some();
+ let row_h = if thumbs { 54.0 } else { 26.0 };
+ let scroll = egui::ScrollArea::vertical().id_salt("presets-scroll").auto_shrink([false, false]).show(ui, |ui| {
+ ui.spacing_mut().item_spacing.y = 0.0;
+ let mut groups: BTreeMap> = BTreeMap::new();
+ for p in &app.session.presets {
+ groups.entry(p.group.clone()).or_default().push(p.clone());
+ }
+ for (g, items) in groups {
+ // A group holding stock presets is a stock group (shown in the UI language), even
+ // when user presets share its name; a group of user presets keeps its name.
+ let builtin = items.iter().any(|p| p.builtin);
+ let open_id = egui::Id::new(("preset-group", &g));
+ let open: bool = ui.data(|d| d.get_temp(open_id)).unwrap_or(true);
+ let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 32.0), Sense::click());
+ register(ui.ctx(), format!("presetGroup:{g}"), r);
+ paint(
+ ui.painter(),
+ Rect::from_center_size(pos2(r.left() + 22.0, r.center().y), vec2(12.0, 12.0)),
+ if open { Icon::ChevronDown } else { Icon::ChevronRight },
+ t.text_label,
+ );
+ ui.painter().text(
+ pos2(r.left() + 36.0, r.center().y),
+ Align2::LEFT_CENTER,
+ crate::i18n::builtin_label(&g, builtin),
+ t.semibold(13.0),
+ t.text_label,
+ );
+ if resp.clicked() {
+ ui.data_mut(|d| d.insert_temp(open_id, !open));
+ }
+ resp.context_menu(|ui| {
+ if ui.button(crate::i18n::tr("Export Group…")).clicked() {
+ let _ = app.run("file.exportPresets", json!({"group": g}));
+ }
+ });
+ if !open {
+ continue;
+ }
+ for pr in items {
+ let (pid, name, fav) = (pr.id.clone(), crate::i18n::builtin_label(&pr.name, pr.builtin).to_string(), pr.favorite);
+ let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), row_h), Sense::click());
+ register(ui.ctx(), format!("preset:{pid}"), r);
+ let is_last = last.as_ref().is_some_and(|(l, _)| *l == pid);
+ if is_last {
+ ui.painter().rect_filled(r, 0.0, t.tool_active);
+ } else if resp.hovered() {
+ ui.painter().rect_filled(r, 0.0, t.hover.gamma_multiply(0.7));
+ }
+ let look = if thumbs || resp.hovered() { current.as_ref().map(|d| pr.apply(d, 1.0)) } else { None };
+ let mut text_x = r.left() + 40.0;
+ if thumbs && let (Some(id), Some(s)) = (active, look.as_ref()) {
+ let tr = Rect::from_min_size(pos2(r.left() + 36.0, r.top() + 5.0), vec2(66.0, row_h - 10.0));
+ ui.painter().rect_filled(tr, 2.0, t.canvas);
+ if let Some(job) = app.session.variant_job(id, s, THUMB_EDGE)
+ && let Some(tex) = app.renderer.variant(job)
+ {
+ ui.painter().image(tex.tex.id(), tr, cover_uv(tr, tex.size), Color32::WHITE);
+ }
+ text_x = tr.right() + 10.0;
+ }
+ ui.painter().text(pos2(text_x, r.center().y), Align2::LEFT_CENTER, &name, t.font(13.0), t.text_label);
+ if fav {
+ paint(ui.painter(), Rect::from_center_size(pos2(r.right() - 20.0, r.center().y), vec2(12.0, 12.0)), Icon::StarFilled, t.star);
+ }
+ // resting on a preset previews it in the loupe (no history entry)
+ if resp.hovered()
+ && let Some(s) = look
+ {
+ let label = crate::i18n::tr_format!("Preset: {name}", name = name);
+ app.hover_preview = Some(HoverPreview { label: label.clone(), settings: s });
+ if let Some(id) = active {
+ ui.data_mut(|d| d.insert_temp(egui::Id::new("last-preset-hover"), (id, pr.clone(), label)));
+ }
+ }
+ if resp.clicked() {
+ let _ = app.run("preset.apply", json!({"id": pid, "amount": 100}));
+ ui.data_mut(|d| d.insert_temp(amt_id, (pid.clone(), 100.0)));
+ ui.data_mut(|d| d.remove::<(lightcraft_catalog::PhotoId, Preset, String)>(egui::Id::new("last-preset-hover")));
+ app.toast(ui.ctx(), crate::i18n::tr_format!("Preset: {name}", name = name));
+ }
+ let (builtin, group) = (pr.builtin, pr.group.clone());
+ resp.context_menu(|ui| {
+ if ui.button(crate::i18n::tr(if fav { "Remove from Favorites" } else { "Add to Favorites" })).clicked() {
+ let _ = app.run("preset.favorite", json!({"id": pid}));
+ }
+ if builtin {
+ return;
+ }
+ ui.separator();
+ if ui
+ .add_enabled(app.session.active().is_some(), egui::Button::new(crate::i18n::tr("Update with Current Settings")))
+ .clicked()
+ {
+ match app.run("preset.update", json!({"id": pid})) {
+ Ok(_) => app.toast(ui.ctx(), crate::i18n::tr_format!("Updated “{name}”", name = name)),
+ Err(e) => app.toast(ui.ctx(), e),
+ }
+ }
+ if ui.button(crate::i18n::tr("Rename…")).clicked() {
+ crate::panels::dialogs::prompt(app, "Rename Preset", "Preset name", &name, "preset.rename", json!({"id": pid}), "name");
+ }
+ ui.menu_button(crate::i18n::tr("Move to Group"), |ui| {
+ let mut groups: Vec = app.session.presets.iter().filter(|p| !p.builtin).map(|p| p.group.clone()).collect();
+ groups.sort();
+ groups.dedup();
+ for g in groups.iter().filter(|g| **g != group) {
+ if ui.button(g).clicked() {
+ let _ = app.run("preset.move", json!({"id": pid, "group": g}));
+ }
+ }
+ if ui.button(crate::i18n::tr("New Group…")).clicked() {
+ crate::panels::dialogs::prompt(
+ app,
+ "Move Preset to New Group",
+ "Group name",
+ "",
+ "preset.move",
+ json!({"id": pid}),
+ "group",
+ );
+ }
+ });
+ ui.separator();
+ if ui.button(crate::i18n::tr("Delete Preset")).clicked() {
+ let _ = app.run("preset.delete", json!({"id": pid}));
+ }
+ });
+ }
+ }
+ ui.add_space(30.0);
+ });
+ let last_hover_id = egui::Id::new("last-preset-hover");
+ let pointer_in_scroll = ui.ctx().input(|i| i.pointer.hover_pos()).is_some_and(|pos| scroll.inner_rect.contains(pos));
+ if pointer_in_scroll {
+ if app.hover_preview.is_none()
+ && let (Some(id), Some(curr)) = (active, current.as_ref())
+ && let Some((saved_id, saved_preset, saved_label)) =
+ ui.data(|d| d.get_temp::<(lightcraft_catalog::PhotoId, Preset, String)>(last_hover_id))
+ && saved_id == id
+ {
+ app.hover_preview = Some(HoverPreview { label: saved_label, settings: saved_preset.apply(curr, 1.0) });
+ }
+ } else {
+ ui.data_mut(|d| d.remove::<(lightcraft_catalog::PhotoId, Preset, String)>(last_hover_id));
+ }
+ });
}
/// The part of a `size` image that covers `r` (centre crop), as texture coordinates.
diff --git a/crates/ui-egui/src/panels/right.rs b/crates/ui-egui/src/panels/right.rs
index ff6bc4013..1c5a04ad9 100644
--- a/crates/ui-egui/src/panels/right.rs
+++ b/crates/ui-egui/src/panels/right.rs
@@ -14,10 +14,7 @@ use crate::widgets::{divider, register, slider, text_button};
pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
let t = Tokens::get(ui.ctx());
let frame = egui::Frame::NONE.fill(t.chrome).stroke(egui::Stroke::new(1.0, t.divider));
- // the presets column and the left sidebar are laid out after this panel: leave them their room
- let reserve = if app.ui.presets { t.panel_w } else { 0.0 } + if app.ui.left_panel { crate::state::LEFT_WIDTH.min } else { 0.0 };
- let width = app.ui.right_width;
- let resized = super::resizable_side(ui, false, "right_panel", frame, width, crate::state::RIGHT_WIDTH, reserve, |ui| {
+ frame.show(ui, |ui| {
let Some(id) = app.session.active() else {
// the Keyword List is the library's, not a photo's: it is there without a selection
if app.ui.right == RightPanel::Keywords {
@@ -52,9 +49,6 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
}
});
});
- if let Some(w) = resized {
- app.ui.right_width = w;
- }
}
pub fn header(ui: &mut egui::Ui, title: &str) {
diff --git a/crates/ui-egui/src/shortcuts.rs b/crates/ui-egui/src/shortcuts.rs
index e3af3c66a..7771ffb58 100644
--- a/crates/ui-egui/src/shortcuts.rs
+++ b/crates/ui-egui/src/shortcuts.rs
@@ -192,7 +192,7 @@ pub fn format(m: Modifiers, k: Key) -> Option {
/// Secondary key bindings for commands that already exist: `(shortcut, command id, params JSON)`.
/// They complement the primary shortcut declared on the command (Lightroom-desktop keys that our
-/// primary keymap assigns elsewhere, see docs/parity.md → Shortcuts). Shown in Help → Keyboard Shortcuts.
+/// primary keymap assigns elsewhere, see docs/ui-parity.md → Shortcuts). Shown in Help → Keyboard Shortcuts.
pub const ALIASES: &[(&str, &str, &str)] = &[
("Cmd+D", "library.selectNone", "{}"),
("Shift+E", "dialog.export", "{}"),
@@ -300,12 +300,33 @@ fn grid_bracket_command(keymap: &Keymap, grid: bool, id: &'static str, shortcut:
if keymap.iter().any(|(other, sc)| other != id && find_bindable(other).is_some() && parse(sc) == Some(shortcut)) { None } else { Some(command) }
}
+/// Content widgets keep keyboard ownership, including text fields and arrow-driven sliders.
+/// Dock chrome owns activation, traversal, resizing and cancellation; its other keys reach commands.
+fn keyboard_owned(app: &LightcraftApp, ctx: &egui::Context) -> bool {
+ if !ctx.egui_wants_keyboard_input() {
+ return false;
+ }
+ let chrome = app
+ .ui
+ .docking
+ .as_ref()
+ .is_some_and(|workspace| craft_ui::docking::DockArea::new(egui::Id::new("lightcraft-docking")).chrome_has_focus(ctx, &workspace.layout));
+ if !chrome {
+ return true;
+ }
+ ctx.input(|input| {
+ [Key::Enter, Key::Space, Key::Tab, Key::Escape, Key::ArrowLeft, Key::ArrowRight, Key::ArrowUp, Key::ArrowDown]
+ .iter()
+ .any(|key| input.key_pressed(*key))
+ })
+}
+
pub fn handle(app: &mut LightcraftApp, ctx: &egui::Context) {
if !matches!(app.ui.dialog, Some(crate::state::Dialog::Shortcuts)) {
app.recording_shortcut = None;
}
// don't steal keys from text fields or from the keymap editor recording a shortcut
- if ctx.egui_wants_keyboard_input() || app.recording_shortcut.is_some() || app.ui.name_edit.is_some() {
+ if keyboard_owned(app, ctx) || app.recording_shortcut.is_some() || app.ui.name_edit.is_some() {
return;
}
let mut fire: Vec = Vec::new();
diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs
index b178a167c..6a50e2baf 100644
--- a/crates/ui-egui/src/state.rs
+++ b/crates/ui-egui/src/state.rs
@@ -280,6 +280,8 @@ pub enum KeywordingView {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct UiState {
+ /// Shared panel arrangement; absent in legacy settings until their first layout.
+ pub docking: Option,
#[serde(default = "crate::i18n::default_language")]
pub language: crate::i18n::Locale,
/// The Build Previews run last announced (its identity, finished?).
@@ -780,6 +782,7 @@ pub struct SyncCounts {
impl Default for UiState {
fn default() -> Self {
UiState {
+ docking: None,
language: crate::i18n::default_language(),
preview_build_seen: None,
unsaved_seen: false,
diff --git a/crates/ui-egui/src/tests_docking_acceptance.rs b/crates/ui-egui/src/tests_docking_acceptance.rs
new file mode 100644
index 000000000..0e3c499a7
--- /dev/null
+++ b/crates/ui-egui/src/tests_docking_acceptance.rs
@@ -0,0 +1,334 @@
+//! Dock changes exercise the same real catalog and command handlers as the application.
+use crate::{LightcraftApp, Services, docking::Panel, headless::Headless, state::RightPanel};
+use egui::{Event, Modifiers, PointerButton};
+use serde_json::json;
+
+fn app() -> Headless {
+ let mut app = LightcraftApp::new(lightcraft_engine::Session::with_demo(), Services { png: None, ..Default::default() });
+ app.ui.left_panel = true;
+ let mut h = Headless::new(app, [1400.0, 1000.0], 1.0);
+ for _ in 0..5 {
+ h.step();
+ }
+ h
+}
+fn dock(h: &mut Headless, p: serde_json::Value) {
+ h.app.run("ui.dock", p).unwrap();
+ for _ in 0..4 {
+ h.step();
+ }
+}
+fn rect(h: &Headless, id: &str) -> egui::Rect {
+ h.app.widgets.iter().find(|(key, _)| key == id).unwrap().1
+}
+fn release(h: &mut Headless, pos: egui::Pos2) {
+ h.app.synthetic.push(Event::PointerButton { pos, button: PointerButton::Primary, pressed: true, modifiers: Modifiers::NONE });
+ h.step();
+ h.app.synthetic.push(Event::PointerButton { pos, button: PointerButton::Primary, pressed: false, modifiers: Modifiers::NONE });
+ h.step();
+}
+
+#[test]
+fn inactive_same_mode_commands_reveal_without_closing_or_editing() {
+ let mut h = app();
+ dock(&mut h, json!({"operation":"move","panel":"tools","target":"library","zone":"center"}));
+ for command in ["panel.crop", "panel.crop", "tool.brush", "section.light", "panel.profiles"] {
+ dock(&mut h, json!({"operation":"activate","panel":"library"}));
+ let undo = h.app.session.undo.len();
+ let photos = serde_json::to_value(h.app.session.catalog.photos().map(|photo| photo.as_ref()).collect::>()).unwrap();
+ h.app.run(command, json!({})).unwrap();
+ assert!(h.app.ui.docking.as_ref().unwrap().exposed(Panel::Tools), "{command}");
+ assert_ne!(h.app.ui.right, RightPanel::None);
+ assert_eq!(h.app.session.undo.len(), undo);
+ assert_eq!(serde_json::to_value(h.app.session.catalog.photos().map(|photo| photo.as_ref()).collect::>()).unwrap(), photos);
+ h.step();
+ }
+ h.app.ui.presets = true;
+ h.step();
+ dock(&mut h, json!({"operation":"move","panel":"presets","target":"library","zone":"center"}));
+ dock(&mut h, json!({"operation":"activate","panel":"library"}));
+ h.app.run("panel.presets", json!({})).unwrap();
+ assert!(h.app.ui.docking.as_ref().unwrap().exposed(Panel::Presets));
+ dock(&mut h, json!({"operation":"activate","panel":"library"}));
+ dock(&mut h, json!({"operation":"open","panel":"presets"}));
+ assert!(h.app.ui.docking.as_ref().unwrap().exposed(Panel::Presets));
+}
+
+#[test]
+fn crop_canvas_double_click_reveals_edit_in_actual_custom_workspace() {
+ let mut h = app();
+ h.app.ui.view = crate::state::ViewMode::Detail;
+ h.app.run("panel.crop", json!({})).unwrap();
+ assert!(h.settle(std::time::Duration::from_secs(120)), "the demo photo settles before pointer input");
+ h.app.ui.presets = true;
+ h.step();
+ dock(&mut h, json!({"operation":"move","panel":"tools","target":"presets","zone":"center"}));
+ dock(&mut h, json!({"operation":"activate","panel":"presets"}));
+ assert_eq!(h.app.ui.right, RightPanel::Crop);
+ assert!(!h.app.ui.docking.as_ref().unwrap().exposed(Panel::Tools));
+ let library = h.app.ui.docking.as_ref().unwrap().layout.location(&Panel::Library).unwrap();
+ let id = h.app.session.active().unwrap();
+ let photo = serde_json::to_value(h.app.session.catalog.photo(id).unwrap().as_ref()).unwrap();
+ let undo = h.app.session.undo.len();
+ let center = h.app.image_rect.expect("crop photo is rendered").center();
+ release(&mut h, center);
+ release(&mut h, center);
+ for _ in 0..3 {
+ h.step();
+ }
+ assert_eq!(h.app.ui.right, RightPanel::Edit, "the real Canvas double-click completes Crop");
+ let workspace = h.app.ui.docking.as_ref().unwrap();
+ assert!(workspace.exposed(Panel::Tools), "tool.done reveals Tools after the Canvas callback");
+ assert_eq!(workspace.layout.location(&Panel::Tools).unwrap().anchor, Some(Panel::Presets));
+ assert_eq!(workspace.layout.location(&Panel::Library).unwrap(), library);
+ assert_eq!(h.app.session.active(), Some(id));
+ assert_eq!(h.app.session.undo.len(), undo);
+ assert_eq!(serde_json::to_value(h.app.session.catalog.photo(id).unwrap().as_ref()).unwrap(), photo);
+}
+
+#[test]
+fn floating_panel_blocks_photo_album_drop_then_uncovered_target_adds_exact_selection() {
+ let mut h = app();
+ let album = h.app.run("album.create", json!({"name":"A Dock Target", "addSelected":false})).unwrap()["id"].as_u64().unwrap();
+ h.step();
+ h.step();
+ let row = rect(&h, &format!("source:album:{album}"));
+ let ids: Vec<_> = h.app.session.visible_cloned().iter().take(2).map(|id| id.0).collect();
+ dock(&mut h, json!({"operation":"float","panel":"tools","rect":[row.left()-10.0,row.top()-30.0,330,500]}));
+ let undo = h.app.session.undo.len();
+ h.app.ui.dragging_photos = Some(ids.clone());
+ release(&mut h, row.center());
+ assert!(h.app.session.catalog.album(lightcraft_catalog::AlbumId(album)).unwrap().photos.is_empty());
+ assert_eq!(h.app.session.undo.len(), undo);
+ dock(&mut h, json!({"operation":"moveFloating","panel":"tools","rect":[800,100,330,500]}));
+ h.app.ui.dragging_photos = Some(ids.clone());
+ release(&mut h, row.center());
+ assert_eq!(
+ h.app.session.catalog.album(lightcraft_catalog::AlbumId(album)).unwrap().photos,
+ ids.into_iter().map(lightcraft_catalog::PhotoId).collect::>()
+ );
+ assert_eq!(h.app.session.undo.len(), undo + 1);
+}
+
+#[test]
+fn preset_hover_survives_float_movement_without_mutating_photo() {
+ let mut h = app();
+ h.app.run("panel.presets", json!({})).unwrap();
+ dock(&mut h, json!({"operation":"float","panel":"presets","rect":[500,100,300,700]}));
+ let undo = h.app.session.undo.len();
+ let photos = serde_json::to_value(h.app.session.catalog.photos().map(|photo| photo.as_ref()).collect::>()).unwrap();
+ for x in [500, 650] {
+ dock(&mut h, json!({"operation":"moveFloating","panel":"presets","rect":[x,100,300,700]}));
+ let row = h.app.widgets.iter().find(|(id, _)| id.starts_with("preset:")).unwrap().1;
+ h.app.synthetic.push(Event::PointerMoved(row.center()));
+ h.step();
+ h.step();
+ assert!(h.app.hover_preview.is_some());
+ assert_eq!(h.app.session.undo.len(), undo);
+ assert_eq!(serde_json::to_value(h.app.session.catalog.photos().map(|photo| photo.as_ref()).collect::>()).unwrap(), photos);
+ }
+ h.app.synthetic.push(Event::PointerMoved(egui::pos2(5.0, 5.0)));
+ h.step();
+ h.step();
+ assert!(h.app.hover_preview.is_none());
+}
+
+fn key(h: &mut Headless, key: egui::Key) {
+ h.app.synthetic.push(Event::Key { key, physical_key: Some(key), pressed: true, repeat: false, modifiers: Modifiers::NONE });
+ h.step();
+ h.app.synthetic.push(Event::Key { key, physical_key: Some(key), pressed: false, repeat: false, modifiers: Modifiers::NONE });
+ h.step();
+}
+
+#[test]
+fn dock_tab_keeps_activation_keys_and_global_command_remains_reachable() {
+ let mut h = app();
+ h.app.run("app.setShortcut", json!({"id":"panel.crop","shortcut":"K"})).unwrap();
+ dock(&mut h, json!({"operation":"move","panel":"tools","target":"library","zone":"center"}));
+ h.app.run("panel.crop", json!({})).unwrap();
+ h.step();
+ // The test chooses the renderer's actual registered tab, rather than an arbitrary focus owner.
+ let id = egui::Id::new("lightcraft-docking").with(("tab", Panel::Tools));
+ h.view.ctx.memory_mut(|memory| memory.request_focus(id));
+ h.step();
+ key(&mut h, egui::Key::Enter);
+ assert_eq!(h.app.ui.right, RightPanel::Crop, "Enter activates the tab without tool.done");
+ key(&mut h, egui::Key::K);
+ assert_eq!(h.app.ui.right, RightPanel::None, "the global crop toggle is reachable from tab focus");
+}
+
+#[test]
+fn content_text_edit_retains_command_keys_and_typed_text() {
+ let mut h = app();
+ h.app.run("app.setShortcut", json!({"id":"panel.crop","shortcut":"K"})).unwrap();
+ h.app.run("panel.keywords", json!({})).unwrap();
+ h.step();
+ h.step();
+ let result = h.request("ui.clickWidget", json!({"id":"field:keywordFilter"}), std::time::Duration::from_secs(20));
+ assert_eq!(result["ok"], true, "{result}");
+ key(&mut h, egui::Key::K);
+ h.app.synthetic.push(Event::Text("dock search".into()));
+ h.step();
+ assert_eq!(h.app.ui.right, RightPanel::Keywords);
+ assert_eq!(h.view.ctx.data(|data| data.get_temp::(egui::Id::new("keyword-list-filter"))).as_deref(), Some("dock search"));
+}
+
+#[test]
+fn floating_presets_block_keyword_photo_drop_then_uncovered_target_changes_exact_metadata() {
+ let mut h = app();
+ h.app.run("panel.keywords", json!({})).unwrap();
+ h.app.run("keyword.create", json!({"name":"A Dock Keyword"})).unwrap();
+ h.step();
+ h.step();
+ let row = rect(&h, "keywordRow:A Dock Keyword");
+ h.app.ui.presets = true;
+ h.step();
+ let ids: Vec<_> = h.app.session.visible_cloned().iter().take(2).map(|id| id.0).collect();
+ dock(&mut h, json!({"operation":"float","panel":"presets","rect":[row.left()-10.0,row.top()-30.0,300,500]}));
+ let undo = h.app.session.undo.len();
+ let before: Vec<_> = ids.iter().map(|id| h.app.session.catalog.photo(lightcraft_catalog::PhotoId(*id)).unwrap().meta.keywords.clone()).collect();
+ h.app.ui.dragging_photos = Some(ids.clone());
+ release(&mut h, row.center());
+ for (id, expected) in ids.iter().zip(&before) {
+ assert_eq!(&h.app.session.catalog.photo(lightcraft_catalog::PhotoId(*id)).unwrap().meta.keywords, expected);
+ }
+ assert_eq!(h.app.session.undo.len(), undo);
+ dock(&mut h, json!({"operation":"moveFloating","panel":"presets","rect":[400,100,300,500]}));
+ h.app.ui.dragging_photos = Some(ids.clone());
+ release(&mut h, row.center());
+ for (id, mut expected) in ids.iter().zip(before) {
+ expected.push("A Dock Keyword".into());
+ let mut actual = h.app.session.catalog.photo(lightcraft_catalog::PhotoId(*id)).unwrap().meta.keywords.clone();
+ expected.sort();
+ actual.sort();
+ assert_eq!(actual, expected);
+ }
+ assert_eq!(h.app.session.undo.len(), undo + 1);
+}
+
+#[test]
+fn covered_folder_rejects_album_move_and_uncovered_folder_nests_exact_album() {
+ let mut h = app();
+ let folder = h.app.run("album.create", json!({"name":"A Dock Folder","folder":true})).unwrap()["id"].as_u64().unwrap();
+ let child = h.app.run("album.create", json!({"name":"B Dock Album","addSelected":false})).unwrap()["id"].as_u64().unwrap();
+ h.step();
+ h.step();
+ let row = rect(&h, &format!("source:folder:{folder}"));
+ dock(&mut h, json!({"operation":"float","panel":"tools","rect":[row.left()-10.0,row.top()-30.0,330,500]}));
+ let undo = h.app.session.undo.len();
+ h.app.ui.dragging_album = Some(child);
+ release(&mut h, row.center());
+ assert_eq!(h.app.session.catalog.album(lightcraft_catalog::AlbumId(child)).unwrap().parent, None);
+ assert_eq!(h.app.session.undo.len(), undo);
+ dock(&mut h, json!({"operation":"moveFloating","panel":"tools","rect":[800,100,330,500]}));
+ h.app.ui.dragging_album = Some(child);
+ release(&mut h, row.center());
+ assert_eq!(h.app.session.catalog.album(lightcraft_catalog::AlbumId(child)).unwrap().parent, Some(lightcraft_catalog::AlbumId(folder)));
+ assert_eq!(h.app.session.undo.len(), undo + 1);
+}
+
+#[test]
+fn covered_keyword_rejects_hierarchy_move_and_uncovered_target_moves_exact_keyword() {
+ let mut h = app();
+ h.app.run("panel.keywords", json!({})).unwrap();
+ h.app.run("keyword.create", json!({"name":"A Dock Parent"})).unwrap();
+ h.app.run("keyword.create", json!({"name":"B Dock Child"})).unwrap();
+ h.app.ui.presets = true;
+ h.step();
+ h.step();
+ let row = rect(&h, "keywordRow:A Dock Parent");
+ dock(&mut h, json!({"operation":"float","panel":"presets","rect":[row.left()-10.0,row.top()-30.0,300,500]}));
+ let undo = h.app.session.undo.len();
+ h.app.ui.dragging_keyword = Some("B Dock Child".into());
+ release(&mut h, row.center());
+ assert!(h.app.session.catalog.has_keyword("B Dock Child"));
+ assert!(!h.app.session.catalog.has_keyword("A Dock Parent|B Dock Child"));
+ assert_eq!(h.app.session.undo.len(), undo);
+ dock(&mut h, json!({"operation":"moveFloating","panel":"presets","rect":[400,100,300,500]}));
+ h.app.ui.dragging_keyword = Some("B Dock Child".into());
+ release(&mut h, row.center());
+ assert!(!h.app.session.catalog.has_keyword("B Dock Child"));
+ assert!(h.app.session.catalog.has_keyword("A Dock Parent|B Dock Child"));
+ assert_eq!(h.app.session.undo.len(), undo + 1);
+}
+
+#[test]
+fn reset_during_held_divider_drag_keeps_replacement_layout_on_later_movement() {
+ let mut h = app();
+ let divider = egui::Id::new("lightcraft-docking").with(("split", Vec::::new()));
+ let from = h.view.ctx.read_response(divider).expect("the custom workspace root divider is rendered").rect.center();
+ let before = h.app.ui.docking.as_ref().unwrap().layout.clone();
+ h.app.synthetic.push(Event::PointerButton { pos: from, button: PointerButton::Primary, pressed: true, modifiers: Modifiers::NONE });
+ h.step();
+ h.app.synthetic.push(Event::PointerMoved(from + egui::vec2(40.0, 0.0)));
+ h.step();
+ assert_ne!(h.app.ui.docking.as_ref().unwrap().layout, before, "fixture performs a real divider resize");
+ assert_eq!(h.view.ctx.dragged_id(), Some(divider));
+ let resized = h.app.ui.docking.as_ref().unwrap().layout.clone();
+ h.app.synthetic.push(Event::PointerMoved(from + egui::vec2(55.0, 0.0)));
+ h.step();
+ assert_ne!(h.app.ui.docking.as_ref().unwrap().layout, resized, "renderer-generated actions preserve continuous dragging");
+ let undo = h.app.session.undo.len();
+ h.app.run("ui.dock.reset", json!({})).unwrap();
+ let reset = serde_json::to_value(&h.app.ui.docking).unwrap();
+ h.app.synthetic.push(Event::PointerMoved(from + egui::vec2(90.0, 0.0)));
+ h.step();
+ assert_eq!(serde_json::to_value(&h.app.ui.docking).unwrap(), reset, "a held old divider cannot resize the replacement tree");
+ h.app.synthetic.push(Event::PointerButton {
+ pos: from + egui::vec2(90.0, 0.0),
+ button: PointerButton::Primary,
+ pressed: false,
+ modifiers: Modifiers::NONE,
+ });
+ h.step();
+ assert_eq!(serde_json::to_value(&h.app.ui.docking).unwrap(), reset);
+ assert_eq!(h.app.session.undo.len(), undo);
+}
+
+fn held_divider_replacement_route(route: &str) {
+ let mut h = app();
+ let divider = egui::Id::new("lightcraft-docking").with(("split", Vec::::new()));
+ let from = h.view.ctx.read_response(divider).unwrap().rect.center();
+ let photos = serde_json::to_value(h.app.session.catalog.photos().map(|photo| photo.as_ref()).collect::>()).unwrap();
+ h.app.synthetic.push(Event::PointerButton { pos: from, button: PointerButton::Primary, pressed: true, modifiers: Modifiers::NONE });
+ h.step();
+ h.app.synthetic.push(Event::PointerMoved(from + egui::vec2(40.0, 0.0)));
+ h.step();
+ assert_eq!(h.view.ctx.dragged_id(), Some(divider));
+ let undo = h.app.session.undo.len();
+ match route {
+ "ui.set" => {
+ let response = h.request("ui.set", json!({"rightWidth":410.0}), std::time::Duration::from_secs(20));
+ assert_eq!(response["ok"], true, "{response}");
+ assert_eq!(h.app.ui.right_width, 410.0);
+ }
+ "panel.close" => {
+ h.app.run("panel.close", json!({})).unwrap();
+ h.step();
+ }
+ "public-action" => {
+ let action = craft_ui::docking::Action::Float { panel: Panel::Tools, rect: [800.0, 100.0, 330.0, 500.0] };
+ h.app.run("ui.dock", json!({"action":action})).unwrap();
+ h.step();
+ }
+ _ => panic!("unknown test route"),
+ }
+ let replacement = serde_json::to_value(&h.app.ui.docking).unwrap();
+ h.app.synthetic.push(Event::PointerMoved(from + egui::vec2(90.0, 0.0)));
+ h.step();
+ assert_eq!(serde_json::to_value(&h.app.ui.docking).unwrap(), replacement, "{route} cancels the previous divider gesture");
+ assert_eq!(h.app.session.undo.len(), undo);
+ assert_eq!(serde_json::to_value(h.app.session.catalog.photos().map(|photo| photo.as_ref()).collect::>()).unwrap(), photos);
+}
+#[test]
+fn actual_ui_set_width_replacement_cancels_held_divider() {
+ held_divider_replacement_route("ui.set");
+}
+#[test]
+fn panel_close_sync_replacement_cancels_held_divider() {
+ held_divider_replacement_route("panel.close");
+}
+#[test]
+fn externally_serialized_action_replacement_cancels_held_divider() {
+ held_divider_replacement_route("public-action");
+}
diff --git a/crates/ui-egui/src/tests_masking.rs b/crates/ui-egui/src/tests_masking.rs
index c748ecf14..b95fa1ab2 100644
--- a/crates/ui-egui/src/tests_masking.rs
+++ b/crates/ui-egui/src/tests_masking.rs
@@ -143,6 +143,16 @@ fn mask_adjustments_hide_overlay_without_disabling_the_mask() {
// Changing the shape needs the overlay; changing the image needs an unobscured preview.
exec(&mut h, "mask.refine", json!({"value": 10.0}));
assert!(h.app.ui.mask_overlay);
+ // The dock header pushes this adjustment below the initial viewport. Exercise the
+ // actual visible slider rather than sending a synthetic click outside its clipped body.
+ let panel = h.app.widgets.iter().find(|(id, _)| id == "panel:right_panel").unwrap().1;
+ let slider = h.app.widgets.iter().find(|(id, _)| id == "slider:exposure").unwrap().1;
+ assert!(!panel.contains_rect(slider), "exposure initially needs scrolling: {slider:?} in {panel:?}");
+ h.request("ui.move", json!({"x": panel.center().x, "y": panel.center().y}), T);
+ h.request("ui.scroll", json!({"dy": -160.0}), T);
+ h.settle(SETTLE);
+ let slider = h.app.widgets.iter().find(|(id, _)| id == "slider:exposure").unwrap().1;
+ assert!(panel.contains_rect(slider), "exposure is visible before clicking: {slider:?} in {panel:?}");
let r = h.request("ui.clickWidget", json!({"id": "slider:exposure", "fx": 0.7}), T);
assert_eq!(r["ok"], true, "{r}");
assert!(!h.app.ui.mask_overlay, "the exposure slider hides the overlay after release");
diff --git a/crates/ui-egui/src/text_field.rs b/crates/ui-egui/src/text_field.rs
index c620ee0a4..d9e5dfcf3 100644
--- a/crates/ui-egui/src/text_field.rs
+++ b/crates/ui-egui/src/text_field.rs
@@ -194,7 +194,9 @@ impl<'a> TextField<'a> {
let right = ui.input(|i| i.pointer.button_down(egui::PointerButton::Secondary) || i.pointer.button_released(egui::PointerButton::Secondary));
let selection = egui::text_edit::TextEditState::load(ui.ctx(), id).and_then(|s| s.cursor.char_range());
let mut response = ui.add(edit);
- if right && response.contains_pointer() {
+ // The menu may take focus on a primary-button release. egui collapses a
+ // TextEdit selection when focus leaves; retain the range our own menu acts on.
+ if right && response.contains_pointer() || memo.menu_open && !ui.memory(|m| m.has_focus(id)) {
let mut state = egui::text_edit::TextEditState::load(ui.ctx(), id).unwrap_or_default();
state.cursor.set_char_range(selection);
state.store(ui.ctx(), id);
@@ -514,6 +516,20 @@ mod tests {
assert_eq!(rig.text, "Kyoto");
}
+ #[test]
+ fn clicking_back_into_field_dismisses_its_menu_and_places_a_new_caret() {
+ let mut rig = Rig::new("Lisbon");
+ rig.select_all_by_keys();
+ rig.click(FIELD, PointerButton::Secondary);
+ let rect = rig.rect(FIELD).unwrap();
+ rig.click_at(egui::pos2(rect.left() + 2.0, rect.center().y), PointerButton::Primary);
+ let range = egui::text_edit::TextEditState::load(&rig.view.ctx, id(FIELD)).unwrap().cursor.char_range().unwrap();
+ assert!(range.is_empty(), "a click back in the field places a caret: {range:?}");
+ rig.type_text("X");
+ assert_eq!(rig.text, "XLisbon");
+ assert!(rig.endings.is_empty(), "dismissing into the field keeps the edit open");
+ }
+
/// With nothing selected, Cut and Copy are greyed out: clicking one does nothing, as in a native
/// menu, and neither closes the menu nor ends the edit.
#[test]
diff --git a/crates/ui-egui/src/theme.rs b/crates/ui-egui/src/theme.rs
index 0d7e1570c..620871c36 100644
--- a/crates/ui-egui/src/theme.rs
+++ b/crates/ui-egui/src/theme.rs
@@ -237,18 +237,11 @@ pub fn apply(ctx: &egui::Context) {
v.popup_shadow = egui::epaint::Shadow { offset: [0, 4], blur: 16, spread: 0, color: Color32::from_black_alpha(140) };
v.window_shadow = egui::epaint::Shadow { offset: [0, 8], blur: 30, spread: 0, color: Color32::from_black_alpha(160) };
let w = &mut v.widgets;
- for (wv, fill) in [
- (&mut w.noninteractive, t.chrome),
- (&mut w.inactive, t.button),
- (&mut w.hovered, t.hover),
- (&mut w.active, t.pressed),
- (&mut w.open, t.hover),
- ] {
- wv.bg_fill = fill;
- wv.weak_bg_fill = fill;
- wv.corner_radius = CornerRadius::same(4);
- wv.fg_stroke = Stroke::new(1.0, t.text_label);
- }
+ craft_ui::theme::WidgetPalette { noninteractive: t.chrome, inactive: t.button, hovered: t.hover, active: t.pressed, open: t.hover }.apply_to(
+ w,
+ CornerRadius::same(4),
+ Stroke::new(1.0, t.text_label),
+ );
w.noninteractive.bg_stroke = Stroke::new(1.0, t.divider);
w.inactive.bg_stroke = Stroke::new(1.0, t.button_border);
ctx.set_visuals(v);
diff --git a/crates/ui-egui/tests/docking_workflows.rs b/crates/ui-egui/tests/docking_workflows.rs
new file mode 100644
index 000000000..aa80e8301
--- /dev/null
+++ b/crates/ui-egui/tests/docking_workflows.rs
@@ -0,0 +1,126 @@
+//! Real library/tool panel moves preserve the selected tool and saved arrangement.
+use egui::{Event, Modifiers, PointerButton, vec2};
+use egui_kittest::{Harness, kittest::Queryable};
+use lightcraft_ui_egui::{LightcraftApp, Services, UiState, docking::Panel, state::RightPanel};
+use serde_json::json;
+use std::time::{Duration, Instant};
+
+fn harness(state: Option, width: f32, scale: f32) -> Harness<'static, LightcraftApp> {
+ let mut app = LightcraftApp::new(lightcraft_engine::Session::with_demo(), Services { png: None, ..Default::default() });
+ if let Some(state) = state {
+ app.ui = state;
+ } else {
+ app.ui.left_panel = true;
+ app.ui.right = RightPanel::Edit;
+ }
+ let mut h = Harness::builder().with_size(vec2(width, 900.0)).with_pixels_per_point(scale).build_ui_state(
+ |ui, app: &mut LightcraftApp| {
+ app.logic(&ui.ctx().clone());
+ app.ui(ui);
+ },
+ app,
+ );
+ h.run_steps(8);
+ h
+}
+
+fn drag(h: &mut Harness<'_, LightcraftApp>, from: egui::Pos2, to: egui::Pos2) {
+ h.event(Event::PointerMoved(from));
+ h.run_steps(2);
+ h.event(Event::PointerButton { pos: from, button: PointerButton::Primary, pressed: true, modifiers: Modifiers::NONE });
+ h.run_steps(2);
+ for step in 1..=8 {
+ h.event(Event::PointerMoved(from.lerp(to, step as f32 / 8.0)));
+ h.run_steps(1);
+ }
+ h.event(Event::PointerButton { pos: to, button: PointerButton::Primary, pressed: false, modifiers: Modifiers::NONE });
+ h.run_steps(4);
+}
+
+fn settle_photo(h: &mut Harness<'_, LightcraftApp>) {
+ let started = Instant::now();
+ let mut quiet = 0;
+ while quiet < 4 {
+ h.run_steps(2);
+ if h.state().renderer.in_flight() == 0 && h.state().loupe_shown.is_some_and(|(_, source)| source == "render") {
+ quiet += 1;
+ } else {
+ quiet = 0;
+ }
+ assert!(started.elapsed() < Duration::from_secs(30), "the synthetic photo and panel thumbnails must finish rendering before capture");
+ std::thread::sleep(Duration::from_millis(5));
+ }
+}
+
+#[test]
+fn real_drag_float_close_reopen_and_state_reload_keep_photo_tool_mode() {
+ let mut h = harness(None, 1400.0, 1.0);
+ let source = h.get_by_role_and_label(egui::accesskit::Role::Tab, "Tools").rect().center();
+ let target = h.get_by_role_and_label(egui::accesskit::Role::Tab, "Library").rect().center();
+ drag(&mut h, source, target);
+ assert_eq!(h.state().ui.docking.as_ref().unwrap().layout.location(&Panel::Tools).unwrap().anchor, Some(Panel::Library));
+ h.get_by_role_and_label(egui::accesskit::Role::Tab, "Tools").click_button(PointerButton::Secondary);
+ h.run_steps(3);
+ h.get_by_label("Float panel").click();
+ h.run_steps(5);
+ let rect = h.state().ui.docking.as_ref().unwrap().layout.floating.iter().find(|g| g.panels.contains(&Panel::Tools)).unwrap().rect;
+ h.get_by_role_and_label(egui::accesskit::Role::Tab, "Tools").click_button(PointerButton::Secondary);
+ h.run_steps(3);
+ h.get_by_label("Close panel").click();
+ h.run_steps(4);
+ assert_eq!(h.state().ui.right, RightPanel::None);
+ h.state_mut().ui.right = RightPanel::Crop;
+ h.run_steps(4);
+ assert_eq!(h.state().ui.docking.as_ref().unwrap().layout.floating.iter().find(|g| g.panels.contains(&Panel::Tools)).unwrap().rect, rect);
+ let saved = serde_json::to_string(&h.state().ui).unwrap();
+ let expected = serde_json::to_value(&h.state().ui.docking).unwrap();
+ let mut reloaded = harness(Some(serde_json::from_str(&saved).unwrap()), 1400.0, 1.0);
+ assert_eq!(serde_json::to_value(&reloaded.state().ui.docking).unwrap(), expected);
+ assert_eq!(reloaded.state().ui.right, RightPanel::Crop);
+ reloaded.state_mut().run("ui.dock", json!({"operation":"move","panel":"tools","target":"library","zone":"center"})).unwrap();
+ reloaded.run_steps(4);
+ assert_eq!(reloaded.state().ui.right, RightPanel::Crop);
+ reloaded.state_mut().run("ui.dock", json!({"operation":"close","panel":"library"})).unwrap();
+ reloaded.run_steps(3);
+ assert!(!reloaded.state().ui.left_panel);
+ assert_eq!(reloaded.state().ui.right, RightPanel::Crop);
+}
+
+#[test]
+fn invalid_docking_requests_preserve_layout_and_selected_tool() {
+ let mut h = harness(None, 1400.0, 1.0);
+ let before = serde_json::to_value(&h.state().ui.docking).unwrap();
+ for params in [
+ json!({"operation":"close","panel":"canvas"}),
+ json!({"operation":"move","panel":"tools","target":"canvas","zone":"center"}),
+ json!({"operation":"float","panel":"tools","rect":[0,0,-1,100]}),
+ json!({"operation":"move","panel":"unknown","target":"library","zone":"center"}),
+ ] {
+ assert!(h.state_mut().run("ui.dock", params).is_err());
+ assert_eq!(serde_json::to_value(&h.state().ui.docking).unwrap(), before);
+ assert_eq!(h.state().ui.right, RightPanel::Edit);
+ }
+}
+
+#[test]
+fn docking_renders_real_photo_panels_default_floating_and_redocked() {
+ for width in [900.0, 1400.0] {
+ for scale in [1.0, 2.0] {
+ let mut h = harness(None, width, scale);
+ for state in ["default", "floating", "redocked"] {
+ if state == "floating" {
+ h.state_mut().run("ui.dock", json!({"operation":"float","panel":"tools","rect":[120,90,320,600]})).unwrap();
+ } else if state == "redocked" {
+ h.state_mut().run("ui.dock", json!({"operation":"move","panel":"tools","target":"library","zone":"center"})).unwrap();
+ }
+ h.run_steps(8);
+ settle_photo(&mut h);
+ h.state().ui.docking.as_ref().unwrap().validate().unwrap();
+ if let Ok(directory) = std::env::var("CRAFT_UI_DOCKING_DIR") {
+ std::fs::create_dir_all(&directory).unwrap();
+ h.render().unwrap().save(format!("{directory}/light-{width}-{scale}-{state}.png")).unwrap();
+ }
+ }
+ }
+ }
+}
diff --git a/docs/architecture.md b/docs/architecture.md
new file mode 100644
index 000000000..c50066a10
--- /dev/null
+++ b/docs/architecture.md
@@ -0,0 +1,70 @@
+# Architecture
+
+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (created from the code; the full design notes stay in the local `plan/architecture.md`) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
+
+How LightCraft is built today. The longer design document (`plan/architecture.md`) is local-only
+(`plan/` is gitignored); this page is the committed, current summary. Sizes are Rust lines counted
+on 2026-10-10 (≈ 202,000 tracked lines in 505 files: 24 library crates, 3 apps and `xtask`).
+
+## Layers
+
+`cargo xtask layers` (part of `cargo xtask ci`, `xtask/src/layers.rs`) enforces that nothing below
+L5 depends on egui, eframe, winit or rfd, and that each crate only uses lower layers.
+
+| Layer | Crates (lines) | Role |
+|---|---|---|
+| L0 | `geom` (1.4k), `color` (0.9k), `raster` (0.8k), `tiff` (2.0k), `fetch` (1.4k), `sysmem` (0.1k) | Matrices and warps, colour spaces and transfer functions, float image buffers, TIFF/IFD read and write, pinned HTTPS model downloads, the one `unsafe` FFI call (`malloc_zone_pressure_relief`) |
+| L1 | `raw` (22.3k), `codecs` (7.3k), `meta` (4.3k), `develop` (2.5k), `denoise-core` (2.2k), `denoise` (2.7k), `faces` (5.3k), `scenes` (1.0k), `heif` (2.1k, optional) | Raw decoders and demosaic, DNG colour model; standard codecs and encoders; EXIF/XMP/IPTC; the non-destructive edit model and control specs; AI denoise; face detection and recognition; procedural demo photos; HEIC |
+| L2 | `pipeline` (9.8k) | The develop pipeline: scene-referred float, resolution independent; the CPU reference (oracle) for everything else |
+| L3 | `catalog` (11.0k), `gpu` (5.1k), `preview` (1.3k), `merge` (3.8k), `segment` (2.3k) | Library model and persistence; wgpu compute twin of the pipeline; thumbnail/preview cache and job pool; HDR and panorama merge; SAM 3 segmentation |
+| L4 | `engine` (53.2k) | Session, command registry (everything is a command), history, render and export jobs, import, Lightroom catalog and preset import, faces index, view-models |
+| L5 | `ui-egui` (46.9k), `mcp` (2.3k) | The Lightroom-style egui interface (swappable); the MCP server |
+| L6 | `apps/lightcraft` (2.8k), `apps/lightcraft-cli` (1.5k), `apps/lightcraft-web` (3.4k), `xtask` (2.7k) | Desktop app (eframe + wgpu, native menus, control server), CLI (render, commands, calibrate, snapshot, MCP), WASM build, tooling |
+
+## Data model
+
+- **Photo** records in `catalog` hold the original's path, content hash, metadata, ratings, flags,
+ labels, keywords, faces and the current `develop::Settings`. Albums, smart albums (rule trees),
+ stacks, virtual copies, versions and folder records live alongside.
+- **Persistence** (`crates/catalog/src/journal.rs`): an append-only journal of human-readable
+ operations plus periodic snapshots, compacted in the background; torn appends and crashes are
+ tested (`tests_torn_append.rs`, `tests_journal.rs`). A lock file guards against two writers.
+- **Edits** are a `develop::Settings` value (every slider is a control spec with id, range and
+ default), normalized to image coordinates so previews and exports match. Process versions
+ (`docs/process-versions.md`) keep old edits rendering the same after a retune.
+- **Sidecars**: XMP read and write with Adobe `crs:` interop (`docs/xmp-interop.md`); Lightroom
+ Classic `.lrcat` import through a pure-Rust SQLite reader (`docs/lightroom-catalog-import.md`).
+
+## Rendering
+
+1. **Decode** (`raw`, `codecs`): raw files to linear camera RGB (demosaic, black/white levels,
+ opcodes, highlight handling) and a camera-to-XYZ colour model; standard images to linear light
+ through their ICC profile.
+2. **Develop** (`pipeline`): white balance, profile, exposure and tone (local Laplacian
+ highlights/shadows), curves, HSL / colour mixer, point colour, colour grading, texture, clarity,
+ dehaze, detail (sharpening, NR, AI denoise result), optics (lens profile, CA, defringe,
+ vignette), geometry (crop, Upright, transforms), masks with local adjustments, heal/clone, grain,
+ effects. Stage caching and a source pyramid make slider drags cheap.
+3. **GPU** (`gpu`): the same stages in WGSL on Metal / DX12 / Vulkan, checked against the CPU path
+ (within 1/255); GPU errors fall back to the CPU (`docs/gpu-pipeline.md`).
+4. **Display and export**: display-profile conversion (`docs/display-profiles.md`); export through
+ one encoder for app, CLI, MCP and web (`lightcraft_engine::export`).
+
+## Agent control
+
+Every action is an engine command (id, label, menu path, shortcut, params, enabled, run). The UI,
+the CLI, the JSON-lines control channel (`docs/control-protocol.md`) and MCP (`docs/mcp.md`) all
+dispatch by id, and headless snapshots render the UI without a window.
+
+## Quality gates
+
+`cargo xtask ci`: fmt, clippy `-D warnings` with the never-crash lints, the HEIF build, tests,
+`parity` (every id and path cited in [parity-checklist.md](parity-checklist.md) exists), `layers`,
+`assets` (every asset attributed, no Adobe formats) and the WASM build. Other workflows build and
+test FreeBSD and Windows ARM64, lint packaging, and build releases.
+
+## Revision history
+
+| Date | Change | Summary |
+|---|---|---|
+| 2026-10-10 | major | Created from the code (crate sizes, layers, data model, rendering, control, gates) |
diff --git a/docs/control-protocol.md b/docs/control-protocol.md
index 552f76952..8abf5a1ae 100644
--- a/docs/control-protocol.md
+++ b/docs/control-protocol.md
@@ -22,6 +22,34 @@ that hit an error reply should reconnect. The port has no authentication, so onl
mode ([mcp.md](mcp.md)) is a thin layer over this channel. Implementation:
`crates/ui-egui/src/control.rs` (methods) and `apps/lightcraft/src/control_server.rs` (transport).
+## Isolated automation profiles
+
+Set `LIGHTCRAFT_CONFIG_DIR` to a fresh directory to isolate UI preferences, app logs, the emergency
+panic log, the GPU initialization marker, camera profiles and default model folders. The nonempty
+value is used as the complete settings directory. Unset retains the normal platform paths; an explicit
+empty value disables the settings directory, while the emergency panic log retains its system-temp path.
+Explicit model-directory environment variables still take precedence over their defaults.
+Also pass `--library DIR` with a separate scratch library: the configuration override does not change
+the default photo-library location. Do not use `--memory` or `LIGHTCRAFT_NO_PREFS` when testing persistence.
+
+A native acceptance run must reject the stderr message `rendering headlessly`: `ui.screenshot`
+automatically uses the CPU fallback after two seconds without a presented frame. That fallback is useful
+for headless scripts, but cannot establish native-window acceptance. Control input injects egui events;
+it does not establish operating-system input delivery or screen-reader acceptance.
+
+On macOS, the native menu owns its keyboard accelerators. `ui.key` injects egui key events and
+does not synthesize the Cocoa menu events for those shortcuts. Use `ui.menu.invoke` with the
+command ID (for example `edit.undo`, `edit.redo`, `view.filmstrip` or `view.leftPanel`) to test
+their command behavior. That API invokes the app command dispatcher directly; it does not traverse
+Cocoa menu dispatch. Native menu activation and keyboard accelerator delivery require a separate test.
+
+Before capturing a finished Detail view, poll `ui.inspect` until the expected `loupe.photo` is active,
+`loupe.source` is `render` or `cached`, `loupe.pending` is false, and the render queues are empty.
+For the loupe, `cached` is a prior view render keyed by the current photo and develop settings;
+`thumb`, `small` and `embedded` are separate fallback sources. A presented frame can otherwise
+contain an in-progress rendering placeholder. Require loaded thumbnail textures only when the
+filmstrip is visible.
+
## Methods
| Method | Params | Result |
@@ -127,3 +155,22 @@ one pixel.
renders are in flight. Each request runs frames until it is answered and its injected input
(clicks, keys, drags) has played out. Widget ids for `ui.clickWidget` come from `ui.widgets`
(e.g. `button:upright-auto`). A 1600×1000 demo snapshot takes ~1–2 s (debug build).
+
+## Panel docking
+
+Library, Tools, and Presets panels can be regrouped, split, floated within the app window, closed, and reopened. Tools keeps the current Edit/Crop/Masking mode; the photo view and filmstrip stay together. The document/session view is protected. Layouts, active tabs, panel visibility, and floating rectangles are saved with the app's existing UI preferences. These operations do not edit document contents.
+
+Panel IDs: `library`, `tools`, `presets`. The protected center is `canvas`.
+
+`ui.dock` accepts `operation` and `panel`. Operations are `open`, `close`, `activate`, `float` (with finite positive `rect: [x, y, width, height]`), or `move` (with `target` and `zone: center|left|right|top|bottom`). Center groups with the target; side zones split beside it. `ui.dock.reset` restores the default arrangement. Invalid moves return an error without changing the layout.
+
+Invoke these UI commands through `engine.execute`, for example `{"command":"ui.dock","params":{"operation":"float","panel":"tools","rect":[100,100,320,440]}}`.
+
+Advanced `ui.dock` operations use the same validated transaction as pointer gestures:
+`moveFloating` updates `rect`, `resizeSplit` accepts a boolean child `path` and
+`size` (`{"Ratio":0.5}`, `{"FixedFirst":280}`, or `{"FixedSecond":280}`),
+`setStackOpen` takes `open`, and `resizeStack` takes a point `height` or null.
+A center `move` can include `before` (panel ID or null to append) for tab order.
+The equivalent `{"action": ...}` form accepts a serialized shared docking action;
+this is also the form emitted by the renderer. Protected-canvas and app-specific panel
+rules apply equally to both forms.
diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md
new file mode 100644
index 000000000..32d307cfb
--- /dev/null
+++ b/docs/file-format-parity.md
@@ -0,0 +1,79 @@
+# File format parity
+
+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (created; formats measured from the code and the target's Info.plist) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
+
+Every format Lightroom reads or writes, with our support, fidelity and tests. Camera raw formats are
+deep enough to have their own document: [raw-parity.md](raw-parity.md).
+
+**How measured:** the target's document types come from the installed Lightroom 9.6
+`Info.plist` (`CFBundleDocumentTypes`: catalog `mcat`, `xmp`, toolkit / module / library bundles, and
+the raw-and-image list: jpg, tif, dng, heic and 27 raw extensions) plus Lightroom Classic's documented
+import and export formats. Ours come from `crates/codecs/src/sniff.rs`, `crates/codecs/src/encode.rs`,
+`crates/engine/src/export.rs`, `crates/engine/src/import.rs` and the tests named below.
+
+## Images (non-raw)
+
+| Format | Target reads | Target writes | LightCraft reads | LightCraft writes | Notes / tests |
+|---|---|---|---|---|---|
+| JPEG | ✅ | ✅ | ✅ (parallel decoder, ICC, EXIF orientation) | ✅ quality, size limit, ICC, metadata | `crates/codecs/src/jpeg.rs`, export tests |
+| TIFF | ✅ 8/16/32-bit | ✅ 8/16-bit, LZW/ZIP | ✅ 1–64-bit integer, 16–64-bit float, bilevel, CCITT fax, WhiteIsZero | ✅ 8/16-bit, 32-bit float linear, none / LZW / ZIP | `crates/tiff`, `crates/codecs/src/tiff_codec.rs` |
+| PNG | ✅ | ✅ | ✅ | ✅ | |
+| PSD / PSB | ✅ (maximized-compatibility composite) | ✅ (Classic: PSD export, edit-in round trip) | ✅ merged composite | ⬜ (external-editor round trip writes 16-bit TIFF) | `crates/codecs/src/psd.rs` |
+| HEIC / HEIF | ✅ | ⬜ (Classic) / HDR only | ✅ in every release build (`crates/heif`, heic-rs) | ⬜ | `heif` feature on by packaging scripts |
+| AVIF | ✅ | ✅ (HDR) | ⬜ recognised, not decoded (no permissive pure-Rust AV1 decoder) | ✅ 8/10-bit, PQ HDR | |
+| JPEG XL | ✅ | ✅ | ✅ (`jxl-oxide`) incl. DNG 1.7 JXL tiles | ⬜ | |
+| WebP | not in its document types | ⬜ | ✅ | ✅ | beyond target |
+| GIF, BMP | ⬜ | ⬜ | ✅ | ⬜ | beyond target |
+| DNG (from raw) | ✅ | ✅ incl. lossy, embedded original | ✅ | ✅ uncompressed or LJ92; ⬜ lossy DNG, embedded original | `crates/raw/src/dngwrite.rs` |
+| Render to DNG (15.5) | — | ✅ | — | ⬜ | new in Classic 15.5 |
+| Original + sidecar | — | ✅ | — | ✅ | |
+| HDR gain map JPEG (ISO 21496-1) | ✅ | ✅ | ✅ (gain map read) | ✅ | `crates/codecs/src/gainmap.rs` |
+| PQ / HLG HDR | ✅ | ✅ AVIF, JXL, TIFF | — | 🟡 PQ AVIF, float TIFF; no HLG | |
+| Contact sheet / print PDF | — | ✅ (Print module: PDF / JPEG / printer) | — | 🟡 paginated contact-sheet PDF (A4 / Letter, 150 dpi, sRGB) | `crates/engine/src/contact_sheet.rs` |
+| Video (MP4, MOV, AVCHD…) | ✅ playback, trim, edit | ✅ export | 🟡 catalogued (kind, duration) | ⬜ | see video gap |
+
+## Colour spaces and profiles
+
+| Feature | Target | LightCraft |
+|---|---|---|
+| Export colour spaces | sRGB, Adobe RGB, Display P3, ProPhoto, Rec.2020, custom ICC | ✅ sRGB, Display P3, Adobe RGB-compatible, ProPhoto, Rec.2020 with gamut mapping (CPU and GPU), embedded ICC; ⬜ custom ICC output profiles; AVIF stays sRGB |
+| Input ICC (JPEG / TIFF / PNG) | ✅ | ✅ matrix / TRC and LUT profiles (moxcms) |
+| Soft proofing with printer profiles | ✅ | 🟡 |
+
+## Metadata, sidecars, presets and catalogs
+
+| Format | Target | LightCraft | Notes |
+|---|---|---|---|
+| XMP sidecars (`crs:` develop settings, ratings, keywords, regions) | read / write | ✅ read / write, manual (⌘S) and automatic | [xmp-interop.md](xmp-interop.md) |
+| EXIF / IPTC / XMP embedded on export | ✅ | ✅ with metadata filters | `crates/meta` |
+| Develop presets `.xmp` | ✅ | ✅ import (mapping of supported settings) | |
+| Legacy presets `.lrtemplate` | ✅ (converted) | ✅ import | |
+| Camera / creative profiles (`.dcp`, `.xmp` profiles) | ✅ | ⬜ deliberately (Adobe profile formats are never used); `.cube` LUT profiles ✅ | |
+| Lens profiles `.lcp` | ✅ | ⬜ deliberately | |
+| Other apps' presets | — | ✅ Luminar `.lmp` / `.mplumpack`, `.zip` bundles | beyond target |
+| Lightroom Classic catalog `.lrcat` | native | 🟡 import (ratings, flags, labels, keywords, collections, virtual copies, supported develop settings; read-only pure-Rust SQLite incl. WAL) | [lightroom-catalog-import.md](lightroom-catalog-import.md) |
+| Lightroom (cloud) library `.lrlibrary` | native | ⬜ | cloud library format, local cache only |
+| Write back to `.lrcat` | native | ⬜ (one-way migration by design) | |
+| Keyword lists (text) | import / export | ✅ | |
+| GPX track logs | ✅ | ✅ | `crates/meta/src/gpx.rs` |
+| Export presets, metadata presets, filename templates | ✅ (`.lrtemplate`) | ✅ own JSON | not interchangeable |
+| Lightroom plug-ins (`.lrplugin`, `.lrdevplugin`) | ✅ | ⬜ | see ecosystem in [target-app-parity.md](target-app-parity.md) |
+
+## Scores
+
+| Part | Weight | Ready for real work | Kind |
+|---|---:|---:|---|
+| Standard image import / export | 35% | ~85% | estimated (table above; missing: AVIF read, JXL / PSD write, lossy DNG) |
+| Sidecars, metadata, presets | 25% | ~85% | estimated |
+| Lightroom catalog migration | 25% | ~50% | estimated (one-way, approximate rendering of unsupported settings) |
+| HDR, print, video output | 15% | ~35% | estimated |
+| **Non-raw file formats** | 100% | **~70%** | estimated; raw is in [raw-parity.md](raw-parity.md) (~50%) |
+
+Remaining: **30–55 h** (AVIF decode 8–15 h if a permissive decoder is written or found; JXL encode
+4–8 h; PSD write 4–8 h; lossy DNG and Render to DNG 4–8 h; HLG 3–5 h; catalog import depth 8–12 h).
+
+## Revision history
+
+| Date | Change | Summary |
+|---|---|---|
+| 2026-10-10 | major | Created from the code and the installed Lightroom's document types |
diff --git a/docs/gaps.md b/docs/gaps.md
new file mode 100644
index 000000000..64b1cb8ac
--- /dev/null
+++ b/docs/gaps.md
@@ -0,0 +1,77 @@
+# Gaps: where LightCraft falls short of Lightroom
+
+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (created; absorbs the checklist's Top gaps and the ROADMAP's honest assessment, re-ranked after a full re-measure) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
+
+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
+working on). When you close a gap, delete or shrink its entry, update its rows in
+[parity-checklist.md](parity-checklist.md), and the numbers in [target-app-parity.md](target-app-parity.md)
+and [ROADMAP.md](../ROADMAP.md) if they move.
+
+Hours are Opus 5.5 agent wall-clock hours (see [target-app-parity.md](target-app-parity.md#how-hours-are-calibrated)).
+`B` marks a gap that blocks **beta**.
+
+## Ranked
+
+| # | 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, 4 bundled profiles or 52 spectral matrices; others use a neutral matrix | Δ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) |
+| 2 B | **CR3 beyond three verified bodies** (LR-IMP-FORMATS): CRX variants other than those verified on M50 / R100 / R8 fall back to the preview; CR3 sRAW / mRAW and HEIF CR3 too | measured: Canon is the largest bucket (33% of photographers) and its share whose camera fully works is the lowest of the big four, 72%; 17 of 22 popular Canon bodies write CR3, 2 verified (raw-parity.md) | +6.5 points of photographers whose camera works, the biggest single raw gain | 25–45 | [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 B | **Original-pixel detail at 100%** (LR-VIEW-ZOOM, P0): the whole-image preview is capped (2,560 px default); compare, overlays and soft proofing lack sharp zoom windows (#323) | checklist row 🟡 | checking focus, the first thing culling needs, isn't reliable on large files | 8–15 | [ui-parity](ui-parity.md) |
+| 5 B | **AI masks: Subject, Sky, People, Background, Landscape** are heuristics; Object / Describe run SAM 3 but its download has no mirror configured | `crates/segment`; ai-masks.md; MASK rows 67% | masking is how most Lightroom users edit today | 40–70 + model decision | [target-app-parity](target-app-parity.md#ai) |
+| 6 B | **Compressed ORF** (every Olympus / OM body since ~2008) | measured: 66 of 78 Olympus / OM models preview-only; `orf.rs` refuses; OM bucket 3% of photographers, 3% of them fully work | +2.6 points; best gain per hour of any raw gap (~0.2) | 8–15 | [raw-parity](raw-parity.md) |
+| 7 | **Nikon High Efficiency NEF** (HE / HE★ on Z 8, Z 9, Z 6III, Z f, Z50II, Z5II when set to HE) | named and preview only (#193); needs a pure-Rust JPEG XS decoder | +1.1 points (only shots taken in HE; lossless works on the same bodies); low gain per hour (~0.04), so not a beta blocker | 20–40 | [raw-parity](raw-parity.md) |
+| 8 B | **No lens profile database** (LR-EDIT-OPTICS-PROFILE): only DNG opcodes, RW2 in-file distortion and Sony ILCE-7RM4A distortion | Adobe ships 3,635 lens profiles; we never use them | distortion / vignetting correction is a default step for many | 40–80 + calibration shots | [raw-parity](raw-parity.md#lens-profiles) |
+| 9 B | **Tests don't run in CI on macOS, Windows or Linux x64**: only FreeBSD runs `cargo test`; the others build packages | `.github/workflows/` (freebsd, packaging-lint, release, windows-arm64) | regressions reach users on the main platforms; `cargo xtask ci` runs only locally | 6–12 | [target-app-parity](target-app-parity.md#by-dimension) |
+| 10 B | **Per-model verification thin, plus cheap per-body decode fixes**: 234 of 1,446 Adobe models verified (16%), ~40% of photographers on a verified body; Nikon 5 bodies verified, Canon 11. Cheap fixes: Z 6II packed 14-bit NEF (#769, PR #671), Sony A100 / F828 regression (#702), DSLR-A200…A390 / A850 12-bit ARW (#535) | measured: [raw/coverage.tsv](raw/coverage.tsv) | per-body bugs (levels, crops, WB) surface only from users | 25–45 | [raw-parity](raw-parity.md#by-maker-measured) |
+| 11 | **Enhance: Raw Details, Super Resolution; AI denoise without shippable weights or X-Trans** | ENH rows 0%; denoise Bayer only, RawNIND weights GPL and opt-in | Fujifilm users get no AI denoise; nobody gets it out of the box | 30–60 + model decision | [raw-parity](raw-parity.md#demosaic-levels-crops-and-lens-metadata) |
+| 12 | **Content-aware / generative remove, detect distractions, reflections removal** | REM rows 75%; patch synthesis missing | cleanup work goes back to Photoshop | 25–45 (+ generative model) | [target-app-parity](target-app-parity.md#ai) |
+| 13 | **Lightroom catalog migration is one-way and approximate**: unsupported Adobe profiles / AI settings archived, history kept as data | lightroom-catalog-import.md | switching users lose some looks | 8–12 | [file-format-parity](file-format-parity.md) |
+| 14 | **HDR display** (EDR / Windows HDR); HLG; HDR settings from `crs` XMP | HDR rows 70%; LR-VIEW-HDR-DISPLAY ⬜ | HDR editing works blind on HDR monitors | 12–25 | [hardware-parity](hardware-parity.md) |
+| 15 | **Print module**: custom packages, printer colour management (contact-sheet PDF only) | LRC-PRINT rows | Classic users who print | 25–40 | [target-app-parity](target-app-parity.md#features) |
+| 16 | **Map view, Book, Slideshow module, publish services** | LRC-MAP / BOOK / SS rows ⬜ | Classic users; publish services matter for Flickr / SmugMug workflows | 60–100 | [target-app-parity](target-app-parity.md#features) |
+| 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, MRW, ERF, KDC, DCR, MEF, GPR, old FinePix | raw-parity.md | small user share each | 45–85 | [raw-parity](raw-parity.md) |
+| 21 | **Localization**: 9 of 15 shipped languages partial (74–99% of catalog messages), lower-layer errors English; Hindi, Arabic, Indonesian, Korean, Vietnamese absent; no RTL or complex shaping | measured from catalogs | non-English users see mixed English | 55–90 (shipped to full); 145–255 (five new) | [localization-parity](localization-parity.md) |
+| 22 | **Pen pressure**, OS monitor profiles, follow-window colour | hardware-parity.md | brush users with tablets; multi-monitor setups | 11–22 | [hardware-parity](hardware-parity.md) |
+| 23 | **Accessibility** audit with screen readers; canvas tools keyboard-reachable | LR-BEHAV-ACCESS 🟡 | blind and low-vision users | 10–20 | [ui-parity](ui-parity.md) |
+| 24 | **Brush live preview and masking feel** (#517, #518); Feather / Edge mask refinement (15.5) | issues | masking feels slower than Lightroom | 8–12 | [ui-parity](ui-parity.md) |
+| 25 | **File writes**: JXL encode, PSD export, lossy DNG, Render to DNG; AVIF import | file-format-parity.md | interchange edge cases | 25–45 | [file-format-parity](file-format-parity.md) |
+| 26 | **Performance at scale**: 85k-photo library opens in 1.7–4.7 s; 100k grid test pending; colour NR at half resolution | ROADMAP log, gpu-pipeline.md | large-catalog users | 15–30 | [target-app-parity](target-app-parity.md#by-dimension) |
+| 27 | **Windows / Linux runtime coverage**: Windows installer UI unverified on Windows (#79); GPU path proven on Apple only | ROADMAP, issues | non-Mac users find bugs first | 15–30 | [hardware-parity](hardware-parity.md) |
+| 28 | **Small Classic shortcuts and keys**: rating `[` `]`, flag cycling, filter-bar keys, F1 help, panel keys (Tab, F5–F8) | [ui-parity](ui-parity.md#shortcuts) | Classic muscle memory | 4–8 | [ui-parity](ui-parity.md) |
+
+Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform work they need around them,
+**~340–600 h** to beta (incl. 40–80 h for the ~92 open core-path user issues) (see [target-app-parity.md](target-app-parity.md#stage)).
+
+## Gaps by kind
+
+- **Feature:** 5, 8, 11, 12, 13, 15, 16, 18, 19
+- **UI / UX:** 4, 23, 24, 28
+- **File format:** 2, 6, 7, 10, 13, 20, 25 (raw ones in [raw-parity.md](raw-parity.md))
+- **Hardware:** 14, 17, 22, 27
+- **Localization:** 21
+- **Quality / fidelity:** 1, 3
+- **Stability and platforms:** 9, 26, 27
+
+## Closed recently (kept for calibration)
+
+- 2026-10-10: Nikon lossy-after-split NEF (#633), ORF 12.8 bpp (#651), HDR gain map / PQ AVIF / float
+ TIFF export (#646), activity stack (#345).
+- 2026-10-09: HEIC in release builds (#609), Samsung uncompressed SRW (#459), contact-sheet PDF (#563),
+ Ukrainian UI (#564), Sony ILCE-7CR profile.
+- 2026-10-08: CR3 CRX lossless and C-RAW (#279), Fujifilm compressed RAF (#282), AI RAW denoise (#384),
+ Lightroom catalog import (#218), German, Spanish, Russian UI.
+- 2026-10-07: RW2 every format (#215), faces recognition in pure Rust (#174).
+- 2026-10-05: compressed NEF (#86). The older history is in [ROADMAP.md](../ROADMAP.md#progress-log).
+
+## Revision history
+
+| Date | Change | Summary |
+|---|---|---|
+| 2026-10-10 | minor | RAW gaps re-measured model by model (raw-parity.md): CR3, compressed ORF and verification entries carry measured shares; HE NEF no longer a beta blocker (+1.1 points for 20–40 h); verification entry absorbs the cheap per-body fixes (#769, #702, A200 series) |
+| 2026-10-10 | minor | Beta total raised to 340–600 h to include the ~92 open core-path user issues |
+| 2026-10-10 | major | Created from the checklist's Top gaps (whose items 1–2 had been duplicated by a merge) and ROADMAP's Where we stand; re-ranked; added evidence, impact, hours and beta flags; added platform-CI, lens-database, HE NEF and localization gaps |
diff --git a/docs/hardware-parity.md b/docs/hardware-parity.md
new file mode 100644
index 000000000..3f1defc57
--- /dev/null
+++ b/docs/hardware-parity.md
@@ -0,0 +1,50 @@
+# Hardware parity
+
+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (created from the code, gpu-pipeline.md and the parity checklist) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
+
+What Lightroom does with the machine's hardware, per platform, and what LightCraft does. Sources:
+[gpu-pipeline.md](gpu-pipeline.md), [display-profiles.md](display-profiles.md), [denoise.md](denoise.md),
+[ai-masks.md](ai-masks.md), `crates/gpu`, `crates/engine/src/devices.rs`, `crates/ui-egui/src/panels/second.rs`.
+
+| Feature | Target | LightCraft: macOS | Windows | Linux / BSD | Web | Status |
+|---|---|---|---|---|---|---|
+| GPU for display and editing | Metal / DX12, "Use GPU for display / image processing / export" | wgpu Metal: geometry, resize, blur, WB, NR, guided filter, dehaze, masks, finish; CPU fallback per render | DX12 only (Vulkan opt-in, #136) | Vulkan | CPU only (no WebGPU yet) | 🟡 defringe, spot removal, heuristic masks, long brushes and high-bit exports stay on CPU |
+| GPU export | ✅ | ✅ (6000 × 4000 in ~0.3 s vs ~1.2 s CPU) | ✅ | ✅ | — | ✅ (black-export fix on an Intel iGPU, #78) |
+| AI on GPU / neural engine | Metal / DirectML / Core ML, model per feature | AI denoise on GPU or CPU (setting); SAM 3 on Metal via candle; faces on CPU | CPU / GPU denoise | CPU / GPU denoise | — | 🟡 no Core ML / Neural Engine / DirectML |
+| HDR (EDR) display | ✅ (macOS EDR, Windows HDR) | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ (LR-VIEW-HDR-DISPLAY) |
+| Monitor profile | automatic per display, follows the window | 🟡 picked by hand | 🟡 | 🟡 | — | 🟡 OS profile not read; no follow across monitors |
+| Second window / second monitor | ✅ (Classic) | ✅ (⌘F11) | ✅ | ✅ | — | ✅ (Classic secondary-window keys missing) |
+| Pen pressure / tilt (brush masks) | ✅ pressure for size / flow | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ |
+| Trackpad gestures | pinch zoom, pan | ✅ pinch, two-finger pan | 🟡 (winit) | 🟡 | 🟡 | ✅ on macOS |
+| Card readers / cameras as import sources | ✅ (devices panel, eject after import) | ✅ mounted volumes with DCIM | ✅ | ✅ | — | 🟡 no eject-after-import check, no MTP / PTP phones |
+| Tethered capture | ✅ Classic (Canon, Nikon, Sony, Leica) | ⬜ | ⬜ | ⬜ | — | ⬜ |
+| Printers | ✅ Print module, printer colour management | ⬜ (PDF contact sheets only) | ⬜ | ⬜ | — | ⬜ |
+| Control surfaces (MIDI / Loupedeck via plug-ins) | via SDK plug-ins | control channel and MCP can drive every command | same | same | — | 🟡 different mechanism; no MIDI mapping |
+| Multi-core CPU | ✅ | ✅ worker pools, parallel JPEG decode, parallel import copy | ✅ | ✅ | single thread | ✅ |
+| Memory pressure | managed | ✅ memory budget, `crates/sysmem` returns allocator pages on macOS | ✅ budget | ✅ budget | — | ✅ |
+
+## Score
+
+**~40% ready** (estimated): the GPU editing path is real and fast (measured: 24 MP exposure drag
+3.9 ms on GPU vs 22–42 ms CPU; NR drag 18 ms vs 301–471 ms), but HDR display, pen pressure,
+tethering, printing, automatic monitor profiles and neural-engine inference are missing. GPU
+correctness is proven on Apple GPUs; other vendors rely on user reports.
+
+## Remaining (Opus 5.5 agent hours)
+
+| Work | Hours | Needs |
+|---|---:|---|
+| HDR display (EDR on macOS, Windows HDR, Linux where possible) | 10–20 | an HDR display to verify (human) |
+| OS monitor profile, follow window across displays | 5–10 | multi-monitor setup |
+| Pen pressure for brush masks (winit tablet events or a pure-Rust tablet layer) | 6–12 | a tablet |
+| Remaining stages on GPU (defringe, spot removal, masks) | 10–20 | |
+| Tethered capture (PTP over USB in pure Rust, Canon / Nikon / Sony first) | 25–45 | cameras (human) |
+| Printing with colour management | 15–25 | printers (human) |
+| Neural-engine / DirectML inference for AI features | 10–20 | depends on the AI model decision |
+| **Total** | **80–150 h** (the core set for beta, HDR display + monitor profile + pen pressure: 20–40 h) | |
+
+## Revision history
+
+| Date | Change | Summary |
+|---|---|---|
+| 2026-10-10 | major | Created |
diff --git a/docs/localization-parity.md b/docs/localization-parity.md
new file mode 100644
index 000000000..18aae618a
--- /dev/null
+++ b/docs/localization-parity.md
@@ -0,0 +1,92 @@
+# Localization parity
+
+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (first per-language measurement from the catalogs) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
+
+How far each interface language is from `full`. How to add or maintain a language is in
+[localization.md](localization.md); per-language notes are in `localization-.md`
+([zh-hans](localization-zh-hans.md), [zh-hant](localization-zh-hant.md), [ja](localization-ja.md),
+[pt-br](localization-pt-br.md), [es](localization-es.md), [de](localization-de.md),
+[ru](localization-ru.md)).
+
+## The target
+
+Lightroom 9.6 (installed on the owner's Mac) ships 16 interface languages, listed from its bundle's
+`.lproj` folder names: English, German, Spanish, French, Italian, Japanese, Korean, Norwegian Bokmål,
+Dutch, Polish, Portuguese (Brazil), Russian, Swedish, Thai, Simplified Chinese and Traditional
+Chinese. It does **not** ship Hindi, Arabic, Indonesian or Vietnamese, so for those LightCraft would
+go beyond the target.
+
+## How the numbers are measured
+
+**Measured** (a Python count over `crates/ui-egui/locales/*.json`, 2026-10-10):
+
+- The denominator is every message any catalog knows: the union of the plain catalogs' keys (2,074
+ English source strings: menus, commands, controls, panels, dialogs, tooltips, presets, What's New
+ lines) plus the 230 messages of the formats catalogs (`*-formats.json`: text with runtime values and
+ date patterns), 2,304 messages in all.
+- A message counts as translated when the catalog has a non-empty value that differs from the English
+ key. Some identical values are correct (`ISO`, `OK` in German), so the figure slightly understates
+ coverage; on the other hand, strings that never reach a catalog (technical errors raised in lower
+ crates, some `format!` text outside `tr_format!`) are not in the denominator at all. That is why no
+ language is `full` even at 99%.
+- The UI has 563 distinct `tr("…")` literals and 198 `tr_format!` messages in `crates/ui-egui` and
+ `apps/`; command, control, menu and preset labels reach the catalogs through `display_label`.
+
+Script support is checked against the code: `language_table!` in `crates/ui-egui/src/i18n.rs` lists
+scripts `Latn`, `Cyrl`, `Hans`, `Hant`, `Jpan` only. The UI is egui, which has no bidirectional layout
+and no complex-script shaping, so Arabic (RTL, joining) and Devanagari (conjuncts, reordering) cannot
+render correctly today.
+
+Native-speaker review: no catalog has had a formal review. Several catalogs have edits from community
+contributors whose commits only touch that language (ja, zh-hans, zh-hant, fr, de, ru, uk, pt-br);
+that is promising but is not recorded as review, so the column says `no (community edits)`.
+
+## The twelve key languages
+
+| # | Language | Code | UI strings translated | Dialogs / tooltips / help | Script support | Native review | Status | To `full` |
+|---|---|---|---:|---|---|---|---|---:|
+| 1 | English | `en` | 2,304 / 2,304 (100%, source) | all; Help ▸ What's New and the shortcut sheet; no full user manual | Latin (Inter) | n/a | **full** | 0 h |
+| 2 | Simplified Chinese | `zh-hans` | 1,994 / 2,304 (87%) | menus, panels, dialogs, presets, profiles, dates; lower-layer errors English | Hans via craft-fonts (Noto Sans CJK SC); IME through winit; no vertical text in UI (not in target either) | no (community edits) | partial | 6–10 h |
+| 3 | Spanish | `es` | 1,972 / 2,304 (86%) | as zh-hans | Latin | no (community edits) | partial | 6–10 h |
+| 4 | Hindi | `hi` | 0 (0%) | none | **Devanagari shaping missing** (egui has no complex shaping) | no | none (target: not shipped) | 50–90 h (shaping 25–50 h, shared with Arabic) |
+| 5 | Arabic | `ar` | 0 (0%) | none | **RTL layout and Arabic shaping missing** | no | none (target: not shipped) | 60–110 h (RTL mirroring 30–50 h + shaping, shared with Hindi) |
+| 6 | French | `fr` | 2,021 / 2,304 (88%) | as zh-hans | Latin | no (community edits) | partial | 5–9 h |
+| 7 | Portuguese (Brazil) | `pt-br` | 1,966 / 2,304 (85%) | as zh-hans | Latin | no (community edits) | partial | 6–10 h |
+| 8 | Indonesian | `id` | 0 (0%) | none | Latin (Inter covers it) | no | none (target: not shipped) | 10–16 h |
+| 9 | Japanese | `ja` | 1,995 / 2,304 (87%) | as zh-hans; vertical export watermarks | Jpan via craft-fonts (BIZ UDPGothic / UDMincho); IME through winit | no (community edits) | partial | 6–10 h |
+| 10 | German | `de` | 2,104 / 2,304 (91%) | as zh-hans; What's New translated line by line | Latin | no (community edits) | partial | 4–8 h |
+| 11 | Korean | `ko` | 0 (0%) | none | **no Korean (Hangul) face**: needs a non-Adobe OFL face in craft-fonts (Noto CJK is allowed only for Chinese) | no | none | 14–22 h |
+| 12 | Vietnamese | `vi` | 0 (0%) | none | Latin with stacked diacritics (Inter covers it; check combining marks) | no | none (target: not shipped) | 10–16 h |
+
+## Other languages LightCraft ships (3)
+
+| Language | Code | UI strings translated | Script | Native review | Status | To `full` |
+|---|---|---:|---|---|---|---:|
+| Traditional Chinese (Taiwan) | `zh-hant` | 1,993 / 2,304 (87%) | Hant; uses the Simplified Chinese face until craft-fonts has a TC face | no (community edits) | partial | 8–14 h (incl. the font) |
+| Russian | `ru` | 1,704 / 2,304 (74%) | Cyrillic (Inter) | no (community edits) | partial | 8–12 h |
+| Ukrainian | `uk` | 2,287 / 2,304 (99%) | Cyrillic (Inter) | no (community edits) | partial (lower-layer errors English) | 2–4 h |
+
+Target languages LightCraft lacks: Italian, Korean, Dutch, Polish, Swedish, Norwegian Bokmål, Thai
+(Thai needs shaping and line breaking without spaces). Each Latin-script one is ~10–16 h; Thai
+~20–35 h on top of the shaping work.
+
+## Shared work before any language is `full` (counted once, ~20–35 h)
+
+- Route lower-layer error messages (catalog, raw, codecs, engine) through translatable message ids
+ instead of English `format!` strings (~12–20 h).
+- A check in `cargo xtask ci` that every user-visible literal reaches a catalog (~4–8 h).
+- A user manual / help pages beyond What's New and the shortcut sheet (Lightroom links to online
+ help; ~4–8 h for a translatable in-app help index).
+- Native-speaker review passes (human; cannot be done by agents).
+
+## Totals
+
+- Bring the 9 shipped non-English catalogs to `full`: ~55–90 h (incl. the shared work).
+- Add the 5 missing key languages: ~145–255 h, most of it RTL and complex shaping in egui.
+- Reach the target's 16 languages: + ~60–100 h (Italian, Dutch, Polish, Swedish, Norwegian, Thai).
+
+## Revision history
+
+| Date | Change | Summary |
+|---|---|---|
+| 2026-10-10 | major | Created: per-language counts measured from the catalogs, the twelve key languages, script support, estimates |
diff --git a/docs/parity.md b/docs/parity-checklist.md
similarity index 94%
rename from docs/parity.md
rename to docs/parity-checklist.md
index 292f1f0e8..fe0ab85a9 100644
--- a/docs/parity.md
+++ b/docs/parity-checklist.md
@@ -1,7 +1,15 @@
-# Lightroom parity tracker
+# Lightroom parity checklist
-A living checklist of every Lightroom feature LightCraft aims to match, what we have, and what is missing. Agents and
-humans pick work from **[Top gaps](#top-gaps)**; whoever lands a feature updates its row in the same commit.
+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** minor (renamed from `parity.md`; Top gaps moved to gaps.md, shortcut notes to ui-parity.md) · **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
+enough to replace Lightroom is assessed in [target-app-parity.md](target-app-parity.md), and the work
+list is [gaps.md](gaps.md). Whoever lands a feature updates its row in the same commit.
+
+The rows are maintained by hand; the summary block is generated (`cargo xtask parity --write`), and
+`cargo xtask parity` (part of `cargo xtask ci`) checks that every command id, control id and path
+cited here still exists. Formerly docs/parity.md.
- **Ids** come from the (local, gitignored) reference in `plan/lightroom/`: `LR-…` = feature catalog (03),
`MENU-…` = menu items (04), `KEY-…` / `KEYC-…` = keyboard shortcuts (06, desktop / Classic), `LRC-…` = Classic
@@ -13,9 +21,6 @@ humans pick work from **[Top gaps](#top-gaps)**; whoever lands a feature updates
id (`lightcraft-cli controls`), plus source files. A trailing `*` matches a prefix (`ctl:mixer.*`).
- Feature names and notes are our own words. No Adobe text, screenshots or assets belong here.
-`cargo xtask parity` checks that every `cmd:`/`ctl:` id and file path below still exists (part of `cargo xtask ci`)
-and prints the summary; `cargo xtask parity --write` refreshes the summary table below.
-
## Summary
@@ -42,92 +47,21 @@ and prints the summary; `cargo xtask parity --write` refreshes the summary table
| S. Export (EXP) | 15 | 2 | 1 | 0 | 7/7 (100%) | 7/7 (100%) |
| T. Share (SHARE) | 0 | 0 | 0 | 4 | — | — |
| U. Map & location (MAP) | 0 | 1 | 1 | 0 | — | 0/1 (0%) |
-| V. Preferences (PREF) | 7 | 1 | 2 | 2 | 1/1 (100%) | 5/5 (100%) |
+| V. Preferences (PREF) | 6 | 2 | 2 | 2 | 1/1 (100%) | 4/5 (80%) |
| W. Cloud & AI infrastructure (CLOUD / AI) | 1 | 0 | 1 | 2 | — | — |
| X. Cross-cutting behaviours (BEHAV) | 18 | 6 | 1 | 1 | 8/8 (100%) | 6/9 (67%) |
| Y. Menus | 82 | 0 | 4 | 8 | 47/47 (100%) | 23/23 (100%) |
| Z. Keyboard shortcuts (desktop) | 75 | 3 | 3 | 1 | 49/52 (94%) | 23/24 (96%) |
-| Lightroom Classic extras | 32 | 11 | 39 | 9 | — | 22/24 (92%) |
-| **Total** | 393 | 39 | 84 | 36 | 192/200 (96%) | 142/152 (93%) |
+| Lightroom Classic extras | 33 | 15 | 35 | 9 | — | 23/25 (92%) |
+| **Total** | 397 | 49 | 73 | 36 | 192/200 (96%) | 143/155 (92%) |
-Weighted completion (✅ = 1, 🟡 = ½, 🚫 left out): **79.9%** of 516 in-scope rows — P0 98.0% of 200 · P1 95.7% of 152 · P2 42.9% of 163.
+Weighted completion (✅ = 1, 🟡 = ½, 🚫 left out): **81.2%** of 519 in-scope rows — P0 98.0% of 200 · P1 95.2% of 155 · P2 47.2% of 163.
-## Top gaps
+## Top gaps and shortcut notes
-Ordered by user impact, then tier, then effort. The checklist above counts features that *exist*; these are the gaps
-that decide whether a photographer can switch (see the honest assessment in [ROADMAP.md](../ROADMAP.md#where-we-stand)).
-Take the first one nobody is working on.
-
-1. **LR-PROF-CAMERACOLOR** (P0): our own camera colour calibration. Sony ARW, Nikon NEF and Panasonic RW2 now get a guarded file-local fit to their own embedded JPEG (colour matrix + hue/saturation/value table + tone and chroma curves, relative WB; per-model profiles pooled from many photos via `lightcraft-cli calibrate` for ARW and NEF; ARW: 7 of 10 public samples accepted, mean ΔE vs the camera JPEG 17–26 → 3–10; NEF: 13 of 13 decodable samples from 6 bodies accepted, ΔE 13–46 → 3–8, one mixed-light scene 13 → 12; RW2: 140 of 174 public samples accepted, from 93 of the 114 bodies with a preview, median ΔE 5.0); Nikon CropArea and untagged-preview ColorSpace metadata are respected; measured calibration and fidelity remain missing. Other non-DNG raws and rejected fits still use a neutral matrix. Expand validated preview fitting and use matrices the files carry themselves; never Adobe data.
-2. **LR-IMP-FORMATS** (P0): **CR3** first (every Canon body since ~2018), then compressed RAF / ORF, Nikon
-1. **LR-PROF-CAMERACOLOR** (P0): our own camera colour calibration. Sony ARW, Nikon NEF, Panasonic RW2 and Fujifilm RAF now get a guarded file-local fit to their own embedded JPEG (colour matrix + hue/saturation/value table + tone and chroma curves, relative WB; per-model profiles pooled from many photos via `lightcraft-cli calibrate` for ARW, NEF and RAF; ARW: 7 of 10 public samples accepted, mean ΔE vs the camera JPEG 17–26 → 3–10; NEF: 13 of 13 decodable samples from 6 bodies accepted, ΔE 13–46 → 3–8, one mixed-light scene 13 → 12; RW2: 140 of 174 public samples accepted, from 93 of the 114 bodies with a preview, median ΔE 5.0; RAF: 23 of 24 references accepted with bundled X-H2S/X-T4 profiles; 150 withheld-file tests improve mean ΔE slightly over per-file fitting); measured calibration and fidelity remain missing. Other non-DNG raws and rejected fits still use a neutral matrix. Expand validated preview fitting and use matrices the files carry themselves; never Adobe data.
-2. **LR-IMP-FORMATS** (P0): **CR3** first (every Canon body since ~2018), then compressed ORF, Nikon
-1. **LR-PROF-CAMERACOLOR** (P0): our own camera colour calibration. Sony ARW, Nikon NEF and Panasonic RW2 now get a guarded file-local fit to their own embedded JPEG (colour matrix + hue/saturation/value table + tone and chroma curves, relative WB; per-model profiles pooled from many photos via `lightcraft-cli calibrate` for ARW and NEF; ARW: 7 of 10 public samples accepted, mean ΔE vs the camera JPEG 17–26 → 3–10; NEF: 13 of 13 decodable samples from 6 bodies accepted, ΔE 13–46 → 3–8, one mixed-light scene 13 → 12; RW2: 140 of 174 public samples accepted, from 93 of the 114 bodies with a preview, median ΔE 5.0); measured calibration and fidelity remain missing. Other non-DNG raws and rejected fits still use a neutral matrix. Expand validated preview fitting and use matrices the files carry themselves; never Adobe data.
-2. **LR-IMP-FORMATS** (P0): CR3 lossless Bayer and version 0x100/0x200 C-RAW now decodes (M50/R100/R8 full sensor exact). Remaining unverified CRX variants, compressed RAF / ORF, Nikon
- Canon sRAW; AVIF decode (HEIC decodes behind the codecs' non-default `heif` feature:
- the optional `lightcraft-heif` crate on heic-rs, the same decoder and policy as PhotoCraft). Clean-room, from prose descriptions only (see
- `crates/raw/src/vendor/nefc.rs` for how compressed NEF was done). Until decoded, such photos are `preview_only`.
-3. **LR-IMP-CAMERA-COVERAGE** (P0): per-model verification; grow the CC0 corpus and fix per-model bugs (like the CR2
- colour-filter layout, fixed in #85 by reading the file's own tag).
-4. **LR-BEHAV-RENDER-FIDELITY** (P1): a side-by-side fidelity suite against Lightroom renders (kept local in `plan/`),
- then tune tone, highlights, texture/clarity/dehaze, NR and sharpening against it. A retune that changes how existing
- settings render ships as a new process version, so existing edits keep their look (`docs/process-versions.md`).
-4b. **LR-VIEW-ZOOM** (P0): finish the sharp zoomed views of issue #323: windows for compare, overlays and soft proofing
- (Before/After has them); check sharpness and the wide-stage margin on real raw photos (only synthetic scenes so far).
-5. **LR-EDIT-OPTICS-PROFILE** (P1): a lens-profile database of our own (corrections embedded in DNG files and the
- distortion correction Panasonic / Leica cameras record in RW2 / RWL files work today; issue #256). Sony ILCE-7RM4A Bayer
- ARW 16-sample tables in native 3:2 framing now feed the same distortion path; broader coverage and Sony
- vignetting / lateral CA remain (see `docs/sony-lens-corrections.md`).
-6. **AI masks and Enhance** (LR-MASK-SUBJECT / SKY / PEOPLE / OBJECTS, LR-EDIT-DETAIL-DENOISE, SUPERRES, LENSBLUR):
- Object and Describe masks run SAM 3 in pure Rust (`crates/segment`); the weights (SAM License) are never bundled:
- the app offers a consented, verified download, but **LightCraft's CDN mirrors are not configured yet** (the
- built-in list is empty, so today users need their own mirror or a manual install). Subject / Sky / People could
- use the same model with fixed prompts; denoise / super-resolution models remain a maintainer decision.
-7. **HDR** (Q. HDR, LR-EXP-HDR), **video** (R. Video), **Classic output modules** (Map view, Book, Slideshow module,
- Print, publish): large, well understood, lower priority than 1–5. Contact-sheet PDF export now covers a
- basic Print workflow; custom picture packages, printer integration and print colour management remain open.
-8. **LR-VIEW-ZOOM** (P0): pinch zoom and two-finger pan now work, with stable image cursors between gesture events.
- The whole-image preview is still capped by
- Settings → Performance → Preview size (2,560 px by default). Render visible regions at native resolution so
- 100% can reliably show original-pixel detail on larger photos.
-
-Recognition runtime gap closed (LR-PREF-PEOPLE): the pinned SFace and AuraFace models now run on the shared pure-Rust
-CPU engine and agree with an independent reference runtime on public crops. Live-face threshold calibration, small-face
-coverage and separated clusters remain open; see [faces.md](faces.md).
-
-## Shortcuts: conflicts and missing bindings
-
-Compared `plan/lightroom/06-shortcuts.md` (desktop part) with our bindings: command specs in `crates/engine/src/cmd/`,
-`UI_COMMANDS` in `crates/ui-egui/src/menus.rs` and secondary bindings (`ALIASES`) in `crates/ui-egui/src/shortcuts.rs`.
-`no_conflicting_bindings` (same file) fails when one key fires two actions.
-
-**Fixed (M16.1):** added Lightroom-desktop keys as secondary bindings for existing commands — ⌘D Select None, ⇧E
-Export dialog, Space Toggle zoom, ⇧M Create Version, ⇧X Reject + advance, ⇧U Unflag + advance. `⇧Y` fired both
-Before/After Split and the History panel; History lost the binding. `W` and `⇧⌘I` also fired the engine command
-under the UI command that wraps it (a no-op error); the UI command now wins.
-
-**Deliberate differences (our key → Lightroom desktop key)** — each is a conflict with another binding we have:
-
-| Action | Ours | Lightroom desktop | Why |
-|---|---|---|---|
-| Pick flag | P | Z | Z = toggle zoom (Classic convention); P is the Classic pick key |
-| Photos panel (left) | ⌘⇧L | P | P = pick |
-| Expand/collapse edit sections | ⌘⌥1–5 | ⌘1–6 | ⌘0 = Zoom to Fit, ⌘1 = Zoom 100 %; Geometry lives in the Crop panel |
-| Histogram | ⌘⇧H | ⌘0 | ⌘0 = Zoom to Fit |
-| Square Grid | ⇧G | (none; ⇧G = Guided Upright) | Guided Upright is a button in the Crop panel |
-| Export dialog | ⌘⇧E (+ ⇧E) | ⇧E | ⌘⇧E is "Edit in Photoshop" there; no external-editor command yet |
-| Crop overlay cycle | ⇧O | O | O = mask overlay; ⇧O (mask colour / overlay orientation) unused otherwise |
-| Create Version | ⌘⇧S (+ ⇧M) | ⇧M (Windows: Ctrl+⇧S) | — |
-| Select None | ⌘⇧A (+ ⌘D) | ⌘D | — |
-
-**Library culling (KEYC-RATING):** `0–5`, `6–9` and `P/X/U` reach egui on macOS even when displayed in the native menu (adapted from PR #261; issue #283). `Shift+0–9` applies and advances once; `Shift+P` picks and advances in Photo Grid / Square Grid and retains Presets elsewhere. See [library shortcuts](library-shortcuts.md) for the verified Classic mapping and regression coverage. Remaining Classic gaps: rating `[` / `]`, flag cycling and filter-bar keys.
-
-**Still missing / broken:**
-- No command yet: F1 help (verify the rest of the old list: full screen, settings, stacks,
- visualize spots and merges have commands now).
-- `H` opens Remove; Lightroom also uses it (Classic) to hide pins — pins toggle from View → Show Mask Pins.
-- ⌘M / ⌘H / ⌘Q / ⌘W rely on the platform window defaults (unverified).
+Moved on 2026-10-10: the ranked gaps are in [gaps.md](gaps.md); shortcut conflicts, deliberate
+differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts).
@@ -644,7 +578,7 @@ visible menu bar yet: the menu model is only exposed through the control channel
## Z. Keyboard shortcuts (desktop)
From `06-shortcuts.md` part 1. Evidence is our binding; conflicts are explained in
-[Shortcuts: conflicts and missing bindings](#shortcuts-conflicts-and-missing-bindings).
+[UI parity → Shortcuts](ui-parity.md#shortcuts).
| Id | Feature | Tier | Status | Evidence | Missing / notes |
|---|---|---|---|---|---|
@@ -837,3 +771,10 @@ dialog offers Horizontal/Vertical and multiline text. Japanese glyphs fall back
Mincho faces (BIZ UDMincho; on the web build, BIZ UDPGothic) when built with `CRAFT_FONTS_DIR`. Vertical lettering uses upright em cells and right-to-left newline columns;
Latin stays upright. Advanced Japanese composition remains open. Regression coverage:
`export::tests::japanese_watermarks_support_vertical_columns`, `japanese_watermark_options_and_legacy_defaults`, `watermarks_work_without_craft_fonts`.
+
+## Revision history
+
+| Date | Change | Summary |
+|---|---|---|
+| 2026-10-10 | minor | Renamed from `parity.md` (progress-docs standard); Top gaps moved to gaps.md (its duplicated items 1–2 from a bad merge resolved there), shortcut notes moved to ui-parity.md. Rows unchanged; a recount of the rows gives 81.1% of 517 in-scope rows (P0 98.0%, P1 95.1%, P2 46.9%), so the generated summary above is due a `cargo xtask parity --write` |
+| 2026-09-30 – 2026-10-10 | — | Rows added and updated with each feature (see `git log -- docs/parity.md docs/parity-checklist.md`) |
diff --git a/docs/process-versions.md b/docs/process-versions.md
index f9831b7a5..73bd7dcde 100644
--- a/docs/process-versions.md
+++ b/docs/process-versions.md
@@ -32,7 +32,7 @@ Today there is one process, **V1**: LightCraft's rendering from before process v
passing: settings saved without the field must stay V1, not become the new latest. That is the
field's `#[serde(default = "ProcessVersion::legacy")]`; the struct-level `Default` gives new
settings the latest instead.
-4. Render a few procedural scenes on both processes and compare, and update `docs/parity.md`.
+4. Render a few procedural scenes on both processes and compare, and update `docs/parity-checklist.md`.
## What gets which process
diff --git a/docs/raw-parity.md b/docs/raw-parity.md
new file mode 100644
index 000000000..ca5cbcfdc
--- /dev/null
+++ b/docs/raw-parity.md
@@ -0,0 +1,253 @@
+# RAW parity: cameras, formats, demosaic, colour and lens profiles
+
+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (model-by-model measurement against Adobe's published camera list; use-weighted coverage; variant matrix; colour and lens sections; ranked next cameras) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 (Camera Raw 18.7)
+
+Raw processing is the deepest single dimension of Lightroom parity: a photographer whose camera
+does not decode, or decodes with the wrong colour, cannot switch. This document measures it model by
+model. The numbers feed [target-app-parity.md](target-app-parity.md); individual shortfalls are
+ranked in [gaps.md](gaps.md). Details per format: [cr3.md](cr3.md),
+[raf-compression.md](raf-compression.md), [sony-lens-corrections.md](sony-lens-corrections.md),
+[camera-preview-colour.md](camera-preview-colour.md), [denoise.md](denoise.md).
+
+## Headline
+
+| 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 | **234** (16%) | measured: [raw/evidence.tsv](raw/evidence.tsv) |
+| Decoder path should handle it, **unverified** | **943** (65%) | measured from the format rules (632 of them DNG, mostly phones) |
+| **Preview-only** (embedded JPEG) | **233** (16%) | measured |
+| **Unsupported** (not imported, refused or decodes wrongly) | **36** (2%) | measured |
+| Models added since 2017 (Camera Raw 10+): verified / unverified / preview / unsupported | 81 / 641 / 24 / 8 of 754 | measured |
+| **Share of photographers whose camera fully works** (decodes from sensor data), use-weighted | **~84%** | estimated: measured classes × written usage weights × per-format confidence |
+| …of which on a body we have verified | **~40%** | estimated, same weights |
+| Camera colour: Adobe profiles vs ours | 1,500 Adobe Standard profiles, 481 models with Camera Matching profiles vs **4 bundled profiles** + per-file fits + 52 spectral matrices | 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%) | **~56%** (range 50–60%) | estimated, see [Scores](#scores) |
+
+Re-measure with `python3 docs/raw/coverage.py` (standard library only): it classifies every Adobe
+model from the format rules in the script (which mirror `crates/raw/src`) plus the per-model evidence,
+rewrites [raw/coverage.tsv](raw/coverage.tsv) and prints the tables below; `--check` fails when the
+committed table is stale.
+
+## How it was measured
+
+- **Adobe's list**: helpx "Camera Raw supported cameras" (the live page refuses scripted requests, so
+ the Wayback Machine capture of 2026-10-03 was parsed): 1,446 camera rows from 40 makers after
+ dropping profile-only rows; maker, model, extension(s), Camera Matching profile, minimum Camera Raw
+ version. The year a model was added is approximated from that Camera Raw major version.
+- **What we decode**: the decoders and their refusals in `crates/raw/src` (`lib.rs` routing, `vendor/*.rs`
+ coding variants, `crx.rs` CRX limits, `orf.rs` / `srw.rs` compressed refusals), the import list
+ `lightcraft_engine::import::EXTENSIONS`, and `KNOWN_UNSUPPORTED` in `crates/raw/tests/corpus.rs`. Doc
+ claims were not used.
+- **Verified** means a real file from that body decoded from sensor data in: the corpus test over the
+ `RAW_SAMPLES` manifest (`xtask/src/main.rs`, 101 CC0 files; bodies whose sample is in
+ `KNOWN_UNSUPPORTED` excluded), `crates/raw/tests/cr3_corpus.rs` (3 bodies, exact), the RAF reference
+ check ([raf-compression.md](raf-compression.md)), and the raw.pixls.us bulk checks recorded in issue
+ #535 (all 100 Sony bodies there; 87 decode) and PR #215 (every Panasonic / Leica RW2 / RWL / RAW body
+ there, 178 files from 118 bodies). Bulk-check body lists were matched to Adobe names through the
+ raw.pixls.us folder names. Checks without a body list (≈ 60 NEFs, 35 CR2 bodies for the CFA layout)
+ are not counted as verified.
+- **Usage weights** ([raw/usage-weights.tsv](raw/usage-weights.tsv)): maker buckets from 2025 market
+ data (Nikkei production: Canon ~36%, Sony ~29%, Nikon ~14%, Fujifilm ~12% of mirrorless; BCN retail:
+ Sony 29.9%, Canon 27.4%, Nikon 15.1%), adjusted for the DSLR installed base and for the makers named
+ in this repo's 325 user issues (Sony 33, Nikon 31, Canon 26, phones 23, Fujifilm 9, Panasonic / Leica
+ 8, OM / Olympus 6, Pentax / Ricoh 6). Inside each bucket, 80% goes to the popular bodies of the last
+ ~8 years (listed in the file), 20% to the bucket's other Adobe models.
+- **Per-format confidence** for unverified bodies (the chance their files decode correctly): DNG, CR2,
+ RAF, RW2 0.95; NEF, ARW, PEF 0.9; ORF, SRW, SR2 0.8; **CR3 0.6** (the CRX decoder refuses several
+ coding variants and only three bodies are verified; maintainers report the R7 working).
+
+## By maker (measured)
+
+| Maker | Adobe models | verified | unverified | preview | unsupported | Main reason for preview / unsupported |
+|---|---:|---:|---:|---:|---:|---|
+| Sony | 149 | 86 | 51 | 9 | 3 | DSLR-A200…A390 and A850 12-bit compressed ARW (preview, #535); A100 and DSC-F828 refused (regression #702); SRF not imported |
+| Canon | 134 | 11 | 100 | 21 | 2 | CRW (2000–2004 bodies) preview; EOS-1D / 1Ds TIFF raws not imported |
+| Samsung | 128 | 1 | 118 | 9 | 0 | compressed SRW (NX1, NX30, NX300, NX500, NX2000, NX3000, NX3300, NX mini, Galaxy NX) |
+| Apple | 113 | 1 | 112 | 0 | 0 | — (DNG / ProRAW) |
+| Panasonic | 104 | 93 | 11 | 0 | 0 | — |
+| Nikon | 99 | 5 | 94 | 0 | 0 | — (HE / HE★ is a per-shot setting, preview-only) |
+| Fujifilm | 97 | 16 | 55 | 26 | 0 | pre-X FinePix RAF without a raw IFD (estimated split) |
+| Google | 88 | 1 | 87 | 0 | 0 | — |
+| Olympus | 71 | 3 | 9 | 59 | 0 | **compressed ORF** |
+| Leica | 61 | 13 | 48 | 0 | 0 | — |
+| Xiaomi, Oppo, LG, Huawei, OnePlus, Motorola, Nokia | 139 | 0 | 139 | 0 | 0 | — (DNG) |
+| Pentax / Ricoh | 60 | 4 | 56 | 0 | 0 | — |
+| Hasselblad | 34 | 0 | 2 | 32 | 0 | Hasselblad-compressed 3FR / FFF |
+| Phase One / Leaf / Mamiya | 59 | 0 | 0 | 43 | 16 | IIQ, MOS preview; TIFF raws and MEF not imported |
+| Casio | 26 | 0 | 26 | 0 | 0 | — (DNG) |
+| DJI | 21 | 0 | 21 | 0 | 0 | — (DNG) |
+| OM Digital Solutions | 7 | 0 | 0 | 7 | 0 | **compressed ORF** |
+| Kodak, Konica Minolta, Epson, Sigma, GoPro, Contax, others | 56 | 0 | 14 | 27 | 15 | DCR, GPR not imported; KDC, MRW, ERF, X3F preview |
+| **All** | **1,446** | **234 (16%)** | **943 (65%)** | **233 (16%)** | **36 (2%)** | |
+
+## By use (estimated)
+
+| Bucket | Weight | Camera fully works | On a verified body | Why |
+|---|---:|---:|---:|---|
+| Canon | 33% | 72% | 16% | 17 of the 22 popular bodies write CR3; 2 of them verified, the rest at 0.6 |
+| Sony | 27% | 98% | 91% | bulk-checked |
+| Nikon | 16% | 91% | 12% | decoders cover every NEF coding but HE; few bodies verified |
+| Fujifilm | 10% | 91% | 45% | |
+| Panasonic | 4% | 100% | 98% | bulk-checked |
+| OM / Olympus | 3% | 3% | 1% | compressed ORF |
+| Phones | 3% | 93% | 1% | DNG |
+| Pentax / Ricoh | 1.5% | 94% | 14% | |
+| Leica | 1% | 95% | 5% | |
+| DJI | 1% | 95% | 0% | |
+| Medium format and others | 0.5% | 23% | 0% | IIQ, 3FR, X3F |
+| **Weighted** | 100% | **84%** | **40%** | |
+
+About 5 in 6 raw photographers' cameras decode, but only about 2 in 5 use a body we have actually
+checked, and Canon, the largest bucket, is the weakest of the big four.
+
+## Variant matrix (depth per format)
+
+✅ decoded and checked on real files · 🟡 decoded, partial or unverified · ⬜ preview-only or refused.
+
+| Format | Variant | Status | Evidence |
+|---|---|---|---|
+| CR2 | lossless JPEG slices | ✅ | 8 corpus bodies; CFA phase from `CR2CFAPattern` (#85, 35 bodies); masked-column black level |
+| CR2 | sRAW / mRAW (YCbCr) | ✅ | `vendor/cr2/sraw.rs`, 31 samples from 16 models |
+| CR3 | CRX lossless, version 0x100 | ✅ | M50, R100, R8 exact against a reference |
+| CR3 | C-RAW 0x100 (horizontal tiles), 0x200 (single 14-bit tile, adaptive QP) | ✅ | same 3 bodies exact |
+| CR3 | other CRX variants (vertical tiles, partial subbands, other plane flags, 0x200 multi-tile) | ⬜ | refused in `crx.rs`, falls back to the preview; which bodies write them is unmeasured |
+| 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 |
+| NEF | uncompressed, lossless, lossy type 1 / 2, lossy after split | ✅ | D5100, D7000, D7500, Z f corpus; ~60 NEFs by hand; #633 |
+| 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 |
+| ARW | uncompressed, cRAW (ARW2), lossless LJ92 (L / M / S) | ✅ | 86 bodies (#535); M/S black level 1024 |
+| ARW | packed 12-bit (A900) | ✅ | one CC0 file; A850 unmeasured |
+| ARW | 12-bit compressed of DSLR-A200…A390 | ⬜ | preview (#535) |
+| ARW | A100 ARW; SRF (F828, V3) | ⬜ | refused (#702); SRF not imported |
+| ARW | ARQ pixel shift | ⬜ | no code |
+| SR2 | DSC-R1 | ✅ | CC0 3221 sample |
+| RAF | uncompressed, lossless and lossy compressed; Bayer and X-Trans; GFX 14/16-bit | ✅ | 24 files from 13 bodies exact |
+| RAF | pre-X FinePix without a raw IFD | ⬜ | `raf.rs` refuses (26 Adobe models, estimated split) |
+| ORF | uncompressed 16-bit, 12-bit packed, two-field 12-bit, 12.8 bpp | ✅ | E-1, E-400, XZ-2 corpus; #651 |
+| ORF | **compressed** (every Olympus / OM body since ~2008) | ⬜ | `orf.rs` refuses; the E-M1 and E-M10 III corpus files are `KNOWN_UNSUPPORTED` |
+| RW2 / RWL / RAW | formats 2, 4, 5, 6, 7, 8 and 16-bit words | ✅ | 118 bodies (#215) |
+| PEF | uncompressed, Huffman, packed 12-bit | ✅ | K10D, K-3, K-5 IIs |
+| PEF / DNG | Pentax pixel shift | ⬜ | frames not combined |
+| SRW | uncompressed, 12-bit packed | ✅ | EX1 corpus; NX10, NX20 |
+| SRW | compressed 32770 / 32772 / 32773 | ⬜ | refused |
+| DNG | uncompressed, LJ92, lossy JPEG / Smart Preview, Deflate incl. float, LinearRaw, tiles / strips, row / column interleave | ✅ | 6 corpus DNGs; 4 ILCE-6400 JXL DNGs |
+| DNG | JPEG XL (DNG 1.7, lossless and lossy) | ✅ | synthetic bit-exact; real lossless and lossy files |
+| DNG | Apple ProRAW (incl. semantic mattes) | ✅ | iPhone 12 Pro corpus; ProfileGainTableMap read but off (ΔE00 19 against Lightroom with it on) |
+| DNG | phone DNGs (Pixel, Galaxy, Xiaomi…) | 🟡 | Pixel 2 XL only; white-level pile-up fix #548 |
+| DNG | GoPro GPR (VC-5) | ⬜ | refused |
+| 3FR / FFF | DNG-like layouts | 🟡 | decoded when uncompressed or LJ92 |
+| 3FR / FFF | Hasselblad compression | ⬜ | preview |
+| IIQ, MOS, MRW, ERF, KDC, X3F | all | ⬜ | preview |
+| DCR, MEF, MFW, DXO, GPR, TIFF raws | all | ⬜ | not imported |
+
+## Demosaic, levels, crops and lens metadata
+
+| Stage | Target | LightCraft | Status |
+|---|---|---|---|
+| Bayer demosaic | Adobe's own, plus AI "Raw Details" | AHD (default), PPG, bilinear (`crates/raw/src/demosaic`) | 🟡 quality unmeasured against Lightroom; no Raw Details |
+| X-Trans demosaic | ✅ | in-house edge-weighted directional | 🟡 unmeasured |
+| Foveon | ✅ (older bodies) | — | ⬜ |
+| Monochrome / LinearRaw | ✅ | handled, no demosaic | ✅ |
+| Pixel shift (Sony ARQ, Pentax, OM high-res, Nikon) | ✅ (Sony, Pentax, Nikon) | — | ⬜ |
+| Black and white levels | per model, calibrated | read from each file (masked columns, maker notes, SR2SubIFD, CMP1) | 🟡 per-body bugs keep surfacing: A450–A700 SR2 black level and M/S ARW pedestal (#535, fixed); 5D Mark IV CR2 renders dark (#455, open); white-level pile-up in phone DNGs (#548, fixed) |
+| Crop masks and aspect | ✅ | DefaultCrop, Sony recorded size, Canon AspectInfo, Panasonic in-camera crops, Nikon CropArea | 🟡 7RM3 / 7RM4 crop origin 8–32 px off (cosmetic) |
+| Lens metadata | ✅ read for profile selection | EXIF lens fields read; used only for the in-file corrections below | 🟡 |
+| Highlight reconstruction | ✅ | neutral clip, chromaticity diffusion (`highlight.rs`) | 🟡 tuned by eye |
+| AI denoise | ✅ Bayer and X-Trans, model bundled | Bayer only, user-installed weights, pure-Rust CPU / GPU | 🟡 no X-Trans, no shippable weights |
+| Super Resolution | ✅ | — | ⬜ |
+| HDR / panorama merge to raw DNG | ✅ | linear DNG (float16 HDR) with the source's colour tags | ✅ |
+| Convert to DNG | ✅ | uncompressed or LJ92 tiles (`dngwrite.rs`) | ✅ (no lossy DNG) |
+
+## Colour
+
+| 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 (**4 bundled**: ILCE-7CR, ILCE-7M4, X-H2S, X-T4); else a guarded per-file fit to the camera's embedded JPEG; else spectral matrices (**52 models**, rawtoaces-data); 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 only where matrices exist; per-file fits give relative WB (#296, #730) |
+
+**How far the default rendering is from Adobe's** (CIE ΔE; measured where stated):
+
+- Against the camera JPEG (what the fit targets): the Sony bulk check (#535) accepted fits on 81 of 87
+ bodies, median ΔE 5.1, ΔL\* +0.2; rejected fits fall back to a neutral matrix at median ΔE 18.5,
+ ΔL\* −11, chroma ×0.39. RW2 140 of 174 files accepted, median 5.0. NEF 13 of 13, 2.9–7.8. RAF 23 of
+ 24, pooled X-H2S 1.62 / X-T4 2.34. SRW 2.2–7.6.
+- Against Lightroom itself (few): Sony DRO median ΔE76 7.5 → 3.3; Olympus E-1 vs Lightroom Classic
+ ΔE00 11.25 → 8.30; ProRAW with its gain map ΔE00 19 (why the map is off). No measurement against
+ Adobe Standard across models exists.
+- User evidence: 33 open issues about raw colour or rendering (wrong colours, dark, flat, green,
+ desaturated), the largest group of open core-path issues.
+
+**Colour score ~35%** (estimated): most decoded files start close to the camera's own look (ΔE ~5),
+about 10% start far off (ΔE ~18), nothing is calibrated, Kelvin WB is missing for fitted cameras, and
+the distance to Adobe Standard is unmeasured. In the readiness numbers this becomes a colour factor
+of ×0.85 for a working photographer (they correct colour by hand, at a cost) and ×0.75 for the
+essentials user (the default look is what they keep).
+
+## Lens profiles
+
+| Item | Adobe | LightCraft |
+|---|---|---|
+| Profile database | 3,635 profile files, 56 makers, auto-selected | **0** (Adobe LCPs never used; no database of our own) |
+| In-file corrections | ✅ | DNG opcodes (WarpRectilinear, FixVignetteRadial, GainMap…) applied; Panasonic / Leica RW2 distortion (79 photos, 13 bodies, 11 lenses); Sony tag 0x7037 distortion for the ILCE-7RM4A (2 lenses) |
+| Vignetting / lateral CA from maker notes | ✅ | ⬜ |
+| Manual distortion, vignetting, CA, defringe | ✅ | ✅ |
+
+**Lens score ~10%** (estimated): automatic correction reaches roughly Panasonic / Leica, phone and
+drone DNG users (~9% of photographers by the weights above); Adobe reaches nearly everyone.
+
+## Scores
+
+| Part | Weight | Score | Basis |
+|---|---:|---:|---|
+| Decoding | 60% | **76%** | use-weighted 84% × 0.9 for per-body decode bugs still surfacing (levels, crops, WB: #535, #455, #461, #702) |
+| Colour | 25% | **35%** | see [Colour](#colour) |
+| Lens profiles | 15% | **10%** | see [Lens profiles](#lens-profiles) |
+| **RAW readiness** | 100% | **~56%** (range 50–60%) | 45.6 + 8.75 + 1.5; demosaic and highlight quality unmeasured |
+
+The previous estimate (earlier on 2026-10-10) was ~50%, with decoding at ~78% weighted by maker
+from the format list alone. Measured model by model, decoding comes out about the same; the change is
+the written decomposition, not new decoding.
+
+## Remaining work, ranked by photographers unlocked per hour
+
+Hours are Opus 5.5 agent hours, calibrated on this repo's raw PRs: RW2 every format 6.5 h (#215),
+compressed RAF 8.6 h (#282), CR3 CRX 10 h (#279), NEF lossy after split 4.7 h (#633), ORF 12.8 bpp
+1.7 h (#651), HEIC 7 h (#609). "Gain" is the change in the use-weighted share whose camera fully
+works, from [raw/usage-weights.tsv](raw/usage-weights.tsv).
+
+| # | Camera / variant | Gain (points of photographers) | Hours | Gain per hour | Needs |
+|---:|---|---:|---:|---:|---|
+| 1 | Compressed ORF (every Olympus / OM body since ~2008) | +2.6 | 8–15 | ~0.20 | CC0 samples (have 2) |
+| 2 | CR3: remaining CRX variants, then verify the 17 popular CR3 bodies | +6.5 | 25–45 | ~0.19 | samples per body (raw.pixls.us has most) |
+| 3 | Verify popular Nikon, Fujifilm, Canon CR2 and Pentax bodies (one CC0 file each, fix what breaks) | +2.0 | 8–15 | ~0.17 | public samples |
+| 4 | Nikon packed 14-bit under a compressed tag (Z 6II uncompressed, #769) | +0.2 | 1–2 | ~0.13 | PR #671 exists |
+| 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 and DSC-F828 regression (#702) | +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, MRW, KDC, ERF, DCR, MEF, GPR | +0.6 together | 45–85 | ~0.01 | samples; low use |
+
+Beyond decoding:
+
+| Gap | Hours | Needs |
+|---|---:|---|
+| Measured colour calibration for the ~60 popular bodies, Kelvin WB, render-fidelity suite against Lightroom | 80–150 | chart shots per camera (a human with cameras) or licensable measured data |
+| Lens profile database of our own (fitting tools, data format, top 100 lenses), maker-note vignetting / CA | 40–80 | calibration shots (a human with lenses) |
+| Demosaic and highlight quality measured against Lightroom; X-Trans AI denoise | 15–30 | |
+| **Total RAW** | **255–480 h** (decoding list above 120–220 h; ~60% parallelizes) | |
+
+## Revision history
+
+| Date | Change | Summary |
+|---|---|---|
+| 2026-10-10 (later) | major | Model-by-model measurement against Adobe's published list (1,446 models: 234 verified, 943 unverified, 233 preview, 36 unsupported); `docs/raw/` data and re-measure script; use-weighted share 84% (40% on verified bodies); variant matrix; colour and lens sections with scores; ranked next cameras; RAW readiness ~56% from written parts |
+| 2026-10-10 | major | Created: formats, makers, demosaic, colour and lens checklists; target counts from the installed Lightroom 9.6 bundle listing |
diff --git a/docs/raw/adobe-cameras.tsv b/docs/raw/adobe-cameras.tsv
new file mode 100644
index 000000000..a80946ca8
--- /dev/null
+++ b/docs/raw/adobe-cameras.tsv
@@ -0,0 +1,1450 @@
+# Adobe Camera Raw / Lightroom supported cameras, parsed from helpx.adobe.com/camera-raw/desktop/dng-and-file-formats/camera-raw-plug-supported-cameras.html
+# (Wayback Machine capture 20261003133011; the live page refuses scripted requests). Facts only: maker, model, extension,
+# whether Adobe ships a Camera Matching profile, minimum Camera Raw version, and the year that ACR major version shipped (approximate date added).
+maker camera extensions camera_matching acr_version year_added
+Apple iPhone 17e Front Camera DNG No 18.3 2025
+Apple iPhone 17e Rear Camera DNG No 18.3 2025
+Apple iPhone 17 Front Camera DNG No 18.0 2025
+Apple iPhone 17 Ultrawide Camera DNG No 18.0 2025
+Apple iPhone 17 Wide Camera DNG No 18.0 2025
+Apple iPhone 17 Pro Front Camera DNG No 18.0 2025
+Apple iPhone 17 Pro Telephoto Camera DNG No 18.0 2025
+Apple iPhone 17 Pro Ultrawide Camera DNG No 18.0 2025
+Apple iPhone 17 Pro Wide Camera DNG No 18.0 2025
+Apple iPhone 17 Pro Max Front Camera DNG No 18.0 2025
+Apple iPhone 17 Pro Max Telephoto Camera DNG No 18.0 2025
+Apple iPhone 17 Pro Max Ultrawide Camera DNG No 18.0 2025
+Apple iPhone 17 Pro Max Wide Camera DNG No 18.0 2025
+Apple iPhone Air Front Camera DNG No 18.0 2025
+Apple iPhone Air Wide Camera DNG No 18.0 2025
+Apple iPhone 16e Front Camera DNG No 17.3 2024
+Apple iPhone 16e Wide Camera DNG No 17.3 2024
+Apple iPhone 16 Front Camera DNG No 17.0 2024
+Apple iPhone 16 Ultrawide Camera DNG No 17.0 2024
+Apple iPhone 16 Wide Camera DNG No 17.0 2024
+Apple iPhone 16 Plus Front Camera DNG No 17.0 2024
+Apple iPhone 16 Plus Ultrawide Camera DNG No 17.0 2024
+Apple iPhone 16 Plus Wide Camera DNG No 17.0 2024
+Apple iPhone 16 Pro Front Camera DNG No 17.0 2024
+Apple iPhone 16 Pro Telephoto Camera DNG No 17.0 2024
+Apple iPhone 16 Pro Ultrawide Camera DNG No 17.0 2024
+Apple iPhone 16 Pro Wide Camera DNG No 17.0 2024
+Apple iPhone 16 Pro Max Front Camera DNG No 17.0 2024
+Apple iPhone 16 Pro Max Telephoto Camera DNG No 17.0 2024
+Apple iPhone 16 Pro Max Ultrawide Camera DNG No 17.0 2024
+Apple iPhone 16 Pro Max WIde Camera DNG No 17.0 2024
+Apple iPhone 15 Plus Ultrawide Camera DNG No 16.0 2023
+Apple iPhone 15 Pro Telephoto Camera DNG Apple ProRAW 16.0 2023
+Apple iPhone 15 Pro Front Camera DNG Apple ProRAW 16.0 2023
+Apple iPhone 15 Pro Wide Camera DNG Apple ProRAW 16.0 2023
+Apple iPhone 15 Pro Ultrawide Camera DNG Apple ProRAW 16.0 2023
+Apple iPhone 15 Pro Max Front Camera DNG Apple ProRAW 16.0 2023
+Apple iPhone 15 Pro Max Telephoto Camera DNG Apple ProRAW 16.0 2023
+Apple iPhone 15 Pro Max Ultrawide Camera DNG Apple ProRAW 16.0 2023
+Apple iPhone 15 Pro Max Wide Camera DNG Apple ProRAW 16.0 2023
+Apple iPhone 14 Front Camera DNG No 15.0 2022
+Apple iPhone 14 Ultrawide Camera DNG No 15.0 2022
+Apple iPhone 14 Wide Camera DNG No 15.0 2022
+Apple iPhone 14 Plus Front Camera DNG No 15.0 2022
+Apple iPhone 14 Plus Ultrawide Camera DNG No 15.0 2022
+Apple iPhone 14 Plus Wide Camera DNG No 15.0 2022
+Apple iPhone 14 Pro Front Camera DNG Apple ProRAW 15.0 2022
+Apple iPhone 14 Pro Telephoto Camera DNG Apple ProRAW 15.0 2022
+Apple iPhone 14 Pro Ultrawide Camera DNG Apple ProRAW 15.0 2022
+Apple iPhone 14 Pro Wide Camera DNG Apple ProRAW 15.0 2022
+Apple iPhone 14 Pro Max Front Camera DNG Apple ProRAW 15.0 2022
+Apple iPhone 14 Pro Max Telephoto Camera DNG Apple ProRAW 15.0 2022
+Apple iPhone 14 Pro Max Ultrawide Camera DNG Apple ProRAW 15.0 2022
+Apple iPhone 14 Pro Max Wide Camera DNG Apple ProRAW 15.0 2022
+Apple iPhone 13 Front Camera DNG No 14.0 2021
+Apple iPhone 13 Rear Ultrawide Camera DNG No 14.0 2021
+Apple iPhone 13 Rear Wide Camera DNG No 14.0 2021
+Apple iPhone 13 mini Front Camera DNG No 14.0 2021
+Apple iPhone 13 mini Rear Ultrawide Camera DNG No 14.0 2021
+Apple iPhone 13 mini Rear Wide Camera DNG No 14.0 2021
+Apple iPhone 13 Pro Front Camera DNG Apple ProRAW 14.0 2021
+Apple iPhone 13 Pro Rear Telephoto Camera DNG Apple ProRAW 14.0 2021
+Apple iPhone 13 Pro Rear Ultrawide Camera DNG Apple ProRAW 14.0 2021
+Apple iPhone 13 Pro Rear Wide Camera DNG Apple ProRAW 14.0 2021
+Apple iPhone 13 Pro Max Front Camera DNG Apple ProRAW 14.0 2021
+Apple iPhone 13 Pro Max Rear Telephoto Camera DNG Apple ProRAW 14.0 2021
+Apple iPhone 13 Pro Max Rear Ultrawide Camera DNG Apple ProRAW 14.0 2021
+Apple iPhone 13 Pro Max Rear Wide Camera DNG Apple ProRAW 14.0 2021
+Apple iPhone 12 Rear Wide Camera DNG No 13.1 2020
+Apple iPhone 12 Mini Rear Wide Camera DNG No 13.1 2020
+Apple iPhone 12 Ultrawide camera DNG No 14.1 2021
+Apple iPhone 12 Mini Ultrawide camera DNG No 14.1 2021
+Apple iPhone 12 Pro Front Camera DNG No 13.1 2020
+Apple iPhone 12 Pro Rear Wide Camera DNG Apple ProRAW 13.1 2020
+Apple iPhone 12 Pro Rear Telephoto Camera DNG Apple ProRAW 13.1 2020
+Apple iPhone 12 Pro Rear Ultra Wide Camera DNG Apple ProRAW 13.1 2020
+Apple iPhone 12 Pro Max Front Camera DNG No 13.1 2020
+Apple iPhone 12 Pro Max Rear Wide Camera DNG Apple ProRAW 13.1 2020
+Apple iPhone 12 Pro Max Rear Telephoto Camera DNG Apple ProRAW 13.1 2020
+Apple iPhone 12 Pro Max Rear Ultra Wide Camera DNG Apple ProRAW 13.1 2020
+Apple iPhone 11 DNG No 12.0 2019
+Apple iPhone 11 Pro DNG No 12.0 2019
+Apple iPhone 11 Pro Max DNG No 12.0 2019
+Apple iPad Pro 9.7" (Wifi and Cellular) DNG No 9.7 2015
+Apple iPad Pro 11-inch (2nd generation) DNG No 12.3 2019
+Apple iPad Pro 11-inch (3rd generation) DNG No 13.4 2020
+Apple iPad Pro 11-inch (4th generation) Wide Camera DNG No 15.1 2022
+Apple iPad Pro 11-inch (2018 model) DNG No 11.1 2018
+Apple iPad Pro 11-inch M4 (2024 version) Wide Camera DNG No 16.4 2023
+Apple iPad Air 11-inch M2 (2024 version) Wide Camera DNG No 16.4 2023
+Apple iPad Pro 12.9-inch (4th generation) DNG No 12.3 2019
+Apple iPad Pro 12.9-inch (5th generation) DNG No 13.4 2020
+Apple iPad Pro 12.9-inch (6th generation) Wide Camera DNG No 15.1 2022
+Apple iPad Pro 12.9-inch (2018 model) DNG No 11.1 2018
+Apple iPad Pro 13-inch M4 (2024 version) Wide Camera DNG No 16.4 2023
+Apple iPad mini (6th Generation) Rear Camera DNG No 14.0 2021
+Apple iPhone 6s DNG No 9.7 2015
+Apple iPhone 6s Plus DNG No 9.7 2015
+Apple iPhone 7 DNG No 9.7 2015
+Apple iPhone 7 Plus DNG No 9.7 2015
+Apple iPhone 8 DNG No 10.1 2017
+Apple iPhone 8 Plus DNG No 10.1 2017
+Apple iPhone SE (2020 version) DNG No 12.3 2019
+Apple iPhone SE DNG No 9.7 2015
+Apple iPhone X DNG No 10.1 2017
+Apple iPhone XR DNG No 11.1 2018
+Apple iPhone XS DNG No 11.1 2018
+Apple iPhone XS Max DNG No 11.1 2018
+Apple iPad Air (5th generation) Rear Camera DNG No 14.3 2021
+Apple iPhone SE (3rd generation) Front Camera DNG No 14.3 2021
+Apple iPhone SE (3rd generation) Rear Camera DNG No 14.3 2021
+Apple iPad (10th Generation) Wide Camera DNG No 15.1 2022
+Apple iPad mini (A17 Pro) Rear Camera DNG No 17.0.1 2024
+Canon EOS-1D TIF Yes 1.0 2003
+Canon EOS-1D C CR2 Yes 7.4 2012
+Canon EOS-1D Mark II CR2 Yes 2.2 2003
+Canon EOS-1D Mark II N CR2 Yes 3.3 2005
+Canon EOS-1D Mark III CR2 Yes 4.1 2007
+Canon EOS-1D Mark IV CR2 Yes 5.6 2008
+Canon EOS-1Ds TIF Yes 1.0 2003
+Canon EOS-1Ds Mark II CR2 Yes 3.0 2005
+Canon EOS-1Ds Mark III CR2 Yes 4.3 2007
+Canon EOS-1D X CR2 Yes 7.1 2012
+Canon EOS-1D X Mark II CR2 Yes 9.5 2015
+Canon EOS-1D X Mark III CR3 Yes 12.2 2019
+Canon EOS 5D CR2 Yes 3.3 2005
+Canon EOS 5D Mark II CR2 Yes 5.2 2008
+Canon EOS-5D Mark III CR2 Yes 7.1 2012
+Canon EOS-5D Mark IV (** see note) CR2 Yes 9.7 2015
+Canon EOS 5DS CR2 Yes 9.0 2015
+Canon EOS 5DS R CR2 Yes 9.0 2015
+Canon EOS 6D CR2 Yes 7.3 2012
+Canon EOS 6D Mark II CR2 Yes 9.12 2015
+Canon EOS 7D CR2 Yes 5.6 2008
+Canon EOS 7D Mark II CR2 Yes 8.7 2013
+Canon EOS 10D CRW Yes 2.0 2003
+Canon EOS 20D CR2 Yes 2.4 2003
+Canon EOS 20Da CR2 Yes 3.3 2005
+Canon EOS 30D CR2 Yes 3.4 2005
+Canon EOS 40D CR2 Yes 4.2 2007
+Canon EOS 50D CR2 Yes 5.1 2008
+Canon EOS 60D CR2 Yes 6.2 2010
+Canon EOS 60Da CR2 Yes 7.1 2012
+Canon EOS 70D CR2 Yes 8.2 2013
+Canon EOS 77D (EOS 9000D) CR2 Yes 9.10 2015
+Canon EOS 80D CR2 Yes 9.5 2015
+Canon EOS 90D CR3 Yes 11.4 2018
+Canon EOS 200D (EOS Kiss X9, EOS Rebel SL2) CR2 Yes 9.12 2015
+Canon EOS 250D (EOS Rebel SL3, EOS 200D II, EOS Kiss X10) CR3 Yes 11.3 2018
+Canon EOS D30 CRW Yes 1.0 2003
+Canon EOS D60 CRW Yes 1.0 2003
+Canon EOS 300D (EOS Digital Rebel, EOS Kiss Digital) CRW Yes 2.0 2003
+Canon EOS 100D (EOS Digital Rebel SL1, EOS Kiss X7) CR2 Yes 7.4 2012
+Canon EOS 500D (EOS Digital Rebel T1i, EOS Kiss X3 Digital) CR2 Yes 5.4 2008
+Canon EOS 550D (EOS Digital Rebel T2i, EOS Kiss X4 Digital) CR2 Yes 6.1 2010
+Canon EOS 1100D (EOS Digital Rebel T3, EOS Kiss X50 Digital) CR2 Yes 6.4 2010
+Canon EOS 600D (EOS Digital Rebel T3i, EOS Kiss X5 Digital) CR2 Yes 6.4 2010
+Canon EOS 650D (EOS Digital Rebel T4i, EOS Kiss X4i) CR2 Yes 7.1 2012
+Canon EOS 700D (EOS Digital Rebel T5i, EOS Kiss X7i) CR2 Yes 7.4 2012
+Canon EOS 1200D (EOS Digital Rebel T5, EOS Kiss X70) CR2 Yes 8.4 2013
+Canon EOS 1300D (EOS Digital Rebel T6, EOS Kiss X80) CR2 Yes 9.5 2015
+Canon EOS 1500D (EOS Rebel T7, EOS Kiss X90, EOS 2000D) CR2 Yes 10.3 2017
+Canon EOS 3000D (EOS Rebel T100, EOS 4000D) CR2 Yes 10.3 2017
+Canon EOS 750D (EOS Digital Rebel T6i, EOS Kiss X8i) CR2 Yes 9.0 2015
+Canon EOS 760D (EOS Digital Rebel T6s, EOS Kiss 8000D) CR2 Yes 9.0 2015
+Canon EOS 800D (EOS Digital Rebel T7i, EOS Kiss X9i) CR2 Yes 9.10 2015
+Canon EOS 850D (EOS Kiss X10i, EOS Rebel T8i) CR3 Yes 12.2.1 2019
+Canon EOS 1000D (EOS Digital Rebel XS, EOS Kiss F) CR2 Yes 5.1 2008
+Canon EOS 450D (EOS Digital Rebel XSi, EOS Kiss Digital X2) CR2 Yes 4.4 2007
+Canon EOS 350D (EOS Digital Rebel XT, EOS Kiss Digital N) CR2, CRW Yes 3.1 2005
+Canon EOS 400D (EOS Digital Rebel XTi, EOS Kiss Digital X) CR2 Yes 3.6 2005
+Canon EOS C50 CR3 Yes 18.0 2025
+Canon EOS M CR2 Yes 7.2 2012
+Canon EOS M2 CR2 Yes 8.3 2013
+Canon EOS M200 CR3 Yes 12.1 2019
+Canon EOS M3 CR2 Yes 9.0 2015
+Canon EOS M5 CR2 Yes 9.8 2015
+Canon EOS M50 Mark II CR3 Yes 13.1 2020
+Canon EOS M6 CR2 Yes 9.10 2015
+Canon EOS M6 Mark II CR3 Yes 11.4 2018
+Canon EOS M10 CR2 Yes 9.3 2015
+Canon EOS M50 (EOS Kiss M) CR3 Yes 10.3 2017
+Canon EOS M100 CR2 Yes 10.1 2017
+Canon EOS R (** see note) CR3 Yes 11.0 2018
+Canon EOS R1 CR3 Yes 16.5 2023
+Canon EOS R3 CR3 Yes 14.0 2021
+Canon EOS R5 CR3 Yes 12.4 2019
+Canon EOS R5 C CR3 Yes 14.3 2021
+Canon EOS R5 Mark II CR3 Yes 16.4 2023
+Canon EOS R6 CR3 Yes 12.4 2019
+Canon EOS R6 V CR3 Yes 18.4 2025
+Canon EOS R6 Mark II CR3 Yes 15.0 2022
+Canon EOS R6 Mark III CR3 Yes 18.0 2025
+Canon EOS R7 CR3 Yes 14.4 2021
+Canon EOS R8 Mark II CR3 Yes 18.5 2025
+Canon EOS R10 CR3 Yes 14.4 2021
+Canon EOS Ra CR3 Yes 12.1 2019
+Canon EOS RP CR3 Yes 11.2.1 2018
+Canon EOS R8 CR3 Yes 15.2 2022
+Canon EOS R50 CR3 Yes 15.2 2022
+Canon EOS R50 V CR3 Yes 17.3 2024
+Canon EOS R100 CR3 Yes 15.3 2022
+Canon PowerShot 600 CRW No 1.0 2003
+Canon PowerShot A5 CRW No 1.0 2003
+Canon PowerShot A50 CRW No 1.0 2003
+Canon PowerShot G1 CRW No 1.0 2003
+Canon PowerShot G1 X CR2 Yes 7.1 2012
+Canon PowerShot G1 X Mark II CR2 Yes 8.5 2013
+Canon PowerShot G1 X Mark III CR2 Yes 10.1 2017
+Canon PowerShot G2 CRW No 1.0 2003
+Canon PowerShot G3 CRW No 1.0 2003
+Canon PowerShot G3 X CR2 Yes 9.1.1 2015
+Canon PowerShot G5 CRW No 2.0 2003
+Canon PowerShot G5 X CR2 Yes 9.3 2015
+Canon PowerShot G5 X Mark II CR3 Yes 11.4 2018
+Canon PowerShot G6 CRW Yes 2.4 2003
+Canon PowerShot G7 X CR2 Yes 8.7 2013
+Canon PowerShot G7 X Mark II CR2 Yes 9.6 2015
+Canon PowerShot G7 X Mark III CR3 Yes 11.4 2018
+Canon PowerShot G9 CR2 Yes 4.3 2007
+Canon PowerShot G9X CR2 Yes 9.3 2015
+Canon PowerShot G9 X Mark II CR2 Yes 9.9 2015
+Canon PowerShot G10 CR2 Yes 5.2 2008
+Canon PowerShot G11 CR2 Yes 5.6 2008
+Canon PowerShot G12 CR2 Yes 6.3 2010
+Canon PowerShot G15 CR2 Yes 7.3 2012
+Canon PowerShot G16 CR2 Yes 8.2 2013
+Canon PowerShot Pro 1 CRW No 2.2 2003
+Canon PowerShot Pro70 CRW No 1.0 2003
+Canon PowerShot Pro90 IS CRW No 1.0 2003
+Canon PowerShot S30 CRW No 1.0 2003
+Canon PowerShot S40 CRW No 1.0 2003
+Canon PowerShot S45 CRW No 1.0 2003
+Canon PowerShot S50 CRW No 2.0 2003
+Canon PowerShot S60 CRW No 2.3 2003
+Canon PowerShot S90 CR2 Yes 5.6 2008
+Canon PowerShot S95 CR2 Yes 6.3 2010
+Canon PowerShot S70 CRW No 2.4 2003
+Canon PowerShot S100 CR2 Yes 6.6 2010
+Canon PowerShot S100V CR2 No 6.7 2010
+Canon PowerShot S110 CR2 Yes 7.3 2012
+Canon PowerShot S120 CR2 Yes 8.3 2013
+Canon PowerShot SX1 IS CR2 Yes 5.4 2008
+Canon PowerShot SX50 CR2 Yes 7.3 2012
+Canon PowerShot SX60 HS CR2 Yes 8.7 2013
+Canon PowerShot SX70 HS CR3 Yes 11.1 2018
+Canon PowerShot V1 CR3 Yes 17.2 2024
+Casio EXILIM EX-10 DNG No 8.3 2013
+Casio EXILIM EX-100 DNG No 8.4 2013
+Casio EXILIM EX-100F DNG No 9.1.1 2015
+Casio EXILIM EX-100PRO DNG No 8.7 2013
+Casio EXILIM EX-FC300S DNG No 7.3 2012
+Casio EXILIM EX-FH100 DNG No 6.2 2010
+Casio EXILIM EX-ZR700 DNG No 7.4 2012
+Casio EXILIM EX-ZR710 DNG No 7.4 2012
+Casio EXILIM EX-ZR750 DNG No 7.4 2012
+Casio EXILIM EX-ZR800 DNG No 8.2 2013
+Casio EXILIM EX-ZR850 DNG No 8.4 2013
+Casio EXILIM EX-ZR1000 DNG No 7.3 2012
+Casio EXILIM EX-ZR1100 DNG No 8.2 2013
+Casio EXILIM EX-ZR1200 DNG No 8.2 2013
+Casio EXILIM EX-ZR1300 DNG No 8.5 2013
+Casio EXILIM EX-ZR1500 DNG No 8.5 2013
+Casio EXILIM EX-ZR3000 DNG No 9.1.1 2015
+Casio EXILIM EX-ZR3100 DNG No 9.5.1 2015
+Casio EXILIM EX-ZR3200 DNG No 9.9 2015
+Casio EXILIM EX-ZR3500 DNG No 9.0 2015
+Casio EXILIM EX-ZR3600 DNG No 9.5.1 2015
+Casio EXILIM EX-ZR3700 DNG No 9.9 2015
+Casio EXILIM EX-ZR4000 DNG No 9.7 2015
+Casio EXILIM EX-ZR4100 DNG No 10.0 2017
+Casio EXILIM EX-ZR5000 DNG No 9.7 2015
+Casio EXILIM EX-ZR5100 DNG No 10.0 2017
+Contax N Digital RAW No 2.3 2003
+DJI DJI Mavic 2 Pro (Hasselblad L1D-20c) DNG No 13.2 2020
+DJI DJI AIR 2S DNG No 13.3 2020
+DJI DJI Mini 2 DNG No 13.3 2020
+DJI DJI Air 3 Wide Camera DNG No 16.0.1 2023
+DJI DJI Air 3 Telephoto Camera DNG No 16.0.1 2023
+DJI DJI Mini 3 Pro DNG No 16.0.1 2023
+DJI DJI Mini 4 Pro DNG No 16.0.1 2023
+DJI DJI Mavic 3 Classic (Hasselblad L2D-20c) DNG No 16.0.1 2023
+DJI DJI Mavic 3 Cine Super Telephoto Camera DNG No 16.0.1 2023
+DJI DJI Mavic 3 Cine (Hasselblad L2D-20c) DNG No 16.0.1 2023
+DJI DJI Mavic 3 Pro Super Telephoto Camera DNG No 16.0.1 2023
+DJI DJI Mavic 3 Pro Telephoto Camera DNG No 16.0.1 2023
+DJI DJI Mavic 3 Pro (Hasselblad L2D-20c) DNG No 16.0.1 2023
+DJI DJI Mavic 3 (Hasselblad L2D-20c) DNG No 16.0.1 2023
+DJI DJI Osmo Pocket 3 D DNG No 16.0.1 2023
+DJI DJI Air 3S Telephoto Camera DNG No 17.1 2024
+DJI DJI Air 3S Wide Camera DNG No 17.1 2024
+DJI DJI Osmo Action 5 Pro DNG No 17.1 2024
+DJI DJI Mavic 4 Pro (DJI FC9284) DNG No 18.4 2025
+DJI DJI Mavic 4 Pro (DJI FC9287) DNG No 18.4 2025
+DJI DJI Mavic 4 Pro (Hasselblad L3D-100c) DNG No 18.4 2025
+DxO DxO ONE DNG No 9.2 2015
+Epson R-D1 ERF No 2.3 2003
+Epson R-D1s ERF No 3.4 2005
+Epson R-D1x ERF No 5.4 2008
+Fujifilm GFX 50R RAF Yes 10.5 2017
+Fujifilm GFX 50S RAF Yes 9.9 2015
+Fujifilm GFX 50S II RAF Yes 13.4 2020
+Fujifilm GFX 100 RAF Yes 11.4 2018
+Fujifilm GFX 100 II RAF Yes 15.5 2022
+Fujifilm GFX 100 II IR RAF Yes 17.5 2024
+Fujifilm GFX 100 IR RAF Yes 13.2 2020
+Fujifilm GFX 100S RAF Yes 13.2 2020
+Fujifilm GFX 100S II RAF Yes 16.2 2023
+Fujifilm GFX 100RF (Final Support) RAF Yes 17.3 2024
+Fujifilm FinePix E900 RAF No 3.3 2005
+Fujifilm FinePix F505 EXR RAF No 7.1 2012
+Fujifilm FinePix F550 EXR RAF No 6.4 2010
+Fujifilm FinePix F600 EXR RAF No 6.5 2010
+Fujifilm FinePix F605 EXR RAF No 7.1 2012
+Fujifilm FinePix F700 RAF No 2.1 2003
+Fujifilm FinePix F770 EXR RAF No 7.1 2012
+Fujifilm FinePix F775 EXR RAF No 7.1 2012
+Fujifilm FinePix F800 EXR RAF No 7.2 2012
+Fujifilm FinePix F900 EXR RAF No 7.4 2012
+Fujifilm FinePix HS10 RAF No 6.2 2010
+Fujifilm FinePix HS20 EXR RAF No 6.4 2010
+Fujifilm FinePix HS22 EXR RAF No 8.2 2013
+Fujifilm FinePix HS30 EXR RAF No 7.1 2012
+Fujifilm FinePix HS33 EXR RAF No 7.1 2012
+Fujifilm FinePix HS35 EXR RAF No 8.2 2013
+Fujifilm FinePix HS50 EXR RAF No 7.4 2012
+Fujifilm FinePix IS-1 RAF No 4.2 2007
+Fujifilm FinePix IS Pro RAF No 5.1 2008
+Fujifilm FinePix S1 RAF No 8.5 2013
+Fujifilm FinePix S100fs RAF No 4.4 2007
+Fujifilm FinePix S2 Pro RAF No 1.0 2003
+Fujifilm FinePix S20 Pro RAF No 2.3 2003
+Fujifilm FinePix S200 EXR RAF No 6.4 2010
+Fujifilm FinePix S205 EXR RAF No 8.2 2013
+Fujifilm FinePix S3 Pro RAF No 3.0 2005
+Fujifilm FinePix S5 Pro RAF No 4.1 2007
+Fujifilm FinePix S5000 Z RAF No 2.1 2003
+Fujifilm FinePix S5200 (FinePix S5600) RAF No 3.3 2005
+Fujifilm FinePix S6000fd (FinePix S6500fd) RAF No 3.6 2005
+Fujifilm FinePix S7000 Z RAF No 2.1 2003
+Fujifilm FinePix S9000 (FinePix S9500) RAF No 3.3 2005
+Fujifilm FinePix S9100 (FinePix S9600) RAF No 3.6 2005
+Fujifilm FinePix SL1000 RAF No 7.4 2012
+Fujifilm FinePix X10 RAF Yes 6.6 2010
+Fujifilm FinePix X100 RAF Yes 6.4 2010
+Fujifilm FinePix X-M1 RAF Yes 8.2 2013
+Fujifilm FinePix X-S1 RAF Yes 7.0 2012
+Fujifilm X100F RAF Yes 9.9 2015
+Fujifilm X100S RAF Yes 7.4 2012
+Fujifilm X100T RAF Yes 8.7 2013
+Fujifilm X100V** RAF Yes 12.1 2019
+Fujifilm X100VI RAF Yes 16.2 2023
+Fujifilm X20 RAF Yes 7.4 2012
+Fujifilm X30 RAF Yes 8.7 2013
+Fujifilm X70 RAF Yes 9.4 2015
+Fujifilm X-A1 RAF Yes 8.2 2013
+Fujifilm X-A2 RAF Yes 9.0 2015
+Fujifilm X-A3 RAF Yes 9.8 2015
+Fujifilm X-A5 RAF Yes 10.2 2017
+Fujifilm X-A7 RAF Yes 11.4.1 2018
+Fujifilm X-A10 RAF Yes 9.9 2015
+Fujifilm X-A20 RAF Yes 10.2 2017
+Fujifilm X-E1 RAF Yes 7.2 2012
+Fujifilm X-E2 RAF Yes 8.3 2013
+Fujifilm X-E2S RAF Yes 9.4 2015
+Fujifilm X-E3 RAF Yes 10.0 2017
+Fujifilm X-E4 RAF Yes 13.2 2020
+Fujifilm X-E5 RAF Yes 17.4 2024
+Fujifilm X-H1 RAF Yes 10.2 2017
+Fujifilm X-H2 IR RAF Yes 17.5 2024
+Fujifilm X-H2S RAF Yes 14.4 2021
+Fujifilm X-M5 RAF Yes 17.0 2024
+Fujifilm X-Pro1 RAF Yes 7.1 2012
+Fujifilm X-Pro2 RAF Yes 9.4 2015
+Fujifilm X-Pro3** RAF Yes 12.0 2019
+Fujifilm X-S10 RAF Yes 13.0 2020
+Fujifilm X-S20 RAF Yes 15.3 2022
+Fujifilm X-T1 RAF Yes 8.4 2013
+Fujifilm X-T1 IR RAF Yes 9.3 2015
+Fujifilm X-T2 RAF Yes 9.6 2015
+Fujifilm X-T3 RAF Yes 11.0 2018
+Fujifilm X-T4** RAF Yes 12.2 2019
+Fujifilm X-T5 RAF Yes 15.0 2022
+Fujifilm X-T10 RAF Yes 9.1 2015
+Fujifilm X-T20 RAF Yes 9.9 2015
+Fujifilm X-T30 RAF Yes 11.2 2018
+Fujifilm X-T30 II RAF Yes 14.0 2021
+Fujifilm X-T30 III RAF Yes 15.0 2022
+Fujifilm X-T50 RAF Yes 16.3 2023
+Fujifilm X-T100 RAF Yes 10.4 2017
+Fujifilm X-T200 RAF Yes 12.1 2019
+Fujifilm XF1 RAF Yes 7.2 2012
+Fujifilm XF10 RAF Yes 10.4 2017
+Fujifilm XQ1 RAF Yes 8.3 2013
+Fujifilm XQ2 RAF Yes 9.0 2015
+Fujifilm X-H2 RAF Yes 14.5 2021
+Google Pixel DNG No 9.8 2015
+Google Pixel XL DNG No 9.8 2015
+Google Pixel 2 DNG No 10.1 2017
+Google Pixel 2 XL DNG No 10.1 2017
+Google Pixel 3 DNG No 11.1 2018
+Google Pixel 3 Front Standard Camera DNG No 11.3 2018
+Google Pixel 3 Front Wide Camera DNG No 11.3 2018
+Google Pixel 3 XL DNG No 11.1 2018
+Google Pixel 3 XL Front Standard Camera DNG No 11.3 2018
+Google Pixel 3 XL Front Wide Camera DNG No 11.3 2018
+Google Pixel 4 DNG No 12.1 2019
+Google Pixel 4 XL DNG No 12.1 2019
+Google Pixel 5 Front Camera DNG No 13.1 2020
+Google Pixel 5 Rear Main Camera DNG No 13.1 2020
+Google Pixel 5 Rear Ultra Wide Camera DNG No 13.1 2020
+Google Pixel 6 Front Camera DNG No 14.1 2021
+Google Pixel 6 Rear Main Camera DNG No 14.1 2021
+Google Pixel 6 Rear Ultrawide Camera DNG No 14.1 2021
+Google Pixel 6 Pro Front Camera DNG No 14.1 2021
+Google Pixel 6 Pro Rear Main Camera DNG No 14.1 2021
+Google Pixel 6 Pro Rear Telephoto Camera DNG No 14.1 2021
+Google Pixel 6 Pro Rear Ultrawide Camera DNG No 14.1 2021
+Google Pixel 7 Front Camera DNG No 15.0 2022
+Google Pixel 7 Ultrawide Camera DNG No 15.0 2022
+Google Pixel 7 Wide Camera DNG No 15.0 2022
+Google Pixel 7 Pro Front Camera DNG No 15.0 2022
+Google Pixel 7 Pro Telephoto Camera DNG No 15.0 2022
+Google Pixel 7 Pro Ultrawide Camera DNG No 15.0 2022
+Google Pixel 7 Pro Wide Camera DNG No 15.0 2022
+Google Pixel 4a Front Camera DNG No 15.3 2022
+Google Pixel 4a Rear Wide Camera DNG No 15.3 2022
+Google Pixel 5a Front Camera DNG No 15.3 2022
+Google Pixel 5a Rear Main Camera DNG No 15.3 2022
+Google Pixel 5a Rear Ultra Wide Camera DNG No 15.3 2022
+Google Pixel 6a Front Camera DNG No 15.3 2022
+Google Pixel 6a Rear Main Camera DNG No 15.3 2022
+Google Pixel 6a Rear Ultrawide Camera DNG No 15.3 2022
+Google Pixel 7a Front Camera DNG No 15.4.1 2022
+Google Pixel 7a Ultrawide Camera DNG No 15.4.1 2022
+Google Pixel 7a Wide Camera DNG No 15.4.1 2022
+Google Pixel 8 Front Camera* DNG No 16.0.1 2023
+Google Pixel 8 Ultrawide Camera* DNG No 16.0.1 2023
+Google Pixel 8 Wide Camera* DNG No 16.0.1 2023
+Google Pixel 8 Pro Front Camera* DNG No 16.0.1 2023
+Google Pixel 8 Pro Telephoto Camera* DNG No 16.0.1 2023
+Google Pixel 8 Pro Ultrawide Camera* DNG No 16.0.1 2023
+Google Pixel 8 Pro Wide Camera* DNG No 16.0.1 2023
+Google Pixel 8a Front Camera DNG No 16.4 2023
+Google Pixel 8a Ultrawide Camera DNG No 16.4 2023
+Google Pixel 8a Wide Camera DNG No 16.4 2023
+Google Pixel 9 Front Camera DNG No 16.5 2023
+Google Pixel 9 Ultrawide Camera DNG No 16.5 2023
+Google Pixel 9 Wide Camera DNG No 16.5 2023
+Google Pixel 9 Pro Front Camera DNG No 16.5 2023
+Google Pixel 9 Pro Telephoto Camera DNG No 16.5 2023
+Google Pixel 9 Pro Ultrawide Camera DNG No 16.5 2023
+Google Pixel 9 Pro Wide Camera DNG No 16.5 2023
+Google Pixel 9 Pro XL Front Camera DNG No 16.5 2023
+Google Pixel Pixel 9 Pro XL Telephoto Camera DNG No 16.5 2023
+Google Pixel Pixel 9 Pro XL Ultrawide Camera DNG No 16.5 2023
+Google Pixel 9 Pro XL Wide Camera DNG No 16.5 2023
+Google Pixel 9 Pro Fold Front Camera DNG No 16.5 2023
+Google Pixel 9 Pro Fold Telephoto Camera DNG No 16.5 2023
+Google Pixel 9 Pro Fold Ultrawide Camera DNG No 16.5 2023
+Google Pixel 9 Pro Fold Wide Camera DNG No 16.5 2023
+Google Pixel 9a Front Camera DNG Google Pixel 17.4 2024
+Google Pixel 9a Ultrawide Camera DNG Google Pixel 17.4 2024
+Google Pixel 9a Wide Camera DNG Google Pixel 17.4 2024
+Google Pixel 10 Pro Front Camera DNG No 18.0 2025
+Google Pixel 10 Pro Telephoto Camera DNG No 18.0 2025
+Google Pixel 10 Pro Ultrawide Camera DNG No 18.0 2025
+Google Pixel 10 Pro Wide Camera DNG No 18.0 2025
+Google Pixel 10 Pro XL Front Camera DNG No 18.0 2025
+Google Pixel 10 Pro XL Telephoto Camera DNG No 18.0 2025
+Google Pixel 10 Pro XL Ultrawide Camera DNG No 18.0 2025
+Google Pixel 10 Pro XL Wide Camera DNG No 18.0 2025
+Google Pixel 10 Front Camera DNG Google Pixel 18.1.1 2025
+Google Pixel 10 Telephoto Camera DNG Google Pixel 18.1.1 2025
+Google Pixel 10 Ultrawide Camera DNG Google Pixel 18.1.1 2025
+Google Pixel 10 Wide Camera DNG Google Pixel 18.1.1 2025
+Google Pixel 10 Pro Fold Front Camera DNG Google Pixel 18.1.1 2025
+Google Pixel 10 Pro Fold Telephoto Camera DNG Google Pixel 18.1.1 2025
+Google Pixel 10 Pro Fold Ultrawide Camera DNG Google Pixel 18.1.1 2025
+Google Pixel 10 Pro Fold Wide Camera DNG Google Pixel 18.1.1 2025
+Google Pixel Fold Inner Camera DNG Google Pixel 15.5 2022
+Google Pixel Fold Telephoto Camera DNG Google Pixel 15.5 2022
+Google Pixel Fold Ultrawide Camera DNG Google Pixel 15.5 2022
+Google Pixel Fold Wide Camera DNG Google Pixel 15.5 2022
+GoPro FUSION GPR No 10.1 2017
+GoPro HERO5 Black GPR No 9.7 2015
+GoPro HERO6 Black GPR No 10.1 2017
+GoPro HERO7 Black GPR No 11.1 2018
+GoPro HERO10 Black GPR No 14.1 2021
+GoPro HERO12 Black GPR No 17.0 2024
+GoPro HERO13 Black GPR No 17.0 2024
+GoPro MISSION 1 GPR No 18.5 2025
+GoPro MISSION 1 PRO GPR No 18.5 2025
+Hasselblad 503CWD 3FR No 5.4 2008
+Hasselblad A6D-100c 3FR Yes 10.1 2017
+Hasselblad CF-22 3FR No 5.4 2008
+Hasselblad CF-22MS 3FR No 5.4 2008
+Hasselblad CF-39 3FR No 5.4 2008
+Hasselblad CF-39MS 3FR No 5.4 2008
+Hasselblad CFH-22 3FR No 5.4 2008
+Hasselblad CFH-39 3FR No 5.4 2008
+Hasselblad CFV 3FR No 5.4 2008
+Hasselblad CFV-50c 3FR No 5.4 2008
+Hasselblad H2D DNG No 2.3 2003
+Hasselblad H2D-22 3FR No 5.4 2008
+Hasselblad H2D-39 3FR No 5.4 2008
+Hasselblad H3D-22 3FR No 5.4 2008
+Hasselblad H3D-39 3FR No 5.4 2008
+Hasselblad H3DII-22 3FR No 5.4 2008
+Hasselblad H3DII-31 3FR No 5.4 2008
+Hasselblad H3DII-39 3FR No 5.4 2008
+Hasselblad H3DII-39MS 3FR No 5.4 2008
+Hasselblad H3DII-50 3FR No 5.4 2008
+Hasselblad H4D-40 3FR No 6.4 2010
+Hasselblad H4D-60 3FR Yes 6.5 2010
+Hasselblad H5D-40 3FR No 7.4 2012
+Hasselblad H5D-50 3FR No 7.4 2012
+Hasselblad H5D-50c 3FR, FFF (** see note) Yes 8.4 2013
+Hasselblad H5D-60 3FR No 8.1 2013
+Hasselblad H6D-100c 3FR No 9.7 2015
+Hasselblad HV ARW Yes 8.4 2013
+Hasselblad Lunar 3FR Yes 7.4 2012
+Hasselblad Stellar II 3FR Yes 9.0 2015
+Hasselblad X1D 3FR Yes 9.8 2015
+Hasselblad X2D II 100C (**) 3FR Yes 17.5 2024
+Hasselblad X1D II 50C 3FR Yes 11.4 2018
+Hasselblad X2D 100C 3FR Yes 14.3 2021
+Huawei Honor 20 Pro Rear Main Camera DNG No 12.0 2019
+Huawei Honor 20 Pro Rear Telephoto Camera DNG No 12.0 2019
+Huawei Honor 20 Pro Rear Wide Camera DNG No 12.0 2019
+Huawei Mate 10 Pro DNG No 12.0 2019
+Huawei P30 Rear Main Camera DNG No 12.0 2019
+Huawei P30 Rear Telephoto Camera DNG No 12.0 2019
+Huawei P30 Rear Wide Camera DNG No 12.0 2019
+Huawei P30 Pro Rear Main Camera DNG No 12.0 2019
+Huawei P30 Pro Rear Telephoto Camera DNG No 12.0 2019
+Huawei P30 Pro Rear Wide Camera DNG No 12.0 2019
+Huawei P9 Lite DNG No 11.1 2018
+Insta360 Ace Pro 2 DNG No 18.0 2025
+Insta360 GO Ultra DNG No 18.0 2025
+Kodak DCS 14n DCR No 2.1 2003
+Kodak DCS 720x DCR No 2.1 2003
+Kodak DCS 760 DCR No 2.1 2003
+Kodak DCS Pro 14nx DCR No 2.2 2003
+Kodak DCS Pro SLR/n DCR No 2.2 2003
+Kodak EasyShare P712 DCR No 3.5 2005
+Kodak EasyShare P850 KDC No 3.3 2005
+Kodak EasyShare P880 KDC No 3.3 2005
+Kodak EasyShare Z1015 IS KDC No 5.1 2008
+Kodak EasyShare Z980 KDC No 5.4 2008
+Kodak EasyShare Z981 RAW No 6.1 2010
+Kodak EasyShare Z990 RAW No 6.4 2010
+Konica Minolta Alpha-5 Digital (China) MRW No 3.2 2005
+Konica Minolta Alpha Sweet Digital (Japan) MRW No 3.2 2005
+Konica Minolta DiMAGE 5 MRW No 1.0 2003
+Konica Minolta DiMAGE 7 MRW No 1.0 2003
+Konica Minolta DiMAGE 7i MRW No 1.0 2003
+Konica Minolta DiMAGE 7Hi MRW No 1.0 2003
+Konica Minolta DiMAGE A1 MRW No 2.0 2003
+Konica Minolta DiMAGE A2 MRW No 2.2 2003
+Konica Minolta DiMAGE A200 MRW No 2.4 2003
+Konica Minolta Maxxum 5D Dynax 5D MRW No 3.2 2005
+Konica Minolta Maxxum 7D Dynax 7D MRW No 2.4 2003
+Leaf AFi II 6 MOS No 5.1 2008
+Leaf AFi II 7 MOS No 5.1 2008
+Leaf Aptus 17 MOS No 4.2 2007
+Leaf Aptus 22 MOS No 3.2 2005
+Leaf Aptus 54s MOS No 4.2 2007
+Leaf Aptus 65 MOS No 3.4 2005
+Leaf Aptus 75 MOS No 3.4 2005
+Leaf Aptus 75s MOS No 4.2 2007
+Leaf Aptus-II 5 MOS No 5.6 2008
+Leaf Aptus-II 6 MOS No 5.1 2008
+Leaf Aptus-II 7 MOS No 5.1 2008
+Leaf Aptus-II 8 MOS No 6.1 2010
+Leaf Aptus-II 10R MOS No 6.1 2010
+Leaf Aptus-II 12 MOS No 6.5 2010
+Leaf Valeo 6 MOS No 2.0 2003
+Leaf Valeo 11 MOS No 2.0 2003
+Leaf Valeo 17 MOS No 3.2 2005
+Leaf Valeo 22 MOS No 2.1 2003
+Leaf Credo 40 IIQ No 7.2 2012
+Leaf Credo 50 IIQ No 8.7 2013
+Leaf Credo 60 IIQ No 7.2 2012
+Leaf Credo 80 IIQ No 7.1 2012
+Leica C (Typ 112) RWL No 8.2 2013
+Leica CL DNG No 10.1 2017
+Leica C-Lux DNG Yes 10.4 2017
+Leica D-Lux 2 RAW No 3.2 2005
+Leica D-Lux 3 RAW No 3.6 2005
+Leica D-Lux 4 RWL No 5.2 2008
+Leica D-Lux 5 RWL No 6.4 2010
+Leica D-Lux 6 RWL No 7.2 2012
+Leica D-Lux 7 RWL Yes 11.1 2018
+Leica D-Lux (Typ 109) RWL No 8.7 2013
+Leica D-Lux 8 DNG No 16.4 2023
+Leica D-Lux 8 "100 Years of Leica" DNG No 18.0 2025
+Leica Digilux 2 RAW No 2.1 2003
+Leica Digilux 3 RAW No 3.6 2005
+Leica Digital-Modul-R DNG No 2.3 2003
+Leica Leitz Phone 3 DNG No 16.3 2023
+Leica M8 DNG Yes 2.3 2003
+Leica M9 DNG No 5.6 2008
+Leica M10 DNG No 9.8 2015
+Leica M10-D DNG No 11.1 2018
+Leica M10-P DNG No 11.1 2018
+Leica M10-R DNG No 12.3 2019
+Leica M11-D DNG No 16.3 2023
+Leica M (Typ 262) DNG No 9.4 2015
+Leica M-D (Typ 262) DNG No 9.6 2015
+Leica M-E DNG No 7.3 2012
+Leica M-EV1 DNG No 17.5 2024
+Leica M-P DNG No 8.7 2013
+Leica M Monochrom DNG No 7.1 2012
+Leica M Monochrom (Typ 246) DNG No 9.0 2015
+Leica M10 Monochrom DNG No 12.2 2019
+Leica M11 DNG No 13.4 2020
+Leica M11 MONOCHROM DNG No 15.0 2022
+Leica M11-P DNG No 16.0 2023
+Leica Q (Typ 116) DNG No 9.1.1 2015
+Leica Q2 DNG No 11.2 2018
+Leica Q2 Monochrom DNG No 13.0 2020
+Leica Q3 DNG No 15.3 2022
+Leica Q3 43 DNG No 16.4 2023
+Leica Q3 Monochrom DNG No 18.0 2025
+Leica S2 DNG No 6.2 2010
+Leica S (Typ 007) DNG No 9.2 2015
+Leica S3 DNG No 12.2.1 2019
+Leica SL (Typ 601) DNG No 9.3 2015
+Leica SL2 DNG No 12.1 2019
+Leica SL2-S DNG No 13.0 2020
+Leica SL3 DNG No 16.2 2023
+Leica SL3-P DNG No 18.2 2025
+Leica SL3-S DNG No 17.0 2024
+Leica T (Typ 701) DNG No 8.3 2013
+Leica TL DNG No 9.8 2015
+Leica TL2 DNG No 9.12 2015
+Leica V-LUX 1 RAW No 3.6 2005
+Leica V-LUX 2 RAW No 6.4 2010
+Leica V-LUX 3 RAW No 6.6 2010
+Leica V-LUX 4 RWL No 7.2 2012
+Leica V-LUX 5 RWL Yes 11.4 2018
+Leica V-LUX (Typ 114) RWL No 8.7 2013
+Leica X2 RWL No 7.1 2012
+Leica X (Typ 113) DNG No 8.7 2013
+Leica X-U (Typ 113) DNG No 9.4 2015
+LG G7 ThinQ DNG No 11.1 2018
+LG G8 ThinQ Front Camera DNG No 12.2 2019
+LG G8 ThinQ Rear Main Camera DNG No 12.2 2019
+LG G8 ThinQ Rear Wide Camera DNG No 12.2 2019
+LG V40 ThinQ Front Standard Camera DNG No 11.3 2018
+LG V40 ThinQ Rear Main Camera DNG No 11.3 2018
+LG V40 ThinQ Rear Wide Camera DNG No 11.3 2018
+LG V50s ThinQ Front Camera DNG No 12.2 2019
+LG V50s ThinQ Rear Main Camera DNG No 12.2 2019
+LG V50s ThinQ Rear Wide Camera DNG No 12.2 2019
+LG V60 ThinQ Front Camera DNG No 13.1 2020
+LG V60 ThinQ Rear Main Camera DNG No 13.1 2020
+LG Velvet Front Camera DNG No 13.1 2020
+LG Velvet Rear Main Camera DNG No 13.1 2020
+LG Velvet Rear Wide Camera DNG No 13.1 2020
+LG Wing Rear Main Camera DNG No 13.1 2020
+LG Wing Front Camera DNG No 13.1 2020
+LG Wing Rear Wide Camera DNG No 13.1 2020
+Light L16 DNG No 10.1 2017
+Mamiya Mamiya DM22 MOS No 5.6 2008
+Mamiya Mamiya DM28 MOS No 5.6 2008
+Mamiya Mamiya DM33 MOS No 5.6 2008
+Mamiya Mamiya DM56 MOS No 5.6 2008
+Mamiya Mamiya M18 TIF, IIQ No 5.6 2008
+Mamiya Mamiya M22 TIF, IIQ No 5.6 2008
+Mamiya Mamiya M31 TIF, IIQ No 5.6 2008
+Mamiya Mamiya DM40 MOS No 6.1 2010
+Mamiya ZD MEF, MFW No 3.3 2005
+Motorola Moto G Power (2021) Front Camera DNG No 14.0 2021
+Motorola Moto G Power (2021) Rear Macro Camera DNG No 14.0 2021
+Motorola Moto G Power (2021) Rear Wide Camera DNG No 14.0 2021
+Nikon 1 J1 NEF Yes 6.6 2010
+Nikon 1 J2 NEF Yes 7.2 2012
+Nikon 1 J3 NEF Yes 7.4 2012
+Nikon 1 J4 NEF Yes 8.5 2013
+Nikon 1 J5 NEF Yes 9.1 2015
+Nikon 1 S1 NEF Yes 7.4 2012
+Nikon 1 S2 NEF No 8.5 2013
+Nikon 1 V1 NEF Yes 6.6 2010
+Nikon 1 V2 NEF Yes 7.3 2012
+Nikon 1 V3 NEF Yes 8.5 2013
+Nikon COOLPIX 5000 NEF No 1.0 2003
+Nikon COOLPIX 5400 NEF No 2.3 2003
+Nikon COOLPIX 5700 NEF No 1.0 2003
+Nikon COOLPIX 8400 NEF No 3.2 2005
+Nikon COOLPIX 8700 NEF No 2.2 2003
+Nikon COOLPIX 8800 NEF No 3.2 2005
+Nikon COOLPIX A NEF Yes 7.4 2012
+Nikon COOLPIX A1000 NRW No 11.2 2018
+Nikon COOLPIX B700 NRW Yes 9.6 2015
+Nikon COOLPIX P330 NRW No 7.4 2012
+Nikon COOLPIX P340 NRW No 8.4 2013
+Nikon COOLPIX P1000 NRW Yes 11.1 2018
+Nikon COOLPIX P1100 NRW Yes 17.2 2024
+Nikon COOLPIX P6000 NRW No 5.1 2008
+Nikon COOLPIX P7000 NRW No 6.3 2010
+Nikon COOLPIX P7100 NRW No 6.5 2010
+Nikon COOLPIX P7700 NRW No 7.2 2012
+Nikon COOLPIX P7800 NRW No 8.3 2013
+Nikon COOLPIX P950 NEF Yes 12.2 2019
+Nikon D1 NEF Yes 1.0 2003
+Nikon D1H NEF Yes 1.0 2003
+Nikon D1X NEF Yes 1.0 2003
+Nikon D2H NEF Yes 2.1 2003
+Nikon D2Hs NEF Yes 3.2 2005
+Nikon D2X NEF Yes 3.1 2005
+Nikon D2Xs NEF Yes 3.5 2005
+Nikon D3 NEF Yes 4.3 2007
+Nikon D3s NEF Yes 5.6 2008
+Nikon D3X NEF Yes 5.3 2008
+Nikon D4 NEF Yes 7.1 2012
+Nikon D4S NEF Yes 8.4 2013
+Nikon D5 NEF Yes 9.5 2015
+Nikon D40 NEF Yes 3.7 2005
+Nikon D40x NEF Yes 4.0 2007
+Nikon D50 NEF Yes 3.2 2005
+Nikon D60 NEF Yes 4.4 2007
+Nikon D6 NEF Yes 12.2.1 2019
+Nikon D70 NEF Yes 3.0 2005
+Nikon D70S NEF Yes 3.2 2005
+Nikon D80 NEF Yes 3.6 2005
+Nikon D90 NEF Yes 5.1 2008
+Nikon D100 NEF Yes 1.0 2003
+Nikon D200 NEF Yes 3.3 2005
+Nikon D300 NEF Yes 4.3 2007
+Nikon D300s NEF Yes 5.5 2008
+Nikon D500 NEF Yes 9.5 2015
+Nikon D600 NEF Yes 7.3 2012
+Nikon D610 NEF Yes 8.3 2013
+Nikon D700 NEF Yes 5.1 2008
+Nikon D750 NEF Yes 8.7 2013
+Nikon D780 NEF Yes 12.2 2019
+Nikon D800 NEF Yes 7.1 2012
+Nikon D800E NEF Yes 7.1 2012
+Nikon D810 NEF Yes 8.6 2013
+Nikon D810A NEF Yes 9.1 2015
+Nikon D850 NEF Yes 9.12.1 2015
+Nikon D3000 NEF Yes 5.5 2008
+Nikon D3100 NEF Yes 6.3 2010
+Nikon D3200 NEF Yes 7.1 2012
+Nikon D3300 NEF Yes 8.4 2013
+Nikon D3400 NEF Yes 9.7 2015
+Nikon D3500 NEF Yes 11.1 2018
+Nikon D5000 NEF Yes 5.4 2008
+Nikon D5100 NEF Yes 6.4 2010
+Nikon D5200 NEF Yes 7.3 2012
+Nikon D5300 NEF Yes 8.3 2013
+Nikon D5500 NEF Yes 9.0 2015
+Nikon D5600 NEF Yes 9.8 2015
+Nikon D7000 NEF Yes 6.3 2010
+Nikon D7100 NEF Yes 7.4 2012
+Nikon D7200 NEF Yes 9.0 2015
+Nikon D7500 NEF Yes 9.12 2015
+Nikon Df NEF Yes 8.3 2013
+Nikon Z 30 NEF Yes 14.4 2021
+Nikon Z 50 NEF Yes 12.1 2019
+Nikon Z 50 II NEF Yes 17.1 2024
+Nikon Z 5 NEF Yes 12.4 2019
+Nikon Z 5 2 NEF Yes 17.3 2024
+Nikon Z 5 2C NEF Yes 18.5 2025
+Nikon Z 6 NEF Yes 11.1 2018
+Nikon Z 6 II NEF Yes 13.1 2020
+Nikon Z 6 III NEF Yes 16.3 2023
+Nikon Z 7 NEF Yes 11.0 2018
+Nikon Z 7 II NEF Yes 13.1 2020
+Nikon Z 8 NEF Yes 15.3 2022
+Nikon Z 9 (*See note) NEF Yes 14.0 2021
+Nikon Z fc NEF Yes 13.4 2020
+Nikon Z f NEF Yes 15.5 2022
+Nikon ZR NEF Yes 17.5.1 2024
+Nokia Lumia 1020 DNG No 8.3 2013
+Nokia HMD Global Nokia 9 PureView Front Camera DNG No 11.3 2018
+Nokia HMD Global Nokia 9 PureView Rear Camera DNG No 11.3 2018
+OM Digital Solutions OM System OM-1 ORF Yes 14.2 2021
+OM Digital Solutions OM System OM-5 ORF Yes 15.0 2022
+OM Digital Solutions OM System TG - 7 ORF Yes 16.0 2023
+OM Digital Solutions OM System OM-1 MARK II ORF Yes 16.2 2023
+OM Digital Solutions OM System OM-3 ORF Yes 17.2 2024
+OM Digital Solutions OM-5 Mark II ORF/ORI Yes 17.4.1 2024
+OM Digital Solutions OM-3 Astro ORF/ORI Yes 18.2 2025
+Olympus AIR A01 ORF Yes 9.1.1 2015
+Olympus Camedia C-5050 Zoom ORF No 1.0 2003
+Olympus Camedia C-5060 Zoom ORF No 2.1 2003
+Olympus Camedia C-7070 Wide Zoom ORF No 3.1 2005
+Olympus Camedia C-8080 Wide Zoom ORF No 2.2 2003
+Olympus E-1 ORF No 2.1 2003
+Olympus E-3 ORF No 4.3 2007
+Olympus E-5 ORF Yes 6.3 2010
+Olympus E-10 ORF No 1.0 2003
+Olympus E-20 ORF No 1.0 2003
+Olympus E-30 ORF No 5.3 2008
+Olympus E-420 ORF No 5.0 2008
+Olympus E-450 ORF No 5.4 2008
+Olympus E-520 ORF No 5.0 2008
+Olympus E-600 ORF No 6.1 2010
+Olympus E-620 ORF No 5.4 2008
+Olympus EVOLT E-300 ORF No 3.1 2005
+Olympus EVOLT E-330 ORF No 3.4 2005
+Olympus EVOLT E-400 ORF No 3.6 2005
+Olympus EVOLT E-410 ORF No 4.1 2007
+Olympus EVOLT E-500 ORF No 3.3 2005
+Olympus EVOLT E-510 ORF No 4.2 2007
+Olympus OM-D E-M1 ORF Yes 8.3 2013
+Olympus OM-D E-M1X ORF Yes 11.2 2018
+Olympus OM-D E-M1 Mark II ORF Yes 9.9 2015
+Olympus OM-D E-M1 Mark III ORF Yes 12.2 2019
+Olympus OM-D E-M5 ORF Yes 7.1 2012
+Olympus OM-D E-M5 Mark II ORF Yes 9.0 2015
+Olympus OM-D E-M5 Mark III ORF Yes 12.0 2019
+Olympus OM-D E-M10 ORF Yes 8.5 2013
+Olympus OM-D E-M10 Mark II ORF Yes 9.2 2015
+Olympus OM-D E-M10 Mark III ORF Yes 10.0 2017
+Olympus OM-D E-M10 Mark III S ORF Yes 12.4 2019
+Olympus OM-D E-M10 Mark IV ORF Yes 12.4 2019
+Olympus PEN E-P1 ORF Yes 5.5 2008
+Olympus PEN E-P2 ORF Yes 5.6 2008
+Olympus PEN E-P3 ORF Yes 6.5 2010
+Olympus PEN E-P5 ORF Yes 8.1 2013
+Olympus PEN E-P7 ORF Yes 13.3 2020
+Olympus PEN E-PL1 ORF Yes 6.1 2010
+Olympus PEN E-PL1s ORF Yes 6.4 2010
+Olympus PEN E-PL10 ORF Yes 12.0 2019
+Olympus PEN E-PL2 ORF Yes 6.4 2010
+Olympus PEN E-PL3 ORF Yes 6.5 2010
+Olympus PEN E-PL6 ORF Yes 8.1 2013
+Olympus PEN E-PL5 ORF Yes 7.3 2012
+Olympus PEN E-PL7 ORF Yes 8.7 2013
+Olympus PEN E-PL8 ORF Yes 9.8 2015
+Olympus PEN E-PL9 ORF Yes 10.2 2017
+Olympus PEN E-PM1 ORF Yes 6.5 2010
+Olympus PEN E-PM2 ORF Yes 7.3 2012
+Olympus PEN-F ORF Yes 9.5 2015
+Olympus SP-310 ORF No 3.3 2005
+Olympus SP-320 ORF No 3.4 2005
+Olympus SP-350 ORF No 3.3 2005
+Olympus SP-500 UZ ORF No 3.3 2005
+Olympus SP-510 UZ ORF No 3.6 2005
+Olympus SP-550 UZ ORF No 4.1 2007
+Olympus SP-560 UZ ORF No 4.3 2007
+Olympus SP-565 UZ ORF No 5.1 2008
+Olympus SP-570 UZ ORF No 4.4 2007
+Olympus Stylus 1 ORF Yes 8.3 2013
+Olympus Stylus 1s ORF Yes 8.7 2013
+Olympus Stylus SH-2 ORF Yes 9.0 2015
+Olympus Stylus SH-3 ORF Yes 9.5 2015
+Olympus Stylus Tough TG-4 ORF Yes 9.0 2015
+Olympus Stylus Tough TG-5 ORF Yes 9.12 2015
+Olympus Stylus Tough TG-6 ORF Yes 11.3 2018
+Olympus Stylus XZ-2 iHS ORF Yes 7.3 2012
+Olympus Stylus XZ-10 ORF Yes 7.4 2012
+Olympus XZ-1 ORF Yes 6.4 2010
+OnePlus 7T Front Camera DNG No 12.2 2019
+OnePlus 7T Rear Main Camera DNG No 12.2 2019
+OnePlus 8 Front Camera DNG No 13.1 2020
+OnePlus 8 Rear Main Camera DNG No 13.1 2020
+OnePlus 8 Pro Front Camera DNG No 13.1 2020
+OnePlus 8 Pro Rear Main Camera DNG No 13.1 2020
+OnePlus 12 Telephoto Camera DNG No 16.1.1 2023
+OnePlus 12 Ultrawide Camera DNG No 16.1.1 2023
+OnePlus 12 Wide Camera DNG No 16.1.1 2023
+Oppo Find X7 Telephoto Camera DNG No 16.1.1 2023
+Oppo Find X7 Ultrawide Camera DNG No 16.1.1 2023
+Oppo Find X7 Wide Camera DNG No 16.1.1 2023
+Oppo Find X7 Ultra Super Telephoto Camera DNG No 16.1.1 2023
+Oppo Find X7 Ultra Telephoto Camera DNG No 16.1.1 2023
+Oppo Find X7 Ultra Ultrawide Camera DNG No 16.1.1 2023
+Oppo Find X7 Ultra Wide Camera DNG No 16.1.1 2023
+Oppo Find X8 Ultra Super Telephoto Camera DNG No 18.0 2025
+Oppo Find X8 Ultra Telephoto Camera DNG No 18.0 2025
+Oppo Find X8 Ultra Ultrawide Camera DNG No 18.0 2025
+Oppo Find X8 Ultra Wide Camera DNG No 18.0 2025
+Oppo Find X8 Telephoto Camera DNG No 18.2 2025
+Oppo Find X8 Ultrawide Camera DNG No 18.2 2025
+Oppo Find X8 Wide Camera DNG No 18.2 2025
+Oppo Find X8s Telephoto Camera DNG No 18.2 2025
+Oppo Find X8s Ultrawide Camera DNG No 18.2 2025
+Oppo Find X8s Wide Camera DNG No 18.2 2025
+Oppo Find X8s+ Telephoto Camera DNG No 18.2 2025
+Oppo Find X8s+ Ultrawide Camera DNG No 18.2 2025
+Oppo Find X8s+ Wide Camera DNG No 18.2 2025
+Oppo Find X8 Pro Super Telephoto Camera DNG No 18.2 2025
+Oppo Find X8 Pro Telephoto Camera DNG No 18.2 2025
+Oppo Find X8 Pro Ultrawide Camera DNG No 18.2 2025
+Oppo Find X8 Pro Wide Camera DNG No 18.2 2025
+Oppo Find X9 Telephoto Camera DNG No 18.3.1 2025
+Oppo Find X9 Ultrawide Camera DNG No 18.3.1 2025
+Oppo Find X9 Wide Camera DNG No 18.3.1 2025
+Oppo Find X9 Pro Telephoto Camera DNG No 18.3.1 2025
+Oppo Find X9 Pro Ultrawide Camera DNG No 18.3.1 2025
+Oppo Find X9 Pro Wide Camera DNG No 18.3.1 2025
+Oppo Find X9s Pro Telephoto Camera DNG No 18.3.1 2025
+Oppo Find X9s Pro Ultrawide Camera DNG No 18.3.1 2025
+Oppo Find X9s Pro Wide Camera DNG No 18.3.1 2025
+Oppo Find X9 Ultra Super Telephoto Camera DNG No 18.3.1 2025
+Oppo Find X9 Ultra Telephoto Camera DNG No 18.3.1 2025
+Oppo Find X9 Ultra Ultrawide Camera DNG No 18.3.1 2025
+Oppo Find X9 Ultra Wide Camera DNG No 18.3.1 2025
+Panasonic LUMIX AG-GH4 RW2 No 8.5 2013
+Panasonic LUMIX DC-FZ80 (DC-FZ82, DC-FZ85) RW2 Yes 9.9 2015
+Panasonic LUMIX DC-FZ80D (DC-FZ82D, DC-FZ85D) RW2 Yes 16.4 2023
+Panasonic LUMIX DC-FZ81 (DC-FZ83) RW2 Yes 9.10.1 2015
+Panasonic LUMIX DC-FZ1000M2 (DC-FZ10002) RW2 Yes 11.2.1 2018
+Panasonic LUMIX DC-G100 (DC-G110) RW2 Yes 12.4 2019
+Panasonic LUMIX DC-G9 RW2 Yes 10.1 2017
+Panasonic LUMIX DC-G99 (DC-G90, DC-G91, DC-G95) RW2 Yes 11.3 2018
+Panasonic LUMIX DC-GF9 (DC-GX850, DC-GX800) RW2 Yes 9.9 2015
+Panasonic LUMIX DC-GF10 (DC-GF90) RW2 Yes 10.2 2017
+Panasonic LUMIX DC-GH5 RW2 Yes 9.9 2015
+Panasonic LUMIX DC-GH5M2 RW2 Yes 13.3 2020
+Panasonic LUMIX DC-GH5s RW2 Yes 10.2 2017
+Panasonic LUMIX DC-GH6 RW2 Yes 14.3 2021
+Panasonic LUMIX DC-GH7 RW2 Yes 16.4 2023
+Panasonic LUMIX DC-GX880 RW2 Yes 11.4 2018
+Panasonic LUMIX DC-LX100 II RW2 Yes 11.0 2018
+Panasonic LUMIX DC-S1 RW2 Yes 11.2.1 2018
+Panasonic LUMIX DC-S1H RW2 Yes 11.4.1 2018
+Panasonic LUMIX DC-S1R RW2 Yes 11.2.1 2018
+Panasonic LUMIX DC-S5 RW2 Yes 13.0 2020
+Panasonic LUMIX DC-S9 RW2 Yes 16.4 2023
+Panasonic LUMIX DC-ZS70 (DC-TZ90, DC-TZ91, DC-TZ92, DC-TZ93) RW2 Yes 9.10.1 2015
+Panasonic LUMIX DC-ZS80 (DC-TZ95, DC-TZ96, DC-TZ97) RW2 Yes 11.2.1 2018
+Panasonic LUMIX DMC-CM1 RW2 No 8.7 2013
+Panasonic LUMIX DMC-CM10 RW2 No 9.5 2015
+Panasonic LUMIX DMC-FZ1000 RW2 Yes 8.6 2013
+Panasonic LUMIX DMC-FX150 RW2 No 5.2 2008
+Panasonic LUMIX DMC-FZ8 RAW No 4.0 2007
+Panasonic LUMIX DMC-FZ18 RAW No 4.2 2007
+Panasonic LUMIX DMC-FZ28 RW2 No 5.2 2008
+Panasonic LUMIX DMC-FZ30 RAW No 3.2 2005
+Panasonic LUMIX DMC-FZ35 RW2 No 5.5 2008
+Panasonic LUMIX DMC-FZ38 RW2 No 5.6 2008
+Panasonic LUMIX DMC-FZ40 (DMC-FZ45) RW2 No 6.2 2010
+Panasonic LUMIX DMC-FZ50 RAW No 3.5 2005
+Panasonic LUMIX DMC-FZ70 RW2 No 8.2 2013
+Panasonic LUMIX DMC-FZ72 RW2 No 8.2 2013
+Panasonic LUMIX DMC-FZ100 RW2 No 6.2 2010
+Panasonic LUMIX DMC-FZ150 RW2 No 6.5 2010
+Panasonic LUMIX DMC-FZ200 RW2 No 7.2 2012
+Panasonic LUMIX DMC-FZ2500 (DMC-FZ2000, DMC-FZH1) RW2 Yes 9.8 2015
+Panasonic LUMIX DMC-FZ300 RW2 Yes 9.1.1 2015
+Panasonic LUMIX DMC-FZ330 RW2 Yes 9.1.1 2015
+Panasonic LUMIX DMC-G1 RW2 No 5.2 2008
+Panasonic LUMIX DMC-G2 RW2 No 6.1 2010
+Panasonic LUMIX DMC-G3 RW2 No 6.5 2010
+Panasonic LUMIX DMC-G5 RW2 No 7.2 2012
+Panasonic LUMIX DMC-G6 RW2 No 8.1 2013
+Panasonic LUMIX DMC-G7 RW2 Yes 9.1 2015
+Panasonic LUMIX DMC-G8 (DMC-G80, DMC-G81, DMC-G85) RW2 Yes 9.7 2015
+Panasonic LUMIX DMC-G10 RW2 No 6.1 2010
+Panasonic LUMIX DMC-GF1 RW2 No 5.5 2008
+Panasonic LUMIX DMC-GF2 RW2 No 6.3 2010
+Panasonic LUMIX DMC-GF3 RW2 No 6.5 2010
+Panasonic LUMIX DMC-GF5 RW2 No 7.1 2012
+Panasonic LUMIX DMC-GF6 RW2 No 7.4 2012
+Panasonic LUMIX DMC-GF7 RW2 No 9.0 2015
+Panasonic LUMIX DMC-GF8 RW2 No 9.5 2015
+Panasonic LUMIX DMC-GH1 RW2 No 5.4 2008
+Panasonic LUMIX DMC-GH2 RW2 No 6.3 2010
+Panasonic LUMIX DMC-GH3 RW2 No 7.3 2012
+Panasonic LUMIX DMC-GH4 RW2 Yes 8.6 2013
+Panasonic LUMIX DMC-GM1 RW2 No 8.3 2013
+Panasonic LUMIX DMC-GM1S RW2 No 8.7 2013
+Panasonic LUMIX DMC-GM5 RW2 No 8.7 2013
+Panasonic LUMIX DMC-GX1 RW2 No 6.6 2010
+Panasonic LUMIX DMC-GX7 RW2 No 8.2 2013
+Panasonic LUMIX DMC-GX8 RW2 Yes 9.1.1 2015
+Panasonic LUMIX DMC-GX85 (DMC-GX80, DMC-GX7MK2) RW2 Yes 9.6 2015
+Panasonic LUMIX DC-GX9 (DC-GX7MK3) RW2 Yes 10.3 2017
+Panasonic LUMIX DMC-L1 RAW No 3.5 2005
+Panasonic LUMIX DMC-L10 RAW No 4.3 2007
+Panasonic LUMIX DMC-LC1 RAW No 2.1 2003
+Panasonic LUMIX DMC-LF1 RW2 No 8.1 2013
+Panasonic LUMIX DMC-LX1 RAW No 3.2 2005
+Panasonic LUMIX DMC-LX2 RAW No 3.6 2005
+Panasonic LUMIX DMC-LX3 RW2 No 5.2 2008
+Panasonic LUMIX DMC-LX5 RW2 No 6.2 2010
+Panasonic LUMIX DMC-LX7 RW2 No 7.2 2012
+Panasonic LUMIX DMC-LX9 (DMC-LX10, DMC-LX15) RW2 Yes 9.7 2015
+Panasonic LUMIX DMC-LX100 RW2 Yes 8.7 2013
+Panasonic LUMIX DMC-TZ82 RW2 Yes 9.8 2015
+Panasonic LUMIX DMC-ZS40 (DMC-TZ60, DMC-TZ61) RW2 No 8.4 2013
+Panasonic LUMIX DMC-ZS50 (DMC-TZ70, DMC-TZ71) RW2 No 9.0 2015
+Panasonic LUMIX DMC-ZS60 (DMC-TZ80, DMC-TZ81, DMC-TZ85) RW2 Yes 9.4 2015
+Panasonic LUMIX DMC-ZS100 (DMC-ZS110, DMC-TZ100, DMC-TZ101, DMC-TZ110, DMC-TX1) RW2 Yes 9.5 2015
+Panasonic LUMIX ZS200 (DC-TX2, DC-TZ200, DC-TZ202, DC-TZ220, DC-ZS220) RW2 Yes 10.3 2017
+Panasonic LUMIX DC-G95D RW2 Yes 14.5 2021
+Panasonic LUMIX DC-G99D RW2 Yes 14.4 2021
+Panasonic LUMIX DC-TX2D (DC-TZ200D, DC-TZ202D, DC-TZ220D, DC-ZS200D, DC-ZS220D) RW2 Yes 15.0 2022
+Panasonic LUMIX DC-ZS80D (DC-TZ95D, DC-TZ96D) RW2 Yes 15.1 2022
+Panasonic LUMIX DC-S5M2 RW2 Yes 15.2 2022
+Panasonic LUMIX DC-S5M2X RW2 Yes 15.3 2022
+Panasonic LUMIX DCG9M2 RW2 Yes 16.0 2023
+Panasonic LUMIX DC-G100D RW2 Yes 16.1 2023
+Panasonic LUMIX DC-S5D RW2 Yes 17.0 2024
+Panasonic LUMIX DC - G97 (DC-G99M2) RW2 Yes 17.2 2024
+Panasonic LUMIX DC - TZ99 (DC - ZS99) RW2 Yes 17.2 2024
+Panasonic LUMIX DC-S1RM2 (Final Support) RW2 Yes 17.3 2024
+Panasonic LUMIX DC-S1M2ES (Final Support) RW2 Yes 17.4 2024
+Panasonic LUMIX DC-S1M2 RW2 Yes 17.4 2024
+Panasonic LUMIX DC-ZS300 (DC-TZ300, DC-TX3) RW2 Yes 18.3 2025
+Panasonic LUMIX DC-L10 RW2 Yes 18.3 2025
+Parrot Anafi DNG No 10.5 2017
+Parrot ANAFI Ai DNG No 13.4 2020
+Pentax 645D DNG, PEF No 6.2 2010
+Pentax 645Z DNG, PEF Yes 8.5 2013
+Pentax *ist D PEF No 2.1 2003
+Pentax *ist DL PEF No 3.3 2005
+Pentax *ist DL2 PEF No 3.4 2005
+Pentax *ist DS PEF No 3.2 2005
+Pentax *ist DS2 PEF No 3.3 2005
+Pentax K-01 DNG No 7.1 2012
+Pentax K-1 DNG, PEF Yes 9.5.1 2015
+Pentax K-1 II (* see note) DNG, PEF Yes 10.4 2017
+Pentax K-3 DNG, PEF No 8.3 2013
+Pentax K-3 II († see note) DNG, PEF Yes 9.1.1 2015
+Pentax K-5 DNG, PEF No 6.3 2010
+Pentax K-5 II DNG, PEF No 7.3 2012
+Pentax K-5 IIs DNG, PEF No 7.3 2012
+Pentax K-7 PEF Yes 5.4 2008
+Pentax K-3 Mark III DNG, PEF Yes 13.3 2020
+Pentax K-3 Mark III Monochrome DNG/PEF No 15.4 2022
+Pentax K-30 DNG No 7.2 2012
+Pentax K-50 DNG No 8.2 2013
+Pentax K-70 PEF No 9.8 2015
+Pentax K-500 DNG No 8.2 2013
+Pentax KP DNG, PEF No 9.10 2015
+Pentax K-r PEF No 6.3 2010
+Pentax K-S1 DNG, PEF No 8.7 2013
+Pentax K-S2 DNG, PEF No 9.1 2015
+Pentax K-x PEF No 5.6 2008
+Pentax K10D DNG, PEF Yes 3.7 2005
+Pentax K20D DNG, PEF Yes 4.4 2007
+Pentax K100D PEF No 3.6 2005
+Pentax K100D Super PEF No 4.2 2007
+Pentax K110D PEF No 3.6 2005
+Pentax K200D DNG, PEF Yes 4.4 2007
+Pentax K2000 K-m PEF No 5.1 2008
+Pentax KF PEF, DNG No 15.1 2022
+Pentax MX-1 DNG No 7.4 2012
+Pentax Q DNG No 6.5 2010
+Pentax Q7 DNG No 8.2 2013
+Pentax Q10 DNG, PEF No 7.3 2012
+Pentax QS-1 DNG No 8.7 2013
+Pixii PIXII Camera (A1571) DNG No 14.1 2021
+Phase One H 20 TIF No 4.1 2007
+Phase One H 25 TIF No 4.1 2007
+Phase One IQ 140 IIQ No 6.5 2010
+Phase One IQ 160 IIQ No 6.5 2010
+Phase One IQ 150 IIQ No 9.4 2015
+Phase One IQ 180 IIQ No 6.5 2010
+Phase One IQ 250 IIQ No 8.4 2013
+Phase One IQ 260 IIQ No 8.3 2013
+Phase One IQ 280 IIQ No 8.3 2013
+Phase One IQ3 50MP IIQ No 9.1.1 2015
+Phase One IQ3 60MP IIQ No 9.1.1 2015
+Phase One IQ3 80MP IIQ No 9.1.1 2015
+Phase One IQ3 100MP IIQ No 9.9 2015
+Phase One IQ3 100MP Trichromatic (*see note) IIQ No 10.4 2017
+Phase One IQ4 150MP (**see note) IIQ No 12.2 2019
+Phase One IQ4 150MP Achromatic (** see note) IIQ No 13.2 2020
+Phase One P 20 TIF No 4.1 2007
+Phase One P 20+ TIF No 4.2 2007
+Phase One P 21 TIF No 4.1 2007
+Phase One P 21+ TIF No 4.2 2007
+Phase One P 25 TIF No 4.1 2007
+Phase One P 25+ TIF No 4.2 2007
+Phase One P 30 TIF No 4.1 2007
+Phase One P 30+ TIF No 4.2 2007
+Phase One P 40+ IIQ No 6.5 2010
+Phase One P 45 TIF No 4.1 2007
+Phase One P 45+ TIF No 4.2 2007
+Phase One P 65+ IIQ No 6.5 2010
+Ricoh GR DNG No 8.1 2013
+Ricoh GR II DNG No 9.1.1 2015
+Ricoh GR III DNG Yes 11.3 2018
+Ricoh GR IIIx DNG Yes 14.1 2021
+Ricoh GR III HDF DNG Yes 16.3 2023
+Ricoh GR IIIx HDF DNG Yes 16.3 2023
+Ricoh GR IV DNG Yes 17.5 2024
+Ricoh GR IV Monochrome DNG No 18.2 2025
+Ricoh GR Digital DNG No 2.3 2003
+Ricoh GR Digital II DNG No 2.3 2003
+Ricoh GR Digital III DNG No 5.5 2008
+Ricoh GR Digital IV DNG No 6.6 2010
+Ricoh GX100 DNG No 2.3 2003
+Ricoh GX200 DNG No 2.3 2003
+Ricoh GXR with GR LENS A12 28mm F2.5 DNG No 6.3 2010
+Ricoh GXR with GR LENS A12 50mm F2.5 MACRO DNG No 5.6 2008
+Ricoh GXR with GR LENS A16 24-85 F3.5-5.5 DNG No 6.7 2010
+Ricoh GXR with GR LENS S10 24-72mm F2.5-4.4 VC DNG No 5.6 2008
+Ricoh GXR with GR LENS Mount A12 DNG No 6.5 2010
+Ricoh THETA Z1 DNG No 11.3 2018
+Samsung EK-GN100 SRW No 7.4 2012
+Samsung EK-GN110 SRW No 7.4 2012
+Samsung EK-GN120 SRW No 7.4 2012
+Samsung EK-KN120 SRW No 7.4 2012
+Samsung EX2F SRW No 7.2 2012
+Samsung NX300 SRW No 7.4 2012
+Samsung NX500 SRW No 9.0 2015
+Samsung NX1000 SRW No 7.1 2012
+Samsung NX1100 SRW No 7.4 2012
+Samsung NX2000 SRW No 7.4 2012
+Samsung NX3000 SRW No 8.5 2013
+Samsung NX3300 SRW No 9.5 2015
+Samsung NX U SRW No 7.4 2012
+Samsung Pro 815 DNG No 2.3 2003
+Samsung TL350 (WB2000) SRW No 6.3 2010
+Samsung TL500 (EX1) SRW No 6.2 2010
+Samsung Galaxy Note 9 DNG No 11.1 2018
+Samsung Galaxy Note 20 Rear Main Camera DNG No 13.1 2020
+Samsung Galaxy Note 20 Ultra Rear Main Camera DNG No 13.1 2020
+Samsung Galaxy S20 Front Camera DNG No 12.2.1 2019
+Samsung Galaxy S20 Rear Main Camera DNG No 12.2.1 2019
+Samsung Galaxy S20+ Front Camera DNG No 12.2.1 2019
+Samsung Galaxy S20+ Rear Main Camera DNG No 12.2.1 2019
+Samsung Galaxy S20 Ultra Front Camera DNG No 12.2.1 2019
+Samsung Galaxy S20 Ultra Rear Main Camera DNG No 12.2.1 2019
+Samsung Galaxy S21 Rear Main Camera DNG No 13.2 2020
+Samsung Galaxy S21 Rear Wide Camera DNG No 13.2 2020
+Samsung Galaxy S21+ Rear Main Camera DNG No 13.2 2020
+Samsung Galaxy S21+ Rear Wide Camera DNG No 13.2 2020
+Samsung Galaxy S21 Ultra Rear Main Camera DNG No 13.2 2020
+Samsung Galaxy S21 Ultra Rear Wide Camera DNG No 13.2 2020
+Samsung Galaxy S21 Ultra Super Telephoto Camera DNG No 14.1 2021
+Samsung Galaxy S21 Ultra Telephoto Camera DNG No 14.1 2021
+Samsung Galaxy S6 DNG No 11.1 2018
+Samsung Galaxy S6 Edge DNG No 11.1 2018
+Samsung Galaxy S7 DNG No 9.8 2015
+Samsung Galaxy S7 Edge DNG No 9.8 2015
+Samsung Galaxy S8 DNG No 10.0 2017
+Samsung Galaxy S8+ DNG No 10.0 2017
+Samsung Galaxy S9 DNG No 10.4 2017
+Samsung Galaxy S9+ DNG No 10.4 2017
+Samsung Galaxy Z Fold3 Rear Main Camera DNG No 14.1 2021
+Samsung Galaxy Z Fold3 Rear Telephoto Camera DNG No 14.1 2021
+Samsung Galaxy Z Fold3 Rear Ultrawide Camera DNG No 14.1 2021
+Samsung Galaxy S22 Front Camera DNG No 14.2 2021
+Samsung Galaxy S22 Rear Telephoto Camera DNG Samsung Expert Raw 14.2 2021
+Samsung Galaxy S22 Rear Ultrawide Camera DNG Samsung Expert Raw 14.2 2021
+Samsung Galaxy S22 Rear Wide Camera DNG Samsung Expert Raw 14.2 2021
+Samsung Galaxy S22+ Front Camera DNG No 14.2 2021
+Samsung Galaxy S22+ Rear Telephoto Camera DNG Samsung Expert Raw 14.2 2021
+Samsung Galaxy S22+ Rear Ultrawide Camera DNG Samsung Expert Raw 14.2 2021
+Samsung Galaxy S22+ Rear Wide Camera DNG Samsung Expert Raw 14.2 2021
+Samsung Galaxy S22 Ultra Front Camera DNG No 14.2 2021
+Samsung Galaxy S22 Ultra Rear Super Telephoto Camera DNG Samsung Expert Raw 14.2 2021
+Samsung Galaxy S22 Ultra Rear Telephoto Camera DNG Samsung Expert Raw 14.2 2021
+Samsung Galaxy S22 Ultra Rear Ultrawide Camera DNG Samsung Expert Raw 14.2 2021
+Samsung Galaxy S22 Ultra Rear Wide Camera DNG Samsung Expert Raw 14.2 2021
+Samsung Galaxy S23 Front Camera DNG Samsung Expert Raw 15.2 2022
+Samsung Galaxy S23 Rear Telephoto Camera DNG Samsung Expert Raw 15.2 2022
+Samsung Galaxy S23 Rear Ultrawide Camera DNG Samsung Expert Raw 15.2 2022
+Samsung Galaxy S23 Rear Wide Camera DNG Samsung Expert Raw 15.2 2022
+Samsung Galaxy S23+ Front Camera DNG Samsung Expert Raw 15.2 2022
+Samsung Galaxy S23+ Rear Telephoto Camera DNG Samsung Expert Raw 15.2 2022
+Samsung Galaxy S23+ Rear Ultrawide Camera DNG Samsung Expert Raw 15.2 2022
+Samsung Galaxy S23+ Rear Wide Camera DNG Samsung Expert Raw 15.2 2022
+Samsung Galaxy S23 Ultra Front Camera DNG Samsung Expert Raw 15.2 2022
+Samsung Galaxy S23 Ultra Rear Super Telephoto Camera DNG Samsung Expert Raw 15.2 2022
+Samsung Galaxy S23 Ultra Rear Telephoto Camera DNG Samsung Expert Raw 15.2 2022
+Samsung Galaxy S23 Ultra Rear Ultrawide Camera DNG Samsung Expert Raw 15.2 2022
+Samsung Galaxy S23 Ultra Rear Wide Camera DNG Samsung Expert Raw 15.2 2022
+Samsung Galaxy S24 Front Camera DNG No 16.1.1 2023
+Samsung Galaxy S24 Rear Telephoto Camera DNG No 16.1.1 2023
+Samsung Galaxy S24 Rear Ultrawide Camera DNG No 16.1.1 2023
+Samsung Galaxy S24 Rear Wide Camera DNG No 16.1.1 2023
+Samsung Galaxy S24+ Front Camera DNG No 16.1.1 2023
+Samsung Galaxy S24+ Rear Telephoto Camera DNG No 16.1.1 2023
+Samsung Galaxy S24+ Rear Ultrawide Camera DNG No 16.1.1 2023
+Samsung Galaxy S24+ Rear Wide Camera DNG No 16.1.1 2023
+Samsung Galaxy S24 Ultra Front Camera DNG No 16.1.1 2023
+Samsung Galaxy S24 Ultra Rear Super Telephoto Camera DNG No 16.1.1 2023
+Samsung Galaxy S24 Ultra Rear Telephoto Camera DNG No 16.1.1 2023
+Samsung Galaxy S24 Ultra Rear Ultrawide Camera DNG No 16.1.1 2023
+Samsung Galaxy S24 Ultra Rear Wide Camera DNG No 16.1.1 2023
+Samsung Galaxy S25 Front Camera DNG No 17.2 2024
+Samsung Galaxy S25 Rear Telephoto Camera DNG No 17.2 2024
+Samsung Galaxy S25 Rear Ultrawide Camera DNG No 17.2 2024
+Samsung Galaxy S25 Rear Wide Camera DNG No 17.2 2024
+Samsung Galaxy S25+ Front Camera DNG No 17.2 2024
+Samsung Galaxy S25+ Rear Telephoto Camera DNG No 17.2 2024
+Samsung Galaxy S25+ Rear Ultrawide Camera DNG No 17.2 2024
+Samsung Galaxy S25+ Rear Wide Camera DNG No 17.2 2024
+Samsung Galaxy S25 Ultra Front Camera DNG No 17.2 2024
+Samsung Galaxy S25 Ultra Rear Super Telephoto Camera DNG No 17.2 2024
+Samsung Galaxy S25 Ultra Rear Telephoto Camera DNG No 17.2 2024
+Samsung Galaxy S25 Ultra Rear Ultrawide Camera DNG No 17.2 2024
+Samsung Galaxy S25 Ultra Rear Wide Camera DNG No 17.2 2024
+Samsung Galaxy Z Fold4 Rear Telephoto Camera DNG Samsung Expert Raw 14.5 2021
+Samsung Galaxy Z Fold4 Rear Ultrawide Camera DNG Samsung Expert Raw 14.5 2021
+Samsung Galaxy Z Fold4 Rear Wide Camera DNG Samsung Expert Raw 14.5 2021
+Samsung Galaxy Z Fold5 Front Camera DNG Samsung Expert Raw 15.5 2022
+Samsung Galaxy Z Fold5 Telephoto Camera DNG Samsung Expert Raw 15.5 2022
+Samsung Galaxy Z Fold5 Ultrawide Camera DNG Samsung Expert Raw 15.5 2022
+Samsung Galaxy Z Fold5 Wide Camera DNG Samsung Expert Raw 15.5 2022
+Samsung Galaxy Z Fold6 Front Camera DNG No 16.5 2023
+Samsung Galaxy Z Fold6 Rear Telephoto Camera DNG No 16.5 2023
+Samsung Galaxy Z Fold6 Rear Ultrawide Camera DNG No 16.5 2023
+Samsung Galaxy Z Fold6 Rear Wide Camera DNG No 16.5 2023
+Samsung Galaxy Z Fold Special Edition Front Camera DNG Samsung Expert Raw 17.1 2024
+Samsung Galaxy Z Fold Special Edition Rear Telephoto Camera DNG Samsung Expert Raw 17.1 2024
+Samsung Galaxy Z Fold Special Edition Rear Ultrawide Camera DNG Samsung Expert Raw 17.1 2024
+Samsung Galaxy Z Fold Special Edition Rear Wide Camera DNG Samsung Expert Raw 17.1 2024
+Samsung Galaxy Z Flip6 Rear Ultrawide Camera DNG No 16.5 2023
+Samsung Galaxy Z Flip6 Rear Wide Camera DNG No 16.5 2023
+Samsung Galaxy Z Flip7 FE Front Camera DNG Samsung Expert Raw 17.5 2024
+Samsung Galaxy Z Flip7 FE Rear Ultrawide Camera DNG Samsung Expert Raw 17.5 2024
+Samsung Galaxy Z Flip7 FE Wide Camera DNG Samsung Expert Raw 17.5 2024
+Samsung Galaxy Z Flip7 Front Camera DNG No 18.0 2025
+Samsung Galaxy Z Flip7 Rear Ultrawide Camera DNG No 18.0 2025
+Samsung Galaxy Z Flip7 Rear Wide Camera DNG No 18.0 2025
+Samsung Galaxy Z Fold7 Front Camera DNG Samsung Expert Raw 17.5 2024
+Samsung Galaxy Z Fold7 Rear Telephoto Camera DNG Samsung Expert Raw 17.5 2024
+Samsung Galaxy Z Fold7 Rear Ultrawide Camera DNG Samsung Expert Raw 17.5 2024
+Samsung Galaxy Z Fold7 Under-Display Camera DNG Samsung Expert Raw 17.5 2024
+Samsung Galaxy Z Fold7 Rear Wide Camera DNG Samsung Expert Raw 17.5 2024
+Samsung Galaxy Z Trifold Front Camera DNG Samsung Expert Raw 18.1.1 2025
+Samsung Galaxy Z Trifold Rear Telephoto Camera DNG Samsung Expert Raw 18.1.1 2025
+Samsung Galaxy Z Trifold Rear Ultrawide Camera DNG Samsung Expert Raw 18.1.1 2025
+Samsung Galaxy Z Trifold Rear Wide Camera DNG Samsung Expert Raw 18.1.1 2025
+Sigma BF DNG Yes 17.3 2024
+Sigma DP1 X3F No 4.6, 5.1 2007
+Sigma DP1s X3F No 5.6 2008
+Sigma DP2 X3F No 5.4 2008
+Sigma fp DNG Yes 12.1 2019
+Sigma fp L DNG Yes 13.2 2020
+Sigma SD9 X3F No 2.2 2003
+Sigma SD10 X3F No 2.2 2003
+Sigma SD14 X3F No 4.1 2007
+Skydio Skydio 2 DNG No 12.4 2019
+Sony Xperia 5 IV Telephoto Camera DNG No 15.0 2022
+Sony Xperia 5 IV Ultrawide Camera DNG No 15.0 2022
+Sony Xperia 5 IV Wide Camera DNG No 15.0 2022
+Sony Xperia 1 V Front Camera DNG No 15.4 2022
+Sony Xperia 1 V Ultrawide Camera DNG No 15.4 2022
+Sony Xperia 1 V Wide Camera DNG No 15.4 2022
+Sony Xperia 1 V Zoom Camera DNG No 15.4 2022
+Sony A1 II (ILCE-1M2) (** see note) ARW Yes 17.1 2024
+Sony A7 (ILCE-7) ARW Yes 8.3 2013
+Sony A7 II (ILCE-A7M2) ARW Yes 8.7.1 2013
+Sony A7 III (ILCE-7M3) ARW Yes 10.3 2017
+Sony A7 IV (ILCE-7M4) ARW Yes 14.1 2021
+Sony A7 V (ILCE-7M5) (** see note) ARW No 18.1.1 2025
+Sony A7C (ILCE-7C) ARW Yes 13.1 2020
+Sony A7C II (ILCE-7CM2) ARW Yes 16.0 2023
+Sony A7CR (ILCE-7CR) ARW Yes 16.0 2023
+Sony A7R (ILCE-7R) ARW Yes 8.3 2013
+Sony A7R II (ILCE-7RM2) ARW Yes 9.1.1 2015
+Sony A7R III (ILCE-7RM3) ARW Yes 10.1 2017
+Sony A7R IV (ILCE-7RM4) ARW Yes 11.4.1 2018
+Sony A7S (ILCE-7S) ARW Yes 8.5 2013
+Sony A7S II (ILCE-7SM2) ARW Yes 9.2 2015
+Sony A7s III (ILCE-7SM3) ARW Yes 13.0 2020
+Sony A9 (ILCE-9) ARW Yes 9.10.1 2015
+Sony A9 II (ILCE-9M2) ARW Yes 12.1 2019
+Sony A9 III (ILCE-9M3) ARW Yes 16.1 2023
+Sony A33 (SLT-A33) ARW No 6.2 2010
+Sony A35 (SLT-A35) ARW No 6.5 2010
+Sony A37 (SLT-A37) ARW No 7.1 2012
+Sony A55V (SLT-A55V) ARW No 6.2 2010
+Sony A57 (SLT-A57) ARW No 7.1 2012
+Sony A58 (SLT-A58) ARW No 7.4 2012
+Sony A65 (SLT-A65) ARW No 6.5 2010
+Sony A68 (ILCA-68) ARW Yes 9.4 2015
+Sony A77 (SLT-A77) ARW No 6.5 2010
+Sony A77 II (ILCA-77M2) ARW Yes 8.5 2013
+Sony A7R IIIA (ILCE-7RM3A) ARW Yes 13.3 2020
+Sony A7R IVA (ILCE-7RM4A) ARW Yes 13.3 2020
+Sony A7R V (ILCE-7RM5) ARW Yes 15.1 2022
+Sony A7R VI (ILCE-7RM6) (** see note) ARW No 18.3.2 2025
+Sony A99 (SLT-A99V) ARW Yes 7.2 2012
+Sony A99 II (ILCA-99M2) ARW Yes 9.8 2015
+Sony A100 (DSLR-A100) ARW No 3.5 2005
+Sony A200 (DSLR-A200) ARW No 4.4 2007
+Sony A230 (DSLR-A230) ARW No 5.4 2008
+Sony A290 (DSLR-A290) ARW No 6.2 2010
+Sony A300 (DSLR-A300) ARW No 4.4 2007
+Sony A330 (DSLR A330) ARW No 5.4 2008
+Sony A350 (DSLR-A350) ARW No 4.4 2007
+Sony A380 (DSLR-A380) ARW No 5.4 2008
+Sony A390 (DSLR-A390) ARW No 6.2 2010
+Sony A450 (DSLR-A450) ARW No 6.1 2010
+Sony A500 (DSLR-A500) ARW No 5.6 2008
+Sony A550 (DSLR-A550) ARW No 5.6 2008
+Sony A560 (DSLR-A560) ARW No 6.3 2010
+Sony A580 (DSLR-A580) ARW No 6.3 2010
+Sony A700 (DSLR-A700) ARW No 4.2 2007
+Sony A850 (DSLR-A850) ARW No 5.6 2008
+Sony A900 (DSLR-A900) ARW No 5.1 2008
+Sony A3000 (ILCE-3000) ARW No 8.2 2013
+Sony A3500 (ILCE-3500) ARW No 8.2 2013
+Sony A5000 (ILCE-5000) ARW Yes 8.4 2013
+Sony A5100 (ILCE-5100) ARW Yes 8.7 2013
+Sony A6000 (ILCE-6000) ARW Yes 8.4 2013
+Sony A6100 (ILCE-6100) ARW Yes 12.0 2019
+Sony A6300 (ILCE-6300) ARW Yes 9.5 2015
+Sony A6400 (ILCE-6400) ARW Yes 11.2.1 2018
+Sony A6500 (ILCE-6500) ARW Yes 9.8 2015
+Sony A6600 (ILCE-6600) ARW Yes 12.0 2019
+Sony A6700 (ILCE-6700) ARW Yes 15.5 2022
+Sony DSC-F828 SRF No 2.1 2003
+Sony DSC-HX95 ARW Yes 11.1 2018
+Sony DSC-HX99 ARW Yes 11.1 2018
+Sony DSC-V3 SRF No 3.0 2005
+Sony DSC-RX100M7A ARW Yes 17.3.1 2024
+Sony FX3 (ILME-FX3) ARW Yes 13.3 2020
+Sony FX30 (ILME-FX30) ARW Yes 15.1 2022
+Sony NEX-3 ARW Yes 6.2 2010
+Sony NEX-3N ARW Yes 7.4 2012
+Sony NEX-5 ARW Yes 6.2 2010
+Sony NEX-5N ARW Yes 6.5 2010
+Sony NEX-5R ARW Yes 7.2 2012
+Sony NEX-5T ARW Yes 8.2 2013
+Sony NEX-6 ARW Yes 7.2 2012
+Sony NEX-7 ARW Yes 6.6 2010
+Sony NEX-C3 ARW Yes 6.5 2010
+Sony NEX-F3 ARW Yes 7.1 2012
+Sony NEX-VG20 ARW Yes 7.1 2012
+Sony NEX-VG30 ARW Yes 7.3 2012
+Sony NEX-VG900 ARW Yes 7.3 2012
+Sony RX0 (DSC-RX0) ARW Yes 10.0 2017
+Sony RX0 II (DSC-RX0M2) ARW Yes 11.3 2018
+Sony RX1 (DSC-RX1) ARW Yes 7.3 2012
+Sony RX1R (DSC-RX1R) ARW Yes 8.1 2013
+Sony RX1R II (DSC-RX1RM2) ARW Yes 9.3 2015
+Sony RX10 (DSC-RX10) ARW Yes 8.3 2013
+Sony RX10 II (DSC-RX10M2) ARW Yes 9.1.1 2015
+Sony RX10 III (DSC-RX10M3) ARW Yes 9.5.1 2015
+Sony RX10 IV (DSC-RX10M4) ARW Yes 10.1 2017
+Sony RX10 V (DSC-RX10M5) (** see note) ARW No 18.4.1 2025
+Sony RX100 (DSC-RX100) ARW Yes 7.2 2012
+Sony RX100 II (DSC-RX100M2) ARW Yes 8.2 2013
+Sony RX100 III (DSC-RX100M3) ARW Yes 8.5 2013
+Sony RX100 IV (DSC-RX100M4) ARW Yes 9.1.1 2015
+Sony RX100 V (DSC-RX100M5) ARW Yes 9.8 2015
+Sony RX100 VA (DSC-RX100M5A) ARW Yes 10.5 2017
+Sony RX100 VI (DSC-RX100M6) ARW Yes 10.5 2017
+Sony RX100 VII (DSC-RX100M7) ARW Yes 11.4.1 2018
+Sony QX1 (ILCE-QX1) ARW Yes 8.7 2013
+Sony ZV-1 (DCZV1/B) ARW Yes 12.4 2019
+Sony Alpha 1 (ILCE-1)* ARW Yes 13.2 2020
+Sony Xperia 1 Wide-angle Camera DNG No 13.3 2020
+Sony Xperia 1 II Telephoto Camera DNG No 13.3 2020
+Sony Xperia 1 II Ultra wide-angle Camera DNG No 13.3 2020
+Sony Xperia 1 II Wide-angle Camera DNG No 13.3 2020
+Sony Xperia 1 IV Wide Camera DNG No 14.5 2021
+Sony Xperia 1 IV Zoom Camera DNG No 14.5 2021
+Sony Xperia 1 IV Ultrawide Camera DNG No 14.5 2021
+Sony Xperia 1 VI Front Camera DNG No 16.4 2023
+Sony Xperia 1 VI Ultrawide Camera DNG No 16.4 2023
+Sony Xperia 1 VI Wide Camera DNG No 16.4 2023
+Sony Xperia 1 VI Zoom Camera DNG No 16.4 2023
+Sony Xperia 1 VII Front Camera DNG No 17.4 2024
+Sony Xperia 1 VII Ultrawide Camera DNG No 17.4 2024
+Sony Xperia 1 VII Wide Camera DNG No 17.4 2024
+Sony Xperia 1 VII Zoom Camera DNG No 17.4 2024
+Sony Xperia 1 III Telephoto Camera DNG No 14.0 2021
+Sony Xperia 1 III Ultra wide-angle Camera DNG No 14.0 2021
+Sony Xperia 1 III Wide-angle Camera DNG No 14.0 2021
+Sony Xperia 5 III Super Telephoto Camera DNG No 14.0 2021
+Sony Xperia 5 III Telephoto Camera DNG No 14.0 2021
+Sony Xperia 5 III Ultra wide-angle Camera DNG No 14.0 2021
+Sony Xperia 5 III Wide-angle Camera DNG No 14.0 2021
+Sony Xperia 5 V Front Camera DNG No 16.4 2023
+Sony Xperia 5 V Ultrawide Camera DNG No 16.4 2023
+Sony Xperia 5 V Wide Camera DNG No 16.4 2023
+Sony Xperia Pro-I Rear Wide Camera DNG No 14.2 2021
+Sony ZV-E10 ARW Yes 14.0 2021
+Sony ZV-E1 ARW Yes 15.4 2022
+Sony ZV-E10M2 ARW
+Sony ZV-1M2 ARW Yes 15.4 2022
+Sony ZV-1A ARW No 17.4 2024
+Sony ILX-LR1 ARW Yes 16.2.1 2023
+Sony ILCE - 1M2 (Final Support) ARW Yes 17.2 2024
+Sony ILCE - 6100A ARW Yes 17.2 2024
+Sony ILCE - 6400A ARW Yes 17.2 2024
+Sony FX2 (ILMEFX2) (** see note) ARW Yes 17.4.1 2024
+Sony FX3A (ILMEFX3A) ARW Yes 17.4.1 2024
+Sony RX1R III (DSC-RX1RM3) ARW Yes 17.4.2 2024
+Xiaomi Mi 11 Lite Front Camera DNG No 14.0 2021
+Xiaomi Mi 11 Lite Rear Ultrawide Camera DNG No 14.0 2021
+Xiaomi Mi 11 Lite Rear Wide Camera DNG No 14.0 2021
+Xiaomi Xiaomi 12S DNG No 14.4 2021
+Xiaomi Xiaomi 12S Pro DNG No 14.4 2021
+Xiaomi Xiaomi 12S Ultra DNG No 14.4 2021
+Xiaomi Xiaomi 13 Rear Telephoto Camera DNG No 15.1 2022
+Xiaomi Xiaomi 13 Rear Ultrawide Camera DNG No 15.1 2022
+Xiaomi Xiaomi 13 Rear Wide Camera DNG No 15.1 2022
+Xiaomi Xiaomi 13 Pro Rear Telephoto Camera DNG No 15.1 2022
+Xiaomi Xiaomi 13 Pro Rear Ultrawide Camera DNG No 15.1 2022
+Xiaomi Xiaomi 13 Pro Rear Wide Camera DNG No 15.1 2022
+Xiaomi Xiaomi 13 Ultra Rear Super Telephoto Camera DNG No 15.3 2022
+Xiaomi Xiaomi 13 Ultra Rear Telephoto Camera DNG No 15.3 2022
+Xiaomi Xiaomi 13 Ultra Rear Ultrawide Camera DNG No 15.3 2022
+Xiaomi Xiaomi 13 Ultra Rear Wide Camera DNG No 15.3 2022
+Xiaomi Xiaomi 13T Front Camera DNG No 15.5 2022
+Xiaomi Xiaomi 13T Rear Telephoto Camera DNG No 15.5 2022
+Xiaomi Xiaomi 13T Rear Ultrawide Camera DNG No 15.5 2022
+Xiaomi Xiaomi 13T Rear Wide Camera DNG No 15.5 2022
+Xiaomi Xiaomi 13T Pro Front Camera DNG No 15.5 2022
+Xiaomi Xiaomi 13T Pro Rear Telephoto Camera DNG No 15.5 2022
+Xiaomi Xiaomi 13T Pro Rear Ultrawide Camera DNG No 15.5 2022
+Xiaomi Xiaomi 13T Pro Rear Wide Camera DNG No 15.5 2022
+Xiaomi Xiaomi 14 Rear Telephoto Camera DNG No 16.0 2023
+Xiaomi Xiaomi 14 Rear Ultrawide Camera DNG No 16.0 2023
+Xiaomi Xiaomi 14 Rear Wide Camera DNG No 16.0 2023
+Xiaomi Xiaomi 14 Pro Rear Telephoto Camera DNG No 16.0 2023
+Xiaomi Xiaomi 14 Pro Rear Ultrawide Camera DNG No 16.0 2023
+Xiaomi Xiaomi 14 Pro Rear Wide Camera DNG No 16.0 2023
+Xiaomi Xiaomi 14 Ultra Rear Super Telephoto Camera DNG No 16.2 2023
+Xiaomi Xiaomi 14 Ultra Rear Telephoto Camera DNG No 16.2 2023
+Xiaomi Xiaomi 14 Ultra Rear Ultrawide Camera DNG No 16.2 2023
+Xiaomi Xiaomi 14 Ultra Rear Wide Camera DNG No 16.2 2023
+Xiaomi Xiaomi 14T Front Camera DNG No 16.5 2023
+Xiaomi Xiaomi 14T Telephoto Camera DNG No 16.5 2023
+Xiaomi Xiaomi 14T Ultrawide Camera DNG No 16.5 2023
+Xiaomi Xiaomi 14T Wide Camera DNG No 16.5 2023
+Xiaomi Xiaomi 14T Pro Front Camera DNG No 16.5 2023
+Xiaomi Xiaomi 14T Pro Telephoto Camera DNG No 16.5 2023
+Xiaomi Xiaomi 14T Pro Ultrawide Camera DNG No 16.5 2023
+Xiaomi Xiaomi 14T Pro Wide Camera DNG No 16.5 2023
+Xiaomi Xiaomi MIX Fold 3 Rear Telephoto Camera DNG No 15.5 2022
+Xiaomi Xiaomi MIX Fold 3 Rear Super Telephoto Camera DNG No 15.5 2022
+Xiaomi Xiaomi MIX Fold 3 Rear Ultrawide Camera DNG No 15.5 2022
+Xiaomi Xiaomi MIX Fold 3 Rear Wide Camera DNG No 15.5 2022
+Xiaomi Xiaomi Civi 4 Pro Rear Telephoto Camera DNG No 16.3 2023
+Xiaomi Xiaomi Civi 4 Pro Rear Ultrawide Camera DNG No 16.3 2023
+Xiaomi Xiaomi Civi 4 Pro Rear Wide Camera DNG No 16.3 2023
+Xiaomi Xiaomi 15 Rear Telephoto Camera DNG No 17.0 2024
+Xiaomi Xiaomi 15 Rear Ultrawide Camera DNG No 17.0 2024
+Xiaomi Xiaomi 15 Rear Wide Camera DNG No 17.0 2024
+Xiaomi Xiaomi 15 Pro Rear Telephoto Camera DNG No 17.0 2024
+Xiaomi Xiaomi 15 Pro Rear Ultrawide Camera DNG No 17.0 2024
+Xiaomi Xiaomi 15 Pro Rear Wide Camera DNG No 17.0 2024
+Xiaomi Xiaomi 17 Ultra Rear Telephoto Camera DNG No 18.1.1 2025
+Xiaomi Xiaomi 17 Ultra Rear Ultrawide Camera DNG No 18.1.1 2025
+Xiaomi Xiaomi 17 Ultra Rear Wide Camera DNG No 18.1.1 2025
+Yuneec CGO4 DNG No 9.5 2015
+Yuneec ION L1 Pro DNG No 11.3 2018
+Zeiss ZX1 DNG No 12.1 2019
diff --git a/docs/raw/coverage.py b/docs/raw/coverage.py
new file mode 100644
index 000000000..c635c993a
--- /dev/null
+++ b/docs/raw/coverage.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env python3
+"""Measure LightCraft's raw camera coverage against Adobe's supported-camera list.
+
+ python3 docs/raw/coverage.py # print the summary, rewrite docs/raw/coverage.tsv
+ python3 docs/raw/coverage.py --check # exit 1 if coverage.tsv is stale
+
+Inputs (all in docs/raw/):
+ adobe-cameras.tsv Adobe's published list (maker, camera, extensions, ...).
+ evidence.tsv per-model evidence: verified decodes, known preview-only or refused bodies.
+ usage-weights.tsv estimated share of photographers per maker bucket and popular bodies.
+
+Every Adobe-listed model gets one class:
+ verified decoded from sensor data, and a real file from this body was checked (evidence.tsv)
+ unverified the decoder path for its format and coding should handle it; no sample checked
+ preview opens as the embedded JPEG (format or coding not decoded)
+ unsupported not imported, refused, or decodes wrongly
+
+The format rules below mirror the decoders in crates/raw/src (see docs/raw-parity.md). When a decoder
+changes, update the rules and evidence, rerun, and commit coverage.tsv with the docs.
+Standard library only; no third-party packages.
+"""
+
+import csv
+import os
+import re
+import sys
+
+HERE = os.path.dirname(os.path.abspath(__file__))
+
+# Olympus ORF bodies that write uncompressed / packed data (crates/raw/src/vendor/orf.rs). Everything else
+# writes compressed ORF, which is preview-only (the E-M5 II / PEN-F high-res mode alone is decoded).
+ORF_DECODED = re.compile(r"^(E-1|E-10|E-20|EVOLT E-300|EVOLT E-330|EVOLT E-400|EVOLT E-500|E-300|E-330|E-500|Camedia C-\S+.*|Stylus XZ-2 iHS)$")
+# Samsung SRW bodies with compressed raw data (crates/raw/src/vendor/srw.rs refuses 32770 / 32772 / 32773).
+SRW_COMPRESSED = re.compile(r"(^|\W)(NX1|NX30|NX300|NX300M|NX500|NX2000|NX3000|NX3300|NX mini|EK-GN\d+|EK-KN\d+)(\W|$)")
+# Fujifilm bodies before the X series whose RAF has no raw IFD (crates/raw/src/vendor/raf.rs).
+RAF_OLD = re.compile(r"^(FinePix (E|F|S\d|S\d{3,4}|IS)|IS Pro|S5Pro|S3Pro|S2Pro)", re.I)
+
+# Probability that an unverified body of a format decodes correctly, used for the use-weighted share only.
+CONFIDENCE = {"DNG": 0.95, "CR2": 0.95, "CR3": 0.6, "NEF": 0.9, "NRW": 0.9, "ARW": 0.9, "SR2": 0.8, "RAF": 0.95,
+ "RW2": 0.95, "RWL": 0.95, "RAW": 0.95, "PEF": 0.9, "ORF": 0.8, "SRW": 0.8}
+
+
+def read_tsv(name):
+ with open(os.path.join(HERE, name), newline="", encoding="utf-8") as f:
+ lines = [line for line in f if not line.startswith("#")]
+ return list(csv.DictReader(lines, delimiter="\t"))
+
+
+def exts(field):
+ return [e.strip().upper() for e in re.split(r"[,/+]", re.sub(r"\(.*?\)", "", field)) if e.strip()]
+
+
+def classify(maker, camera, ext_field):
+ """(class, format, reason) from the format rules alone."""
+ es = exts(ext_field)
+ for e in es:
+ if e == "DNG":
+ return "unverified", "DNG", "DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)"
+ if e == "CR2":
+ return "unverified", "CR2", "CR2 lossless JPEG decoder"
+ if e == "CR3":
+ return "unverified", "CR3", "CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview"
+ if e in ("NEF", "NRW"):
+ return "unverified", e, "NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only"
+ if e == "ARW":
+ return "unverified", "ARW", "ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)"
+ if e == "SR2":
+ return "unverified", "SR2", "SR2 decoder"
+ if e == "RAF":
+ if RAF_OLD.search(camera):
+ return "preview", "RAF", "pre-X FinePix RAF without a raw IFD"
+ return "unverified", "RAF", "RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)"
+ if e in ("RW2", "RWL") or (e == "RAW" and maker in ("Panasonic", "Leica")):
+ return "unverified", e, "Panasonic decoder, every raw format"
+ if e == "PEF":
+ return "unverified", "PEF", "PEF decoder (uncompressed, Huffman, packed 12-bit)"
+ if e == "ORF":
+ if ORF_DECODED.match(camera):
+ return "unverified", "ORF", "uncompressed / packed ORF"
+ return "preview", "ORF", "compressed ORF is not decoded"
+ if e == "SRW":
+ if SRW_COMPRESSED.search(camera):
+ return "preview", "SRW", "compressed SRW is not decoded"
+ return "unverified", "SRW", "uncompressed / packed SRW"
+ e = es[0] if es else ""
+ if e in ("CRW", "3FR", "FFF", "IIQ", "MOS", "ERF", "KDC", "MRW", "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"
+
+
+def main():
+ adobe = read_tsv("adobe-cameras.tsv")
+ evidence = {(r["maker"], r["camera"]): r for r in read_tsv("evidence.tsv")}
+ rows = []
+ for a in adobe:
+ cls, fmt, reason = classify(a["maker"], a["camera"], a["extensions"])
+ ev = evidence.get((a["maker"], a["camera"]))
+ if ev:
+ cls, reason = ev["status"], ev["source"]
+ rows.append({"maker": a["maker"], "camera": a["camera"], "format": fmt, "year_added": a["year_added"], "class": cls, "reason": reason})
+
+ out = os.path.join(HERE, "coverage.tsv")
+ header = ["maker", "camera", "format", "year_added", "class", "reason"]
+ text = "# Generated by docs/raw/coverage.py; do not edit.\n" + "\t".join(header) + "\n"
+ text += "".join("\t".join(r[k] for k in header) + "\n" for r in rows)
+ if "--check" in sys.argv:
+ with open(out, encoding="utf-8") as f:
+ if f.read() != text:
+ print("docs/raw/coverage.tsv is stale: run python3 docs/raw/coverage.py")
+ return 1
+ else:
+ with open(out, "w", encoding="utf-8") as f:
+ f.write(text)
+
+ classes = ["verified", "unverified", "preview", "unsupported"]
+ by_maker = {}
+ for r in rows:
+ by_maker.setdefault(r["maker"], {c: 0 for c in classes})[r["class"]] += 1
+ print("| Maker | Adobe models | verified | unverified | preview | unsupported |")
+ print("|---|---:|---:|---:|---:|---:|")
+ for m, c in sorted(by_maker.items(), key=lambda kv: -sum(kv[1].values())):
+ print(f"| {m} | {sum(c.values())} | " + " | ".join(str(c[k]) for k in classes) + " |")
+ tot = {k: sum(c[k] for c in by_maker.values()) for k in classes}
+ n = sum(tot.values())
+ print(f"| **All** | **{n}** | " + " | ".join(f"**{tot[k]}** ({100 * tot[k] / n:.0f}%)" for k in classes) + " |")
+ recent = [r for r in rows if r["year_added"] and int(r["year_added"]) >= 2017]
+ rc = {k: sum(1 for r in recent if r["class"] == k) for k in classes}
+ print(f"\nModels added since 2017 (ACR 10+): {len(recent)}: " + ", ".join(f"{k} {rc[k]}" for k in classes))
+
+ # Use-weighted share of photographers whose camera fully works (decodes from sensor data).
+ weights = read_tsv("usage-weights.tsv")
+ by_cam = {(r["maker"], r["camera"]): r for r in rows}
+ total = works = verified_share = 0.0
+ print("\n| Bucket | Weight | Camera fully works | of which verified bodies |")
+ print("|---|---:|---:|---:|")
+ for b in [w for w in weights if w["kind"] == "maker"]:
+ makers = b["makers_or_camera"].split(",")
+ popular = [w["makers_or_camera"] for w in weights if w["kind"] == "body" and w["bucket"] == b["bucket"]]
+ members = [r for r in rows if r["maker"] in makers]
+ pop_rows = [r for m in makers for (mk, cam), r in by_cam.items() if mk == m and cam in popular]
+ missing = set(popular) - {r["camera"] for r in pop_rows}
+ if missing:
+ print(f"usage-weights.tsv: unknown cameras {sorted(missing)}", file=sys.stderr)
+ return 1
+ rest = [r for r in members if r["camera"] not in popular]
+
+ def score(r):
+ if r["class"] == "verified":
+ return 1.0
+ if r["class"] == "unverified":
+ return CONFIDENCE.get(r["format"], 0.7)
+ return 0.0
+
+ def mean(rs, f):
+ return sum(f(r) for r in rs) / len(rs) if rs else 0.0
+
+ if pop_rows:
+ s = 0.8 * mean(pop_rows, score) + 0.2 * mean(rest, score)
+ v = 0.8 * mean(pop_rows, lambda r: r["class"] == "verified") + 0.2 * mean(rest, lambda r: r["class"] == "verified")
+ else:
+ s, v = mean(members, score), mean(members, lambda r: r["class"] == "verified")
+ w = float(b["weight"])
+ total += w
+ works += w * s
+ verified_share += w * v
+ print(f"| {b['bucket']} | {w:g}% | {100 * s:.0f}% | {100 * v:.0f}% |")
+ print(f"| **Weighted** | {total:g}% | **{100 * works / total:.0f}%** | **{100 * verified_share / total:.0f}%** |")
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/docs/raw/coverage.tsv b/docs/raw/coverage.tsv
new file mode 100644
index 000000000..8dc879069
--- /dev/null
+++ b/docs/raw/coverage.tsv
@@ -0,0 +1,1448 @@
+# Generated by docs/raw/coverage.py; do not edit.
+maker camera format year_added class reason
+Apple iPhone 17e Front Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 17e Rear Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 17 Front Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 17 Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 17 Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 17 Pro Front Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 17 Pro Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 17 Pro Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 17 Pro Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 17 Pro Max Front Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 17 Pro Max Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 17 Pro Max Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 17 Pro Max Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone Air Front Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone Air Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 16e Front Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 16e Wide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 16 Front Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 16 Ultrawide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 16 Wide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 16 Plus Front Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 16 Plus Ultrawide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 16 Plus Wide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 16 Pro Front Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 16 Pro Telephoto Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 16 Pro Ultrawide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 16 Pro Wide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 16 Pro Max Front Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 16 Pro Max Telephoto Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 16 Pro Max Ultrawide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 16 Pro Max WIde Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 15 Plus Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 15 Pro Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 15 Pro Front Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 15 Pro Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 15 Pro Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 15 Pro Max Front Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 15 Pro Max Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 15 Pro Max Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 15 Pro Max Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 14 Front Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 14 Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 14 Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 14 Plus Front Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 14 Plus Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 14 Plus Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 14 Pro Front Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 14 Pro Telephoto Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 14 Pro Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 14 Pro Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 14 Pro Max Front Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 14 Pro Max Telephoto Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 14 Pro Max Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 14 Pro Max Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 13 Front Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 13 Rear Ultrawide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 13 Rear Wide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 13 mini Front Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 13 mini Rear Ultrawide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 13 mini Rear Wide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 13 Pro Front Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 13 Pro Rear Telephoto Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 13 Pro Rear Ultrawide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 13 Pro Rear Wide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 13 Pro Max Front Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 13 Pro Max Rear Telephoto Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 13 Pro Max Rear Ultrawide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 13 Pro Max Rear Wide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 12 Rear Wide Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 12 Mini Rear Wide Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 12 Ultrawide camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 12 Mini Ultrawide camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 12 Pro Front Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 12 Pro Rear Wide Camera DNG 2020 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Apple iPhone 12 Pro Rear Telephoto Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 12 Pro Rear Ultra Wide Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 12 Pro Max Front Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 12 Pro Max Rear Wide Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 12 Pro Max Rear Telephoto Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 12 Pro Max Rear Ultra Wide Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 11 DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 11 Pro DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 11 Pro Max DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPad Pro 9.7" (Wifi and Cellular) DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPad Pro 11-inch (2nd generation) DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPad Pro 11-inch (3rd generation) DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPad Pro 11-inch (4th generation) Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPad Pro 11-inch (2018 model) DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPad Pro 11-inch M4 (2024 version) Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPad Air 11-inch M2 (2024 version) Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPad Pro 12.9-inch (4th generation) DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPad Pro 12.9-inch (5th generation) DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPad Pro 12.9-inch (6th generation) Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPad Pro 12.9-inch (2018 model) DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPad Pro 13-inch M4 (2024 version) Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPad mini (6th Generation) Rear Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 6s DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 6s Plus DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 7 DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 7 Plus DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 8 DNG 2017 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone 8 Plus DNG 2017 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone SE (2020 version) DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone SE DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone X DNG 2017 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone XR DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone XS DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone XS Max DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPad Air (5th generation) Rear Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone SE (3rd generation) Front Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPhone SE (3rd generation) Rear Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPad (10th Generation) Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Apple iPad mini (A17 Pro) Rear Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Canon EOS-1D TIF 2003 unsupported TIF is not imported
+Canon EOS-1D C CR2 2012 unverified CR2 lossless JPEG decoder
+Canon EOS-1D Mark II CR2 2003 unverified CR2 lossless JPEG decoder
+Canon EOS-1D Mark II N CR2 2005 unverified CR2 lossless JPEG decoder
+Canon EOS-1D Mark III CR2 2007 unverified CR2 lossless JPEG decoder
+Canon EOS-1D Mark IV CR2 2008 unverified CR2 lossless JPEG decoder
+Canon EOS-1Ds TIF 2003 unsupported TIF is not imported
+Canon EOS-1Ds Mark II CR2 2005 unverified CR2 lossless JPEG decoder
+Canon EOS-1Ds Mark III CR2 2007 unverified CR2 lossless JPEG decoder
+Canon EOS-1D X CR2 2012 unverified CR2 lossless JPEG decoder
+Canon EOS-1D X Mark II CR2 2015 unverified CR2 lossless JPEG decoder
+Canon EOS-1D X Mark III CR3 2019 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS 5D CR2 2005 unverified CR2 lossless JPEG decoder
+Canon EOS 5D Mark II CR2 2008 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Canon EOS-5D Mark III CR2 2012 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Canon EOS-5D Mark IV (** see note) CR2 2015 unverified CR2 lossless JPEG decoder
+Canon EOS 5DS CR2 2015 unverified CR2 lossless JPEG decoder
+Canon EOS 5DS R CR2 2015 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Canon EOS 6D CR2 2012 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Canon EOS 6D Mark II CR2 2015 unverified CR2 lossless JPEG decoder
+Canon EOS 7D CR2 2008 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Canon EOS 7D Mark II CR2 2013 unverified CR2 lossless JPEG decoder
+Canon EOS 10D CRW 2003 preview CRW is opened from its embedded JPEG only
+Canon EOS 20D CR2 2003 unverified CR2 lossless JPEG decoder
+Canon EOS 20Da CR2 2005 unverified CR2 lossless JPEG decoder
+Canon EOS 30D CR2 2005 unverified CR2 lossless JPEG decoder
+Canon EOS 40D CR2 2007 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Canon EOS 50D CR2 2008 unverified CR2 lossless JPEG decoder
+Canon EOS 60D CR2 2010 unverified CR2 lossless JPEG decoder
+Canon EOS 60Da CR2 2012 unverified CR2 lossless JPEG decoder
+Canon EOS 70D CR2 2013 unverified CR2 lossless JPEG decoder
+Canon EOS 77D (EOS 9000D) CR2 2015 unverified CR2 lossless JPEG decoder
+Canon EOS 80D CR2 2015 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Canon EOS 90D CR3 2018 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS 200D (EOS Kiss X9, EOS Rebel SL2) CR2 2015 unverified CR2 lossless JPEG decoder
+Canon EOS 250D (EOS Rebel SL3, EOS 200D II, EOS Kiss X10) CR3 2018 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+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 100D (EOS Digital Rebel SL1, EOS Kiss X7) CR2 2012 unverified CR2 lossless JPEG decoder
+Canon EOS 500D (EOS Digital Rebel T1i, EOS Kiss X3 Digital) CR2 2008 unverified CR2 lossless JPEG decoder
+Canon EOS 550D (EOS Digital Rebel T2i, EOS Kiss X4 Digital) CR2 2010 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Canon EOS 1100D (EOS Digital Rebel T3, EOS Kiss X50 Digital) CR2 2010 unverified CR2 lossless JPEG decoder
+Canon EOS 600D (EOS Digital Rebel T3i, EOS Kiss X5 Digital) CR2 2010 unverified CR2 lossless JPEG decoder
+Canon EOS 650D (EOS Digital Rebel T4i, EOS Kiss X4i) CR2 2012 unverified CR2 lossless JPEG decoder
+Canon EOS 700D (EOS Digital Rebel T5i, EOS Kiss X7i) CR2 2012 unverified CR2 lossless JPEG decoder
+Canon EOS 1200D (EOS Digital Rebel T5, EOS Kiss X70) CR2 2013 unverified CR2 lossless JPEG decoder
+Canon EOS 1300D (EOS Digital Rebel T6, EOS Kiss X80) CR2 2015 unverified CR2 lossless JPEG decoder
+Canon EOS 1500D (EOS Rebel T7, EOS Kiss X90, EOS 2000D) CR2 2017 unverified CR2 lossless JPEG decoder
+Canon EOS 3000D (EOS Rebel T100, EOS 4000D) CR2 2017 unverified CR2 lossless JPEG decoder
+Canon EOS 750D (EOS Digital Rebel T6i, EOS Kiss X8i) CR2 2015 unverified CR2 lossless JPEG decoder
+Canon EOS 760D (EOS Digital Rebel T6s, EOS Kiss 8000D) CR2 2015 unverified CR2 lossless JPEG decoder
+Canon EOS 800D (EOS Digital Rebel T7i, EOS Kiss X9i) CR2 2015 unverified CR2 lossless JPEG decoder
+Canon EOS 850D (EOS Kiss X10i, EOS Rebel T8i) CR3 2019 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS 1000D (EOS Digital Rebel XS, EOS Kiss F) CR2 2008 unverified CR2 lossless JPEG decoder
+Canon EOS 450D (EOS Digital Rebel XSi, EOS Kiss Digital X2) CR2 2007 unverified CR2 lossless JPEG decoder
+Canon EOS 350D (EOS Digital Rebel XT, EOS Kiss Digital N) CR2 2005 unverified CR2 lossless JPEG decoder
+Canon EOS 400D (EOS Digital Rebel XTi, EOS Kiss Digital X) CR2 2005 unverified CR2 lossless JPEG decoder
+Canon EOS C50 CR3 2025 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS M CR2 2012 unverified CR2 lossless JPEG decoder
+Canon EOS M2 CR2 2013 unverified CR2 lossless JPEG decoder
+Canon EOS M200 CR3 2019 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS M3 CR2 2015 unverified CR2 lossless JPEG decoder
+Canon EOS M5 CR2 2015 unverified CR2 lossless JPEG decoder
+Canon EOS M50 Mark II CR3 2020 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS M6 CR2 2015 unverified CR2 lossless JPEG decoder
+Canon EOS M6 Mark II CR3 2018 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS M10 CR2 2015 unverified CR2 lossless JPEG decoder
+Canon EOS M50 (EOS Kiss M) CR3 2017 verified corpus: crates/raw/tests/cr3_corpus.rs (exact sensor hashes, docs/cr3.md)
+Canon EOS M100 CR2 2017 unverified CR2 lossless JPEG decoder
+Canon EOS R (** see note) CR3 2018 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS R1 CR3 2023 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS R3 CR3 2021 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS R5 CR3 2019 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS R5 C CR3 2021 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS R5 Mark II CR3 2023 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS R6 CR3 2019 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS R6 V CR3 2025 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS R6 Mark II CR3 2022 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS R6 Mark III CR3 2025 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS R7 CR3 2021 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS R8 Mark II CR3 2025 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS R10 CR3 2021 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS Ra CR3 2019 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS RP CR3 2018 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS R8 CR3 2022 verified corpus: crates/raw/tests/cr3_corpus.rs (exact sensor hashes, docs/cr3.md)
+Canon EOS R50 CR3 2022 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS R50 V CR3 2024 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon EOS R100 CR3 2022 verified corpus: crates/raw/tests/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 G1 X CR2 2012 unverified CR2 lossless JPEG decoder
+Canon PowerShot G1 X Mark II CR2 2013 unverified CR2 lossless JPEG decoder
+Canon PowerShot G1 X Mark III CR2 2017 unverified CR2 lossless JPEG decoder
+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 G3 X CR2 2015 unverified CR2 lossless JPEG decoder
+Canon PowerShot G5 CRW 2003 preview CRW is opened from its embedded JPEG only
+Canon PowerShot G5 X CR2 2015 unverified CR2 lossless JPEG decoder
+Canon PowerShot G5 X Mark II CR3 2018 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon PowerShot G6 CRW 2003 preview CRW is opened from its embedded JPEG only
+Canon PowerShot G7 X CR2 2013 unverified CR2 lossless JPEG decoder
+Canon PowerShot G7 X Mark II CR2 2015 unverified CR2 lossless JPEG decoder
+Canon PowerShot G7 X Mark III CR3 2018 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon PowerShot G9 CR2 2007 unverified CR2 lossless JPEG decoder
+Canon PowerShot G9X CR2 2015 unverified CR2 lossless JPEG decoder
+Canon PowerShot G9 X Mark II CR2 2015 unverified CR2 lossless JPEG decoder
+Canon PowerShot G10 CR2 2008 unverified CR2 lossless JPEG decoder
+Canon PowerShot G11 CR2 2008 unverified CR2 lossless JPEG decoder
+Canon PowerShot G12 CR2 2010 unverified CR2 lossless JPEG decoder
+Canon PowerShot G15 CR2 2012 unverified CR2 lossless JPEG decoder
+Canon PowerShot G16 CR2 2013 unverified CR2 lossless JPEG decoder
+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 S90 CR2 2008 unverified CR2 lossless JPEG decoder
+Canon PowerShot S95 CR2 2010 unverified CR2 lossless JPEG decoder
+Canon PowerShot S70 CRW 2003 preview CRW is opened from its embedded JPEG only
+Canon PowerShot S100 CR2 2010 unverified CR2 lossless JPEG decoder
+Canon PowerShot S100V CR2 2010 unverified CR2 lossless JPEG decoder
+Canon PowerShot S110 CR2 2012 unverified CR2 lossless JPEG decoder
+Canon PowerShot S120 CR2 2013 unverified CR2 lossless JPEG decoder
+Canon PowerShot SX1 IS CR2 2008 unverified CR2 lossless JPEG decoder
+Canon PowerShot SX50 CR2 2012 unverified CR2 lossless JPEG decoder
+Canon PowerShot SX60 HS CR2 2013 unverified CR2 lossless JPEG decoder
+Canon PowerShot SX70 HS CR3 2018 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Canon PowerShot V1 CR3 2024 unverified CRX decoder: lossless and C-RAW 0x100 / 0x200 single-tile; other variants fall back to the preview
+Casio EXILIM EX-10 DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-100 DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-100F DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-100PRO DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-FC300S DNG 2012 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-FH100 DNG 2010 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR700 DNG 2012 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR710 DNG 2012 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR750 DNG 2012 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR800 DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR850 DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR1000 DNG 2012 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR1100 DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR1200 DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR1300 DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR1500 DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR3000 DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR3100 DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR3200 DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR3500 DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR3600 DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR3700 DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR4000 DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR4100 DNG 2017 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR5000 DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Casio EXILIM EX-ZR5100 DNG 2017 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Contax N Digital RAW 2003 preview RAW is opened from its embedded JPEG only
+DJI DJI Mavic 2 Pro (Hasselblad L1D-20c) DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI AIR 2S DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Mini 2 DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Air 3 Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Air 3 Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Mini 3 Pro DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Mini 4 Pro DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Mavic 3 Classic (Hasselblad L2D-20c) DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Mavic 3 Cine Super Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Mavic 3 Cine (Hasselblad L2D-20c) DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Mavic 3 Pro Super Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Mavic 3 Pro Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Mavic 3 Pro (Hasselblad L2D-20c) DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Mavic 3 (Hasselblad L2D-20c) DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Osmo Pocket 3 D DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Air 3S Telephoto Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Air 3S Wide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Osmo Action 5 Pro DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Mavic 4 Pro (DJI FC9284) DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Mavic 4 Pro (DJI FC9287) DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DJI DJI Mavic 4 Pro (Hasselblad L3D-100c) DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+DxO DxO ONE DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Epson R-D1 ERF 2003 preview ERF is opened from its embedded JPEG only
+Epson R-D1s ERF 2005 preview ERF is opened from its embedded JPEG only
+Epson R-D1x ERF 2008 preview ERF is opened from its embedded JPEG only
+Fujifilm GFX 50R RAF 2017 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm GFX 50S RAF 2015 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm GFX 50S II RAF 2020 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm GFX 100 RAF 2018 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm GFX 100 II RAF 2022 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm GFX 100 II IR RAF 2024 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm GFX 100 IR RAF 2020 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm GFX 100S RAF 2020 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm GFX 100S II RAF 2023 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm GFX 100RF (Final Support) RAF 2024 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm FinePix E900 RAF 2005 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix F505 EXR RAF 2012 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix F550 EXR RAF 2010 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix F600 EXR RAF 2010 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix F605 EXR RAF 2012 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix F700 RAF 2003 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix F770 EXR RAF 2012 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix F775 EXR RAF 2012 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix F800 EXR RAF 2012 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix F900 EXR RAF 2012 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix HS10 RAF 2010 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm FinePix HS20 EXR RAF 2010 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm FinePix HS22 EXR RAF 2013 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm FinePix HS30 EXR RAF 2012 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm FinePix HS33 EXR RAF 2012 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm FinePix HS35 EXR RAF 2013 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm FinePix HS50 EXR RAF 2012 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm FinePix IS-1 RAF 2007 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix IS Pro RAF 2008 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix S1 RAF 2013 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix S100fs RAF 2007 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix S2 Pro RAF 2003 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix S20 Pro RAF 2003 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix S200 EXR RAF 2010 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix S205 EXR RAF 2013 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix S3 Pro RAF 2005 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix S5 Pro RAF 2007 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix S5000 Z RAF 2003 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix S5200 (FinePix S5600) RAF 2005 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix S6000fd (FinePix S6500fd) RAF 2005 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix S7000 Z RAF 2003 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix S9000 (FinePix S9500) RAF 2005 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix S9100 (FinePix S9600) RAF 2005 preview pre-X FinePix RAF without a raw IFD
+Fujifilm FinePix SL1000 RAF 2012 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm FinePix X10 RAF 2010 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm FinePix X100 RAF 2010 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm FinePix X-M1 RAF 2013 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm FinePix X-S1 RAF 2012 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X100F RAF 2015 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X100S RAF 2012 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X100T RAF 2013 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X100V** RAF 2019 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X100VI RAF 2023 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X20 RAF 2012 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X30 RAF 2013 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X70 RAF 2015 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-A1 RAF 2013 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-A2 RAF 2015 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-A3 RAF 2015 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-A5 RAF 2017 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-A7 RAF 2018 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-A10 RAF 2015 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-A20 RAF 2017 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-E1 RAF 2012 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-E2 RAF 2013 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-E2S RAF 2015 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-E3 RAF 2017 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-E4 RAF 2020 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-E5 RAF 2024 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-H1 RAF 2017 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-H2 IR RAF 2024 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-H2S RAF 2021 verified docs/raf-compression.md (supplied files, exact sensor arrays)
+Fujifilm X-M5 RAF 2024 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-Pro1 RAF 2012 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-Pro2 RAF 2015 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-Pro3** RAF 2019 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-S10 RAF 2020 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-S20 RAF 2022 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-T1 RAF 2013 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-T1 IR RAF 2015 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-T2 RAF 2015 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-T3 RAF 2018 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-T4** RAF 2019 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-T5 RAF 2022 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-T10 RAF 2015 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-T20 RAF 2015 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-T30 RAF 2018 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-T30 II RAF 2021 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-T30 III RAF 2022 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-T50 RAF 2023 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-T100 RAF 2017 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-T200 RAF 2019 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm XF1 RAF 2012 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm XF10 RAF 2017 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm XQ1 RAF 2013 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm XQ2 RAF 2015 unverified RAF decoder (uncompressed, lossless / lossy compressed, Bayer and X-Trans)
+Fujifilm X-H2 RAF 2021 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Google Pixel DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel XL DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 2 DNG 2017 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 2 XL DNG 2017 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Google Pixel 3 DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 3 Front Standard Camera DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 3 Front Wide Camera DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 3 XL DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 3 XL Front Standard Camera DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 3 XL Front Wide Camera DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 4 DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 4 XL DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 5 Front Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 5 Rear Main Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 5 Rear Ultra Wide Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 6 Front Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 6 Rear Main Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 6 Rear Ultrawide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 6 Pro Front Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 6 Pro Rear Main Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 6 Pro Rear Telephoto Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 6 Pro Rear Ultrawide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 7 Front Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 7 Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 7 Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 7 Pro Front Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 7 Pro Telephoto Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 7 Pro Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 7 Pro Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 4a Front Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 4a Rear Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 5a Front Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 5a Rear Main Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 5a Rear Ultra Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 6a Front Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 6a Rear Main Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 6a Rear Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 7a Front Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 7a Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 7a Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 8 Front Camera* DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 8 Ultrawide Camera* DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 8 Wide Camera* DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 8 Pro Front Camera* DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 8 Pro Telephoto Camera* DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 8 Pro Ultrawide Camera* DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 8 Pro Wide Camera* DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 8a Front Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 8a Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 8a Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 9 Front Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 9 Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 9 Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 9 Pro Front Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 9 Pro Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 9 Pro Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 9 Pro Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 9 Pro XL Front Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel Pixel 9 Pro XL Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel Pixel 9 Pro XL Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 9 Pro XL Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 9 Pro Fold Front Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 9 Pro Fold Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 9 Pro Fold Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 9 Pro Fold Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 9a Front Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 9a Ultrawide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 9a Wide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 10 Pro Front Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 10 Pro Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 10 Pro Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 10 Pro Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 10 Pro XL Front Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 10 Pro XL Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 10 Pro XL Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 10 Pro XL Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 10 Front Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 10 Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 10 Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 10 Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 10 Pro Fold Front Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 10 Pro Fold Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 10 Pro Fold Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel 10 Pro Fold Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel Fold Inner Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel Fold Telephoto Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel Fold Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Google Pixel Fold Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+GoPro FUSION GPR 2017 unsupported GPR is not imported
+GoPro HERO5 Black GPR 2015 unsupported GPR is not imported
+GoPro HERO6 Black GPR 2017 unsupported GPR is not imported
+GoPro HERO7 Black GPR 2018 unsupported GPR is not imported
+GoPro HERO10 Black GPR 2021 unsupported GPR is not imported
+GoPro HERO12 Black GPR 2024 unsupported GPR is not imported
+GoPro HERO13 Black GPR 2024 unsupported GPR is not imported
+GoPro MISSION 1 GPR 2025 unsupported GPR is not imported
+GoPro MISSION 1 PRO GPR 2025 unsupported GPR is not imported
+Hasselblad 503CWD 3FR 2008 preview 3FR is opened from its embedded JPEG only
+Hasselblad A6D-100c 3FR 2017 preview 3FR is opened from its embedded JPEG only
+Hasselblad CF-22 3FR 2008 preview 3FR is opened from its embedded JPEG only
+Hasselblad CF-22MS 3FR 2008 preview 3FR is opened from its embedded JPEG only
+Hasselblad CF-39 3FR 2008 preview 3FR is opened from its embedded JPEG only
+Hasselblad CF-39MS 3FR 2008 preview 3FR is opened from its embedded JPEG only
+Hasselblad CFH-22 3FR 2008 preview 3FR is opened from its embedded JPEG only
+Hasselblad CFH-39 3FR 2008 preview 3FR is opened from its embedded JPEG only
+Hasselblad CFV 3FR 2008 preview 3FR is opened from its embedded JPEG only
+Hasselblad CFV-50c 3FR 2008 preview 3FR is opened from its embedded JPEG only
+Hasselblad H2D DNG 2003 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Hasselblad H2D-22 3FR 2008 preview 3FR is opened from its embedded JPEG only
+Hasselblad H2D-39 3FR 2008 preview 3FR is opened from its embedded JPEG only
+Hasselblad H3D-22 3FR 2008 preview 3FR is opened from its embedded JPEG only
+Hasselblad H3D-39 3FR 2008 preview 3FR is opened from its embedded JPEG only
+Hasselblad H3DII-22 3FR 2008 preview 3FR is opened from its embedded JPEG only
+Hasselblad H3DII-31 3FR 2008 preview 3FR is opened from its embedded JPEG only
+Hasselblad H3DII-39 3FR 2008 preview 3FR is opened from its embedded JPEG only
+Hasselblad H3DII-39MS 3FR 2008 preview 3FR is opened from its embedded JPEG only
+Hasselblad H3DII-50 3FR 2008 preview 3FR is opened from its embedded JPEG only
+Hasselblad H4D-40 3FR 2010 preview 3FR is opened from its embedded JPEG only
+Hasselblad H4D-60 3FR 2010 preview 3FR is opened from its embedded JPEG only
+Hasselblad H5D-40 3FR 2012 preview 3FR is opened from its embedded JPEG only
+Hasselblad H5D-50 3FR 2012 preview 3FR is opened from its embedded JPEG only
+Hasselblad H5D-50c 3FR 2013 preview 3FR is opened from its embedded JPEG only
+Hasselblad H5D-60 3FR 2013 preview 3FR is opened from its embedded JPEG only
+Hasselblad H6D-100c 3FR 2015 preview 3FR is opened from its embedded JPEG only
+Hasselblad HV ARW 2013 unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Hasselblad Lunar 3FR 2012 preview 3FR is opened from its embedded JPEG only
+Hasselblad Stellar II 3FR 2015 preview 3FR is opened from its embedded JPEG only
+Hasselblad X1D 3FR 2015 preview 3FR is opened from its embedded JPEG only
+Hasselblad X2D II 100C (**) 3FR 2024 preview 3FR is opened from its embedded JPEG only
+Hasselblad X1D II 50C 3FR 2018 preview 3FR is opened from its embedded JPEG only
+Hasselblad X2D 100C 3FR 2021 preview 3FR is opened from its embedded JPEG only
+Huawei Honor 20 Pro Rear Main Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Huawei Honor 20 Pro Rear Telephoto Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Huawei Honor 20 Pro Rear Wide Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Huawei Mate 10 Pro DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Huawei P30 Rear Main Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Huawei P30 Rear Telephoto Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Huawei P30 Rear Wide Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Huawei P30 Pro Rear Main Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Huawei P30 Pro Rear Telephoto Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Huawei P30 Pro Rear Wide Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Huawei P9 Lite DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Insta360 Ace Pro 2 DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Insta360 GO Ultra DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Kodak DCS 14n DCR 2003 unsupported DCR is not imported
+Kodak DCS 720x DCR 2003 unsupported DCR is not imported
+Kodak DCS 760 DCR 2003 unsupported DCR is not imported
+Kodak DCS Pro 14nx DCR 2003 unsupported DCR is not imported
+Kodak DCS Pro SLR/n DCR 2003 unsupported DCR is not imported
+Kodak EasyShare P712 DCR 2005 unsupported DCR is not imported
+Kodak EasyShare P850 KDC 2005 preview KDC is opened from its embedded JPEG only
+Kodak EasyShare P880 KDC 2005 preview KDC is opened from its embedded JPEG only
+Kodak EasyShare Z1015 IS KDC 2008 preview KDC is opened from its embedded JPEG only
+Kodak EasyShare Z980 KDC 2008 preview KDC is opened from its embedded JPEG only
+Kodak EasyShare Z981 RAW 2010 preview RAW is opened from its embedded JPEG only
+Kodak EasyShare Z990 RAW 2010 preview RAW is opened from its embedded JPEG only
+Konica Minolta Alpha-5 Digital (China) MRW 2005 preview MRW is opened from its embedded JPEG only
+Konica Minolta Alpha Sweet Digital (Japan) MRW 2005 preview MRW is opened from its embedded JPEG only
+Konica Minolta DiMAGE 5 MRW 2003 preview MRW is opened from its embedded JPEG only
+Konica Minolta DiMAGE 7 MRW 2003 preview MRW is opened from its embedded JPEG only
+Konica Minolta DiMAGE 7i MRW 2003 preview MRW is opened from its embedded JPEG only
+Konica Minolta DiMAGE 7Hi MRW 2003 preview MRW is opened from its embedded JPEG only
+Konica Minolta DiMAGE A1 MRW 2003 preview MRW is opened from its embedded JPEG only
+Konica Minolta DiMAGE A2 MRW 2003 preview MRW is opened from its embedded JPEG only
+Konica Minolta DiMAGE A200 MRW 2003 preview MRW is opened from its embedded JPEG only
+Konica Minolta Maxxum 5D Dynax 5D MRW 2005 preview MRW is opened from its embedded JPEG only
+Konica Minolta Maxxum 7D Dynax 7D MRW 2003 preview MRW is opened from its embedded JPEG only
+Leaf AFi II 6 MOS 2008 preview MOS is opened from its embedded JPEG only
+Leaf AFi II 7 MOS 2008 preview MOS is opened from its embedded JPEG only
+Leaf Aptus 17 MOS 2007 preview MOS is opened from its embedded JPEG only
+Leaf Aptus 22 MOS 2005 preview MOS is opened from its embedded JPEG only
+Leaf Aptus 54s MOS 2007 preview MOS is opened from its embedded JPEG only
+Leaf Aptus 65 MOS 2005 preview MOS is opened from its embedded JPEG only
+Leaf Aptus 75 MOS 2005 preview MOS is opened from its embedded JPEG only
+Leaf Aptus 75s MOS 2007 preview MOS is opened from its embedded JPEG only
+Leaf Aptus-II 5 MOS 2008 preview MOS is opened from its embedded JPEG only
+Leaf Aptus-II 6 MOS 2008 preview MOS is opened from its embedded JPEG only
+Leaf Aptus-II 7 MOS 2008 preview MOS is opened from its embedded JPEG only
+Leaf Aptus-II 8 MOS 2010 preview MOS is opened from its embedded JPEG only
+Leaf Aptus-II 10R MOS 2010 preview MOS is opened from its embedded JPEG only
+Leaf Aptus-II 12 MOS 2010 preview MOS is opened from its embedded JPEG only
+Leaf Valeo 6 MOS 2003 preview MOS is opened from its embedded JPEG only
+Leaf Valeo 11 MOS 2003 preview MOS is opened from its embedded JPEG only
+Leaf Valeo 17 MOS 2005 preview MOS is opened from its embedded JPEG only
+Leaf Valeo 22 MOS 2003 preview MOS is opened from its embedded JPEG only
+Leaf Credo 40 IIQ 2012 preview IIQ is opened from its embedded JPEG only
+Leaf Credo 50 IIQ 2013 preview IIQ is opened from its embedded JPEG only
+Leaf Credo 60 IIQ 2012 preview IIQ is opened from its embedded JPEG only
+Leaf Credo 80 IIQ 2012 preview IIQ is opened from its embedded JPEG only
+Leica C (Typ 112) RWL 2013 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica CL DNG 2017 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica C-Lux DNG 2017 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica D-Lux 2 RAW 2005 unverified Panasonic decoder, every raw format
+Leica D-Lux 3 RAW 2005 verified PR #215: raw.pixls.us bulk check of every RW2 / RWL / RAW body
+Leica D-Lux 4 RWL 2008 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica D-Lux 5 RWL 2010 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica D-Lux 6 RWL 2012 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica D-Lux 7 RWL 2018 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica D-Lux (Typ 109) RWL 2013 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica D-Lux 8 DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica D-Lux 8 "100 Years of Leica" DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica Digilux 2 RAW 2003 verified PR #215: raw.pixls.us bulk check of every RW2 / RWL / RAW body
+Leica Digilux 3 RAW 2005 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica Digital-Modul-R DNG 2003 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica Leitz Phone 3 DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica M8 DNG 2003 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica M9 DNG 2008 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica M10 DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica M10-D DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica M10-P DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica M10-R DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica M11-D DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica M (Typ 262) DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica M-D (Typ 262) DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica M-E DNG 2012 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica M-EV1 DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica M-P DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica M Monochrom DNG 2012 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica M Monochrom (Typ 246) DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica M10 Monochrom DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica M11 DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica M11 MONOCHROM DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica M11-P DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica Q (Typ 116) DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica Q2 DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica Q2 Monochrom DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica Q3 DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica Q3 43 DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica Q3 Monochrom DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica S2 DNG 2010 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica S (Typ 007) DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica S3 DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica SL (Typ 601) DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica SL2 DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica SL2-S DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica SL3 DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica SL3-P DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica SL3-S DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica T (Typ 701) DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica TL DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica TL2 DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica V-LUX 1 RAW 2005 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica V-LUX 2 RAW 2010 unverified Panasonic decoder, every raw format
+Leica V-LUX 3 RAW 2010 unverified Panasonic decoder, every raw format
+Leica V-LUX 4 RWL 2012 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica V-LUX 5 RWL 2018 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica V-LUX (Typ 114) RWL 2013 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica X2 RWL 2012 unverified Panasonic decoder, every raw format
+Leica X (Typ 113) DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Leica X-U (Typ 113) DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+LG G7 ThinQ DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+LG G8 ThinQ Front Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+LG G8 ThinQ Rear Main Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+LG G8 ThinQ Rear Wide Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+LG V40 ThinQ Front Standard Camera DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+LG V40 ThinQ Rear Main Camera DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+LG V40 ThinQ Rear Wide Camera DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+LG V50s ThinQ Front Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+LG V50s ThinQ Rear Main Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+LG V50s ThinQ Rear Wide Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+LG V60 ThinQ Front Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+LG V60 ThinQ Rear Main Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+LG Velvet Front Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+LG Velvet Rear Main Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+LG Velvet Rear Wide Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+LG Wing Rear Main Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+LG Wing Front Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+LG Wing Rear Wide Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Light L16 DNG 2017 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Mamiya Mamiya DM22 MOS 2008 preview MOS is opened from its embedded JPEG only
+Mamiya Mamiya DM28 MOS 2008 preview MOS is opened from its embedded JPEG only
+Mamiya Mamiya DM33 MOS 2008 preview MOS is opened from its embedded JPEG only
+Mamiya Mamiya DM56 MOS 2008 preview MOS is opened from its embedded JPEG only
+Mamiya Mamiya M18 TIF 2008 unsupported TIF is not imported
+Mamiya Mamiya M22 TIF 2008 unsupported TIF is not imported
+Mamiya Mamiya M31 TIF 2008 unsupported TIF is not imported
+Mamiya Mamiya DM40 MOS 2010 preview MOS is opened from its embedded JPEG only
+Mamiya ZD MEF 2005 unsupported MEF is not imported
+Motorola Moto G Power (2021) Front Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Motorola Moto G Power (2021) Rear Macro Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Motorola Moto G Power (2021) Rear Wide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Nikon 1 J1 NEF 2010 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon 1 J2 NEF 2012 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon 1 J3 NEF 2012 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon 1 J4 NEF 2013 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon 1 J5 NEF 2015 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon 1 S1 NEF 2012 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon 1 S2 NEF 2013 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon 1 V1 NEF 2010 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon 1 V2 NEF 2012 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon 1 V3 NEF 2013 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon COOLPIX 5000 NEF 2003 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon COOLPIX 5400 NEF 2003 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon COOLPIX 5700 NEF 2003 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon COOLPIX 8400 NEF 2005 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon COOLPIX 8700 NEF 2003 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon COOLPIX 8800 NEF 2005 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon COOLPIX A NEF 2012 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon COOLPIX A1000 NRW 2018 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon COOLPIX B700 NRW 2015 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Nikon COOLPIX P330 NRW 2012 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon COOLPIX P340 NRW 2013 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon COOLPIX P1000 NRW 2018 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon COOLPIX P1100 NRW 2024 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon COOLPIX P6000 NRW 2008 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon COOLPIX P7000 NRW 2010 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon COOLPIX P7100 NRW 2010 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon COOLPIX P7700 NRW 2012 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon COOLPIX P7800 NRW 2013 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon COOLPIX P950 NEF 2019 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D1 NEF 2003 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D1H NEF 2003 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D1X NEF 2003 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D2H NEF 2003 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D2Hs NEF 2005 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D2X NEF 2005 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D2Xs NEF 2005 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D3 NEF 2007 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D3s NEF 2008 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D3X NEF 2008 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D4 NEF 2012 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D4S NEF 2013 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D5 NEF 2015 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D40 NEF 2005 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D40x NEF 2007 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D50 NEF 2005 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D60 NEF 2007 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D6 NEF 2019 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D70 NEF 2005 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D70S NEF 2005 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D80 NEF 2005 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D90 NEF 2008 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D100 NEF 2003 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D200 NEF 2005 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D300 NEF 2007 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D300s NEF 2008 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D500 NEF 2015 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D600 NEF 2012 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D610 NEF 2013 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D700 NEF 2008 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D750 NEF 2013 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D780 NEF 2019 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D800 NEF 2012 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D800E NEF 2012 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D810 NEF 2013 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D810A NEF 2015 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D850 NEF 2015 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D3000 NEF 2008 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D3100 NEF 2010 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D3200 NEF 2012 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D3300 NEF 2013 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D3400 NEF 2015 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D3500 NEF 2018 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D5000 NEF 2008 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D5100 NEF 2010 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Nikon D5200 NEF 2012 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D5300 NEF 2013 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D5500 NEF 2015 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D5600 NEF 2015 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D7000 NEF 2010 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Nikon D7100 NEF 2012 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D7200 NEF 2015 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon D7500 NEF 2015 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Nikon Df NEF 2013 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon Z 30 NEF 2021 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon Z 50 NEF 2019 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon Z 50 II NEF 2024 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon Z 5 NEF 2019 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon Z 5 2 NEF 2024 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon Z 5 2C NEF 2025 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon Z 6 NEF 2018 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon Z 6 II NEF 2020 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon Z 6 III NEF 2023 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon Z 7 NEF 2018 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon Z 7 II NEF 2020 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon Z 8 NEF 2022 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon Z 9 (*See note) NEF 2021 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon Z fc NEF 2020 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nikon Z f NEF 2022 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Nikon ZR NEF 2024 unverified NEF decoder (uncompressed, lossless, lossy, lossy after split); HE / HE* setting preview-only
+Nokia Lumia 1020 DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Nokia HMD Global Nokia 9 PureView Front Camera DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Nokia HMD Global Nokia 9 PureView Rear Camera DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+OM Digital Solutions OM System OM-1 ORF 2021 preview compressed ORF is not decoded
+OM Digital Solutions OM System OM-5 ORF 2022 preview compressed ORF is not decoded
+OM Digital Solutions OM System TG - 7 ORF 2023 preview compressed ORF is not decoded
+OM Digital Solutions OM System OM-1 MARK II ORF 2023 preview compressed ORF is not decoded
+OM Digital Solutions OM System OM-3 ORF 2024 preview compressed ORF is not decoded
+OM Digital Solutions OM-5 Mark II ORF 2024 preview compressed ORF is not decoded
+OM Digital Solutions OM-3 Astro ORF 2025 preview compressed ORF is not decoded
+Olympus AIR A01 ORF 2015 preview compressed ORF is not decoded
+Olympus Camedia C-5050 Zoom ORF 2003 unverified uncompressed / packed ORF
+Olympus Camedia C-5060 Zoom ORF 2003 unverified uncompressed / packed ORF
+Olympus Camedia C-7070 Wide Zoom ORF 2005 unverified uncompressed / packed ORF
+Olympus Camedia C-8080 Wide Zoom ORF 2003 unverified uncompressed / packed ORF
+Olympus E-1 ORF 2003 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Olympus E-3 ORF 2007 preview compressed ORF is not decoded
+Olympus E-5 ORF 2010 preview compressed ORF is not decoded
+Olympus E-10 ORF 2003 unverified uncompressed / packed ORF
+Olympus E-20 ORF 2003 unverified uncompressed / packed ORF
+Olympus E-30 ORF 2008 preview compressed ORF is not decoded
+Olympus E-420 ORF 2008 preview compressed ORF is not decoded
+Olympus E-450 ORF 2008 preview compressed ORF is not decoded
+Olympus E-520 ORF 2008 preview compressed ORF is not decoded
+Olympus E-600 ORF 2010 preview compressed ORF is not decoded
+Olympus E-620 ORF 2008 preview compressed ORF is not decoded
+Olympus EVOLT E-300 ORF 2005 unverified uncompressed / packed ORF
+Olympus EVOLT E-330 ORF 2005 unverified uncompressed / packed ORF
+Olympus EVOLT E-400 ORF 2005 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Olympus EVOLT E-410 ORF 2007 preview compressed ORF is not decoded
+Olympus EVOLT E-500 ORF 2005 unverified uncompressed / packed ORF
+Olympus EVOLT E-510 ORF 2007 preview compressed ORF is not decoded
+Olympus OM-D E-M1 ORF 2013 preview compressed ORF is not decoded
+Olympus OM-D E-M1X ORF 2018 preview compressed ORF is not decoded
+Olympus OM-D E-M1 Mark II ORF 2015 preview compressed ORF is not decoded
+Olympus OM-D E-M1 Mark III ORF 2019 preview compressed ORF is not decoded
+Olympus OM-D E-M5 ORF 2012 preview compressed ORF is not decoded
+Olympus OM-D E-M5 Mark II ORF 2015 preview compressed ORF is not decoded
+Olympus OM-D E-M5 Mark III ORF 2019 preview compressed ORF is not decoded
+Olympus OM-D E-M10 ORF 2013 preview compressed ORF is not decoded
+Olympus OM-D E-M10 Mark II ORF 2015 preview compressed ORF is not decoded
+Olympus OM-D E-M10 Mark III ORF 2017 preview compressed ORF is not decoded
+Olympus OM-D E-M10 Mark III S ORF 2019 preview compressed ORF is not decoded
+Olympus OM-D E-M10 Mark IV ORF 2019 preview compressed ORF is not decoded
+Olympus PEN E-P1 ORF 2008 preview compressed ORF is not decoded
+Olympus PEN E-P2 ORF 2008 preview compressed ORF is not decoded
+Olympus PEN E-P3 ORF 2010 preview compressed ORF is not decoded
+Olympus PEN E-P5 ORF 2013 preview compressed ORF is not decoded
+Olympus PEN E-P7 ORF 2020 preview compressed ORF is not decoded
+Olympus PEN E-PL1 ORF 2010 preview compressed ORF is not decoded
+Olympus PEN E-PL1s ORF 2010 preview compressed ORF is not decoded
+Olympus PEN E-PL10 ORF 2019 preview compressed ORF is not decoded
+Olympus PEN E-PL2 ORF 2010 preview compressed ORF is not decoded
+Olympus PEN E-PL3 ORF 2010 preview compressed ORF is not decoded
+Olympus PEN E-PL6 ORF 2013 preview compressed ORF is not decoded
+Olympus PEN E-PL5 ORF 2012 preview compressed ORF is not decoded
+Olympus PEN E-PL7 ORF 2013 preview compressed ORF is not decoded
+Olympus PEN E-PL8 ORF 2015 preview compressed ORF is not decoded
+Olympus PEN E-PL9 ORF 2017 preview compressed ORF is not decoded
+Olympus PEN E-PM1 ORF 2010 preview compressed ORF is not decoded
+Olympus PEN E-PM2 ORF 2012 preview compressed ORF is not decoded
+Olympus PEN-F ORF 2015 preview compressed ORF is not decoded
+Olympus SP-310 ORF 2005 preview compressed ORF is not decoded
+Olympus SP-320 ORF 2005 preview compressed ORF is not decoded
+Olympus SP-350 ORF 2005 preview compressed ORF is not decoded
+Olympus SP-500 UZ ORF 2005 preview compressed ORF is not decoded
+Olympus SP-510 UZ ORF 2005 preview compressed ORF is not decoded
+Olympus SP-550 UZ ORF 2007 preview compressed ORF is not decoded
+Olympus SP-560 UZ ORF 2007 preview compressed ORF is not decoded
+Olympus SP-565 UZ ORF 2008 preview compressed ORF is not decoded
+Olympus SP-570 UZ ORF 2007 preview compressed ORF is not decoded
+Olympus Stylus 1 ORF 2013 preview compressed ORF is not decoded
+Olympus Stylus 1s ORF 2013 preview compressed ORF is not decoded
+Olympus Stylus SH-2 ORF 2015 preview compressed ORF is not decoded
+Olympus Stylus SH-3 ORF 2015 preview compressed ORF is not decoded
+Olympus Stylus Tough TG-4 ORF 2015 preview compressed ORF is not decoded
+Olympus Stylus Tough TG-5 ORF 2015 preview compressed ORF is not decoded
+Olympus Stylus Tough TG-6 ORF 2018 preview compressed ORF is not decoded
+Olympus Stylus XZ-2 iHS ORF 2012 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Olympus Stylus XZ-10 ORF 2012 preview compressed ORF is not decoded
+Olympus XZ-1 ORF 2010 preview compressed ORF is not decoded
+OnePlus 7T Front Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+OnePlus 7T Rear Main Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+OnePlus 8 Front Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+OnePlus 8 Rear Main Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+OnePlus 8 Pro Front Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+OnePlus 8 Pro Rear Main Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+OnePlus 12 Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+OnePlus 12 Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+OnePlus 12 Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X7 Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X7 Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X7 Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X7 Ultra Super Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X7 Ultra Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X7 Ultra Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X7 Ultra Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X8 Ultra Super Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X8 Ultra Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X8 Ultra Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X8 Ultra Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X8 Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X8 Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X8 Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X8s Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X8s Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X8s Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X8s+ Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X8s+ Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X8s+ Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X8 Pro Super Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X8 Pro Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X8 Pro Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X8 Pro Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X9 Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X9 Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X9 Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X9 Pro Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X9 Pro Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X9 Pro Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X9s Pro Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X9s Pro Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X9s Pro Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X9 Ultra Super Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X9 Ultra Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X9 Ultra Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Oppo Find X9 Ultra Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Panasonic LUMIX AG-GH4 RW2 2013 unverified Panasonic decoder, every raw format
+Panasonic LUMIX DC-FZ80 (DC-FZ82, DC-FZ85) RW2 2015 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-FZ80D (DC-FZ82D, DC-FZ85D) RW2 2023 unverified Panasonic decoder, every raw format
+Panasonic LUMIX DC-FZ81 (DC-FZ83) RW2 2015 unverified Panasonic decoder, every raw format
+Panasonic LUMIX DC-FZ1000M2 (DC-FZ10002) RW2 2018 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-G100 (DC-G110) RW2 2019 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-G9 RW2 2017 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-G99 (DC-G90, DC-G91, DC-G95) RW2 2018 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-GF9 (DC-GX850, DC-GX800) RW2 2015 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-GF10 (DC-GF90) RW2 2017 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-GH5 RW2 2015 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-GH5M2 RW2 2020 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-GH5s RW2 2017 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-GH6 RW2 2021 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-GH7 RW2 2023 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-GX880 RW2 2018 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-LX100 II RW2 2018 verified PR #215: raw.pixls.us bulk check of every RW2 / RWL / RAW body
+Panasonic LUMIX DC-S1 RW2 2018 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-S1H RW2 2018 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-S1R RW2 2018 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-S5 RW2 2020 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-S9 RW2 2023 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-ZS70 (DC-TZ90, DC-TZ91, DC-TZ92, DC-TZ93) RW2 2015 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-ZS80 (DC-TZ95, DC-TZ96, DC-TZ97) RW2 2018 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-CM1 RW2 2013 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-CM10 RW2 2015 unverified Panasonic decoder, every raw format
+Panasonic LUMIX DMC-FZ1000 RW2 2013 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FX150 RW2 2008 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ8 RAW 2007 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ18 RAW 2007 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ28 RW2 2008 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ30 RAW 2005 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ35 RW2 2008 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ38 RW2 2008 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ40 (DMC-FZ45) RW2 2010 verified PR #215: raw.pixls.us bulk check of every RW2 / RWL / RAW body
+Panasonic LUMIX DMC-FZ50 RAW 2005 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ70 RW2 2013 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ72 RW2 2013 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ100 RW2 2010 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ150 RW2 2010 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ200 RW2 2012 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ2500 (DMC-FZ2000, DMC-FZH1) RW2 2015 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ300 RW2 2015 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ330 RW2 2015 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-G1 RW2 2008 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-G2 RW2 2010 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-G3 RW2 2010 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-G5 RW2 2012 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-G6 RW2 2013 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-G7 RW2 2015 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-G8 (DMC-G80, DMC-G81, DMC-G85) RW2 2015 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-G10 RW2 2010 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GF1 RW2 2008 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GF2 RW2 2010 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GF3 RW2 2010 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GF5 RW2 2012 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GF6 RW2 2012 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GF7 RW2 2015 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GF8 RW2 2015 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GH1 RW2 2008 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GH2 RW2 2010 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GH3 RW2 2012 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GH4 RW2 2013 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GM1 RW2 2013 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GM1S RW2 2013 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GM5 RW2 2013 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GX1 RW2 2010 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GX7 RW2 2013 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GX8 RW2 2015 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GX85 (DMC-GX80, DMC-GX7MK2) RW2 2015 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-GX9 (DC-GX7MK3) RW2 2017 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-L1 RAW 2005 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-L10 RAW 2007 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-LC1 RAW 2003 unverified Panasonic decoder, every raw format
+Panasonic LUMIX DMC-LF1 RW2 2013 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-LX1 RAW 2005 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-LX2 RAW 2005 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-LX3 RW2 2008 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-LX5 RW2 2010 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-LX7 RW2 2012 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-LX9 (DMC-LX10, DMC-LX15) RW2 2015 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-LX100 RW2 2013 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-TZ82 RW2 2015 unverified Panasonic decoder, every raw format
+Panasonic LUMIX DMC-ZS40 (DMC-TZ60, DMC-TZ61) RW2 2013 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-ZS50 (DMC-TZ70, DMC-TZ71) RW2 2015 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-ZS60 (DMC-TZ80, DMC-TZ81, DMC-TZ85) RW2 2015 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-ZS100 (DMC-ZS110, DMC-TZ100, DMC-TZ101, DMC-TZ110, DMC-TX1) RW2 2015 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX ZS200 (DC-TX2, DC-TZ200, DC-TZ202, DC-TZ220, DC-ZS220) RW2 2017 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-G95D RW2 2021 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-G99D RW2 2021 unverified Panasonic decoder, every raw format
+Panasonic LUMIX DC-TX2D (DC-TZ200D, DC-TZ202D, DC-TZ220D, DC-ZS200D, DC-ZS220D) RW2 2022 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-ZS80D (DC-TZ95D, DC-TZ96D) RW2 2022 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-S5M2 RW2 2022 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-S5M2X RW2 2022 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DCG9M2 RW2 2023 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-G100D RW2 2023 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-S5D RW2 2024 unverified Panasonic decoder, every raw format
+Panasonic LUMIX DC - G97 (DC-G99M2) RW2 2024 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC - TZ99 (DC - ZS99) RW2 2024 unverified Panasonic decoder, every raw format
+Panasonic LUMIX DC-S1RM2 (Final Support) RW2 2024 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-S1M2ES (Final Support) RW2 2024 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-S1M2 RW2 2024 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-ZS300 (DC-TZ300, DC-TX3) RW2 2025 unverified Panasonic decoder, every raw format
+Panasonic LUMIX DC-L10 RW2 2025 unverified Panasonic decoder, every raw format
+Parrot Anafi DNG 2017 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Parrot ANAFI Ai DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax 645D DNG 2010 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax 645Z DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax *ist D PEF 2003 unverified PEF decoder (uncompressed, Huffman, packed 12-bit)
+Pentax *ist DL PEF 2005 unverified PEF decoder (uncompressed, Huffman, packed 12-bit)
+Pentax *ist DL2 PEF 2005 unverified PEF decoder (uncompressed, Huffman, packed 12-bit)
+Pentax *ist DS PEF 2005 unverified PEF decoder (uncompressed, Huffman, packed 12-bit)
+Pentax *ist DS2 PEF 2005 unverified PEF decoder (uncompressed, Huffman, packed 12-bit)
+Pentax K-01 DNG 2012 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax K-1 DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax K-1 II (* see note) DNG 2017 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax K-3 DNG 2013 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Pentax K-3 II († see note) DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax K-5 DNG 2010 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax K-5 II DNG 2012 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax K-5 IIs DNG 2012 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Pentax K-7 PEF 2008 unverified PEF decoder (uncompressed, Huffman, packed 12-bit)
+Pentax K-3 Mark III DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax K-3 Mark III Monochrome DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax K-30 DNG 2012 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax K-50 DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax K-70 PEF 2015 unverified PEF decoder (uncompressed, Huffman, packed 12-bit)
+Pentax K-500 DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax KP DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax K-r PEF 2010 unverified PEF decoder (uncompressed, Huffman, packed 12-bit)
+Pentax K-S1 DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax K-S2 DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax K-x PEF 2008 unverified PEF decoder (uncompressed, Huffman, packed 12-bit)
+Pentax K10D DNG 2005 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Pentax K20D DNG 2007 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax K100D PEF 2005 unverified PEF decoder (uncompressed, Huffman, packed 12-bit)
+Pentax K100D Super PEF 2007 unverified PEF decoder (uncompressed, Huffman, packed 12-bit)
+Pentax K110D PEF 2005 unverified PEF decoder (uncompressed, Huffman, packed 12-bit)
+Pentax K200D DNG 2007 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax K2000 K-m PEF 2008 unverified PEF decoder (uncompressed, Huffman, packed 12-bit)
+Pentax KF PEF 2022 unverified PEF decoder (uncompressed, Huffman, packed 12-bit)
+Pentax MX-1 DNG 2012 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax Q DNG 2010 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax Q7 DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax Q10 DNG 2012 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pentax QS-1 DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Pixii PIXII Camera (A1571) DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Phase One H 20 TIF 2007 unsupported TIF is not imported
+Phase One H 25 TIF 2007 unsupported TIF is not imported
+Phase One IQ 140 IIQ 2010 preview IIQ is opened from its embedded JPEG only
+Phase One IQ 160 IIQ 2010 preview IIQ is opened from its embedded JPEG only
+Phase One IQ 150 IIQ 2015 preview IIQ is opened from its embedded JPEG only
+Phase One IQ 180 IIQ 2010 preview IIQ is opened from its embedded JPEG only
+Phase One IQ 250 IIQ 2013 preview IIQ is opened from its embedded JPEG only
+Phase One IQ 260 IIQ 2013 preview IIQ is opened from its embedded JPEG only
+Phase One IQ 280 IIQ 2013 preview IIQ is opened from its embedded JPEG only
+Phase One IQ3 50MP IIQ 2015 preview IIQ is opened from its embedded JPEG only
+Phase One IQ3 60MP IIQ 2015 preview IIQ is opened from its embedded JPEG only
+Phase One IQ3 80MP IIQ 2015 preview IIQ is opened from its embedded JPEG only
+Phase One IQ3 100MP IIQ 2015 preview IIQ is opened from its embedded JPEG only
+Phase One IQ3 100MP Trichromatic (*see note) IIQ 2017 preview IIQ is opened from its embedded JPEG only
+Phase One IQ4 150MP (**see note) IIQ 2019 preview IIQ is opened from its embedded JPEG only
+Phase One IQ4 150MP Achromatic (** see note) IIQ 2020 preview IIQ is opened from its embedded JPEG only
+Phase One P 20 TIF 2007 unsupported TIF is not imported
+Phase One P 20+ TIF 2007 unsupported TIF is not imported
+Phase One P 21 TIF 2007 unsupported TIF is not imported
+Phase One P 21+ TIF 2007 unsupported TIF is not imported
+Phase One P 25 TIF 2007 unsupported TIF is not imported
+Phase One P 25+ TIF 2007 unsupported TIF is not imported
+Phase One P 30 TIF 2007 unsupported TIF is not imported
+Phase One P 30+ TIF 2007 unsupported TIF is not imported
+Phase One P 40+ IIQ 2010 preview IIQ is opened from its embedded JPEG only
+Phase One P 45 TIF 2007 unsupported TIF is not imported
+Phase One P 45+ TIF 2007 unsupported TIF is not imported
+Phase One P 65+ IIQ 2010 preview IIQ is opened from its embedded JPEG only
+Ricoh GR DNG 2013 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Ricoh GR II DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Ricoh GR III DNG 2018 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Ricoh GR IIIx DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Ricoh GR III HDF DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Ricoh GR IIIx HDF DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Ricoh GR IV DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Ricoh GR IV Monochrome DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Ricoh GR Digital DNG 2003 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Ricoh GR Digital II DNG 2003 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Ricoh GR Digital III DNG 2008 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Ricoh GR Digital IV DNG 2010 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Ricoh GX100 DNG 2003 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Ricoh GX200 DNG 2003 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Ricoh GXR with GR LENS A12 28mm F2.5 DNG 2010 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Ricoh GXR with GR LENS A12 50mm F2.5 MACRO DNG 2008 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Ricoh GXR with GR LENS A16 24-85 F3.5-5.5 DNG 2010 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Ricoh GXR with GR LENS S10 24-72mm F2.5-4.4 VC DNG 2008 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Ricoh GXR with GR LENS Mount A12 DNG 2010 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Ricoh THETA Z1 DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung EK-GN100 SRW 2012 preview compressed SRW is not decoded
+Samsung EK-GN110 SRW 2012 preview compressed SRW is not decoded
+Samsung EK-GN120 SRW 2012 preview compressed SRW is not decoded
+Samsung EK-KN120 SRW 2012 preview compressed SRW is not decoded
+Samsung EX2F SRW 2012 unverified uncompressed / packed SRW
+Samsung NX300 SRW 2012 preview compressed SRW is not decoded
+Samsung NX500 SRW 2015 preview compressed SRW is not decoded
+Samsung NX1000 SRW 2012 unverified uncompressed / packed SRW
+Samsung NX1100 SRW 2012 unverified uncompressed / packed SRW
+Samsung NX2000 SRW 2012 preview compressed SRW is not decoded
+Samsung NX3000 SRW 2013 preview compressed SRW is not decoded
+Samsung NX3300 SRW 2015 preview compressed SRW is not decoded
+Samsung NX U SRW 2012 unverified uncompressed / packed SRW
+Samsung Pro 815 DNG 2003 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung TL350 (WB2000) SRW 2010 unverified uncompressed / packed SRW
+Samsung TL500 (EX1) SRW 2010 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Samsung Galaxy Note 9 DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Note 20 Rear Main Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Note 20 Ultra Rear Main Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S20 Front Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S20 Rear Main Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S20+ Front Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S20+ Rear Main Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S20 Ultra Front Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S20 Ultra Rear Main Camera DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S21 Rear Main Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S21 Rear Wide Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S21+ Rear Main Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S21+ Rear Wide Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S21 Ultra Rear Main Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S21 Ultra Rear Wide Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S21 Ultra Super Telephoto Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S21 Ultra Telephoto Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S6 DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S6 Edge DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S7 DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S7 Edge DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S8 DNG 2017 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S8+ DNG 2017 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S9 DNG 2017 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S9+ DNG 2017 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold3 Rear Main Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold3 Rear Telephoto Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold3 Rear Ultrawide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S22 Front Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S22 Rear Telephoto Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S22 Rear Ultrawide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S22 Rear Wide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S22+ Front Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S22+ Rear Telephoto Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S22+ Rear Ultrawide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S22+ Rear Wide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S22 Ultra Front Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S22 Ultra Rear Super Telephoto Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S22 Ultra Rear Telephoto Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S22 Ultra Rear Ultrawide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S22 Ultra Rear Wide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S23 Front Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S23 Rear Telephoto Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S23 Rear Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S23 Rear Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S23+ Front Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S23+ Rear Telephoto Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S23+ Rear Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S23+ Rear Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S23 Ultra Front Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S23 Ultra Rear Super Telephoto Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S23 Ultra Rear Telephoto Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S23 Ultra Rear Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S23 Ultra Rear Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S24 Front Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S24 Rear Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S24 Rear Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S24 Rear Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S24+ Front Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S24+ Rear Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S24+ Rear Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S24+ Rear Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S24 Ultra Front Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S24 Ultra Rear Super Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S24 Ultra Rear Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S24 Ultra Rear Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S24 Ultra Rear Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S25 Front Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S25 Rear Telephoto Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S25 Rear Ultrawide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S25 Rear Wide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S25+ Front Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S25+ Rear Telephoto Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S25+ Rear Ultrawide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S25+ Rear Wide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S25 Ultra Front Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S25 Ultra Rear Super Telephoto Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S25 Ultra Rear Telephoto Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S25 Ultra Rear Ultrawide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy S25 Ultra Rear Wide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold4 Rear Telephoto Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold4 Rear Ultrawide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold4 Rear Wide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold5 Front Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold5 Telephoto Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold5 Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold5 Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold6 Front Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold6 Rear Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold6 Rear Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold6 Rear Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold Special Edition Front Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold Special Edition Rear Telephoto Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold Special Edition Rear Ultrawide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold Special Edition Rear Wide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Flip6 Rear Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Flip6 Rear Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Flip7 FE Front Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Flip7 FE Rear Ultrawide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Flip7 FE Wide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Flip7 Front Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Flip7 Rear Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Flip7 Rear Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold7 Front Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold7 Rear Telephoto Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold7 Rear Ultrawide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold7 Under-Display Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Fold7 Rear Wide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Trifold Front Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Trifold Rear Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Trifold Rear Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Samsung Galaxy Z Trifold Rear Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sigma BF DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sigma DP1 X3F 2007 preview X3F is opened from its embedded JPEG only
+Sigma DP1s X3F 2008 preview X3F is opened from its embedded JPEG only
+Sigma DP2 X3F 2008 preview X3F is opened from its embedded JPEG only
+Sigma fp DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sigma fp L DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sigma SD9 X3F 2003 preview X3F is opened from its embedded JPEG only
+Sigma SD10 X3F 2003 preview X3F is opened from its embedded JPEG only
+Sigma SD14 X3F 2007 preview X3F is opened from its embedded JPEG only
+Skydio Skydio 2 DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 5 IV Telephoto Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 5 IV Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 5 IV Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 V Front Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 V Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 V Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 V Zoom Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony A1 II (ILCE-1M2) (** see note) ARW 2024 unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Sony A7 (ILCE-7) ARW 2013 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7 II (ILCE-A7M2) ARW 2013 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies
+Sony A7 III (ILCE-7M3) ARW 2017 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7 IV (ILCE-7M4) ARW 2021 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7 V (ILCE-7M5) (** see note) ARW 2025 unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Sony A7C (ILCE-7C) ARW 2020 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7C II (ILCE-7CM2) ARW 2023 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7CR (ILCE-7CR) ARW 2023 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7R (ILCE-7R) ARW 2013 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7R II (ILCE-7RM2) ARW 2015 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7R III (ILCE-7RM3) ARW 2017 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7R IV (ILCE-7RM4) ARW 2018 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7S (ILCE-7S) ARW 2013 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7S II (ILCE-7SM2) ARW 2015 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7s III (ILCE-7SM3) ARW 2020 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A9 (ILCE-9) ARW 2015 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A9 II (ILCE-9M2) ARW 2019 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A9 III (ILCE-9M3) ARW 2023 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A33 (SLT-A33) ARW 2010 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A35 (SLT-A35) ARW 2010 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A37 (SLT-A37) ARW 2012 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A55V (SLT-A55V) ARW 2010 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies
+Sony A57 (SLT-A57) ARW 2012 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A58 (SLT-A58) ARW 2012 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A65 (SLT-A65) ARW 2010 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A68 (ILCA-68) ARW 2015 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A77 (SLT-A77) ARW 2010 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A77 II (ILCA-77M2) ARW 2013 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7R IIIA (ILCE-7RM3A) ARW 2020 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7R IVA (ILCE-7RM4A) ARW 2020 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7R V (ILCE-7RM5) ARW 2022 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7R VI (ILCE-7RM6) (** see note) ARW 2025 unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Sony A99 (SLT-A99V) ARW 2012 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies
+Sony A99 II (ILCA-99M2) ARW 2015 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A100 (DSLR-A100) ARW 2005 unsupported issue #702: regression, refused as not a readable photo
+Sony A200 (DSLR-A200) ARW 2007 preview issue #535: preview-only on the raw.pixls.us sample
+Sony A230 (DSLR-A230) ARW 2008 preview issue #535: preview-only on the raw.pixls.us sample
+Sony A290 (DSLR-A290) ARW 2010 preview issue #535: preview-only on the raw.pixls.us sample
+Sony A300 (DSLR-A300) ARW 2007 preview issue #535: preview-only on the raw.pixls.us sample
+Sony A330 (DSLR A330) ARW 2008 preview issue #535: preview-only on the raw.pixls.us sample
+Sony A350 (DSLR-A350) ARW 2007 preview issue #535: preview-only on the raw.pixls.us sample
+Sony A380 (DSLR-A380) ARW 2008 preview issue #535: preview-only on the raw.pixls.us sample
+Sony A390 (DSLR-A390) ARW 2010 preview issue #535: preview-only on the raw.pixls.us sample
+Sony A450 (DSLR-A450) ARW 2010 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A500 (DSLR-A500) ARW 2008 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A550 (DSLR-A550) ARW 2008 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A560 (DSLR-A560) ARW 2010 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A580 (DSLR-A580) ARW 2010 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A700 (DSLR-A700) ARW 2007 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A850 (DSLR-A850) ARW 2008 preview issue #535: preview-only on the raw.pixls.us sample
+Sony A900 (DSLR-A900) ARW 2008 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A3000 (ILCE-3000) ARW 2013 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A3500 (ILCE-3500) ARW 2013 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A5000 (ILCE-5000) ARW 2013 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A5100 (ILCE-5100) ARW 2013 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A6000 (ILCE-6000) ARW 2013 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A6100 (ILCE-6100) ARW 2019 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A6300 (ILCE-6300) ARW 2015 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A6400 (ILCE-6400) ARW 2018 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A6500 (ILCE-6500) ARW 2015 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A6600 (ILCE-6600) ARW 2019 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A6700 (ILCE-6700) ARW 2022 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony DSC-F828 SRF 2003 unsupported issue #702 (SRF refused); SRF not in the import list
+Sony DSC-HX95 ARW 2018 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony DSC-HX99 ARW 2018 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony DSC-V3 SRF 2005 unsupported SRF not in the import list
+Sony DSC-RX100M7A ARW 2024 unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Sony FX3 (ILME-FX3) ARW 2020 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony FX30 (ILME-FX30) ARW 2022 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-3 ARW 2010 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-3N ARW 2012 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-5 ARW 2010 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-5N ARW 2010 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-5R ARW 2012 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-5T ARW 2013 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-6 ARW 2012 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-7 ARW 2010 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-C3 ARW 2010 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-F3 ARW 2012 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-VG20 ARW 2012 unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Sony NEX-VG30 ARW 2012 unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Sony NEX-VG900 ARW 2012 unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Sony RX0 (DSC-RX0) ARW 2017 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX0 II (DSC-RX0M2) ARW 2018 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX1 (DSC-RX1) ARW 2012 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX1R (DSC-RX1R) ARW 2013 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX1R II (DSC-RX1RM2) ARW 2015 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX10 (DSC-RX10) ARW 2013 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX10 II (DSC-RX10M2) ARW 2015 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX10 III (DSC-RX10M3) ARW 2015 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX10 IV (DSC-RX10M4) ARW 2017 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX10 V (DSC-RX10M5) (** see note) ARW 2025 unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Sony RX100 (DSC-RX100) ARW 2012 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX100 II (DSC-RX100M2) ARW 2013 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX100 III (DSC-RX100M3) ARW 2013 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX100 IV (DSC-RX100M4) ARW 2015 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX100 V (DSC-RX100M5) ARW 2015 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX100 VA (DSC-RX100M5A) ARW 2017 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX100 VI (DSC-RX100M6) ARW 2017 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX100 VII (DSC-RX100M7) ARW 2018 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony QX1 (ILCE-QX1) ARW 2013 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony ZV-1 (DCZV1/B) ARW 2019 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony Alpha 1 (ILCE-1)* ARW 2020 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony Xperia 1 Wide-angle Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 II Telephoto Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 II Ultra wide-angle Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 II Wide-angle Camera DNG 2020 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 IV Wide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 IV Zoom Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 IV Ultrawide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 VI Front Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 VI Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 VI Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 VI Zoom Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 VII Front Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 VII Ultrawide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 VII Wide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 VII Zoom Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 III Telephoto Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 III Ultra wide-angle Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 1 III Wide-angle Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 5 III Super Telephoto Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 5 III Telephoto Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 5 III Ultra wide-angle Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 5 III Wide-angle Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 5 V Front Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 5 V Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia 5 V Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony Xperia Pro-I Rear Wide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Sony ZV-E10 ARW 2021 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony ZV-E1 ARW 2022 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony ZV-E10M2 ARW unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Sony ZV-1M2 ARW 2022 unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Sony ZV-1A ARW 2024 unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Sony ILX-LR1 ARW 2023 unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Sony ILCE - 1M2 (Final Support) ARW 2024 unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Sony ILCE - 6100A ARW 2024 unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Sony ILCE - 6400A ARW 2024 unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Sony FX2 (ILMEFX2) (** see note) ARW 2024 unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Sony FX3A (ILMEFX3A) ARW 2024 unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Sony RX1R III (DSC-RX1RM3) ARW 2024 unverified ARW decoder (uncompressed, cRAW, packed 12-bit, lossless, downsized YCbCr)
+Xiaomi Mi 11 Lite Front Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Mi 11 Lite Rear Ultrawide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Mi 11 Lite Rear Wide Camera DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 12S DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 12S Pro DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 12S Ultra DNG 2021 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 13 Rear Telephoto Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 13 Rear Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 13 Rear Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 13 Pro Rear Telephoto Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 13 Pro Rear Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 13 Pro Rear Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 13 Ultra Rear Super Telephoto Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 13 Ultra Rear Telephoto Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 13 Ultra Rear Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 13 Ultra Rear Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 13T Front Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 13T Rear Telephoto Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 13T Rear Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 13T Rear Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 13T Pro Front Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 13T Pro Rear Telephoto Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 13T Pro Rear Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 13T Pro Rear Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 14 Rear Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 14 Rear Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 14 Rear Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 14 Pro Rear Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 14 Pro Rear Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 14 Pro Rear Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 14 Ultra Rear Super Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 14 Ultra Rear Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 14 Ultra Rear Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 14 Ultra Rear Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 14T Front Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 14T Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 14T Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 14T Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 14T Pro Front Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 14T Pro Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 14T Pro Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 14T Pro Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi MIX Fold 3 Rear Telephoto Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi MIX Fold 3 Rear Super Telephoto Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi MIX Fold 3 Rear Ultrawide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi MIX Fold 3 Rear Wide Camera DNG 2022 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi Civi 4 Pro Rear Telephoto Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi Civi 4 Pro Rear Ultrawide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi Civi 4 Pro Rear Wide Camera DNG 2023 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 15 Rear Telephoto Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 15 Rear Ultrawide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 15 Rear Wide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 15 Pro Rear Telephoto Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 15 Pro Rear Ultrawide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 15 Pro Rear Wide Camera DNG 2024 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 17 Ultra Rear Telephoto Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 17 Ultra Rear Ultrawide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Xiaomi Xiaomi 17 Ultra Rear Wide Camera DNG 2025 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Yuneec CGO4 DNG 2015 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Yuneec ION L1 Pro DNG 2018 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
+Zeiss ZX1 DNG 2019 unverified DNG decoder (uncompressed, LJ92, lossy, Deflate, JPEG XL, LinearRaw)
diff --git a/docs/raw/evidence.tsv b/docs/raw/evidence.tsv
new file mode 100644
index 000000000..9b7460544
--- /dev/null
+++ b/docs/raw/evidence.tsv
@@ -0,0 +1,249 @@
+# Per-model evidence that overrides the format rules in coverage.py. status: verified | preview | unsupported.
+# "verified" means a real file from this body decoded from sensor data in a corpus test or a recorded bulk check (source column).
+maker camera status source
+Apple iPhone 12 Pro Rear Wide Camera verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Canon EOS 40D verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Canon EOS 550D (EOS Digital Rebel T2i, EOS Kiss X4 Digital) verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Canon EOS 5D Mark II verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Canon EOS 5DS R verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Canon EOS 6D verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Canon EOS 7D verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Canon EOS 80D verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Canon EOS M50 (EOS Kiss M) verified corpus: crates/raw/tests/cr3_corpus.rs (exact sensor hashes, docs/cr3.md)
+Canon EOS R100 verified corpus: crates/raw/tests/cr3_corpus.rs (exact sensor hashes, docs/cr3.md)
+Canon EOS R8 verified corpus: crates/raw/tests/cr3_corpus.rs (exact sensor hashes, docs/cr3.md)
+Canon EOS-5D Mark III verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm GFX 100 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm GFX 100RF (Final Support) verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm GFX 100S verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm GFX 50S verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-A2 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-A5 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-E1 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-E5 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-H2 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-H2S verified docs/raf-compression.md (supplied files, exact sensor arrays)
+Fujifilm X-M5 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-T2 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-T20 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-T4** verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-T5 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Fujifilm X-T50 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Google Pixel 2 XL verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Leica C (Typ 112) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica D-Lux (Typ 109) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica D-Lux 3 verified PR #215: raw.pixls.us bulk check of every RW2 / RWL / RAW body
+Leica D-Lux 4 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica D-Lux 5 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica D-Lux 6 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica D-Lux 7 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica Digilux 2 verified PR #215: raw.pixls.us bulk check of every RW2 / RWL / RAW body
+Leica Digilux 3 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica V-LUX (Typ 114) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica V-LUX 1 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica V-LUX 4 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Leica V-LUX 5 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Nikon COOLPIX B700 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Nikon D5100 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Nikon D7000 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Nikon D7500 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Nikon Z f verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Olympus E-1 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Olympus EVOLT E-400 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Olympus Stylus XZ-2 iHS verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Panasonic LUMIX DC - G97 (DC-G99M2) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-FZ1000M2 (DC-FZ10002) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-FZ80 (DC-FZ82, DC-FZ85) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-G100 (DC-G110) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-G100D verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-G9 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-G95D verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-G99 (DC-G90, DC-G91, DC-G95) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-GF10 (DC-GF90) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-GF9 (DC-GX850, DC-GX800) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-GH5 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-GH5M2 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-GH5s verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-GH6 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-GH7 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-GX880 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-GX9 (DC-GX7MK3) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-LX100 II verified PR #215: raw.pixls.us bulk check of every RW2 / RWL / RAW body
+Panasonic LUMIX DC-S1 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-S1H verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-S1M2 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-S1M2ES (Final Support) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-S1R verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-S1RM2 (Final Support) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-S5 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-S5M2 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-S5M2X verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-S9 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-TX2D (DC-TZ200D, DC-TZ202D, DC-TZ220D, DC-ZS200D, DC-ZS220D) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-ZS70 (DC-TZ90, DC-TZ91, DC-TZ92, DC-TZ93) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-ZS80 (DC-TZ95, DC-TZ96, DC-TZ97) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DC-ZS80D (DC-TZ95D, DC-TZ96D) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DCG9M2 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-CM1 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FX150 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ100 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ1000 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ150 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ18 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ200 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ2500 (DMC-FZ2000, DMC-FZH1) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ28 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ30 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ300 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ330 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ35 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ38 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ40 (DMC-FZ45) verified PR #215: raw.pixls.us bulk check of every RW2 / RWL / RAW body
+Panasonic LUMIX DMC-FZ50 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ70 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ72 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-FZ8 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-G1 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-G10 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-G2 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-G3 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-G5 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-G6 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-G7 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-G8 (DMC-G80, DMC-G81, DMC-G85) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GF1 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GF2 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GF3 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GF5 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GF6 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GF7 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GF8 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GH1 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GH2 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GH3 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GH4 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GM1 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GM1S verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GM5 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GX1 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GX7 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GX8 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-GX85 (DMC-GX80, DMC-GX7MK2) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-L1 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-L10 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-LF1 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-LX1 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-LX100 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-LX2 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-LX3 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-LX5 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-LX7 verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-LX9 (DMC-LX10, DMC-LX15) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-ZS100 (DMC-ZS110, DMC-TZ100, DMC-TZ101, DMC-TZ110, DMC-TX1) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-ZS40 (DMC-TZ60, DMC-TZ61) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-ZS50 (DMC-TZ70, DMC-TZ71) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX DMC-ZS60 (DMC-TZ80, DMC-TZ81, DMC-TZ85) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Panasonic LUMIX ZS200 (DC-TX2, DC-TZ200, DC-TZ202, DC-TZ220, DC-ZS220) verified PR #215: 178 raw.pixls.us files from 118 Panasonic / Leica bodies, every RW2 format
+Pentax K-3 verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Pentax K-5 IIs verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Pentax K10D verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Ricoh GR III verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Samsung TL500 (EX1) verified corpus: crates/raw/tests/corpus.rs (xtask RAW_SAMPLES)
+Sony A100 (DSLR-A100) unsupported issue #702: regression, refused as not a readable photo
+Sony A200 (DSLR-A200) preview issue #535: preview-only on the raw.pixls.us sample
+Sony A230 (DSLR-A230) preview issue #535: preview-only on the raw.pixls.us sample
+Sony A290 (DSLR-A290) preview issue #535: preview-only on the raw.pixls.us sample
+Sony A300 (DSLR-A300) preview issue #535: preview-only on the raw.pixls.us sample
+Sony A3000 (ILCE-3000) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A33 (SLT-A33) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A330 (DSLR A330) preview issue #535: preview-only on the raw.pixls.us sample
+Sony A35 (SLT-A35) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A350 (DSLR-A350) preview issue #535: preview-only on the raw.pixls.us sample
+Sony A3500 (ILCE-3500) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A37 (SLT-A37) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A380 (DSLR-A380) preview issue #535: preview-only on the raw.pixls.us sample
+Sony A390 (DSLR-A390) preview issue #535: preview-only on the raw.pixls.us sample
+Sony A450 (DSLR-A450) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A500 (DSLR-A500) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A5000 (ILCE-5000) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A5100 (ILCE-5100) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A550 (DSLR-A550) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A55V (SLT-A55V) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies
+Sony A560 (DSLR-A560) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A57 (SLT-A57) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A58 (SLT-A58) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A580 (DSLR-A580) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A6000 (ILCE-6000) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A6100 (ILCE-6100) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A6300 (ILCE-6300) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A6400 (ILCE-6400) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A65 (SLT-A65) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A6500 (ILCE-6500) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A6600 (ILCE-6600) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A6700 (ILCE-6700) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A68 (ILCA-68) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7 (ILCE-7) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7 II (ILCE-A7M2) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies
+Sony A7 III (ILCE-7M3) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7 IV (ILCE-7M4) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A700 (DSLR-A700) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A77 (SLT-A77) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A77 II (ILCA-77M2) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7C (ILCE-7C) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7C II (ILCE-7CM2) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7CR (ILCE-7CR) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7R (ILCE-7R) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7R II (ILCE-7RM2) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7R III (ILCE-7RM3) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7R IIIA (ILCE-7RM3A) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7R IV (ILCE-7RM4) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7R IVA (ILCE-7RM4A) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7R V (ILCE-7RM5) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7S (ILCE-7S) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7S II (ILCE-7SM2) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A7s III (ILCE-7SM3) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A850 (DSLR-A850) preview issue #535: preview-only on the raw.pixls.us sample
+Sony A9 (ILCE-9) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A9 II (ILCE-9M2) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A9 III (ILCE-9M3) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A900 (DSLR-A900) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony A99 (SLT-A99V) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies
+Sony A99 II (ILCA-99M2) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony Alpha 1 (ILCE-1)* verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony DSC-F828 unsupported issue #702 (SRF refused); SRF not in the import list
+Sony DSC-HX95 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony DSC-HX99 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony DSC-V3 unsupported SRF not in the import list
+Sony FX3 (ILME-FX3) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony FX30 (ILME-FX30) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-3 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-3N verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-5 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-5N verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-5R verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-5T verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-6 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-7 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-C3 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony NEX-F3 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony QX1 (ILCE-QX1) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX0 (DSC-RX0) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX0 II (DSC-RX0M2) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX1 (DSC-RX1) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX10 (DSC-RX10) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX10 II (DSC-RX10M2) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX10 III (DSC-RX10M3) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX10 IV (DSC-RX10M4) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX100 (DSC-RX100) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX100 II (DSC-RX100M2) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX100 III (DSC-RX100M3) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX100 IV (DSC-RX100M4) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX100 V (DSC-RX100M5) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX100 VA (DSC-RX100M5A) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX100 VI (DSC-RX100M6) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX100 VII (DSC-RX100M7) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX1R (DSC-RX1R) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony RX1R II (DSC-RX1RM2) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony ZV-1 (DCZV1/B) verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony ZV-E1 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
+Sony ZV-E10 verified issue #535: raw.pixls.us bulk check of all 100 Sony bodies (decodes; BL/WL/crop/WB vs ExifTool)
diff --git a/docs/raw/usage-weights.tsv b/docs/raw/usage-weights.tsv
new file mode 100644
index 000000000..f3f57c5e0
--- /dev/null
+++ b/docs/raw/usage-weights.tsv
@@ -0,0 +1,114 @@
+# Estimated share of Lightroom raw photographers by camera maker, and the popular bodies of the last ~8 years inside each.
+# Sources (2026-10-10): Nikkei 2025 mirrorless production via PhotoRumors (Canon 2.3 M, Sony 1.8 M, Nikon 0.86 M, Fujifilm 0.75 M
+# units, i.e. ~36 / 29 / 14 / 12 % of CIPA's 6.31 M); BCN 2025 Japanese retail via PetaPixel (Sony 29.9 %, Canon 27.4 %, Nikon 15.1 %);
+# camera makers named in this repo's user issues (325 issues: Sony 33, Nikon 31, Canon 26, phones 23, Fujifilm 9, Panasonic/Leica 8,
+# OM/Olympus 6, Pentax/Ricoh 6, Samsung NX 5). Canon and Nikon get extra weight for their installed base of DSLRs.
+# Rows "maker" give the maker bucket's weight (percent of photographers); rows "body" give a popular body inside a bucket.
+# Inside a bucket, the listed bodies share 80 % equally and every other Adobe-listed model of the bucket's makers shares 20 %.
+kind bucket makers_or_camera weight
+maker Canon Canon 33
+maker Sony Sony 27
+maker Nikon Nikon 16
+maker Fujifilm Fujifilm 10
+maker Panasonic Panasonic 4
+maker OM / Olympus Olympus,OM Digital Solutions 3
+maker Phones Apple,Google,Samsung,Xiaomi,Huawei,Oppo,OnePlus,LG,Motorola,Nokia 3
+maker Pentax / Ricoh Pentax,Ricoh 1.5
+maker Leica Leica 1
+maker DJI DJI 1
+maker Medium format and others Hasselblad,Phase One,Leaf,Mamiya,Sigma,Kodak,Konica Minolta,Epson,Casio,GoPro,Insta360,Zeiss,Pixii,Light,Skydio,Parrot,Yuneec,Contax,DxO 0.5
+body Canon EOS R6 Mark II 1
+body Canon EOS R5 1
+body Canon EOS R6 1
+body Canon EOS R7 1
+body Canon EOS R8 1
+body Canon EOS R10 1
+body Canon EOS R50 1
+body Canon EOS RP 1
+body Canon EOS R (** see note) 1
+body Canon EOS R5 Mark II 1
+body Canon EOS R6 Mark III 1
+body Canon EOS R3 1
+body Canon EOS R100 1
+body Canon EOS 90D 1
+body Canon EOS M50 (EOS Kiss M) 1
+body Canon EOS M50 Mark II 1
+body Canon EOS M6 Mark II 1
+body Canon EOS-5D Mark IV (** see note) 1
+body Canon EOS 6D Mark II 1
+body Canon EOS 80D 1
+body Canon EOS 250D (EOS Rebel SL3, EOS 200D II, EOS Kiss X10) 1
+body Canon EOS 1500D (EOS Rebel T7, EOS Kiss X90, EOS 2000D) 1
+body Sony A7 IV (ILCE-7M4) 1
+body Sony A7 III (ILCE-7M3) 1
+body Sony A7R V (ILCE-7RM5) 1
+body Sony A7R IV (ILCE-7RM4) 1
+body Sony A7C (ILCE-7C) 1
+body Sony A7C II (ILCE-7CM2) 1
+body Sony A7CR (ILCE-7CR) 1
+body Sony A6400 (ILCE-6400) 1
+body Sony A6700 (ILCE-6700) 1
+body Sony A6600 (ILCE-6600) 1
+body Sony A6100 (ILCE-6100) 1
+body Sony Alpha 1 (ILCE-1)* 1
+body Sony A9 III (ILCE-9M3) 1
+body Sony ZV-E10 1
+body Sony A7s III (ILCE-7SM3) 1
+body Sony RX100 VII (DSC-RX100M7) 1
+body Nikon Z 6 II 1
+body Nikon Z 6 III 1
+body Nikon Z 8 1
+body Nikon Z 9 (*See note) 1
+body Nikon Z f 1
+body Nikon Z 5 1
+body Nikon Z 50 1
+body Nikon Z 50 II 1
+body Nikon Z fc 1
+body Nikon Z 7 II 1
+body Nikon D850 1
+body Nikon D750 1
+body Nikon D7500 1
+body Nikon D780 1
+body Fujifilm X-T5 1
+body Fujifilm X-T4** 1
+body Fujifilm X-S20 1
+body Fujifilm X-S10 1
+body Fujifilm X-H2S 1
+body Fujifilm X-H2 1
+body Fujifilm X100V** 1
+body Fujifilm X100VI 1
+body Fujifilm X-T30 II 1
+body Fujifilm X-E4 1
+body Fujifilm X-T50 1
+body Fujifilm GFX 100S 1
+body Fujifilm X-M5 1
+body Panasonic LUMIX DC-S5 1
+body Panasonic LUMIX DC-S5M2 1
+body Panasonic LUMIX DC-GH6 1
+body Panasonic LUMIX DC-GH5M2 1
+body Panasonic LUMIX DCG9M2 1
+body Panasonic LUMIX DC-G9 1
+body Panasonic LUMIX DC-S1 1
+body Panasonic LUMIX DC-GX9 (DC-GX7MK3) 1
+body Panasonic LUMIX DMC-G8 (DMC-G80, DMC-G81, DMC-G85) 1
+body Panasonic LUMIX DC-LX100 II 1
+body OM / Olympus OM-D E-M1 Mark III 1
+body OM / Olympus OM-D E-M1 Mark II 1
+body OM / Olympus OM-D E-M10 Mark IV 1
+body OM / Olympus OM-D E-M5 Mark III 1
+body OM / Olympus PEN E-P7 1
+body OM / Olympus Stylus Tough TG-6 1
+body OM / Olympus OM System OM-1 1
+body OM / Olympus OM System OM-1 MARK II 1
+body OM / Olympus OM System OM-5 1
+body Pentax / Ricoh K-1 II (* see note) 1
+body Pentax / Ricoh K-3 Mark III 1
+body Pentax / Ricoh KF 1
+body Pentax / Ricoh K-70 1
+body Pentax / Ricoh GR III 1
+body Pentax / Ricoh GR IIIx 1
+body Leica Q3 1
+body Leica Q2 1
+body Leica M11 1
+body Leica SL2 1
+body Leica M10 1
diff --git a/docs/releasing.md b/docs/releasing.md
index de348811c..feff91d87 100644
--- a/docs/releasing.md
+++ b/docs/releasing.md
@@ -22,7 +22,7 @@ release that has already been published, so bump the version before producing an
## Builds and artifacts
-Release jobs build macOS universal, Windows x86/x64/ARM64, Linux x86_64/aarch64, FreeBSD x86_64,
+Release jobs build macOS universal, Windows x86/x64/ARM64, Linux x86_64/aarch64/riscv64, FreeBSD x86_64,
and the WASM web app. The platform scripts in [`packaging/`](../packaging/) write their outputs to
`dist/release/` (the full list of files is the README's Downloads section):
@@ -32,6 +32,9 @@ and the WASM web app. The platform scripts in [`packaging/`](../packaging/) writ
architecture (`packaging/linux/package.sh`). Each AppImage embeds
`gh-releases-zsync|storytold|lightcraft|latest|lightcraft-*-linux-.AppImage.zsync`, so
AppImageUpdate fetches only the changed blocks from the latest published (non-pre-) release.
+- Linux riscv64: `.tar.gz` only, cross-compiled on Ubuntu 24.04 against its riscv64 ports
+ libraries (so glibc 2.39 or newer), with the CLI smoke-tested under QEMU (`CROSS_*` variables
+ of `packaging/linux/package.sh`).
- Flatpak: a single-file `.flatpak` bundle for each architecture, repackaged from that
architecture's Linux tarball (`packaging/linux/flatpak-bundle.sh` with
`packaging/linux/flatpak/ai.storyteller.lightcraft.bundle.yml`; no Rust build). The from-source
diff --git a/docs/roadmap.md b/docs/roadmap.md
new file mode 100644
index 000000000..f50d36886
--- /dev/null
+++ b/docs/roadmap.md
@@ -0,0 +1,113 @@
+# Roadmap: milestones, current focus and what's next
+
+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (created; milestones moved here from ROADMAP.md, focus re-ranked after the 2026-10-10 re-measure) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
+
+Forward-looking plan. The summary with the headline numbers is [ROADMAP.md](../ROADMAP.md); the
+ranked shortfalls are [gaps.md](gaps.md); the numbers come from [target-app-parity.md](target-app-parity.md).
+LightCraft has no `cargo xtask scorecard`; the measured checklist is
+[parity-checklist.md](parity-checklist.md) (`cargo xtask parity`).
+
+## Current focus
+
+Toward **beta** (~340–600 Opus 5.5 agent hours; ranked, each a gap in [gaps.md](gaps.md)):
+
+1. **Camera colour of our own** (gap 1, 50–90 h): chart-based calibration for the most-used models,
+ building on the per-file JPEG fits and pooled profiles (`lightcraft-cli calibrate`); never Adobe
+ data.
+2. **CR3 for every Canon body** (gap 2, 25–45 h): the remaining CRX variants, sRAW / mRAW, HEIF CR3;
+ clean-room from prose descriptions (decided 2026-10-05; compressed NEF is the template).
+3. **Render-fidelity suite** (gap 3, 30–60 h): score our output against Lightroom's on the same CC0
+ raws (references local in `plan/`), then tune; a retune ships as a new process version.
+4. **Full-resolution zoom** (gap 4, 8–15 h): visible-region rendering so 100% shows original pixels.
+5. **AI masks** (gap 5, 40–70 h): Subject / Sky / People on licensable weights; configure the SAM 3
+ mirror. Needs the maintainer's model decision.
+6. **Compressed ORF and HE NEF** (gaps 6–7, 28–55 h).
+7. **Lens profiles of our own** (gap 8, 40–80 h).
+8. **Tests in CI on macOS, Windows and Linux** (gap 9, 6–12 h).
+9. **Per-model verification** (gap 10, 20–35 h).
+
+## Alpha gate
+
+The target's core workflows: what a typical Lightroom user does every day. Alpha requires each to
+work end to end on the main platform (macOS) and the work to save and reopen. Checked on 2026-10-10
+against the code, tests, issues and [gaps.md](gaps.md).
+
+| Workflow | Works end to end? | Evidence | Hours to pass |
+|---|---|---|---:|
+| 1. Import a shoot (raw + JPEG) from a card or folder into the library | yes | Add / Copy / Move import with templates, duplicates, devices with DCIM, background import with cancel (`crates/engine/src/import.rs`, `tests_import.rs`, `tests_import_move.rs`); checklist A 83% | 0 |
+| 2. Cull: rate, flag, label, compare / survey, filter, check focus | yes (partial: focus check) | ratings / flags / labels with advance, Compare, Survey ≤ 48, filter bar, smart albums (checklist B–D 88–92%); 100% zoom is capped by the preview size (2,560 px default, adjustable in Settings), gap 4 — degrades focus checks on large files but does not block culling | 0 (8–15 h to close gap 4, beta) |
+| 3. Develop raws non-destructively (global sliders, crop, masks, heal) | yes for most cameras (partial: Canon CR3 outside the verified variants, compressed ORF, HE NEF open as embedded previews) | Sony, Nikon (except HE), Fujifilm, Panasonic, Pentax, CR2, DNG decode from sensor data (raw-parity.md); every Edit slider, crop / Upright, brush / gradient / range masks, heal / clone (checklist F, H, K); colour estimated per file rather than measured, gaps 1–3 | 0 (coverage and colour gaps are beta blockers 1–3, 6–7) |
+| 4. Save, quit and reopen the work | yes | crash-safe journal + snapshots, torn-append and crash tests (`crates/catalog/src/journal.rs`, `tests_journal.rs`, `tests_torn_append.rs`); XMP sidecars read / write; failed saves reported | 0 |
+| 5. Batch-apply a look: presets, copy / paste / sync settings | yes | presets (incl. `.xmp` / `.lrtemplate` import), copy / paste / sync, auto sync (checklist L 86%, N 100%) | 0 |
+| 6. Export deliverables (JPEG / TIFF, size, sharpening, metadata, watermark, naming) | yes | one encoder for app, CLI and MCP (`crates/engine/src/export.rs`, `tests_export.rs`); checklist S 89% | 0 |
+
+**Result: passes.** Every core workflow runs end to end on macOS and the work survives quit and
+reopen; the partial rows degrade fidelity for some cameras and large files but don't stop the
+workflow, so LightCraft stays **alpha** (ready for real work ~61%, above the ~40% bar).
+
+## After beta
+
+Enhance (Raw Details, Super Resolution, X-Trans denoise), content-aware remove, HDR display, the
+Classic output modules (Print, then Map, Book, Slideshow, publish services), tethering, video,
+plug-in ecosystem, localization to `full` and new scripts (RTL, shaping). Estimates in
+[gaps.md](gaps.md) entries 11–28; ~500–1,300 h after beta.
+
+## Milestones
+
+Original wall-clock estimates (made before work started, 4–6 parallel agents) are kept for
+calibration: M0–M13 took ≈ 25 active hours against an estimate of ≈ 110–170 h.
+
+**Status legend:** ✅ done · 🚧 in progress · ⬜ not started
+
+| # | Milestone | Scope (summary) | Estimate (h) | Status |
+|---|---|---|---|---|
+| M0 | Skeleton + visual shell | workspace, xtask CI + layering, geom/color/raster, develop model, pipeline v0, catalog v0, engine commands, Lightroom-look UI (grid, loupe, filmstrip, Edit panel), control channel, MCP, web build | 3–5 | ✅ |
+| M1 | Library core | import (JPEG/PNG/TIFF/WebP), EXIF/XMP, persistent catalog (op log + snapshots), albums, ratings/flags/labels, filter/search/sort, thumbnail cache, 100k-photo grid | 6–10 | ✅ (100k-photo grid scale test pending) |
+| M2 | Pipeline v1 (quality) | WB temp/tint, profiles, local tone mapping (highlights/shadows), curves, HSL, point colour, colour grading, texture/clarity/dehaze, vignette, grain, B&W, auto tone/WB, histogram, before/after | 10–15 | ✅ (look tuning vs our references ongoing) |
+| M3 | RAW I | TIFF/DNG (LJ92, deflate, tiles, opcodes), demosaic (AHD/PPG/bilinear), highlight recovery, DNG colour model, CR2, NEF, ARW, embedded previews | 10–15 | ✅ (DNG, CR2 incl. sRAW, ARW, NEF uncompressed / lossless / lossy / lossy after split, embedded previews) |
+| M4 | Crop, geometry, optics | crop tool + overlays, straighten, Upright (auto/level/vertical/full/guided), manual transforms, CA, defringe, manual lens corrections | 6–10 | ✅ |
+| M5 | Performance | source pyramids, wgpu compute pipeline (CPU oracle), draft/full renders, prefetch, budgets (16 ms slider updates on 24 MP) | 10–15 | 🚧 (stage cache, source pyramid, wgpu pipeline, prefetch, memory budget ✅; colour NR at half resolution, GPU histogram ⬜) |
+| M6 | Masking | brush, linear/radial gradients, colour/luminance/depth range, add/subtract/intersect/invert, all local adjustments, masks panel | 8–12 | 🚧 (brush/linear/radial/colour/luminance range, add/subtract/intersect, masks panel ✅; depth range, AI masks ⬜) |
+| M7 | Detail | sharpening + masking preview, luminance/colour NR, Denoise, Raw Details, Super Resolution | 6–10 | 🚧 (sharpening, luminance/colour NR ✅; AI Denoise 🟡 (Bayer, optional weights; policy/fidelity open); Raw Details, Super Resolution ⬜) |
+| M8 | Heal / Remove | content-aware remove (PatchMatch), heal, clone, brush spots, visualize spots, red/pet eye | 6–10 | 🚧 (heal, clone, auto source, visualize spots, red/pet eye ✅; PatchMatch remove ⬜) |
+| M9 | Presets, profiles, versions, sync | preset browser + amount, create/import presets, profile browser, versions, history, copy/paste/sync settings | 5–8 | ✅ |
+| M10 | Export & share | export dialog (JPEG/PNG/TIFF/DNG/AVIF/JXL/original), sizing, sharpening, metadata, watermark, naming, batch jobs, XMP sidecars, HDR export | 6–10 | 🚧 (all formats incl. DNG/original, sizing, presets, background jobs, HDR gain map JPEG / PQ AVIF ✅; JXL encode ⬜) |
+| M11 | RAW II | CR3, RAF (X-Trans), ORF, RW2, PEF, SRW, 3FR, IIQ + long tail; camera calibration DB; HEIC/AVIF/JXL import | 20–35 | 🚧 (RAF all, RW2 every format, PEF, ORF uncompressed and 12.8 bpp, SRW uncompressed, HEIC ✅; CR3 🟡 (3 bodies verified); **camera colour calibration** 🚧 (per-file JPEG fits, 4 bundled profiles, 52 spectral matrices; measured database missing); compressed ORF / SRW, HE NEF, IIQ, X3F ⬜) |
+| M12 | AI & smart features | subject/sky/background/people/object masks, semantic search, faces/People (permissively licensed models, pure-Rust inference) | 20–40 | 🚧 (faces: detection, recognition, People view ✅; Object / Describe masks via SAM 3 🟡; Subject / Sky / People masks, semantic search ⬜) |
+| M13 | Merge | HDR merge (deghost), panorama (projections, boundary warp, fill edges), HDR panorama | 10–15 | ✅ |
+| M14 | Video | import/playback/trim via FilmCraft crates, global edits + presets on video, video export | 6–10 | ⬜ |
+| M15 | Classic modules | Map, Book, Slideshow, Print, Web; smart collections, stacks, virtual copies, publish services, tethering | 25–40 | 🚧 (smart albums, stacks, virtual copies, compare/survey ✅; contact-sheet PDF 🟡; Map/Book/Slideshow/Print/Web ⬜) |
+| M16 | 1.0 polish | preferences, shortcut editor, accessibility, localization, packaging (dmg/msi/AppImage/web), hardening | 10–20 | 🚧 (settings, keyboard shortcuts sheet, packaging basics ✅; English/Chinese (Simplified, Traditional)/Japanese/Brazilian Portuguese/Spanish/German/Russian/Ukrainian localisation 🟡; accessibility and further locales ⬜) |
+
+## Risks that coding hours alone don't retire
+
+- **AI features** (subject / sky / people masks, generative remove) need model weights with
+ licences we can ship; classical fallbacks first. No permissively licensed sky-segmentation or
+ raw-denoise model was found; we may need to train our own.
+- **Camera colour and lens data** is a data problem: we never use Adobe's matrices, DCPs or LCPs.
+ DNG-embedded data first, then our own calibration; long-tail coverage grows over time and needs
+ people with cameras, lenses and charts.
+- **Raw-format sources:** decoders are written from prose format descriptions (even ones published
+ alongside GPL code); decoder source is never read (decided 2026-10-05). Still open:
+ freedom-to-operate review for local Laplacian filters, PatchMatch and HEVC (HEIC).
+- **Look parity** with Adobe's default rendering is tuned by eye until the fidelity suite exists.
+ Retuning changes existing edits, so it ships as a new process version
+ ([process-versions.md](process-versions.md)).
+
+## Design notes kept from the old ROADMAP
+
+- **Folder records:** library folders have records of their own in the catalog
+ (`Catalog.folder_records`, keyed by folder path; colour labels first). Photos still store full
+ paths, so moving a whole disk relinks each photo. If that becomes a problem (an "Update Folder
+ Location" for a renamed drive, journal growth on large moves), the next step is the Classic model:
+ photos point to a folder id (root + relative path).
+- **Zoom:** native pinch zoom and two-finger pan work in Detail, Compare and Reference views; the
+ whole-image preview still has a configurable cap (2,560 px default), see gap 4.
+
+## Revision history
+
+| Date | Change | Summary |
+|---|---|---|
+| 2026-10-10 | minor | Beta hours 340–600; full ready ~59% in the gate note |
+| 2026-10-10 | minor | Added the Alpha gate table (core-workflow gate from the revised standard): passes, stays alpha |
+| 2026-10-10 | major | Created: milestones moved from ROADMAP.md (statuses refreshed: M3 ✅, M11 / M12 / M15 detail), current focus re-ranked toward beta, risks and design notes kept |
diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md
new file mode 100644
index 000000000..b88063dae
--- /dev/null
+++ b/docs/target-app-parity.md
@@ -0,0 +1,304 @@
+# Target-app parity: LightCraft vs Adobe Lightroom
+
+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (full re-measure against Lightroom Classic 15.5 and the installed Lightroom 9.6; merged ROADMAP's parity estimate and honest assessment into this file) · **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
+have their own documents: [raw-parity.md](raw-parity.md), [file-format-parity.md](file-format-parity.md),
+[ui-parity.md](ui-parity.md), [hardware-parity.md](hardware-parity.md),
+[localization-parity.md](localization-parity.md).
+
+## Headline
+
+| Number | Value | Kind |
+|---|---:|---|
+| **Feature breadth**, checklist | **81.1%** of 517 in-scope rows (P0 98.0%, P1 95.1%, P2 46.9%) | **measured**: recount of [parity-checklist.md](parity-checklist.md) rows (✅ 1, 🟡 ½, 🚫 left out), the same rule `cargo xtask parity` uses |
+| **Feature breadth**, weighted by use | **~77%** | estimated: feature-area table below |
+| **Ready for real work** (full target) | **~61%** (range 56–66%) | estimated: weighted sum over the dimensions with written weights, see [below](#ready-for-real-work-mainstream-practitioner-and-essentials-user) |
+| **Mainstream practitioner** | **~49%** (range 44–54%) | estimated: weekly areas of a working photographer, depth 71.8% × 0.804 × colour 0.85 |
+| **Essentials user** | **~64%** (range 59–69%) | estimated: core features at default settings, depth 77.4% × 0.830 |
+| Remaining to **beta** | **~340–600 h** | estimated; ~60% parallelizes |
+| Remaining to **full parity** | **~1,000–1,800 h** | estimated; ~70% parallelizes |
+| **Stage** | **alpha** | see [Stage](#stage) |
+
+## The target and how it was measured
+
+- **Target:** Lightroom Classic 15.5 (August 2026; latest per Adobe's release notes) for the
+ library, modules and depth; Lightroom 9.6 (the cloud-library desktop app), installed on the
+ owner's Mac at `/Applications/Adobe Lightroom CC`, for the interface LightCraft's layout follows.
+ Both share the Camera Raw engine.
+- **Installed bundle, inspected by listing only** (the clean-room rule forbids reading contents):
+ `Info.plist` document types (31 raw extensions, jpg, tif, heic, xmp, catalog bundles); 16
+ `.lproj` localizations; `CameraProfiles` (1,500 Adobe Standard and 395 Camera Matching model
+ names); `LensProfiles` (3,635 files, 56 makers); `ModelZoo` (37 AI model names). The app was not
+ launched.
+- **LightCraft side:** origin/main at `b70ae174` (2026-10-10): code counts (226 engine commands, 143
+ UI commands, 122 + 10 develop controls, 2,561 tests, ≈ 202,000 Rust lines in 24 crates + 3 apps),
+ the checklist rows, raw corpus manifests, locale catalogs, CI workflows, docs.
+- **Behaviour and output** were not compared against a running Lightroom in this pass; the
+ measured ΔE figures in [raw-parity.md](raw-parity.md) come from earlier work.
+
+**Presence is not parity.** ✅ rows are set by whoever lands a feature and have not been checked
+systematically against Lightroom's behaviour; bugs keep turning up in ✅ areas (CR2 colour-filter
+phase #85, duplicate Local entries #22, black GPU exports on an Intel iGPU #78).
+
+## By dimension
+
+| 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) | 81% of Adobe's 1,446 models decode (16% verified), measured | ~56% | 255–480 h | [raw-parity.md](raw-parity.md) |
+| UI/UX fidelity | ~85% | ~70% | 40–70 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) |
+| Localization (12 key languages) | 7 of 12 present | ~52% (mean of the 12, measured) | 130–210 h for the target's set; +145–255 h for Hindi, Arabic, Indonesian, Vietnamese, Korean | [localization-parity.md](localization-parity.md) |
+| Performance | ~80% | ~70% | 30–50 h | 24 MP GPU slider 3.9 ms, export 0.3 s (measured, [gpu-pipeline.md](gpu-pipeline.md)); 85k library opens in 1.7–4.7 s; 100k test pending |
+| Stability | — | ~60% | 30–60 h | never-crash lints and guards workspace-wide, crash-tested journal, 2,561 tests; but CI runs tests only on FreeBSD, user reports keep finding bugs in ✅ areas |
+| Platforms | macOS, Windows (x64 / x86 / ARM64), Linux (AppImage, deb, rpm, Flatpak, Nix), FreeBSD, web | ~60% | 25–45 h | macOS is where it is tested; Windows installer UI unverified on Windows (#79); web experimental (CPU only) |
+| Ecosystem / plug-ins | ~25% | ~15% | 40–80 h + owner decision | no Lightroom SDK equivalent (export, publish, metadata plug-ins); beyond the target: JSON-lines control channel, MCP server with 28 tools + one per command, CLI |
+| AI features | ~30% | ~20% | 120–220 h + model decisions | [AI](#ai) (overlaps the masking and Enhance rows; not added twice) |
+
+## Features
+
+Weights are a judgement of what Lightroom users spend their time on (library and develop dominate;
+Classic output modules and video are used by few). Breadth and readiness per area are estimated
+from the checklist sections and the deep-dive documents.
+
+| Area | Weight | Breadth | Ready | Remaining | Notes |
+|---|---:|---:|---:|---:|---|
+| 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% | 76% | 120–220 h | [raw-parity.md](raw-parity.md), measured model by model: 84% 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 | 4 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 |
+| Remove, heal, Enhance (denoise, Raw Details, Super Resolution, Lens Blur) | 7% | 45% | 30% | 70–130 h | checklist I 75%, P 0% |
+| Presets, profiles, versions, history, sync | 5% | 95% | 85% | 8–15 h | |
+| Export | 6% | 89% | 75% | 15–30 h | checklist S 88.9% |
+| Merge and HDR | 3% | 85% | 60% | 20–35 h | merge 100%, HDR 70% (no HDR display) |
+| Views, compare, zoom, culling | 5% | 90% | 70% | 15–30 h | zoom detail P0 gap |
+| Classic output modules (Map, Book, Slideshow, Print, Web, publish, tethering) | 4% | 15% | 10% | 90–150 h | Classic extras 48% counts many small keys; the modules themselves are mostly ⬜ |
+| Video | 1% | 0% | 0% | 25–45 h | catalogued only |
+| **Weighted** | 100% | **~77%** | **~61%** (60.6%) | **600–1,100 h** | |
+
+## AI
+
+Lightroom 9.6 ships 37 on-device models (names listed from its `ModelZoo`), covering subject, sky,
+portrait, skin and object selection with refinement, depth estimation, denoise, dust detection,
+reflections removal, distraction / people detection, culling scores (eyes open, blur, aesthetics) and
+search embeddings; generative remove runs in Adobe's cloud.
+
+| Feature | LightCraft | Status |
+|---|---|---|
+| Subject / Sky / Background masks | classical heuristics; the file's own DNG semantic mattes | 🟡 |
+| People (faces, body, hair, skin…) and Landscape masks | — | ⬜ |
+| Object / Describe masks | SAM 3 in pure Rust (Metal / CPU); opt-in 3.4 GB download, no mirror configured | 🟡 |
+| Depth range mask, Lens Blur | — | ⬜ |
+| AI Denoise | Bayer only, pure-Rust CPU / GPU, user-installed weights | 🟡 |
+| Raw Details, Super Resolution | — | ⬜ |
+| Generative remove, distractions, reflections | — (generative is out of scope: cloud) | ⬜ |
+| Dust detection | ✅ | ✅ |
+| Faces / People | YuNet detection, SFace / AuraFace recognition (opt-in downloads), People view | ✅ (MVP, [faces.md](faces.md)) |
+| Assisted culling | focus, bursts | 🟡 (no eyes-open / aesthetics scores) |
+| Auto (tone), adaptive presets | Auto calibrated on Lightroom's Auto results (#628); no adaptive presets | 🟡 |
+| Natural-language search | — | ⬜ |
+
+**Blocked on a model strategy** (maintainer decision): licensable weights or our own training;
+pure-Rust inference is proven (faces, SAM 3, denoise).
+
+## Ready for real work, mainstream practitioner and essentials user
+
+Three readiness numbers (craftrules `standards/progress-docs.md`). The full number is a weighted
+sum over the dimensions with written weights. Mainstream and essentials take the weighted depth of
+the areas their user touches, then apply written multiplicative discounts for what still stops real
+work. Each has its own hours to ~95%, calibrated as in [How hours are calibrated](#how-hours-are-calibrated)
+(essentials ⊂ mainstream ⊂ full):
+
+| Audience | Ready | Opus 5.5 agent wall-clock hours to ~95% | Work that dominates |
+|---|---:|---:|---|
+| Full target (ready for real work) | **~61%** | **1,000–1,800 h** (~70% parallelizes) | raw colour, lens and camera data; AI models; Classic output modules; localization; plug-in ecosystem |
+| Mainstream practitioner | **~49%** | **450–840 h** (~60% parallelizes) | camera colour and render fidelity; CR3 / ORF / HE NEF; lens database; subject / sky masks; Lightroom XMP exchange; stability |
+| Essentials user | **~64%** | **160–300 h** (~50% parallelizes) | default raw look for popular cameras; launch stability; basic-slider feel; opening CR3 / HEIC files people send |
+
+### Full target (ready for real work): ~61%
+
+Weighted sum over the dimensions of [By dimension](#by-dimension) (AI is left out because it
+overlaps the masking and Enhance feature areas). RAW decoding and camera colour are their own rows,
+valued from the model-by-model measurement in [raw-parity.md](raw-parity.md):
+
+| Dimension | Weight | Ready |
+|---|---:|---:|
+| Features: the other 11 areas, weighted depth from [Features](#features) | 45% | 60.9% |
+| RAW decoding and camera coverage | 9% | 76% (84% use-weighted × 0.9) |
+| Camera colour and default rendering | 6% | 35% |
+| UI/UX fidelity | 8% | 70% |
+| File formats (non-raw) | 6% | 70% |
+| Hardware | 3% | 40% |
+| Localization | 3% | 52% |
+| Performance | 6% | 70% |
+| Stability | 8% | 60% |
+| Platforms | 4% | 60% |
+| Ecosystem / plug-ins | 2% | 15% |
+| **Weighted sum** | 100% | **60.6% ≈ 61%** (range 56–66%) |
+
+Mainstream comes out lower than full: its scope is the raw-develop core, where LightCraft is weakest
+(camera colour, CR3 coverage), and its discounts are multiplicative, while the full sum is lifted by
+strong library, presets, UI and format dimensions.
+
+**Why the numbers moved with the RAW measurement:** decoding measured model by model against
+Adobe's list is better than the earlier 50% estimate (84% of photographers' cameras decode, 76% after
+a bug discount), colour is lower (35%, was 40%) and is now its own row. Net: full +2 points.
+Mainstream now carries colour as a ×0.85 discount on top of the measured coverage (−2 points);
+essentials replaces its guessed 45% raw look with 84% × 0.75 (+1 point).
+
+### Discounts (evidence: open issues from users, 2026-10-10)
+
+| Discount | Mainstream | Essentials | Evidence |
+|---|---:|---:|---|
+| Interaction fidelity / UI clarity | ×0.95 | ×0.95 | brush live preview gaps and feathering (#517, #518); crop artefacts and limits (#731, #742, #770, #771); Compare white-balance selector (#743–#745); low-res slider previews (#330); dialogs taller than the window (#781); mouse-wheel scroll (#714); 100% zoom capped (gap 4) |
+| Stability on real machines | ×0.92 | ×0.92 | launch and quit crashes or silent startup failures (#250 macOS 12 Intel, #315 Windows Intel GPU, #431 Intel Mac, #260, #462, #783 Linux / Nouveau, #790 Windows installer); tests run in CI on FreeBSD only (gap 9) |
+| File exchange with Lightroom users (mainstream) / opening files people send (essentials) | ×0.92 | ×0.95 | Adobe XMP sidecars change colours (#290), XMP CameraProfile and Upright ignored (#204, #205), catalog import fails (#599), same Light values render differently (#523, #209), no write-back to `.lrcat`; for casual users: CR3 beyond verified bodies, compressed ORF, HE NEF open as previews (#473, #485, #556), HEIC trouble (#693) |
+| **Product** | **×0.804** | **×0.830** | |
+
+### Mainstream practitioner: ~49%
+
+A working photographer (wedding, portrait, events, landscape) shooting raw on one current camera,
+weekly: import, cull, develop, a few masks, sync a look, export. Excluded: Classic output modules,
+video, generative / cloud AI, plug-ins, tethering and other specialist hardware, languages other
+than the user's own.
+
+| Area | Weight | Depth |
+|---|---:|---:|
+| Import and library | 18% | 80% |
+| Cull: views, compare, zoom, ratings | 12% | 70% |
+| Raw decoding for their camera | 27% | 84% (measured use-weighted share whose camera fully works, [raw-parity.md](raw-parity.md#by-use-estimated)) |
+| Global develop adjustments | 15% | 70% |
+| Crop, geometry, lens corrections | 6% | 50% |
+| Masking (manual, subject / sky; no generative) | 8% | 45% |
+| Heal / remove (no generative), denoise | 5% | 40% |
+| Presets, copy / paste / sync | 5% | 85% |
+| Export | 4% | 75% |
+| **Weighted depth** | 100% | **71.8%** |
+
+Colour fidelity is a written discount, **×0.85**: most raws start near the camera's own look (median
+ΔE ~5), about 10% far off (ΔE ~18), none calibrated to Adobe Standard, so a working photographer
+corrects colour by hand (33 open colour issues). 71.8% × 0.804 × 0.85 = **~49%** (range 44–54%).
+
+### Essentials user: ~64%
+
+A casual photographer with default settings: import, look through, rate, fix a few photos with the
+basic sliders, crop, apply a preset, undo, export JPEGs.
+
+| Feature | Weight | Depth |
+|---|---:|---:|
+| Import photos | 15% | 80% |
+| Browse grid and detail view | 10% | 85% |
+| Rate and flag | 5% | 90% |
+| Basic sliders (exposure, contrast, highlights, shadows, white balance, vibrance / saturation) | 22% | 70% (slider strength differs from Lightroom, #196, #521) |
+| Auto | 5% | 65% (#318) |
+| Crop and straighten | 10% | 80% |
+| Presets and profiles | 8% | 80% |
+| Undo and history | 5% | 90% |
+| Their camera's raws: decode × default look | 10% | 63% (84% whose camera fully works × colour factor 0.75) |
+| Export JPEG | 10% | 85% |
+| **Weighted depth** | 100% | **77.4%** |
+
+77.4% × 0.830 = **~64%** (range 59–69%).
+
+### User evidence (GitHub issues, excluding the four maintainers)
+
+- 324 issues from 159 users; 162 open. 8,719 stars, 2,698 forks (2026-10-10).
+- Of the 162 open: **~92 core-path bugs** (33 colour / rendering, 16 decoding, ~36 UI / workflow, 7
+ launch / stability), ~11 developer / test infrastructure, **~59 feature requests or niche asks**
+ (plug-ins, mobile, sync servers, tablets, MIDI, geocoding, themes).
+- Praise in issue text (love / amazing / great work / awesome): ~14 issues; "thank you" in 27.
+ **No explicit "switched from Lightroom" report** was found ("switched" matches 6 issues, none a
+ switch report).
+- Reading: strong interest, but most open user issues are core-path, and raw colour dominates them,
+ so the mainstream number is held down by default rendering rather than missing features.
+
+### Why the full number moved (55% → 46% → 59%)
+
+The first figure (~55%) was the feature depth "pulled down" by an unwritten judgement. A later pass
+applied the mainstream discounts to it (~46%). The standard defines the full number as a weighted
+sum over the dimensions, so it is now that sum with the weights written above: ~59%. Method aligned
+with the standard, no new evidence. The stage stays **alpha**.
+
+## Stage
+
+**Alpha.** Core workflows exist end to end (import, cull, develop, mask, export, catalog migration),
+but ready-for-real-work is ~61%, below the ~75% beta bar, and the main file formats have blocking
+gaps: Canon CR3 decodes from sensor data on three verified bodies only, compressed ORF and HE NEF
+open as previews, and colour on every non-DNG raw is estimated rather than measured.
+
+**Alpha gate:** all six core workflows (import, cull, develop, save and reopen, batch looks, export)
+work end to end on macOS; see the table in [roadmap.md](roadmap.md#alpha-gate). The partial rows (CR3
+variants, compressed ORF, HE NEF as previews; capped 100% zoom) degrade but don't block a workflow.
+
+**To beta:** ~14 points of readiness, **~340–600 h**: the nine `B` gaps in [gaps.md](gaps.md)
+(~230–430 h), plus burning down the ~92 open core-path user issues (40–80 h) and stability work
+around them. Beta needs: CR3, ORF and
+HE NEF decoding from sensor data; measured colour for the popular models and a fidelity suite with
+scores; full-resolution zoom; Subject / Sky / People masks; a lens profile database for the
+popular lenses; tests in CI on macOS, Windows and Linux.
+
+## How hours are calibrated
+
+Hours are Opus 5.5 agent wall-clock hours, one session working sequentially, including tests and
+verification. Calibrated on this repository's history (git log and GitHub PR open-to-merge times):
+
+- **Project pace:** first commit 2026-09-30; by 2026-10-10, 1,066 non-merge commits and 325 merged
+ PRs, commits in 159 distinct clock hours, with 4–6 parallel agents plus community contributors.
+ On 2026-10-02 the ROADMAP recorded ≈ 105–145 agent-hours spent; M0–M13 took ≈ 25 active hours with
+ parallel agents against an up-front estimate of 110–170 h.
+- **UI and library rows:** a lead agent closed ≈ 45 checklist rows in ≈ 5 h (2026-10-01) and ≈ 60 in
+ ≈ 12 h (2026-10-02): 6–12 minutes per small row.
+- **Raw decoders:** RW2 in every format 6.5 h (#215), compressed RAF 8.6 h (#282), CR3 CRX 10 h
+ (#279), NEF lossy after split 4.7 h (#633), ORF 12.8 bpp 1.7 h (#651), HEIC 7 h (#609).
+- **Large features:** AI RAW denoise 8.3 h (#384, 12.8k lines), SAM 3 masks 14.9 h (#162),
+ HDR editing and export 2.3 h (#646, on existing groundwork), contact-sheet PDF (#563), German UI
+ 13.8 h (#265).
+- PR open time includes queueing and review, so these are upper bounds per feature; the ranges add
+ a long-tail factor of 1.3–1.5× for verification against real files.
+
+### Why the estimate went up
+
+The previous estimate (2026-10-02, kept below) was ≈ 270–505 h to full parity. It counted checklist
+rows only. New evidence since: the target's own lists (≈ 1,000 dedicated camera models, 3,635 lens
+profiles, 37 AI models, 16 languages) measured from the installed bundle; per-maker verification
+counts; that CI tests only FreeBSD; and the standard's added dimensions (localization, stability,
+platforms, ecosystem). The raw-codec package it estimated at 40–80 h took roughly that and closed
+most of its list; what remains is the data-heavy part (colour, lenses, models) it did not count.
+
+## History of the numbers
+
+| Date | Checklist (all / P0 / P1 / P2) | Ready for real work | Remaining to full parity | Source |
+|---|---|---|---|---|
+| 2026-10-10 (RAW) | 81.1% (unchanged) | ~61% full · ~49% mainstream · ~64% essentials | unchanged | RAW measured model by model against Adobe's list; RAW decode and colour as explicit rows |
+| 2026-10-10 (final) | 81.1% (unchanged) | ~59% full (weighted sum) · ~51% mainstream · ~63% essentials | full 1,000–1,800 h · mainstream 450–840 h · essentials 160–300 h | method aligned with the standard |
+| 2026-10-10 (later) | 81.1% (unchanged) | ~46% full · ~51% mainstream · ~63% essentials | 1,000–1,800 h | full × discounts (superseded) |
+| 2026-10-10 | 81.1% / 98.0% / 95.1% / 46.9% (517 rows) | ~55% | 1,000–1,800 h | this re-measure |
+| 2026-10-08 | 79.7% / 98.2% / 95.6% / 41.6% (509 rows) | 60–70% | — | ROADMAP *Where we stand* |
+| 2026-10-02 | 75.6% | — | 270–505 h (cloud parity without AI / Classic: 130–255 h) | ROADMAP *Parity estimate* |
+
+The 2026-10-08 assessment by dimension, kept for evidence: feature checklist 79.7%; RAW coverage
+~55%; colour and image quality ~55–65%; AI and computational ~15–20%; workflow and library ~85%
+(single machine); Classic modules ~30%; HDR and video ~25%; platform and robustness ~70%. By kind of
+user: JPEG / DNG shooter ~85%; Nikon / Sony / Panasonic / older-Canon ~65%; Fujifilm ~65%; Canon
+CR3 / Olympus compressed ~45%; Classic power user ~45%; relies on AI ~25%. Today's numbers per user:
+JPEG / DNG ~75%; Sony, Panasonic, Fujifilm ~60%; Nikon ~55% (HE NEF); Canon CR3 ~40%; OM / Olympus
+~30%; Classic power user ~40%; relies on AI ~20%.
+
+The 2026-10-02 effort table, kept for calibration: remaining P0/P1 UI 5–10 h; raw codecs 40–80 h;
+lens database 15–30 h; video 20–40 h; AI 80–150 h; HDR 15–25 h; Classic modules 60–100 h; smaller
+P2 items 12–25 h; look tuning, performance, packaging, hardening 25–45 h.
+
+## Revision history
+
+| Date | Change | Summary |
+|---|---|---|
+| 2026-10-10 | minor | RAW measured model by model (raw-parity.md): RAW decoding (76%) and colour (35%) are explicit rows of the full sum (59% → 61%); mainstream uses the use-weighted 84% plus a ×0.85 colour discount (51% → 49%); essentials uses 84% × colour 0.75 (63% → 64%); beta ~14 points |
+| 2026-10-10 | minor | Full number restored to an additive weighted sum over the dimensions (~59%; method aligned with the standard, no new evidence); hours to ~95% for each of the three audiences; beta distance ~16 points |
+| 2026-10-10 | minor | Added mainstream practitioner (~51%) and essentials user (~63%) numbers; full ready re-derived from written weights × discounts (55% → 46%); user-issue evidence; beta hours 340–600 |
+| 2026-10-10 | minor | Stage re-checked against the core-workflow alpha gate: passes, stays alpha |
+| 2026-10-10 | major | Created per the progress-docs standard: 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* |
diff --git a/docs/ui-parity.md b/docs/ui-parity.md
new file mode 100644
index 000000000..4216f1091
--- /dev/null
+++ b/docs/ui-parity.md
@@ -0,0 +1,81 @@
+# UI parity
+
+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (created; shortcut notes moved here from the checklist) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
+
+How LightCraft's interaction compares with Lightroom's: views, panels, tools and handles, modifier
+keys, shortcuts, zoom, context menus and feel. Rows for individual menu items and shortcuts live in
+[parity-checklist.md](parity-checklist.md) (sections Y and Z, Classic keys); this page is the
+assessment and the notes that don't fit a row. Visual reference for agents: `plan/lightroom/`
+(local, gitignored; `10-observed-ui.md` and screenshots).
+
+## Measured counts (2026-10-10)
+
+- 226 engine commands (`crates/engine/src/cmd/`, 30 modules) and 143 UI commands (`UI_COMMANDS` in
+ `crates/ui-egui/src/menus.rs`, 73 with a shortcut) plus 10 language commands; 23 secondary
+ shortcut bindings (`ALIASES`, `crates/ui-egui/src/shortcuts.rs`); a keymap editor.
+- 122 develop controls plus 10 indexed templates (`crates/develop/src/controls.rs`).
+- Checklist: menus 95.3% (86 in-scope rows), desktop shortcuts 94.4% (81 rows), Classic extras
+ 48.2% (82 rows), measured by recounting [parity-checklist.md](parity-checklist.md).
+- 31 panel modules (`crates/ui-egui/src/panels/`), 26 context-menu sites.
+
+## Assessment by area
+
+| Area | Target | LightCraft | Ready | Main gaps |
+|---|---|---|---:|---|
+| Layout and views | Lightroom: grid / detail / edit; Classic: Library, Develop, Map, Book, Slideshow, Print, Web modules, filmstrip, panels | Lightroom-style single window: Photo Grid, Square Grid, Detail, Compare, Survey (≤ 48), Before / After, People, quick slideshow, second window, filmstrip | 75% | no Classic module switcher (by design) or output modules; dark theme only |
+| Develop panels and sliders | Light, Color, Effects, Detail, Optics, Geometry, Lens Blur, Calibration | every global slider; scrub, double-click reset, Alt-drag previews where implemented | 80% | Lens Blur, Calibration panel depth; slider feel (acceleration, fine Shift-drag) unmeasured |
+| Crop, Upright, transform handles | handles, aspect lock, angle, overlays (O / ⇧O), guided Upright | ✅ all of them; overlay cycle on ⇧O | 85% | crop opacity of cut-out area (15.5) |
+| Masking tools | brush (A / B, flow, density, auto mask, pressure), linear / radial, range masks, AI masks, pins | brush, linear, radial, colour / luminance range, add / subtract / intersect / invert, masks panel, pins | 60% | brush live preview (#517 / #518), no pen pressure, no depth range, AI masks, Feather / Edge controls (15.5) |
+| Remove / heal | content-aware remove, heal, clone, visualize spots | heal, clone, auto source, visualize spots, red / pet eye | 65% | content-aware (patch synthesis) remove, generative remove, detect distractions |
+| Zoom and navigation | Fit / Fill / 1:1 / 2:1 … 11:1, Navigator, spacebar pan | Fit to 800%, pinch zoom, pan, Navigator-like | 60% | **original-pixel detail at 100% (preview capped at 2,560 px; P0 gap LR-VIEW-ZOOM)** |
+| Library culling and metadata | ratings, flags, labels with keys, painter, keywording, metadata panel, filter bar | ratings / flags / labels and advance, keyword list and sets, keywording box, metadata fields, smart albums, filter bar | 85% | painter tool, quick collection, some Classic keys |
+| Shortcuts | Lightroom desktop and Classic keymaps | Lightroom-desktop keymap with Classic additions, editable | 85% | deliberate differences below; no Classic keymap layer |
+| Context menus | everywhere | 26 sites (grid, filmstrip, albums, folders, keywords, masks, presets…) | 70% | unaudited against Lightroom's menus item by item |
+| Text fields | native editing behaviour | custom text field (LR-BEHAV-TEXTFIELD) | 70% | IME and selection edge cases |
+| Accessibility | VoiceOver / Narrator, keyboard navigation | AccessKit: sliders, buttons, dropdowns, section headers announce | 30% | canvas tools pointer-only; not audited with a screen reader |
+| Feel and responsiveness | GPU-interactive sliders (15.3) | ≤ 16 ms slider budget on GPU; background renders | 75% | colour NR at half resolution, 100k-photo grid test |
+| **UI/UX overall** | | | **~70%** | estimated, weighted by use (develop and library 60%, tools 25%, rest 15%) |
+
+Remaining: **40–70 h** (zoom detail 8–15 h, brush and masking feel 8–12 h, accessibility audit 10–20
+h, Classic keymap layer 4–8 h, context-menu audit 4–8 h, text-field and IME polish 4–8 h). Output
+modules and AI tools are counted with their features in [target-app-parity.md](target-app-parity.md).
+
+## Shortcuts
+
+Compared `plan/lightroom/06-shortcuts.md` (desktop part) with our bindings: command specs in `crates/engine/src/cmd/`,
+`UI_COMMANDS` in `crates/ui-egui/src/menus.rs` and secondary bindings (`ALIASES`) in `crates/ui-egui/src/shortcuts.rs`.
+`no_conflicting_bindings` (same file) fails when one key fires two actions.
+
+**Fixed (M16.1):** added Lightroom-desktop keys as secondary bindings for existing commands — ⌘D Select None, ⇧E
+Export dialog, Space Toggle zoom, ⇧M Create Version, ⇧X Reject + advance, ⇧U Unflag + advance. `⇧Y` fired both
+Before/After Split and the History panel; History lost the binding. `W` and `⇧⌘I` also fired the engine command
+under the UI command that wraps it (a no-op error); the UI command now wins.
+
+**Deliberate differences (our key → Lightroom desktop key)** — each is a conflict with another binding we have:
+
+| Action | Ours | Lightroom desktop | Why |
+|---|---|---|---|
+| Pick flag | P | Z | Z = toggle zoom (Classic convention); P is the Classic pick key |
+| Photos panel (left) | ⌘⇧L | P | P = pick |
+| Expand/collapse edit sections | ⌘⌥1–5 | ⌘1–6 | ⌘0 = Zoom to Fit, ⌘1 = Zoom 100 %; Geometry lives in the Crop panel |
+| Histogram | ⌘⇧H | ⌘0 | ⌘0 = Zoom to Fit |
+| Square Grid | ⇧G | (none; ⇧G = Guided Upright) | Guided Upright is a button in the Crop panel |
+| Export dialog | ⌘⇧E (+ ⇧E) | ⇧E | ⌘⇧E is "Edit in Photoshop" there; no external-editor command yet |
+| Crop overlay cycle | ⇧O | O | O = mask overlay; ⇧O (mask colour / overlay orientation) unused otherwise |
+| Create Version | ⌘⇧S (+ ⇧M) | ⇧M (Windows: Ctrl+⇧S) | — |
+| Select None | ⌘⇧A (+ ⌘D) | ⌘D | — |
+
+**Library culling (KEYC-RATING):** `0–5`, `6–9` and `P/X/U` reach egui on macOS even when displayed in the native menu (adapted from PR #261; issue #283). `Shift+0–9` applies and advances once; `Shift+P` picks and advances in Photo Grid / Square Grid and retains Presets elsewhere. See [library shortcuts](library-shortcuts.md) for the verified Classic mapping and regression coverage. Remaining Classic gaps: rating `[` / `]`, flag cycling and filter-bar keys.
+
+**Still missing / broken:**
+- No command yet: F1 help (verify the rest of the old list: full screen, settings, stacks,
+ visualize spots and merges have commands now).
+- `H` opens Remove; Lightroom also uses it (Classic) to hide pins — pins toggle from View → Show Mask Pins.
+- ⌘M / ⌘H / ⌘Q / ⌘W rely on the platform window defaults (unverified).
+
+
+## Revision history
+
+| Date | Change | Summary |
+|---|---|---|
+| 2026-10-10 | major | Created: measured command and shortcut counts, assessment by area, estimates; moved the shortcut conflicts section here from the checklist |
diff --git a/docs/whats-new.md b/docs/whats-new.md
index 671556e63..dea4de583 100644
--- a/docs/whats-new.md
+++ b/docs/whats-new.md
@@ -1,6 +1,6 @@
# What's new in LightCraft
-## October 2026
+## 2026-10-10
### Previews match exports on Mac screens
- On macOS, photos are shown through the profile of the screen the window is on while no monitor profile is chosen in
@@ -17,6 +17,8 @@
- Quitting while a task that can be stopped is running asks first ("Quit Anyway" stops it).
- Agents list and stop tasks with `activity.list` and `activity.cancel`; `ui.inspect` reports them as `activity`.
+## 2026-10-09
+
### Smart album rules
- The rule editor's field menu is grouped: Rating, Pick Flag, Color Label and Any Searchable Text at the top, then
submenus for Source, File, Date, Keywords & People, Description, Camera Info, Location, Size, Develop and Assisted
@@ -135,6 +137,8 @@
next to it while you rotate (issue #534). For an exact angle, type it in the new Angle field under Straighten and press Return
(Esc keeps the old angle; a decimal comma works too), or click the Straighten value; hovering any slider's value now says it can be typed.
+## 2026-10-08
+
### Albums tree
- Album folders fold with the same disclosure triangle as Local, By Date, Folders and Keywords.
- Right-click a folder ▸ New ▸ Create Album… / Create Smart Album… / Create Smart Album from Filter… / Create Folder…
@@ -164,6 +168,60 @@
press the new keys (Esc cancels), × removes it, ↺ restores the original, Reset All undoes every change. A key that
belonged to another command moves to the new one. Menus show the new keys; agents use `app.setShortcut`.
+### Formats
+- HEIC / HEIF photos (iPhone and Mac) open now: the optional `lightcraft-heif` crate (heic-rs, pure Rust) behind
+ codecs' `heif` feature — 8- and 10-bit, alpha, grid tiles, the container's rotation/mirror/crop, ICC, EXIF and XMP.
+ Off by default (HEVC patents are the distributor's call, same as PhotoCraft); official builds pass `--features heif`.
+- HEIC colours now match libheif to within one code value (they were up to ~10 off): the HEVC stream's own
+ full-range/matrix signalling is honoured (iPhone photos are full range), chroma is upsampled like libheif, grids
+ take their tiles' ICC profile, and `imir` mirrors the way libheif writes it. Importing a HEIC reads its size from
+ the container instead of decoding it; small previews use the file's embedded thumbnail. The release packages
+ (macOS, Windows, Linux, FreeBSD, Nix) now really are built with HEIC support; a build without it reports
+ "HEIC/HEIF support isn't included in this build" for each `.heic`/`.heif` at import.
+
+### Editing
+- The Tint slider works the right way round (issues #188, #321): left adds green, right adds magenta, as its track
+ shows and as in Lightroom, and Tint values in Lightroom XMP sidecars now render as they do there. A custom Tint
+ saved in an earlier version now shifts the other way; set it again (or re-run Auto / the white-balance picker).
+- White balance on DNGs (and other raws with a colour matrix) re-develops the photo for the new white through the
+ camera's own colour matrices, as Lightroom does, instead of shifting the colours of the as-shot rendering: a grey
+ lit by the chosen white comes out grey and saturated colours move as the camera records them. The eyedropper and
+ Auto use the same model. Custom white balances on these photos render slightly differently than before.
+- Crop (issue #295): a Lock toggle keeps the aspect ratio on every handle, Custom takes your own ratio (Apply), and
+ dragging a handle into the image edge stops there instead of pushing the crop out of shape.
+
+### Library and views
+- Trackpads: pinch to zoom around the pointer and scroll with two fingers to pan the photo; panning keeps the photo
+ inside the view. A plain mouse wheel over a zoomed photo pans it too.
+- A Folders section in the sidebar lists the folders your photos were imported from; choose one to see its photos.
+- Select All and multi-selection show every selected photo in the grid and filmstrip, not only the active one
+ (issues #187, #298). Importing files that are in Recently Deleted asks whether to leave them there, restore them
+ (with their edits) or import them as new; the trash view's Photo menu has Empty Recently Deleted.
+- The Import Photos review opens bigger and can be resized; its photo grid fills it (issue #337). Shift-click checks
+ or unchecks a range of photos (issue #338).
+- When a folder holds several file types, the Import Photos review has a toggle per type (`ARW · 120`, `JPG · 120`):
+ import only the raws and leave the JPEGs beside them (issue #344).
+
+### Languages
+- The interface is available in Spanish (issue #371), German and Russian (Edit ▸ Language), alongside English,
+ Chinese (Simplified and Traditional), Japanese and Brazilian Portuguese.
+
+### Editing
+- Type an exact value into any slider (issue #322): click the number next to its name, type (`1.5`, `-20`, `5600`)
+ and press Return; Esc keeps the old value.
+
+### Editing
+- The eye on the Light, Color and Detail section headers now hides their adjustments, as it already did for Effects,
+ Optics, Geometry and Calibration (issue #316).
+
+### Library
+- Choosing a date under By Date or a keyword under Keywords shows those photos from All Photos, as their counts
+ promise, instead of filtering whatever album or folder was open, which often showed nothing (issue #341).
+)
+)
+
+## 2026-10-07
+
### RAW decoding
- Olympus ORF raws get the starting look fitted to the camera's own JPEG too, instead of opening flat and grey (the
E-1, E-400 and XZ-2 corpus files: all accepted), with white balance relative to the as-shot look as for the other
@@ -220,72 +278,7 @@
existing LightCraft edits are preserved by default. Source settings/history are archived, unsupported
fields are reported, and the original Lightroom database stays read-only. Rendering is approximate.
-### Formats
-- HEIC / HEIF photos (iPhone and Mac) open now: the optional `lightcraft-heif` crate (heic-rs, pure Rust) behind
- codecs' `heif` feature — 8- and 10-bit, alpha, grid tiles, the container's rotation/mirror/crop, ICC, EXIF and XMP.
- Off by default (HEVC patents are the distributor's call, same as PhotoCraft); official builds pass `--features heif`.
-- HEIC colours now match libheif to within one code value (they were up to ~10 off): the HEVC stream's own
- full-range/matrix signalling is honoured (iPhone photos are full range), chroma is upsampled like libheif, grids
- take their tiles' ICC profile, and `imir` mirrors the way libheif writes it. Importing a HEIC reads its size from
- the container instead of decoding it; small previews use the file's embedded thumbnail. The release packages
- (macOS, Windows, Linux, FreeBSD, Nix) now really are built with HEIC support; a build without it reports
- "HEIC/HEIF support isn't included in this build" for each `.heic`/`.heif` at import.
-
-### Presets and profiles
-- Import presets from other editors: XMP presets, classic `.lrtemplate` files, "DNG presets" from mobile apps and `.zip`
- bundles of any of these — whole folders at once, grouped by pack. Masks inside presets come along.
-- Luminar looks: `.lmp` files and `.mplumpack` collections import as presets (grouped by collection); the sliders
- with a counterpart here come along, the rest is listed.
-- 23 new built-in presets: Portrait, Landscape, Urban, Food, Seasons, Vintage and B&W toners.
-- Importing XMP presets no longer lists bookkeeping fields (`Cluster`, `SortName`, `SupportsAmount2`, the as-shot
- white, empty Point Color slots…) as settings that couldn't be carried over.
-- A preset whose lens-profile switch is off no longer turns off the lens corrections built into a DNG (iPhone ProRAW
- and other files with embedded distortion / vignetting corrections), matching what the preset does elsewhere.
-- XMP presets apply their red / green / blue curves only when they also have the master curve and all three channel
- curves, as Lightroom does: a preset with just one channel curve (or no master curve) leaves the channels alone.
-- Imported `.cube` LUT profiles appear in the Profile menu and the profile browser, grouped by their folder, and stay
- favourites across restarts (issue #328).
-
-### Editing
-- The Tint slider works the right way round (issues #188, #321): left adds green, right adds magenta, as its track
- shows and as in Lightroom, and Tint values in Lightroom XMP sidecars now render as they do there. A custom Tint
- saved in an earlier version now shifts the other way; set it again (or re-run Auto / the white-balance picker).
-- White balance on DNGs (and other raws with a colour matrix) re-develops the photo for the new white through the
- camera's own colour matrices, as Lightroom does, instead of shifting the colours of the as-shot rendering: a grey
- lit by the chosen white comes out grey and saturated colours move as the camera records them. The eyedropper and
- Auto use the same model. Custom white balances on these photos render slightly differently than before.
-- Crop (issue #295): a Lock toggle keeps the aspect ratio on every handle, Custom takes your own ratio (Apply), and
- dragging a handle into the image edge stops there instead of pushing the crop out of shape.
-
-### Library and views
-- Trackpads: pinch to zoom around the pointer and scroll with two fingers to pan the photo; panning keeps the photo
- inside the view. A plain mouse wheel over a zoomed photo pans it too.
-- A Folders section in the sidebar lists the folders your photos were imported from; choose one to see its photos.
-- Select All and multi-selection show every selected photo in the grid and filmstrip, not only the active one
- (issues #187, #298). Importing files that are in Recently Deleted asks whether to leave them there, restore them
- (with their edits) or import them as new; the trash view's Photo menu has Empty Recently Deleted.
-- The Import Photos review opens bigger and can be resized; its photo grid fills it (issue #337). Shift-click checks
- or unchecks a range of photos (issue #338).
-- When a folder holds several file types, the Import Photos review has a toggle per type (`ARW · 120`, `JPG · 120`):
- import only the raws and leave the JPEGs beside them (issue #344).
-
-### Languages
-- The interface is available in Spanish (issue #371), German and Russian (Edit ▸ Language), alongside English,
- Chinese (Simplified and Traditional), Japanese and Brazilian Portuguese.
-
-### Editing
-- Type an exact value into any slider (issue #322): click the number next to its name, type (`1.5`, `-20`, `5600`)
- and press Return; Esc keeps the old value.
-
-### Editing
-- The eye on the Light, Color and Detail section headers now hides their adjustments, as it already did for Effects,
- Optics, Geometry and Calibration (issue #316).
-
-### Library
-- Choosing a date under By Date or a keyword under Keywords shows those photos from All Photos, as their counts
- promise, instead of filtering whatever album or folder was open, which often showed nothing (issue #341).
-)
-)
+## 2026-10-05
### Reliability
- The Windows installer asks where to install LightCraft (Program Files by default; upgrades keep the folder you
@@ -383,6 +376,23 @@
library) are written atomically and saved as soon as you open another library, not only at quit. Quitting while
changes couldn't be saved tries once more, then asks: Try Saving Again, Quit Anyway or Cancel.
+## 2026-10-02
+
+### Presets and profiles
+- Import presets from other editors: XMP presets, classic `.lrtemplate` files, "DNG presets" from mobile apps and `.zip`
+ bundles of any of these — whole folders at once, grouped by pack. Masks inside presets come along.
+- Luminar looks: `.lmp` files and `.mplumpack` collections import as presets (grouped by collection); the sliders
+ with a counterpart here come along, the rest is listed.
+- 23 new built-in presets: Portrait, Landscape, Urban, Food, Seasons, Vintage and B&W toners.
+- Importing XMP presets no longer lists bookkeeping fields (`Cluster`, `SortName`, `SupportsAmount2`, the as-shot
+ white, empty Point Color slots…) as settings that couldn't be carried over.
+- A preset whose lens-profile switch is off no longer turns off the lens corrections built into a DNG (iPhone ProRAW
+ and other files with embedded distortion / vignetting corrections), matching what the preset does elsewhere.
+- XMP presets apply their red / green / blue curves only when they also have the master curve and all three channel
+ curves, as Lightroom does: a preset with just one channel curve (or no master curve) leaves the channels alone.
+- Imported `.cube` LUT profiles appear in the Profile menu and the profile browser, grouped by their folder, and stay
+ favourites across restarts (issue #328).
+
### Library
- Photos in Recently Deleted can be restored from the app: right-click ▸ Restore (or Delete Permanently), also in the
Photo menu. The filmstrip has the photo context menu too. Adding a file again that is in Recently Deleted no
diff --git a/docs/xmp-interop.md b/docs/xmp-interop.md
index 59ce22b3f..6d96390e8 100644
--- a/docs/xmp-interop.md
+++ b/docs/xmp-interop.md
@@ -75,7 +75,7 @@ have none. So saving one photo never overwrites the other's metadata (`Session::
LightCraft reads face, pet, focus and barcode regions in the Metadata Working Group's region schema
(`mwg-rs:Regions`, `http://www.metadataworkinggroup.com/schemas/regions/`), as Lightroom, digiKam, Picasa and others
write them, from sidecars and from XMP embedded in the file. Named faces become people in the People view
-(LR-LIB-PEOPLE in [`parity.md`](parity.md)), and every region is drawn as a box in the loupe.
+(LR-LIB-PEOPLE in [`parity-checklist.md`](parity-checklist.md)), and every region is drawn as a box in the loupe.
- **Read only.** LightCraft never writes `mwg-rs:Regions`; saving a sidecar keeps another application's regions byte
for byte. Removing or resizing a box in the loupe changes the library only.
diff --git a/packaging/linux/package.sh b/packaging/linux/package.sh
index 441cd69c6..b5e1d9457 100755
--- a/packaging/linux/package.sh
+++ b/packaging/linux/package.sh
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
-# Build and package LightCraft for Linux ( is x86_64 or aarch64):
+# Build and package LightCraft for Linux ( is x86_64 or aarch64, or riscv64 when cross-compiling):
#
# $DIST/lightcraft--linux-.AppImage any distro with glibc >= the build host's
# $DIST/lightcraft--linux-.AppImage.zsync delta updates (needs zsyncmake)
@@ -13,6 +13,8 @@
# (downloaded into $CARGO_TARGET_DIR if missing). Build on an old distro (CI: Ubuntu 22.04,
# glibc 2.35) so the binaries run on newer ones. Optional: desktop-file-validate, appstreamcli,
# zsyncmake (the zsync package) for the AppImage's .zsync.
+# Cross-compiling (CI: riscv64, tar only): set CROSS_ARCH, CROSS_TARGET (the Rust target),
+# CROSS_COMPILE (binutils prefix, for strip) and optionally EMULATOR (runs the CLI smoke test).
set -euo pipefail
# shellcheck source=../env.sh
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
@@ -25,13 +27,14 @@ while [ $# -gt 0 ]; do
case "$1" in
--skip-build) SKIP_BUILD=1; shift ;;
--formats) FORMATS="$2"; shift 2 ;;
- -h | --help) sed -n '2,15p' "$0"; exit 0 ;;
+ -h | --help) sed -n '2,17p' "$0"; exit 0 ;;
*) echo "unknown argument: $1" >&2; exit 2 ;;
esac
done
-ARCH="$(uname -m)"
+ARCH="${CROSS_ARCH:-$(uname -m)}"
case "$ARCH" in
+ riscv64) DEB_ARCH=riscv64 ;;
x86_64) DEB_ARCH=amd64 ;;
aarch64 | arm64) ARCH=aarch64; DEB_ARCH=arm64 ;;
*) echo "unsupported architecture $ARCH" >&2; exit 2 ;;
@@ -42,9 +45,10 @@ BASENAME="lightcraft-$VERSION-linux-$ARCH"
echo "==> LightCraft $VERSION for Linux $ARCH ($FORMATS)"
if [ "$SKIP_BUILD" = 0 ]; then
- (cd "$ROOT" && cargo build --release --locked -p lightcraft -p lightcraft-cli --features lightcraft/heif,lightcraft-cli/heif)
+ (cd "$ROOT" && cargo build --release --locked -p lightcraft -p lightcraft-cli --features lightcraft/heif,lightcraft-cli/heif \
+ ${CROSS_TARGET:+--target "$CROSS_TARGET"})
fi
-BIN="$CARGO_TARGET_DIR/release"
+BIN="$CARGO_TARGET_DIR/${CROSS_TARGET:+$CROSS_TARGET/}release"
WORK="$CARGO_TARGET_DIR/linux-package"
STAGE="$WORK/root"
rm -rf "$WORK"
@@ -52,7 +56,7 @@ rm -rf "$WORK"
# ---- stage an FHS tree (shared by every format) -------------------------------------------------
install -Dm755 "$BIN/lightcraft" "$STAGE/usr/bin/lightcraft"
install -Dm755 "$BIN/lightcraft-cli" "$STAGE/usr/bin/lightcraft-cli"
-strip "$STAGE/usr/bin/lightcraft" "$STAGE/usr/bin/lightcraft-cli" 2>/dev/null || true
+"${CROSS_COMPILE:-}strip" "$STAGE/usr/bin/lightcraft" "$STAGE/usr/bin/lightcraft-cli" 2>/dev/null || true
install -Dm644 "$HERE/$APP_ID.desktop" "$STAGE/usr/share/applications/$APP_ID.desktop"
install -Dm644 "$HERE/$APP_ID.mime.xml" "$STAGE/usr/share/mime/packages/$APP_ID.xml"
mkdir -p "$STAGE/usr/share/metainfo"
@@ -135,6 +139,10 @@ if has appimage; then
fi
fi
-"$STAGE/usr/bin/lightcraft-cli" --version
+if [ -z "${CROSS_TARGET:-}" ]; then
+ "$STAGE/usr/bin/lightcraft-cli" --version
+elif [ -n "${EMULATOR:-}" ]; then
+ "$EMULATOR" "$STAGE/usr/bin/lightcraft-cli" --version
+fi
echo "==> done"
ls -lh "$DIST"
diff --git a/xtask/src/layers.rs b/xtask/src/layers.rs
index 051b3aedf..4443cb230 100644
--- a/xtask/src/layers.rs
+++ b/xtask/src/layers.rs
@@ -90,7 +90,7 @@ fn intra_layer_allowed(from: &str, to: &str) -> bool {
/// External crates that constitute a UI toolkit / windowing dependency.
/// Entries ending in `*` are prefixes.
-pub const UI_CRATES: &[&str] = &["egui", "eframe", "winit", "egui_kittest", "rfd", "bevy*"];
+pub const UI_CRATES: &[&str] = &["craft-ui", "egui", "eframe", "winit", "egui_kittest", "rfd", "bevy*"];
/// First layer allowed to use UI crates.
pub const UI_MIN_LAYER: u8 = 5;
@@ -291,7 +291,7 @@ mod tests {
#[test]
fn ui_crates_forbidden_below_l5() {
- for dep in ["egui", "eframe", "winit", "rfd"] {
+ for dep in ["craft-ui", "egui", "eframe", "winit", "rfd"] {
let v = check(&[c("lightcraft-engine", &[(dep, Normal, false)])]);
assert!(matches!(v[..], [Violation::UiBelowL5 { .. }]), "{dep}");
}
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index b9aaa2256..cd226d6f2 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -27,7 +27,7 @@ commands:
pack square PNGs (<= 256 px) into a Windows .ico (see packaging/icons.sh)
layers enforce the crate dependency layering (plan/architecture.md §3)
parity [--write]
- check docs/parity.md (every cmd:/ctl: id and path it cites exists) and print the
+ check docs/parity-checklist.md (every cmd:/ctl: id and path it cites exists) and print the
Lightroom parity summary; --write refreshes the summary table in the document
wasm cargo check --target wasm32-unknown-unknown for the wasm-safe crates (+ the web app)
web [--serve [port]] [--dev]
diff --git a/xtask/src/parity.rs b/xtask/src/parity.rs
index bad6a5b1d..04bffe06e 100644
--- a/xtask/src/parity.rs
+++ b/xtask/src/parity.rs
@@ -1,4 +1,4 @@
-//! `cargo xtask parity`: the Lightroom parity tracker (`docs/parity.md`).
+//! `cargo xtask parity`: the Lightroom parity tracker (`docs/parity-checklist.md`).
//!
//! - **Reference check** (in `ci`): every `` `cmd:` `` must be a registered command (engine command specs, as
//! listed by `lightcraft-cli commands --json`, or a UI command from `UI_COMMANDS`), every `` `ctl:` `` a develop
@@ -10,7 +10,7 @@
use std::collections::{BTreeSet, HashSet};
use std::path::Path;
-pub const DOC: &str = "docs/parity.md";
+pub const DOC: &str = "docs/parity-checklist.md";
const BEGIN: &str = "";
const END: &str = "";