Show a face beside each name in the people rail
The rail was drawing an empty square for every person: cover was hardcoded to a default image. That is the one place a portrait matters most, because the rail is how the user decides which unnamed group to open first, and a list of "Unnamed (24 faces)" rows tells them nothing. The portrait is the person's confirmed face with the largest crop_px -- the most source pixels the face actually occupied, so the one they have the best chance of recognising -- falling back to a suggestion so a freshly clustered group still has a face beside it. Cached in the controller, because every mutating action reloads the whole screen and cutting a portrait costs a JPEG decode per person. Without the cache, confirming one face would re-decode a proxy for every person in the library, and the rail does not change when a suggestion is accepted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,13 @@ use crate::faces::{crop_face, FaceCrop, FACE_TIER};
|
||||
/// the same person* — is one the user cannot make from a postage stamp.
|
||||
pub const FACE_CROP_EDGE: u32 = 128;
|
||||
|
||||
/// Edge of the portrait beside a name in the people rail.
|
||||
///
|
||||
/// Small on purpose: the rail is for *recognising* a person you already know,
|
||||
/// not for judging a likeness, and forty of them at grid size would be a
|
||||
/// second grid competing with the real one.
|
||||
pub const COVER_CROP_EDGE: u32 = 80;
|
||||
|
||||
/// A row in the people rail.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PersonRow {
|
||||
@@ -185,6 +192,37 @@ pub fn load_faces(
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// The face to show as a person's portrait.
|
||||
///
|
||||
/// Confirmed faces first, then the largest — `crop_px` is the number of source
|
||||
/// pixels the face actually occupied, so the largest is the one the user has
|
||||
/// the best chance of recognising. Falling back to a suggestion means a
|
||||
/// freshly clustered group still has a face beside it, which is the moment the
|
||||
/// portrait matters most: the rail is how the user decides which unnamed group
|
||||
/// to open first.
|
||||
pub fn load_cover(
|
||||
catalog: &Catalog,
|
||||
store: &ThumbStore,
|
||||
person: PersonId,
|
||||
) -> Result<Option<FaceCrop>, dr_catalog::CatalogError> {
|
||||
let rows = faces::for_person(catalog.connection(), person, true)?;
|
||||
let Some(best) = rows
|
||||
.into_iter()
|
||||
.max_by(|a, b| {
|
||||
a.confirmed
|
||||
.cmp(&b.confirmed)
|
||||
.then(a.crop_px.total_cmp(&b.crop_px))
|
||||
})
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some((w, h, rgba)) = decode_proxy(catalog, store, best.image_id) else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(crop_face(&rgba, w, h, &best, COVER_CROP_EDGE))
|
||||
}
|
||||
|
||||
fn decode_proxy(
|
||||
catalog: &Catalog,
|
||||
store: &ThumbStore,
|
||||
|
||||
Reference in New Issue
Block a user