mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-13 09:20:07 -06:00
bb0f0d5e113aeda52fc7d3bb421908718b742a92
21
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> |
||
|
|
61005f3dc1 |
Culling: the loupe uses built previews; photos prepared ahead and in the background (#3077)
* PERF-CULL-0: ui.inspect reports how the loupe showed each photo it was stepped to
perf.loupeSteps records, per step, the ms from the step to the first frame
with anything of the photo, with a full-size picture (camera JPEG, cached
or rendered) and with LightCraft's own render, measured on the frames the
app draws, so culling benchmarks exclude the control channel's round trip.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 95f0343848fc0a238f80f07ceb292eaf6779aeaa)
* PERF-CULL-1: browsing a folder shows its first photos within a second
The Local view read and hashed every file of the folder before showing any
of them (2,933 EOS R5 C CR3s: 30.6 s to the first photo). It now hands the
files the library lacks straight to the import, ordered the way the view
sorts them (file time for the date sorts), which adds them batch by batch:
first photo 0.9 s, twelve thumbnails 1.4 s, the whole folder in the same
~34 s as before, in the background.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit b766558e7ef4f802919a144da53e444f70e0186b)
* PERF-CULL-2: the loupe makes the next photos' camera previews ahead of the step
Culling steps through photos, and each step waited for the next raw's
embedded JPEG to be read and decoded (p50 187 ms, p95 332 ms on EOS R5 C
CR3s) and then for a full raw develop. Now:
- the loupe's stand-ins for the next five photos in the direction of
travel and the one behind are made ahead (Slot::QuickAhead, priority
just below the loupe's own render) and taken over at the step, so a
step shows the camera JPEG at once (p50 0 ms);
- a slot keeps its photo while the photo stays wanted, so photos that
shift places as the user steps are not made twice;
- decoded camera JPEGs stay in the memory LRU (never on disk), so going
back shows them again without a decode;
- while an arrow key is held (a photo left within 0.25 s), the raw
develop waits until the photo has stayed 0.12 s, and a render queued
for a photo already left is dropped, so the workers make the previews
instead of renders nobody sees.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0430b95636d2b6adce8887af7b9464687c95549e)
* PERF-CULL-3: CR3 previews take the smallest embedded JPEG that covers the view; plain sRGB JPEGs decode straight to 8-bit
Upstream's embedded_preview_covering (
|
||
|
|
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
|
||
|
|
d977d3e1a4 |
LR-EDIT-DETAIL-SHARPEN: Radius and Detail work, and sharpening acts at pixel scale (#757) (#762)
* LR-EDIT-DETAIL-SHARPEN: Radius and Detail work, and sharpening acts at pixel scale (#757) * LR-EDIT-DETAIL-SHARPEN: an enlarging export keeps every Radius step (#757) The sharpening blur's sigma was capped at 4 px, so a 2x export rendered Radius 2 and 3 alike (both scaled past 4 px). The cap is now the largest sigma the Gaussian taps support, GAUSS_SIGMA_MAX (64), which the CPU and GPU blurs share. Test: enlarged_export_keeps_radius_steps_apart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ti8KemZbjE2ZgHgV1q1BM * LR-EDIT-DETAIL-SHARPEN: preserve process-aware previews after rebase (#757) Use the V3 pixel-scale blur and Masking weight for the Radius, Detail and Masking previews on both renderers. Preserve the legacy texture-band previews for V1/V2 and compute the same Gaussian when sharpening is disabled. Cover process selection, radius changes, and prepared/fresh plane equivalence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ti8KemZbjE2ZgHgV1q1BM --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com> |
||
|
|
5da455a3c5 | LR-EDIT-OPTICS-PROFILE: merge main (#3144: ILCE-7M3 distortion, kept in the gaps and checklist rows) | ||
|
|
36b244286a |
Merge remote-tracking branch 'origin/main' into pr-3073
# Conflicts: # docs/gaps.md |
||
|
|
0852c21a9d |
Merge remote-tracking branch 'origin/main' into pr-3144
# Conflicts: # crates/engine/src/media/source.rs # docs/gaps.md # docs/parity-checklist.md # docs/raw-parity.md # docs/whats-new.md |
||
|
|
06c1b9ae07 |
Use crw craftlib (#3503)
* CRW-LIB: decode Canon CRW through the shared craft-crw crate LightCraft decodes Canon CRW (the CIFF raws of the EOS D30, D60, 10D, 300D and the PowerShot G1/G6) through craft-libs' `craft-crw`, the clean-room decoder derived and verified on the `clean-room-crw-format-decode` branch and moved there with its frozen tables and derivation record. `crates/raw/src/vendor/crw.rs` is now the adapter: it maps the crate's header and samples onto `RawImage` (CFA, levels, active area, orientation, as-shot white balance, the make/model/time records a CRW keeps instead of Exif) and converts the capture time through chrono. The dependency is a local path (`../craft-libs/crates/crw`) until the craft-libs branch merges; then it becomes a git revision. Everything LightCraft-side from that branch comes along unchanged: `probe_bytes` fills metadata gaps from the decoder's records so CRW imports name their camera; the EOS D60 bundled camera profile and its chart-measured dual-illuminant matrices (`chart_table.rs`, `spectral::Camera` carries its illuminant pair) give the D60 a Kelvin white-balance scale; CRW joins the JPEG-fitted starting look, the catalog's raw formats and `lightcraft-cli calibrate`; `RENDER_CACHE_VERSION` 71; the CC0 CRW corpus entries for `cargo xtask corpus`; coverage, parity, gaps, raw-parity, target-app-parity and ROADMAP updates. The G5 stays preview-only (the crate refuses it with the reason) and the robustness test keeps its CRW sample. What does not come along: the decoder, its tables and `crw_oracle.rs` (now craft-crw's digest-pinned sample test) and the derivation record, which lives in craft-libs `crates/crw/clean-room/`. `clean-room/crw/` here keeps only the EOS D60 chart-calibration document and its capture list, with a README pointing at the record. Gates: fmt, clippy -D warnings (raw all targets; engine, catalog, cli), lightcraft-raw tests (475 + 134, the corpus files decoding through the adapter), catalog tests, engine lib tests (1,300 pass; the two failures, `tests_import::recursive_discovery_…` and `originals::tests::write_targets_pin_absent_paths_…`, are in files unchanged from main), `cargo xtask parity`, `assets`, `layers`, `coverage.py --check`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * no message * no message * Update documentation after moving code to craft-libs * Review coment fixes * CRW: pin craft-crw f3b62ec, a truncated entropy stream is Corrupt craft-libs #472 makes craft_crw::decode refuse a stream that ends more than Limits::max_stream_end_fill (64 samples, one block) before the frame does, instead of filling the rest with predictor values and returning success. The adapter already maps craft_crw::Error::Corrupt to RawError::Corrupt; a regression test proves a truncated CRW now fails at this boundary (it decoded as a flat frame on the old pin) while its header still reads. Only craft-crw moves; craft-ui/docpath/update/window stay on c5a1cbf. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Code review comment fix * Add some tests --------- Co-authored-by: Robert Woldberg <314159+rwoldberg@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
bad1b88cd9 |
LR-EDIT-OPTICS-PROFILE: a replaced original's missing file correction isn't restored; the loupe key carries the lens profile
A fresh decode that finds no file correction now renders without one, even when the catalog still holds the old file's; a database profile still comes from the catalog. The loupe view-cache key includes the photo's lens profile, as the render, thumbnail and variant keys do, so a changed database profile never shows the old loupe render. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
29443e117c |
LR-EDIT-OPTICS-PROFILE: merge main (#3430 integration round 3, #3390)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5207c3baaf | Integration: #2777 restore dotCooCoo sync_github_name in people.toml; RENDER_CACHE_VERSION 71 to 72 | ||
|
|
cde3b51a4d | Merge PR #2623: LR-CROP-RECT: sidecar settings kept in range; a turned crop bounded to the photo (#2555) | ||
|
|
3f09c85c56 |
LR-EDIT-OPTICS-PROFILE: merge main (integration batch 45)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
953c4ee5fc |
raw: decode lossy single-plane (grey) JPEG XL tiles in LinearRaw DNGs
Lightroom Classic's HDR and panorama merges of monochrome raws store a one-plane LinearRaw image as lossy (XYB) JPEG XL tiles whose codestream declares a grey colour encoding (16-bit float with a linear curve, or 16-bit integer with the sRGB curve). Those were refused as "lossy JPEG XL tile with a single colour plane", so the files opened preview-only. A lossy grey tile is still coded as three XYB channels. It is now restored like the RGB case: jxl-oxide renders linear sRGB (never the declared encoding, which would clip), the single plane is the luminance (CIE Y, D65) of that, then the declared transfer curve is applied. One plane must be declared grey and three RGB; HDR curves, other white points and intensity targets above 255 stay unsupported. RENDER_CACHE_VERSION 70 -> 71: files that showed their embedded preview before render from raw data now, under the same content key. Refs #275 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ti8KemZbjE2ZgHgV1q1BM |
||
|
|
698352009d |
LR-EDIT-OPTICS-PROFILE: merge main (59a5d00c6, the ui-egui split of #3111)
The lens-database dialog arms go into main's split dialogs module; quick.rs and frame.rs take main's explicit imports plus what this PR uses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c0f4f54e7f |
LR-CROP-RECT: the crop's source size is counted in the turned photo (#2555)
`source_edge_needed` bounds the crop in the photo as shown (turned by its orientation), but counted the rect's pixels with the unturned width and height. On a photo turned a quarter, a crop whose aspect differs from the photo's then asked for too small a source: a square crop of a 3:2 photo shown 2000 px long needs the full-size source, and got the 2560 px preview. It now counts them with the turned size. The two tests that pinned the old count use the turned size too, and one checks that square crop. Found by CodeRabbit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvoftLURuf4uBmSatewB6V |
||
|
|
c9d44f7804 | style(engine): expand module imports explicitly | ||
|
|
f267ddb504 |
LR-EDIT-OPTICS-PROFILE: pinned profiles, whole-folder install, shared hosts (review, #3073)
Render caches: a database lens profile is pinned to the photo. A decode never
overrides the catalog's database profile (media::source::pinned_lens), and
the profile is part of the render, thumbnail and variant cache keys
(media::quick::lens_key; only database profiles with the correction on, so
existing thumbnails keep their keys). When a download replaces the
database's files (lens_profiles::replaced), the refresh also re-reads raws
whose profile came from the database and raws that named no lens; an
ordinary start does not. library.refreshLensProfiles takes {all: true}.
Installation: files are staged beside the database folder, SOURCE.txt is
written there last, and the staged folder replaces the old one as a whole;
a failed swap puts the previous database back (and recover() does at the
next start). The app's own folder is read only with its marker, and
installed() checks every pinned file at its pinned size.
Shared hosts: with_default_lens_database() only selects the existing folder;
the permission to fetch is with_lens_auto_download() (desktop only), and the
first-start decision is one function, lensfun_download::fetch_on_start.
CLI render / merge and the headless MCP server select the folder;
lens.downloadDatabase selects the folder it downloads into.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
4db57c976b |
LR-EDIT-OPTICS-PROFILE: enable Sony ILCE-7M3 embedded distortion correction
The A7 III's raw-IFD 0x7037 tables were refused until their geometry was checked. Measured against the camera's own JPEG of the same exposure (Lens Comp. Distortion: Auto) on 27 RAW+JPEG pairs from one body with the FE 16-35 F4 ZA, FE 28-70 and FE 50 F1.8 (strong barrel to strong pincushion, compressed and uncompressed, TV and monitor checkerboards): corrected board corners sit 0.06-0.44 px (median, 6000 px frame) from the JPEG's, against 0.8-26 px uncorrected; LightCraft applies the camera's amount of correction within 2 %; corrected rows and columns are as straight as the JPEG's. Sony's crop and the default crop agree on this body, so the warp is centred on the image. A unit test pins the warp from three of these tables to the camera's fitted geometry; the model boundary tests now accept ILCE-7M3 and still refuse its neighbours. RENDER_CACHE_VERSION is bumped. The measurement script handles screen moiré (X-corner check, duplicate corners, consensus lattice vectors). The corpus test's two ILCE-7M3 samples now expect one warp; raw.pixls.us is not reachable from this environment, so that test was skipped here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015gRtdWF1E9358FfzhzYqCS |
||
|
|
f860577c51 |
LR-CROP-RECT: the source level is sized from the bounded crop (#2555)
source_edge_needed read the turned crop's rect unbounded, so a crop reaching far past the photo (from a sidecar or an agent) picked the 512 px thumbnail for a frame the render bounds to photo size, and the view was stretched from it. It now bounds the crop as Frame does, in the photo's oriented frame; crops on the photo keep their source level. Found by CodeRabbit on #2623. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvoftPiKeZ3QkVHvA5LmR6 |
||
|
|
21304db29b | refactor(lightcraft-engine): split media.rs into media/ |