<tauri><rust><GUI>基于rust和tauri的图片显示程序(本地图片的加载、显示、保存)

254 阅读5分钟

前言

本文是基于rust和tauri,由于tauri是前、后端结合的GUI框架,既可以直接生成包含前端代码的文件,也可以在已有的前端项目上集成tauri框架,将前端页面化为桌面GUI。

环境配置

系统:windows 10 平台:visual studio code 语言:rust、javascript 库:tauri2.0

概述

本文基于tauri框架,创建一个图片显示器程序。要实现的功能是,如何从本地路径加载、显示、保存图片。

前提准备

由于本文涉及到文件选择,但我们不使用html的input元素,而是使用tauri的插件,所以我们需要添加一个文件对话框的库:

npm run tauri add dialog

dialog的用法如下(tauri官方手册):

屏幕截图 2025-02-10 132309.png

比如我们在本文想要实现文件选择,那么就需要打开一个文件对话框,其官方代码如下:

open对话框示例代码
import { open } from '@tauri-apps/plugin-dialog';

// Open a dialog
const file = await open({
  multiple: false,
  directory: false,
});
console.log(file);
// Prints file path and name to the console

事实上dialog插件可以在javascript和rust中使用,但本文只选择在将javascript中使用,不涉及rust中的使用,详细可以参考tauri官网: v2.tauri.app/zh-cn/plugi…

1、创建前端项目

此处不再赘述,可以参看我的前一篇博文: juejin.cn/post/746867…

本文将在之前的项目的基础上进行修改。我们将修改之前的计算器布局,添加一个图片选择与显示的前端页面,可以命名为imgshow.html。 imgshow.html

<div class="imgshow">
    <div id="btnzone">
        <button id="openfilebtn">加载图片</button>
        <button id="savefilebtn">保存图片</button>
    </div>
    <p id="srcfilepathp">源图片路径:</p> 
    <div id="img1div" class="img1div">         
        <img  id="srcimgshow" alt="src">       
    </div>   
</div>

将上面的文件放入public文件夹,以便调用。然后我们需要修改原来的main.js文件:

async function loadImage(path,divid) {
    const response = await fetch(path);
    const template = await response.text();
    document.querySelector(divid).innerHTML = template;
};
loadImage('./imgshow.html','#app');

可以单独运行页面,看一下效果:

屏幕截图 2025-02-11 151139.png

上图是初始运行效果,未加载任何图片。

2、图片显示

现在,我们希望点击加载图片按钮时,会弹出一个文件选择对话框,以便于我们可以选择本地文件夹内的图片,当我们选择完成,获取本地图片路径,同时将图片显示在img元素内。 我们在前文已经说过,将使用dialog这个插件。

import { open,message,save } from '@tauri-apps/plugin-dialog'

如上,open是打开文件对话框,message是消息对话框,save是保存文件对话框。 我们先使用open:


    //加载图片按钮
    openfilebtn.addEventListener('click',async ()=>{
        const file=await open({
          multiple:false,
          directory:false,
        });
        if (file){
          srcfilepathp.innerHTML = "源图片路径:";
          srcfilepathp.innerHTML += file;
          const url=convertFileSrc(file);
          srcurl=url;
          img1.src=url;
        } else {
          await message('没有选择源文件', { title: 'Tauri', kind: 'info' });
        }
        
        });

如上,我们使用open对话框获取文件路径,但是此处获取的是本地图片的绝对路径,是无法直接在前端打开的,因为浏览器是不能直接显示本地路径文件的。所以我们需要对路径进行转换,我们可以使用tauri提供的convertFileSrc这个函数:

import { invoke,convertFileSrc } from '@tauri-apps/api/core'

这个函数与invoke一样,属于tauri的核心api,可以查看convertFileSrc的源码解释,它的作用就是将文件路径转为web中可以使用的格式。但是仅仅转换格式还不够,出于安全考虑,如果你想要真正在web中显示本地图片,还需要进行设置,打开tauri.conf.json文件:

  "security": {
      "csp": "default-src 'self' ipc: http://ipc.localhost; img-src 'self' asset: http://asset.localhost",
      "assetProtocol": {
        "enable": true,
        "scope": []
      }
    }

如上,找到security这个设置项,修改csp的值,此处的

"default-src 'self' ipc: http://ipc.localhost; img-src 'self' asset: http://asset.localhost" 是官方推荐的设置,另外还要添加assetProtocol,将enable设置为true。否则运行时,那怕路径是正确的,也无法正确显示图片,会报错。 看一下演示:

在这里插入图片描述

3、保存图片

事实上,对于本地文件的操作,tauri提供了一个fs插件库,但是这个库因为安全问题,对于路径的使用有限制:

屏幕截图 2025-02-11 152853.png

也就是说fs库在对文件进行操作时,需要在特定的路径下进行,如果要自定义,是无法直接实现的,关于这个问题,事实上是有争论的,在官方的github issue中有讨论,感兴趣的可以去看看: github.com/tauri-apps/… fs这个库提供了readFilewriteFile这样的读写文件的函数,但是他们都要基于一个基本目录,而这个基本目录有哪些呢:

declare enum BaseDirectory {
    Audio = 1,
    Cache = 2,
    Config = 3,
    Data = 4,
    LocalData = 5,
    Document = 6,
    Download = 7,
    Picture = 8,
    Public = 9,
    Video = 10,
    Resource = 11,
    Temp = 12,
    AppConfig = 13,
    AppData = 14,
    AppLocalData = 15,
    AppCache = 16,
    AppLog = 17,
    Desktop = 18,
    Executable = 19,
    Font = 20,
    Home = 21,
    Runtime = 22,
    Template = 23
}

如上,举个例子,如果你选择基本路径为Public,那么你能访问的路径就是(windows系统):C:\Users\Public下的文件。而且还需要启用权限,打开src-tauri/capabilities/default.json文件(这是初始化tauri时自动生成的),配置permissions,添加fs的权限:

{
      "identifier":"fs:allow-app-read-recursive",
      "allow":[{"path":"$PUBLIC/*"}]
    },

上面权限的意思是,允许对设置的文件夹进行完整的读取访问。详细的权限可以参看tauri的官方手册:

屏幕截图 2025-02-11 155935.png tauri.app/zh-cn/plugi…

此处只是简单说明,但我们并不希望使用这种方式,因为它限制了文件路径,无法自定义任意路径。所以我们需要换一种方式,使用rust来读取本地文件,然后把读取的文件内容再传递给前端调用,同样,也可以将前端获取的数据,传递给rust来保存到本地文件。 所以,我们修改rust文件lib.rs:

#[tauri::command]
fn read_file(path:std::path::PathBuf) ->Vec<u8> {
    std::fs::read(path).unwrap()
}

#[tauri::command]
fn write_file(path:std::path::PathBuf,data:Vec<u8>) {
    std::fs::write(path, data).unwrap()
}

如上,我们添加了两个函数,通过使用rust的标准文件读写库fs来对文件进行操作,以读取文件为例,通过传入文件路径,使用std::fs::read读取文件内容,然后返回。然后通过invoke在前端调用,同样地,如果我们要保存文件,如图片,也是一样的逻辑。

  const response=await fetch(srcurl);
        const blob=await response.blob();
        const buffer=await blob.arrayBuffer();
        const imgdata=new Uint8Array(buffer);
        await invoke('write_file',{path:file,data:imgdata})
            .then(
              await message('保存成功', { title: 'Tauri', kind: 'info' })
            )

如上,我们在javascript中通过图片的url获取图片的原始数据,也就是字节数组,然后我们通过invoke将文件路径和字节数组传给rust的函数,rust端使用std::fs::write写入文件。