100% on-device. Zero uploads. WebGPU + ONNX, or CPU Lanczos. Your images never leave the browser.
PrivaScale is a mobile-first PWA that upscales images entirely in the browser. No server, no tracking, no cloud. It starts immediately with the bundled AI model — no setup screens.
- Open the app — it auto-starts: WebGPU → bundled Real-ESRGAN, no WebGPU → CPU-only mode (Lanczos, AI options disabled).
- Add a photo (drop, paste, or Choose photo — on mobile the OS offers gallery, camera or files).
- Pick scale 2× / 4×, tap Upscale.
- Compare before/after with the slider, or switch to zoom mode (pinch, double-tap, +/−, mouse wheel) to inspect details up to 8×.
- Download (PNG / JPG / WEBP). Back (↩) returns to the pre-upscale state to retry with other settings — no re-upload needed.
Optional: Settings → Face restore rebuilds faces (skin/detail) after upscale via YuNet detection + GPEN enhancement (~37 MB, downloaded once on first use).
Optional: Settings → 4x-UltraSharp for photos (~32 MB, downloaded once on selection).
- Fully local — pixels never leave the device
- Zero-setup start — no model gate; bundled AI on WebGPU, CPU fallback otherwise
- Models: Real-ESRGAN x4v3 (shipped), 4x-UltraSharp (optional download), Lanczos 3 (CPU, no neural net)
- Face restore (opt-in): YuNet face detection + GPEN-BFR enhancement blended into the upscaled image
- Tiled inference for large images
- Mobile-first UI — single-row dock, compare slider, zoom mode (pinch / double-tap), settings sheet
- Mobile hardening — screen wake lock during processing, 16 MP output cap on phones, adaptive tiling (256 px desktop / 128 px mobile)
- Privacy wipe — remove photo vs clear everything (including cached models)
- Preferences persist — model, scale, format and face-restore choices are saved on-device (localStorage, no image data) and restored on next launch
- PWA — installable; works offline after models are on disk
App code is MIT. Weights are not. See src/models/LICENSE.md.
| Model | In git? | Size | Best for | License |
|---|---|---|---|---|
| Real-ESRGAN x4v3 | Yes | ~5 MB | General photos (default) | BSD 3-Clause — redistributable |
| 4x-UltraSharp | No | ~32 MB | Photos, texture | CC BY-NC-SA 4.0 — non-commercial, not redistributed |
| YuNet 640 (face detect) | No | ~0.3 MB | Face restore pass | Via Viso Master collection — non-commercial |
| GPEN-BFR-256 fp16 (face enhance) | No | ~36 MB | Face restore pass | Research / non-commercial, not redistributed |
| Lanczos 3 | Built-in | — | CPU-only upsample | App MIT |
Non-bundled weights are gitignored. Get them locally:
npm run fetch:models # UltraSharp + YuNet + GPEN (~73 MB) + Real-ESRGANOr let the app download them on demand (model selection / face-restore toggle). Files land in src/models/ and the browser cache — after that, everything works offline.
For a commercial product, use Real-ESRGAN + Lanczos only, or replace the NC models with weights you have rights to.
Browser: Chrome / Edge 113+ (WebGPU). Without WebGPU the app runs CPU-only (Lanczos 3).
Tooling: Node.js 18+ or Python 3.8+. Serve over HTTP, not file://.
git clone https://github.com/orfeomorello/privascale.git
cd privascale
npm install # also copies ONNX Runtime into src/vendor/ort
npm run fetch:models # optional: UltraSharp + face models (~73 MB)
npm test # unit tests (node:test)
npx serve src # → http://localhost:3000Other servers:
python -m http.server --directory src 8000Privacy check in DevTools: await PrivacyAudit.verifyNoDataLeak().
The app is fully static — src/ is the site root and all paths are relative, so it works under any domain or subpath.
npm run package # → privascale-static.zip (Pages-safe)
npm run package -- --include-models # also bundle ALL *.onnx (~140 MB, fully offline)Cloudflare Pages (pages.dev) — recommended:
npx wrangler pages deploy src --project-name privascaleor connect the git repo in the Cloudflare dashboard (build command: none, output directory: src).
| File | Size | Handling |
|---|---|---|
4x-UltraSharp.onnx, GPEN-BFR-256.fp16.onnx |
32–38 MB | Excluded from the zip by default; downloaded from Hugging Face on first use and cached |
ort-wasm-simd-threaded.jsep.wasm (+ asyncify) |
~26–28 MB | Needed for WebGPU inference — workarounds below |
Workarounds for the ORT wasm files on Pages:
- R2 object storage (keeps WebGPU): upload the two
.wasmfiles to an R2 bucket with a custom domain, then point the runtime at it — e.g.ort.env.wasm.wasmPaths = 'https://cdn.example.com/ort/'inmodel-loader.js/face-restore.js(R2 serves arbitrary sizes, enable CORS). - Another static host without per-file limits (Netlify, Vercel, GitHub Pages, any VPS) — deploy the zip as-is with
--include-modelsfor a fully offline bundle. - CPU-only on Pages: not recommended — it would require forcing the plain ORT build and dropping WebGPU.
Desktop: drop / paste (Ctrl+V) / Choose photo → Upscale → drag the compare handle, or switch to zoom (mouse wheel, drag to pan, +/− buttons).
Mobile: Choose photo (gallery, camera or files) → Upscale → drag to compare, pinch or double-tap to zoom. Download on iOS may open a tab; use Share → Save Image.
After upscale: Download the result, ↩ Back to change model/scale and upscale again without re-uploading, 🗑️ to remove the photo.
Settings (top right): model (Real-ESRGAN included / UltraSharp download / Lanczos CPU), scale, face-restore toggle, model storage (update / clear everything).
- CSP:
default-src 'self'; Hugging Face is allowed only to fetch optional non-commercial models - Images stay in memory / optional Cache Storage; never uploaded
- Preferences (model, scale, format, face restore) are stored in a single localStorage key — no image data, declared in
PrivacyAudit - Secure wipe: overwrite + drop references; Clear everything also drops cached models
src/
├── index.html
├── css/styles.css
├── js/ # pipeline, UI, ONNX loader, Lanczos CPU, face restore, settings store
├── vendor/ort/ # ONNX Runtime (npm run vendor:ort)
├── models/
│ ├── model-config.json
│ ├── LICENSE.md
│ ├── realesr-general-x4v3.onnx # in git (BSD)
│ ├── 4x-UltraSharp.onnx # gitignored — fetch or auto-download
│ ├── yunet_n_640_640.onnx # gitignored — face detection
│ └── GPEN-BFR-256.fp16.onnx # gitignored — face enhancement
├── assets/icons/ # PWA icons (generated)
└── service-worker.js
scripts/ # fetch-models, vendor-ort, package (zip for static hosting)
test/ # unit tests (`npm test`)
.github/ # repo social preview image
| Issue | Fix |
|---|---|
Blank page on file:// |
Serve over HTTP (npx serve src) |
| Stuck in CPU mode | Use Chrome/Edge with WebGPU; AI models require it |
| 4x-UltraSharp missing | Select it in Settings — it downloads automatically |
| Face restore finds no faces | Works on visible, front-facing faces; tiny/background faces are skipped |
| Face restore slow | Runs on WebGPU if available, else WASM — prefer desktop or smaller images |
| Image too large | Try 2× or a smaller crop |
| iOS download | Long-press → Share → Save Image |
| HEIC | iPhone camera: Most Compatible (JPEG), or convert first |
| Port in use | npx serve src -l 3001 |
Restore optional weights anytime: npm run fetch:models.
- Code: MIT — LICENSE
- Real-ESRGAN weights: BSD 3-Clause (bundled)
- UltraSharp: CC BY-NC-SA 4.0 — not in this repository; downloaded by you
- YuNet / GPEN: research / non-commercial — not in this repository; downloaded by you
- Real-ESRGAN — Tencent ARC
- Kim2091 — UltraSharp
- GPEN — Yang et al. (face restoration)
- OpenCV YuNet — face detection
- harisreedhar/Face-Upscalers-ONNX — ONNX conversions
- ONNX Runtime Web
- OpenModelDB
PrivaScale — upscale locally, stay private.
