From 162a594d8005a227b0f5ec23f610d81fd5866887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Fri, 20 Feb 2026 17:28:19 +0100 Subject: [PATCH] feat: Reenable native imaging for all platforms --- lib/utils/client_manager.dart | 2 + lib/utils/custom_image_resizer.dart | 101 ++++++++++++++++++++++++++++ pubspec.lock | 12 +++- pubspec.yaml | 1 + scripts/prepare-web.sh | 13 +++- 5 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 lib/utils/custom_image_resizer.dart diff --git a/lib/utils/client_manager.dart b/lib/utils/client_manager.dart index 31ef63971..713461ce1 100644 --- a/lib/utils/client_manager.dart +++ b/lib/utils/client_manager.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:fluffychat/utils/custom_image_resizer.dart'; import 'package:flutter/foundation.dart'; import 'package:collection/collection.dart'; @@ -122,6 +123,7 @@ abstract class ClientManager { // To make room emotes work 'im.ponies.room_emotes', }, + customImageResizer: customImageResizer, logLevel: kReleaseMode ? Level.warning : Level.verbose, database: await flutterMatrixSdkDatabaseBuilder(clientName), supportedLoginTypes: { diff --git a/lib/utils/custom_image_resizer.dart b/lib/utils/custom_image_resizer.dart new file mode 100644 index 000000000..9be5f206c --- /dev/null +++ b/lib/utils/custom_image_resizer.dart @@ -0,0 +1,101 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/painting.dart'; + +import 'package:matrix/matrix.dart'; +import 'package:native_imaging/native_imaging.dart' as native; + +(int, int) _scaleToBox(int width, int height, {required int boxSize}) { + final fit = applyBoxFit( + BoxFit.scaleDown, + Size(width.toDouble(), height.toDouble()), + Size(boxSize.toDouble(), boxSize.toDouble()), + ).destination; + return (fit.width.round(), fit.height.round()); +} + +Future customImageResizer( + MatrixImageFileResizeArguments arguments, +) async { + await native.init(); + + var imageBytes = arguments.bytes; + String? blurhash; + + var originalWidth = 0; + var originalHeight = 0; + var width = 0; + var height = 0; + + try { + // for the other platforms + final dartCodec = await instantiateImageCodec(arguments.bytes); + final frameCount = dartCodec.frameCount; + final dartFrame = await dartCodec.getNextFrame(); + final rgbaData = await dartFrame.image.toByteData(); + if (rgbaData == null) { + return null; + } + final rgba = Uint8List.view( + rgbaData.buffer, + rgbaData.offsetInBytes, + rgbaData.lengthInBytes, + ); + + width = originalWidth = dartFrame.image.width; + height = originalHeight = dartFrame.image.height; + + var nativeImg = native.Image.fromRGBA(width, height, rgba); + + dartFrame.image.dispose(); + dartCodec.dispose(); + + if (arguments.calcBlurhash) { + // scale down image for blurhashing to speed it up + final (blurW, blurH) = _scaleToBox(width, height, boxSize: 100); + final blurhashImg = nativeImg.resample( + blurW, + blurH, + // nearest is unsupported... + native.Transform.bilinear, + ); + + blurhash = blurhashImg.toBlurhash(3, 3); + + blurhashImg.free(); + } + + if (frameCount > 1) { + // Don't scale down animated images, since those would lose frames. + nativeImg.free(); + } else { + final max = arguments.maxDimension; + if (width > max || height > max) { + (width, height) = _scaleToBox(width, height, boxSize: max); + + final scaledImg = nativeImg.resample( + width, + height, + native.Transform.lanczos, + ); + nativeImg.free(); + nativeImg = scaledImg; + } + + imageBytes = await nativeImg.toJpeg(75); + nativeImg.free(); + } + } catch (e, s) { + Logs().e('Could not generate preview', e, s); + } + + return MatrixImageFileResizedResponse( + bytes: imageBytes, + width: width, + height: height, + originalWidth: originalWidth, + originalHeight: originalHeight, + blurhash: blurhash, + ); +} diff --git a/pubspec.lock b/pubspec.lock index e78f876fa..7a7792360 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -984,10 +984,10 @@ packages: dependency: transitive description: name: js - sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.6.7" json_annotation: dependency: transitive description: @@ -1156,6 +1156,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + native_imaging: + dependency: "direct main" + description: + name: native_imaging + sha256: b910bd7c4fae77d9f91fb59840995e4fb91505b20a46ee1be88db3e4c4e219c4 + url: "https://pub.dev" + source: hosted + version: "0.3.0" native_toolchain_c: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9fd3fc710..5fd6fa5a3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,6 +55,7 @@ dependencies: linkify: ^5.0.0 matrix: ^6.1.1 mime: ^2.0.0 + native_imaging: ^0.3.0 opus_caf_converter_dart: ^1.0.1 package_info_plus: ^9.0.0 particles_network: ^1.9.3 diff --git a/scripts/prepare-web.sh b/scripts/prepare-web.sh index 085274204..b4ec1889a 100755 --- a/scripts/prepare-web.sh +++ b/scripts/prepare-web.sh @@ -1,5 +1,6 @@ #!/bin/sh -ve +# Compile Vodozemac for web version=$(yq ".dependencies.flutter_vodozemac" < pubspec.yaml) version=$(expr "$version" : '\^*\(.*\)') git clone https://github.com/famedly/dart-vodozemac.git -b ${version} .vodozemac @@ -10,6 +11,14 @@ cd .. rm -f ./assets/vodozemac/vodozemac_bindings_dart* mv .vodozemac/dart/web/pkg/vodozemac_bindings_dart* ./assets/vodozemac/ rm -rf .vodozemac - flutter pub get -dart compile js ./web/native_executor.dart -o ./web/native_executor.js -m \ No newline at end of file +dart compile js ./web/native_executor.dart -o ./web/native_executor.js -m + +# Download native_imaging for web: +version=$(yq ".dependencies.native_imaging" < pubspec.yaml) +version=$(expr "$version" : '\^*\(.*\)') +curl -L "https://github.com/famedly/dart_native_imaging/releases/download/v${version}/native_imaging.zip" > native_imaging.zip +unzip native_imaging.zip +mv js/* web/ +rmdir js +rm native_imaging.zip \ No newline at end of file