From 9fa566723492e02e0346d0caaef292ed7d2c3e0b Mon Sep 17 00:00:00 2001 From: Krille Fear Date: Fri, 11 Mar 2022 09:23:51 +0100 Subject: [PATCH] refactor: Make MatrixFile final and move all image calculation into isolate This makes all fields in a MatrixFile final and the object therefore stateless. It also moves all calculations into the isolate. After some benchmarks it seems that this does not really speed up the thumbnail creation but it does no longer block the UI for some seconds. --- lib/src/utils/matrix_file.dart | 126 +++++++++++++++++++++----------- test/encryption/utils_test.dart | 2 +- 2 files changed, 86 insertions(+), 42 deletions(-) diff --git a/lib/src/utils/matrix_file.dart b/lib/src/utils/matrix_file.dart index 356225a0..7f360918 100644 --- a/lib/src/utils/matrix_file.dart +++ b/lib/src/utils/matrix_file.dart @@ -28,9 +28,9 @@ import 'package:mime/mime.dart'; import '../../matrix.dart'; class MatrixFile { - Uint8List bytes; - String name; - String mimeType; + final Uint8List bytes; + final String name; + final String mimeType; /// Encrypts this file and returns the /// encryption information as an [EncryptedFile]. @@ -66,14 +66,36 @@ class MatrixFile { } class MatrixImageFile extends MatrixFile { - Image? _image; - MatrixImageFile({ required Uint8List bytes, required String name, String? mimeType, + this.width, + this.height, + this.blurhash, }) : super(bytes: bytes, name: name, mimeType: mimeType); + /// Creates a new image file and calculates the width, height and blurhash. + static Future create( + {required Uint8List bytes, + required String name, + String? mimeType, + Future Function(FutureOr Function(U arg) function, U arg)? + compute}) async { + final metaData = compute != null + ? await compute(_calcMetadata, bytes) + : _calcMetadata(bytes); + + return MatrixImageFile( + bytes: metaData?.bytes ?? bytes, + name: name, + mimeType: mimeType, + width: metaData?.width, + height: metaData?.height, + blurhash: metaData?.blurhash, + ); + } + /// builds a [MatrixImageFile] and shrinks it in order to reduce traffic /// /// in case shrinking does not work (e.g. for unsupported MIME types), the @@ -85,11 +107,11 @@ class MatrixImageFile extends MatrixFile { String? mimeType, Future Function(FutureOr Function(U arg) function, U arg)? compute}) async { - Image? image; final arguments = _ResizeArguments( bytes: bytes, maxDimension: maxDimension, fileName: name, + calcBlurhash: true, ); final resizedData = compute != null ? await compute(_resize, arguments) @@ -98,48 +120,26 @@ class MatrixImageFile extends MatrixFile { if (resizedData == null) { return MatrixImageFile(bytes: bytes, name: name, mimeType: mimeType); } - image = decodeImage(resizedData); - - if (image == null) { - return MatrixImageFile(bytes: bytes, name: name, mimeType: mimeType); - } - - final encoded = encodeNamedImage(image, name); - if (encoded == null) { - return MatrixImageFile(bytes: bytes, name: name, mimeType: mimeType); - } final thumbnailFile = MatrixImageFile( - bytes: Uint8List.fromList(encoded), + bytes: resizedData.bytes, name: name, mimeType: mimeType, + width: resizedData.width, + height: resizedData.height, + blurhash: resizedData.blurhash, ); - // preserving the previously generated image - thumbnailFile._image = image; return thumbnailFile; } /// returns the width of the image - int? get width { - _image ??= decodeImage(bytes); - return _image?.width; - } + final int? width; /// returns the height of the image - int? get height { - _image ??= decodeImage(bytes); - return _image?.height; - } + final int? height; /// generates the blur hash for the image - String? get blurhash { - _image ??= decodeImage(bytes)!; - if (_image != null) { - final blur = BlurHash.encode(_image!, numCompX: 4, numCompY: 3); - return blur.hash; - } - return null; - } + final String? blurhash; @override String get msgType => 'm.image'; @@ -171,7 +171,23 @@ class MatrixImageFile extends MatrixFile { return thumbnailFile; } - static Uint8List? _resize(_ResizeArguments arguments) { + static _ResizedResponse? _calcMetadata(Uint8List bytes) { + final image = decodeImage(bytes); + if (image == null) return null; + + return _ResizedResponse( + bytes: bytes, + width: image.width, + height: image.height, + blurhash: BlurHash.encode( + image, + numCompX: 4, + numCompY: 3, + ).hash, + ); + } + + static _ResizedResponse? _resize(_ResizeArguments arguments) { final image = decodeImage(arguments.bytes); final resized = copyResize(image!, @@ -180,26 +196,54 @@ class MatrixImageFile extends MatrixFile { final encoded = encodeNamedImage(resized, arguments.fileName); if (encoded == null) return null; - return Uint8List.fromList(encoded); + final bytes = Uint8List.fromList(encoded); + return _ResizedResponse( + bytes: bytes, + width: resized.width, + height: resized.height, + blurhash: arguments.calcBlurhash + ? BlurHash.encode( + resized, + numCompX: 4, + numCompY: 3, + ).hash + : null, + ); } } +class _ResizedResponse { + final Uint8List bytes; + final int width; + final int height; + final String? blurhash; + + const _ResizedResponse({ + required this.bytes, + required this.width, + required this.height, + this.blurhash, + }); +} + class _ResizeArguments { final Uint8List bytes; final int maxDimension; final String fileName; + final bool calcBlurhash; const _ResizeArguments({ required this.bytes, required this.maxDimension, required this.fileName, + required this.calcBlurhash, }); } class MatrixVideoFile extends MatrixFile { - int? width; - int? height; - int? duration; + final int? width; + final int? height; + final int? duration; MatrixVideoFile( {required Uint8List bytes, @@ -221,7 +265,7 @@ class MatrixVideoFile extends MatrixFile { } class MatrixAudioFile extends MatrixFile { - int? duration; + final int? duration; MatrixAudioFile( {required Uint8List bytes, diff --git a/test/encryption/utils_test.dart b/test/encryption/utils_test.dart index 79a7f8d2..b21d0990 100644 --- a/test/encryption/utils_test.dart +++ b/test/encryption/utils_test.dart @@ -48,7 +48,7 @@ void main() { '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'; final data = base64Decode(base64Image); - final image = MatrixImageFile( + final image = await MatrixImageFile.create( bytes: data, name: 'bomb.png', mimeType: 'image/png',