如何以高效的方式在颤动中操纵图像像素?
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
回调,您可以考虑 Slider
的 onChangeEnd
回调,所以你只有在放开滑块拇指时才执行 compute
。
更改最大图像分辨率
请注意,如果您不需要图像的原始分辨率 - 那么在 ImagePicker 插件中您可以选择设置 maxWidth
和 maxHeight
,这将减少工作量。
ImagePicker().pickImage(
source: ImageSource.gallery,
maxWidth: 1080,
maxHeight: 1080,
)
用户体验
如果工作需要一些时间,那么现在是使用加载指示器 (f.ex. CircularProgressIndicator
) 以显示应用程序正在运行的好时机。有时,使用 AnimatedSwitcher
小部件为变化添加动画可能会很酷。
我正在尝试开发一个在 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
回调,您可以考虑 Slider
的 onChangeEnd
回调,所以你只有在放开滑块拇指时才执行 compute
。
更改最大图像分辨率
请注意,如果您不需要图像的原始分辨率 - 那么在 ImagePicker 插件中您可以选择设置 maxWidth
和 maxHeight
,这将减少工作量。
ImagePicker().pickImage(
source: ImageSource.gallery,
maxWidth: 1080,
maxHeight: 1080,
)
用户体验
如果工作需要一些时间,那么现在是使用加载指示器 (f.ex. CircularProgressIndicator
) 以显示应用程序正在运行的好时机。有时,使用 AnimatedSwitcher
小部件为变化添加动画可能会很酷。