Pressing HDR no longer swaps the layout in one frame. The SDR curve squeezes
into the HDR layout's SDR section as the HDR section and legend open, and
Visualize HDR and HDR Headroom slide open with it (LR-EDIT-HDR-MODE); turning
HDR off runs it back. An SDR edit never shows an HDR histogram.
The flow lives in its own module, panels/edit/hdr_flow.rs, with small hooks in
histogram.rs and sliders.rs.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* LR-EXP-WATERMARK: decomposed Korean and Vietnamese export text draws whole (#3480)
Export text (contact-sheet captions, watermarks, slideshow overlays, print
page text) laid each char out as its own glyph. macOS often stores Korean
file names in NFD, so a caption for 제주도.jpg drew as loose jamo, and NFD
Vietnamese letters lost their stacked marks (#3277).
TextLayout::new now composes the text to NFC first (unicode-normalization,
MIT OR Apache-2.0). Every Hangul syllable and Vietnamese letter has a
composed form; sequences without one draw as before.
* LR-EXP-WATERMARK: CJK compatibility ideographs keep their code points in export text (#3480)
NFC maps each CJK compatibility ideograph (U+F900-U+FAFF, U+2F800-U+2FA1F)
to its unified form, so a name spelled with 塚 (U+FA10) would draw as 塚
(U+585A). Compose the text around them and keep those characters as they
are.
* LR-EXP-WATERMARK: export text already in NFC stays as it is (#3480)
A navigation link with ?bench opened the first photo of the library in
browser storage and committed Exposure edits to it, finishing at 0 instead
of the photo's own value. ?bench now implies ?store=memory and loads the
generated demo photos, so the benchmark cannot edit stored photos.
* 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>
Use the same exact-score known-crop preference as best_lens in the built-in fixed-lens fallback. Preserve higher-score priority and cover both profile orders through the public lookup API.
Validated with 33 lensdb and 5 engine lens-profile tests, plus full cargo xtask ci (8/8, Rust 1.95.0, Apple M5 Max Metal and WASM). The new tie regression fails against the previously published selector. No workflows or lockfile changes.
Co-authored-by: Codex <noreply@openai.com>
As in Lightroom Classic, a ⌘/Ctrl-click on a tile in Survey deselects that
photo (library.select, mode toggle), as the hover × does; a plain click still
only makes it active, and the last photo stays so the survey never empties.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge upstream main at 496524ced5 without rewriting published history. Preserve current upstream fixes and retain only the independent feature change in the resulting PR diff.
Co-authored-by: Codex <noreply@openai.com>
Mask coordinates lived on the corrected frame, so a Transform, Upright or lens-correction change
moved the photo under them and the masked look stayed behind, offset from the person it was drawn
on (4.3 px on a 240 px render under Vertical 25 + Rotate 4: about 100 px at 6000 px). Rotate Left /
Right turned the crop but not the masks.
A mask's coordinates are now on the photo before the warp (Mask::on_photo, catalog format 10):
the CPU render, the GPU render, Auto Mask brush windows and the on-photo tools map them through
the warp (Frame::for_mask, Warp::from_source). Rotate Left / Right turns masks with the photo
(mask_space::turn). The AI models see the photo without lens corrections, Upright, Transform,
crop or flips, so stored Object / Prompt selections stay on their content and changing those
doesn't ask the model again. Older masks (no on_photo) render where they were and move onto the
photo, without moving, at their next edit (mask_space::attach).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#3419 added a test that holds device memory until an allocation fails; on lavapipe the allocations
never fail, so it skips with the opt-in too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The check grepped for "no GPU adapter" only, so the ctx unit tests' "no
GPU device" skips (and nn's "no GPU runner here") went unseen. It now
lists every "need":"gpu" line except the expected device-loss one. On
the 2026-10-30 lavapipe logs it shows all 24 GPU skips without the
opt-in (the old grep showed 21) and nothing with it.
The log goes in a new mktemp -d directory instead of a fixed /tmp name
another local user could plant a symlink at.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/gpu-pipeline.md says how to run lightcraft-gpu's tests on a machine
without a GPU: LIGHTCRAFT_GPU_ALLOW_SOFTWARE=1 admits Mesa's lavapipe on
Linux and WARP on Windows (Metal on macOS needs nothing), and
LIGHTCRAFT_SKIP_LOG shows whether anything still skipped for want of an
adapter. The comments on the variable and in equivalence.rs no longer
say CI runs them: the platform CI legs of #1148 are not landing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`Gpu::new` refuses software adapters (llvmpipe / lavapipe, WARP): the CPU
pipeline is faster. CI runners have nothing else, so the CPU <-> GPU
equivalence tests have always skipped there.
`LIGHTCRAFT_GPU_ALLOW_SOFTWARE=1` admits them. The app never sets it and
the default is unchanged. On lavapipe all 134 compared renders stay within
max 1 LSB, mean <= 0.0003 LSB, the same as Metal on an M4 Pro.
The two big-source window tests used a 24 MP (288 MB) source. That is over
lavapipe's 128 MiB storage binding (the WebGPU default), so geometry ran on
the CPU and nothing was uploaded: one test failed and the other passed
without checking anything. They now use 3200 x 3200 (117 MiB): above
RETAIN_SOURCE_BYTES, so the source is still shared, and uploaded on every
adapter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>