Compare images and videos side by side — split, slider, peek, highlight, and subtract modes, with synced zoom/pan across up to 4 panes. Everything runs client-side; nothing is uploaded anywhere and is extremely lightweight.
LensDiff can either be used here or you can get the Docker container or download the desktop app that also work offline.
- View modes:
- 2 images/videos loaded: split, slider, peek,
highlight (red/green diffchecker-style overlay), subtract
(
differenceblend with a brightness boost slider) - 3 to 4 images/videos loaded: split-vertical, split-horizontal, grid
- 2 images/videos loaded: split, slider, peek,
highlight (red/green diffchecker-style overlay), subtract
(
- Synced zoom/pan across every visible pane, each independently fit to its own native resolution so mismatched image sizes still line up visually
- Synced video playback: play/pause/scrub/speed together, shorter clips hold their last frame, all clips restart together once every clip has ended
- PSNR + a simplified block-based SSIM shown in a Details overlay.
- Light / dark / system theme, persisted across sessions
- Full keyboard control (see Usage below)
Download the latest installer for your OS from the Releases page:
| OS | File |
|---|---|
| macOS (Apple Silicon) | LensDiff_x.y.z_aarch64.dmg |
| macOS (Intel) | LensDiff_x.y.z_x64.dmg |
| Windows | LensDiff_x.y.z_x64-setup.exe (or .msi) |
| Linux | LensDiff_x.y.z_amd64.AppImage (or .deb) |
macOS note: builds aren't code-signed with an Apple Developer ID yet (see Code signing status), so on first launch macOS may say the app "is damaged and can't be opened." This is Gatekeeper reacting to an unsigned/unnotarized app, not an actually corrupted download. Fix it once, in Terminal:
xattr -cr /Applications/LensDiff.appThen launch it normally. You only need to do this once per install.
Once installed, the app checks for updates automatically on launch.
brew tap EternalBlack/lensdiff
brew install --cask lensdiffPrebuilt images are published to GHCR on every release:
docker run -p 8080:80 ghcr.io/eternalblack/lensdiff:latestOr with Docker Compose, pulling the published image instead of building locally:
# docker-compose.yml
services:
lensdiff:
image: ghcr.io/eternalblack/lensdiff:latest
ports:
- "8080:80"
restart: unless-stoppeddocker compose up -dThen open http://localhost:8080.
To build the image yourself instead of pulling it, see Build with Docker below.
Right-click any pane to replace, add, or remove it. Drag and drop files directly onto a pane to load them.
| Key | Action |
|---|---|
1–5 |
Switch mode (5 pair-mode icons, or 3 layout-mode icons) |
s |
Swap the two panes |
f |
Fit to view |
a |
Actual size (100%, relative to the first pane's native resolution) |
r |
Reload / clear |
Space |
Play / pause (when a video is loaded) |
Requires Node.js 20+ and pnpm 9+.
pnpm install
pnpm dev # http://localhost:3000docker compose up --build
# -> http://localhost:8080or directly:
docker build -f docker/Dockerfile -t lensdiff .
docker run -p 8080:80 lensdiffThe final image is just nginx + static files — no Node runtime, no build tooling — typically well under 30MB.
Additional requirements: Rust and the Tauri prerequisites for your OS.
pnpm tauri:dev # dev build, opens a native window
pnpm tauri:build # release build for your current OSIf pnpm tauri:build fails with something like:
failed to bundle project: error running bundle_dmg.sh: `failed to run .../bundle_dmg.sh`
First check whether the .dmg was actually produced anyway — look in
src-tauri/target/release/bundle/dmg/ (or the per-target path if you
passed --target). This error is frequently cosmetic: the DMG itself
gets created, but the icon-layout step (which scripts Finder via
AppleScript to position icons inside the DMG window) fails separately.
The most common real cause is that Finder hasn't been granted Automation permission for whatever app runs your shell (Terminal, iTerm, VS Code, etc.). To fix:
- System Settings → Privacy & Security → Automation
- Find your terminal app in the list and enable the Finder checkbox
under it. If your terminal app isn't listed yet, run this once to
trigger the permission prompt, then approve it:
osascript -e 'tell application "Finder" to activate' - Re-run
pnpm tauri:build
If it's still stuck, reset Automation permissions entirely and let macOS re-prompt from scratch:
tccutil reset AppleEventsBuilds are currently unsigned (no Apple Developer ID, no Windows code signing certificate). This means:
- macOS shows the "damaged" Gatekeeper message described above until
fixed with
xattr -cr - Windows SmartScreen will likely show an "unrecognized app" warning
To remove these warnings for real, you'd add Apple notarization to
release.yml's build step:
- name: Build and release
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}This requires an active Apple Developer Program membership ($99/yr),which I dont have, to generate the certificate and an app-specific password for notarization. Windows code signing similarly requires a paid code-signing certificate from a CA. Neither is set up here since both cost money and this project hasn't needed it yet — worth revisiting once there's real distribution.
Why Tauri?
Because compared to Electron this is super lightweight.Why at all?
Because I need something to compare images and I like hosting/owning stuff myself.- Subtract/highlight modes are image-only; real-time per-frame diffing on
video is feasible (draw both frames into an offscreen canvas each
requestAnimationFrame) but deliberately deferred for CPU-cost reasons. - No GIF/MP4 export (a "social share" clip like some diff tools offer) —
discussed as a good future feature via
MediaRecordercapturing the stage canvas, not built yet. - More than 4 panes isn't supported — the grid/pairwise-mode logic doesn't
have an obvious generalization past 4 without a rethink (e.g. a generic
ceil(sqrt(n))auto-grid). - Code signing/notarization not set up yet — see Code signing status above.
This project is licensed under the MIT License.
