【flutter尝试ffi】 基于frb开发属于自己的shine_image组件(启航篇)

310 阅读3分钟

基于rust与flutter开发的图像处理雏形

之前在写shine_http的时候就考虑过要写一个做图片处理的包。

原因在于image在一个app是最常用到的资源,大量的图片,不光会消耗庞大的内存,也会给dart很大的压力。

所以决定用rust做个图形的插件包。

本篇由于刚开始,只暂时实现analyze图片,resize图片。目前所有处理的图片都以webp格式的编码返回,不过也可能考虑改为全面支持avif。

图像处理库的简单需求

上传方向

graph TD
APP --> 上传需求 --> 获取本地图片or输入url --> 尺寸处理,滤镜风格化选择,统一的压缩格式 -->返回带有原图信息的图片和处理过的图片信息类 --> 上传

使用方向

graph TD
获取图片 --> 解析图片 --> 缓存缩略图片 --> 显示压缩图 --> 点击显示原图 --> 切换原图,缓存原图,移除缩略图缓存 --> 回收资源  

Frist - 解析图片

解析图片我采取的思路是通过rust下载图片并解析为dart的uint8list的类型,通过frb的零拷贝方式返回给dart后,通过image.memory来显示,原先我还根据图片类型进行了判断,不同格式的图片进行decoder。

不过尝试了gif,png,jpeg,webp等多种主流格式后,发现并不需要对应的decoder,所以就直接解析返回了,代码也就非常简单了。

#[tokio::main(flavor = "current_thread")]
pub async fn analyze_image(url:String) -> Result<ZeroCopyBuffer<Vec<u8>>> {
    let res = reqwest::get(url)
        .await
        .expect("图片下载失败");

    let bytes = res.bytes().await.expect("转换字节码错误").to_vec();

    Ok(ZeroCopyBuffer(bytes))
}

如果后续测试有发现解析字节流的方式不可行,那么根据判断更新一下对应的解码代码应该就可以了。

Second - 上传图片

上传的目前简单写了一些图片格式的逻辑,其他格式其实处理也不难,这里不写只是因为做个最小型验证。

#[tokio::main(flavor = "current_thread")]
pub async fn compression_image(pixel: Vec<u8>,width:u32,height:u32) -> Result<ZeroCopyBuffer<Vec<u8>>>{
    if let Ok(o) = guess_format(&pixel) {
        match o {
            ImageFormat::Jpeg | ImageFormat::Pnm | ImageFormat::Tiff | ImageFormat::Tga | ImageFormat::Dds | ImageFormat::Bmp | ImageFormat::Ico | ImageFormat::Hdr | ImageFormat::OpenExr | ImageFormat::Farbfeld => {
                let converted = image::load_from_memory(&pixel).expect("图片加载内存失败").resize(width, height,FilterType::CatmullRom);
                default_write_image(converted).await
            },
            ImageFormat::Gif => {
                let gifdecoder = GifDecoder::new(pixel.as_slice())?;
                let decoder_frames = gifdecoder.into_frames();
                let frames = decoder_frames.collect_frames()?;
                animation_write_image(frames,(width,height)).await
            }
            ImageFormat::WebP => {
                Err(anyhow!("WebP逻辑待编写"))
            }
            ImageFormat::Png => {
                Err(anyhow!("png逻辑待编写"))
            }
            ImageFormat::Avif => {
                Err(anyhow!("avif逻辑待编写"))
            }
            _ => Err(anyhow!("我是未被支持的格式"))
        }
    } else {
        Err(anyhow!("无法识别图片格式"))
    }
}

主逻辑在这,具体的事物处理逻辑被分为了default_write_imageanimation_write_image,前者是普通的单图处理,后者是多帧的动图处理。

由于webp,png,avif是同时具有单图与动图的格式,所以可以单独匹配处理。

Third - 结尾

既然简单的编写完了,那么我们肯定得赶紧试试,逻辑是否是通的。

  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        const Text("You're running on"),
        FutureBuilder<List<dynamic>>(
          future: Future.wait([imageU8]),
          builder: (context, snap) {
            final data = snap.data;
            if (data == null) return const CircularProgressIndicator();
            return Container(
              child: Column(
                children: [
                  Text("rust处理"),
                  Image.memory(
                    data[0],
                    width: 100,
                    height: 100,
                  ),
                ],
              ),
            );
          },
        ),
        Text("dart处理"),
        Image.network(
          "https://isparta.github.io/compare-webp/image/gif_webp/webp/2.webp",
          width: 100,
          height: 100,
        )
      ],
    ),
  ),

ok,let's go!

E558379F6700252D377A -original-original.gif

嗯,看着没毛病。

compression_image测试也没问题,但是忘了录屏了,有点懒得重写了。

由于这个功能还得基于image.memory封装成plugin,目前这样使用肯定不够实用的,所以就摆烂到下一个篇章再展示吧!

QQ图片20221128070717.jpg

嗯嗯,我感觉到宇宙中有只闪耀宝可梦诞生了,我得去蹲闪光拿下!!!就这样,bye了个bye~