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

This commit is contained in:
laurenspriem 2024-04-30 10:46:16 +05:30
parent 8b1545239c
commit 1cd31d2cab
2 changed files with 73 additions and 8 deletions

View file

@ -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}",

View file

@ -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;
}
}
}