mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-15 03:36:57 -04:00
LRC-LIB-PEOPLE: face pictures come from the embedded preview and are kept in the thumbnail cache (#1440)
An unedited raw's face tile is cut from its largest embedded preview, read the planned way (header and JPEG, never the sensor data), when the preview holds at least 3/4 of the tile's pixels across the face; otherwise, and for edited photos and other files, it is developed as before. Tiles keep going to the memory + on-disk thumbnail cache, now at 16 px size steps so the thumbnail slider reuses them. The planned preview path carries an Adobe RGB preview colour space and applies it instead of declining to the whole-file read (Sony alpha 1 previews).
This commit is contained in:
1 parent
68c924047d
commit
6477d031fc
10 files changed
+581
-36
No files matched your search
@@ -292,6 +292,37 @@ shot), so check a model on your own photos before trusting it:
|
||||
(ignoring shots taken within a few seconds of it, which would make it too easy) and reports, for each threshold, how many
|
||||
suggestions it would make and how many were right.
|
||||
|
||||
## Face pictures in the People view
|
||||
|
||||
Each face on a person card, in Unnamed faces and on a person's page is a square around the face, about 1.7 times the
|
||||
face box, cut out of the photo with the photo's own look (`Session::face_job`). Where the pixels come from:
|
||||
|
||||
- **An unedited raw** (it still looks the way it was imported, the case in which its grid thumbnail is the camera's
|
||||
embedded JPEG too) is cut from its **largest embedded preview** (`crates/engine/src/face_tiles.rs`). The preview is
|
||||
read the planned way ([raw-preview-reads](raw-preview-reads.md)): the file's header and the JPEG, never the sensor
|
||||
data, and the raw is not developed. The preview is used when it holds the square at **at least ¾ of the pixels the
|
||||
tile asks for** (up to the 2,560 px the develop path would use); the square is then enlarged by at most 4⁄3, which
|
||||
is not visible at tile size (the camera's JPEG is sharpened, so its pixels carry more detail than a binned raw's),
|
||||
and is the bound the raw path already accepts when it bins an X-Trans sensor for a preview. A smaller preview, one of
|
||||
another shape than the photo (letterboxed, another aspect ratio), one that does not decode, or a file that is offline
|
||||
leaves the face to the develop path below.
|
||||
- **Everything else** (an edited raw, whose edits the camera's JPEG can't take the way the raw does; JPEG, HEIC and
|
||||
other files; a raw whose preview is too small) is a develop render of the photo with its crop replaced by the square,
|
||||
from the smallest source level that keeps the face sharp, as before.
|
||||
|
||||
Measured on two Sony raws on an external 5400 rpm disk (file cache dropped before each face, a centred face box of 4 %
|
||||
and 10 % of the frame, 176 and 352 px tiles): an α1 ARW (74 MB, 8640 × 5760 Adobe RGB preview) read 70.6 MiB in
|
||||
0.77–0.92 s per face before and reads 7.8 MiB in 0.20–0.26 s; an A7S III ARW (25 MB, full-size 4240 × 2832 preview)
|
||||
read 24.3 MiB in 0.38–0.56 s and reads 1.1 MiB in 0.04–0.09 s. The tiles differ from the develop path's by 2–6 levels
|
||||
of 255 on average (the camera's rendering against LightCraft's camera look, which is fitted to it).
|
||||
|
||||
Rendered faces are kept like other thumbnails: in memory, and in the library's thumbnail cache on disk (`thumbs/`,
|
||||
within the Settings ▸ Performance cache budget, least recently used files go first). The key is the photo's content
|
||||
hash, its develop settings with the face's square as the crop, the tile size, the renderer version and the camera
|
||||
profiles, so an edit, another square or a new renderer makes a new picture, and the next launch shows the faces
|
||||
without reading the originals. Tile sizes are rounded up to a multiple of 16 px, so moving the thumbnail slider
|
||||
reuses the pictures of its step instead of rendering every face again.
|
||||
|
||||
## Commands
|
||||
|
||||
All of this is reachable from the control channel, the CLI and MCP: `faces.models.list`, `faces.models.inspect {path}`,
|
||||
|
||||
@@ -705,7 +705,7 @@ From `08-lightroom-classic-extras.md` (Classic-only features) and part 2 of `06-
|
||||
| LRC-LIB-KEYWORDS | Hierarchical keywords, sets, painter, Keyword List | P1 | ✅ | `crates/catalog/src/keywords.rs`, `crates/ui-egui/src/panels/keyword_list.rs`, `cmd:keyword.list`, `cmd:keyword.create`, `cmd:keyword.edit`, `cmd:keyword.move`, `cmd:keyword.info`, `cmd:keyword.purgeUnused`, `cmd:keyword.setDefaultParent`, `cmd:keyword.sets`, `cmd:keyword.toggleFromSet`, `cmd:keyword.saveSet`, `cmd:tool.keywordPainter` | hierarchical `a\|b\|c` keywords (tree, parent filters include children, rename moves children); keyword sets + Recent Keywords (⌥1–⌥9); keyword painter (Keywords panel ▸ Paint: click photos in the grid to toggle a keyword, Esc stops). Keyword List (Keywords panel): every keyword, those without photos too (a keyword list in the catalog, format version 4), with counts, a filter box, a tri-state tick box for the selection and an arrow to show its photos; + / Create Keyword Tag (name, synonyms, put inside, add to selected photos), double-click / Edit Keyword Tag, − / Delete with confirmation, context menu (create inside, Put New Keywords Inside This Keyword, purge unused keywords); drag a keyword onto another to nest it or onto the list's title for the top level (a merge asks first), drag photos onto a keyword to tag them. The list is there without a selection too; Put New Keywords Inside This Keyword (kept with the keyword in the library, undoable) also places new keywords typed in the Keywords panel. Keyword tag options include on export, export containing keywords and export synonyms shape exported files (names in `dc:subject`, paths in `lr:hierarchicalSubject`), and files' `lr:hierarchicalSubject` paths come back as keywords on import and from sidecars; Person is stored only. Keywording box (`crates/ui-egui/src/panels/keywording.rs`): the selection's keywords as chips (one only some selected photos have marked with an asterisk; × only removes, from every selected photo; with no selection, the active photo's), a Keywords / & Containing / Will Export switch (Lightroom Classic's Keyword Tags views: the keywords to edit, them with their parents, what exported files carry), keyword-set buttons and suggestions that follow the selection, shared text fields for typing and painting. Edit Keyword Set (Keyword Set ▸ Edit Set…): a set's name and nine slots typed in place, empty slots kept so each keyword stays on its ⌥ key, a set renamed in place (`cmd:keyword.saveSet` `replace`) or saved as a new set (`new`, a taken name refused). File ▸ Import Keywords… / Export Keywords… (`cmd:keyword.import`, `cmd:keyword.export`): keyword list files in Lightroom Classic's format (tab-indented, `{synonym}`, `[left out of export]`), which Capture One, darktable, Bridge and Photo Supreme (its Formatted Vocabulary File) read and write too; import adds what is missing in one undo step, export names the keywords Capture One refuses (; , < >). Not yet: synonyms in search, person keywords tied to faces, Keyword Shortcut, IPTC keywords in exported files (Piwigo, RawTherapee), hierarchy from digiKam / ACDSee / Microsoft XMP fields |
|
||||
| LRC-LIB-METADATA | Metadata panel & presets | P1 | ✅ | `cmd:photo.setMeta`, `cmd:metadata.savePreset`, `cmd:metadata.applyPreset`, `cmd:metadata.presets`, `cmd:metadata.deletePreset`, `cmd:dialog.captureTime` | IPTC core, accessibility, place, capture-time edit; copyright status (unknown / copyrighted / public domain), rights usage terms and copyright info URL (`photo.setMeta` `copyrightStatus`, `usageTerms`, `copyrightUrl`; XMP Rights Management fields, kept by "copyright only" exports, a smart-album rule); metadata presets (Photo → Metadata Preset, Save Metadata Preset… from the active photo; applied on import from Settings) |
|
||||
| LRC-LIB-QUICKDEV | Quick develop | P2 | ✅ | `cmd:develop.quickAdjust`, `crates/ui-egui/src/panels/edit.rs` (`quick_develop`) | grid with several photos selected: Edit panel ▸ Quick Develop steps (exposure ⅓ / 1 stop, contrast, highlights, shadows, whites, blacks, clarity, vibrance, temperature) added to each photo's own value, one undo step |
|
||||
| LRC-LIB-PEOPLE | People view | P2 | 🟡 | `cmd:view.people`, `cmd:view.person`, `cmd:faces.person`, `cmd:faces.unnamed`, `cmd:faces.nameFaces`, `crates/ui-egui/src/panels/people.rs`, `crates/ui-egui/src/panels/person.rs`, `crates/ui-egui/src/panels/unnamed.rs` | Named People cards (face close-up, the photo count on the picture, name) for the photos the active filters let through, with the filter chips shown above them, sized by the thumbnail slider. Below them the **Unnamed faces**: every face nobody has named as a cropped picture, look-alikes next to each other once recognition has looked at them, a suggested name on a face the named ones recognise; click or Shift-click to select, type a name, Enter names the whole group in one undo step. A click on a person opens their page: only cropped faces, then **More** (unnamed faces that look like them, best first; click to confirm, × to hide). Every face is shown by the detector's box where the scan has one, so faces are equally close. Until recognition is set up a line at the top offers the next step (Settings ▸ Faces, or Turn on), as does the loupe's name box. No clusters with separators between groups, no persistent reject, no merge of two people
|
||||
| LRC-LIB-PEOPLE | People view | P2 | 🟡 | `cmd:view.people`, `cmd:view.person`, `cmd:faces.person`, `cmd:faces.unnamed`, `cmd:faces.nameFaces`, `crates/ui-egui/src/panels/people.rs`, `crates/ui-egui/src/panels/person.rs`, `crates/ui-egui/src/panels/unnamed.rs`, `crates/engine/src/face_tiles.rs` | Named People cards (face close-up, the photo count on the picture, name) for the photos the active filters let through, with the filter chips shown above them, sized by the thumbnail slider. Below them the **Unnamed faces**: every face nobody has named as a cropped picture, look-alikes next to each other once recognition has looked at them, a suggested name on a face the named ones recognise; click or Shift-click to select, type a name, Enter names the whole group in one undo step. A click on a person opens their page: only cropped faces, then **More** (unnamed faces that look like them, best first; click to confirm, × to hide). Every face is shown by the detector's box where the scan has one, so faces are equally close. Until recognition is set up a line at the top offers the next step (Settings ▸ Faces, or Turn on), as does the loupe's name box. Face pictures of unedited raws are cut from the largest embedded preview (planned read: header and JPEG only, never the sensor data) when it holds at least ¾ of the tile's pixels across the face, else developed as before; all are kept in the thumbnail disk cache at 16 px size steps, so a later launch shows them without reading the originals (#1440, `docs/faces.md`). No clusters with separators between groups, no persistent reject, no merge of two people
|
||||
| LRC-LIB-COMMENTS | Comments panel | P2 | ⬜ | | |
|
||||
| LRC-LIB-VISUALSEARCH | Find similar photos | P2 | ✅ | `cmd:library.findSimilar`, `crates/pipeline/src/cull.rs` (`signature`) | photo menu / Photo ▸ Find Similar Photos: look-alike photos (composition and tones), most similar first, as a filter (`only`) |
|
||||
| LRC-LIB-MISSING | Missing files & relink | P1 | ✅ | `cmd:library.missing`, `cmd:photo.relink`, `cmd:library.findMissing`, `cmd:file.findMissing`, `cmd:photo.locate`, `crates/engine/src/cmd/missing.rs` (`checked_path`), `crates/ui-egui/src/panels/left.rs` (`locate_missing_item`) | File → Find Missing Photos… (anywhere in a folder: same name, size and content hash; renamed files by size + content hash; same name and size alone only when unambiguous — look-alikes are skipped and reported; searched on a worker thread, one undo step), photo menu → Locate Missing File…; a folder or disk row's Locate Missing Files… chooses its new location and scopes the same recursive background finder to that folder and its subfolders. Unreadable files show "!" in the grid and a reason in the loupe; undo never moves files. Missing Photos in the sidebar (counted on a worker thread; the view, the Info panel, the grid's context menu and thumbnails read file availability from a cache a worker thread fills, so an offline NAS never stalls a frame); it covers library photos only — Local browse records are never checked, for the count, the view, `library.missing` and Find Missing alike |
|
||||
|
||||
@@ -60,11 +60,13 @@ those:
|
||||
header probe reads samples and whose header plan is `Whole`, which is where that plan was untested
|
||||
before;
|
||||
- the quick path must be able to show what the whole-file path would. Two container facts are not in
|
||||
the JPEG, so a file that states one where the bounded header cannot see it declines and the whole
|
||||
file keeps the preview: a preview colour space of **Adobe RGB** (the whole-file path applies it
|
||||
through `decode_raw_preview`; a JPEG-only decode has the JPEG's own space, else sRGB — a container
|
||||
that says sRGB does not decline, since that is what the decode assumes anyway), and the file's own
|
||||
EXIF orientation when the preview JPEG carries none of its own;
|
||||
the JPEG. A preview colour space of **Adobe RGB** is read from the planned buffer and handed on
|
||||
(`PreviewRead::adobe_rgb`), and the quick decode applies it as `decode_raw_preview` does (issue
|
||||
#1440: a Sony α1 writes Adobe RGB previews, which until then sent every one of them to the
|
||||
whole-file read — 70 MB for an 8 MB JPEG); a container that says sRGB needs nothing, since that is
|
||||
what the decode assumes anyway. The file's own EXIF orientation, when the preview JPEG carries none
|
||||
of its own and the bounded header cannot see it, still declines and the whole file keeps the
|
||||
preview;
|
||||
- a format the planner cannot describe — NEF, ORF and PEF probe from samples, CRW/MRW/X3F are found
|
||||
by scanning — a plan that does not fit the buffer or wants more than 60 % of the file (the bound the
|
||||
import path's own partial read uses), or a window that does not decode all answer `None`, and the
|
||||
@@ -75,8 +77,8 @@ route**, reading a median **7.8 %** of their bytes (0.9 %–27.7 %) — by forma
|
||||
4.1 %, ARW 7.5 %, CR3 8.4 %, DNG 9.7 %, CR2 14.3 % — while the 21 RAFs stay on the header route
|
||||
(median 4.5 %). The remaining 38 read whole: NEF, NRW, ORF, PEF and Samsung SRW because the planner
|
||||
does not describe them (the first four say so; an SRW plans to the whole file, and the 60 % budget
|
||||
then keeps the whole-file read), and the ARWs, CR2s, CR3s and DNGs that declare Adobe RGB or whose
|
||||
plan wants more than that budget. Across the whole corpus the plan handed on **the same preview as
|
||||
then keeps the whole-file read), and the ARWs, CR2s, CR3s and DNGs that declared Adobe RGB (these take
|
||||
the plan route since #1440; the counts here are from before) or whose plan wants more than that budget. Across the whole corpus the plan handed on **the same preview as
|
||||
the whole file, every time** — and wrote it down: `cargo test -p lightcraft-raw --release --test
|
||||
sparse_corpus -- --nocapture` prints these numbers. `LIGHTCRAFT_PROFILE=1` says which route answered:
|
||||
|
||||
@@ -117,8 +119,10 @@ render queue stayed empty.
|
||||
whole-file path produce identical pixels, and the read stays under half the file. On a synthetic
|
||||
TIFF/DNG whose preview only its tags locate — the plan path — the same identity holds and the CFA
|
||||
strip is not read; the same structure without the tag that makes it a raw declines instead of
|
||||
guessing, and the caller keeps reading it whole. So do a file whose own orientation or Adobe RGB
|
||||
colour space only the whole-file path can show, while one that states sRGB stays on the fast path.
|
||||
guessing, and the caller keeps reading it whole. So does a file whose own orientation only the
|
||||
whole-file path can show. One that states Adobe RGB takes the plan route and shows the same pixels
|
||||
as the whole-file path (the space changes them: decoded as sRGB they differ), and one that states
|
||||
sRGB stays on the fast path.
|
||||
- `crates/raw/tests/sparse_corpus.rs`: `preview_plan` is asked for **every** corpus file, not only
|
||||
the ones whose header plan is `Ready`, and the preview it yields must equal the whole file's with
|
||||
the skipped bytes `0` and `0xFF`. The test prints what each format reads.
|
||||
|
||||
@@ -2,6 +2,16 @@
|
||||
|
||||
## 2026-10-13
|
||||
|
||||
### Faces load faster in the People view
|
||||
- Faces on unedited raws are cut from the camera's preview inside the file instead of developing the raw (issue
|
||||
#1440): a Sony α1 face reads 8 MB instead of 70 MB, an A7S III face about 1 MB instead of 24 MB, several times
|
||||
faster on an external hard disk. A face whose preview is too small, and any edited photo, is still developed from
|
||||
the raw, so it keeps its look.
|
||||
- Raws whose preview is in Adobe RGB (Sony α1 among others) no longer read the whole file for their grid thumbnail:
|
||||
only the header and the preview are read, as for other raws.
|
||||
- Faces are kept in the thumbnail cache at a few fixed sizes, so moving the thumbnail slider no longer renders them
|
||||
all again.
|
||||
|
||||
### Slider drags on large raws
|
||||
- Dragging a slider no longer re-renders the edited photo's filmstrip thumbnail on every frame; it follows once you
|
||||
let go. On a 64 MP raw the first of those renders decoded the whole file again, which held up the loupe's
|
||||
|
||||
Reference in new issue
Block a user