diff --git a/mobile/lib/ui/viewer/people/person_cluster_suggestion.dart b/mobile/lib/ui/viewer/people/person_cluster_suggestion.dart index e310c5af8..bf30f928c 100644 --- a/mobile/lib/ui/viewer/people/person_cluster_suggestion.dart +++ b/mobile/lib/ui/viewer/people/person_cluster_suggestion.dart @@ -1,3 +1,4 @@ +import "dart:async" show unawaited; import "dart:math"; import "package:flutter/foundation.dart" show kDebugMode; @@ -61,8 +62,9 @@ class _PersonClustersState extends State { ), ); } - final numberOfDifferentSuggestions = snapshot.data!.length; - final currentSuggestion = snapshot.data![currentSuggestionIndex]; + final allSuggestions = snapshot.data!; + final numberOfDifferentSuggestions = allSuggestions.length; + final currentSuggestion = allSuggestions[currentSuggestionIndex]; final int clusterID = currentSuggestion.clusterIDToMerge; final double distance = currentSuggestion.distancePersonToCluster; final bool usingMean = currentSuggestion.usedOnlyMeanForSuggestion; @@ -90,6 +92,7 @@ class _PersonClustersState extends State { usingMean, files, numberOfDifferentSuggestions, + allSuggestions, ), ), ); @@ -155,8 +158,9 @@ class _PersonClustersState extends State { bool usingMean, List files, int numberOfSuggestions, + List allSuggestions, ) { - return Column( + final widgetToReturn = Column( key: ValueKey("cluster_id-$clusterID"), children: [ if (kDebugMode) @@ -233,6 +237,28 @@ class _PersonClustersState extends State { ), ], ); + // Precompute face thumbnails for next suggestions, in case there are + const precompute = 6; + const maxComputations = 10; + int compCount = 0; + + if (allSuggestions.length > currentSuggestionIndex + 1) { + for (final suggestion in allSuggestions.sublist( + currentSuggestionIndex + 1, + min(allSuggestions.length, currentSuggestionIndex + precompute), + )) { + final files = suggestion.filesInCluster; + final clusterID = suggestion.clusterIDToMerge; + for (final file in files.sublist(0, min(files.length, 8))) { + unawaited(PersonFaceWidget.precomputeFaceCrops(file, clusterID)); + compCount++; + if (compCount >= maxComputations) { + break; + } + } + } + } + return widgetToReturn; } List _buildThumbnailWidgets( diff --git a/mobile/lib/ui/viewer/search/result/person_face_widget.dart b/mobile/lib/ui/viewer/search/result/person_face_widget.dart index f2c96fc7a..bdbfaab94 100644 --- a/mobile/lib/ui/viewer/search/result/person_face_widget.dart +++ b/mobile/lib/ui/viewer/search/result/person_face_widget.dart @@ -33,9 +33,64 @@ class PersonFaceWidget extends StatelessWidget { ), super(key: key); + static Future precomputeFaceCrops(file, clusterID) async { + try { + final Face? face = await FaceMLDataDB.instance.getCoverFaceForPerson( + recentFileID: file.uploadedFileID!, + clusterID: clusterID, + ); + if (face == null) { + debugPrint( + "No cover face for cluster $clusterID and recentFile ${file.uploadedFileID}", + ); + return; + } + final Uint8List? cachedFace = faceCropCache.get(face.faceID); + if (cachedFace != null) { + return; + } + final faceCropCacheFile = cachedFaceCropPath(face.faceID); + if ((await faceCropCacheFile.exists())) { + final data = await faceCropCacheFile.readAsBytes(); + faceCropCache.put(face.faceID, data); + return; + } + EnteFile? fileForFaceCrop = file; + if (face.fileID != file.uploadedFileID!) { + fileForFaceCrop = + await FilesDB.instance.getAnyUploadedFile(face.fileID); + } + if (fileForFaceCrop == null) { + return; + } + + final result = await pool.withResource( + () async => await getFaceCrops( + fileForFaceCrop!, + { + face.faceID: face.detection.box, + }, + ), + ); + final Uint8List? computedCrop = result?[face.faceID]; + if (computedCrop != null) { + faceCropCache.put(face.faceID, computedCrop); + faceCropCacheFile.writeAsBytes(computedCrop).ignore(); + } + return; + } catch (e, s) { + log( + "Error getting cover face for cluster $clusterID", + error: e, + stackTrace: s, + ); + return; + } + } + @override Widget build(BuildContext context) { - if (useGeneratedFaceCrops) { + if (!useGeneratedFaceCrops) { return FutureBuilder( future: getFaceCrop(), builder: (context, snapshot) {