Commit Graph

85 Commits

Author SHA1 Message Date
Brandon Thomas 07c3f0261c M5.2d: geometry resample on the GPU
The source is uploaded once per view (kept with the GPU stages) and resampled on
the device: orientation as an integer pixel map, the Mitchell area prefilter
with the CPU's exact taps, then one bilinear resample through the
crop/straighten/flip affine or through the full warp (inverse perspective,
manual distortion, embedded DNG WarpRectilinear per plane, lateral CA, manual
and embedded vignetting gain, blank outside the source). `Frame::sample_plan`
(new) decides prefilter size and mode for both renderers; sources larger than
the device's buffer limit are still resampled on the CPU.

Equivalence: all 8 orientations with and without crop/straighten/flip, embedded
lens + perspective: max 1 LSB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 02:34:40 -04:00
Brandon Thomas 2dfd65b39a M5.2c: mask evaluation on the GPU
Linear and radial gradients, luminance and colour ranges and brush strokes are
evaluated by a WGSL kernel per component, combined (add/subtract/intersect,
component and mask invert, amount) into one device buffer of alpha planes that
the per-pixel stage reads directly. Brushes use the CPU's dabs
(`masks::brush_dabs`, shared with the rasterizer) and the closed form of the
flow accumulation (1 − Π(1 − a·flow), capped at density). Sky / Subject /
Background and other heuristic shapes, and brushes of more than 4096 dabs, are
evaluated on the CPU inside the same render and uploaded.

New equivalence case with CPU-evaluated shapes, subtract/intersect and amount;
all cases stay at max 1 LSB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 02:32:50 -04:00
Brandon Thomas 9ec34d774f M5.2b: spatial planes, white balance and noise reduction on the GPU
Gaussian blur (three box passes each way, running sums per 16-pixel chunk), the
separable resample with the CPU's exact taps (uploaded tables), the guided filter
and the fast guided filter (coefficients on the subsampled grid), log luminance,
dark channel + airlight (percentile of every 7th value read back), white balance
and luminance/colour noise reduction now run as WGSL kernels. A render records
everything into one command encoder and reads back only the output (and the
airlight samples); defringe and spot removal still run on the CPU when used.

Equivalence unchanged: max 1 LSB, mean < 0.001 LSB over all test cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 02:31:19 -04:00
Brandon Thomas c7a1e3e2f6 M5.2a: lightcraft-gpu crate (wgpu compute) with the per-pixel stage on the GPU
New L3 crate `lightcraft-gpu`: a wgpu device (Metal/Vulkan/DX12; none on wasm32),
WGSL kernels compiled once, buffer/dispatch/readback helpers, and a renderer that
runs the per-pixel stage (tone map, highlights/shadows, clarity/texture/sharpen,
dehaze, local mask terms, OkLCh colour ops, grading, vignette, gamut map, sRGB
encode, curves, grain) on the device. Earlier stages still run on the CPU and are
uploaded (per-stage hybrid). Stage results are cached per view next to the CPU
StageCache (new `StageCache::extension`).

The CPU pipeline stays the oracle and shares its resolved parameters with the
kernels: `finish::FinishParams` / `mask_terms`, `pipeline::plan` (stage-cache keys),
`local::plane_sigmas` / `nr_params` / `wb_matrix_for`, exact LUTs and OkLab
matrices exported for the shader prelude. Equivalence tests (skip without an
adapter): mean |d| < 0.5 LSB, max <= 3 LSB; measured max 1 LSB on Apple M4 Pro.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 02:29:54 -04:00
Brandon Thomas 179aa90f01 Merge M5 CPU performance (stage cache, full-res source level, parallel planes)
Integrated with M4 optics/Upright: render_impl resolves Upright and uses frame_for (lens + auto CA)
on the cached source; defringe runs in the cached linear stage and optics settings are part of its key.
Stage-cache equivalence test extended with optics/geometry steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 02:05:14 -04:00
Brandon Thomas 4195556551 M5.2: GPU pipeline design note (wgpu compute, CPU oracle, stage-cache mapping)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 02:01:33 -04:00
Brandon Thomas 74b94e089a M5.3: compute independent spatial planes side by side
The guided base, clarity band, texture band and dark channel (and the paired blurs inside the
guided filter) are independent; each alone scales poorly (subsampled grids, short rows), so they now
run under raster::par_join. Same arithmetic, identical pixels. render_bench gains a per-feature
breakdown of the per-pixel stage (ONLY=finish).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 02:01:25 -04:00
Brandon Thomas 8c49450872 M10.2: export dialog uses themed sliders, choice buttons and a label column
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:55:36 -04:00
Brandon Thomas 844c60c9d4 M4.4: Upright Auto no longer skews natural scenes
Vanishing points need structural support (≥3 inliers, length, spread, 60% family consensus) and must lie
outside the frame; Level needs consensus; Auto confirms a vertical-implied roll with the horizontals and
limits its level fallback to ±5°. Regression test: Auto ≈ identity on all 24 demo scenes; building tests
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:54:13 -04:00
Brandon Thomas 0a44dbff3e Merge branch 'worktree-agent-a26e56615434931a6' 2026-10-01 01:49:23 -04:00
Brandon Thomas 37ab239b7b M0.10: headless UI rendering — CPU rasterizer for egui, ui.screenshot headless + fallback, lightcraft-cli snapshot
- ui-egui/softpaint: pure-Rust rasterizer for tessellated egui output (gamma-space premultiplied
  blending like egui-wgpu, bilinear texel-centre sampling, scissor clips, tie-break rule so shared
  edges blend once, parallel row bands, deterministic) + CPU texture mirror from TexturesDelta.
- ui-egui/headless: HeadlessView (offscreen context with our fonts/theme) and Headless (windowless
  app session: logic+ui per frame, answers viewport Screenshot/InnerSize/Close, settle loop,
  control requests through the shared handler).
- ui.screenshot {headless: true} in the running app renders from the logic tick (works while the
  window is occluded / display asleep); windowed captures fall back to headless after 2 s.
  Screenshots now wait for in-flight renders, not just queued ones.
- lightcraft-cli snapshot [--demo|--library DIR] [--script f.jsonl] -o out.png --size WxH --scale S.
- Fixes found with it: dialog backdrop was painted over the dialog itself (now an area below the
  window); dialog content padding; crop overlay options use our buttons (registered widget ids).
- Docs: control-protocol.md (headless section), AGENTS.md (prefer headless when unattended).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:48:48 -04:00
Brandon Thomas 5b55e2f6cc docs: README — optics, geometry and Upright status
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:30:44 -04:00
Brandon Thomas 054a3a2081 M0.8: lightcraft --help / unknown options no longer launch the GUI
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:30:20 -04:00
Brandon Thomas 0dc9e6d759 Merge branch 'worktree-agent-a454c39b02296b0eb'
# Conflicts:
#	crates/engine/src/files.rs
2026-10-01 01:22:21 -04:00
Brandon Thomas b00dda4cec M4.4: Upright — Auto/Level/Vertical/Full from detected lines + Guided upright (up to 4 guides)
- pipeline::upright: own gradient-based line-segment detector (level-line region growing seeded from the
  strongest gradients, weighted principal-axis fit, elongation test); vanishing points by RANSAC over
  segment pairs + least-squares refinement (smallest eigenvector of Σ w·l·lᵀ)
- corrections as a virtual camera rotation K·R·K⁻¹ (vertical VP → y axis; Full also horizontal VP → x axis
  with focal length from VP orthogonality when plausible); Level = rotation; Auto = Full/Vertical when
  moderate, else Level; all re-centred
- Guided: guides stored in normalized lens-corrected coordinates; 2+ vertical/horizontal guides define the
  vanishing points, a single guide levels
- the analysis runs on the lens-corrected image at 1024 px and the result is stored in
  geometry.upright_transform (identical preview/export); settings naming a mode without a transform
  (XMP, CLI) are resolved at render time
- commands geometry.upright {mode} (Update = run again) and geometry.guides {guides, add}
- UI: Upright mode buttons, Update, Draw/Clear Guides in the crop panel; on-canvas guide drawing that
  follows the warp
- tests: segment detector on known lines; Vertical/Auto/Full fix converging verticals; Level fixes a tilted
  horizon; Full fixes both families; guided from two guides; analysis resolution independence; lazy vs
  stored transform; engine commands

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:16:18 -04:00
Brandon Thomas 8cbdece6f2 Merge main into geometry/optics branch (embedded lens + XMP sidecar import, ProbeInfo fields)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:08:21 -04:00
Brandon Thomas eca6c9e2e2 docs: README — RAW formats and XMP interop status
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:08:10 -04:00
Brandon Thomas 36e80900bb docs: AGENTS.md — merge agent branches one at a time with CI in between
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:08:02 -04:00
Brandon Thomas 7d421dc3d3 M3.8: fix merge — unsupported-raw preview probe carries embedded XMP; AGENTS.md per-agent ports/scratch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:07:51 -04:00
Brandon Thomas 733e59f84e M4.3: geometry — manual transforms as one homography in the geometry frame, Constrain Crop
- pipeline::transform::manual: Vertical/Horizontal keystone as a virtual camera rotation K·R·K⁻¹ (re-centred,
  local scale preserved), Rotate, Aspect, Scale, Offset X/Y composed into one homography
- the homography lives in the Warp: output → crop affine → inverse perspective → lens model → source, still a
  single resample; masks/crop/spots stay in transformed normalized coordinates
- Constrain Crop: the effective crop shrinks about its centre until no blank (outside-source) area remains;
  aspect unchanged
- UI: Constrain Crop checkbox in the crop panel's Geometry section
- tests: slider signs, known keystones straightened (3 slider combos), constrain removes blank corners,
  preview vs export match

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:07:19 -04:00
Brandon Thomas 4dd57efcac Merge branch 'worktree-agent-af78b5603e6882a3f' 2026-10-01 01:06:29 -04:00
Brandon Thomas 3ef4eb0687 M9.2: preset files — .lcpreset import/export (groups preserved), XMP presets via crs: mapping
- engine presets.rs: .lcpreset JSON format {format: "lightcraft.preset", version, presets[]},
  parse_preset_file (.lcpreset file / bare preset / array, or XMP), folder expansion,
  Session::add_presets (identical presets skipped, id clashes get fresh user.* ids, built-ins
  never replaced).
- crs::preset_from_xmp: crs: fields → partial settings, crs:Name/crs:Group (fallback: file name /
  "Imported Presets").
- Commands preset.import {paths} and preset.export {path, ids?, group?}; UI commands
  file.importPresets / file.exportPresets (File menu, with native open/save dialogs on desktop),
  Presets panel "⋯" menu (Import Presets…, Export User Presets…) and group context menu
  (Export Group…).
- Tests: export → import round trip keeps groups/settings, duplicates skipped, clashing ids,
  malformed files reported, hand-written XMP preset imports and applies.
- docs/xmp-interop.md: preset file section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:05:26 -04:00
Brandon Thomas b6c9f66857 M10.8: keep lc:settings JSON readable in sidecars (escape only <, >, & in element content)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:04:00 -04:00
Brandon Thomas 452521a16f M5.1: full-resolution source level for exports larger than a preview
Exports above 2560 px were rendered from the 2560 px preview proxy and upsampled. SourceLevel::Full
(native size; procedural demo scenes at their nominal size) is now selected for outputs larger than
a preview; the media cache keeps only the last full-size original. render_bench gains a batch-export
scenario (decode + render + JPEG per file).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:03:48 -04:00
Brandon Thomas a0da99660d M4.2: optics — manual distortion/vignetting, lateral CA (auto + manual), defringe, DNG-embedded profile corrections
- pipeline::optics::Warp: one inverse map (lens-corrected → source) inside the single geometry resample:
  manual radial distortion, embedded DNG WarpRectilinear (per plane), per-channel lateral CA scale,
  radial vignetting gain (manual + embedded FixVignetteRadial); all in normalized radii
- automatic lateral CA estimate: radial edge profiles matched sub-pixel per plane, robust LSQ fit of
  displacement = α·r (cached per source)
- defringe: purple/green hue ranges (OkLab hue) desaturated next to high-contrast edges, width relative
  to the image long edge
- "Enable Lens Corrections" applies only lens data embedded in DNGs (opcodes are no longer baked at decode;
  stored on the photo record, on by default at import when present). No LCP data anywhere.
- new controls: optics.caRed/caBlue, defringe hue ranges; Optics panel: Manual sub-section, Defringe flyout
- tests: barrel grid straightened, CA removal (auto + manual), embedded corrections toggle/scale, preview vs
  export resolution independence, CA estimator accuracy, orientation of embedded lens data

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:57:47 -04:00
Brandon Thomas 0892148bab M5.3: faster per-pixel stage and row-parallel guided filter
- finish: sRGB encode through an interpolated 4096-entry table (< 2e-5 from the exact curve,
  tested), exp2 instead of powf(2, x).
- guided / guided_fast: coefficient and output passes run row-parallel (were serial loops);
  Image::zip_map.
- blur_bench reports process CPU time next to wall-clock (meaningful on a loaded machine).
- RENDER_CACHE_VERSION 3 (exposure-after-filters and the table change pixels by <= 2 LSB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:57:02 -04:00
Brandon Thomas 42d2298118 M5.3: stage cache — slider drags redo only the stages the setting feeds; draft quality during drags
- pipeline: StageCache + render_cached: resampled source, white-balanced/retouched/denoised image
  and each spatial plane (log L, guided base, clarity, texture, dark channel + airlight) are keyed
  by exactly their inputs; keeps a draft-size and a full-size entry warm.
- Exposure moved after the spatial filters (applied per pixel; planes shifted by EV): identical up
  to rounding (test: <= 2 LSB vs. exposing the source), so exposure drags hit the cache too.
- engine: RenderJob::draft / with_stages; ui: loupe and before views own a StageCache, loupe
  renders at Draft quality while interacting.
- engine example render_bench (min-of-N wall + process CPU time).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:42:50 -04:00
Brandon Thomas 07359a735e M3.8: embedded previews for every raw container + engine fallback; raw corpus
- embedded_preview: CR3 PRVW/THMB boxes (plus RAF, RW2, ORF, PEF from the
  format commits); stops at the first valid box of each kind
- engine: raw variants that report Unsupported are probed and shown from
  their embedded JPEG (orientation from the JPEG or the raw), not an error
- crates/raw/tests/corpus.rs: every corpus/raw file is recognised, has a
  JPEG preview (DNG optional) and decodes or is a known-unsupported
  variant; prints decode timings
- xtask corpus --download: 37 CC0 raw.pixls.us samples (matched by SHA-256)
- ROADMAP/crate docs: format coverage, NEF Huffman / RW2 format 4 /
  compressed ORF+RAF gaps, neutral colour-matrix fallback

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:40:09 -04:00
Brandon Thomas 8af27c11b1 M10.8: XMP sidecars — save/read metadata + develop settings, auto-write, crs: interchange
- crates/engine/src/sidecar.rs: <stem>.xmp (default) or <file>.xmp next to originals with rating,
  label, title, caption, copyright, creator, keywords, capture time/GPS, pick flag + full develop
  settings in lc:settings; atomic writes; read on import (sidecar wins for metadata, edits restored)
  and via photo.readMetadataFromFile (one undo step); raw/DNG embedded XMP used without a sidecar.
- Commands: photo.saveMetadataToFile (Cmd+S), photo.readMetadataFromFile, library.xmpPreferences
  {autoWrite, naming}, library.toggleAutoWriteXmp; prefs persisted in the library's prefs.json.
  Auto-write rewrites sidecars after each committed change (not mid-drag; undo/redo too).
- crates/engine/src/crs.rs: crs: develop fields (PV2012 light, WB abs/incremental, presence, HSL,
  B&W mix, parametric + point curves, colour grading, sharpening, NR, vignette, grain, optics,
  geometry, crop) → partial settings; documented in docs/xmp-interop.md.
- meta: write_xmp_lc for extra lc: properties; crs namespace canonicalised on read (never written).
- Tests: packet round trip, crs mapping with hand-written snippets, save → delete library →
  re-import restores edits, auto-write + prefs persistence, foreign sidecar, DNG-embedded XMP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:36:19 -04:00
Brandon Thomas daa32c3e4a Merge main into raw formats branch (nrw/raf/orf/rw2/pef/cr3 in the open dialog, nrw import)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:31:23 -04:00
Brandon Thomas f5b28bfca9 M11.3: Olympus ORF decoding (uncompressed)
16-bit LE words (12-bit-in-top-bits detected and shifted), 12-bit rows
of LE 32-bit words read MSB-first (black-box), maker-note
ImageProcessing crop, BlackLevel2, WB_RBLevels for old- and new-style
notes, CFA phase from the data (GRBG/RGGB), CameraSettings preview.
Compressed ORF reports Unsupported (documented gap).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:26:58 -04:00
Brandon Thomas 008e8d0f8c docs: README feature status (persistence, MCP libraries, export metadata/watermark)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:19:08 -04:00
Brandon Thomas 3f86346c5d M11.3: Pentax PEF decoding (uncompressed and Huffman)
Huffman table from maker note 0x0220 (codes/lengths, black-box layout),
T.81 difference categories, same-colour horizontal prediction with
per-row-parity vertical seeds; BlackPoint, WB levels, maker-note image
area or dark-column trimming. Unit round-trip test with our own table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:17:42 -04:00
Brandon Thomas 55c5c6f0c6 M1.4: persistent libraries for headless MCP (lightcraft-cli mcp --library DIR); bump render cache version
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:17:38 -04:00
Brandon Thomas fa77faeb6b Merge branch 'worktree-agent-a51e3311b37df5c4e' 2026-10-01 00:13:33 -04:00
Brandon Thomas 424e23e6e8 M10.6: text watermark on export (Inter, size relative to image, 9 anchors, opacity, shadow)
ab_glyph (Apache-2.0, pure Rust) rasterizes the already-attributed Inter font.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:13:33 -04:00
Brandon Thomas d752f0e8db M11.3: Panasonic RW2 decoding (packed 12/14-bit)
Black-box derived layout: 0x4000-byte chunks stored rotated by 0x1ff8,
16-byte LE blocks read LSB-first (10x12-bit or 9x14-bit samples).
Sensor borders, CFA code, per-channel black, linearity limit, WB levels,
ISO; JpgFromRaw preview via a generic undefined-tag JPEG candidate.
The quantised raw format 4 reports Unsupported (documented gap).
Adds a rawdump debug example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:11:34 -04:00
Brandon Thomas bdf479baf6 M0.4: demo scenes — remove vertical banding in terrain shading (wide slope stencil, smooth light direction, depth warp, aerial perspective)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:09:30 -04:00
Brandon Thomas 1da0263d56 Merge main into persistence branch (keep JobPool renderer; wasm inline job timing via now_ms)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:09:07 -04:00
Brandon Thomas 759d3c7f8a M1.3: import (recursive folders, content-hash duplicates, add vs copy)
- engine::import: expand files/folders recursively (sorted; hidden entries
  and the library folder skipped), skip known paths, probe + hash in
  parallel (std:🧵:scope), skip duplicates by content hash (against
  the library and within the batch), add in place or copy into
  <library>/Originals/YYYY/YYYY-MM-DD/ with unique names; one undoable op.
- library.import {paths, mode: add|copy, album?} returns
  {imported, duplicates[{path, existing, reason}], failed, scanned}.
- probe_bytes records the content hash (SipHash-1-3 128) on every photo.
- system clock for import/edit times on native (Session::with_system_clock).
- app: command-line folders are expanded by the engine (re-importing the
  same folder is a no-op); example gen_photos writes procedural test JPEGs
  (never committed).
- Measured (200 procedural 2000 px JPEGs, M-series): import 2.25 s; reopen:
  library load 1.7 ms, control ready 0.12 s, first thumbnail 0.17 s,
  visible grid drawn 0.48 s (thumbnails from the disk cache).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:04:07 -04:00
Brandon Thomas 77ff166841 Merge branch 'worktree-agent-ac4c81fc23fc995ec' 2026-10-01 00:03:41 -04:00
Brandon Thomas c6954d5bd9 M10.5: export metadata policy (all / all except camera / copyright / none, remove location) with EXIF + XMP embedding
New lightcraft_meta::write_exif (IFD0 + Exif + GPS IFDs; round-trip tested).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:03:38 -04:00
Brandon Thomas 2d2bc2ad8f M0.11: web build (WASM)
apps/lightcraft-web runs the egui UI in the browser (eframe web, glow/WebGL2):
procedural demo library on load; in-memory imports via file picker
(Cmd+Shift+I) and drag-and-drop (MemStore + bytes-based engine hooks);
export goes through app.export and downloads in the browser; `?bench` logs
first-frame and slider latency. `cargo xtask web [--serve [port]] [--dev]`
builds with the matching wasm-bindgen CLI into <target>/web/; the web app is
part of the CI wasm check. Wasm fixes: lazy profiling Instant in pipeline,
web-time clocks in ui-egui (render timings were 0 on wasm). Docs: docs/web.md.
No persistence on web yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:01:18 -04:00
Brandon Thomas 3cc97c58d1 M5.1: cache-friendly Gaussian blur (no transposes; in-row horizontal passes, banded running-sum vertical passes) + per-stage pipeline profiling
3–10x faster blurs on a 3000x2400 plane (min-of-9; machine under load, see blur_bench example).
LIGHTCRAFT_PROFILE=1 now also times log-lum, base, clarity, texture, dehaze, masks and NR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:59:10 -04:00
Brandon Thomas 17057f7bbc docs: README (MCP, CLI, export, status, contributing), AGENTS.md (asset enforcement, crash recovery, testing/perf), screenshots re-captured with Inter
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:52:34 -04:00
Brandon Thomas f0ffa11333 M0.1: enforce the asset rule in CI (cargo xtask assets); replace Adobe-published Source Sans 3 with Inter
Every image/icon/font/media/profile file must match an assets/ATTRIBUTION.md path pattern;
Adobe profile/template formats and Adobe-named assets are rejected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:52:33 -04:00
Brandon Thomas bcecab7d84 M1.5: thumbnail/preview cache (disk + memory LRU, prioritized job pool)
- new crate lightcraft-preview (L3): 128-bit content hashing (SipHash-1-3),
  cost-bounded Lru, DiskCache (JPEG q90 files keyed by content hash +
  settings hash + size + RENDER_CACHE_VERSION; temp+rename writes; LRU prune
  by mtime to 80% of budget; corrupt files dropped), PreviewCache (memory
  LRU over disk), JobPool (per-slot dedupe, priorities, reprioritize/drop,
  inline mode for wasm).
- engine: Session::thumb_job buckets thumbnails to 128/256/384/512 and serves
  them from the cache without decoding the source; decoded thumbnail
  sources now in a 384 MB LRU (was unbounded); library attaches
  <library>/thumbs; library.info cache stats; library.clearPreviews.
- ui-egui: Renderer runs on JobPool; thumbnails scrolled out of the
  prefetch area are dropped from the queue; on-screen thumbnails get
  re-prioritized; ui.inspect reports in-flight renders + thumb textures.
- Demo library (24 photos): grid fully drawn 4.4 s cold -> 0.21 s warm
  (24/24 disk hits, no source decoded).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:51:47 -04:00
Brandon Thomas c7be09429d M10.4: multithreaded AVIF encoding (ravif threading, speed 8): ~4x faster export
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:45:06 -04:00
Brandon Thomas f7b502309c M11.2: Fujifilm RAF decoding (uncompressed Bayer and X-Trans)
RAF header/record parsing, raw IFD (0xf000) with 16-bit, 12-bit LSB and
32-bit-word MSB packings found by black-box analysis of CC0 samples,
X-Trans layout (reversed record, anchored at 0,0), crop, black levels,
WB_GRB levels, metadata and preview from the embedded JPEG. Compressed
RAF reports Unsupported. Colour uses the documented neutral fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:44:09 -04:00
Brandon Thomas e7b3724b5f M1.4: crash-safe catalog persistence (op log + snapshot)
- catalog: Store trait (FsStore: temp+fsync+rename+dir fsync; MemStore),
  Journal: CRC-checked JSON-lines op log with sequence numbers + atomic
  snapshots; torn final record dropped and truncated, stale records after a
  snapshot skipped, mid-log damage preserved as catalog.log.damaged-N.
- PushHistory op so each edit logs one history step (not the full history).
- engine: Session::open_library/persist/close_library; every top-level
  command is fsynced before returning; presets.json + view.json;
  library.info / library.compact commands.
- app: default library ~/Pictures/LightCraft Library (LIGHTCRAFT_LIBRARY or
  --library DIR override), --memory for throwaway sessions, snapshot on quit.
- tests: proptest replay(log)==state with random snapshots/reopens, every
  truncation of the last record, stale log, mid-log damage, FsStore, engine
  restart without close.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:40:40 -04:00