Inspiration
Needed a convincing scan effect for a document submission. Found nothing that looked right. Built it. Added compression and editing because they belong in the same tool.
What it does
ScanDrift is three tools in one:
Scan Simulator — takes any clean PDF or image and applies a physically-accurate scan effect pipeline. Every parameter is tunable:
- Noise, grain, blur, brightness, contrast
- Yellow/warm tint with uneven aging via fractal noise
- Random rotation, row-by-row sensor jitter, lamp illumination falloff
- Binding shadow, edge vignette, lamp banding, paper fiber texture
- Crease/fold, dust particles, vertical streaks
- Channel misregistration — CCD bar R/G/B vertical shift producing colour fringing on high-contrast edges
- Moiré interference — two-frequency sinusoidal beat pattern simulating halftone material scanned at a mismatched sampling frequency
- Ambient light leak — warm lid-edge bleed for documents that won't lie flat
- Motion smear — text-edge trailing from page shift during scanning
- Scan line dropout — stuck CCD elements producing full-width white/black lines
- JPEG internalization — internal round-trip simulating ADF scanners that compress internally even in "lossless" mode
- Focus gradient — depth-of-field falloff from scanner optics with configurable focused edge and intensity
- Back-bleed — faint mirrored ghost of reverse-page text through paper
- Page warp — multi-octave fractal displacement map for non-flat surfaces
- Spine curve, crumple with directional shadow/highlight, torn edge simulation
- Auto-deskew via horizontal projection profile analysis
- Per-page variation — noise, brightness, tint, rotation vary slightly per page, simulating real multi-page scan inconsistency
- Signature overlay — draw in browser or upload, drag/resize/rotate, place on any page with snap-to-angle
6 named scanner profiles: Canon Office 2020, HP Home Budget, Library Xerox (Worn), Archive Flatbed 2005, Fujitsu Production, Magazine/Print. Output as PDF, ZIP of PNGs, or single image.
Universal File Compressor — 4 file types, separate pipelines:
- PDF: 3 Ghostscript presets (High/Medium/Low). Low preset runs both Ghostscript and a full rasterization pipeline then picks the smaller output
- Images: Sharp-based compression with quality slider, format conversion (JPEG/PNG/WebP), and optional resize — only returns compressed if smaller
- Office docs (DOCX, XLSX, PPTX): recompresses embedded images, repacks at maximum DEFLATE
- ZIP: recompresses embedded images, repacks all entries at maximum DEFLATE
PDF Canvas Editor — pages rendered server-side at 300 DPI:
- Tools: whiteout, blackout, highlight, rectangle, circle, arrow, line, pen, text, stamp, eraser, select
- Text: floating format toolbar with bold, italic, size, font family, colour. Double-click any placed text to re-edit inline
- Signature: draw with smooth multi-stroke bezier or upload, placed as a draggable resizable annotation
- Select tool: click to select, drag to move, corner handles to resize
- Page thumbnail sidebar with live annotation preview
- Zoom 20–500% via buttons or Ctrl+scroll
- 80-level undo/redo, full keyboard shortcut map
- Export flattens annotations at full 300 DPI, assembles with pdf-lib preserving original page dimensions, recompresses with Ghostscript
Freemium: first 3 pages free, $5 one-time Stripe payment unlocks unlimited pages for 10 hours via HMAC-SHA256 signed IP-bound tokens.
How I built it
Node.js and Express backend. PDF pages rendered via PDF.js with a custom
NodeFontDataFactory for server-side font loading. Scan effects operate
directly on raw pixel buffers using Sharp's .raw() pipeline — no
intermediate writes across the 20+ effect chain. Effects requiring spatial
lookups (warp, focus gradient, back-bleed) work on full Uint8ClampedArray
copies. Concurrency is scaled dynamically — heavy effects force single-page
processing; lighter effects allow up to 3 concurrent pages.
Challenges I ran into
Making each effect physically accurate rather than visually approximate. Channel misregistration required understanding how CCD bar scanners read separate colour channels in sequential passes. Moiré required modelling two sinusoidal frequencies at an angular offset to produce the correct basket-weave pattern. Back-bleed required a mirrored, washed-out ghost composite that only affects background pixels, not existing ink. Getting the editor export to match original document dimensions — not render dimensions — required threading PDF.js viewport data all the way through to the pdf-lib assembly step.
What I learned
How much physical imperfection goes into a real scan. Each effect seemed simple in isolation but interacts with every other effect in the chain — grain on top of yellowing on top of warp reads completely differently than any one of them alone. Calibrating the stack to feel like one coherent scan rather than a list of filters was most of the work.
What's next for ScanDrift
- Batch processing across multiple files
- Public API for developer and automation use
- Additional hardware profiles — thermal fax, dot-matrix, photocopier
- Browser extension for one-click document scanning
- OCR layer preservation on compressed PDFs
Built With
- admzip
- archiver
- canvas
- claude
- express.js
- ghostscript
- git
- html/css
- javascript
- node.js
- pdf-lib
- pdf.js
- sharp
- stripe
Log in or sign up for Devpost to join the conversation.