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>
Task: LRC-LIB-CATALOG-IMPORT
Count offline records, Local promotions and virtual copies correctly. Keep destination, warnings and errors in a persistent import result, with Show in Library clearing filters. Preserve library-switch ownership, reimport identities, existing edits and one-step undo.
Avoid repeated missing-original probes and unnecessary staged-photo, catalog-operation and develop-patch copies. Preserve upstream phased progress and typed cancellation. Address Astra review findings with regression coverage.
Validated the combined main tree with all eight cargo xtask ci gates (3,462 passing test executions; zero failures), fresh 30k import/persistence/metadata checks, duplicate-free same-session and reopened reimports, and visually inspected headless screenshots. Historical performance comparisons apply to the f730805d production changes before main integration; native Windows import/GUI acceptance remains unqualified.
Generated with Codex.
Setting up Synchronize Folder, an import review or an import made a table
of every photo in the library on the session's thread before the first
file was looked at, and naming one photo's sidecar worked out who owns
every shared sidecar name in the library. In a library of 3.5 million
photos (`scan_setup_bench`, release, a folder of 500): 5.8 s before a
Synchronize scan, 2.6 s before an import review, 1.7 s before an import,
2.4 s for each sidecar written.
- A scan keeps the library as it is (photos are shared, not copied) and
looks up the files it finds (`library_files`) and the contents it probes
(`library_hashes`) on its own thread.
- An import looks the library up with its first files
(`ImportJob::look_up_library`), from the library and the options as they
were when it was set up.
- The owners of shared sidecar names are worked out for the photos asked
about (`StemOwners::among`): all five callers name only those photos.
- Synchronize Folder names its photos' sidecars in the scan, after it has
seen that the folder is there.
Now 0.9 s, 0.05 s, 0.05 s and under 1 s. What is found, and how sidecars
are named, is unchanged: the tests hold the owners against the table of
the whole library as it was made before, and the key made without
allocating against the key, for every string of up to six of the
characters that decide it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`embedded_preview_srgb` (the loupe stand-in and the grid thumbnail of an unedited raw) decoded the
largest embedded JPEG whatever the size asked for: a 512 px thumbnail of a Sony A7R VI ARW decoded
its 9984 x 6656 JPEG (5.6 MB) although the file holds a 1616 x 1080 one (0.3 MB).
`lightcraft_raw::embedded_preview_covering(bytes, min_edge)` returns the smallest of a TIFF-based
raw's JPEGs whose long edge is at least `min_edge` and that is a copy of the largest one: aspect
ratio within 1 % (not a letterboxed thumbnail of another shape) and the same number of components
(not the grey semantic masks of an iPhone ProRAW DNG); else the largest, as `embedded_preview`.
The colour-space handling stays in `decode_raw_preview` (a `covering` argument); an unsupported
raw's editing source and the camera-look fit keep the largest JPEG, so a source never depends on
its size level.
On 120 raws of every make in the corpus at 256 / 512 / 1600 px: wherever the largest JPEG is still
chosen the output is identical byte for byte; where a smaller one is (Sony, Nikon, Panasonic,
Adobe DNG) orientation matches, the size is equal or 1-2 px off, mean difference <= 4.7 / 255.
- Rebased onto #1020: its plan route (`preview_window` -> `planned_preview`) now takes the box size and picks
with `embedded_preview_covering` too, so a TIFF raw read through the sparse plan gets the whole file's JPEG.
Co-Authored-By: Claude <noreply@anthropic.com>
`fit` to the source's own size copies the whole picture (`resize` returns a clone). The raw path
called it unconditionally, so every full-size decode (an export, and its denoised twin) made a
second float RGB buffer: 288 MB and 160 ms at 24 MP, ~390 MB and 225 ms at 33 MP. The JPEG and
preview paths in the same file already fit only when the picture is larger than `max_edge`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The grid queues no render behind an unedited raw's quick thumbnail, since the embedded JPEG is
meant to be the thumbnail. But whether that JPEG can be read is only known when the quick job
runs, and `quick_thumb_job` had no fallback: when the preview loader returned nothing the job
failed with "no quick preview", the failure was remembered for that key, and the cell stayed
empty until an edit changed it. The quick job now carries the real thumbnail job as its last
resort, as `quick_view_job` already does for the loupe.
An unedited raw's face tile is cut from its largest embedded preview, read
the planned way (header and JPEG, never the sensor data), when the preview
holds at least 3/4 of the tile's pixels across the face; otherwise, and for
edited photos and other files, it is developed as before. Tiles keep going
to the memory + on-disk thumbnail cache, now at 16 px size steps so the
thumbnail slider reuses them. The planned preview path carries an Adobe RGB
preview colour space and applies it instead of declining to the whole-file
read (Sony alpha 1 previews).
As in PhotoCraft: when portable.txt (or LightCraft.portable) sits next to the
executable, config_dir() is <exe dir>/LightCraftData, so settings, logs and
models stay with a portable copy instead of %APPDATA%. LIGHTCRAFT_CONFIG_DIR
still wins and is used without probing beside the executable. A portable
folder that can't be written logs a warning and the platform folder is used.
The check runs once per process.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: crates/engine/src/tests_merge.rs (#1123's test landed where this branch's is; both kept),
docs/parity-checklist.md (the LR-MERGE-HDR row: both notes) and docs/whats-new.md (both entries).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015gRtdWF1E9358FfzhzYqCS