Flutter&Rust#03 | 图片格式转换 jpeg/webp 前两篇介绍了如何 Rust 在Flutter 中的
Flutter&Rust#01系列文章:
前两篇介绍了如何 Rust 在Flutter 中的基本使用方式。接下来,我们将基于 Rust 实现一个非常实用的功能:图片格式转换器。该功能将作为 匠心千刃 的一部分。
有时想要进行图片格式转化比较麻烦,绝大多数转换器都需要登录、收费,更别说批量转换这种高级需求了,实用的移动端转化软件更是凤毛麟角。 匠心千刃作为一个 Flutter 构建的全平台的工具应用,在未来发布之后,图片格式转换的功能将供所有人 免费使用,毕竟这玩意没有任何的服务成本。
由于图片的格式非常多,不同格式的图片特点,会分篇进行介绍。本文主要介绍输出是 jpg/jpeg
和 webp
两种格式的转换。
1. 了解 Jpeg 和 webp 格式的图片
我们知道,图片是由二维像素点构成的色彩空间。每个像素点承载的色彩数据将通过二进制进行编码,持久化到文件中。色彩数据本身比较大,一般图片格式会在编码的过程中进行压缩存储,从而便于存储和传输
有损压缩和无损压缩
- 无损压缩会保留 全部的色彩信息,图片体积就会相对较大。如 png、bmp;
- 有损压缩 丢失一部分色彩信息,在可接受的程度下,减少图片体积大小。如 jpg/webp;
jpeg 和 jpg 格式
两者在本质上没有任何区别,全称是 Joint Photographic Experts Group。由于历史原因,早期的windows 操作系统支持三位后缀名,所以简写为 jpg
.现代操作系统和软件完全支持 .jpg
和 .jpeg
两种扩展名,二者可以互换使用,完全没有区别。
jpeg 的压缩算法是有损的,可以通过编码时的 质量
来控制损耗程度。
另外,jpeg 图片没有 Alpha 通道,也就是说 不支持透明度。
webp 格式
webp 支持无损和有损压缩,在编码的过程中可以根据需要选择压缩模式。并且支持透明度(Alpha通道),与PNG 类似。文件体积较小,图像质量和压缩比之间平衡良好。
在 AndroidStudio 中有将 png 转化为 webp 的工具,如下搜索,在 75%
的质量下,可以将 3.4MB 的 png 图片转化为 113KB 大小的 webp 文件。仅占原图的 3% 。
2. 转换应用交互
如下所示,在 多媒体处理/图片处理/格式转换
菜单中进行交互操作。面板中:
- 头部是可编辑的表单,用于选择或输入转换的配置参数。比如输出格式、可损压缩的编码质量、输出目录等。
- 中间是会展示选择和转换图片及其基本信息,通过右上角的关闭按钮可以移除选择。
- 点击转换按钮处理转换任务,底部栏展示提示信息。
面板的界面布局整体呈上中下结构,通过 Column 组件竖直排列。其中格式下拉选择、文件选择输入、按钮等,使用 tolyui 的相关组件快速实现展示效果:
1.45 MB 的 png 图片通过 75% 的编码质量,可以将体积减少到原图的 10%。通过 rust 只需 70 ms 左右即可完成,效率还是非常可以的可以的。试了一下 Dart 通过 image 类库实现相同的转换,需要 360 ms 左右。
3. Rust 核心算法
Flutter 作为前端的交互界面,具体的转换功能交由 Rust 处理。如下所示,定义 convert_to_jpg
方法,传入输入和输出文件地址,以及编码时的质量数值。通过image 库的 JpegEncoder::new_with_quality 创建指定质量的 Jpeg 编码器即可:
use std::fs::File;
use std::io::BufWriter;
use image::codecs::jpeg::JpegEncoder;
use image::ImageReader;
pub fn convert_to_jpg(input_path: &str, output_path: &str, quality: u8)-> Result<(), image::ImageError>{
let img = ImageReader::open(input_path)?.decode()?;
let output_file = File::create(output_path)?;
let writer = BufWriter::new(output_file);
let mut encoder = JpegEncoder::new_with_quality(writer, quality);
encoder.encode_image(&img)?;
Ok(())
}
转换为 webp 时, lossless 可以指定是否启用无损压缩;quality 指定有损压缩的质量,也是 1~100。 当前版本的 image 好像不支持 webp 的有损压缩,这里额外通过 webp 库进行处理。
[dependencies]
image = "0.25.2"
webp = "0.3.0"
pub fn convert_to_webp(input_path: &str, output_path: &str, quality: f32, lossless: bool) -> Result<(), image::ImageError> {
let img = ImageReader::open(input_path)?.decode()?;
let encoder = Encoder::from_image(&img).expect("Failed to decode image");
let webp_data: WebPMemory;
if lossless {
webp_data = encoder.encode_lossless();
} else {
webp_data = encoder.encode(quality);
}
fs::write(output_path, webp_data.as_bytes()).expect("Failed to write file");
Ok(())
}
Rust 方面的逻辑处理完毕后,就可以通过工具生成桥接代码。
flutter_rust_bridge_codegen generate
4. webp 的压缩
webp 支持无损压缩,所以在头部的操作表单中加入一个 ChekedBox
,选中时表示需要无损压缩。此时编码质量的输入框将会消失。如下所示,75% 质量输出的 webp 只有 41.38 KB, 仅为原尺寸的 2%
对比 AndroidStudio 中的 webp 转换器可以看出,Rust 的算法效力是和它完全一致的。
对于一些需要打包入应用的图片文件,如果是过大的 png,可以考虑使用 webp 进行压缩,可以减少应用体积。网络图片也可以转换成 webp 提高加载的速度。现在掌握了 Rust 转换 webp 的技术手段,就可以打造更实用和好用的工具,比如支持批量图片转换。这将在 匠心千刃 的后续开发中逐步支持。
本文主要介绍了基于 rust 将图片转换为 jpeg/webp 格式的方式,后面将继续介绍其他图片格式的特点,以及优化匠心千刃的交互。随着项目的推进,将遇到很多 rust 和 dart 桥接的知识点,比如如何 dart 处理 rust 端的异常, rust 的代码如何异步处理,或返回 Stream 流。如何进行更复杂类型的参数传递等。敬请期待 ~
转载自:https://juejin.cn/post/7415672130190376960