「TAURI」で「画像ビューア」のサンプルアプリを作ろう

バックエンドをJavaScriptから呼び出す
「viewer」→「src」→「main.js」のJavaScriptのコードで、下記のサンプルコードのようにバックエンドの「Rust」を呼び出してみます。Rustのコードを書かないと、ビルドして実行しても図5のようにファイルを開いたり保存したりするダイアログが出るだけで何も起きません。画像ファイルの拡張子は「png」「jpg」ファイルだけで十分かもしれません。
「open」関数と「save」関数でパス名を取得したときの「invoke」関数でRustにメッセージを送って返事を受け取りますが、「async」と「await」で非同期にメッセージをやり取りしないとデータの受け渡しが上手くいきません。
「open」関数でパス名を取得したときの「invoke」関数で受け取る戻り値は「タプル」です。JavaScriptの「Array」も同じ型の変数が複数並んだ「配列」ですが、「Array」は他にも何番目にくる型が異なることもあるタプルにもなります。
・「viewer」→「src」→「main.js」のサンプルコード(前略)
//画像ファイルを開く
function openImage() {
open({
filters: [
{ name: 'PNG', extensions: ['png'] },
{ name: 'JPG', extensions: ['jpg'] },
{ name: 'Bitmap', extensions: ['bmp'] },
{ name: 'Targa', extensions: ['tga'] },
{ name: 'TIFF', extensions: ['tiff'] },
],
}).then(async file => {
let data = await invoke("open_img", { path: file });
_canvas.width = data[0];
_canvas.height = data[1];
let img = _context.createImageData(_canvas.width, _canvas.height);
for ( i = 0; i < img.data.length; i++ ) {
img.data[i] = data[2][i]
}
_context.putImageData(img,0,0);
});
}
//画像ファイルを保存
function saveImage() {
let w = _canvas.width;
let h = _canvas.height;
let img = _context.getImageData(0, 0, w, h);
let arr = [];
img.data.forEach( (data) => { arr.push(data); });
save({
filters: [
{ name: 'PNG', extensions: ['png'] },
{ name: 'JPG', extensions: ['jpg'] },
{ name: 'Bitmap', extensions: ['bmp'] },
{ name: 'Targa', extensions: ['tga'] },
{ name: 'TIFF', extensions: ['tiff'] },
],
}).then(async file => {
await invoke("save_img", {
path:file,
width:w,
height:h,
data:arr
});
});
}
【サンプルコードの解説】
「openImage」関数でファイルを開くダイアログの「open」関数を呼び出します。フィルターで「png」「jpg」「bmp」「tga」「tiff」拡張子だけ開けるようにします。ファイルが選択されたらバックエンドのRustに向けて"open_img"メッセージを呼び出してパス名を渡し、Rustで画像を読み込んだ生データをmain.jsで受け取り、canvasタグにその画像の幅と高さを指定し表示します。ただし次のRustのコードを書くまでまだ機能しません。
「saveImage」関数でcanvasグラフィックスの幅と高さを取得し、canvasグラフィックスの生データをすべて「arr」配列に追加(push)します。ファイルを保存ダイアログの「save」関数を呼び出します。フィルターで「png」「jpg」「bmp」「tga」「tiff」拡張子だけで保存できるようにします。ファイルが選択されたらバックエンドのRustに向けて"save_img"メッセージを呼び出し、ファイル名と幅と高さとarr配列を渡してRustで画像ファイルに保存します。ただし次のRustのコードを書くまでまだ機能しません。
ここでファイルダイアログの拡張子は「open」関数の「filters」引数で['png','jpg','bmp']などと複数の拡張子を指定することもできます。
画像ファイルの読み込み
Rustのコード「src-tauri」→「src」→「main.rs」ファイルを下記サンプルコードのように書きます。これをデバッグ実行すると、やっと「開く」ボタンで図6のように画像ファイルを開いて表示できるようになりました。
Rustでは、画像の生データは「Vec<u8>」の1バイトの正の整数(u8)の数値の羅列になります。順番にR→G→B→A→R→G→B→A→R→G→B→A…という順に並んでいます。RはRed、GはGreen、BはBlue、AはAlphaで、それぞれ0~255で色を表します。
・画像を読み込む「main.rs」のサンプルコード#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
//画像を扱うクレート
use image::{self, GenericImageView,Rgba};
//画像ファイルを開くコマンド
#[tauri::command]
fn open_img(path: &str) -> (usize,usize,Vec<u8>) {
let img = image::open(path).expect("Dimage");
let width = img.width() as usize;
let height = img.height() as usize;
let img_src = img.into_rgba8();
(width,height,img_src.to_vec())
}
//メイン関数
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
open_img])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
【サンプルコードの解説】
「use」文で「image」クレートにアクセスできるようにします。
「#[tauri::command]」属性で「open_img」関数をTAURIの「コマンド」として宣言します。「コマンド」にすることでフロントエンドのJavaScriptから呼び出してTAURIのプログラムを実行できます。
imageクレートの「open」関数で「path」名の画像ファイルを開きます。「width」変数に画像の幅を、「height」変数に画像の高さを、「img_src」変数にRGBAがそれぞれ1バイトずつの画像の生データを取得し、open_img関数の戻り値でその3つをタプルにして返します。
「main」関数でtauriクレートの「Builder」構造体の「invoke_handler」メソッドで「main.js」から送られてきたメッセージの「open_img」と同名の関数とやり取りし、「run」メソッドでコマンドを走らせます。
画像ファイルの書き出し
最後にRustのコード「src-tauri」→「src」→「main.rs」を下記のようにコードを書きます。これで「保存」ボタンでデスクトップなどに画像を名前を付けて保存できます。
画像をファイルに保存する場合は、画像の生データをimageクレートの「ImageBuffer」構造体のデータに変換(「from_vec」メソッド)して、その「ImageBuffer」構造体の「save」メソッドで画像ファイルに保存します。「save」メソッドは引数に渡される「path」名の拡張子により自動的にその拡張子のファイル形式で保存されます。
・画像を書き出す「main.rs」サンプルコード(前略)
//画像ファイルを保存するコマンド
#[tauri::command]
fn save_img(path: &str,width:u32,height:u32,data:Vec<u8>) {
let img = image::ImageBuffer::<Rgba<u8>, Vec<u8>>::from_vec(
width, height, data).unwrap();
img.save(path).expect("error save");
}
//メイン関数
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
open_img,
save_img])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
【サンプルコードの解説】
「#[tauri::command]」属性で「save_img」関数をTAURIのコマンドとして宣言します。
imageクレートの「ImageBuffer」構造体のfrom_vecメソッドで「width」引数に画像の幅を、「height」引数に画像の高さを、「buf」引数(ここでは「data」変数を渡している)にRGBAがそれぞれ1バイトずつの画像の生データを渡し、生成された「ImageBuffer」構造体のインスタンスを「img」変数に代入します。
img変数の「save」関数で「path」名に画像ファイルを保存します。
「main」関数でtauriクレートの「Builder」構造体の「invoke_handler」メソッドで「main.js」から送られてきたメッセージの「save_img」と同名の関数とやり取りし、「run」メソッドでコマンドを走らせます。
おわりに
今回は、TAURIを使ったサンプルアプリとして画像ビューアを作りました。フロントエンドのJavaScriptでファイルダイアログを表示し、バックエンドのRustで画像ファイルを読み込んだり書き出したりしました。
次回は今回の画像ビューアを改造して、お絵描きアプリを開発していきます。これは単なるお絵描きアプリではなく、丸シールアート風の円だけで絵を描く丸アートアプリです。




