Commit Graph
3164 Commits
Author SHA1 Message Date
dev-null-undefinedandClaude 33c362a7e4 LR-EDIT-OPTICS-PROFILE: Sony lens corrections verified on the ILCE-7RM6; docs and tracker
docs/sony-ilce7rm6-lens-corrections.md records the three Sony tables, the
measured conventions with their residuals, the conversion to
WarpRectilinear and its fit gate, the model allowlist and how to extend
it, the measurement method, the verification and the limits.

Verified locally on 54 ILCE-7RM6 photos with four lenses. The corrected
render lines up with the camera JPEG within 0.679 px at 1440 px at the
corners and edge midpoints of the 33 measurable photos shot with
in-camera correction on (up to 17.3 px uncorrected). The 13 controls show
the model's own shift. Every fit passes the 0.25 px gate (margin 0.075).
Lateral CA stays within 0.3 px, but applied after demosaic it raises the
render's corner CA (rms 0.032 -> 0.052 px); the doc and tracker say so.

Its "Two conventions" section sets these conventions next to the
ILCE-7RM4A's (vendor::arw_lens, docs/sony-lens-corrections.md, #498):
on the 34 measurable ILCE-7RM6 photos that formula gives 1.429 / 0.319 px
(max / rms) against 0.596 / 0.143, failing only on the 24-70 GM II and
fitting the 100 mm and 200-600 better; no single unit fits every lens
under either, and what would settle it (Sony On/Off exports of a wide
zoom, more lenses). docs/sony-lens-corrections.md gains a pointer to it.

Tracker rows LR-EDIT-OPTICS-PROFILE (one note for both bodies) and
LR-IMP-CAMERA-COVERAGE in docs/parity-checklist.md, gap 8 in
docs/gaps.md, the lens-profile section of docs/raw-parity.md (lens score
unchanged), whats-new (lossless ARWs now; Compressed / Compressed HQ once
the ARW 6.0 decoder, #625, lands) and the colour-fit follow-up note are
updated.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-13 13:09:40 +02:00
dev-null-undefinedandClaude d982a073c9 LR-EDIT-OPTICS-PROFILE: Sony embedded corrections on by default for ARW
An ILCE-7RM6 ARW with Sony's distortion table now imports with Enable
Lens Corrections on: Photo::camera_defaults already turns the correction
on for any file whose probe yields embedded lens data, and the ARW
decoder emits it since the previous commit. No new default rule is
needed, so RW2 and DNG keep theirs (on when the file carries a
correction) and stored settings are never rewritten.

Tests pin it: a synthetic ARW imports with the correction on (another
body's tables are not read: off), RW2 / DNG defaults are unchanged, and
an ARW catalogued without lens data or switched off by the user keeps
its settings across a library reopen. The Optics panel's hint drops
"(DNG lens opcodes only)" in every locale.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-13 13:09:40 +02:00
dev-null-undefinedandClaude b677fbac0a LR-EDIT-OPTICS-PROFILE: Sony ARW lateral CA correction from the embedded table
vendor::arw_lens_rm6::warp_opcode (was distortion_opcode) turns the CA table
(0x7035: red half, blue half, on the distortion knots) into the R and B
planes of the WarpRectilinear: a colour's source radius is
g (1 + c(g) / 3.36e6) at the green source radius g. The unit is provisional
(borderline measurement, about +-30 % scene bias). Each colour plane goes
through the same fit gate as G, and a table with a value beyond 2e-3 relative
(about 12 px at the full-frame corner; the 54 photos' tables stay below
5.1e-4, i.e. 1724) is refused; without a CA table, or when it is refused, the
warp keeps its single G plane, and CA alone is never applied. fit_radial_warp
now rounds its gate up to the next sample, capped at 1 (a NaN gates every
sample), so the band between the last sample inside the crop and the crop
corner is checked too (RW2 passes 1.0: unchanged).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-13 13:09:40 +02:00
dev-null-undefinedandClaude 85cc4273be LR-EDIT-OPTICS-PROFILE: Sony ARW distortion correction from the embedded table
New vendor::arw_lens_rm6 reads the raw IFD's lens tables (0x7037 distortion,
0x7032 vignetting, 0x7035 lateral CA, their flags) and turns the distortion
table into a one-plane WarpRectilinear in OpcodeList3, with the conventions
measured on 54 ILCE-7RM6 photos: knots at i/15 of the full-frame crop half
diagonal, S(r) = r (1 + v(r)/15000), frame-filling zoom, centred on the
default crop. Only the ILCE-7RM6 is enabled; vignetting is read, not applied.
The radial least-squares fit is split out as vendor::fit_radial_warp (RW2
results unchanged); its error gate can be limited to the radii the crop
shows, so a padded active area (lossless files) doesn't refuse a fit over
pixels that are never rendered.

It sits next to main's vendor::arw_lens (#498), which holds the ILCE-7RM4A
conventions measured against Sony's exports (unit 16384, table indexed at the
output radius); the two are selected by camera model (disjoint allowlists).
arw::decode chains this module's opcodes after arw_lens::distortion, with the
same active area and the same crop (the DNG-style default crop when the file
has one, which on the ILCE-7RM6 equals Sony's crop tags), and like it only
for CFA data (not the downsized linear-RGB lossless sizes, where the knot
spacing in full-frame pixels would not hold).

The corpus test checks the conversion on the two lossless RawDB files (CC0).
The APS-C one is pinned in xtask RAW_SAMPLES with its sha256; the full-frame
one is not (at most two pins per PR, and #625 pins two Compressed ARW 6.0
files), so its case runs only where the file is present.

RENDER_CACHE_VERSION 62 -> 63.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-13 13:09:40 +02:00
dev-null-undefinedandClaude 6c5b4d5701 UI: find gsettings on PATH for Appearance Mode ▸ Auto (NixOS has no /usr/bin/gsettings)
Without the XDG settings portal, the start-up fallback asks gsettings for
the GNOME colour scheme, but only looked in /usr/bin, /bin and
/usr/local/bin. NixOS keeps it in /run/current-system/sw/bin or the
per-user profile, so Auto never followed the desktop there. It now tries
the absolute PATH entries first, then those three; relative entries are
still skipped, so nothing runs from the working folder.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-13 13:07:36 +02:00
dev-null-undefinedandClaude dfe87240b0 Nix: evaluate the flake in CI so a new git dependency can't break nix build / nix develop again
Since UI.9 (9bfabc98) Cargo.lock has a git source, craft-ui from
storytold/craft-libs. importCargoLock needs a hash for every git
dependency, so evaluation stopped with "No hash was found while vendoring
the git dependency craft-ui-0.1.0" and both `nix build` and `nix develop`
failed on NixOS for two days (#1314 fixed it with
cargoLock.allowBuiltinFetchGit).

The flake is not built in CI, so nothing noticed: the new Nix workflow
runs `nix flake check --no-build --all-systems` whenever flake.nix,
flake.lock, nix/ or Cargo.lock change.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-13 13:07:36 +02:00
dev-null-undefinedandClaude e7cd00ef56 M5.6: a zoomed view with Dehaze keeps its airlight proxy (each pan and slider step resampled the whole original)
A windowed render dehazes with the whole frame's airlight, estimated on a 384 px proxy of the frame
(`local::frame_airlight`). Every window asked for that proxy again, so every pan of a 1:1 view and every
slider step while zoomed sampled the whole original down to it on the CPU: ~200 ms of CPU on a 64 MP raw.

The proxy (taken before white balance) is now kept for the last 4 (source fingerprint, frame, size); white
balance, the dark channel and the percentile still run on it every time. Same pixels: the GPU window of
`render_bench` is byte-identical.

`optics::fingerprint` indexed the first pixel of an empty image; it now hashes only the dimensions then.

render_bench ONLY=window GPU_ONLY=1 on a 64 MP Sony ILCE-7RM6 raw (RTX 4070 Ti SUPER, 4-CPU quota), CPU time:
pan 208-212 -> 9-11 ms, exposure change 200-204 -> 4 ms, NR change 211-217 -> 13 ms.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-13 13:07:34 +02:00
dev-null-undefinedandClaude 17a68863f6 LR-EDIT-OPTICS-PROFILE: share the radial warp fit between RW2 and Sony
Move the forward-map inversion, least-squares fit and solve4 out of the RW2
decoder into vendor::warp_from_forward_map; RW2 calls it in DNG-normalised units.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-13 13:07:20 +02:00
Matthew James SivakandClaude Opus 5.5 a2911a5086 LR-VIEW-ZOOM: drag-scale follow-ups from review (#330)
- A full set of slow drags forgets only the one remembered longest ago
  instead of all 256 (each forgotten pair costs one more slow full-size drag).
- DragScale's doc says a slow drag is remembered for its photo only.
- A host frame time over 1 s (a hidden, occluded or stalled window) counts
  as unknown, like no frame time at all, so it can't mark a drag slow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 12:56:50 +02:00
Atmik Shetty 21861c2f14 LR-EDIT-DETAIL-NR: noise reduction works in the original's pixels, so previews match the full-size render scaled down (fixes #960)
Grid thumbnails looked much more denoised than the loupe. `nr_params` sized
the radii from the output's scale relative to the source buffer it was given,
not the original: a 256-px cell drawn from the 512-px thumbnail level used
half the full-size radius on pixels that each average ~23 sensor pixels, and
below 0.4x (0.35x for colour) the radius stopped shrinking at all. The
guided filter's eps (the variance treated as noise) also stayed at its
full-size value although downscaling divides the noise variance by 1/scale²,
so texture in small previews was flattened as noise.

- `SourceInfo::native_long` (filled by the engine from the catalog's photo
  size, kept when the decoder supplies its own facts, carried to web
  workers) and `Plan::detail_scale` = output pixels per original pixel
  (from `px_per_long`, so a crop no longer changes it).
- `nr_params(s, scale)`: radii x scale with no floor, eps x scale². Below
  1 px the smallest blur the box Gaussian makes is blended in by the ratio
  of variances (`blur::box_sigma`); a fade under 1% skips the filter.
  At scale 1 the parameters are unchanged: 1:1 views and full-size exports
  render as before. The GPU takes the same parameters.
- RENDER_CACHE_VERSION 63: cached thumbnails are redrawn.

Measured on a 1536x1024 noisy synthetic scene (tests_nr_scale.rs), error
of the preview against the full-size render scaled down, as a fraction of
leaving NR off, luminance NR 50 at preview scales 0.5 / 0.33 / 0.25 /
0.167 / 0.1 / 0.05: before 0.53 / 0.70 / 1.04 / 2.26 / 3.87 / 6.07, after
0.28 / 0.37 / 0.66 / 0.90 / 0.99 / 1.00. A 256-px render from the 512-px
level now matches one from the original (mean 0.07 LSB vs 0.71 before).
2026-10-13 16:14:19 +05:30
Atmik Shetty 25e832b404 KEYC-COMPARE: = and - make the grid thumbnails larger and smaller
View ▸ Increase / Decrease Grid Thumbnail Size (view.thumbSizeUp / view.thumbSizeDown)
step the bottom-bar slider by 30 pt within its 90–480 range in Photo Grid, Square
Grid and People, as Lightroom Classic's Library Grid keys do; + also enlarges.
Detail, Compare, Survey and Reference ignore the keys, text fields keep them, and
⌘= / ⌘− still zoom the photo. The slider's range is now shared constants.
2026-10-13 16:11:47 +05:30
Atmik Shetty db69e288c8 KEYC-RATING: backtick cycles the flag; Cmd+Up / Cmd+Down raise / lower it
photo.cycleFlag steps each selected photo's own flag unflagged -> pick ->
reject -> unflagged; photo.increaseFlag / photo.decreaseFlag move it up or
down reject < unflagged < pick without wrapping. One batch undo step, Compare
and Survey act on the active photo, Auto Advance follows, text fields keep
the key. Shown in Photo > Set Flag and Help > Keyboard Shortcuts.
2026-10-13 16:11:45 +05:30
Markus FriedelandClaude Opus 5.5 8907f5f56f LR-VIEW-PHOTOGRID: a mouse-wheel notch scrolls a third of the grid or filmstrip (#1481)
One notch scrolls the photo grid by a third of its visible height and the
filmstrip by a third of its width, as in Lightroom Classic, instead of the fixed
100 points a side panel scrolls. It jumps at once instead of gliding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 12:38:21 +02:00
dev-null-undefinedandClaude Opus 5.5 6467d54325 Perf: camera-look colour step 1.5-1.9x faster on full-size raws, same pixels
The hue/saturation table of a fitted camera look (ILCE-7RM4A, APS-C ILCE-7RM6, ...) ran
HsvTable::apply pixel by pixel: branches on the data (largest channel, hue sector),
saturating float-to-usize casts and bounds-checked table reads, ~550 instructions and 1.2
branch misses per pixel. CameraColourModel::apply now runs it 16 pixels at a time
(HueSat::apply_lanes -> HsvTable::apply_lanes, structure of arrays): per pixel the same IEEE
operations in the same order, with the branches turned into selects and the table indices
computed as exact f32 integers, so the compiler vectorises everything but the table reads,
powf and the rare rem_euclid, which run lane by lane. DNG profile tables and the other colour
models keep the per-pixel path.

Measured on a Core Ultra 9 185H P-core (busy machine, min of N):
- HsvTable alone, 72x5x5 fitted-look-shaped table, random colours, one thread:
  243 -> 106 cycles (57 -> 26 ns) per pixel.
- Colour stage of a full-size load, one thread: 60 MP ILCE-7RM4A 3.80 -> 1.99 s,
  26 MP APS-C ILCE-7RM6 HQ 1.88 -> 0.98 s; 4 CPUs: 1.02 -> 0.67 s, 0.56 -> 0.30 s.

Exactness: new tests compare the lanes with the one-by-one path bit for bit (to_bits) on tens
of thousands of colours (every combination of zero, -0, tiny, negative, around 1, huge, NaN and
+-inf components; grey; hues at the 0/360 degree wrap; near-black noise in the restore's blend)
for fitted, value-scaled, 2D, many-turn and degenerate tables, 16 and 5 lanes, and on an image
whose rows aren't a whole number of lanes. Stage fingerprints (decode, AHD, 2560 px and
full-size loads) of 7 Sony raws are identical to origin/main.

Sources: own design.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 12:03:21 +02:00
radialmonster 2dad18d2ec LR-RAW-NEF: stretch non-square pixels (D1X/D1H) to square
The D1X/D1H raw SubIFD is 4028x1324 with XResolution 600 / YResolution 300:
the pixel is twice as tall as it is wide. Nothing read that, so the
picture came out about 3:1. Add a pixel aspect to RawImage/RawInfo, read
it from the raw IFD resolution ratio (NEF) and from DefaultScale (DNG),
accept only 1.5..4, and resample the rows after demosaic. developed_size()
reports the stretched size, binning is off for non-square pixels, and the
catalog and lens-profile sizing use developed_size().
2026-10-13 06:01:40 -04:00
draco892 bd0ca28bb0 build: fix host architecture detection in Windows PowerShell 5.1
Use Windows environment variables instead of RuntimeInformation
to avoid a null value during the final CLI smoke test.

Account for emulated shells using PROCESSOR_ARCHITEW6432.
2026-10-13 12:00:04 +02:00
Claude 951d52788a LR-MERGE-HDR: pixels next to clipped ones don't tint HDR merges
Measured against the darkest frame on real ARW brackets, each frame is linear to 1 % up to the clip far from
clipped pixels, but within 2 px of them the +2 EV frame's green reads 3–5 % low: demosaicing interpolates it from
clipped photosites. That frame carries the most weight (k = 4) and those pixels were unclipped themselves, so they
kept it, and bright windows took a magenta tinge.

A frame's weight now goes by its brightest channel within 1 px (3 × 3; wider margins changed nothing more). On two
real interior brackets (ILCE-7M3, 0 / −2 / +2 EV), bright pixels whose green against red and blue is over 10 % below
the darkest frame's, in camera RGB: dining room 0.32 % → 0.08 %, kitchen 2.86 % → 0.28 %. Rendered at −3 EV with
main's colour tags (#1123), window pixels with G < 0.85·min(R, B) go 50 → 3 and 16 → 10 (the darkest frame alone:
2 and 205); most of the visible magenta those brackets showed before came from the missing colour tags. Test: a
bracket whose +2 EV frame has 5 % less green next to its clipped pixels merges without the deficit (fails before:
−1.96 %).

Docs: docs/merge.md, the LR-MERGE-HDR row, docs/whats-new.md (also for the frame-order fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015gRtdWF1E9358FfzhzYqCS
2026-10-13 09:51:11 +00:00
dev-null-undefinedandClaude Opus 5.5 5bf455c456 LRC-LIB-CATALOG-IMPORT: an ARW6 is described from the whole file, not a partial read
header_plan skipped an ARW6's strip but its first 64 KB and then said Ready, while the ARW6
header probe (Mode::Header) validates every tile: its picture header, stream tables and the
first lines of its residual stream, which lie in the skipped bytes. The probe of the partly
read buffer then failed ("ARW6 first-half row count outside 2..=7") where the whole file
succeeds: sparse_corpus failed on arw6-sony-ilce7rm6-apsc-land-c.arw, the first ARW6 file it
reads (it stops at the first failure). Like NEF, ORF and PEF, whose header probes read
samples, an ARW6 now gets HeaderPlan::Whole from the header plan. The preview plan is
unchanged: the embedded preview reads no tile, and the 13 ARW previews in the corpus are
still read from 21 % of their bytes.

Only the test reads the header plan today (the app's partial reads use preview_plan), so no
import changes. New test: a synthetic two-tile ARW6 whose second tile lies past a first one
of bulk size (Ready before, Whole now). sparse_corpus passes on the 8 ILCE-7RM6 ARW6 files.

cargo test --workspace: 3205 passed, 1 failed, visualize::tests::
all_masks_overlay_shows_every_visible_mask_at_once, which fails on main b3d392b4 too
(PR #1456 addresses it); fmt, clippy, parity, layers, assets and wasm pass.

Sources: own design.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 11:45:40 +02:00
eliets2 0cd7513a8d LR-MERGE-HDR: an HDR merge of raws keeps the as-shot white balance of its inputs (#1144)
The HDR merge has the same cause as the panorama: its result took the
reference frame's colour tags, and a raw whose white balance is only in the
vendor's multipliers (CR2, NEF, ARW...) has no AsShotNeutral in them.

merge_hdr now takes the median multipliers of all its inputs (the rule of
consensus_multipliers: the mean of the middle two for an even count; missing,
non-finite and non-positive values do not vote) and sets the result's
AsShotNeutral to their reciprocal, as stitch does. With no multipliers (DNG
sources) the reference frame's tags are kept, as before. HdrResult carries the
multipliers so that the brackets of an HDR panorama vote as frames of their own
when the merged brackets are stitched (the engine passes them on).

In the app and the CLI the neutral from starting_colour_tags (#965) is already
there for frames balanced alike, so those renders do not change.

Tests: an HDR merge of raw-style frames gets the median AsShotNeutral (odd and
even counts, missing and unusable multipliers ignored) and the written DNG says
it; without multipliers the reference frame's colour is unchanged.
2026-10-13 12:23:36 +03:00
eliets2 25314aaacf LR-MERGE-PANO: a panorama of raws keeps the as-shot white balance of its frames (#1144)
A raw whose white balance is only in the vendor's multipliers (CR2, NEF, ARW...)
has no AsShotNeutral of its own. Loading a frame dropped the multipliers and
stitch copied the first frame's colour tags, so a panorama of frames that reach
the stitcher without a neutral wrote a DNG with neither AsShotNeutral nor
AsShotWhiteXY, and a viewer had no white to start from (a strong colour cast
until White Balance > Auto).

Frames now carry the multipliers (Frame::wb_multipliers, filled from the decoded
raw; load_frame_with is split so a decoded raw can be tested without a file).
stitch takes the median R/G and B/G of the multipliers of the frames that are in
the panorama (the mean of the middle two for an even count; frames without
multipliers, or with non-finite or non-positive ones, do not vote) and sets the
result's AsShotNeutral to their reciprocal, green = 1. The source's
AsShotWhiteXY is dropped with it: a reader takes it before the neutral and the
DNG spec makes the two exclusive. With no multipliers anywhere (DNG sources) the
first frame's tags are kept, as before.

In the app and the CLI a raw without colour matrices already gets a neutral from
starting_colour_tags (#965). For frames balanced alike the new neutral renders
the same (checked in the engine crate); it differs when the camera balanced the
frames differently: the panorama follows the median instead of the first frame.

Tests: the frame carries the multipliers; the consensus for odd and even counts,
with missing and unusable values; stitching raw-style frames gives the median
AsShotNeutral, ignores frames outside the panorama and leaves the colour as it
was without multipliers; the written DNG, read back with the raw reader, has the
reciprocal AsShotNeutral and the white balance the source's multipliers give.
2026-10-13 12:23:36 +03:00
Claude 5cb57ef4ea LR-PROF-CAMERACOLOR: Canon EOS R7 / R6 Mark II calibrate measurements
Held-out and library-wide calibrate runs on real CR3s (calibrate's CR3 /
CR2 input is #1073): profiles change little (R7 2.52 -> 2.57, R6 Mark II
6.12 -> 6.09 mean dE), so none is bundled; notes the CRX adaptive QP
172/175 files that stay preview-only. The catalog test pins that an EOS
R6 Mark II (Exif "Canon EOS R6m2") edits in Kelvin through its measured
matrices and an EOS R7 stays relative.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CzWyUn77ZWLe3q3qMiQbVt
2026-10-13 09:10:47 +00:00
cookingwithsimonandClaude Opus 5.5 d9cea1404a LR-IMP-PROFILES: presets apply the imported profile they name (#1001)
A Lightroom preset's crs:Look (name, group, UUID, amount; the Look table
in .lrtemplate files) is kept as profile.look and resolved against the
user's imported profiles by name, the group breaking ties. Lightroom's six
basic profiles map to ours; its own creative/camera profiles and profiles
not imported yet are reported per preset. References are resolved again
after profile.import / profile.deleteImported, so profiles and presets can
be imported in either order.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FxP2xEy623xzWGqCWXVwtb
2026-10-13 09:08:53 +00:00
draco892 eec4746a56 build: automatically include craft-fonts in desktop packages
Detect ../craft-fonts or use CRAFT_FONTS_DIR on macOS, Linux and Windows.
Download the pinned release revision when the checkout is missing
and require Chinese and Japanese fonts during the build.

Document font setup and preserve skip-build behavior.
2026-10-13 11:01:40 +02:00
PiMinusMaxandClaude Opus 5.5 0d13a7d5d7 M5.6: a full-size raw source is no longer copied once more after the colour model
`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>
2026-10-13 10:27:54 +02:00
witkowski01andClaude Opus 5.5 488edec6c8 i18n(pl): plural forms, the messages main gained, and paintable menu arrows
Fixes for the Polish catalogs of #1100, on top of a merge of main:

- English plural suffixes ("s"/"") and the "it"/"them" words in 51 format
  messages are now consumed with `{:.0}`, as docs/localization.md asks;
  Polish showed "Zaimportowano 5 zdjęćs" and "utracisz they are". The
  counts read "Zaimportowane zdjęcia: 5", which is right for every number
  (Polish has three plural forms; ru and uk use the same pattern).
- The 19 format messages main gained since the translation was written;
  without them the build fails.
- 118 plain messages the other catalogs already translate.
- "▸" and "▾" in translations become "→" and "⌄", as the other catalogs do
  (translated_catalogs_do_not_use_missing_menu_triangles).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:27:09 +02:00
PiMinusMaxandClaude Opus 5.5 b36b0d14b7 KEY-SLIDER-NUDGE: scrolling sideways over a slider adjusts it
A trackpad swipe, a tilting wheel or Shift+wheel over a Develop slider nudges it by the
wheel/arrow-key steps, without selecting it first (the right panel only scrolls vertically).
Only a mostly sideways movement counts, so scrolling the panel leaves the sliders alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:10:24 +02:00
witkowski01 5d981c2c68 Merge main into the Polish translation 2026-10-13 10:04:35 +02:00
radialmonster 4f51f051e7 LR-RAW-NEF: only take the NRW block's white balance where the as-shot kelvin is mirrored (P7000); P7100 gets none 2026-10-13 04:03:59 -04:00
dev-null-undefinedandClaude bc1f138ee3 Perf: ARW6 decode another 20-34 % faster, same samples
- The two halves of each stream are independent bit streams: they now decode in parallel
  (`decode_stream`) instead of one after the other inside each of a tile's 13 streams.
- The vertical inverse 5/3 runs its rows in parallel. The horizontal pass lifts each row in place
  (`lift_row`, scratch space reused per thread), rows in parallel, instead of allocating two vectors
  per row and copying the result back; a test checks it against the vertical pass, row by row.

Best of 3 on 4 CPUs, previous and new decoder alternating: ILCE-7RM6 FF HQ 0.60 → 0.48 s
(process CPU 1.14 → 0.87 s), FF Compressed 0.62 → 0.48 s, APS-C HQ 0.34 → 0.23 s, APS-C
Compressed 0.31 → 0.24 s (docs/arw6-compression.md → Timings). The decoded mosaic is identical
(hash of every sample) on the 8 ARW6 files of the RawDB ILCE-7RM6 set and 3 private A7R VI photos.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-13 10:00:33 +02:00
dev-null-undefinedandClaude Opus 5.5 79ab54e167 Perf: ARW6 decode 20-33 % faster, same samples
- The vertical inverse 5/3 lifts whole rows at once (`inverse_lift` with samples `w` values wide)
  instead of gathering each column through a strided iterator and scattering it back: the same
  integer arithmetic, sequential memory. The horizontal pass is the same function with w = 1.
- `0^n 1` codes are counted with one `leading_zeros` over the next MAX_DPT + 1 bits instead of bit by
  bit (same bits consumed, same errors); each line reserves its values up front.
- Colour reconstruction reads plane rows as slices and runs over rows in parallel (it was one
  thread per tile: 4 threads on a full-frame file whatever the machine).

Best of 5 on 4 CPUs, previous and new decoder alternating: ILCE-7RM6 FF HQ 0.56 → 0.46 s,
FF Compressed 0.54 → 0.38 s, APS-C HQ 0.34 → 0.23 s, APS-C Compressed 0.35 → 0.24 s
(docs/arw6-compression.md → Timings). The decoded mosaic is identical (hash of every sample) on
the 8 ARW6 corpus files and 2 private full-frame photos (ISO 100, ISO 12800); the pinned corpus
sums still match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:00:33 +02:00
dev-null-undefinedandClaude Opus 5.5 f407813569 Perf: AHD demosaic 1.7× faster and 0.8 GB lighter on a 64 MP raw, same pixels
Three changes, each leaving the output bit for bit as it was:
- Each step runs only where the tile's output depends on it (choice ±0, homogeneity ±1, Lab ±2,
  greens ±3, mosaic ±5 inside the 6 px margin), with direct neighbour indices instead of an
  index clamped on every read; the clamped values at the buffer edge never reached the output.
- A candidate pixel is built in registers before its Lab conversion: writing its channels
  through an index and reading the pixel back stalled on store forwarding (the hottest
  instructions of the profile).
- Tiles are written into the output one band at a time, instead of all 64 MP of tiles being
  collected and then copied (797 MB held twice). Tiles inside the image copy their mosaic rows
  instead of reflecting and taking a modulo per sample.

Sony ILCE-7RM6 9984 × 6656 Compressed HQ raw, demosaic only, best of 5 on 4 cores:
1.85 s → 1.05 s wall, 6.3 s → 4.0 s CPU. Verified identical (f32 bits) on 7 real raws
(ILCE-7RM6 FF/APS-C, ARW6 HQ/C and lossless, ILCE-7RM4A) and, against the previous code, on
random mosaics of all four Bayer layouts at 1 × 1 … 300 × 7, 257 × 130 (tile edges, reflection).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:00:33 +02:00
dev-null-undefinedandClaude Opus 5.5 5f2f013c8e Perf: a full-size raw decode no longer copies its 64 MP image to "fit" it
`load_bytes_now` ran every development through `resample::fit`, which returns a clone when the
image already fits. At full size (1:1 zoom, export) that cloned the whole picture: 797 MB and
~0.3 s for a 9984 × 6656 Sony ILCE-7RM6 raw. It now fits only when the image is larger, as the
other preview paths in files.rs already do. Output unchanged (same pixels, by construction).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:00:12 +02:00
radialmonster 967e57fa50 LR-RAW-NEF: read the as-shot white balance from the NRW data block when the maker note has no WB tag (P7000, P7100) 2026-10-13 03:58:09 -04:00
radialmonster fe9e6e3433 LR-RAW-NEF: size the NRW word packing by the row, not by the padded strip (P7000 stripes) 2026-10-13 03:58:09 -04:00
eliets2 be592cf2b1 LR-IMP-FORMATS: CI-enforce the unsupported CRX variants, listed in docs/cr3.md
docs/cr3.md gains a variant-status table: every supported and rejected CRX
coding variant, the function that gates it and the exact Unsupported text.

crates/raw/tests/cr3_variants.rs enforces the rejections (19 tests). Each
rejected variant is rebuilt as a small synthetic CR3 (container, CMP1
descriptor, CRX stream) and the test asserts the exact string, and that
probe_info (the import path) and decode return the same error. Control
fixtures decode through the same container, including both edges of the
accepted uniform quantization range (4..=44) and adaptive QP range
(131..=168), so a rejection can only come from the documented gate. The
table itself is prose and is not checked against the tests.

The doc said only QP values 131-167 were accepted; the decoder also accepts
168 (quantization value 44, step 102), so that sentence now says so.

Tests and docs only; no decoder change.
2026-10-13 10:56:29 +03:00
Atmik Shetty cb8b797cd4 KEY-SLIDER-RESET: a slider's name and thumb say a double-click resets it (fixes #1436)
A double-click on a slider's name or thumb already reset it, but nothing said so: only the value had a
tooltip. Resting the pointer on the name or on the thumb now shows "Exposure — double-click to reset
it", the wording (and translations) the curve channels already use. The rest of the track, which is
clicked and dragged, gets no tooltip, and the value keeps "Click to type a value".

The first click of a double-click on the thumb moved the value to where the pointer landed, a few
points off its centre: one history step, then the reset another. A click on the thumb now leaves the
value alone, so the reset is one undo step, as from the name. A disabled slider's name no longer
resets it.

No per-slider reset button: Lightroom has none, and the double-click it documents is now shown where
it works. Tests: tests_slider_reset.rs (name and thumb reset Exposure and Contrast in one undo step,
the hints, a click on the thumb keeps the value, typing a value still works) and
widgets/tests_slider_reset.rs (a disabled slider's name).
2026-10-13 13:16:08 +05:30
radialmonster e82b5089f7 LR-RAW-DNG: DNGs written by older LightCraft keep their sensor-anchored CFAPattern reading; the writer now marks its spec-anchored files 'LightCraft DNG 2' 2026-10-13 03:44:44 -04:00
eliets2 115b19bdcb LR-IMP-FORMATS: fuzz structurally valid CR3 containers in the robustness tests
crates/raw/tests/robust.rs only mutated DNGs. Add two small valid CR3
fixtures (a 4x4 lossless CRX and a 16x16 three-level C-RAW CRX) and a
property that flips bytes in them or truncates them, then runs the existing
exercise() helper (probe, embedded_preview, decode, develop and the colour
transform) plus probe_info, the header-only description used on import:
errors or clean decodes, never a panic. Unlike random TIFF-like bytes, a
structurally valid file gets past the container walk, so the mutations land
in the CRX marker walker and entropy readers. A fixture test asserts that
both files decode as built, so the mutations hit live decode paths.

Tests only; the existing DNG property keeps its 400 cases and the new one
uses proptest's default (256, PROPTEST_CASES overrides it).
2026-10-13 10:41:11 +03:00
Atmik Shetty 64ccc20853 KEY-HIDEPINS: Tool Overlay modes (Auto / Always / Selected / Never) for spot and mask pins, Shift+H cycles them (fixes #224)
The Remove tool's spot pins (Remove / Content-Aware / Heal / Clone) and the
mask pins now follow a Tool Overlay mode, saved with the UI state:

- Auto (default): shown while the pointer is over the photo (or a pin is
  dragged), hidden while a new spot or brush stroke is painted
- Always: all of them
- Selected: only the selected spot's or mask's
- Never: none; the tool still works and a click paints a new spot

A hidden spot can't be grabbed. Mask pins also need Show Pins
(view.maskPins), which keeps its meaning and reply.

Command view.toolOverlay {mode?: auto|always|selected|never} (no mode steps
to the next), Shift+H (H stays Remove), View > Tool Overlay with the modes
and the cycle item, and a Tool Overlay dropdown in the Remove and Masking
panels. Headless tests cover each mode with the pointer over and away from
the photo, grabbing, the key, the command, the menu, the dropdown, mask pins
with Show Pins, and the ui.json round trip.
2026-10-13 13:10:57 +05:30
eliets2 f4b7175429 LR-IMP-FORMATS: property tests for the CRX decoder through the public decode path
crates/raw/tests/crx_properties.rs drives lightcraft_raw::decode with
synthetic CR3 containers built around CRX streams that the test encodes
itself: reference entropy coders for the MED, Rice, MELCODE and QP-map
layouts, and the exact algebraic inverse of one 5/3 lifting line. The
tile-halo coefficients are free parameters of that inverse, so synthesis
must reconstruct any encoded plane exactly. Tests only; no decoder change.

- Wavelet round trip: random planes decode exactly across odd plane sizes
  and multi-tile halo configurations (eight geometries, arbitrary halo
  values).
- Quantization: every uniform quant value 4..=44 and every adaptive QP value
  131..=168 dequantizes within the 14-bit range. Overshoot is clamped, and
  an in-range plane lands exactly on level * step, so the step sequence
  itself is observable. Quant 3 and 45 and QP 130 and 169 are rejected by
  decode and probe_info alike. Random QP maps, including odd map heights,
  with random per-band base and gain stay in range.
- Lossless samples outside the bit depth are Corrupt errors instead of being
  clamped; in-range planes decode exactly.
- Adversarial entropy payloads planted in well-formed lossless, C-RAW,
  version 0x200 and QP-map streams either error or decode deterministically
  and never panic. A QP Rice prefix longer than the 23-zero escape is
  Corrupt.
- Random IAD1 geometry either decodes with the active area inside the
  sensor and the crop inside the active area, or is rejected.
- Decoding gives identical results across repeated and four-way parallel
  invocations.
- Odd QP-map heights decode: the last QP row has no partner and is used
  alone, and QP values are averaged before the step lookup. Large QP jumps
  take the escape code.

The case budget is proptest's default (256 per property); set
PROPTEST_CASES to change it.
2026-10-13 10:40:45 +03:00
wstockandClaude Fable 5.1 5e9a1b6ec7 Visualize: the all-masks overlay shows the union of the masks, not the last one
`Overlay::AllMasks` painted each mask's plane in turn, and every mask view
rewrites the whole image from its alpha (White on Black writes `255·a`,
Image on Black multiplies by `a`…), so each plane wiped the ones before it
and only the last visible mask showed. The planes are now merged (per-pixel
max) and painted once; the GPU path goes through the same `apply`.

The test added with #919 could not pass: besides the above, its hidden-mask
check expected the plain pixel under White on Black, where everything
outside the masks is black by definition (as the single-mask test asserts).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NH3itd85jPuM6wNjgcGfEH
2026-10-13 07:26:18 +00:00
wstockandClaude Fable 5.1 e935ad75e2 LR-VIEW-COMPARE: ⌘/Ctrl- and Shift-clicks on the filmstrip select in Compare
In Compare a filmstrip click goes to `compare.set` (#747), so the grid's
modifier clicks never reached `library.select` and multi-select from the
filmstrip was impossible there (#187 follow-up). A ⌘/Ctrl-click now toggles
the photo and a Shift-click extends from the active photo, as in the grid;
the two photos shown stay selected and the active photo is unchanged, so a
rating key still acts on a photo that is on screen. A plain click still
replaces the active side.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NH3itd85jPuM6wNjgcGfEH
2026-10-13 07:22:42 +00:00
s17sh70x2jfl031agandClaude Opus 5.5 094686a2f6 i18n(pt-br): translate AI Denoise
The AI Denoise panel, model download and settings were still in English
in Brazilian Portuguese; this translates its 98 strings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:11:58 -07:00
Marco Arnulfo 0d8e3e3913 LR-IMP-ADD-DIALOG: an import ends showing its first photo, in All Photos when the view doesn't hold it, unless another photo was chosen meanwhile (fixes #1446) 2026-10-13 09:04:10 +02:00
Marco Arnulfo c10ca4e958 LR-IMP-PREVIEW-READ: a raw whose embedded preview can't be read still gets a thumbnail (fixes #1448)
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.
2026-10-13 09:01:48 +02:00
Matthew James SivakandClaude Opus 5.5 aeb21ac813 UI: align Local folder rows, paint dock gaps, bottom-align the tool strip
- Local folders in the sidebar are drawn at their tree indent like Albums and
  Folders: the icon lines up with the rows above and the disclosure triangle
  sits in the gutter left of it; children nest by 16 pt (#1097).
- The central panel that hosts the dock is filled with chrome, so the gaps the
  dock leaves between panes no longer show the dark clear colour (#1098).
- The tool strip's bottom group is two buttons and one gap tall, so Info ends at
  the bottom margin as Presets starts at the top one (#1099).

Each fix has a headless regression test that fails without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:00:28 +02:00
smposaraandClaude Opus 5.5 26df1c5cdf LR-IMP-CAMERA-COVERAGE: verify Canon EOS RP CR3 pixel for pixel
Four CC0 EOS RP samples from raw.pixls.us (lossless RAW and C-RAW, full
frame and 1.6x crop mode) now have exact full-sensor regressions against
the DNGLab 0.8.0 black-box oracle (Windows binary, checked first against
the documented M50 RAW hash). The decoder already matched; no decoder
change was needed. Crop mode is a single 4032x2656 tile, full frame two
3192-wide tiles.

docs/cr3.md also records that the white level is Canon's specular white,
which at the -1/3-stop ISOs (e.g. 11062 at ISO 5000) is well below the
sensor's saturation point.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 08:59:30 +02:00
OneSlipsandClaude Opus 5.5 bd50896ca1 LR-MASK-EFFECTS: merge upstream main into pr/mask-effects
Conflicts: the 9 locale *-formats.json catalogs (both sides appended
messages at the end; kept both) and docs/parity-checklist.md (header
change list, regenerated summary via `cargo xtask parity --write`,
revision history rows from both sides).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:54:08 +10:00
s17sh70x2jfl031ag b73c0599fa i18n(pt-br): fix plurals and 6 inconsistent strings
Many pt-BR format strings dropped the English plural argument with
{:.0}, so nouns never became plural ("Exportada 5 foto"). Also fixes
the gender of "matiz" and two terms that disagreed with the catalog.
2026-10-12 23:41:18 -07:00
Atmik Shetty 63f68f5044 Merge remote-tracking branch 'upstream/main' into feat/print-color
# Conflicts:
#	crates/engine/src/lib.rs
2026-10-13 12:08:59 +05:30