From 278986c78824f33a29081a8e7384b1fee5b4b42e Mon Sep 17 00:00:00 2001 From: Krille Fear Date: Sat, 20 Nov 2021 16:54:18 +0100 Subject: [PATCH] refactor: Use image package to resize images --- lib/pages/chat_details/chat_details.dart | 11 +- .../new_private_chat/send_file_dialog.dart | 3 +- lib/pages/settings/settings.dart | 11 +- .../settings_emotes/settings_emotes.dart | 4 +- lib/utils/resize_image.dart | 153 +++++------------- lib/utils/room_send_file_extension.dart | 2 +- pubspec.lock | 24 ++- pubspec.yaml | 4 +- 8 files changed, 64 insertions(+), 148 deletions(-) diff --git a/lib/pages/chat_details/chat_details.dart b/lib/pages/chat_details/chat_details.dart index cdf77b6f..e895edc4 100644 --- a/lib/pages/chat_details/chat_details.dart +++ b/lib/pages/chat_details/chat_details.dart @@ -297,12 +297,11 @@ class ChatDetailsController extends State { MatrixFile file; if (PlatformInfos.isMobile) { final result = await ImagePicker().pickImage( - source: action == AvatarAction.camera - ? ImageSource.camera - : ImageSource.gallery, - imageQuality: 50, - maxWidth: 1600, - maxHeight: 1600); + source: action == AvatarAction.camera + ? ImageSource.camera + : ImageSource.gallery, + imageQuality: 50, + ); if (result == null) return; file = MatrixFile( bytes: await result.readAsBytes(), diff --git a/lib/pages/new_private_chat/send_file_dialog.dart b/lib/pages/new_private_chat/send_file_dialog.dart index 14cd8520..f67ef7d2 100644 --- a/lib/pages/new_private_chat/send_file_dialog.dart +++ b/lib/pages/new_private_chat/send_file_dialog.dart @@ -26,12 +26,11 @@ class _SendFileDialogState extends State { bool origImage = false; bool _isSending = false; - static const maxWidth = 1600; Future _send() async { var file = widget.file; if (file is MatrixImageFile && !origImage) { try { - file = await resizeImage(file, max: maxWidth); + file = await file.resizeImage(); } catch (e) { // couldn't resize } diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index b0a43da4..1812b697 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -79,12 +79,11 @@ class SettingsController extends State { MatrixFile file; if (PlatformInfos.isMobile) { final result = await ImagePicker().pickImage( - source: action == AvatarAction.camera - ? ImageSource.camera - : ImageSource.gallery, - imageQuality: 50, - maxWidth: 1600, - maxHeight: 1600); + source: action == AvatarAction.camera + ? ImageSource.camera + : ImageSource.gallery, + imageQuality: 50, + ); if (result == null) return; file = MatrixFile( bytes: await result.readAsBytes(), diff --git a/lib/pages/settings_emotes/settings_emotes.dart b/lib/pages/settings_emotes/settings_emotes.dart index b576587e..bf9b02d3 100644 --- a/lib/pages/settings_emotes/settings_emotes.dart +++ b/lib/pages/settings_emotes/settings_emotes.dart @@ -198,8 +198,6 @@ class EmotesSettingsController extends State { }); } - static const maxImageWidth = 1600; - void imagePickerAction( ValueNotifier controller) async { final result = @@ -210,7 +208,7 @@ class EmotesSettingsController extends State { name: result.fileName, ); try { - file = await resizeImage(file, max: maxImageWidth); + file = await file.resizeImage(calcBlurhash: false); } catch (_) { // do nothing } diff --git a/lib/utils/resize_image.dart b/lib/utils/resize_image.dart index 050c7677..ffd1f6b6 100644 --- a/lib/utils/resize_image.dart +++ b/lib/utils/resize_image.dart @@ -1,126 +1,49 @@ +//@dart=2.12 + +import 'dart:math' as math; import 'dart:typed_data'; -import 'dart:ui'; +import 'package:flutter/foundation.dart'; + +import 'package:blurhash_dart/blurhash_dart.dart'; +import 'package:image/image.dart'; import 'package:matrix/matrix.dart'; -import 'package:native_imaging/native_imaging.dart' as native; -import 'run_in_background.dart'; +extension ResizeImage on MatrixFile { + static const int max = 1200; + static const int quality = 20; -const int defaultMax = 800; - -Future resizeImage(MatrixImageFile file, - {int max = defaultMax}) async { - // we want to resize the image in a separate isolate, because otherwise that can - // freeze up the UI a bit - - // we can't do width / height fetching in a separate isolate, as that may use the UI stuff - - // somehow doing native.init twice fixes it for linux desktop? - // TODO: once native imaging is on sound null safety the errors are consistent and - // then we can properly handle this instead - // https://gitlab.com/famedly/company/frontend/libraries/native_imaging/-/issues/5 - try { - await native.init(); - } catch (_) { - await native.init(); + Future resizeImage({bool calcBlurhash = true}) async { + final bytes = await compute(resizeBytes, this.bytes); + final blurhash = calcBlurhash + ? await compute(createBlurHash, bytes) + : null; + return MatrixImageFile( + bytes: bytes, + name: '${name.split('.').first}_thumbnail_$max.jpg', + blurhash: blurhash?.hash, + ); } - - _IsolateArgs args; - try { - final nativeImg = native.Image(); - await nativeImg.loadEncoded(file.bytes); - file.width = nativeImg.width; - file.height = nativeImg.height; - args = _IsolateArgs( - width: file.width, height: file.height, bytes: file.bytes, max: max); - nativeImg.free(); - } on UnsupportedError { - final dartCodec = await instantiateImageCodec(file.bytes); - final dartFrame = await dartCodec.getNextFrame(); - file.width = dartFrame.image.width; - file.height = dartFrame.image.height; - final rgbaData = await dartFrame.image.toByteData(); - final rgba = Uint8List.view( - rgbaData.buffer, rgbaData.offsetInBytes, rgbaData.lengthInBytes); - dartFrame.image.dispose(); - dartCodec.dispose(); - args = _IsolateArgs( - width: file.width, height: file.height, bytes: rgba, max: max); - } - - final res = await runInBackground(_isolateFunction, args); - file.blurhash = res.blurhash; - final thumbnail = MatrixImageFile( - bytes: res.jpegBytes, - name: file.name != null - ? 'scaled_' + file.name.split('.').first + '.jpg' - : 'thumbnail.jpg', - mimeType: 'image/jpeg', - width: res.width, - height: res.height, - blurhash: res.blurhash, - ); - // only return the thumbnail if the size actually decreased - return thumbnail.size >= file.size || - thumbnail.width >= file.width || - thumbnail.height >= file.height - ? file - : thumbnail; } -class _IsolateArgs { - final int width; - final int height; - final Uint8List bytes; - final int max; - final String name; - _IsolateArgs({this.width, this.height, this.bytes, this.max, this.name}); +Future createBlurHash(Uint8List file) async { + final image = decodeImage(file)!; + return BlurHash.encode(image, numCompX: 4, numCompY: 3); } -class _IsolateResponse { - final String blurhash; - final Uint8List jpegBytes; - final int width; - final int height; - _IsolateResponse({this.blurhash, this.jpegBytes, this.width, this.height}); -} - -Future<_IsolateResponse> _isolateFunction(_IsolateArgs args) async { - // Hack for desktop, see above why - try { - await native.init(); - } catch (_) { - await native.init(); - } - var nativeImg = native.Image(); - - try { - await nativeImg.loadEncoded(args.bytes); - } on UnsupportedError { - nativeImg.loadRGBA(args.width, args.height, args.bytes); - } - if (args.width > args.max || args.height > args.max) { - var w = args.max, h = args.max; - if (args.width > args.height) { - h = args.max * args.height ~/ args.width; - } else { - w = args.max * args.width ~/ args.height; - } - - final scaledImg = nativeImg.resample(w, h, native.Transform.lanczos); - nativeImg.free(); - nativeImg = scaledImg; - } - final jpegBytes = await nativeImg.toJpeg(75); - final blurhash = nativeImg.toBlurhash(3, 3); - - final ret = _IsolateResponse( - blurhash: blurhash, - jpegBytes: jpegBytes, - width: nativeImg.width, - height: nativeImg.height); - - nativeImg.free(); - - return ret; +Future resizeBytes(Uint8List file) async { + final image = decodeImage(file)!; + + // Is file already smaller than max? Then just return. + if (math.max(image.width, image.height) <= ResizeImage.max) { + return file; + } + + // Use the larger side to resize. + final useWidth = image.width >= image.height; + final thumbnail = useWidth + ? copyResize(image, width: ResizeImage.max) + : copyResize(image, height: ResizeImage.max); + + return Uint8List.fromList(encodeJpg(thumbnail, quality: ResizeImage.quality)); } diff --git a/lib/utils/room_send_file_extension.dart b/lib/utils/room_send_file_extension.dart index b8913134..c425843d 100644 --- a/lib/utils/room_send_file_extension.dart +++ b/lib/utils/room_send_file_extension.dart @@ -31,7 +31,7 @@ extension RoomSendFileExtension on Room { MatrixFile thumbnail; try { if (file is MatrixImageFile) { - thumbnail = await resizeImage(file); + thumbnail = await file.resizeImage(); if (thumbnail.size > file.size ~/ 2) { thumbnail = null; diff --git a/pubspec.lock b/pubspec.lock index f6a61965..7932827c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -92,6 +92,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + blurhash_dart: + dependency: "direct main" + description: + name: blurhash_dart + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" boolean_selector: dependency: transitive description: @@ -189,7 +196,7 @@ packages: name: cross_file url: "https://pub.dartlang.org" source: hosted - version: "0.3.1+3" + version: "0.3.2" crypto: dependency: transitive description: @@ -652,7 +659,7 @@ packages: source: hosted version: "4.0.0" image: - dependency: transitive + dependency: "direct main" description: name: image url: "https://pub.dartlang.org" @@ -664,14 +671,14 @@ packages: name: image_picker url: "https://pub.dartlang.org" source: hosted - version: "0.8.4+2" + version: "0.8.4+4" image_picker_for_web: dependency: transitive description: name: image_picker_for_web url: "https://pub.dartlang.org" source: hosted - version: "2.1.2" + version: "2.1.4" image_picker_platform_interface: dependency: transitive description: @@ -821,15 +828,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" - native_imaging: - dependency: "direct main" - description: - path: "." - ref: HEAD - resolved-ref: b3239a12a61a31efb8d02d3d665f2041be6651d6 - url: "https://gitlab.com/famedly/libraries/native_imaging.git" - source: git - version: "0.0.1" nested: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 310ac4ef..88543dc2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,6 +10,7 @@ dependencies: adaptive_dialog: ^1.1.0 adaptive_theme: ^2.2.0 audioplayers: ^0.20.1 + blurhash_dart: ^1.1.0 cached_network_image: ^3.1.0 chewie: ^1.2.2 cupertino_icons: any @@ -42,14 +43,13 @@ dependencies: future_loading_dialog: ^0.2.1 geolocator: ^7.6.2 hive_flutter: ^1.1.0 + image: ^3.0.8 image_picker: ^0.8.4+2 intl: any localstorage: ^4.0.0+1 lottie: ^1.2.1 matrix: ^0.7.0-nullsafety.6 matrix_link_text: ^1.0.2 - native_imaging: - git: https://gitlab.com/famedly/libraries/native_imaging.git open_noti_settings: ^0.2.0 package_info_plus: ^1.2.1 path_provider: ^2.0.5