Nuul is a browser-native privacy photo studio that strips EXIF, GPS, and device metadata from photos up to 50 MB โ entirely in your browser, with zero server storage and zero network transmission.
It is also a full consumer-grade filter app. Choose from sixteen built-in color grades or import your own Lightroom .xmp presets. Every export is re-encoded pixel-by-pixel through a full Lightroom-compatible pipeline: the output file contains only image data. No metadata containers, no GPS coordinates, no device fingerprints survive.
Nuul is for people who care about both how their photos look and what their photos reveal.
The original โ metadata intact, device and location embedded in every pixel container |
Filtered with Kelp โ deep organic tones applied, all metadata structurally removed at export |
Studio desktop โ filter carousel with live thumbnails, each card rendering the actual image in real time |
Mobile view โ Cornerclub selected, the 35mm warm grade active on upload |
-
Sixteen aesthetic filter presets Silverline, Cornerclub, Midnightrun, Loftsunday, Basement, Bluehour, Faded, Mocha, Vapor, Golden Hour, Ice, Kelp, Ritual Night, Candlelight, Soft Grain, and Ritual. Every preset applies the full seven-stage pixel pipeline. Filter choice is aesthetic โ privacy protection is identical across all.
-
Lightroom preset support Import
.xmpfiles directly from Lightroom Classic or Lightroom CC. Presets are parsed in the browser and applied via a pixel-level color grading engine with no server, no conversion tool, no upload. -
Complete metadata removal Canvas re-encoding strips EXIF, GPS coordinates, device make/model, embedded thumbnails, and all other metadata containers by construction โ not by deletion. The output blob is structurally clean.
-
OSINT obfuscation scan Before export, nuul scans for common leaks: API keys, emails, addresses, phone numbers via OCR; QR codes via pixel analysis; face regions via browser-native face detection; browser chrome and open tabs via heuristic screen analysis.
-
Privacy receipt Every export generates a signed JSON receipt documenting what was found and removed. Receipts are stored in a local-only vault within your browser, ensuring no data ever touches a server or is saved to an external database.
-
Zero-network architecture No analytics. No telemetry. No CDN requests during processing. Tesseract OCR assets are served locally. All computation runs in the browser tab.
The nuul filter engine applies Lightroom-compatible color grading through a multi-stage pixel pipeline:
- Tone curve LUT โ exposure (linear EV scale), contrast (S-curve around pivot 0.5), highlights/shadows (tone-range adjustments), whites/blacks (endpoint compression)
- Per-channel RGB curves โ independent tone curves for red, green, blue channels using Fritsch-Carlson monotone cubic spline interpolation
- Temperature / Tint โ per-pixel color shift derived from the raw Kelvin value and green/magenta tint offset stored in the XMP
- Saturation โ global HSL saturation multiplier
- Vibrance โ selective saturation that boosts desaturated colors more, leaving already-saturated tones stable
- HSL per-hue adjustments โ eight Lightroom color ranges (red, orange, yellow, green, aqua, blue, purple, magenta) with cosine bell falloff
- Vignette + Grain โ radial gradient overlay and per-pixel luminance noise
Preview rendering runs at 900px for sharp real-time feedback. Export rendering runs at full resolution. Carousel thumbnails are generated live from the imported image at 120px.
Lightroom exports presets as .xmp files containing XML with crs: namespace attributes. Nuul parses these directly in the browser using the DOM parser and maps the following fields:
Exposure2012, Contrast2012, Highlights2012, Shadows2012, Whites2012, Blacks2012, Temperature, Tint, Vibrance, Saturation, Clarity2012, Dehaze, VignetteAmount, GrainAmount, SharpenAmount, PresetName
Multiple presets can be imported at once. Imported presets appear in the filter strip alongside built-ins with live thumbnails auto-generated from the current image.
The privacy guarantee is architectural, not procedural. Nuul does not delete metadata from the source file. Instead, it draws the image bitmap onto an HTML Canvas element โ which captures only pixel data โ and encodes a new blob from that canvas. The resulting file has no metadata containers because none were ever written.
This means:
- EXIF data (camera settings, timestamps, software) is gone because canvas has no mechanism to carry it
- GPS coordinates are gone for the same reason
- Device identifiers (Make, Model, Serial) are gone
- Embedded thumbnail variants stored in JPEG headers are gone
- ICC color profiles are stripped (canvas uses sRGB)
The approach is verified post-export using exifr.parse() on the output blob. A result of null or an empty object confirms clean output.
flowchart TB
Input[Image File] -->|Decode| Bitmap[ImageBitmap]
Bitmap --> Canvas[Analysis Canvas 1400px]
Canvas --> OCR[Tesseract OCR]
Canvas --> QR[jsQR โ QR Detection]
Canvas --> Face[Browser FaceDetector API]
Canvas --> Screen[Screen Heuristics]
OCR --> PII[PII Rules โ API keys, emails, addresses]
PII --> Findings[ScanFindings]
QR --> Findings
Face --> Findings
Screen --> Findings
Findings --> Risk[Risk Level: social / work / high]
Risk --> Studio[Studio UI]
Studio --> Filter[Color Grade Filter]
Filter --> Export[Canvas Re-encode โ Clean Blob]
Export --> Receipt[JSON Privacy Receipt]
Core
- Next.js 14, React 18, TypeScript 5
- Tailwind CSS
Privacy Pipeline
exifrโ EXIF parsing for scan and post-export verificationjsQRโ pure-JS QR code detection from ImageDataTesseract.jsโ local OCR (assets served from/public/tesseract/, no network)- Browser
FaceDetectorAPI โ face region detection where available - Canvas 2D API โ pixel-level filter engine and metadata-stripping re-encode
Distribution
- Static export via
next exportโ deployable to Vercel, Netlify, Cloudflare Pages, or any CDN - No backend required. No server. No database.
- Node.js 20 or newer
- npm or pnpm
# Install dependencies
npm install
# Start dev server
npm run dev
# โ http://localhost:3000# One-time setup โ connect your GitHub repo at vercel.com
# or push and deploy from CLI:
npx vercel --prodSet build command npm run build, output directory out. Connect the GitHub repo. Free tier, global CDN, no traffic limits.
npm run build
# Output in /out โ drop onto any static host๐๐ด๐ช๐ฏ๐จ ๐ ๐ฐ๐ถ๐ณ ๐๐ช๐จ๐ฉ๐ต๐ณ๐ฐ๐ฐ๐ฎ ๐๐ณ๐ฆ๐ด๐ฆ๐ต๐ด
- In Lightroom, right-click a preset โ Export โ save as
.xmp - In Nuul Studio, click Import Lightroom preset (.xmp) in the filter strip
- Select one or multiple
.xmpfiles - The preset appears in the strip with a live thumbnail generated from your current image
- Click to apply โ the preview updates in real time at 900px
- Export โ the filter is baked into the output at full resolution before metadata stripping
- Canvas re-encoding converts to sRGB โ wide-gamut (P3, ProPhoto) color profiles are not preserved
- Face detection uses the browser
FaceDetectorAPI โ not available in Firefox or older Safari; falls back silently - OCR accuracy depends on image resolution and font clarity; capped at a 3 s timeout to prevent UI blocking
- Receipt Vault uses localStorage โ IndexedDB migration is planned
- Nuul does not protect against reverse image search or determined OSINT adversaries with access to the visual content
- Tone curve editor with draggable control points
- HSL per-channel color adjustments
.cubeLUT support for cinematic color grades- AI-assisted face and object blur
- Full IndexedDB receipt vault with export and search
- Plugin API for custom filter presets
Nuul is released under the MIT License. See the LICENSE file for details.
Author: Vanessa Madison Site: vanessamadison.com
For collaboration, preset sharing, or security review discussions, open an issue or reach out through the site.





