Skip to content

EternalBlack/LensDiff

Repository files navigation

LensDiff

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.

Slider mode comparing two images

Features

  • View modes:
    • 2 images/videos loaded: split, slider, peek, highlight (red/green diffchecker-style overlay), subtract (difference blend with a brightness boost slider)
    • 3 to 4 images/videos loaded: split-vertical, split-horizontal, grid
  • 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)

Install

Desktop app (macOS / Windows / Linux)

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.app

Then launch it normally. You only need to do this once per install.

Once installed, the app checks for updates automatically on launch.

Homebrew (macOS)

brew tap EternalBlack/lensdiff
brew install --cask lensdiff

Docker

Prebuilt images are published to GHCR on every release:

docker run -p 8080:80 ghcr.io/eternalblack/lensdiff:latest

Or 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-stopped
docker compose up -d

Then open http://localhost:8080.

To build the image yourself instead of pulling it, see Build with Docker below.

Usage

Right-click any pane to replace, add, or remove it. Drag and drop files directly onto a pane to load them.

Key Action
15 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)

Development

Requires Node.js 20+ and pnpm 9+.

pnpm install
pnpm dev          # http://localhost:3000

Build with Docker

docker compose up --build
# -> http://localhost:8080

or directly:

docker build -f docker/Dockerfile -t lensdiff .
docker run -p 8080:80 lensdiff

The final image is just nginx + static files — no Node runtime, no build tooling — typically well under 30MB.

Build the desktop app locally (Tauri)

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 OS

Troubleshooting: bundle_dmg.sh fails on macOS

If 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:

  1. System Settings → Privacy & Security → Automation
  2. 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'
  3. Re-run pnpm tauri:build

If it's still stuck, reset Automation permissions entirely and let macOS re-prompt from scratch:

tccutil reset AppleEvents

Code signing status

Builds 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.

FAQ:

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.

Current scope / next steps

  • 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 MediaRecorder capturing 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.

License

This project is licensed under the MIT License.

About

Lightweight image/video comparison app that runs 100% locally. Available via https://eternalblack.github.io/LensDiff/ as Docker image or native app using Tauri.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages