diff --git a/lib/utils/custom_image_resizer.dart b/lib/utils/custom_image_resizer.dart index 48b803a90..f783d5cac 100644 --- a/lib/utils/custom_image_resizer.dart +++ b/lib/utils/custom_image_resizer.dart @@ -1,67 +1,103 @@ -import 'dart:typed_data'; 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 { + if (kIsWeb) { + throw UnsupportedError( + 'customImageResizer only supports non-web platforms.', + ); + } + await native.init(); - late native.Image nativeImg; + + var imageBytes = arguments.bytes; + String? blurhash; + + var originalWidth = 0; + var originalHeight = 0; + var width = 0; + var height = 0; try { - nativeImg = await native.Image.loadEncoded(arguments.bytes); // load on web - } on UnsupportedError { - try { - // for the other platforms - final dartCodec = await instantiateImageCodec(arguments.bytes); - 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, - ); + // 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, + ); - final width = dartFrame.image.width; - final height = dartFrame.image.height; + width = originalWidth = dartFrame.image.width; + height = originalHeight = dartFrame.image.height; - dartFrame.image.dispose(); - dartCodec.dispose(); + var nativeImg = native.Image.fromRGBA(width, height, rgba); - nativeImg = native.Image.fromRGBA(width, height, rgba); - } catch (e, s) { - Logs().e("Could not generate preview", e, s); - rethrow; - } - } + dartFrame.image.dispose(); + dartCodec.dispose(); - final width = nativeImg.width; - final height = nativeImg.height; + 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, + ); - final max = arguments.maxDimension; - if (width > max || height > max) { - var w = max, h = max; - if (width > height) { - h = max * height ~/ width; - } else { - w = max * width ~/ height; + blurhash = blurhashImg.toBlurhash(3, 3); + + blurhashImg.free(); } - final scaledImg = nativeImg.resample(w, h, native.Transform.lanczos); - nativeImg.free(); - nativeImg = scaledImg; + 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); } - final jpegBytes = await nativeImg.toJpeg(75); return MatrixImageFileResizedResponse( - bytes: jpegBytes, - width: nativeImg.width, - height: nativeImg.height, - blurhash: arguments.calcBlurhash ? nativeImg.toBlurhash(3, 3) : null, + bytes: imageBytes, + width: width, + height: height, + originalWidth: originalWidth, + originalHeight: originalHeight, + blurhash: blurhash, ); }