A free, privacy-focused web tool that adds real-world camera imperfections and compression artifacts to images — the grain, flare, softness, and JPEG blocking that tell your eye "this was taken by a real camera." All processing happens locally in your browser: no uploads, no server, no paywall.
Replicates and extends the JPEG artifact functionality of onlinejpgtools.com/add-jpg-artifacts with 14 physically-ordered effects across five stages of a camera's imaging pipeline.
- 14 camera-imperfection effects in one pass (
js/effects/) — lens flaws, focus flaws, sensor noise, phone-ISP color grading, and real JPEG compression, applied in physically-correct order (light enters the lens, passes through focus, hits the sensor, gets processed, gets compressed). - Real JPEG artifacts, not simulated ones (
js/effects/jpeg-artifacts.js) — the browser's native JPEG encoder (Chrome's libjpeg-turbo, Firefox's mozjpeg) produces genuine compression blocking, ringing, and color banding through an encode → decode → re-encode cycle. - Per-effect regions (
js/ui/region.js) — drag, resize, and position a colored overlay per effect, so grain can cover one area while motion blur hits another. Numeric X/Y/W/H inputs for precise placement. - One-click presets (
js/effects/presets.js) — five camera recipes from "Smartphone Snapshot" to "AI Realism (Colour Corrected)", plus a session-persistent Custom preset that captures your current settings automatically. - Zero dependencies, zero build step — plain ES modules served statically;
index.html+css/+js/is the whole app. Verified: nofetch, noXMLHttpRequestanywhere in the app code — images never leave the browser. - 16 automated effect test suites (
tests/) — each effect's physics has a standalone test file (radial darkening, signal-dependent grain, blur attenuation compensation, region scaling, …).
1. LOAD 2. LENS 3. FOCUS 4. SENSOR 5. COLOR / ISP 6. COMPRESS
+--------------------------+ +------------------------+ +-------------+ +----------------------+ +------------------------+ +-----------------------------+
| any image + any static | | vignetting, edge | | soft focus, | | ISO grain, chroma | | white balance drift, | | real encode -> decode -> |
| server (server.py :8000, | | softness, chromatic | | motion blur | | noise, dead pixels, | | dynamic range stretch, | | re-encode cycle through the |
| npx serve .) - runs | | aberration, lens flare | | (per-effect | | dust spots | | split toning | | browser's native JPEG |
| 100% in your browser, | > | (full-frame effects) | > | regions) | > | (per-effect regions) | > | (full-frame effects) | > | encoder -> download |
| no uploads | | | | | | | | | | |
+--------------------------+ +------------------------+ +-------------+ +----------------------+ +------------------------+ +-----------------------------+
- Load. Open the page, drop in an image. Nothing is uploaded — the Canvas API reads the file locally.
- Lens. Full-frame optical flaws: radial vignetting, progressive edge softness (field curvature), chromatic aberration fringing, lens flare ghosting.
- Focus. Gaussian soft focus (missed autofocus) and directional motion blur, each on its own draggable region.
- Sensor. High-ISO luminance grain (signal-dependent), chroma noise blotches, stuck dead/hot pixels, sensor dust spots — all region-addressable.
- Color / ISP. The phone-processor look: white balance drift from mixed lighting, dynamic-range stretch (deep shadows, bright highlights), split toning (blue shadows, golden highlights).
- Compress. The enabled result is cycled through the browser's real JPEG encoder so the artifact at the end of the chain is genuine compression damage, then the image is ready to download.
js/effects/pipeline.js runs enabled effects in this registry order with intermediate
caching, so re-applying after a small change only recomputes the stages downstream of
what changed.
14 effects across five categories. Each effect has its own strength slider (0–100%) and can be toggled independently.
| Effect | Description |
|---|---|
| Vignetting | Radial darkening toward image edges and corners |
| Edge softness | Progressive blur toward frame edges simulating field curvature / astigmatism |
| Chromatic aberration | Color fringing on high-contrast edges from lens dispersion |
| Lens flare | Ghost reflections and veiling haze when shooting toward bright light |
| Effect | Description |
|---|---|
| Soft focus | Gaussian blur simulating missed smartphone autofocus |
| Motion blur | Directional blur from camera shake or slow shutter speed |
| Effect | Description |
|---|---|
| ISO grain | High-ISO luminance noise, monochromatic and signal-dependent for realism |
| Chroma noise | Blotchy color noise from aggressive high-ISO chroma denoise |
| Dead/hot pixels | Stuck bright or dark pixels from sensor manufacturing defects |
| Dust spots | Small dark spots from dust on the camera sensor |
| Effect | Description |
|---|---|
| White balance drift | Warm/cool color cast across the frame, as from mixed lighting |
| Dynamic range stretch | Added contrast with deeper shadows and brighter highlights, phone-ISP tone mapping style |
| Split toning | Blue-ish shadows and golden highlights — the signature phone-photo color grade |
| Effect | Description |
|---|---|
| JPEG artifacts | Compression blocking, ringing, and color banding from heavy JPEG re-encoding |
Applied in this physical order (js/effects/registry.js):
- Vignetting · 2. Edge softness · 3. Chromatic aberration · 4. Lens flare ·
- Soft focus · 6. Motion blur · 7. ISO grain · 8. Chroma noise · 9. Dead/hot pixels ·
- Dust spots · 11. White balance drift · 12. Dynamic range stretch ·
- Split toning · 14. JPEG artifacts
The 8 region-capable effects each get their own colored overlay on the input canvas:
- Click the overlay to make it active (solid border) vs inactive (dashed border)
- Drag to reposition, drag the bottom-right corner to resize
- "Edit region" buttons on each effect card auto-select that effect's overlay
- Numeric X, Y, Width, Height inputs reflect the active region
| Effect | Overlay color |
|---|---|
| Motion blur | Red (#ff6b6b) |
| Soft focus | Yellow (#ffd93d) |
| ISO grain | Blue (#4d96ff) |
| Chroma noise | Teal-green (#38d9a9) |
| Dead/hot pixels | Orange (#ff922b) |
| Dust spots | Purple (#cc5de8) |
| JPEG artifacts | Teal (#20c997) |
Full-frame effects (vignetting, edge softness, chromatic aberration, lens flare, and the color/ISP group) apply to the entire image and show a "Full frame" badge instead.
| Preset | Effects | Use case |
|---|---|---|
| Smartphone Snapshot | Soft focus 20%, ISO grain 25%, JPEG artifacts 40% | Slightly imperfect everyday photo |
| Old/Cheap Lens | Vignetting 55%, Chromatic aberration 50%, Edge softness 45% | Worn-out budget optics |
| High ISO Night Shot | ISO grain 80%, Motion blur 30%, Dead pixels 35% | Noisy, slightly blurry low-light capture |
| Vintage Film | Vignetting 40%, Dust spots 30%, ISO grain 20%, Chromatic aberration 35% | Retro film aesthetic |
| AI Realism (Colour Corrected) | Chroma noise 30%, ISO grain 20%, WB drift 20%, Dynamic range 25%, Split toning 30%, JPEG artifacts 25% | Targets the AI-vs-real color tells; layer a lens/film preset on top if the shoot calls for it |
After processing, your current configuration is automatically saved as a Custom preset for quick re-use during the session.
python3 server.py
# Open http://localhost:8000server.py is a 15-line static file server that also sends cross-origin isolation
headers. Any static server works:
npx serve .
python3 -m http.server 8000Tests — each effect suite is a standalone Node script:
node tests/vignette.test.mjs # one suite
for t in tests/*.test.mjs; do node "$t"; done # all 16├── index.html # single-page app shell
├── server.py # static server (:8000) with cross-origin isolation headers
├── css/app.css # all styling
├── js/
│ ├── app.js # UI wiring, effect cards, region overlays, presets
│ ├── processor.js # canvas processing + download
│ ├── utils.js
│ ├── effects/
│ │ ├── registry.js # master list in pipeline order
│ │ ├── pipeline.js # ordered runner with intermediate caching
│ │ ├── presets.js # 5 camera recipes + session Custom
│ │ └── *.js # 14 effect modules (one per effect)
│ └── ui/ # upload, region selector, output panel
├── tests/ # 16 standalone effect test suites (node, no framework)
└── docs/superpowers/ # design specs + implementation plans from development
- Effects are physically-inspired models, tuned by eye — vignetting, grain, and flare follow the right functional shapes (radial falloff, signal-dependent noise), but parameters are calibrated against human judgment, not measured against reference cameras.
- JPEG artifacts are browser-dependent by design — they come from the browser's actual encoder, so Chrome and Firefox produce slightly different (both genuine) compression.
- Non-deterministic effects re-roll — grain, noise, dust, and flare draw fresh randomness on every Apply, so two runs with identical settings differ, like two real frames from the same camera.
- Processing is resolution-bound — everything runs on in-memory canvases at the source image's resolution; very large images take proportionally longer.
This app was engineered through an agentic AI workflow: 67 commits over four days
(July 2026), driven by a loop of written design specs → implementation plans → agent
implementation → systematic audit → targeted fixes. docs/superpowers/ preserves the
actual specs and plans from that process (including the camera-flaws design and two
audit-driven bugfix passes that fixed 14 rated bugs). The human role was direction and
judgment: the effect physics wishlist, visual quality bar, and every accept/reject call.
Built by D. Desmaan.
Licensed under the MIT License.