Commit Graph

141 Commits

Author SHA1 Message Date
Brandon Thomas 08f2834216 docs: README feature status (library, culling, edit panel, retouch, menus)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:18:45 -04:00
Brandon Thomas c97a7356f5 M2.11: histogram shows the selected photo in the grid (thumbnail fallback) and never another photo's loupe render
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:18:10 -04:00
Brandon Thomas 900fd6101a docs: AGENTS.md — never send OS-level synthetic input (it reaches the user's frontmost app)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:11:49 -04:00
Brandon Thomas 59140d8265 Merge branch 'worktree-agent-aada35d252224c5cd'
# Conflicts:
#	crates/engine/src/media.rs
#	crates/ui-egui/src/state.rs
2026-10-01 11:11:49 -04:00
Brandon Thomas 64dfa8adbd Merge edit-panel parity (Point Color, Calibration, curve refine saturation + targeted adjustment, red/pet eye, visualize spots)
Fix-ups: diagnostic overlays never populate the view-preview cache; Photo Merge's RenderRequest gets
the new overlay field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:02:11 -04:00
Brandon Thomas ce3dd5e156 Merge main (Photo Merge); M1.14b: stack merge results with their sources
Photo Merge's Create Stack option now puts the merged DNG on top of a
collapsed stack of its sources (Catalog::stack_with_ops), so the grid
shows the result; test in tests_merge (and no stack without the option).
Photo Merge dialogs live in a Photo > Photo Merge submenu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:00:51 -04:00
Brandon Thomas 5f401d2eef Merge main (straighten, vignette style) into the polish branch
Also: GPU retired-buffer list leaks its buffers at thread exit instead of
dropping them (wgpu's thread-locals may already be gone; the drop aborted
the test process intermittently), and headless UI tests wait up to 120 s
for renders (they timed out on a loaded machine).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:52:17 -04:00
Brandon Thomas 82cecec366 LR-REM-VISUALIZE: Visualize Spots — high-pass black/white view with threshold
Overlay::Spots(threshold): luminance minus its Gaussian blur (sigma relative to the long edge,
so the same specks show at any preview size), thresholded to white/black; the threshold slider
(0..100) raises sensitivity. Runs after either renderer like the other overlays (equivalence:
share of differing pixels, since the view is binary). Remove panel: Visualize Spots checkbox +
Threshold slider; UI command view.visualizeSpots (A) opens the Remove tool with the view on or
toggles it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:52:06 -04:00
Brandon Thomas 06d30a0e59 LR-EYE-PET: pet eye — glow detection, dark pupil, catchlight
Pet eyes are found by brightness (the tapetum glows in any colour) and the disc is replaced by
a dark neutral pupil (Darken scales it); redeye.catchlight {index, on, offset} adds a soft
white catchlight at an offset from the pupil centre in pupil radii. Red Eye panel: Red Eye /
Pet Eye mode for new corrections and an Add Catchlight checkbox. CPU/GPU twins updated
together (equivalence covers both modes and the catchlight).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:43:57 -04:00
Brandon Thomas b5a873a631 docs: Photo Merge in README and parity
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:43:17 -04:00
Brandon Thomas 00efffd372 Merge branch 'worktree-agent-a5bfb1e4b62622bcb'
# Conflicts:
#	crates/engine/src/media.rs
#	crates/ui-egui/src/control.rs
#	crates/ui-egui/src/lib.rs
#	crates/ui-egui/src/shortcuts.rs
2026-10-01 10:37:52 -04:00
Brandon Thomas ebcc41825b LR-EYE-RED: red eye correction with automatic pupil detection
pipeline::redeye: inside the user's ellipse the pupil is found on the source image (a fixed
grid in normalized coordinates, so previews and exports agree): weighted centroid and area of
red-dominant pixels (or, for pet eyes, of the brightest ones). Pupil Size scales the detected
disc; rendering in scene-linear light after white balance neutralizes the red where it is red
(iris and catchlights stay) and darkens it. CPU (lin stage) and GPU kernel redeye_k with an
equivalence test (both the GPU and the CPU scene-linear paths). Indexed controls
redEye.<i>.pupilSize/darken, redeye.add/delete commands, the Red Eye panel (selected eye's
sliders, delete) and the loupe tool (drag an ellipse, click to select).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:37:02 -04:00
Brandon Thomas b7f8beaa31 M2.8: vignette style picker (Highlight / Color priority / Paint overlay)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:31:59 -04:00
Brandon Thomas 8d5cff2e69 M1.16: library filter bar (LR-FILT-*) + parity rows
Filter bar above the grid (View > Filter Bar, ⇧F, or the top-bar filter
icon): rating with ≥ / = / ≤, picked / rejected / unflagged, colour labels,
kind, edited/unedited, camera / lens / keyword pickers, Clear and "Save as
Smart Album…" — every control drives library.filter. Two rows when the
grid is narrow. Empty grid under a filter says so. Headless test clicks
the bar's widgets and saves a smart album.

docs/parity.md: rows for smart albums, stacks, virtual copies, compare,
survey, filter bar, menu bar updated; finished top gaps removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:31:58 -04:00
Brandon Thomas bc2cda97cf M13.2: Photo Merge commands, dialog with live preview, CLI
- engine: `merge.hdr`, `merge.panorama`, `merge.hdrPanorama` (bracket size from
  EXIF); MergeJob (Send) with progress/cancel, preview renders through the develop
  pipeline (deghost overlay, auto-crop frame); finish writes <name>-HDR/-Pano.dng
  next to the first source, imports, selects, applies Auto Settings / auto crop
- ui: Photo Merge dialog (HDR / Panorama / HDR Panorama) with live preview on a
  worker thread, background final merge with progress toast; context menu and
  Ctrl+H / Ctrl+M (Ctrl now means the physical Control key on macOS)
- cli: `merge hdr|panorama|hdr-panorama`, `synth-merge` for synthetic inputs
- merge::synth: procedural brackets (DNG) and panorama views; docs/merge.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:30:03 -04:00
Brandon Thomas ce89ab5375 M4.1: Straighten tool (drag along the horizon or a vertical) + Auto straighten (crop.autoStraighten)
Auto uses Upright's consensus level angle as the crop angle; sign verified on a tilted test image
and pinned by a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:28:29 -04:00
Brandon Thomas b36920844b Merge main into the polish branch
Resolved: album.create (main's one-liner + smart: None), menus (panels in
the Window menu, main's dialog.newFolder ⇧⌘N, app.showInFinder and
app.exportPrevious placed in the menu layout), shortcuts (native-menu skip
+ main's aliases and UI-over-engine rule; Shift+X/Shift+U aliases go
through the culling path; my Shift+P/X/U flag-and-advance dropped in
favour of the aliases). X and Delete stay egui-handled (contextual keys).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:26:18 -04:00
Brandon Thomas 8b7fb306dc LR-EDIT-LIGHT-CURVE-TAT: targeted adjustment tool for the tone curve and the colour mixer
develop.targeted {target: curve|hue|sat|lum, x, y, delta, channel?}: samples the photo at the
point on a proxy render with the target stage and everything after it neutralized, then
nudges the parametric curve region (by the sampled tone and the splits), moves/adds a point
on the master/RGB point curve, or adjusts the colour-mixer (or B&W mix) bands weighted by
their share of the sampled OkLCh hue. UI: target buttons in the curve editor and the colour
mixer; a vertical drag on the loupe sends one step per unit inside one interaction (one
undo step). New original vector icon (Target) in icons.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:25:29 -04:00
Brandon Thomas 490fe943cd LR-EDIT-COLOR-POINTCOLOR: Point Color — sampled colours with shift, variance, range and visualize
Up to 8 OkLCh samples (pointColor.pick samples the colour after the mixer on a proxy render);
per sample hue/saturation/luminance shift, variance (compress/expand the spread around the
sample) and an overall + per-axis range with soft edges, applied after the colour mixer in
ColorOps and its WGSL twin (equivalence case). Indexed control specs
(pointColor.<i>.hueShift …) so develop.set/controls/MCP address each sample. Visualize range
is a render overlay (RenderRequest::overlay) drawn after either renderer: the sample's own
edit is left out and colours outside the range turn grey. Point Color flyout in the Color
section with swatches, eyedropper, sliders, visualize and delete.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:18:28 -04:00
Brandon Thomas 673e1bf6cb M16.1: native macOS menu bar + in-window menus from the command registry
menubar.rs builds File/Edit/View/Photo/Window/Help from engine + UI
commands (layout table for order and separators, parameterized submenus
for rating, flag, label, sort, add to album), with live labels ("Undo
Exposure"), enabled and checked state; ui.menu.tree exposes it. The
desktop app installs it natively on macOS with muda (pure Rust over
AppKit; no GTK — muda is a macOS-only dependency): app menu (About,
Services, Hide, Quit), Window menu (Minimize/Zoom/Bring All to Front),
Help menu; per-frame state sync; accelerators without Cmd and the text
editing ones step aside while a text field has focus; natively handled
shortcuts are skipped by the egui handler. Other hosts (web, Windows,
Linux) get the same menus in the top bar. Panels/sections/tools move to
the Window menu. Verified with System Events: menus listed with live
enabled state, View > Survey dispatches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:18:02 -04:00
Brandon Thomas 6015af99b7 M2.11: before/after top/bottom (⌥Y) and split top/bottom (⇧⌥Y)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:16:52 -04:00
Brandon Thomas 96903a5fff docs: parity — export with previous, show in finder, new folder, delete key
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:13:02 -04:00
Brandon Thomas b718d9248a M10.9: Export with Previous (⌥⇧⌘E), export dialog remembers the last options (prefs.json); Show in Finder (⌘R); New Folder… (⇧⌘N)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:10:46 -04:00
Brandon Thomas 2c37c26d1e M16.1: ⌫ deletes photos again (was bound to forward-delete), and never in the Masking/Remove/Red Eye panels
"Delete" parsed as egui's forward-Delete key, so the Mac ⌫ key (Backspace) didn't delete. In the
Masking panel ⌫ now deletes the active mask; while retouching it does nothing. ⇧⌘N no longer
fires album.create (which needs a name).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:10:46 -04:00
Brandon Thomas 37d9ecf0e9 LR-EDIT-LIGHT-CURVE-REFINESAT: curve Refine Saturation
curve.refineSaturation (0..100, default 100 = the curve's own saturation): after the tone
curves, the chroma around luma is rescaled so saturation moves from the curve's towards the
pre-curve value, keeping the curve's tone. CPU (finish::refine_saturation) and GPU twin,
equivalence case, slider under the curve editor, crs:CurveRefineSaturation import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:06:14 -04:00
Brandon Thomas 543fdde41e M13.1: lightcraft-merge crate — HDR merge, panorama stitching core
New L3 crate `crates/merge` (no UI deps), clean-room from published papers:
- features: Harris/MOPS-style oriented patch descriptors, ANMS, ratio-test matching
- ransac: normalised DLT homographies, similarity/translation, adaptive RANSAC
- hdr: brightness ordering, auto-align (model selection), robust exposure-ratio
  estimation chained to the reference, saturation-aware weighted radiance merge,
  deghost None/Low/Medium/High with feathered masks + overlay
- pano: pairwise matching, Brown–Lowe verification, focal from homographies,
  rotation+focal bundle adjustment (LM), straightening, spherical/cylindrical/
  perspective + auto, gain compensation, max-weight seams, multi-band blending
  in log space, boundary warp, auto crop, fill edges (push–pull)
- output: LinearRaw DNG carrying source colour tags; raw crate gains Deflate
  tiles (16/32-bit float with FP predictor, 16-bit int with predictor 2)

Tests: synthetic brackets (alignment < 0.3 px, EV < 0.02, radiance error
noise-limited, ghost removal) and synthetic panoramas (relative rotations
< 0.1°, focal < 1 %, error vs ground truth, seam test).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:06:00 -04:00
Brandon Thomas 1e52736703 M1.15: keyboard-first culling — Compare, Survey, Auto Advance
Compare view (Shift+C): select | candidate side by side with synced zoom and
pan; arrows move the candidate, rating/flag/label keys act on the active
(candidate) photo, swap / make-select. Survey view (N): the selection tiled,
keys act on the active photo, hover x removes from the survey. Auto Advance
setting (Photo menu, bottom bar toggle) and Shift+P/X/U flag-and-advance.
Escape returns to Detail. Headless keyboard test.

Fix: render-job keys include the photo id, so a view slot never shows one
photo's result as current for another with identical settings/size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:05:24 -04:00
Brandon Thomas 48efc6df52 LR-EDIT-CALIB: Calibration — shadows tint and R/G/B primary hue/saturation
White-preserving primaries matrix (OkLCh hue rotation / chroma scale of each Rec.2020
primary) plus a luminance-preserving green/magenta shadows tint, applied in scene-linear
light before the tone map. CPU (colorops::calibrate) and GPU (finish.wgsl) with an
equivalence case; seven control specs (calibration.*), a settings group, crs: import
(ShadowTint, Red/Green/BlueHue/Saturation) and a Calibration section at the bottom of the
Edit panel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:01:48 -04:00
Brandon Thomas e32c4549a7 Merge branch 'worktree-agent-ace55e56f06440d66'
# Conflicts:
#	xtask/src/main.rs
2026-10-01 10:00:06 -04:00
Brandon Thomas 0571d8e438 M9.7: virtual copies — new catalog entries sharing the original file
Photo.copy_of / copy_name. photo.virtualCopy (Cmd+') clones the photo with
independent settings and history, names it "Copy N" (numbered per master),
adds it to the original's albums and stacks it (expanded) right after the
original; one undo step for several copies (Session::merge_undo). Copies
never write XMP sidecars. "Copy N" badge in the grid, Info panel line,
copy:yes search token. Also: Info panel stars laid out in a row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:58:36 -04:00
Brandon Thomas 90f5ebac57 xtask: parity — docs/parity.md reference check (in ci) and summary
cargo xtask parity verifies every cmd:/ctl: id and repository path cited in
docs/parity.md exists (engine commands and controls via lightcraft-cli, UI
commands from UI_COMMANDS), that row ids are unique and statuses valid, and prints
the per-section summary; --write refreshes the summary table. ci runs the
reference check after the tests. Parser, checker and summary are unit-tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:53:30 -04:00
Brandon Thomas 5434c5d172 M1.14: stacks — group, expand/collapse, top of stack, auto-stack by time
Stack entity in the catalog (AddStack/RemoveStack/SetStack ops with
inverses, validation, snapshot + journal replay property test). Helpers
build ops for group (merging stacks), ungroup, remove, set top, collapse,
and stack_with_ops() for future merge results. Views arrange stack members
together and collapse to the top. Commands stack.* (Cmd+G, Cmd+Shift+G, S,
Shift+S, expand/collapse all, stack.auto with preview); grid/filmstrip
count badges (click to toggle), context menu, Auto-Stack dialog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:52:11 -04:00
Brandon Thomas 62a2a341e6 docs: Lightroom parity tracker (docs/parity.md)
One row per catalog feature, menu item, desktop shortcut and Classic extra with
tier, status, evidence (command/control ids, files) and what is missing; per-section
summary, ordered Top gaps, and a shortcut conflict review. Linked from README
(Feature status) and AGENTS.md (pick work from Top gaps).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:51:10 -04:00
Brandon Thomas c2a09ed8e9 M5.6: preview-source cache holds 4 sources (current + prefetched neighbours) instead of 10 (~300 MB less)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:50:52 -04:00
Brandon Thomas 382cca190c M16.1: shortcuts — Lightroom-desktop secondary bindings, no double-firing keys
Adds secondary bindings for existing commands (Cmd+D select none, Shift+E export
dialog, Space toggle zoom, Shift+M create version, Shift+X / Shift+U reject /
unflag and advance), listed in Help > Keyboard Shortcuts. Shift+Y no longer also
toggles the History panel; a UI command now shadows the engine command it wraps
on the same key (W, Cmd+Shift+I). New tests reject conflicting bindings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:50:20 -04:00
Brandon Thomas 50741eceb4 M1.13: smart albums — saved filters that update live
Album.smart holds Filter rules (rating, flag, label, kind, edited, keyword,
camera, lens, capture date range, text, album); SetAlbumRules op with
inverse, validation (no nested smart albums, no deleted), journal replay
property test. album.createSmart (from the current view or explicit rules),
album.setRules; New Smart Album dialog, smart icon, live counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:47:38 -04:00
Brandon Thomas d4e28f5cf3 M5.4b: X-Trans previews bin 3× (~2000 px) instead of a full demosaic: 1219 → 136 ms CPU
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:47:16 -04:00
Brandon Thomas 7e727c9063 docs: README — instant culling numbers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:44:39 -04:00
Brandon Thomas 3586c772cb M1.7: a raw with its import settings is not 'edited' (grid badge, edited filter)
Photo::import_defaults moves into the catalog; is_edited compares against it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:43:33 -04:00
Brandon Thomas 9886d49823 Merge branch 'worktree-agent-a69dcec0f0c61bc39'
# Conflicts:
#	crates/engine/src/media.rs
#	crates/ui-egui/src/render.rs
2026-10-01 09:39:30 -04:00
Brandon Thomas 5f74b49977 M10.4: JPEG 4:2:0 chroma straight from 2×2 RGB means (no full-size chroma planes); identical output
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:29:59 -04:00
Brandon Thomas e0feb47ab8 M5.7: cargo xtask bench — render benchmark history + CPU-time regression check
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:25:13 -04:00
Brandon Thomas 570d03e202 M5.4c: prefetch filmstrip neighbours; GPU device created at startup off the UI thread
- Once the current photo's loupe render is done, the next and previous photos in filmstrip
  order get a background loupe job (Slot::Prefetch, priority below on-screen thumbnails,
  once per settings): the decoded 2560 px source is kept in the engine's bounded preview
  LRU and the view render is cached, so stepping to a neighbour shows its developed image
  in ~50 ms and then re-renders only on the GPU.
- lightcraft_gpu::warm_up (called when the app is created) builds the device and kernels on
  a background thread; gpu::ready() answers without blocking and ui.inspect uses it, so the
  UI thread never waits for device creation.
- docs/gpu-pipeline.md: opening-a-photo section.

Stepping through 24 MP raws in the loupe (real app): full render 220–580 ms → 45–62 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:23:36 -04:00
Brandon Thomas 5fd5384bfb M5.4a: instant first view — cached view render / embedded raw preview, then the full render
- engine: QuickJob — what to show the moment a photo opens: the loupe's last render for the
  current settings (kept by Session::loupe_job under a size-independent "view" key in the
  preview cache, memory + disk with a library; the disk JPEG is written off the render
  thread via PreviewCache::put_deferred), else the embedded camera JPEG of a raw that still
  has its import look (import::has_import_look), else a thumbnail-level render.
- Grid / filmstrip: an unedited raw without a thumbnail shows its cached thumbnail, else its
  embedded preview (Slot::ThumbQuick) at once; the real rendered thumbnail follows at
  background priority (same cache keys, so it still ends up matching the develop render;
  edited photos render directly).
- Loupe (detail, compare, filmstrip selection): Slot::Preview holds the stand-in until
  Slot::Main has the photo; the full render then replaces it in place (no blank frame).
- files::embedded_preview_srgb + PreviewLoader hook (desktop/CLI/MCP and web).
- ui.inspect → loupe {photo, source: render|cached|embedded|small|thumb|none, pending}.

Real app (24 MP raws, loaded machine): first image 15–90 ms (thumbnail, then embedded
preview), full render 0.2–0.5 s (was 1.0–2.4 s with nothing shown before it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:18:50 -04:00
Brandon Thomas b13274f711 docs: README — web build persistence and workers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:11:10 -04:00
Brandon Thomas 8e5d6cb521 Merge main (GPU pipeline) into the web branch 2026-10-01 09:02:23 -04:00
Brandon Thomas 3009f836b2 M0.14: web bundle size — fat LTO, size-optimized glue, precompressed output
web profile: fat LTO, 1 codegen unit, strip, panic=abort, opt-level "s", with the per-pixel
crates (pipeline, raster, color, develop, geom, raw, codecs, scenes, preview, JPEG/PNG codecs)
kept at opt-level 3: "s"/"z" there cost +50 %/+130 % render time.
wasm 15.74 MB → 13.53 MB; brotli 3.47 MB → 3.09 MB (gzip 5.25 → 4.58 MB); worker slider job
3.8 → 4.0 ms (noise).

cargo xtask web writes .gz (-9) and .br (-11) copies next to each bundle file (pure Rust:
flate2, brotli) and prints a size table; --serve negotiates Accept-Encoding and serves them
with Content-Encoding. Measurements in docs/web.md → Bundle size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:02:15 -04:00
Brandon Thomas ec6637f238 M5.4b: faster raw path — bin previews/thumbnails straight from the mosaic
- lightcraft-raw: RawImage::develop_binned(k) — k×k superpixel binning of the CFA
  (Bayer k even, X-Trans k multiple of 3), black/white normalised row by row, default
  crop only, clipping preserved for highlight reconstruction. 2560 px previews of a
  24 MP raw bin 2×, thumbnails 4–8×; exports / 1:1 still demosaic (AHD) at full size.
- highlight::reconstruct and its pyramid fill are rayon-parallel (bit-exact).
- X-Trans directional demosaic: precomputed per-phase neighbour offsets and no
  reflection in the interior, no per-pixel allocation (bit-exact, tested against the
  reference formulation): 2.9 s → 0.75 s CPU at 24 MP.
- files::load_bytes runs on a rayon worker so its short parallel loops don't each
  wait for the pool to wake (dominant cost on a loaded machine); LIGHTCRAFT_PROFILE
  prints per-stage source timings; files::bin_factor picks the block size.
- render_bench ONLY=source: per-file decode / normalise / demosaic / binning / load.
- Fix: u64 overflow on hostile IFD sizes in DNG/ARW/NEF raw-IFD selection (proptest).

load_bytes(2560) CPU time, ARW a7 III 24 MP: 2347 → 240 ms; X-T20: 3120 → 1220 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:01:53 -04:00
Brandon Thomas 8f2058a124 M0.13: web render workers — pipeline renders off the main thread
Renders run in dedicated module Web Workers, each a second instance of the app's wasm module
(index.html compiles it once and posts the WebAssembly.Module to worker.js). A job crosses as
JSON (WireJob: source, settings, request); the worker reads originals and cached thumbnails from
browser storage itself, keeps decoded sources and per-view stage caches, and transfers RGBA back.
Jobs follow the worker that decoded the photo; the loupe waits for it. No wasm threads or
SharedArrayBuffer needed; falls back to inline rendering if no worker starts (?workers=0).

Thumbnail disk cache on the web: thumbs/<key>.jpg in OPFS/IndexedDB with an index on the main
thread, pruned to the desktop budget (2 GB → 80 %, LRU).
ui-egui: RenderOffload hook in the Renderer (queue/priorities/dedup stay in the UI).
engine: RenderJob.origin + MediaCache::origin_ref; preview: encode_jpeg/decode_jpeg public.
xtask web: copies worker.js; --serve sends COOP/COEP/CORP (documented in docs/web.md).

Headless Chrome, cold load + grid scroll, 24 demo photos: main-thread busy 12.4 s → 0.7–1.1 s,
long tasks 23 (max 1.1 s) → 0; grid filled in 6.3–8.4 s vs 13–16 s; slider job 4 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:36:15 -04:00
Brandon Thomas d1e00bc3cc docs: README — GPU pipeline and performance numbers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:28:14 -04:00