Desktop photo arranger for print shops and photo studios. Load customer photos, tile them at exact physical sizes on a sheet, and print front and back so the two sides line up for cutting.
Built with Electron. Runs offline.
- Tiles photos at true physical sizes: passport 35x45, visa 51x51, CNIC 85.6x54, or any custom mm size
- Paper presets for 4x6, 5x7, A5 and A4, plus custom sizes
- Double-sided registration - the grid is centred so opposite margins are equal, and the back sheet mirrors cell placement to match the flip direction. Tiles land on top of each other when the paper is turned over
- Several customers on one sheet, each photo with its own copy count and adjustments
- Fit or fill-and-crop framing, zoom, rotation, brightness, contrast, saturation, skin smoothing
- Cut guides: light border, corner crop marks, or none
- DPI warning when a photo is too small for the size it is being printed at
- AI background removal with solid colour replacement
- Save the arranged sheet as a JPG
| Desktop (Electron) | Web (web/) |
|
|---|---|---|
| Printing | One click, forced to exactly 100% scale | Exports a PDF you print at Actual size |
| Install | Windows installer | Just a link, installable as a PWA |
| Offline | Yes | Yes, after first load |
| Licensing | Hardware-ID locked | None |
Both share the same layout engine and both keep photos on the user's machine.
npm install
cp secret.example.js secret.js # then set your own salt
npx electron .The app serves its UI from public/ on a free port bound to 127.0.0.1.
npx electron-builder --win # auto-detect architecture
npx electron-builder --win --x64 # 64-bit
npx electron-builder --win --ia32 # 32-bit, for older shop PCs
npx electron-builder --win --dir # unpacked, for quick testingInstallers land in dist/. Put a 256x256 icon.ico in build/ and electron-builder uses it
automatically. The .exe is unsigned, so Windows shows a SmartScreen warning on first install.
Each install is locked to the machine's hardware ID, with a 3-day trial before activation.
Keys are SHA-256(hwid + salt) truncated to 16 characters.
node keygen.js "CUSTOMER_HWID_HERE"The salt lives in secret.js, which is not tracked in git. Copy secret.example.js and set
your own value. keygen.js is excluded from builds so customers never receive it.
This is a deterrent, not real protection - anyone who unpacks app.asar can read the salt.
Server-side validation is the only way to make it solid.
Print and Ctrl+P both go through Electron's print API at exactly 100% scale with no page
margins. Do not replace this with window.print(): fit-to-page scaling shifts the sheet
toward one corner, and the two sides of a double-sided job stop lining up.
Double-sided workflow:
- Print with the side set to Front
- Put the printed sheet back in the tray
- Switch to Back and print again
- Set the paper flip (long edge / short edge) to match how the sheet turns over
- Only use the nudge if the printer itself drifts - it is saved between sessions
- Background removal downloads its model on first use and needs internet for that one click. Everything else works offline.
- HEIC photos from iPhones are not supported. Convert to JPG first.
Everything in web/ is static - no build step, no server, no backend.
cd web && python3 -m http.server 8000Deploy by pointing GitHub Pages, Vercel or Netlify at the web/ folder.
Output goes through a canvas renderer in millimetre space and into a PDF built with pdf-lib, so page dimensions are exact - a 210 x 297 mm request produces a page measuring 210.000 x 297.000 mm. Both sides come out as a 2-page PDF: print page 1, flip the sheet, print page 2. Registration between the two pages was verified at 0.02% pixel mismatch, which is JPEG noise.
The browser gives no control over print scale, which is why the web version hands you a PDF
instead of calling window.print(). Print it at Actual size (100%) with margins set to None.