Reuse the face grid's decoded crops across a redraw
A confirm or a reject changes one row and redraws the whole grid, and the redraw re-read every crop blob of the selected person (4 MB for the largest) and decoded every one — 316 ms per click on the reference library's 754-face person, to arrive at the pixels already on screen. `load_faces` now takes the crops the previous load decoded, keyed by face, and moves each into its new cell; the blob read is skipped when every face is already in hand. `refresh` drains the old cells into it rather than cloning them. The redraw is 2.6 ms.
This commit is contained in:
@@ -261,18 +261,38 @@ pub fn load_people(
|
||||
/// Crops come from the proxy the grid already built. An image whose proxy has
|
||||
/// been evicted yields a cell with no crop rather than being dropped — the face
|
||||
/// is still real, still counted, and still confirmable from its filename.
|
||||
///
|
||||
/// `cut` is whatever the previous load of this grid had already decoded,
|
||||
/// keyed by face, and is consumed: a crop found there is moved into the new
|
||||
/// cell and neither read from the catalog nor decoded again. A confirm or a
|
||||
/// reject changes one face's row and redraws the whole grid, and without this
|
||||
/// the redraw re-read four megabytes of JPEG and decoded seven hundred of
|
||||
/// them — 300 ms on the reference library's largest person, per click, to
|
||||
/// arrive at pixels the screen was already showing. Face ids are global, so
|
||||
/// a map from another person's grid is merely useless, never wrong.
|
||||
pub fn load_faces(
|
||||
catalog: &Catalog,
|
||||
store: &ThumbStore,
|
||||
person: PersonId,
|
||||
mut cut: std::collections::HashMap<FaceId, FaceCrop>,
|
||||
) -> Result<Vec<FaceCell>, dr_catalog::CatalogError> {
|
||||
let conn = catalog.connection();
|
||||
let rows = faces::for_person(conn, person, true)?;
|
||||
|
||||
// The crops kept at detection time, in one query. Where a face has one this
|
||||
// is the whole cost of drawing it — no proxy, no full-size JPEG decode, and
|
||||
// no dependence on the thumbnail cache still holding the photograph.
|
||||
let stored = faces::crops_for_person(conn, person, true)?;
|
||||
// The crops kept at detection time, in one query — but only when a face
|
||||
// is not already in hand. The common redraw has every face cached and
|
||||
// skips the blob read entirely; a face the cache lacks (a regroup, a
|
||||
// fresh sweep) costs the read for the whole person once, and it is
|
||||
// cached from then on.
|
||||
//
|
||||
// Where a face has a stored crop this is the whole cost of drawing it —
|
||||
// no proxy, no full-size JPEG decode, and no dependence on the thumbnail
|
||||
// cache still holding the photograph.
|
||||
let stored = if rows.iter().all(|f| cut.contains_key(&f.id)) {
|
||||
Default::default()
|
||||
} else {
|
||||
faces::crops_for_person(conn, person, true)?
|
||||
};
|
||||
|
||||
// The fallback path, for faces indexed before crops were kept. One decode
|
||||
// per *image*, not per face: a group photograph holding six faces of one
|
||||
@@ -283,7 +303,10 @@ pub fn load_faces(
|
||||
|
||||
let mut out = Vec::with_capacity(rows.len());
|
||||
for f in rows {
|
||||
let crop = match stored.get(&f.id).and_then(|b| decode_crop(b)) {
|
||||
let crop = match cut
|
||||
.remove(&f.id)
|
||||
.or_else(|| stored.get(&f.id).and_then(|b| decode_crop(b)))
|
||||
{
|
||||
Some(c) => Some(c),
|
||||
None => {
|
||||
let entry = decoded
|
||||
@@ -657,7 +680,7 @@ pub fn preview_split(
|
||||
.map(|(c, _)| c)
|
||||
.unwrap_or_default();
|
||||
|
||||
let cells = load_faces(catalog, store, person)?;
|
||||
let cells = load_faces(catalog, store, person, Default::default())?;
|
||||
if cells.len() < 2 {
|
||||
return Ok(vec![cells]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user