Skip to content

Repository files navigation

Cropalot 📸✂️

Browser-only multi-photo crop & straighten tool for digitizing family photo sheets

Cropalot takes a scan or photo of an album page holding several photographs and helps you pull each picture out as its own image file. Everything happens inside your browser — no account, no upload, no server.

Try it →


🔒 The privacy guarantee, and why you don't have to take our word for it

Most tools tell you they don't upload your photos. Cropalot arranges for your browser to make it impossible.

index.html ships this Content-Security-Policy:

connect-src 'none'

Your browser reads that line before a single line of our JavaScript executes, and then refuses every outbound request this page can attempt — fetch, XMLHttpRequest, WebSocket, EventSource, sendBeacon. There is no code we could write, or that anyone could later add, that transmits your photos while that line is present.

So the audit is one line long. View source, read the <meta> tag, done. You don't have to read the other four thousand.

One thing worth knowing about the honest limits of this:

  • It is not a proof about the server. Code running in a page can't prove its own integrity — anything that could tamper with the app could tamper with a self-check the app displays. That's why Cropalot shows you its commit SHA and instructions to rebuild it yourself, and does not show you a green "verified" badge it drew for itself. (An earlier version did exactly that, hashing copies of its own source that were embedded in the same bundle it was checking. It has been removed; it proved nothing.)

Verify it yourself

# What this page served you
curl -s https://techluddite.github.io/Cropalot/assets/*.js | sha256sum

# Build the same commit and compare
git clone https://github.com/TechLuddite/Cropalot && cd Cropalot
git checkout <commit shown in the app's "This Build" tab>
npm ci && npm run build && sha256sum dist/assets/*.js

Or just open DevTools → Network and crop a sheet. Nothing goes out. If anything ever tried, the Console would show a CSP violation rather than quietly allowing it.

Better still: load the page once, turn on airplane mode, and reload. It still opens, because the app precaches itself and never needed the network to begin with.


✨ What it does today

  • Multi-photo detection with real deskew — estimates the page background, groups the regions that differ from it, and fits each one's minimum-area enclosing rectangle, so a photo lying at an angle is detected at that angle rather than boxed upright.
  • True perspective correction — solves the homography mapping your quadrilateral to a rectangle and inverse-maps every output pixel through it, with bilinear sampling and supersampling on downscale.
  • Detection sensitivity — a slider for low-contrast pages, and a per-photo readout of the tilt found and how completely the photo fills its own box.
  • Manual 4-corner adjustment — drag any corner, with a 3× magnifier for precision. Corners are re-ordered on release, so a corner dragged past its neighbour can't silently produce a broken crop.
  • Non-destructive by construction — the crop is stored once, untouched, and every preview and export is rendered from it on demand. Filters are saved as settings, not baked into pixels, so any edit is reversible forever.
  • Enhancement presets — auto-fix, vintage restore, B&W, sepia, vivid, plus brightness / contrast / saturation / warmth / sharpen / edge-trim sliders, with live preview and a reset.
  • Capture dates written as EXIF — tag an album page with the year it's from and exported JPEGs carry DateTimeOriginal, so 300 scanned photos file themselves under 1974 in your photo app instead of piling up under today.
  • Export as JPEG, PNG or WebP at a quality you choose, either as a ZIP or — on Chromium browsers — written straight into a folder you pick, one file per photo.
  • A library that actually persists — photos live in IndexedDB as Blobs, so the quota is a share of free disk rather than the ~5 MB an origin gets in localStorage.
  • Installable, and genuinely offline — the build precaches itself, so you can cut your connection, reload, and keep working. Install it and it registers as a handler for image files, so "Open with Cropalot" appears in your OS.
  • Off the main thread — detection and rectification run in a Web Worker on an OffscreenCanvas, with the sheet transferred rather than copied. The UI keeps painting at full rate while a 16-megapixel scan is processed.
  • Camera capture — grab a page with a phone or webcam instead of a scanner.
  • Sample sheets that double as a benchmark — three generated album pages, each carrying the exact corners of the photos drawn onto it. Detection runs on them for real and the editor shows the resulting mean IoU against those known corners, so accuracy regressions are visible rather than hidden. Currently 0.93–0.99 IoU, all photos found.

⚠️ Limitations worth knowing before you rely on it

Being straight with you about where this currently falls short:

  • JPEG, PNG and WebP only. Browsers can't decode TIFF or HEIC in an <img>; convert those first. Cropalot now tells you instead of doing nothing.
  • Low-contrast pages need the sensitivity slider. Detection compares each pixel to the estimated page colour, so white-bordered prints on cream album paper sit close to the threshold. The default (7) handles the cases we test; if a light photo comes back split into pieces, raise it. A local-contrast signal would remove the need for the slider, and isn't built yet.
  • Duplicate detection is a hint, not a verdict. It's tuned to catch rescans without flagging two photos from the same roll. Photos of the same scene in similar light can still trip it — it marks them for your attention, and never deletes anything.

🛠️ Tech stack

  • React 19 + TypeScript, built with Vite 6
  • Tailwind CSS v4, Lucide icons
  • Canvas 2D / OffscreenCanvas in a Web Worker for all image analysis and rendering — plain JavaScript, no WASM, no native deps
  • Service worker precaching a build-time-generated asset manifest (no Workbox)
  • Convex hull, rotating-calipers minimum-area rectangle, Sutherland–Hodgman clipping and an 8×8 homography solve, all hand-rolled in src/utils/geometry.ts
  • IndexedDB for the photo library (Blobs, not base64), File System Access API for folder export where available
  • JSZip + FileSaver.js for the ZIP fallback

No backend, no API keys, no analytics, no cookies, no telemetry, no fonts or scripts from a CDN. The dependency list above is the whole of it.


🚀 Getting started

git clone https://github.com/TechLuddite/Cropalot
cd Cropalot
npm install
npm run dev      # http://localhost:3000
Script Purpose
npm run dev Vite dev server on port 3000
npm run build Production build into dist/
npm run preview Serve the production build locally
npm run lint TypeScript type check (tsc --noEmit)
npm run icons Regenerate the PWA icons in public/ (needs npm i -D playwright-core first; the icons are committed, so this is only for changing the mark)

The dev server relaxes connect-src so hot-reload's WebSocket works — see the devCspRelax plugin in vite.config.ts. It is scoped to apply: 'serve' and never runs during a build, so production ships the policy exactly as written in index.html.


📂 Project structure

Cropalot/
├── index.html                      # App shell + the Content-Security-Policy
├── vite.config.ts                  # Build config, dev CSP relaxation, SW generation, commit SHA
├── public/                         # Manifest + PWA icons (regenerate with `npm run icons`)
├── scripts/make-icons.mjs          # Icon generator
└── src/
    ├── main.tsx                    # Entry point
    ├── App.tsx                     # Top-level state & tab routing
    ├── types.ts                    # Shared TypeScript types
    ├── workers/cv.worker.ts        # Detection + rectification, off the main thread
    ├── utils/
    │   ├── batch.ts                # Shared per-sheet pipeline + unattended album run
    │   ├── perceptualHash.ts       # dHash + colour signature for duplicate detection
    │   ├── geometry.ts             # Hull, min-area rect, polygon clipping, homography solver
    │   ├── cvEngine.ts             # Detection pipeline + homography warp
    │   ├── cvClient.ts             # Worker dispatch with inline fallback
    │   ├── canvasCompat.ts         # Canvas helpers usable on either thread
    │   ├── offline.ts              # Service worker registration & readiness
    │   ├── imageProcessing.ts      # Rendering, filters, ZIP & folder export
    │   ├── photoStore.ts           # IndexedDB Blob library
    │   ├── exif.ts                 # EXIF writer for capture dates
    │   └── sampleSheets.ts         # Generated demo album pages
    └── components/
        ├── Navbar.tsx              # Top & bottom navigation
        ├── BatchRunner.tsx         # Unattended album run with per-page results
        ├── SheetUploader.tsx       # Drop zone, sample picker, upload errors
        ├── DetectionEditor.tsx     # Interactive corner editor + magnifier
        ├── GalleryView.tsx         # Extracted photo library & batch export
        ├── PhotoEnhancerModal.tsx  # Per-photo filter studio
        ├── CameraModal.tsx         # Webcam / phone capture
        ├── SettingsModal.tsx       # Preferences
        ├── OfflinePrivacyModal.tsx # Privacy architecture & self-verification
        ├── BuildProvenance.tsx     # Commit SHA + how to verify this build
        ├── SupportModal.tsx        # Credits & donation
        └── AndroidFrame.tsx        # Phone-preview chrome

📖 For contributors

  • docs/ARCHITECTURE.md — how the pipeline fits together, and the invariants that must not be broken (with what goes wrong if they are).
  • docs/LESSONS.md — Cropalot began as a Google AI Studio one-shot. This is the postmortem on what that draft got right, what it claimed but didn't implement, and the recurring failure shapes worth checking for in any generated codebase.
  • CLAUDE.md — the short version, for AI coding sessions.

The one-line summary: npm run lint and npm run build pass on almost every bug this project has ever had. Verify changes by driving the built app in a browser, with a real large scan — not with the small flat-coloured samples.


💖 Support & acknowledgments

Cropalot is free software — no ads, no subscriptions, no paywalls.

👉 Support development via PayPal

Special thanks to Halo MSP — helping businesses with safe and sensible AI and software implementation. For general business IT, see our parent company Tech 2U.


📜 License

MIT — see LICENSE.

About

Hosted online at cropalot.ai.studio, no app install needed, but also 100% local image processing. Scan or capture entire album pages containing multiple photos. Cropalot automatically detects each picture, corrects perspective angles, and extracts high-res individual photos.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages