[Tauri #06] Tauri v2:Honeyview置き換えの画像ビューア「Lookover」を作り、Windows / Mac / Linuxで公開した
はじめに
長年HoneyViewを使ってきました。Windowsの「フォト」は使いやすいけれど、ちょっと足りない。その「足りない」を埋めるアプリを、Tauri v2で作りました。名前は Lookover(ルックオーバー)です。
- 普段は見るだけ(フォルダ内の送り、拡大縮小、等倍、全画面)
- 必要なときだけその場で直す(回転・切り抜き・縮小・明るさ・モザイクなど)
- 直したらワンキーで書き出す(Ctrl+1 で長辺1920のWebPを同じフォルダへ)
画像をNOTEやブログに上げる前に、回転や縮小やモザイクのために別のアプリを開くのが面倒だった、というのが出発点です。着手は昨日で、今日、Windows / Mac / Linux のインストーラーを公開するところまで来ました。
スクリーンショット
リポジトリ(MIT):
GitHub - fixtan/lookover: Windows image viewer with quick edit & one-key export
見るのが主、直すのも一瞬、書き出しはワンキー。Tauri v2 + 素のJavaScript製の画像ビューア。
github.com前回の記事:
[Tauri #05] Tauri v2:多機能化したWeb版時計をデスクトップへ移植 // PROTOCOL.LAIN
Web版の時計をTauri v2のデスクトップ版Lain Clock v0.2.0へ移植。設定ウィンドウ分離・ネイティブメニュー・トレイ常駐・3OSリリースの記録。
lain-lab.com1. 構成:Rustは薄く、画面はバンドラーなしの素のJS
今回もフロントは素のJavaScriptです。ビルドツールも外部ライブラリも使わず、src/ をそのまま frontendDist に指定しています。
"build": { "frontendDist": "../src" }
Rust側がやっているのは、OSにしかできないことだけです。
- ファイルを読む・書く、フォルダの列挙
- ごみ箱へ送る(
trashクレート) - 2つ目の起動を1つ目の窓へ渡す(
tauri-plugin-single-instance) - ファイルダイアログ(
tauri-plugin-dialog)、エクスプローラーで場所を開く(tauri-plugin-opener) - 画像の拡張子の関連付け(
bundle.fileAssociations)
画像のデコード、編集、書き出しは、すべて画面側(Canvas / WebGL2)でやっています。
テストも画面側にあります。開発用の小さなHTTPサーバー(Rustのコマンドを真似るだけ)を立てて、Playwrightでブラウザから叩く構成です。Rustを起動せずに、編集機能を170項目以上で回せます。
2. 編集は「元に触らない」パイプライン
編集は、元の画像を書き換えず、パラメーターの列として持っています。
元の画像 → 向き → 描き込み(モザイク等) → 切り抜き → 大きさ → 色
書き出すときと画面に出すときで同じ順に通すので、見たままの結果が出ます。取り消しとやり直しは、この列を戻すだけです。
色の調整(明るさ・コントラスト・彩度・色温度・ハイライト・シャドウ・シャープ)は、WebGL2のシェーダーでやります。WebGL2が使えない環境のために、同じ計算をするCPU版も用意してあります。
3. キーは「表」から全部作る
ビューアは、キーボードで触る時間が長いアプリです。しかも、自分のホットキーとぶつかる場面が出てきました。
動作確認で、Ctrl+1 が自分の常駐ツールのホットキーと衝突していたのです。そこで、キーを自由に付け替えられるようにしました。
操作の表を1か所に置く
export const ACTION_TABLE = [
{ id: 'next', group: '見る', label: '次の画像', keys: ['ArrowRight', 'ArrowDown', 'PageDown', 'Space'] },
{ id: 'rotR', group: '直す', label: '右へ 90 度', keys: ['R'] },
{ id: 'export1', group: '出す', label: 'ワンキー書き出し 1 番', keys: ['Ctrl+1', 'Ctrl+E'] },
// …
];
キーを押したときの処理、F1の操作一覧、吹き出し、キーの設定画面は、全部この表から作っています。キーを変えれば、一覧もステータスバーの表示も一緒に変わります。変えたところだけを settings.keys に覚えるので、あとで初期割り当てを見直しても、触っていないところは自然に追いつきます。
英語配列と日本語配列の罠
キーを文字列にするときに、英字と数字は event.code(キーの位置)で、記号は event.key(出る文字)で決めています。
if (/^Key[A-Z]$/.test(c)) return c.slice(3); // KeyR → R
if (/^Digit[0-9]$/.test(c)) return c.slice(5); // Digit1 → 1
return e.key; // 記号は出る文字
記号は Shift を付けない決まりにしました。日本語配列では「+」が Shift+「;」ですが、英語配列では Shift+「=」です。Shift を含めてしまうと、配列によって別のキーになってしまうからです。
4. WebKitではWebPが書き出せない
ここで一番ハマったのが、書き出しです。
ワンキー書き出しの初期設定は、長辺1920のWebPです。WindowsのWebView2では問題なく動きました。ところが、macOSのWKWebViewとLinuxのWebKitGTKでは、次のコードがPNGを返します。
canvas.toBlob(cb, 'image/webp'); // WebKit系では、できない形式を黙ってPNGにする
エラーも出ません。拡張子だけが .webp で中身がPNG、という最悪のファイルができあがります。
解決策:出てきた型を見て、できなければJPEGへ
let webpOk = null;
export async function canEncodeWebp() {
if (webpOk !== null) return webpOk;
const c = document.createElement('canvas');
c.width = c.height = 2;
const blob = await new Promise((r) => c.toBlob(r, 'image/webp'));
return (webpOk = blob && blob.type === 'image/webp');
}
export async function usableFormat(format) {
if (format === 'webp' && !(await canEncodeWebp())) return { format: 'jpeg', fellBack: true };
return { format, fellBack: false };
}
小さなcanvasで一度だけ試して、返ってきたBlobの type を見ます。WebPにできない環境では、拡張子ごとJPEGに切り替えて、画面にも「この環境は WebP に書き出せないので、JPEG にした」と出すようにしました。テストでは、toBlob を上書きしてWebKitの挙動を真似ています。
5. 作ったあとに踏んだ小石
フォルダを移動すると、Rustのビルドが落ちる
アプリ名を仮名から変えたとき、フォルダも移動したら、cargo のビルドスクリプトが古い絶対パスを見に行って落ちました。src-tauri/target に絶対パスがキャッシュされているためで、target を消せば直ります。
Linuxで作ったロックファイルを持ち込まない
Linux上で作った package-lock.json をそのままWindowsに持っていったら、@tauri-apps/cli-win32-x64-msvc(Windows用のネイティブバインディング)が見つからないと言われました。ロックファイルはOS依存のパッケージを含むので、node_modules とロックファイルを消して npm install し直しです。
PowerShellでは npm run tauri build -- --bundles … が効かない
-- がnpmのラッパーに食われて、--bundles が渡りませんでした。npx tauri build --bundles nsis と、直接呼ぶと通ります。
MSIは諦めて、NSISだけにした
WindowsのMSI(WiX)が、手元でもGitHub Actionsでも light.exe で落ちました。VBScriptが無効という定番の原因を疑いましたが、入っていました。原因は追い切れていません。管理者権限は関係ありませんでした。
npx tauri build --bundles nsis
NSISのセットアップ(Lookover_0.1.1_x64-setup.exe、1.45MB)は問題なく作れて、インストールも起動も動きました。個人用のアプリなので、MSIは諦めてNSISだけにしました。
6. GitHub Actionsで3OSをまとめて作る
[Tauri #03] の構成をベースに、今回はジョブを4つに分けました。
strategy:
fail-fast: false # 1 つ落ちても、ほかは最後まで作る
matrix:
include:
- name: windows
os: windows-latest
args: '--bundles nsis'
- name: mac-apple-silicon
os: macos-latest
args: '--target aarch64-apple-darwin'
rust_targets: aarch64-apple-darwin
- name: mac-intel
os: macos-latest
args: '--target x86_64-apple-darwin'
rust_targets: x86_64-apple-darwin
- name: linux
os: ubuntu-22.04
args: '--bundles deb,appimage'
Macは、Apple SiliconとIntelを別のジョブで、--target を変えて作ります。タグ(v0.1.1 など)をpushすると動いて、Releasesに下書きで成果物が並びます。
結果は、Windows 5分、Mac 3分、Linux 6分で、全体は7分半でした。
| ファイル | サイズ |
|---|---|
Windows x64-setup.exe | 1.45 MB |
macOS aarch64.dmg / x64.dmg | 1.95 MB / 2.09 MB |
Linux .deb | 2.59 MB |
Linux .AppImage | 77.5 MB |
AppImageだけ大きいのは、WebKitGTKを抱え込んでいるからです。WebView2やWKWebViewを借りるWindowsやMacは、2MB前後で済みます。
タグを打ち直すと、下書きに古い成果物が混ざる
今回、v0.1.1 のタグを2回打ち直しました。
- 修正をコミットしたつもりが、
git commitに-mを付け忘れて中止されていた。それなのにタグだけpushしたので、タグが古いコミット(古いワークフロー)を指していた。 - 直して通ったが、バージョン番号(
tauri.conf.jsonなど3か所)を上げ忘れていて、成果物が0.1.0の名前になった。
タグの付け直しは、こう書きます。
git tag -d v0.1.1
git push origin :refs/tags/v0.1.1
git tag v0.1.1
git push origin v0.1.1
ただ、同じタグ名で2回走ると、下書きのリリースに前の実行のファイルが残ります。今回は 0.1.0 と 0.1.1 の名前が混ざっていたので、編集画面から古い5つを消してから公開しました。
さらに笑い話をひとつ。CIを待つ間に、MacとLinuxの手元でもビルドを始めました。Linuxの機械にはRustを入れるところから。ところがCIの方が先に全部緑になって、手元のビルドは完全に無駄でした。タグを打つだけで済むのが、Actionsの良いところです。
7. 署名していないので、最初に警告が出る
3つとも署名をしていないため、初回は警告が出ます。
- Windows:SmartScreenが「不明な発行元」と出す。「詳細情報」→「実行」。
- macOS:Gatekeeperが止める。右クリック →「開く」。効かないときは、システム設定 →「プライバシーとセキュリティ」→「このまま開く」。それでもだめなら、
xattr -cr /Applications/Lookover.app。(Sequoia以降の挙動は [Tauri #05] にも書きました) - Linux:AppImageは
chmod +xしてから実行。
動作確認は、実機で次の3つでやりました。
- Windows:NSISでインストールして起動
- macOS(Intel):dmgから入れて起動
- Linux:AppImageを直接起動して、画像を開く
Apple Silicon(aarch64)のMacは、手元に実機がなく未確認です。CIでは作っていますが、動くかは分かりません。リリースのノートにも、そう書きました。
まとめ:見るのが主で、直すのは一瞬
HoneyViewの置き換えを狙って、「見る」「直す」「出す」を1つにまとめました。
- 編集は元に触らない。パラメーターの列で持つ
- キーは表から全部作る。配列の違いは
event.codeで吸収する - WebKitは
toBlob('image/webp')で黙ってPNGを返す。型を見てJPEGに逃がす - タグを打ち直すと、下書きに古い成果物が混ざる
「誰も使わないかもしれないが、自分専用のアプリを作る時代」と割り切って、完璧を待たずに公開しました。問題があれば直して出し直せばいい。しばらくはHoneyViewと併用しながら、困ったところを直していきます。
Releases · fixtan/lookover
Windows (NSIS) / macOS (Intel・Apple Silicon) / Linux (deb・AppImage) のインストーラー。署名なし。
github.com