Przeglądaj źródła

[mob][photos] Only decode image once for face thumbnails in file info

laurenspriem 1 rok temu
rodzic
commit
1cd31d2cab

+ 10 - 7
mobile/lib/ui/viewer/file_details/face_widget.dart

@@ -26,6 +26,7 @@ const useGeneratedFaceCrops = true;
 class FaceWidget extends StatefulWidget {
   final EnteFile file;
   final Face face;
+  final Future<Map<String, Uint8List>?>? faceCrops;
   final PersonEntity? person;
   final int? clusterID;
   final bool highlight;
@@ -34,6 +35,7 @@ class FaceWidget extends StatefulWidget {
   const FaceWidget(
     this.file,
     this.face, {
+    this.faceCrops,
     this.person,
     this.clusterID,
     this.highlight = false,
@@ -50,12 +52,13 @@ class _FaceWidgetState extends State<FaceWidget> {
 
   @override
   Widget build(BuildContext context) {
+    final bool givenFaces = widget.faceCrops != null;
     if (useGeneratedFaceCrops) {
-      return FutureBuilder<Uint8List?>(
-        future: getFaceCrop(),
+      return FutureBuilder<Map<String, Uint8List>?>(
+        future: givenFaces ? widget.faceCrops : getFaceCrop(),
         builder: (context, snapshot) {
           if (snapshot.hasData) {
-            final ImageProvider imageProvider = MemoryImage(snapshot.data!);
+            final ImageProvider imageProvider = MemoryImage(snapshot.data![widget.face.faceID]!);
 
             return GestureDetector(
               onTap: () async {
@@ -460,17 +463,17 @@ class _FaceWidgetState extends State<FaceWidget> {
     }
   }
 
-  Future<Uint8List?> getFaceCrop() async {
+  Future<Map<String, Uint8List>?> getFaceCrop() async {
     try {
       final Uint8List? cachedFace = faceCropCache.get(widget.face.faceID);
       if (cachedFace != null) {
-        return cachedFace;
+        return {widget.face.faceID: cachedFace};
       }
       final faceCropCacheFile = cachedFaceCropPath(widget.face.faceID);
       if ((await faceCropCacheFile.exists())) {
         final data = await faceCropCacheFile.readAsBytes();
         faceCropCache.put(widget.face.faceID, data);
-        return data;
+        return {widget.face.faceID: data};
       }
 
       final result = await pool.withResource(
@@ -486,7 +489,7 @@ class _FaceWidgetState extends State<FaceWidget> {
         faceCropCache.put(widget.face.faceID, computedCrop);
         faceCropCacheFile.writeAsBytes(computedCrop).ignore();
       }
-      return computedCrop;
+      return {widget.face.faceID: computedCrop!};
     } catch (e, s) {
       log(
         "Error getting face for faceID: ${widget.face.faceID}",

+ 63 - 1
mobile/lib/ui/viewer/file_details/faces_item_widget.dart

@@ -1,7 +1,10 @@
-import "package:flutter/foundation.dart" show kDebugMode;
+import "dart:developer" as dev show log;
+
+import "package:flutter/foundation.dart" show Uint8List, kDebugMode;
 import "package:flutter/material.dart";
 import "package:logging/logging.dart";
 import "package:photos/face/db.dart";
+import "package:photos/face/model/box.dart";
 import "package:photos/face/model/face.dart";
 import "package:photos/face/model/person.dart";
 import "package:photos/models/file/file.dart";
@@ -10,6 +13,8 @@ import "package:photos/services/machine_learning/face_ml/person/person_service.d
 import "package:photos/ui/components/buttons/chip_button_widget.dart";
 import "package:photos/ui/components/info_item_widget.dart";
 import "package:photos/ui/viewer/file_details/face_widget.dart";
+import "package:photos/utils/face/face_box_crop.dart";
+import "package:photos/utils/thumbnail_util.dart";
 
 class FacesItemWidget extends StatefulWidget {
   final EnteFile file;
@@ -119,6 +124,7 @@ class _FacesItemWidgetState extends State<FacesItemWidget> {
       final lastViewedClusterID = ClusterFeedbackService.lastViewedClusterID;
 
       final faceWidgets = <FaceWidget>[];
+      final faceCrops = getRelevantFaceCrops(faces);
       for (final Face face in faces) {
         final int? clusterID = faceIdsToClusterIds[face.faceID];
         final PersonEntity? person = clusterIDToPerson[clusterID] != null
@@ -130,6 +136,7 @@ class _FacesItemWidgetState extends State<FacesItemWidget> {
           FaceWidget(
             file,
             face,
+            faceCrops: faceCrops,
             clusterID: clusterID,
             person: person,
             highlight: highlight,
@@ -144,4 +151,59 @@ class _FacesItemWidgetState extends State<FacesItemWidget> {
       return <FaceWidget>[];
     }
   }
+
+  Future<Map<String, Uint8List>?> getRelevantFaceCrops(
+    Iterable<Face> faces,
+  ) async {
+    try {
+      final faceIdToCrop = <String, Uint8List>{};
+      final facesWithoutCrops = <String, FaceBox>{};
+      for (final face in faces) {
+        final Uint8List? cachedFace = faceCropCache.get(face.faceID);
+        if (cachedFace != null) {
+          faceIdToCrop[face.faceID] = cachedFace;
+        } else {
+          final faceCropCacheFile = cachedFaceCropPath(face.faceID);
+          if ((await faceCropCacheFile.exists())) {
+            final data = await faceCropCacheFile.readAsBytes();
+            faceCropCache.put(face.faceID, data);
+            faceIdToCrop[face.faceID] = data;
+          } else {
+            facesWithoutCrops[face.faceID] = face.detection.box;
+          }
+        }
+      }
+
+      if (facesWithoutCrops.isEmpty) {
+        return faceIdToCrop;
+      }
+
+      final result = await pool.withResource(
+        () async => await getFaceCrops(
+          widget.file,
+          facesWithoutCrops,
+        ),
+      );
+      if (result == null) {
+        return (faceIdToCrop.isEmpty) ? null : faceIdToCrop;
+      }
+      for (final entry in result.entries) {
+        final Uint8List? computedCrop = result[entry.key];
+        if (computedCrop != null) {
+          faceCropCache.put(entry.key, computedCrop);
+          final faceCropCacheFile = cachedFaceCropPath(entry.key);
+          faceCropCacheFile.writeAsBytes(computedCrop).ignore();
+          faceIdToCrop[entry.key] = computedCrop;
+        }
+      }
+      return (faceIdToCrop.isEmpty) ? null : faceIdToCrop;
+    } catch (e, s) {
+      dev.log(
+        "Error getting face crops for faceIDs: ${faces.map((face) => face.faceID).toList()}",
+        error: e,
+        stackTrace: s,
+      );
+      return null;
+    }
+  }
 }