mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-13 09:20:07 -06:00
bb0f0d5e113aeda52fc7d3bb421908718b742a92
13
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d729c80a5a |
Detail: Remove Hot Pixels, on for newly imported raws (#1345) (#1350)
* WIP: suppress isolated hot photosites in the raw mosaic (#820) Noise model fitted to the image, candidates 5 sigma above every same-colour neighbour, kept when two or more direct neighbours are lit (real light); adjacent hot pairs are replaced too. LIGHTCRAFT_HOT_PIXELS=0 turns it off, LIGHTCRAFT_HP_DEBUG=1 prints statistics. Renders are cached apart from older ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * WIP: hot pixel suppression off by default (LIGHTCRAFT_HOT_PIXELS=1 turns it on) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #1345: mosaic outlier filter: cache key no longer names the #858 comparison switch (merged upstream) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #1345: mosaic outlier filter: fixed thresholds (no LIGHTCRAFT_HP_K / _SPOT knobs), statistics under LIGHTCRAFT_PROFILE Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #1345: Remove Hot Pixels, a box in Detail ▸ Noise Reduction, on for newly imported raws `Detail::nr_outliers` replaces single outlier photosites in the raw mosaic before demosaicing. It is true in `DevelopSettings::for_raw` and false by default, so edits saved before the box existed keep their look. The loaders take the flag (no more `LIGHTCRAFT_HOT_PIXELS`), a decoded source records it, and a cached raw source decoded with the other setting is decoded again. The box shows for raws only (`check:detail.nrOutliers`). * #1345: Remove Hot Pixels off keeps old settings' JSON; smart previews fit either setting - `detail.nr_outliers` is left out of the stored JSON when off, so settings saved before it keep their hash and cached thumbnails. Copy / paste, presets (also at an amount), preset updates and Auto Sync work on `DevelopSettings::to_json_for_merge`, which states it, so turning the box off still carries over. - `DecodedSource::fits_outliers` accepts a smart preview (`from_proxy`): it has no mosaic, so an offline raw with the box on keeps reusing the decoded proxy. (review of #1350) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #1345: the Detail eye and Reset Detail switch Remove Hot Pixels off `DevelopSettings::removes_hot_pixels` (the box and the Detail section's eye) decides the raw decode in the app, the web worker and the thumbnail bench, so a Detail section that is off contributes nothing here either. Reset Detail resets the box to the general default (off), like the sliders it resets. (review of #1350) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #1345: the hot pixel limit counts only replaced photosites, and can be set The 0.2 % limit was checked against the candidates before real light was filtered out, so a dense star field reached it with its stars alone and kept its hot pixels too. It now counts the photosites that would be replaced, as the module docs say. The limit is now a parameter (RawImage::suppress_hot_pixels_with, default HOT_PIXEL_MAX_SHARE) and a setting: Settings > Performance > Hot pixel limit, applied with the new command app.hotPixelLimit. Changing it forgets the decoded sources and changes the render cache keys of photos that remove hot pixels; at the default every key stays as it was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #1345: the app's default hot pixel limit is the default The setting is an f32: 0.2 % read back as 0.2000000029… % was a new limit, so every start forgot the decoded sources and changed the render keys of photos that remove hot pixels. A value within a millionth of the default now is the default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Format the imports * Web loader test states what it decodes with * Format the media exports --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com> |
||
|
|
8972565249 |
LR-PROF-IMPORT: import the user's own DNG camera profiles (.dcp) (#1063)
* LR-PROF-IMPORT: import the user's own DNG camera profiles (.dcp)
profile.import (File > Import Profiles & Presets, drag & drop) now also
reads DNG Camera Profiles: the user's own .dcp files, e.g. made with a
colour-checker tool. They appear in the profile browser and menu under
Camera Profiles with dcp:<camera>-<file> ids; profiles.camera lists them
and profile.deleteCamera / profile.deleteImported remove them. A library
on disk keeps a copy in Profiles/. No profile ships with the app and the
repository holds none: tests build theirs in memory (write_dcp).
lightcraft_raw::dcp parses the file per DNG spec ch. 6 (magic RC in
place of 42; IFD 0 read through the existing DNG colour builders) and
DcpProfile::applied_to puts its matrices, illuminants and look in place
of a raw file's, keeping the shot's white balance, analog balance and
baseline exposure.
Choosing one (develop.profile) replaces the raw file's colour model when
it is decoded, before the starting colour and the white-balance scale
(WbScaleOf) are worked out, so the matrix path takes over from the fitted
JPEG look (CR3/CR2/ARW...) and white balance follows the profile. A
profile without a tone curve of its own (most chart-made ones) keeps the
photo's contrast: the DNG's tone curve, else the tone of the camera-JPEG
fit. The profile travels with SourceRef::File into FileLoader /
PairLoader (new Option<&DcpProfile> argument); a decoded source records
the profile it was made with and is not reused for another. Smart
previews and the web render worker keep the file's own colour; rendered
(non-raw) photos ignore it.
This reverses the documented "Adobe profile formats deliberately
unsupported" stance for user-supplied files only; docs updated.
Split out of #1012.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: build against upstream main (kelvin_presets, 3-arg loaders)
DcpProfile::applied_to keeps the file's white-balance presets (new
ColorData::kelvin_presets upstream): they are camera facts like as-shot
neutral. Upstream's new FileLoader test closures take the profile argument.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: hover previews and browser thumbnails develop the hovered camera profile
The loupe's hover preview (preview_job) and the profile browser's thumbnails (variant_job) took
the photo's own decoded source, so hovering an imported camera profile showed the built-in colour
(and hovering a built-in profile while one was applied kept the camera profile's). Both now decode
as the hovered settings develop: the source follows the settings' camera profile, as the main
render does, and a variant's cache key already follows its settings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: build against upstream main (face-tile test loader and render_bench take the camera profile)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CI: the catalog's staging-link test module is Unix-only (unused import broke clippy on Windows)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CI: same-folder export test expects the '/'-joined subfolder (failed on Windows)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CI: folder-drop tests build widget ids with the Folders tree's '/' paths (failed on Windows)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: rustfmt after merging main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: a real D750 camera profile as a pinned test corpus, checked against Lightroom
dieli's CC0 Nikon D750 ColorChecker set (Calibrite PROFILER dual-illuminant DCP,
matrix-only; the two chart NEFs; Lightroom Classic 15.6 renders with the profile)
is fetched by cargo xtask corpus --download into corpus/d750-colorchecker/, pinned
to commit f37f4054 and by sha256; nothing is committed.
Tests (skip without the corpus): the profile parses as matrix-only with
Cool White Fluorescent + D55; the matrices blend by inverse temperature; the
matrix-only path inverts the colour matrix and adapts to D50; it takes over the
chart NEFs' colour; imported and applied end to end, the charts develop within
mean dE2000 3.84 (flash) / 3.35 (indoor) of Lightroom's renders.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: a single .dcp file is a camera profile only, not also an unreadable LUT
profile.import read every plain file that was neither a folder nor a .zip as a .cube LUT, so importing
one .dcp imported the camera profile and also reported it as failed ("stream did not contain valid
UTF-8"). Only .cube files are read as LUTs now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: review fixes for imported camera profiles
- A library moved or restored elsewhere finds its camera profiles in its own Profiles/ folder.
- A grid thumbnail is cut from the decoded preview only when that preview used the same camera profile.
- Render and source cache keys carry a fingerprint of the profile file, so a deleted or replaced profile never
has its old renders stand in.
- Camera profiles can be favourites, and favourites/recent entries survive reopening the library.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LR-PROF-IMPORT: smart previews without the camera profile, current-profile source facts, unsupported files reported
CodeRabbit review of
|
||
|
|
47839a61db | refactor(bench): dispatch specialized render modes | ||
|
|
1c70b24c74 |
M5.7: perf harnesses for slider latency, zoom windows and photo loading (examples only)
- drag_latency (ui-egui example): the whole app driven headless at 60 frames a second; per develop control, the latency of each drag tick until its draft is the loupe (or, with ZOOM, the zoom window) texture, the ticks never shown, the UI thread's frame time and the time to the full-quality render after release. - drag_tick_bench (engine example): the UI-thread work of one drag tick outside the render. - render_bench: ONLY=window (a 1:1 zoom window cut from the original: first window, pan, slider changes; GPU_ONLY=1, DUMP=file for pixel A/B), ONLY=load / load1 / jpeg (+ LOADONLY=1): the steps of opening a photo. The window rows carry no colon in their names, so `cargo xtask bench` records each of them (parser test). - open_timeline (ui-egui example): opening raws in Detail through the real app logic, headless with the GPU. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
5630734832 |
LR-BEHAV-BG-TASKS: render export batches side by side (issue #496)
A batch ran its photos strictly one after another: each render is row-parallel inside, but a photo's decode, its PNG/TIFF/WebP encode and the wait on a GPU render are mostly serial, so a 32-core machine averaged ~9.5 busy cores on the CPU path and 1-2 with the GPU. run_batch now keeps export_parallelism() photos in flight (cores / 4, 1..=4; one on wasm32) on scoped lane threads plus the calling thread. Each holds an estimate of its working memory from a new export_gate (twice the memory budget, separate from the decode gate it nests over), so full-size 24 MP exports (~1.2 GB each) run two at a time by default. Lanes run at most `lanes` photos past the last one placed, which also bounds the finished files waiting for their turn. Paths, conflict handling, original protection, writes, results and progress stay in batch order (Placer), so the files are the same as one at a time; a panic in a lane becomes that photo's error. render_bench's batch scenario now goes through export_batch. 8 x 24 MP JPEGs, 32 cores + RTX 4080 Laptop: CPU 3.87 s -> 2.35 s, GPU 4.21 s -> 1.82 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
03bc1469f9 |
LR-EDIT-OPTICS-PROFILE: apply the distortion correction Panasonic/Leica record in RW2/RWL (#256)
The RW2 reader reads PanasonicRaw tag 0x0119 (16 LE words) and expresses the camera's own correction as the equivalent DNG WarpRectilinear in OpcodeList3, so embedded_lens() turns it into the existing EmbeddedLens/EmbeddedWarp and "Enable Profile Corrections" applies it like a DNG's warp (no new warp path; Convert to DNG keeps it). Rule, from our own measurements against the embedded camera JPEGs (clean-room; ExifTool tag names only): corrected = rho*(1 + (w8 rho^2 + w4 rho^4 + w11 rho^6)/32768), rho = r/w12 (w12 = active-area half diagonal), output = corrected / (1 + w5/32768), on when (w7 & 0xf) == 1. Chosen on 38 GX85 photos, held out on 35 photos from 12 other bodies / 9 other lenses; corrected renders line up with the camera JPEG within 0.75 px at 1440 px at the corners (up to 13 % of the half diagonal before). Implausible values leave the photo uncorrected. The loader now removes lens opcodes before the RW2 camera-look fit (its binned proxy needs an empty OpcodeList3); Opcode::is_lens_correction() replaces the duplicated matches. RENDER_CACHE_VERSION 11 -> 12. |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
30aac43fab |
M5.2e: renders and exports use the GPU; render_bench GPU column
`media::develop` renders on the GPU when available (views, render_now, exports;
thumbnails stay on the CPU) and falls back to the CPU otherwise. `app.gpu
{enabled?}` toggles it at runtime, `LIGHTCRAFT_GPU=0` disables it for the process,
`ui.inspect` reports the adapter under perf.gpu.
Performance: dropped device buffers are recycled once their thread has submitted
(fresh 100-300 MB allocations per pass cost as much as the passes), Gaussian
passes ping-pong between two buffers and read all channels of a pixel at once,
and long box radii slide over longer chunks. LIGHTCRAFT_PROFILE prints real GPU
stage timings. render_bench prints CPU and GPU columns and the CPU/GPU difference.
24 MP ARW, loaded machine, min of 7: cold loupe 415 -> 32 ms, exposure drag
22-42 -> 3.9 ms, NR drag (1920) 301+ -> 18 ms, export render 1037+ -> 291 ms;
max 1 LSB difference. docs/gpu-pipeline.md describes the implementation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |