* LR-PROF-IMPORT: import the user's own DNG camera profiles (.dcp)
profile.import (File > Import Profiles & Presets, drag & drop) now also
reads DNG Camera Profiles: the user's own .dcp files, e.g. made with a
colour-checker tool. They appear in the profile browser and menu under
Camera Profiles with dcp:<camera>-<file> ids; profiles.camera lists them
and profile.deleteCamera / profile.deleteImported remove them. A library
on disk keeps a copy in Profiles/. No profile ships with the app and the
repository holds none: tests build theirs in memory (write_dcp).
lightcraft_raw::dcp parses the file per DNG spec ch. 6 (magic RC in
place of 42; IFD 0 read through the existing DNG colour builders) and
DcpProfile::applied_to puts its matrices, illuminants and look in place
of a raw file's, keeping the shot's white balance, analog balance and
baseline exposure.
Choosing one (develop.profile) replaces the raw file's colour model when
it is decoded, before the starting colour and the white-balance scale
(WbScaleOf) are worked out, so the matrix path takes over from the fitted
JPEG look (CR3/CR2/ARW...) and white balance follows the profile. A
profile without a tone curve of its own (most chart-made ones) keeps the
photo's contrast: the DNG's tone curve, else the tone of the camera-JPEG
fit. The profile travels with SourceRef::File into FileLoader /
PairLoader (new Option<&DcpProfile> argument); a decoded source records
the profile it was made with and is not reused for another. Smart
previews and the web render worker keep the file's own colour; rendered
(non-raw) photos ignore it.
This reverses the documented "Adobe profile formats deliberately
unsupported" stance for user-supplied files only; docs updated.
Split out of #1012.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: build against upstream main (kelvin_presets, 3-arg loaders)
DcpProfile::applied_to keeps the file's white-balance presets (new
ColorData::kelvin_presets upstream): they are camera facts like as-shot
neutral. Upstream's new FileLoader test closures take the profile argument.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: hover previews and browser thumbnails develop the hovered camera profile
The loupe's hover preview (preview_job) and the profile browser's thumbnails (variant_job) took
the photo's own decoded source, so hovering an imported camera profile showed the built-in colour
(and hovering a built-in profile while one was applied kept the camera profile's). Both now decode
as the hovered settings develop: the source follows the settings' camera profile, as the main
render does, and a variant's cache key already follows its settings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: build against upstream main (face-tile test loader and render_bench take the camera profile)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CI: the catalog's staging-link test module is Unix-only (unused import broke clippy on Windows)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CI: same-folder export test expects the '/'-joined subfolder (failed on Windows)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CI: folder-drop tests build widget ids with the Folders tree's '/' paths (failed on Windows)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: rustfmt after merging main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: a real D750 camera profile as a pinned test corpus, checked against Lightroom
dieli's CC0 Nikon D750 ColorChecker set (Calibrite PROFILER dual-illuminant DCP,
matrix-only; the two chart NEFs; Lightroom Classic 15.6 renders with the profile)
is fetched by cargo xtask corpus --download into corpus/d750-colorchecker/, pinned
to commit f37f4054 and by sha256; nothing is committed.
Tests (skip without the corpus): the profile parses as matrix-only with
Cool White Fluorescent + D55; the matrices blend by inverse temperature; the
matrix-only path inverts the colour matrix and adapts to D50; it takes over the
chart NEFs' colour; imported and applied end to end, the charts develop within
mean dE2000 3.84 (flash) / 3.35 (indoor) of Lightroom's renders.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: a single .dcp file is a camera profile only, not also an unreadable LUT
profile.import read every plain file that was neither a folder nor a .zip as a .cube LUT, so importing
one .dcp imported the camera profile and also reported it as failed ("stream did not contain valid
UTF-8"). Only .cube files are read as LUTs now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: review fixes for imported camera profiles
- A library moved or restored elsewhere finds its camera profiles in its own Profiles/ folder.
- A grid thumbnail is cut from the decoded preview only when that preview used the same camera profile.
- Render and source cache keys carry a fingerprint of the profile file, so a deleted or replaced profile never
has its old renders stand in.
- Camera profiles can be favourites, and favourites/recent entries survive reopening the library.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: smart previews without the camera profile, current-profile source facts, unsupported files reported
CodeRabbit review of 4ef025d9:
- Build Smart Previews develops the proxy without the photo's imported camera profile and never takes a cached
source decoded with it (the denoised picture is kept); before, only the stale-proxy refresh left it out.
- Session::source_info reports decoder facts only from a cached source developed with the camera profile the
photo uses now, else the catalog's.
- profile.import reports a file that is neither .cube, .dcp nor .zip as failed instead of passing over it.
Each with a regression test in tests_dcp.rs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: the Before side decodes with its own camera profile; a failed profile copy fails that file only
- build_job (and region_job_before through it) picks the Before render's source with the Before
settings, so it never reuses or decodes a source developed with the edit's camera profile.
- profile.import: a .dcp whose copy into the library fails is reported in `failed`; the profiles
imported with it stay imported and are saved, and nothing half-written is left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: a cached smart preview stands in while the original is offline
A smart preview carries no camera profile, so with an imported profile the profile check
rejected the cached proxy and every render decoded it again (it gives the same picture). It
now stands in, for its own level and for the thumbnail cut from it, while the original is
known to be offline; otherwise the profile check is as before, so once the original is back
the photo is decoded from it with its profile. Photos without an imported profile keep main's
behaviour. Test `tests_dcp::a_cached_smart_preview_stands_in_while_the_original_is_offline`
(fails without the fix).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: deleting a camera profile removes the library's copy before forgetting it
profile.deleteCamera now removes the library's copy of the profile file
first. A copy that is already gone is fine; any other error (file in use,
no access) fails the command and keeps the profile listed, so the library
never holds a profile file it no longer lists. Test:
a_profile_whose_copy_cannot_be_deleted_stays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: deleting a camera profile never follows `..` out of the profiles folder
A listed path with a `..` component can start with the profiles folder and
still lead out of it; such a path is not treated as the library's copy, so
deleting the profile leaves that file alone. Test:
deleting_a_profile_never_follows_dot_dot_out_of_the_profiles_folder.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: both tone fits of a DNG see its lens opcodes; no side effect in the profile-tag comparison
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: a hover preview's key carries the camera profile file's fingerprint
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: unusable camera profiles not offered; file's WB scale and the profile's tone kept
Review of #1063 (CodeRabbit, outside-diff findings):
- a camera profile whose file is missing or damaged when the library opens
is no longer offered in the profile browser or accepted by develop.profile
(it silently developed with the raw's own colour); the entry stays, so it is
offered again once the file is back.
- a raw on the relative white-balance scale stays relative with a camera
profile (its matrices turned it to Kelvin, so existing edits were read as
absolute temperatures and the catalog and quick previews disagreed).
- with a camera profile its tone curve, then the tone kept for a curve-less
profile, come before the fitted camera look's tone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: one Camera Profiles group; the web loader keeps a raw's own colour
Review of #1063 (CodeRabbit):
- a LUT group named "Camera Profiles" that didn't sort last made the profile
menu list that group twice; it is now one group, last, with both kinds.
- without web workers a render ran inline and the web loader applied the
selected camera profile, unlike the worker (the documented web behaviour
keeps the raw's own colour); the loader now leaves it out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: camera profile files are read at most to the 64 MiB limit
A .dcp larger than the parser accepts was read whole into memory before
being refused. One bounded read (lightcraft_raw::dcp::read_dcp) stops one
byte past the limit and is used for import, reopening a library and the
duplicate check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: a test that a hostile hue/saturation map header in a camera profile drops the map (FredApps, #1063)
Dimensions far past the data, past u32 products or zero, an entry count of 4 billion values and a
data offset past the end each drop the map while the profile's matrix still reads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Docs: parity checklist keeps both sides of the merge
* LR-PROF-IMPORT: tests added on main take the file loader's camera-profile argument
* Merge main; a thumbnail test takes the file loader's camera-profile argument
* Docs: one camera colour row, with main's notes and the import note
* Process key tests take the profile salt argument
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
The film model, levels in density space, film presets, colour and tone of
Film Lab's core (same author, MIT), without the PhotoCraft-only sharpening
and JSON scanning, plus a whole-scan measurement (base, black and white
points, neutral balance) a host stores with a photo's settings. A library
with no caller yet.
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Conflicts only in the docs' "Last updated" header lines (main's #3503 added its entries at the front too): main's
line with this branch's entry kept in front. Cargo.lock: lightcraft-wincolor follows the workspace version to 0.18.0
(main's release bump).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvoftKWm1wYSXdzRBwvFR3
* CRW-LIB: decode Canon CRW through the shared craft-crw crate
LightCraft decodes Canon CRW (the CIFF raws of the EOS D30, D60, 10D, 300D and the
PowerShot G1/G6) through craft-libs' `craft-crw`, the clean-room decoder derived and
verified on the `clean-room-crw-format-decode` branch and moved there with its frozen
tables and derivation record. `crates/raw/src/vendor/crw.rs` is now the adapter: it maps
the crate's header and samples onto `RawImage` (CFA, levels, active area, orientation,
as-shot white balance, the make/model/time records a CRW keeps instead of Exif) and
converts the capture time through chrono. The dependency is a local path
(`../craft-libs/crates/crw`) until the craft-libs branch merges; then it becomes a git
revision.
Everything LightCraft-side from that branch comes along unchanged: `probe_bytes` fills
metadata gaps from the decoder's records so CRW imports name their camera; the EOS D60
bundled camera profile and its chart-measured dual-illuminant matrices (`chart_table.rs`,
`spectral::Camera` carries its illuminant pair) give the D60 a Kelvin white-balance scale;
CRW joins the JPEG-fitted starting look, the catalog's raw formats and `lightcraft-cli
calibrate`; `RENDER_CACHE_VERSION` 71; the CC0 CRW corpus entries for `cargo xtask corpus`;
coverage, parity, gaps, raw-parity, target-app-parity and ROADMAP updates. The G5 stays
preview-only (the crate refuses it with the reason) and the robustness test keeps its CRW
sample.
What does not come along: the decoder, its tables and `crw_oracle.rs` (now craft-crw's
digest-pinned sample test) and the derivation record, which lives in craft-libs
`crates/crw/clean-room/`. `clean-room/crw/` here keeps only the EOS D60 chart-calibration
document and its capture list, with a README pointing at the record.
Gates: fmt, clippy -D warnings (raw all targets; engine, catalog, cli), lightcraft-raw tests
(475 + 134, the corpus files decoding through the adapter), catalog tests, engine lib tests
(1,300 pass; the two failures, `tests_import::recursive_discovery_…` and
`originals::tests::write_targets_pin_absent_paths_…`, are in files unchanged from main),
`cargo xtask parity`, `assets`, `layers`, `coverage.py --check`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* no message
* no message
* Update documentation after moving code to craft-libs
* Review coment fixes
* CRW: pin craft-crw f3b62ec, a truncated entropy stream is Corrupt
craft-libs #472 makes craft_crw::decode refuse a stream that ends more than
Limits::max_stream_end_fill (64 samples, one block) before the frame does,
instead of filling the rest with predictor values and returning success.
The adapter already maps craft_crw::Error::Corrupt to RawError::Corrupt;
a regression test proves a truncated CRW now fails at this boundary (it
decoded as a flat frame on the old pin) while its header still reads.
Only craft-crw moves; craft-ui/docpath/update/window stay on c5a1cbf.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Code review comment fix
* Add some tests
---------
Co-authored-by: Robert Woldberg <314159+rwoldberg@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
main split the desktop app's main.rs and ui-egui's lib.rs into modules (#3103): the Windows
screen_profile service moves with services() to apps/lightcraft/src/session.rs, the field's doc
comment to crates/ui-egui/src/services.rs, and the LR-PREF-DISPLAY row takes main's
dialogs/settings.rs path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GfjNkJiMUagz3DN3zoV2rV
xtask parity reads the UI command tables from menus.rs and every file under menus/, and the desktop app's dialog-filter test checks every source file under src/, so both keep finding their items after files move into subdirectories.
Conflicts in docs/gaps.md and docs/parity-checklist.md: main rewrote both header lines and
moved the #2362 history row. Main's text is kept, with this branch's gap 22 and
LR-PREF-DISPLAY notes added and its history row placed among the 2026-10-20 rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TuX1qw9ieyuJ1ZFrrUxzc
Conflicts:
- apps/lightcraft/src/native_menu.rs: upstream's item swapping (macOS keys
given up for real, Help menu not swapped, Settings / Quit tracked) with
KEY-GRID's keymap-aware rows. `Item.label` stays the text the row shows
(native_item_presentation), so `Item::native_label` reuses it instead of
formatting it again; Settings / Quit store theirs through
display_item_label, as upstream's native_label did. No early return after
a rebuild (upstream).
- crates/ui-egui/src/i18n.rs: upstream's tests (#2729: one test per check
across languages, gaps reported, never a failure) plus the batch's two new
ones (dropped-photos notice, Polish counts); production code as merged.
- grid.rs, keyword_list.rs, masking.rs: the translation tests #2729 removed
stay removed; the batch's new keyword and segmenter tests stay.
- cmd/library.rs, ui-egui/src/lib.rs: both test modules, each with its
#[cfg(test)].
- xtask/src/main.rs: #2302's test_selected and upstream's translation_gaps.
- docs: tracker / gap rows take both sides' edits, headers list both sides'
changes, history rows of both sides newest-first. What's New: one section
per date, newest first (the batch had 10-22 above 10-23, and second 10-21
and 10-19 sections); no line lost.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A message a catalog lacks, or translates as an empty string, shows in
English and fails nothing:
- build.rs no longer requires every formats catalog to carry the same
messages, and tr_format! gets a last arm that formats the English
source, so a message no catalog has builds.
- Coverage tests (every language translates X) and tests of one
language's exact wording are gone; the language-switching, layout,
date, placeholder and user-value tests read the catalog's own value
instead of a hard-coded translation.
- i18n::tests::translation_gaps_report lists, per language, the plain
and format messages it lacks and its untranslated What's New lines.
It always passes; cargo xtask ci
runs it from the test step's binary after the tests.
- catalogs_load checks that every catalog file is a JSON object of
strings with each key once; it.json named "Dimensions" twice.
The merge kept both upstream's (#2710) and the batch's step that sends the
next request (or shuts the write side) during a slow reply, so request 2
went twice and the test waited on a reply nobody gave. Upstream's step
stays, before the sleep. Also drops a mention of `cargo xtask warm`, which
doesn't exist, from test_selected's doc.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts:
- xtask/src/main.rs: upstream's runners (cargo test or nextest, no
examples, doctests apart, one wasm `cargo check --keep-going`) with
#2302's `ci --quick` on top: test_selected builds the same targets
(--lib --bins --tests), the wasm check takes the changed crates in one
call, the full test step is upstream's.
- apps/lightcraft/src/control_server.rs: upstream's `before` closure, with
the batch's no-delay sockets kept.
- docs/parity-checklist.md: LR-BEHAV-ACCESS from main (tests/it path),
LR-BEHAV-LOCALE / LR-BEHAV-LOCALIZE from the batch (Polish).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CI: dev profile keeps line tables for workspace crates and no debug info for dependencies
* Control server tests: persistent_requests_and_slow_replies_outlive_the_idle_read_timeout no longer races the idle timeout
Each request (and the closing half-shutdown) reaches the socket before the
worker reads, so on a slow machine only the reply waits outlast the 100 ms
idle timeout, as the test intends. The idle test sends its input the same
way, and the no-frames test allows 5 s instead of 2 s to give up.
* CI: one integration-test binary per crate
Each crate's tests/*.rs become modules of tests/it/main.rs (51 test files
into 13 binaries, plus 6 that stay their own). Inner #![cfg] attributes
move to the mod lines. Files that change state for the whole process keep
a binary of their own: codecs jxl_tail_heap and raw sr2_heap (dhat global
allocator), gpu shutdown, shutdown_first and memory, ui-egui shutdown.
Docs, comments and the GPU probe name the new paths and targets
(--test it <module>::); the CI report names an it binary's skips after
the crate.
* CI: tests under nextest, doctests apart, one wasm check
xtask ci runs the tests with cargo nextest run --lib --bins --tests when
cargo-nextest is installed, else cargo test with the same targets and a
one-line notice; doctests run after them with cargo test --workspace --doc.
The heif and hardware GPU steps use the same runner. Examples stay out of
the test build (clippy --all-targets still checks them). Concurrent tests
under nextest follow the RUST_TEST_THREADS cap (NEXTEST_TEST_THREADS).
The wasm check is one cargo check --keep-going over the same crates, and
clippy runs with --keep-going.
* CI: tests run under cargo test by default, nextest with LIGHTCRAFT_NEXTEST=1 (per-process scene cache)
- fr-formats: "{n} photo{0} déplacée{0} vers « {1} »" (the adjective took a literal "(s)").
- docs/localization-parity.md: 7 languages in "Other languages LightCraft ships" (was 4);
Swedish and Polish ship, so they leave the "lacks" list and the target totals.
- xtask ci_lock (#2302): the lock-setup errors name the way out
(`LIGHTCRAFT_CI_LOCK=off` or `=<dir>`).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While no monitor profile is chosen in Settings > Display, Windows now shows
previews through the profile Windows Color System assigns to the monitor
showing the window (per-user or system), as macOS and Linux already do. It is
read about once a second, so moving the window to another monitor and changing
the profile in Windows are followed without a restart. While Windows
colour-manages the desktop itself (HDR, Auto Color Management), previews stay
sRGB.
The Win32 calls live in a new L0 helper crate, crates/wincolor, the second
crate allowed `unsafe` after crates/sysmem (SAFETY comments on every block,
safe API, Unknown on any failure); CLAUDE.md and AGENTS.md name it. GDI's
GetICMProfileW keeps the profile a process first saw, so WCS is read directly
and GetICMProfileW is only a fallback.
The profile-file loader moves from the Linux-only colord.rs to a shared
profile_file.rs (unchanged), with a cache that re-reads a file only when it
changes.
Verified on Windows 11 by setting and clearing a Display P3 profile and
toggling HDR while the app ran.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K93HTyCiwYvbRGaAkjPvXn
- ci.yml: cancel-in-progress only for pull requests, so a push to main never cancels the run of
the push before it.
- ci --quick: `git diff --name-status -z -M` and `git ls-files -z`, parsed as NUL-separated
fields (a rename or copy takes two paths), so a non-ASCII path, which git otherwise quotes and
escapes, still maps to its package.
- ci --quick: files outside a package that it compiles in (include_str!/include_bytes!, a build
script's rerun-if-changed: fonts, camera profiles and the app icon under assets/,
docs/whats-new.md, docs/raw/coverage-sweep.tsv, docs/raf-corpus.sha256, contributors/) count
as a change to that package. A test scans the workspace and fails when one is not mapped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`cargo test -p <changed>` resolves features for that selection only, so its artifacts differ from
the full `ci`'s workspace build (and from a worktree warmed from one): it recompiled ~130 crates,
dependencies included. Now `--quick` runs `cargo test --workspace --no-run` (fresh when the
workspace was built before) and executes the test binaries of the changed packages, with the
package directory as cwd like cargo does. Doc tests stay with the full `ci`. A one-file change
in lightcraft-raw in a warmed worktree: 170 s for the whole `ci --quick`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- .github/workflows/ci.yml: the full `cargo xtask ci` on every pull request and push to main
(Linux x64, craft-fonts, rust-cache for dependencies), so it becomes the merge gate.
- `cargo xtask ci --quick [--base REF]`: fmt, clippy, the changed packages' tests, layers and
assets, plus heif / parity / wasm when the changes touch them, measured against the merge base
with origin/main (xtask/src/ci_quick.rs). Workspace-wide files test everything.
- A machine-wide lock (xtask/src/ci_lock.rs): a second `ci`, from another worktree, waits and
names the run it waits for. LIGHTCRAFT_CI_SLOTS=N, LIGHTCRAFT_CI_LOCK=off|<dir>.
- README, CLAUDE.md quality gate, gaps.md gap 9, target-app-parity.md stability note.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stacked on rb/1592: LOOK_VERSION 8 -> 9; the PR's corpus-profile helper is the base's corpus_uses_the_bundled_profile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nikon bodies from before ~2016 record no CropArea, so the decoder kept
their whole sensor data, less trailing dark columns. Where that data
holds masked lines above black or optical black, they were developed as
a coloured or dark bar on that edge: the D7000 and D5100's 36 columns at
~600 (#976), the D800's 14 (#914, the top in portrait), the D600 / D610's
8 (#1236), the D3200's 46, and others.
MEASURED_BODIES (crates/raw/src/vendor/nef.rs) now gives 44 more model /
data size / depth groups the frame of their camera JPEG inside the data,
next to the D4, D7100 and D90 entries already there: D7000, D5100, D800,
D800E, Df, the D4's 12-bit mode, D3, D3S, D700, D300, D300S, D600, D610,
D810, D4S, D5600, Coolpix A, D3000, D40X, D60, D80, D2Xs, D200, D3200
and Nikon 1 V1; and, on best-guess frames short of the evidence rule,
each with a comment naming its evidence, the D3100 (3 of 4 clean files
agree), D5000 (4 of 5, and the D90's sensor), D40 (2 of 2), D50 and D70
(one file each, agreeing) and D70s (next to them, between pixels).
Every best-guess frame keeps the masked lines out by several pixels.
An entry matches model, data size and depth exactly. Bodies whose data
holds only picture beyond the camera frame get no entry and render as
before.
`cargo xtask raw-frame FILE...` (crates/engine/examples/raw_frame.rs)
measures a body: it aligns each raw's embedded camera JPEG with the
sensor data (half-size luminance, gradient correlation, sub-pixel peak;
weak, ambiguous or between-pixel peaks are flagged), counts the edge
lines that hold no picture and reports what the decoder's crop keeps
beyond the camera frame. A masked line is flat and its two CFA phases
agree within the noise or within 0.4% of white, so filler with a small
fixed offset between them (D90, D5000) counts. It prints entries only
with evidence: at least 3 clean files agreeing exactly, of the model
(12- and 14-bit pooled) or, with at least one of its own, with sibling
models of the same readout (data and JPEG size) that agree on the same
frame. On the D7500, which records its CropArea, it finds that area
exactly; it declines the Z f, whose JPEG is distortion-corrected. It
works for any make whose raw embeds a full-size camera JPEG: on Sony,
Canon CR2 / CR3, Pentax and Samsung files it finds the decoder's own
crop (relative to the active area).
Every Nikon raw on raw.pixls.us without CropArea was surveyed this way
(its 138 CC0 samples, plus 124 other publicly posted sample files; only
the measurements are committed). Bodies whose data holds only picture
beyond the frame need no entry (D750, D5300, D5500, D3300, D2X, D2Hs,
D3X, D5200, Coolpix P6000, Nikon 1 AW1 / J3 / V2 / V3, the D800's crop
modes); raw-parity.md lists them, and no longer claims every NEF
coding but HE decodes. measured_bodies_are_consistent checks every entry lies
inside its data and is listed once; measured_older_dx_bodies_image_area
covers the D200, D3200, Nikon 1 V1 and best-guess entries.
Fixes#976Fixes#914Fixes#1236
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>