Commit Graph
3164 Commits
Author SHA1 Message Date
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
PierrunoYTandClaude Sonnet 5.5 3cd471b32d Smooth the plain mouse-wheel zoom like Ctrl+wheel
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-10 21:08:25 +02:00
dev-null-undefinedandClaude 4860f5e8d4 LR-IMP-FORMATS: ARW6 companding curve derived clean-room from CC0 lossless pairs
The 2668-entry table read off the black-box oracle's output is gone.
arw6_curve.rs now builds the 4096-entry table at first use from our own
measurement: piecewise linear through knots u_k = 128 k, c_k = u_k for
k <= 11 and 356.89 + 655.646 ln(k - 6) above, rounded half up, fitted to
the 7 same-exposure lossless (LJ92) / ARW6 pairs of the CC0 ILCE-7RM6 set.
A unit test pins the whole table (sum 27776336, weighted 92792980288),
its anchors, the identity through 1408 and strict monotonicity.

The corpus sums become regression values of the new output; the 12-bit
codes before the curve stay verified against the oracle (Phase 0).
docs/arw6-compression.md describes the curve, how it was measured, its
uncertainty, the structure and x2-scale evidence, why it is not in the
file, and the after-the-fact comparison with the oracle's curve; the
tracker and docs no longer call the output bit-exact against LibRaw.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-10 21:08:17 +02:00
dev-null-undefinedandClaude 4d26a4d7b4 LR-IMP-FORMATS: render cache version 43 (ARW6 decodes instead of falling back to the embedded JPEG)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-10 21:08:17 +02:00
dev-null-undefinedandClaude 2218a53f2b LR-IMP-FORMATS: ARW6 module docs cite docs/arw6-compression.md
docs/superpowers/ held the working spec and plan, which pointed at the
local plan/ folder. The ARW6 module docs now cite the sections of
docs/arw6-compression.md, which gains the facts they relied on from the
spec: the NP field of word 0, the stream header word layout and how the
first half's residual lines are counted.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-10 21:08:17 +02:00
dev-null-undefinedandClaude 2f75b21ac3 LR-IMP-FORMATS: ARW6 corpus pins two files (FF Compressed, APS-C HQ)
`cargo xtask corpus --download` now fetches one full-frame Compressed and
one APS-C Compressed HQ file (about 94 MB) instead of all 12 (about
630 MB); the corpus test checks those two and the lossless-sibling
regression test is gone, since nothing would download those files.
docs/arw6-compression.md keeps the full table with source paths and
SHA-256, says which two xtask fetches and how to fetch the rest with
`curl -L`, and records the CC0 1.0 statement of RawDB's set record
(accessed 2026-10-10).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-10 21:08:17 +02:00
dev-null-undefinedandClaude 1be303ca00 LR-IMP-FORMATS: ARW6 corpus hashes live in RAW_SAMPLES; drop docs/arw6-corpus.sha256
The RawDB entries in xtask carry their SHA-256 as the third field, which
`cargo xtask corpus --download` checks; the corpus test keeps only the
sensor sum checks.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-10 21:08:17 +02:00
dev-null-undefined fa7f1e8d6c LR-IMP-FORMATS: ARW6 docs after the final review 2026-10-10 21:08:17 +02:00
dev-null-undefined 69d3d4cf91 LR-IMP-FORMATS: ARW6 chroma clamps negative greens; 12-bit guard; fallback white 2026-10-10 21:08:17 +02:00
dev-null-undefinedandClaude aea5236790 LR-IMP-FORMATS: ARW6 decode timing method
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-10 21:08:17 +02:00
dev-null-undefinedandClaude 9aa9bcb54e LR-IMP-FORMATS: ARW6 decode timings
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-10 21:08:17 +02:00
dev-null-undefined 458b938248 LR-IMP-FORMATS: ARW6 corpus verification, docs and tracker 2026-10-10 21:08:16 +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
dev-null-undefined 7b4bdfa68b LR-IMP-FORMATS: ARW6 four-tile placement test checks every tile 2026-10-10 21:07:30 +02:00
dev-null-undefined 1a146c12b0 LR-IMP-FORMATS: ARW6 chroma planes are base-free; white fallback not doubled 2026-10-10 21:07:30 +02:00
dev-null-undefined 1bbb57650c LR-IMP-FORMATS: ARW6 halves must end within their last byte 2026-10-10 21:07:30 +02:00
dev-null-undefined 15397a712e LR-IMP-FORMATS: ARW6 colour reconstruction, companding table and ARW dispatch 2026-10-10 21:07:30 +02:00
dev-null-undefined 6cba574990 LR-IMP-FORMATS: ARW6 sensor-anchored frame geometry and tile reconstruction 2026-10-10 21:07:30 +02:00
dev-null-undefined e45c4625b4 LR-IMP-FORMATS: ARW6 container rejection tests and tile byte ranges 2026-10-10 21:07:29 +02:00
dev-null-undefined f101a09142 LR-IMP-FORMATS: ARW6 tile table, tile and stream headers; synthetic ARW6 encoder for tests 2026-10-10 21:07:29 +02:00
dev-null-undefined 868ac707dd LR-IMP-FORMATS: ARW6 lifting arithmetic cannot overflow on hostile coefficients 2026-10-10 21:07:29 +02:00
dev-null-undefined 3a4a70fa8f LR-IMP-FORMATS: ARW6 parity dequantiser and 5/3 inverse lifting with phases 2026-10-10 21:07:29 +02:00
dev-null-undefined 01438b8979 LR-IMP-FORMATS: ARW6 VLD returns an empty line for width 0 2026-10-10 21:07:29 +02:00
dev-null-undefined 083685a34d LR-IMP-FORMATS: RDD 34 VLD line decoder with Sony's ARW6 deviations 2026-10-10 21:07:29 +02:00
PierrunoYTandClaude Sonnet 5.5 732a2f04fd Test: wheel zoom in the loupe with and without Ctrl
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-10 20:59:29 +02:00
PierrunoYTandClaude Sonnet 5.5 7f966965fc Fix #714: mouse wheel zooms the loupe around the pointer
A notched wheel (line/page units, no modifiers) zoomed nothing; only
double-click and pinch/Ctrl+wheel did. Trackpad scrolls still pan.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-10 20:56:23 +02:00
Jason Baldwin 52875ec3f9 Merge origin/main into slideshow-playback (docs restructure #788: parity.md is now parity-checklist.md; slideshow rows re-applied, summary regenerated) 2026-10-10 20:54:29 +02:00
Steffen SauderandClaude Sonnet 5.5 e363d298d9 LR-IMP-FORMATS: point to the two Olympus patents as a clean-room base for compressed ORF
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-10 20:51:23 +02:00
Jason Baldwin 92077d0807 LRC-SS-PLAYBACK: slideshow playback: fades, random order, repeat, previews prepared in advance, playback controls 2026-10-10 20:31:38 +02:00
Brandon ThomasandClaude Opus 5.5 964d9338ba Progress docs: target-app parity, RAW coverage vs Adobe, gaps, alpha gate, readiness by audience (#788)
Progress docs per craftrules standards/progress-docs.md: target-app parity, gaps, roadmap with alpha gate, readiness by audience (full / mainstream / essentials) with Opus 5.5 hours, model-by-model RAW coverage against Adobe's camera list (docs/raw/), and format, hardware, UI and localization parity docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:25:15 -04:00
radialmonster 0041aef955 LR-EDIT-OPTICS-PROFILE: apply DNG WarpRectilinear2 lens corrections (Pixel DNGs, #635) 2026-10-10 14:21:13 -04:00
radialmonster 9da719c6ed LR-EDIT-RESET: Reset Edits returns a raw to its import defaults, sharpening and colour NR included
Reset Edits rebuilt a photo without a default-preset look from DevelopSettings::default(),
so a raw imported with capture sharpening 40 and colour noise reduction 25
(DevelopSettings::for_raw) came back with both at 0, and with its embedded lens
corrections off. It now starts from the photo's own built-in defaults
(Photo::camera_defaults, the same as import) and keeps the white balance of its
current as-shot reference, as before. Rendered files are unchanged (their defaults
are DevelopSettings::default()). Reported in #763's notes.
2026-10-10 14:01:26 -04:00
lloydandClaude Opus 5.5 d7eb6b2ab4 LR-PREF-DISPLAY: macOS previews follow the screen's profile, so they match exports (#787)
Previews looked more saturated than the exported files on Mac screens. The
pixels are the same: preview and export match to within one level on the CPU
and GPU paths, and exports embed a correct sRGB profile. But wgpu leaves the
window's Metal layer without a colour space for sRGB surfaces, and Core
Animation shows such a layer without colour matching ("If nil, no
colormatching occurs", CAMetalLayer.h). On a P3 screen the sRGB previews
were therefore shown too saturated, while Preview and Photos convert the
exports properly.

While no monitor profile is chosen in Settings > Display, the desktop app now
shows previews through the profile of the screen the window is on
(NSWindow.screen.colorSpace.ICCProfileData, safe objc2 calls), through the
existing display-profile path. It is read again about once a second, so
moving the window to another screen is followed. A chosen profile still wins;
"Use Screen Profile" goes back to it. app.displayProfile reports it as
path: null, screen: true. Other platforms and headless sessions are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 19:51:01 +02:00
radialmonster 70103a7e55 LR-EDIT-LIGHT-HIGHLIGHTS: keep local contrast under Highlights/Shadows, as process V2 (#632)
The highlights/shadows base (fast guided filter of log luminance, eps 0.35)
followed thin high-contrast detail, so the per-pixel shift flattened twigs,
lines and texture in bright areas (and dark ones under Shadows); Exposure
kept them.

This changes how every existing edit with Highlights or Shadows renders, so
it ships as a new rendering process (docs/process-versions.md):

- Process::V2 (version 2) is added and made LATEST. New photos and Reset get
  it; existing edits stay on V1 until Update to Current Process. Settings
  saved without the field still load as V1.
- From V2 the base is the same guided filter of a separable median of log
  luminance (radius 0.15 % of the long edge, <= 24 px): fine detail takes its
  surroundings' shift and keeps its contrast, region steps stay put (no new
  halos). V1's base is untouched: local::tone_base_detail is 0 under V1 and
  tone_base then is exactly guided_fast(l, sigma, BASE_EPS), as before.
- CPU (local::prepare) and GPU prepare (median_h / median_v kernels, bit for
  bit with the CPU median) both branch on it via PlaneSigmas::base_detail.
- The base plane's cache key is now (sigma, process, median radius)
  (local::BaseKey), so a warm cache never serves one process the other's
  base. plane_at is shared with the GPU crate.
- finish::base_tone: V2 uses V1's tone map.

Tests: V2 keeps twig/local contrast (>= 0.9 of Exposure's) with no halo; V1
plane sigmas have no median and its base equals guided_fast bit for bit; V1
and V2 render alike without Highlights/Shadows; warm-cache renders across
V1/V2 switches equal fresh ones (fails if the key drops the process); GPU
matches CPU on both processes at 720 and 2400 px, cached and not.

Pentax 645D 2401 at 4000 px: main and V1 renders bit-identical (plain,
Highlights -100, Shadows +100). Highlights -100 bright rel. detail
0.095 (V1) -> 0.147 (V2), Exposure -1: 0.124; Shadows +100 dark rel. detail
0.126 -> 0.148.

RENDER_CACHE_VERSION 54 (adjust at merge).
2026-10-10 13:46:14 -04:00
Nathaniel LedfordandClaude Opus 5.5 02a21b860d LR-BEHAV-SIDEBAR-SECTIONS: review fixes for the header button and the pinned header
A keyword made from Keywords' + is shown (the section and its parents open); a
press on a header button never starts a section drag; a section dragged to the
top edge scrolls past a pinned header again (only a drag picked up by the
pinned header waits until it has left it). Tests that could not fail are
replaced, and the parity rows say what the code does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:14:07 -04:00
Nathaniel LedfordandClaude Opus 5.5 d331829770 LR-BEHAV-SIDEBAR-SECTIONS: a section declares its header button; Keywords gets + (Create Keyword Tag)
Albums' + is no longer a special case of the header: header_button says which
section has a button and whether it acts or opens a menu, and the frame draws
it in the header and its pinned copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:04:02 -04: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
Nathaniel LedfordandClaude Opus 5.5 9cd9e29614 LR-BEHAV-SIDEBAR-STICKY: rows beneath the pinned header are not drop targets
An album or photos released on the pinned Albums header went to the row
scrolled under it. Folding from the pinned header keeps the sideways scroll,
its hand-off to the next frame can't go stale, and a section picked up by the
pinned header doesn't start scrolling at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:51:55 -04: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
Nathaniel LedfordandClaude Opus 5.5 5e68d92c9f LR-BEHAV-SIDEBAR-STICKY: a long section's header stays in sight; sections are easier to tell apart
Pinned header at the top of the sidebar, hairlines between sections, the fold
chevron in the rows' triangle column, a count on folded sections, fainter zero
counts. From review: a move nobody could see is no move, ui.set refuses
`sidebar` together with `collapsedSidebar` and unknown names in the latter.
Translations and What's New for the sidebar work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:44:46 -04:00
Nathaniel LedfordandClaude Opus 5.5 5ac3e556ac LR-BEHAV-SIDEBAR-REORDER: drag sidebar sections by their header, Move Up / Down, Recently Deleted joins My Photos
view.sidebarMoveSection; a section with nothing to list stays with a note
instead of disappearing (so every move is one you can see). ui.set reads
`sidebar` strictly and still takes the older collapsedSidebar list; an album
folder's triangle and row can no longer toggle twice in one click.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:36:13 -04:00
Nathaniel LedfordandClaude Opus 5.5 a64efb149c LR-BEHAV-SIDEBAR-HIDE: show and hide sidebar sections from View ▸ Sidebar Sections and their headers
view.sidebarSection / view.sidebarReset; the same list opens from a section
header's and the My Photos title's context menu. A new album brings a hidden
Albums section back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:29:28 -04:00
Nathaniel LedfordandClaude Opus 5.5 338b67ffbe LR-BEHAV-SIDEBAR-SECTIONS: sidebar sections share one frame, one tree toggle and one saved arrangement
Albums, Local, By Date, Folders and Keywords are drawn by one section frame from
SidebarLayout (order, hidden, collapsed per section), which is whole whatever a
settings file says. The folded sections of older settings files stay folded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:26:29 -04:00
robmdunnandClaude Opus 5.5 afbc0f9744 LR-EXP-TYPE: JPEG XL export (#247)
Export as JPEG XL from the dialog, CLI, MCP and control channel (format jxl): lossy with
quality or lossless, 8/16-bit, effort fast/normal, progressive, EXIF/XMP boxes; HDR edits
as 16-bit Rec. 2020 PQ. Encoder: jixel (pure Rust, BSD-3-Clause OR Apache-2.0, native
only: it spawns its own threads), wrapped in catch_unwind. Lossy files declare an enum
encoding (sRGB / P3 / Rec. 2020; Adobe RGB and ProPhoto written as Rec. 2020); lossless
files embed the ICC profile.

Decoder fix: jxl-oxide desaturated colours outside sRGB when rendering XYB images to
wider primaries; lossy wide-gamut JXL now renders to linear sRGB primaries and is
converted to Rec. 2020 here. jixel's slowest effort is left out: jxl-oxide misdecodes
its (valid) files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:19:30 -04:00
radialmonster 0bd3f3715d LR-BEHAV-GPU: no indirect-call validation pipeline, which Intel Metal fails to compile (issue #250)
On a 2015 MacBook Pro (Intel Iris/Metal), 0.5.0 never opened its window, even with
LIGHTCRAFT_GPU=0: wgpu's indirect-call validation builds an internal compute pipeline when the
window's device is created, Metal's compiler fails on it ("Compiler encountered an internal
error"), the device is lost and eframe exits. That validation only guards indirect draws and
dispatches, which neither LightCraft nor egui issue.

backend::instance_flags clears InstanceFlags::VALIDATION_INDIRECT_CALL for every instance
LightCraft creates (the window's, Gpu::new and the denoise device), unless
WGPU_VALIDATION_INDIRECT_CALL asks for it. Tests: the flag rule, and that the window's instance
descriptor goes without it.
2026-10-10 12:11:41 -04:00
Alex ChambersandClaude Opus 5.5 6dca631482 LR-IMP-CAMERA-COVERAGE: SRF and DSLR-A100 raws open again (#702)
Since #281, probe takes a Sony file as a raw only with raw evidence, and
two layouts have none of the kinds has_raw_ifd looks for: SRF (DSC-F828,
an RGB-looking IFD0 of 14-bit samples) and the DSLR-A100 (raw data
addressed by SubIFDs). Both were "not a readable photo". For Sony, IFD0
samples of 12 or 14 bits, or model DSLR-A100 with an old-style JPEG IFD0
(compression 6) and SubIFDs, now count too; an 8- or 16-bit camera TIFF,
or an exported A100 TIFF with a SubIFDs thumbnail, stays an image. The public F828 and
A100 samples open through their previews as on main 5436951; 133 other Sony files unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 17:50:29 +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
goujichengandDeepSeek V3.2 ab438b05ca LR-IMP-PREVIEW-READ: a preview reads the raw's header, not the raw
A grid thumbnail and a loupe preview of an unedited raw show the camera's embedded JPEG, which
sits near the start of the file — for a 24 MP Fuji RAF, 5.6 MB of a 56 MB file (measured:
DSCF9492.RAF). The loader read the file whole and threw the sensor data away, so every preview
paid for 50 MB nobody looked at. On a local disk that is merely wasteful; on a network share,
where photographers keep libraries, it is the wait: the same preview took 10 s over a GVFS/SMB
Wi-Fi share against 0.96 s after this change.

- `lightcraft_raw::bounded_preview(bytes)` locates the preview from the header alone and reports
  its byte range; `embedded_preview_from_range` extracts it, byte for byte what `embedded_preview`
  returns for the whole file (the pointer is read from the RAF header's fixed u32s at 84/88).
- `engine::files::preview_window` reads a 4 KiB prefix, then — because the container reader clamps
  a slice to the buffer it was given, so a short prefix reports a plausible but truncated 3948
  bytes — re-reads exactly the extent the header declares, and takes the window out of the prefix
  rather than reading those bytes twice. `LIGHTCRAFT_PROFILE=1` prints what each preview cost.
- Formats whose preview only the whole file reveals (CR3 boxes, TIFF pointer tags past the prefix,
  CRW/MRW/X3F scans), a lying pointer, a short read and a file that changes underneath us all fall
  back to the whole-file path: a damaged header never costs the user their preview.
- Tests: the header range matches `embedded_preview` byte for byte, with a clamped prefix, a padded
  extent, a late pointer, a lying pointer and a non-JPEG window covered; on a synthetic RAF with a
  200 KB tail the quick path and the whole-file path produce identical pixels and the read stays
  under half the file; `crates/engine/tests/preview_read.rs` (opt-in, `#[ignore]`) measures a real
  file on a share. No raw fixture is committed.
- `docs/raw-preview-reads.md` records the mechanism and the numbers.

Co-Authored-By: DeepSeek V3.2 <noreply@deepseek.com>
2026-10-10 22:16:04 +08:00