Building a High-Speed Photo Culling Tool with Tauri and React
How I built Glimpse to help stage photographers review thousands of photos in minutes
10,000+
Images Supported
RAW
NEF, ARW, CR2, CR3...
Free
Open Source (MIT)
The Problem
As a stage photographer, I often shoot thousands of photos during a single performance. After each show, I face the daunting task of reviewing all these images to find the best shots - usually under tight deadlines.
Existing tools like Adobe Bridge or Lightroom are powerful, but they're:
- 1. Slow to load - Waiting for thousands of thumbnails to generate is painful
- 2. Feature-heavy - I don't need editing tools during the culling phase
- 3. Mouse-dependent - Keyboard-first workflow is much faster for selection
I needed a tool focused on one thing: quickly reviewing and selecting photos. So I built Glimpse.
Why Tauri + React + Rust?
After evaluating Electron, Flutter, and native options, I chose Tauri 2.0 for several reasons:
Rust Backend
Rust's performance is critical for image processing. Pure-Rust RAW decoders (rawloader + imagepipe) handle NEF, ARW, CR2, CR3, and more without external dependencies.
React Frontend
React's component model and ecosystem (especially @tanstack/react-virtual) make building complex UIs straightforward.
Small Bundle Size
Unlike Electron, Tauri uses the system's native webview. The final app is under 15MB compared to 150MB+ for Electron apps.
Cross-Platform
Single codebase builds for macOS (ARM64 + x64) and Windows. GitHub Actions handles automated releases.
Key Technical Challenges
1. RAW File Support
Web browsers cannot decode RAW files natively. The Rust backend generates JPEG thumbnails and previews on-the-fly using pure-Rust libraries:
// Supported RAW formats via rawloader + imagepipe
NEF (Nikon), ARW (Sony), CR2/CR3 (Canon),
RAF (Fujifilm), ORF (Olympus), RW2 (Panasonic),
PEF (Pentax), DNG (Adobe), SRW (Samsung)
The processing pipeline: decode RAW sensor data, apply demosaicing, convert to sRGB colorspace, then encode as JPEG. All done in parallel using rayon.
2. Virtual Scrolling for 10,000+ Images
Rendering thousands of DOM elements would destroy performance. Instead, I use @tanstack/react-virtual to only render visible items:
How it works:
- 1. Calculate which rows are visible based on scroll position
- 2. Render only those rows (typically 20-30 items)
- 3. Use CSS transforms to position items correctly
- 4. Recycle DOM nodes as user scrolls
Result: smooth 60fps scrolling even with 10,000+ images loaded.
3. Thumbnail Caching System
Generating thumbnails is expensive, especially for RAW files. Glimpse uses SQLite to track cached thumbnails:
First Load
Generate thumbnails in parallel, store in app data directory, record metadata in SQLite
Subsequent Loads
Check file hash against cache, serve existing thumbnails instantly
4. Keyboard-First Navigation
Speed is everything when culling photos. Glimpse is designed for keyboard-first workflow:
| Key | Action |
|---|---|
| Arrow keys | Navigate between images |
| 1-5 | Set rating (star level) |
| X | Mark as rejected |
| Enter | Open detail view |
| C | Compare selected images |
| Esc | Go back |
Performance Optimizations
CSS Grid with ResizeObserver
Instead of recalculating layout on every resize, I use ResizeObserver to efficiently detect container size changes and update the grid column count only when necessary.
Parallel Thumbnail Generation (Rayon)
Rust's rayon crate enables parallel iteration. Thumbnail generation automatically scales to all available CPU cores.
Lazy Preview Loading
High-resolution previews (2000x2000) are only generated when entering detail view, not during initial folder scan.
Async IPC with Tauri
All Rust commands run asynchronously via Tauri's IPC, keeping the UI responsive during heavy processing.
Architecture Overview
Frontend (React + TypeScript)
|
|-- ThumbnailGrid.tsx (Virtual scrolling grid)
|-- DetailView.tsx (Full-screen viewer)
|-- CompareView.tsx (Side-by-side comparison)
|
v Tauri IPC (invoke)
|
Backend (Rust)
|
|-- commands.rs (Tauri command handlers)
|-- image_processor.rs (RAW decode, thumbnail gen)
|-- database.rs (SQLite: sessions, labels, cache)
|
v
SQLite Database
|-- sessions (folder tracking)
|-- labels (ratings, rejections)
|-- cache (thumbnail metadata) Try Glimpse
Glimpse is free and open source. Download it, try it with your photos, and let me know what you think!
Tech Stack Summary
Tauri 2.0
Build smaller, faster, more secure desktop apps with web frontend and Rust backend.
TanStack Virtual
Headless UI for virtualizing large lists. Essential for handling thousands of images.
rawloader
Pure-Rust RAW image decoder supporting major camera manufacturers.
imagepipe
RAW image processing pipeline: demosaicing, color conversion, and more.