Commit Graph
1939 Commits
Author SHA1 Message Date
Claude 8f42f4a96d Merge PR #753 2026-10-11 11:39:27 +00:00
Claude d5d9daa555 Merge PR #759 2026-10-11 11:39:27 +00:00
Claude bda8569f67 Merge PR #760 2026-10-11 11:39:27 +00:00
Claude 0a46f615dc Merge PR #767 2026-10-11 11:39:26 +00:00
Claude b23b20638e Merge PR #772 2026-10-11 11:39:26 +00:00
Claude 9ecd3309b2 Merge PR #779 2026-10-11 11:39:26 +00:00
Claude 5abc39ae7d Merge PR #799 2026-10-11 11:39:26 +00:00
Claude 11dd3bd6b3 Merge PR #803 2026-10-11 11:39:26 +00:00
Claude 68f75cc07a Merge PR #809 2026-10-11 11:39:26 +00:00
Claude 72b02c65b7 Merge PR #817 2026-10-11 11:39:25 +00:00
Claude 5bbce8b9fd Merge PR #855 2026-10-11 11:39:25 +00:00
Claude f30ad32d93 Merge PR #858 2026-10-11 11:39:25 +00:00
Claude 09461d04c7 Merge PR #873 2026-10-11 11:39:25 +00:00
Claude f98749343f Merge PR #877 2026-10-11 11:39:25 +00:00
Claude 95662cfff5 Merge PR #883 2026-10-11 11:39:25 +00:00
Claude 14679de823 Merge PR #885 2026-10-11 11:39:25 +00:00
Claude b038c018c2 Merge PR #829 2026-10-11 11:39:24 +00:00
Claude 72a7cd80b8 Merge PR #792 2026-10-11 11:39:24 +00:00
xristosmoug 0b960d39e3 fix(macos): verify strict signature in release DMG (#863) 2026-10-11 13:26:48 +02:00
ingolfteetzandClaude Opus 5.5 bbd973f321 LRC-LIB-CATALOG-IMPORT: describe raws from their headers without reading pixel data
`lightcraft_raw::sparse::header_plan` says which byte ranges of a raw file the header
probe (`probe_info`, `lightcraft_meta::extract`) reads, given a buffer of the file's length
in which only some ranges are loaded: everything but the bulk sample data (large TIFF
strips/tiles minus the first chunk's coding headers, a CR3's image tracks minus the raw
track's CRX headers and QP maps, a RAF's CFA strip, an RW2's raw data). `preview_plan`
also reads what `embedded_preview` needs, for raws shown from their preview. NEF, ORF and
PEF (their header probe looks at samples) and anything else are read whole.

The corpus test checks, for every raw.pixls.us sample the plan describes, that probing the
partly loaded buffer gives exactly what the whole file gives, with the skipped bytes zero and
again 0xFF: CR3 2.7 %, DNG 2.4 %, CR2 3 %, RAF 6 %, RW2 7 %, ARW 8 % of their bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:25:57 +02:00
Andrew FrahnandClaude Opus 5.5 a7382aacfb Masking: Color Range "Pick" samples the photo
With the Masking panel open the loupe routes clicks to mask_overlay, so the
colorRange eyedropper armed by the Pick button (handled only in
general_interaction) never ran and no sample was taken. mask_overlay now
handles the eyedropper (crosshair cursor, click samples, shift-click adds)
through a shared color_range_pick helper.

Fixes #954

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 22:20:03 +11:00
Lucas LepskiandGPT-6.1 Sol 353c1cf566 UI-PILL: retain padding fix with upstream toolbar layout
Preserve upstream compact-toolbar layout and keyboard regressions alongside the rating and flag padding regression.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
2026-10-11 13:07:05 +02:00
sanyamsingh001andCodex 5a60d94f2f KEY-SLIDER-TYPE: fix immediate numeric entry and Reset precedence
Honor Enter/Escape on the completed opening release, release focus for a
cancelled opening click, and keep a numeric Escape batch out of app shortcuts.
Defer the Edit panel's clicked Reset until after its controls so the normal
click-away commit precedes the explicit reset. Retain the original Numeric
regressions and the single active-draft Reset regression.

Co-authored-by: Codex <codex@openai.com>
2026-10-11 10:55:33 +00:00
Andrew FrahnandClaude Opus 5.5 88e4188e78 Fix #812: the window's renderer can't kill the start-up (DX12 shader errors)
On some Windows machines FXC fails to compile egui's own shaders on DX12
("The parameter is incorrect. (0x80070057)" in egui_pipeline) and wgpu's
default error handler panicked. Each renderer (DX12, Vulkan, GL, glow) is
now probed with error scopes and a logging error handler before the window
opens; the one that works is remembered; a start that dies leaves a marker
and relaunches with the next renderer. LIGHTCRAFT_GPU_BACKEND=glow added.

Sources: own design.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 21:45:02 +11:00
Luniiya 43662e6959 UI: add favorite themes and appearance shortcuts
Port of storytold/photocraft#2445. Each theme in Settings > Interface gets a
star; starred themes are saved with the settings and listed in a right-click
menu on the title bar's appearance button, next to Appearance Settings, so
switching between favorites takes one click. The theme cards now list one
theme per row to make room for the stars, so the settings dialog test
scrolls further to reach the controls below them.
2026-10-11 12:24:01 +02:00
Andrew FrahnandClaude Opus 5.5 23e046daba Reverse geocoding: surface download failures, cache the gazetteer, e2e download test
- A failed, cancelled or finished place-names download is reported once
  as a toast (failures as an error toast that says what to do), and
  metadata.placeNamesStatus reports downloading / lastDownload / lastError.
- The parsed GeoNames data is cached in the session and re-read only when
  the cities file's size or modification time changes.
- End-to-end download test against a fake HTTP server on 127.0.0.1:
  a synthetic zip, a garbage zip, a truncated body and 404s.
- Info panel: Fill Location gets its own row (it was clipped beside
  Show on Map in a narrow panel).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 21:09:53 +11:00
Andrew FrahnandClaude Opus 5.5 d94d6856e9 Library switching: name prompt, library name under My Photos, docs (#902)
- New Library… from the menu asks for a name (default prefilled), then the location.
- The open library's name shows under My Photos in the left panel (hover: its path), so it
  is visible on macOS too, where the window title isn't.
- Switching also remembers the library being left (e.g. the one opened at launch).
- whats-new entry and parity row LRC-LIB-CATALOG-SWITCH.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 21:08:24 +11:00
Andrew FrahnandClaude Opus 5.5 b28b05c475 Reverse geocoding: fill City / State / Country from GPS (offline GeoNames)
New crate lightcraft-geocode (L1): parses the GeoNames cities dump and finds the
nearest place within 50 km through a 1-degree cell index; NaN / out-of-range
positions and malformed lines find nothing. Engine commands
metadata.reverseGeocode (Photo menu, Info panel "Fill Location"; fills only
empty fields unless overwrite, one undo step, activity row for batches),
metadata.downloadPlaceNames (background download of the CC-BY 4.0 GeoNames
files into the settings folder, cancellable activity row) and
metadata.placeNamesStatus. No data is bundled; NOTICE records the licence.

Fixes #582.

Sources: GeoNames dump readme (prose); haversine (textbook); PKWARE APPNOTE
(ZIP, prose); cell index own design.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 20:49:36 +11:00
Andrew FrahnandClaude Opus 5.5 8924ea9afd Library: create folders on disk and move photos between folders
folder.create {parent, name} makes a folder on disk (one undo step; undo
removes it again while it is empty). photo.moveToFolder {folder, ids?}
moves the photos' files with their XMP sidecars into another folder and
relinks them as one undo step (undo moves them back); virtual copies
follow their master. It never overwrites: a name already taken in the
destination refuses the whole move with an actionable error. Missing or
offline originals and Local photos are refused. Across volumes a file is
copied, compared byte for byte and only then removed. The move holds a
row in the activity stack.

UI: New Folder on Disk… and Move Selected Photos Here on a Folders row,
Move to Folder… in the photo context menu; strings in every catalog.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 20:49:19 +11:00
Andrew FrahnandClaude Opus 5.5 75cf78175a Library switching: New Library…, Open Recent Library, library name in the title (#902)
- app.newLibrary {path?, name?}: create a library (in a chosen location, or a given folder)
  and switch to it; refuses an existing library, a non-empty folder, a file or a bad name.
- File ▸ Open Recent Library: the last 8 libraries (app settings), missing ones greyed out;
  opening a gone one is an error that forgets it instead of creating an empty library.
  app.recentLibraries / app.clearRecentLibraries for CLI and MCP.
- The window title names the open library; Settings ▸ General gains New Library….
- A library that can't be opened leaves the current one open.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 20:48:36 +11:00
Andrew FrahnandClaude Opus 5.5 457c899160 Library: a library's own file names its folder; any other file is a clear error
`lightcraft-cli run --library <dir>/catalog.snap` failed with the OS's "File exists"
(creating a folder over the file). Session::open_library now opens the folder that holds
catalog.snap / catalog.log / catalog.lock, and says "is a file; a library is a folder"
for any other file.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 20:48:36 +11:00
Andrew FrahnandClaude Opus 5.5 48edfb8d3a View: Lights Out (Shift+L) dims or blacks out everything but the photo
View > Lights Out (view.lightsOut) cycles On -> Dim (80 %) -> Off; Esc turns
the lights back on; {mode: on|dim|off} sets a state directly. The panels stay
where they are: a black veil is painted over everything but the loupe's photo
(or the grid / Compare / Survey area). Shift+L, since L is Linear Gradient.

Fixes #611. Sources: own design.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 20:47:54 +11:00
lloydandClaude Opus 5.5 9815ca1310 Merge upstream/main into fix-export-saturation
Brings the branch up to date with main (#869 shared docking and the docs it
added); no conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:40:04 +02:00
MarekSpodBiedry 2c8520371c Dropping photos with XMP sidecars no longer imports the sidecars as presets (#936) 2026-10-11 11:37:46 +02:00
cookingwithsimonandClaude Opus 5.5 44b12efea3 LR-EXP-PSD: Edit in PhotoCraft (right-click Edit In, flat PSD hand-off)
Photo > Edit In > Edit in PhotoCraft (Cmd+Shift+E, also on the photo
context menu) renders a copy with the edits as a flat 16-bit PSD, which
PhotoCraft saves back to in place, stacks it on the original and opens
it in PhotoCraft, found where its installers put it. Edit in Other
Editor (Cmd+Alt+E) keeps the configured program. Per-editor format,
colour space and bit depth in Settings > External Editing. Export gains
a flat PSD format.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4kqvR9KVCPia3SSXyB8WG
2026-10-11 09:35:43 +00:00
Andrew FrahnandClaude Opus 5.5 5f35b5af17 Cargo.lock: version for lightcraft-lensdb
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 20:21:42 +11:00
Alex ChambersandClaude Opus 5.5 f05a52968f LR-PROF-CAMERACOLOR: a tone curve from black-and-white camera JPEGs (#577)
A raw shot in a monochrome picture style records full colour, but its
camera JPEG is grey: neither the full look nor the partial look has
colour to fit, so the photo started from the spectral matrices or the
neutral fallback with the default tone curve, about 1.3 EV darker than
the same body's colour-shot photos. The grey JPEG still shows the
camera's tone curve.

When no look was fitted and the JPEG is black and white (`monochrome`),
fit the quantile tone curve alone on top of the colour the photo gets
otherwise: the camera profile's (in `fit_preview`), else the spectral
matrices or the fallback (`monochrome_look`, after the spectral step).
The chroma curve stays neutral; the partial look's checks apply. A
tone-only look keeps the file's colour model for white balance.

LOOK_VERSION 2 -> 3, RENDER_CACHE_VERSION 42 -> 43.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:19:49 +02:00
Andrew FrahnandClaude Opus 5.5 e142075998 NOTICE: credit PhotoCraft for the ported inpaint code; Cargo.lock version for lightcraft-inpaint
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 20:02:17 +11:00
ingolfteetzandClaude Opus 5.5 ad3c7315bb Tests: hold the MCP export-cancel test's export after its first photo until cancelled (#674)
export_progress_and_cancel failed every time on a fast machine (M5 Max, 4
export lanes): the 8 demo photos exported within the 100 ms progress
throttle, so the first progress above 0 was the final 8/8 and the cancel
arrived after the export had finished (a reply to the cancelled request,
or all 8 files on disk).

The test now wraps Headless in a test-only backend that, at the first
photo boundary, keeps polling the server's real progress hook (which
answers pings and reads cancels) until the cancel is read or the test
releases it, bounded at 20 s so a broken cancel fails rather than hangs.
The first export is released once the mid-batch ping is answered (that
was the same race), the second only ends by the cancel. That makes the
outcome exact, so the check tightens from "1..7 files" to "exactly the
one completed photo", which also proves the photos the other lanes had
already rendered are never written. No production code changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 10:52:59 +02:00
Andrew FrahnandClaude Opus 5.5 bac0f75551 Soft proofing against printer ICC profiles: intent, Simulate Paper & Ink, device gamut warning
Soft Proofing's Profile menu takes a printer / paper ICC profile (Other…;
`view.softProof {profile, intent, paperInk}`), RGB or CMYK, matrix or LUT.
The CMS (moxcms) builds a 33^3 proof table over the working space: forward with
the chosen intent (Perceptual / Relative), back relative colorimetric; the
destination gamut warning comes from the colorimetric round trip. Simulate
Paper & Ink renders absolute colorimetric: the profile's media white replaces
screen white and the ink black is unscaled. The pipeline gets the table by id
(`Proof::profile`, `register_proof_profile`), so Proof stays Copy and render
keys include it. Proof copies are named after the profile.

Sources: ICC.1:2022 (rendering intents, media white point, absolute
colorimetric); own design for the proof table.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 19:30:46 +11:00
Andrew FrahnandClaude Opus 5.5 fe6d7b0192 Content-aware Remove: inpaint crate (PatchMatch completion) and a Fill spot mode
New `inpaint` crate (L1): multi-scale patch-based completion with a PatchMatch
nearest-neighbour field, seeded by a membrane fill. Deterministic, no panics on
hostile sizes, cancellable. Ported from PhotoCraft (MIT OR Apache-2.0).

Remove tool: a fourth spot mode, `fill` ("Content-Aware"), rebuilds the spot
from patches of its surroundings instead of one source; coarse-to-fine, so the
structure is chosen at a scale set by the spot size and preview and export agree
(tested). spot.add leaves its source unset.

Sources: C. Barnes et al., PatchMatch (SIGGRAPH 2009); Y. Wexler, E. Shechtman,
M. Irani, Space-Time Completion of Video (TPAMI 2007); P. Perez et al., Poisson
Image Editing (SIGGRAPH 2003) for the membrane fill; code ported from PhotoCraft
crates/algo/src/inpaint.rs + poisson.rs (a680ed22).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 19:30:42 +11:00
Andrew FrahnandClaude Opus 5.5 ab240d6ece Perf: hue/saturation tables skip the sRGB round trip and fmodf in their per-pixel path
HsvTable::apply took the value through linear -> sRGB -> linear even when the table's value
scale is 1 (every hue/saturation-only table, as fitted camera looks are), and wrapped hues with
rem_euclid (an fmodf call) three times per pixel. The value is now kept as it is for a unit
scale and the sRGB-encoded value is reused otherwise; hues are wrapped by a helper that equals
rem_euclid bit for bit (tested) without the call for values within one period.

2048 px render of a 24 MP ARW, single thread: 16.4 -> 15.1 G instructions, 4.94 -> 4.76 G cycles.

Sources: own design.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 19:30:29 +11:00
Andrew FrahnandClaude Opus 5.5 07ae88e923 Perf: preview builds render photos side by side and make the thumbnail from the view's source
library.buildPreviews rendered one photo at a time, and each photo's thumbnail job decoded the
file again at thumbnail level after (before, in fact) the view job had decoded it at preview
level. Now the view renders first and its decoded source serves the thumbnail, and photos
render in as many lanes as an export batch (two for 1:1 previews, a full-size source each);
a panic in one photo counts as that photo's failure.

100 24 MP ARWs, standard previews, loaded machine: 49.4 s -> 38.4 s (CPU 3m28 -> 2m49).

Sources: own design.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 19:30:29 +11:00
Andrew FrahnandClaude Opus 5.5 6873aab6eb Drafts keep the wide planes across a noise-reduction change (CPU NR drag)
A noise-reduction change invalidated the scene-linear stage and with it every spatial
plane (base, clarity, dehaze, chromaticity), so each tick of an NR drag on the CPU rebuilt
all of them. The plan now also carries the linear key without the NR settings; a draft
render whose image differs from the cached one only in NR keeps those wide blurs and redoes
only log luminance and the fine texture band. Full-quality renders never take them.

Draft NR drag, single thread: prepare 18.7 -> 7 ms (render ~135 -> ~125 ms CPU).

Sources: own design.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 19:30:28 +11:00
Andrew FrahnandClaude Opus 5.5 db7e37843e Export: Remove location info also covers Original and DNG formats
No location in the XMP sidecar or the DNG's packet; exporting an original
that embeds GPS with the option on is refused instead of leaking it.

Sources: own design.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 19:30:23 +11:00
Andrew FrahnandClaude Opus 5.5 80e58ed55b engine: lens profile corrections from a lensfun-format database
Raws without an embedded lens correction look their EXIF lens up in the
user-supplied database; the profile goes through the existing Enable Profile
Corrections path (amount sliders, CPU/GPU geometry stage, DNG export).
Synthetic-grid test renders a distorted chart through the pipeline.

Sources: own design (see lightcraft-lensdb for the model sources).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 19:30:18 +11:00
Andrew FrahnandClaude Opus 5.5 00ea386f7f lensdb: lens distortion (poly3/poly5/PTLens), TCA (linear/poly3) and vignetting (pa) profiles, lensfun-format loader
Profiles are interpolated in focal length (vignetting also in aperture and
focus distance), scaled by crop factor, and converted by least squares to the
DNG WarpRectilinear / FixVignetteRadial polynomials the pipeline already
applies. The lensfun database (CC-BY-SA) is only read from a directory the
user supplies; nothing is bundled.

Sources: lensfun's published database format and lens calibration
documentation (no lensfun code read), PanoTools wiki (a, b, c distortion
model), DNG specification 1.6 (WarpRectilinear, FixVignetteRadial);
interpolation and fitting: own design.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 19:30:18 +11:00
PiMinusMaxandClaude Opus 5.5 385348b258 LR-PROF-CAMERACOLOR: the camera-look guard blends in smoothly instead of switching (#820)
Between a kept luminance share of 0.15 and 0.25 the table colour is now blended in with a smoothstep,
so noisy shadows show no step between pixels that get the table colour and pixels that keep theirs.
At 0.25 and above pixels render exactly as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 10:24:09 +02:00
ingolfteetzandClaude Opus 5.5 4700c8a74e LR-IMP-FORMATS: decode Canon EOS R5 Mark II C-RAW (CRX QP-map first literal and escape)
The CRX 0x200 QP map opens with its first symbol as a 32-bit literal; every
later symbol is Rice coded with a limited-length escape: 23 zeros, the
terminating 1 and the symbol in 8 bits (the ITU-T T.87 layout for a 32-bit
limit and 8-bit symbols, without its minus-one offset). The old rule (an
escape after 16 zeros with a 16-bit literal) matched only the first symbol,
which is all the R8 and R7 maps need. R5 Mark II maps (QP 150-162) jump by up
to 12 where the Rice parameter is 0, giving 18- to 22-zero prefixes that
misparsed ("Canon CRX adaptive QP value -21087"): 174 of 306 sampled files
from a private R5 Mark II library opened as previews. Every QP map of its
33,665 C-RAW files now decodes to the exact end of its entropy stream; the six
oracle fixtures are unchanged. Established by black-box analysis of the
bitstream; no third-party decoder source consulted.

Adds the four CC0 R5 Mark II samples (raw.pixls.us 7881-7884) to the corpus
with pinned sensor hashes (the crop-mode framing is left to #754), bumps
RENDER_CACHE_VERSION to 43, and updates cr3.md, raw-parity (235 verified
models), coverage, gaps, the checklist rows, ROADMAP, target-app-parity and
What's New.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:20:49 +02:00
Alex ChambersandClaude Opus 5.5 484c160c13 LR-EXP-PHOTOS: Apple Photos imports in the activity stack (#345)
Rebased onto the activity stack (#601). An export adding its files to
Photos keeps its row through that step, with "Adding to Apple Photos…" as
the detail. The worker turns the row's ✕ off (TaskHandle::set_cancellable)
before its last look at the cancel flag and reports what it saw, so a
cancel either stops the export before Photos is asked or is refused, never
both "cancelled" and imported. activity: a per-task gate orders a cancel's
decision against a step switching cancelling off, so a cancel can't be
accepted between the switch and the step's look at the flag and then be
ignored. An import of its own (export.addToPhotos, or
the Photos step left running after a foreground app.export) holds a row of
kind applePhotos while Photos works: no ✕, no count, the album as detail.
The desktop app refuses export.addToPhotos {wait: true}: its window's
thread never waits for Photos. The label is in every catalog; tests assert
the rows, the refused cancel and the refused wait.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:19:00 +02:00