Commit Graph
32 Commits
Author SHA1 Message Date
Jason Baldwin 43f31532a7 LRC-LIB-CATALOG-IMPORT: Open Library recognises a Lightroom catalog and offers to import it (#891)
Open Library given a Lightroom Classic catalog folder, one of its .lrdata /
.lrcat-data folders, Backups/, the .lrcat or its -wal/-shm/.lock files used to
create an empty LightCraft library inside Lightroom's folder (or, for the
.lrcat file, close the current library and then fail). Now:

- engine: lightroom_folder::lightroom_catalog_at (names only, top-level scan
  bounded to 10 000 entries, never fails); Session::open_library refuses those
  places with EngineError::LightroomCatalog {path, catalog} before creating
  anything. A LightCraft library (catalog.snap / catalog.log) always wins.
- app: app.openLibrary checks before closing the current library, keeps it,
  returns {opened: false, lightroomCatalog, path} and shows a dialog with
  Import Lightroom Catalog… (file.importLightroom with the catalog found) and
  Cancel. The library problem window gets the error instead.
- Lightroom import status keeps the catalog path after the task ends.
- CLI --library explains how to import instead.
2026-10-11 08:40:18 +02:00
Jason Baldwin a3c33658bb Merge origin/main into brush-hold-hardens (clean merge; tracker edit followed the parity-checklist.md rename) 2026-10-10 21:49:33 +02:00
Jason Baldwin 0fdf6c5785 Merge remote-tracking branch 'origin/main' into brush-live-preview-gaps 2026-10-10 21:44:40 +02:00
Jason Baldwin 78c5b4d099 Merge origin/main into crop-angle-constrained 2026-10-10 21:39:04 +02:00
Jason Baldwin 62dacf2791 Merge origin/main into crop-rotation-artefact 2026-10-10 21:37:05 +02:00
Jason Baldwin 7a232ada8b Merge remote-tracking branch 'origin/main' into nef-probe-header-only 2026-10-10 21:37:02 +02:00
Jason Baldwin 7765255ec6 Merge follow-up: whats-new entry under 2026-10-10 2026-10-10 21:36:32 +02:00
Jason Baldwin de6b33c662 Merge remote-tracking branch 'origin/main' into reimport-after-delete 2026-10-10 21:33:45 +02:00
Jason Baldwin e7076762c0 Merge remote-tracking branch 'origin/main' into sony-arw-washed-out 2026-10-10 21:31:53 +02:00
Jason Baldwin be4cbebbd3 LR-PREF-PEOPLE: restore 'faces' typo in the tracker row after the merge 2026-10-10 21:27:27 +02:00
Jason Baldwin 2c2f28952f Merge origin/main into menu-scroll-clips-shortcuts (docs restructure; no conflicts) 2026-10-10 21:18:32 +02:00
Jason Baldwin 85b6fdaedd Merge remote-tracking branch 'origin/main' into faces-toggle-needs-a-model 2026-10-10 21:18:31 +02:00
Jason Baldwin 4dd6fb5089 Merge origin/main into uppercase-extensions (whats-new: keep main's entries, re-add the Formats bullet under 2026-10-10) 2026-10-10 21:16:49 +02:00
Jason Baldwin dd73753723 Merge origin/main into info-dialogs-close (no conflicts; MENU-HELP-SYSINFO note now in docs/parity-checklist.md) 2026-10-10 21:16:30 +02:00
Jason Baldwin 179941f4ff Merge origin/main into dialogs-fit-the-window (re-applied LR-BEHAV-DIALOGS row and LR-EXP-DIALOG note onto docs/parity-checklist.md, regenerated summary) 2026-10-10 21:15:37 +02:00
Jason Baldwin 1064e9847d Merge origin/main into enter-applies-copy-paste (parity rows applied to docs/parity-checklist.md) 2026-10-10 21:07:39 +02:00
Jason Baldwin bf49935fe7 LR-BEHAV-DIALOGS: dialogs fit the window; options scroll, title and buttons stay visible (#781)
Every dialog but Import draws its options in a vertical scroll area capped at the
window height minus the title bar, frame and button row (measured on the previous
frame), so on a 1366x768 screen the Export dialog's title and Cancel / OK stay on
screen. Dialogs that fit keep their size. Lists that scroll on their own (What's
New, All Metadata, shortcuts, smart-album rules, credits) get shorter first rather
than scrolling inside a scrolling dialog. Import keeps its resizable window.
2026-10-10 18:57:37 +02:00
Jason Baldwin 19833e56e3 MENU-HELP-SYSINFO: System Info, All Metadata and What's New end with one Close button (#782)
Their confirm does nothing, so they join About, Shortcuts and Settings in the
footer's informational list: one Close, no Cancel / OK. System Info keeps Copy
to Clipboard; Esc still closes them.
2026-10-10 18:50:12 +02:00
Jason Baldwin 61df5d304f LR-MASK-BRUSH: holding the brush still adds no coverage; flow builds up by passes (#518)
A stationary hold recorded one pointer sample per frame and each sample laid
another dab at the same spot, so flow accumulated with hold time and the
feathered edge hardened. brush_dabs now lays no dab for a sample that hasn't
moved from the last one, and the canvas no longer records repeated samples at
the same position. Moving strokes and passes back over an area are unchanged.
2026-10-10 17:21:23 +02:00
Jason Baldwin 923b1cde5e LR-MASK-BRUSH: the live brush stroke places dabs like the committed one, no gaps on fast strokes (#517)
While the button was held the Detail view drew one hard tinted circle per
pointer sample, so a fast stroke showed separate dabs; the committed stroke
interpolates dabs a quarter radius apart with feather and flow. The live
stroke now uses the pipeline's dab placement (push_dabs, shared with
brush_dabs), dab falloff and flow accumulation in an incremental screen
buffer (crates/ui-egui/src/brush_live.rs): each frame stamps only the new
samples' dabs and uploads only the rectangle they touched. push_dabs also
caps the dabs of a hostile segment instead of looping without bound.
2026-10-10 17:21:22 +02:00
Jason Baldwin bbcbbebf8e LR-EDIT-COPY-PASTE: Enter applies the Copy and Paste Settings dialogs (#735)
Return presses OK in Choose Edit Settings to Copy and Paste Selected
Settings when no widget has the keyboard focus (a Tab-focused button or
checkbox takes Return itself, as egui does). Esc already cancelled them;
tests now cover both keys.
2026-10-10 16:49:39 +02:00
Jason Baldwin b8f868830b LR-CROP-STRAIGHTEN: every way of setting the crop angle keeps the crop inside the photo (#742)
develop.set / develop.adjust / develop.quickAdjust / develop.resetControl on crop.angle
(MCP set_develop, the control channel, lightcraft-cli render --set) and crop.set {angle}
now turn the crop as crop.straighten does, through the new CropGeometry::with_angle:
kept if it still fits, else the largest crop of its aspect inside the rotated photo.
Before, they left the full rect, and the export took in smeared edge pixels in the corners.
2026-10-10 16:01:25 +02:00
Jason Baldwin 60844cf162 LR-EDIT-CROP: no artefact inside the crop overlay when the crop is rotated (#731)
The dimming outside the crop box was four quads, each joining a side of the
photo to the matching side of the box. Aligned, that is the photo minus the
box; rotated, the quads cross themselves and each other, so wedges inside the
box were dimmed and wedges outside doubly dark, growing with the angle (the
reporter's -45° shows it best). The export was never affected: the overlay
alone was wrong.

The dimming is now `outside_crop_mesh`: the photo's rectangle minus the box,
as one mesh of four disjoint half-strips (outside edge i, inside edge i+1,
clipped to the photo). A flat or non-finite box dims nothing.

Tests: a coverage test over several box sizes and angles (every photo point
outside the box in exactly one triangle, none inside the box or outside the
photo), a degenerate-box test, and a headless pixel test at -45° (inside the
box the frame matches the 0° frame; the photo's corner is dimmed), which
failed on the old mesh.
2026-10-10 15:12:03 +02:00
Jason Baldwin 1b178ca2e5 LR-EDIT-DENOISE: a running job's picture is Ready only when the job reports (#719)
The denoise worker saves the picture, then tears down its rayon pool and
returns before it writes its outcome. A `denoise.pump` in that window saw the
file on disk (`denoise_check_photo` for the selected photo, or a reindex),
marked the photo Ready and still held the job as running: `PhotoState::Ready`
and `denoise_busy()` at once, the activity row up for a photo that said it was
done, and the flaky `a_photo_waits_for_its_turn…` assertion on a loaded
machine.

The disk checks now leave the running photo to its job: `denoise_poll` makes
it Ready in the same pump that clears `running`. A test blocks the worker in
the model loader, writes the picture in its place and shows the photo stays
Running until the worker reports, then Ready and idle together.
2026-10-10 14:05:37 +02:00
Jason Baldwin 90875cf8f2 LR-IMP-FORMATS: probe compressed NEFs from their headers, not the Huffman stream (#708)
`probe_info` (every file an import adds) ran the whole Nikon Huffman
decode on a compressed NEF because `nef::decode` had no header mode and
the masked-trailing-column test wanted the samples: 386 ms per 24 MP
file, the full decode time, where every other format probes in < 1 ms.

The header has no source for those columns (the D5100 and D7000 write 8
masked columns at value 0, compressed and uncompressed alike, with no
CropArea or other maker-note tag), so the probe cannot skip the test.
Instead both modes now judge the active width from the first 128 rows
(`nef::MASK_ROWS`) against the nominal white level: the header probe
decodes only those rows (`nefc::decode_rows`, the stream is sequential;
uncompressed files read only the strips holding them) and the full
decode uses the same rule, so `probe_info == RawImage::info` still
holds. On every corpus NEF/NRW the answer is the same for 8 to 512 rows
as for the whole frame. The whole-image size and truncation checks still
apply in both modes; the "lossy after split" end-of-strip rule is only
checked when the whole strip is decoded.

Release, same files: probe 66 -> 3.4 ms (D5100 lossless), 72 -> 3.2 ms
(D7000 lossy 12), 88 -> 3.1 ms (D7500 lossless 12), 92 -> 3.7 ms (Z f
lossless), 23 -> 1.5 ms (D5100 uncompressed), 54 -> 2.7 ms (B700 NRW).

Tests: a synthetic lossless NEF whose stream is cut off after two
thirds of the rows probes to the same info as the whole file while the
full decode reports the truncation; synthetic uncompressed NEFs with
masked columns in the top rows, lower rows, all or none give the same
active area in both modes. Both failed before the fix. Docs: the
`probe_info` doc comment, docs/parity.md (LR-IMP-FORMATS),
docs/gpu-pipeline.md.
2026-10-10 13:58:20 +02:00
Jason Baldwin 3285e0282d LR-LIB-DELETE: importing a deleted photo's file again brings it back (#706)
Import a photo, delete it, import the same file again: the import skipped
it as a duplicate "already in the library" while the grid showed it in
Recently Deleted, and the user was stuck (issues #298, #706). The default
for a file whose photo is in Recently Deleted is now to restore the photo,
with its edits, rating and albums (`onDeleted: restore`; `skip` keeps the
old behaviour, `fresh` imports a clean copy). A photo matched by content
whose file is gone is relinked to the imported file. The import review
checks such files, badges them "Will be restored", counts them as "in
Recently Deleted" and starts its choice on Restore them; dropped files
say "Restored 1 photo from Recently Deleted".

Browsing a folder in Local, Auto Import and a Lightroom catalog import
keep skipping: only an import the user asked for brings a photo back.
2026-10-10 13:51:10 +02:00
Jason Baldwin 58f7e74f2a LR-IMP-FORMATS: file extensions match whatever their case (#705)
Issue #705 (Linux, Nikon `.JPG` hidden by the Add Photos dialog) is the
symptom of #342, fixed by e2d35de8 after the 0.4.0 release and shipped in
0.5.0: 0.4.0 handed GTK / the desktop portal `*.jpg`-style globs, which
they match case-sensitively. Every dialog filter now goes through
`add_filter_nocase` (`*.jpg`, `*.JPG`, `*.[jJ][pP][gG]`).

An audit of every other extension match (folder scan, Browse Folder,
drag-and-drop, the CLI, `codecs` sniffing, the XMP sidecar lookup,
raw+JPEG stems, export format by extension) found them all folding case
already. This adds the regression test that proves it: a folder with
`a.JPG`, `b.Jpg`, `c.JPEG`, `d.PNG`, `e.NEF`, an `a.XMP` sidecar and a
`b.Jpg.xmp` sidecar imports all five (both sidecars read) and
`library.browse` shows all five. Docs note what was case-sensitive and
when it was fixed.
2026-10-10 13:33:32 +02:00
Jason Baldwin 8a60e3cd30 LR-PREF-PEOPLE: Recognise faces stays off until a recognition model is chosen (#678)
Settings ▸ Faces let the switch be turned on with no recognition model
chosen or installed: the box ticked, Settings closed without a word, and
recognition was on with nothing to run. Now the box is enabled only with a
chosen, installed recogniser (and a build that can run models), says
"Choose a model below (Use) to start." on hover, and that hint stays under
it on or off; a box left on by an older build can still be switched off.
faces.enable {enabled: true} refuses without such a model, so the control
channel and MCP cannot reach that state either, and removing the last
recogniser switches recognition off instead of leaving it on with nothing
chosen. Installing a model still chooses it and switches recognition on.

Tests: tests_face_models::recognition_cannot_be_turned_on_without_a_chosen_model
and tests_panels::recognise_faces_stays_off_until_a_model_is_chosen (fails
on main at the first click). Docs: faces.md, parity LR-PREF-PEOPLE.
2026-10-10 11:51:56 +02:00
Jason Baldwin 94fed226df MENU-BAR: a scrolled menu draws no scrollbar over its rows (#691)
A menu taller than the room under the menu bar scrolls inside an egui
ScrollArea whose floating scrollbar is drawn over the rows' right edge,
where the shortcut text ends. At 1.0x the 2-point handle grazes the last
glyph; at 1.75x the bar is in its wide hovered state with a semi-opaque
background and hides the last character of every shortcut, which reads as
clipping. The scroll content, its clip and the rows are the level's width
in both modes (instrumented), so nothing else needs to move: the scroll
area hides its bar. The up/down strips, the wheel and dragging were already
the designed ways to scroll a menu, like native menus; the native macOS
menu bar does not go through this code.

Test: tests_menubar::a_scrolled_menu_draws_no_scrollbar_over_its_shortcuts
(a 700 pt and a 1400 pt window: no tall narrow rectangle along the level's
right edge, equal widths), via a new HeadlessView::painted_shapes; fails on
main with the bar's track [512, 59]-[514, 660] and handle.
2026-10-10 11:39:28 +02:00
Jason Baldwin a677478b92 KEY-ZOOM: Cmd+= / Cmd+- / Cmd+0 zoom the photo, not egui's interface scale (#566)
egui's zoom_with_keyboard was left on, so at the end of every frame it took the
same keys as View ▸ Zoom In / Zoom Out / Zoom to Fit for its browser-style
interface zoom. On macOS the native menu bar got the keys first; on Windows and
Linux the whole UI scaled. theme::apply turns the option off for every context
(desktop, headless, tests).

Headless tests that pressed Cmd+Plus / Cmd+Minus to exercise the interface
zoom's viewport handling set the zoom factor directly; the CLI snapshot test
that did the same is dropped (the unit tests cover the mechanism). New
tests_zoom_keys.rs: the shortcuts step the photo zoom while the zoom factor
stays, and Ctrl+wheel over the photo zooms it in the Edit view.
2026-10-09 20:14:49 +02:00
Jason Baldwin 13e3406829 Merge origin/main into #540 (relative-WB format list: SRW from main joins the shared helper) 2026-10-09 18:12:44 +02:00
Jason Baldwin 29b3f35136 LRC-LIB-CATALOG-IMPORT: read Lightroom's Custom white balance by its as-shot shift on relative-WB raws (#510)
A Custom white balance from a Lightroom catalog or XMP sidecar was taken as absolute Kelvin for every
raw. Raws LightCraft develops relative to their as-shot look (ARW, NEF, RW2, RAF, CR3, CR2, PEF; 6500 K
means as shot) then read a small tweak as a large blue shift: ΔE 22 against Lightroom's export on the
photos of issue #510.

The crs mapper now takes a `Target` (preset, rendered, raw with a measured illuminant, relative-WB raw)
instead of a bool. For relative-WB raws, and rendered files with only `Temperature`, a Kelvin value is
converted to the same mired shift from the packet's `AsShotTemperature` / `AsShotTint`. Without that
reference (Lightroom catalogs keep none for Custom photos) the photo stays As Shot and the import
report lists `Temperature, Tint` as not carried over. Import, sidecar read and catalog import decide
the target the same way through `lightcraft_catalog::relative_wb_format`.

Tests: shift from the issue's numbers (3650 → 3578 K lands at ~6275 K), the As Shot fallback with its
report entry, target selection per file, and the catalog mapper's Custom-WB case.
2026-10-09 17:48:05 +02:00