diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index fce0243f2..d8a3c5b60 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -237,6 +237,8 @@ class _RemotePageState extends State _ffi.inputModel.enterOrLeave(false); DesktopMultiWindow.removeListener(this); _ffi.dialogManager.hideMobileActionsOverlay(); + _ffi.imageModel.disposeImage(); + _ffi.cursorModel.disposeImages(); _ffi.recordingModel.onClose(); _rawKeyFocusNode.dispose(); await _ffi.close(closeSession: closeSession); diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index ce46f5d43..a73f45fee 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -90,6 +90,8 @@ class _RemotePageState extends State { super.dispose(); gFFI.dialogManager.hideMobileActionsOverlay(); gFFI.inputModel.listenToMouse(false); + gFFI.imageModel.disposeImage(); + gFFI.cursorModel.disposeImages(); await gFFI.invokeMethod("enable_soft_keyboard", true); _mobileFocusNode.dispose(); _physicalFocusNode.dispose(); diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index d897a9ec2..a83c381ed 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -1208,6 +1208,7 @@ class ImageModel with ChangeNotifier { parent.target?.canvasModel.updateViewStyle(); } } + _image?.dispose(); _image = image; if (image != null) notifyListeners(); } @@ -1231,6 +1232,11 @@ class ImageModel with ChangeNotifier { final yscale = size.height / _image!.height; return min(xscale, yscale) / 1.5; } + + void disposeImage() { + _image?.dispose(); + _image = null; + } } enum ScrollStyle { @@ -1702,6 +1708,7 @@ class PredefinedCursor { final defaultImg = _image2!; // This function is called only one time, no need to care about the performance. Uint8List data = defaultImg.getBytes(order: img2.ChannelOrder.rgba); + _image?.dispose(); _image = await img.decodeImageFromPixels( data, defaultImg.width, defaultImg.height, ui.PixelFormat.rgba8888); @@ -1937,6 +1944,11 @@ class CursorModel with ChangeNotifier { notifyListeners(); } + disposeImages() { + _images.forEach((_, v) => v.item1.dispose()); + _images.clear(); + } + updateCursorData(Map evt) async { final id = int.parse(evt['id']); final hotx = double.parse(evt['hotx']); @@ -1947,7 +1959,11 @@ class CursorModel with ChangeNotifier { final rgba = Uint8List.fromList(colors.map((s) => s as int).toList()); final image = await img.decodeImageFromPixels( rgba, width, height, ui.PixelFormat.rgba8888); + if (image == null) { + return; + } if (await _updateCache(rgba, image, id, hotx, hoty, width, height)) { + _images[id]?.item1.dispose(); _images[id] = Tuple3(image, hotx, hoty); } diff --git a/flutter/lib/utils/image.dart b/flutter/lib/utils/image.dart index 2a208a683..e62239501 100644 --- a/flutter/lib/utils/image.dart +++ b/flutter/lib/utils/image.dart @@ -5,7 +5,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_hbb/common.dart'; -Future decodeImageFromPixels( +Future decodeImageFromPixels( Uint8List pixels, int width, int height, @@ -18,36 +18,74 @@ Future decodeImageFromPixels( }) async { if (targetWidth != null) { assert(allowUpscaling || targetWidth <= width); + if (!(allowUpscaling || targetWidth <= width)) { + print("not allow upscaling but targetWidth > width"); + return null; + } } if (targetHeight != null) { assert(allowUpscaling || targetHeight <= height); - } - - final ui.ImmutableBuffer buffer = - await ui.ImmutableBuffer.fromUint8List(pixels); - onPixelsCopied?.call(); - final ui.ImageDescriptor descriptor = ui.ImageDescriptor.raw( - buffer, - width: width, - height: height, - rowBytes: rowBytes, - pixelFormat: format, - ); - if (!allowUpscaling) { - if (targetWidth != null && targetWidth > descriptor.width) { - targetWidth = descriptor.width; - } - if (targetHeight != null && targetHeight > descriptor.height) { - targetHeight = descriptor.height; + if (!(allowUpscaling || targetHeight <= height)) { + print("not allow upscaling but targetHeight > height"); + return null; } } - final ui.Codec codec = await descriptor.instantiateCodec( - targetWidth: targetWidth, - targetHeight: targetHeight, - ); + final ui.ImmutableBuffer buffer; + try { + buffer = await ui.ImmutableBuffer.fromUint8List(pixels); + onPixelsCopied?.call(); + } catch (e) { + return null; + } + + final ui.ImageDescriptor descriptor; + try { + descriptor = ui.ImageDescriptor.raw( + buffer, + width: width, + height: height, + rowBytes: rowBytes, + pixelFormat: format, + ); + if (!allowUpscaling) { + if (targetWidth != null && targetWidth > descriptor.width) { + targetWidth = descriptor.width; + } + if (targetHeight != null && targetHeight > descriptor.height) { + targetHeight = descriptor.height; + } + } + } catch (e) { + print("ImageDescriptor.raw failed: $e"); + buffer.dispose(); + return null; + } + + final ui.Codec codec; + try { + codec = await descriptor.instantiateCodec( + targetWidth: targetWidth, + targetHeight: targetHeight, + ); + } catch (e) { + print("instantiateCodec failed: $e"); + buffer.dispose(); + descriptor.dispose(); + return null; + } + + final ui.FrameInfo frameInfo; + try { + frameInfo = await codec.getNextFrame(); + } catch (e) { + print("getNextFrame failed: $e"); + codec.dispose(); + buffer.dispose(); + descriptor.dispose(); + return null; + } - final ui.FrameInfo frameInfo = await codec.getNextFrame(); codec.dispose(); buffer.dispose(); descriptor.dispose();