如何以高效的方式在颤动中操纵图像像素?

How can I manipulate image pixels in flutter on a perfomant way?

我正在尝试开发一个在 Flutter 中使用像素转换的应用程序。在我目前的方法中,我通过 FFI 使用 Rust 来执行所有像素操作算法,但我注意到它的性能不是很好。在我的示例中,我使用滑块将 RED 通道从 0 更改为 255。我怀疑我没有使用最佳方法在颤动中渲染图像。我想要有助于提高性能的建议。这是一个例子:

我的组件

class _RedChannelPageState extends State<RedChannelPage> {
  Uint8List? _imageAsBytes;
  double _sliderValue = 0;
  ui.Image? _img;

  void _pickImage() async {
    final image = await ImagePicker().pickImage(source: ImageSource.gallery);
    if (image != null) {
      final imageAsbytes = await image.readAsBytes();
      loadImage(imageAsBytes).then((value) => {
            setState(() {
              _img = value;
              _imageAsBytes = imageAsbytes;
            })
          });
    }
  }

  Future<ui.Image> loadImage(Uint8List imageAsBytes) async {
    final Completer<ui.Image> completer = Completer();
    ui.decodeImageFromList(imageAsBytes, (ui.Image img) {
      return completer.complete(img);
    });
    return completer.future;
  }

  void _onSlideChange(double value) async {
    setState(() {
      _sliderValue = value;
    });
    final imgResult =
        await compute(spawnThread, [_imageAsBytes!, value.round()]);
    final decodedImage = await loadImage(imgResult);
    setState(() {
      _img = decodedImage;
    });
  }

  static Uint8List spawnThread(List<dynamic> list) {
    final result = FFI(imageAsBytes: list[0]).changeRed(list[1]);
    return result;
  }

我的 FFI class

final Pointer<Uint8> Function(Pointer<Uint8> data, int len, int red)
    nativeRedChannel = nativeAddLib
        .lookup<
            NativeFunction<
                Pointer<Uint8> Function(
                    Pointer<Uint8>, Int32, Int32)>>("red_channel")
        .asFunction();

class FFI {
  const FFI({
    required this.imageAsBytes,
  });
  final Uint8List imageAsBytes;
// send a pointer of Uint8List to rust function and return a encoded JPEG with alterations
  Uint8List changeRed(int redValue) {
    final Pointer<Uint8> allocation =
        malloc.allocate<Uint8>(imageAsBytes.length * sizeOf<Uint8>());
    allocation.asTypedList(imageAsBytes.length).setAll(0, imageAsBytes);
    final pointerResult =
        nativeRedChannel(allocation, imageAsBytes.length, redValue);
    final result = pointerResult.asTypedList(imageAsBytes.length);
    return result;
  }
}

Example

减少对计算方法的调用

给定你的方法名称 _onSlideChange 我假设你在滑块更新其值时调用它,如果是这种情况 - 那么考虑 throttling 可能是合理的计算方法,因此您不会产生多余的隔离物。

由于您使用的是 Material Slider - 而不是使用 onChanged 回调,您可以考虑 SlideronChangeEnd 回调,所以你只有在放开滑块拇指时才执行 compute

更改最大图像分辨率

请注意,如果您不需要图像的原始分辨率 - 那么在 ImagePicker 插件中您可以选择设置 maxWidthmaxHeight,这将减少工作量。

ImagePicker().pickImage(
    source: ImageSource.gallery,
    maxWidth: 1080,
    maxHeight: 1080,
)

用户体验

如果工作需要一些时间,那么现在是使用加载指示器 (f.ex. CircularProgressIndicator) 以显示应用程序正在运行的好时机。有时,使用 AnimatedSwitcher 小部件为变化添加动画可能会很酷。