TauriとReactで高速写真セレクトツールを作った話
舞台写真家が数千枚の写真を数分でレビューするためのアプリ「Glimpse」の開発記録
10,000+
枚の画像に対応
RAW
NEF, ARW, CR2, CR3...
無料
オープンソース (MIT)
課題
舞台写真家として、1回の公演で数千枚の写真を撮影することがあります。公演後、締め切りに追われながら、これらすべての画像をレビューしてベストショットを見つける作業は大変です。
Adobe BridgeやLightroomなどの既存ツールは強力ですが、以下の問題がありました:
- 1. 読み込みが遅い - 数千枚のサムネイル生成を待つのは苦痛
- 2. 機能が多すぎる - セレクト段階では編集機能は不要
- 3. マウス依存 - キーボード中心の操作の方が高速
私が必要としていたのは、写真の高速レビューと選別に特化したツールでした。そこでGlimpseを作りました。
なぜTauri + React + Rustなのか?
Electron、Flutter、ネイティブオプションを評価した結果、以下の理由でTauri 2.0を選択しました:
Rustバックエンド
画像処理にはRustのパフォーマンスが不可欠。Pure-RustのRAWデコーダー(rawloader + imagepipe)でNEF、ARW、CR2、CR3などを外部依存なしで処理。
Reactフロントエンド
Reactのコンポーネントモデルとエコシステム(特に@tanstack/react-virtual)で複雑なUIを効率的に構築。
小さなバンドルサイズ
Electronと違い、Tauriはシステムのネイティブwebviewを使用。最終的なアプリは15MB未満(Electronアプリは150MB以上)。
クロスプラットフォーム
単一コードベースでmacOS(ARM64 + x64)とWindowsをビルド。GitHub Actionsで自動リリース。
技術的な課題と解決策
1. RAWファイルのサポート
Webブラウザはネイティブでは RAWファイルをデコードできません。RustバックエンドでPure-Rustライブラリを使用してJPEGサムネイルとプレビューをオンザフライで生成しています:
// rawloader + imagepipe でサポートするRAW形式
NEF (Nikon), ARW (Sony), CR2/CR3 (Canon),
RAF (Fujifilm), ORF (Olympus), RW2 (Panasonic),
PEF (Pentax), DNG (Adobe), SRW (Samsung)
処理パイプライン:RAWセンサーデータをデコード、デモザイク処理を適用、sRGB色空間に変換、JPEGにエンコード。すべてrayonを使って並列処理。
2. 10,000枚以上の画像のための仮想スクロール
数千のDOM要素をレンダリングするとパフォーマンスが著しく低下します。代わりに@tanstack/react-virtualを使用して、表示されているアイテムのみをレンダリング:
仕組み:
- 1. スクロール位置に基づいて表示される行を計算
- 2. その行のみをレンダリング(通常20〜30アイテム)
- 3. CSSトランスフォームでアイテムを正しく配置
- 4. スクロール時にDOMノードを再利用
結果:10,000枚以上の画像が読み込まれていても滑らかな60fpsスクロールを実現。
3. サムネイルキャッシュシステム
サムネイルの生成は、特にRAWファイルの場合コストが高いです。GlimpseはSQLiteを使用してキャッシュされたサムネイルを追跡:
初回読み込み
サムネイルを並列生成、アプリデータディレクトリに保存、メタデータをSQLiteに記録
2回目以降
ファイルハッシュをキャッシュと照合、既存のサムネイルを即座に提供
4. キーボードファーストのナビゲーション
写真セレクトではスピードがすべて。Glimpseはキーボードファーストのワークフロー用に設計されています:
| キー | アクション |
|---|---|
| 矢印キー | 画像間を移動 |
| 1-5 | レーティング(星の数)を設定 |
| X | リジェクト(不採用)としてマーク |
| Enter | 詳細ビューを開く |
| C | 選択した画像を比較 |
| Esc | 戻る |
パフォーマンス最適化
CSS GridとResizeObserver
リサイズのたびにレイアウトを再計算する代わりに、ResizeObserverでコンテナサイズの変更を効率的に検出し、必要な場合のみグリッドの列数を更新。
並列サムネイル生成(Rayon)
Rustのrayonクレートで並列イテレーションを実現。サムネイル生成は利用可能なすべてのCPUコアに自動スケール。
遅延プレビュー読み込み
高解像度プレビュー(2000x2000)は、初回フォルダスキャン時ではなく、詳細ビューに入った時にのみ生成。
非同期IPC(Tauri)
すべてのRustコマンドはTauriのIPCを介して非同期で実行され、重い処理中もUIの応答性を維持。
アーキテクチャ概要
フロントエンド (React + TypeScript)
|
|-- ThumbnailGrid.tsx (仮想スクロールグリッド)
|-- DetailView.tsx (フルスクリーンビューア)
|-- CompareView.tsx (サイドバイサイド比較)
|
v Tauri IPC (invoke)
|
バックエンド (Rust)
|
|-- commands.rs (Tauriコマンドハンドラ)
|-- image_processor.rs (RAWデコード、サムネイル生成)
|-- database.rs (SQLite: sessions, labels, cache)
|
v
SQLite データベース
|-- sessions (フォルダ追跡)
|-- labels (レーティング、リジェクト)
|-- cache (サムネイルメタデータ) Glimpseを試す
Glimpseは無料でオープンソースです。ダウンロードして、あなたの写真で試してみてください。フィードバックをお待ちしています!