npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

scanic

v1.0.6

Published

Modern document scanner in pure JavaScript and Wasm

Readme

Scanic 📄⚡

Ultra-fast, production-ready document scanning for the modern Web.

Scanic is a high-performance document scanner library that brings professional-grade document edge detection and perspective correction to the browser and Node.js. By combining Rust-powered WebAssembly for pixel crunching and GPU-accelerated Canvas for image warping, Scanic delivers near-native performance (~10ms transforms) with a tiny footprint.

Live Demo | Framework Examples | API Reference


🚀 Why Scanic?

Traditional web scanning solutions often force a trade-off:

  • OpenCV.js: Powerful, but requires a massive 30MB+ download.
  • Pure JS: Lightweight, but struggles with real-time performance and complex transforms.

Scanic bridges this gap:

  • Hybrid Engine: Rust/WASM handles the CPU-heavy edge detection.
  • Turbo Warp: Custom Triangle Subdivision algorithm utilizes the GPU for perspective correction.
  • Zero Latency: Designed for real-time applications like webcam scanning.

✨ Features

  • 🎯 Pinpoint Accuracy: Robust document contour detection even in low-contrast environments.
  • Turbocharged Warp: Perspective transforms in < 10ms (vs 500ms+ in standard loops).
  • 🦀 WASM Core: High-performance Gaussian Blur, Canny Edge Detection, and Dilation.
  • 🛠️ Modern API: Clean, Promise-based API with full TypeScript support.
  • 📦 Featherweight: Under 100KB total size (gzipped).
  • 🧪 Production Grade: Built-in regression tests with physical image baselines.

🛠️ Installation

# via npm
npm install scanic

# via yarn
yarn add scanic

CDN

<script src="https://unpkg.com/scanic/dist/scanic.js"></script>

🎮 Demo

Try the interactive scanner in your browser: 👉 Open Scanic Live Demo


📖 Usage

Simple Usage

import { scanDocument, extractDocument } from 'scanic';

// Simple usage - just detect document
const result = await scanDocument(imageElement);
if (result.success) {
  console.log('Document found at corners:', result.corners);
}

// Extract the document (with perspective correction)
const extracted = await scanDocument(imageElement, { mode: 'extract' });
if (extracted.success) {
  document.body.appendChild(extracted.output); // Display extracted document
}

Optimized Usage (Recommended for Batch/Real-time)

The Scanner class maintains a persistent WebAssembly instance, avoiding the overhead of re-initializing WASM for every scan.

import { Scanner } from 'scanic';

const scanner = new Scanner();

// Initialize once (optional, scan() will initialize if needed)
await scanner.initialize();

// Scan multiple images efficiently
async function onFrame(img) {
  const result = await scanner.scan(img, { mode: 'extract' });
  if (result.success) {
    // Process result...
  }
}

Complete Example

import { scanDocument } from 'scanic';

async function processDocument() {
  // Get image from file input or any source
  const imageFile = document.getElementById('fileInput').files[0];
  const img = new Image();
  
  img.onload = async () => {
    try {  
      // Extract and display the scanned document
      const result = await scanDocument(img, { 
        mode: 'extract',
        output: 'canvas'
      });
      
      if (result.success) {
        // Add the extracted document to the page
        document.getElementById('output').appendChild(result.output);
        
        // Or get as data URL for download/display
        const dataUrl = result.output.toDataURL('image/png');
        console.log('Extracted document as data URL:', dataUrl);
      }    
    } catch (error) {
      console.error('Error processing document:', error);
    }
  };
  
  img.src = URL.createObjectURL(imageFile);
}

// HTML setup
// <input type="file" id="fileInput" accept="image/*" onchange="processDocument()">
// <div id="output"></div>

⚙️ API Reference

scanDocument(image, options?)

The primary function for detecting and extracting documents.

| Parameter | Type | Description | | :--- | :--- | :--- | | image | HTMLImage\|Canvas\|ImageData | The source image to scan. | | options | Object | Configuration options (see below). |

options Properties

| Option | Type | Default | Description | | :--- | :--- | :--- | :--- | | mode | 'detect' \| 'extract' | 'detect' | 'detect' returns coordinates; 'extract' returns the warped image. | | output | 'canvas' \| 'imagedata' \| 'dataurl' | 'canvas' | The format of the returned processed image. | | maxProcessingDimension | number | 800 | Downscales image to this size for detection (faster). | | lowThreshold | number | 75 | Lower threshold for Canny edge detection. | | highThreshold | number | 200 | Upper threshold for Canny edge detection. | | minArea | number | 1000 | Minimum pixel area to consider a contour a "document". | | debug | boolean | false | If true, returns intermediate processing steps. |

Return Value

Returns a Promise<ScannerResult>:

{
  success: boolean;       // Did we find a document?
  corners: CornerPoints;  // { topLeft, topRight, bottomRight, bottomLeft }
  output: any;            // The warped image (if mode is 'extract')
  contour: Array<Point>;  // Raw detection points
  timings: Array<Object>; // Performance breakdown
  message: string;        // Status or error message
}

new Scanner()

The recommended class for high-performance applications (Webcam, Batch processing).

const scanner = new Scanner();
await scanner.initialize(); // Pre-loads WASM
const result = await scanner.scan(image, options);

Examples

const options = {
  mode: 'extract',
  maxProcessingDimension: 1000,  // Higher quality, slower processing
  lowThreshold: 50,              // More sensitive edge detection
  highThreshold: 150,
  dilationKernelSize: 5,         // Larger dilation kernel
  minArea: 2000,                 // Larger minimum document area
  debug: true                    // Enable debug information
};

const result = await scanDocument(imageElement, options);

Different Modes and Output Formats

// Just detect (no image processing)
const detection = await scanDocument(imageElement, { mode: 'detect' });

// Extract as canvas
const extracted = await scanDocument(imageElement, { 
  mode: 'extract',
  output: 'canvas' 
});

// Extract as ImageData
const rawData = await scanDocument(imageElement, { 
  mode: 'extract',
  output: 'imagedata' 
});

// Extract as DataURI
const rawData = await scanDocument(imageElement, { 
  mode: 'extract',
  output: 'dataurl' 
});

💻 Framework Examples

Scanic is framework-agnostic but works great with modern UI libraries:

| Framework | Link | | :--- | :--- | | Vue 3 | Vue.js Example & Guide | | React | React Example & Guide |


🛠️ Development

Clone the repository and set up the development environment:

git clone https://github.com/marquaye/scanic.git
cd scanic
npm install

Start the development server:

npm run dev

Build for production:

npm run build

The built files will be available in the dist/ directory.

Building the WebAssembly Module

The Rust WASM module is pre-compiled and included in the repository. If you need to rebuild it:

npm run build:wasm

This uses Docker to build the WASM module without requiring local Rust installation.

Testing

Scanic uses Vitest for unit and regression testing. We test against real document images to ensure detection accuracy remains consistent.

npm test

🖥️ Node.js Support

Scanic can run on the server! Since it relies on the Canvas API, you need to provide a canvas implementation (like node-canvas) and a DOM environment (jsdom).

import { scanDocument } from 'scanic';
import { loadImage } from 'canvas';
import { JSDOM } from 'jsdom';

// Setup global environment
const dom = new JSDOM();
global.document = dom.window.document;
global.ImageData = dom.window.ImageData;

const img = await loadImage('document.jpg');
const result = await scanDocument(img, { mode: 'extract' });

📊 Comparison

| Feature | Scanic | jscanify | OpenCV.js | | :--- | :--- | :--- | :--- | | Download Size | ~100KB | ~1MB | ~30MB | | Perspective Speed | ~10ms | ~200ms | ~5ms | | WASM Optimized | ✅ Yes | ❌ No | ✅ Yes | | GPU Acceleration | ✅ Yes | ❌ No | ❌ No | | TypeScript | ✅ Yes | ❌ No | ✅ Yes |


🏗️ Performance Architecture

Scanic uses a hybrid JavaScript + WebAssembly approach:

  • JavaScript Layer: High-level API, DOM manipulation, and workflow coordination
  • WebAssembly Layer: CPU-intensive operations like:
    • Gaussian blur with SIMD optimizations
    • Canny edge detection with hysteresis thresholding
    • Gradient calculations using Sobel operators
    • Non-maximum suppression for edge thinning
    • Morphological operations (dilation/erosion)

🤝 Contributing

Contributions are welcome! Whether it's reporting a bug, suggesting a feature, or submitting a pull request, your help is appreciated.

  1. Report Issues: Use the GitHub Issue tracker.
  2. Pull Requests:
    • Fork the repository.
    • Create a feature branch.
    • Commit your changes.
    • Open a Pull Request.

📜 Credits

  • Inspired by jscanify.
  • WASM Blur module powered by Rust.

💖 Sponsors

🏆 Gold Sponsors

🗺️ Roadmap

  • [x] TypeScript definitions
  • [x] High-performance perspective transformation (Triangle Subdivision)
  • [ ] Enhanced WASM module with additional Rust-optimized algorithms
  • [ ] WebGPU acceleration for supported browsers
  • [ ] Mobile-optimized real-time video processing frames
  • [ ] Additional image enhancement filters (Adaptive Thresholding, B&W)

License

MIT License © marquaye