Flutter&Rust#01系列文章:
- 《Flutter&Rust#01 | 突破能力瓶颈》
- 《Flutter&Rust#02 | 图片灰度 - 性能提升!》
- 《Flutter&Rust#03 | 图片格式转换 jpeg/webp 》
- 《Flutter&Rust#04 | 图片编解码》
上一篇我们介绍了如何在 Flutter 中集成 Rust 代码进行开发。本篇将通过一个具体的案例体验一下 Rust 所带来的巨大收益。如下所示:
读取一个图片转化为灰度图片并存储到当前目录
1. 交互效果
rust 代码将使用 image 库进行处理,另外 dart 版的 image 库也可以做到该功能,所以可以对比两者执行该功能的耗时,来对比 Rust 给 Flutter 开发带来的真实收益。这个测试案例的交互非常简单:
用户可以
选择
一张设备中的图片;
上下两个按钮分别通过dart
和rust
代码处理灰度化图片的逻辑。
右上角展示操作消耗的时间。
选择文件过后,用户可以通过x
来取消图片选择,再选择新图片。
- Rust 方法处理 1 MB 左右的图片耗时,约
35~45ms
- Dart 方法处理 1 MB 左右的图片耗时,约
740~800ms
特别是对于处理能力不足的移动端,我试了一张 12 MB 的图片, rust 约 1.1 秒完成,而 dart 需要 23 秒。性能的提升体验,在感知上还是非常明显的。所以图片处理这类复杂计算的工作,交给 Rust 是一个明智之举。
注: 上面是在 release 模式下的对比结果。不同设备、不同时刻性能表现可能有所差异。
请勿在 Debug 模式对比、讨论任何性能问题!
请勿在 Debug 模式对比、讨论任何性能问题!
请勿在 Debug 模式对比、讨论任何性能问题!
另,由于 Dart 最后需要将图像压缩为 PNG , Rust 存储时直接存为 PNG
消耗时长对比,会受到参数的影响。不同的图片也会有所差异,所以具体的倍数很难定量确定。
即便 Dart 代码中压缩比设为最低,Rust 也比 Dart 快 8 倍左右。
2. dart 逻辑处理
如下所示,处理流程如下所示:
- 读取 src 图片文件,通过 img.decodeImage 解码图片
- 遍历图片的像素点,获取rgb值,计算灰度值。
- 将grayImage 对应的像素点rgb设为灰度值
- 解码出 grayImage 的字节数组,写入到 dist 文件中。
import 'dart:io';
import 'dart:typed_data';
import 'package:image/image.dart' as img;
void luma({required String src, required String dist}) {
File imageFile = File(src);
img.Image image = img.decodeImage(imageFile.readAsBytesSync())!;
img.Image grayImage = img.Image(width: image.width, height: image.height, numChannels: 1);
for (int y = 0; y < image.height; y++) {
for (int x = 0; x < image.width; x++) {
img.Pixel pixel = image.getPixel(x, y);
num r = pixel.r;
num g = pixel.g;
num b = pixel.b;
int luma = (0.299 * r + 0.587 * g + 0.114 * b).round();
grayImage.setPixel(x, y, img.ColorInt8.rgba(luma, luma, luma, 1));
}
}
File(dist).writeAsBytesSync(img.encodePng(grayImage));
}
3. rust 逻辑处理
如下所示,处理流程如下所示,其中算法和 dart 的保持一致:
- 读取 src 图片文件,通过 image::open 解码图片
- 遍历图片的像素点,获取rgb值,计算灰度值。
- 将 gray_image 对应的像素点rgb设为灰度值
- 将 gray_image 写入到 dist 文件中。
---->[rust/src/api/image_handler.rs]----
use image::{DynamicImage, GenericImageView, ImageBuffer, Rgba};
#[flutter_rust_bridge::frb(sync)]
pub fn luma_image(src: &str, dist: &str) {
let img = image::open(src).expect("Failed to open image");
let (width, height) = img.dimensions();
let mut gray_image = ImageBuffer::new(width, height);
for (x, y, pixel) in img.pixels() {
let Rgba([r, g, b, _]) = pixel;
let luma = (0.299 * r as f32 + 0.587 * g as f32 + 0.114 * b as f32) as u8;
gray_image.put_pixel(x, y, image::Luma([luma]));
}
gray_image.save(dist).expect("Failed to save image");
}
通过 flutter_rust_bridge_codegen generate 可以自动生成 Dart 和 Rust 的桥接代码:
4.界面构建
界面构建中,核心在于处理选择图片的框和处理文件选择。通过 file_picker 可以在全平台中选择文件:
界面中主要有三个需要状态变化的数据。_path
表示选择的图片,_dist
表示生成的图片,_cost
表示生成的时间。如下所示,_onTapSelect
方法触发选择文件,选中后为 _path
赋值并重新构建:
String? _path;
String? _cost;
String? _dist;
void _onTapSelect() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.image);
if (result != null && result.files.isNotEmpty) {
String? path = result.files.first.path;
if (path != null) {
setState(() {
_path = path;
});
}
}
}
三个状态决定如下三个区域的视图展示,在交互过程中,只需要在不同的时机,改变状态数据即可:
如下所示,点击橙色按钮,执行 rust 的 lumaImage
方法,为 _cost
和 _dist
赋值,并重新构建。Dart 的处理也是类似:
String dist = p.join(File(src).parent.path,basename+"_rust.png");
File(dist).deleteSync();
int start = DateTime.now().microsecondsSinceEpoch;
lumaImage(src: src, dist: dist);
int end = DateTime.now().microsecondsSinceEpoch;
_cost = '$info: ${(end - start) / 1000}ms');
_dist = dist;
setState(() { });
通过这个小例子,可以切实的体会到 Rust 在图片操作中的优越性。Rust 接入 Flutter ,将会为我的 匠心千刃 带来更高的上限。后期会基于 匠心千刃 的打造过程,对比和分享更多 Flutter 和 Rust 的知识,敬请期待 ~