diff --git a/mobile/lib/ui/viewer/file_details/face_widget.dart b/mobile/lib/ui/viewer/file_details/face_widget.dart index da592a150..9d1fa8dec 100644 --- a/mobile/lib/ui/viewer/file_details/face_widget.dart +++ b/mobile/lib/ui/viewer/file_details/face_widget.dart @@ -267,7 +267,7 @@ class _FaceWidgetState extends State { SizedBox( width: 60, height: 60, - child: CroppedFaceImageView( + child: CroppedFaceImgImageView( enteFile: widget.file, face: widget.face, ), diff --git a/mobile/lib/ui/viewer/people/cropped_face_image_view.dart b/mobile/lib/ui/viewer/people/cropped_face_image_view.dart index 0da4129eb..c7e840e71 100644 --- a/mobile/lib/ui/viewer/people/cropped_face_image_view.dart +++ b/mobile/lib/ui/viewer/people/cropped_face_image_view.dart @@ -1,15 +1,14 @@ import "dart:io" show File; +import "dart:typed_data"; import 'dart:ui' as ui; -import "package:computer/computer.dart"; import 'package:flutter/material.dart'; import "package:flutter/widgets.dart"; -import "package:flutter_image_compress/flutter_image_compress.dart"; -import "package:image/image.dart" as img; import "package:logging/logging.dart"; import "package:photos/face/model/face.dart"; import "package:photos/models/file/file.dart"; import "package:photos/ui/viewer/file/thumbnail_widget.dart"; +import "package:photos/utils/face/face_util.dart"; import "package:photos/utils/file_util.dart"; import "package:photos/utils/image_util.dart"; @@ -27,23 +26,22 @@ class CroppedFaceInfo { }); } -class CroppedFaceImageView extends StatefulWidget { +class CroppedFaceImgImageView extends StatefulWidget { final EnteFile enteFile; final Face face; - const CroppedFaceImageView({ + const CroppedFaceImgImageView({ Key? key, required this.enteFile, required this.face, }) : super(key: key); @override - CroppedFaceImageViewState createState() => CroppedFaceImageViewState(); + CroppedFaceImgImageViewState createState() => CroppedFaceImgImageViewState(); } -class CroppedFaceImageViewState extends State { +class CroppedFaceImgImageViewState extends State { ui.Image? _image; - final _computer = Computer.shared(); final _logger = Logger("CroppedFaceImageView"); @override @@ -89,39 +87,9 @@ class CroppedFaceImageViewState extends State { return null; } - img.Image? image = await _computer - .compute(decodeImage, param: {"filePath": ioFile.path}); + final image = await generateImgFaceThumbnails(ioFile.path, [faceBox]); - if (image == null) { - _logger.info( - "Failed to decode image ${widget.enteFile.title}. Compressing to jpg and decoding", - ); - final compressedJPGImage = - await FlutterImageCompress.compressWithFile(ioFile.path); - image = await _computer.compute( - decodeJPGImage, - param: {"image": compressedJPGImage}, - ); - - if (image == null) { - throw Exception("Failed to decode image"); - } - } - - final stopwatch = Stopwatch()..start(); - final croppedImage = img.copyCrop( - image, - x: (image.width * faceBox.xMin).round(), - y: (image.height * faceBox.yMin).round(), - width: (image.width * faceBox.width).round(), - height: (image.height * faceBox.height).round(), - antialias: false, - ); - _logger.info( - "Image crop took ${stopwatch.elapsedMilliseconds}ms ----------------", - ); - stopwatch.stop(); - return convertImageToFlutterUi(croppedImage); + return convertImageToFlutterUi(image.first); } catch (e, s) { _logger.severe("Error getting image", e, s); return null; @@ -129,10 +97,72 @@ class CroppedFaceImageViewState extends State { } } -Future decodeImage(Map args) async { - return await img.decodeImageFile(args["filePath"]); +class CroppedFaceJpgImageView extends StatefulWidget { + final EnteFile enteFile; + final Face face; + + const CroppedFaceJpgImageView({ + Key? key, + required this.enteFile, + required this.face, + }) : super(key: key); + + @override + CroppedFaceJpgImageViewState createState() => CroppedFaceJpgImageViewState(); } -img.Image? decodeJPGImage(Map args) { - return img.decodeJpg(args["image"])!; +class CroppedFaceJpgImageViewState extends State { + Uint8List? _image; + final _logger = Logger("CroppedFaceImageView"); + + @override + void initState() { + super.initState(); + _loadImage(); + } + + @override + void dispose() { + super.dispose(); + } + + Future _loadImage() async { + final image = await getImage(); + if (mounted) { + setState(() { + _image = image; + }); + } + } + + @override + Widget build(BuildContext context) { + return _image != null + ? LayoutBuilder( + builder: (context, constraints) { + return Image.memory( + _image!, + ); + }, + ) + : ThumbnailWidget(widget.enteFile); + } + + Future getImage() async { + try { + final faceBox = widget.face.detection.box; + + final File? ioFile = await getFile(widget.enteFile); + if (ioFile == null) { + return null; + } + + final image = await generateJpgFaceThumbnails(ioFile.path, [faceBox]); + + return image.first; + } catch (e, s) { + _logger.severe("Error getting image", e, s); + return null; + } + } } 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..0f872e9f3 100644 --- a/mobile/lib/ui/viewer/search/result/person_face_widget.dart +++ b/mobile/lib/ui/viewer/search/result/person_face_widget.dart @@ -69,7 +69,7 @@ class PersonFaceWidget extends StatelessWidget { return Stack( fit: StackFit.expand, children: [ - CroppedFaceImageView(enteFile: file, face: face), + CroppedFaceImgImageView(enteFile: file, face: face), ], ); } else {