gud.pics
A mobile-first, installable photo toolkit. Source: https://github.com/FelineStateMachine/gudpics Every tool runs locally in the browser; Cloudflare serves static files and photos never leave the device.
Tools:
/: develops a Fujifilm RAF in the browser with LibRaw (WebAssembly) and applies any of 16 film simulation looks via 3D LUTs, then saves a JPEG. The in-camera JPEG can be viewed and saved too. See NOTICE.md for where the LUTs come from and how close they are.fuji /: crop to an aspect ratio (1:1, photo 5:4 4:3 3:2, social 4:5 9:16 1.91:1, each rotatable) or pad out, drag to position, frame in white or black, vignette and grain, then save or share a JPEG at the platform size.social /: error level analysis. Re-saves the picture as JPEG at a chosen quality, amplifies the difference, estimates the source JPEG quality from its quantization table, and saves the map as PNG.ela /: straightens converging verticals and horizontals using darktable's C algorithms (LSD line detection, RANSAC, perspective fit, homography) compiled to WebAssembly.perspective
Run
The checked-in assets/ and assets/ are ready to use.
python3 scripts/package.py
python3 scripts/serve.py 8765
Open http://localhost:8765. The dev server maps extensionless routes such as / to perspective.html, matching Cloudflare's asset handling. Service workers require localhost or HTTPS.
Rebuild
Requires Python 3, Emscripten (emcc, built here with 6.0.9), Node.js for tests, and Pillow only to regenerate icons. No network is needed for extraction.
sh scripts/build.sh
node scripts/test-core.mjs
python3 scripts/package.py
If Emscripten's installed cache is read-only, set EM_CACHE to a writable directory when running the build.
scripts/ documents each source adaptation. Original source at the pinned revision is in native/; retain original notices. See NOTICE.md and LICENSES before redistributing.
Cloudflare deployment
npx wrangler@4 deploy --dry-run
npx wrangler@4 deploy
The config serves only dist/; it has no backend, secrets, storage bindings, analytics, photo upload endpoint, or runtime npm dependencies. Packaging writes scripts, styles and wasm to dist/ under content-hashed names and rewrites references, so hashed files are cached forever while pages are fetched network-first. Re-run packaging after changes. Existing open tabs finish using their cached version; close all app tabs and reopen to activate an update.
Layout
index.html+home.mjs: landing grid of tools, install and about.fuji.html+fuji.mjs+rawworker.mjs+raf.mjs: the Fuji tool.rawworker.mjsruns LibRaw (assets/, built byraw.wasm native/) and the LUTs off the main thread.raw/ build.sh raf.mjsparses the RAF container and the Fuji makernote.scripts/regenerates the neutral LUTs with darktable-cli.neutral-lut.py social.html+social.mjs: the Social tool, canvas only.ela.html+ela.mjs+elaworker.mjs: the ELA tool; re-encoding and differencing run in a worker on an OffscreenCanvas.perspective.html+app.mjs: the perspective tool UI.worker.mjsruns the WebAssembly core off the main thread.style.css: shared styles. Dark, system font, safe-area aware; the tool page never scrolls.
Perspective tool
Photo import; original orientation via browser decoding; LSD structure detection; RANSAC outlier rejection; vertical, horizontal, or both-axis fit (including shear for both); color-coded accepted/rejected line overlays and counts; rotation and correction strength; crop modes; grid; before/after (tap the compare button or press and hold the photo); JPG/PNG export; install manifest and offline cache.
Prototype limits
- JPEG/PNG only, 60 MB file limit. Imported images are flattened onto white and reduced to at most 4 MP / 2560 px. Analysis uses a 960 px preview.
- Browser 8-bit color handling; exports omit EXIF and source ICC metadata. No RAW or lens distortion profiles.
- Generic lens model, approximate crop, bilinear resampling. Not a full darktable pipeline or a reproduction of Lightroom's proprietary Auto mode.
- No manual line drawing/selection yet. Overlay colors: green vertical accepted, blue horizontal accepted, red vertical rejected, yellow horizontal rejected, gray other edges.
- Real-device iOS/Android memory, installation, photo picker, and sharing behavior still need hands-on validation. A desktop browser with a phone-sized viewport cannot establish those guarantees.
- LSD's inherited error routine can terminate its WASM instance on allocation failure; the UI catches worker errors, but reload may be required after memory exhaustion.