Documentation

Everything you need to integrate pre-upload quality validation into your application.

Installation

npm install blurry-check

Privacy-first design

  • All analysis runs client-side in the browser.
  • Files do not need to leave the user's device.
  • No telemetry, no server uploads, no external file processing.
  • React / Next.js usage must run on the client via 'use client'.

The package requires a browser environment with canvas support. For Node.js server-side use, install the optional canvas peer dependency.

npm install canvas  # optional, for Node.js environments

Quick Start

The fastest way to validate an upload before it reaches your server:

import { validateUpload } from 'blurry-check'

const input = document.querySelector('input[type="file"]')
input.addEventListener('change', async (e) => {
  const file = e.target.files[0]

  // All analysis runs client-side — the file never leaves the browser
  const result = await validateUpload(file, {
    mode: 'document',
    strictness: 'high',
    minScore: 75,
  })

  if (!result.ok) {
    showError(result.message)          // "Image is blurry and too dark."
    showTips(result.recommendations)   // Specific fix suggestions
    return
  }

  uploadToServer(file)
})

Core Modules

BlurryCheck

The main class. Create one instance and reuse it across multiple file checks.

import { BlurryCheck } from 'blurry-check'

const checker = new BlurryCheck({
  method: 'both',
  edgeWidthThreshold: 0.3,
  laplacianThreshold: 150,
  debug: false,
})

const isBlurry = await checker.isImageBlurry(imageFile)
const analysis = await checker.analyzeImage(imageFile)
const pdfResult = await checker.analyzePDF(pdfFile)
const validation = await checker.validateUpload(file, { preset: 'general' })
}

BlurDetector

Low-level blur detection. Exposes edge detection and OpenCV Laplacian methods directly.

import { BlurDetector } from 'blurry-check'

const detector = new BlurDetector({ method: 'both' })
const result = await detector.analyzeImage(imageInput)
console.log(result.isBlurry, result.confidence, result.metrics)

PDFAnalyzer

Full PDF quality analysis with multi-scale rendering, text sharpness, and document frame detection.

import { PDFAnalyzer } from 'blurry-check'

const analyzer = new PDFAnalyzer({ method: 'edge', edgeWidthThreshold: 0.25 })
const result = await analyzer.analyzePDF(pdfFile)
console.log(result.isQualityGood, result.isScanned, result.pagesAnalyzed)

Validators

High-level upload validation with friendly user feedback.

import { validateImage, validateUpload } from 'blurry-check'

// Image-only validation
const imageResult = await validateImage(imageInput, {
  preset: 'profile-photo',
  minWidth: 400,
  minHeight: 400,
})

// Auto-detect image or PDF
const uploadResult = await validateUpload(file, {
  preset: 'document-scan',
  minScore: 75,
})

Configuration

All available configuration options for BlurryCheck and the validator functions.

PropertyTypeDefault
method"edge" | "laplacian" | "both""both"
edgeWidthThresholdnumber0.3
laplacianThresholdnumber150
openCvUrlstringOpenCV CDN
canvasHTMLCanvasElementauto-created
debugbooleanfalse
minScorenumber70
minWidthnumber600
minHeightnumber600
maxWidthnumbernone
maxHeightnumbernone
maxSizeMBnumber10
modeValidationMode'general'
strictness"low" | "medium" | "high"'medium'
maxPagesnumberno limit
samplePages"first" | "all" | "smart" | number[]"all"
maxRenderScalenumber2.0
timeoutMsnumber30000

Validation Modes

Modes apply calibrated defaults for common upload workflows. Use strictness to shift thresholds per mode. The deprecated preset option is mapped to the closest mode internally.

PropertyTypeDefault
generalmode
documentmode
receiptmode
invoicemode
id-cardmode
passportmode
profile-photomode
ocrmode
ai-inputmode

Image Checks

Each image check returns its own QualityCheckResult with status, score, and a user-facing message.

PropertyTypeDefault
blur
brightness
contrast
resolution
fileSize
format

PDF Checks

PDF validation includes per-page metrics and document-level checks.

PropertyTypeDefault
scanned
sharpness
textDensity
orientation
corruptedPages
mobileCapture
pageResolution
brightness
contrast

Real-World Examples

Profile Photo Upload

import { validateImage } from 'blurry-check'

async function handleProfilePhoto(file: File) {
  // Runs client-side — file never leaves the browser
  const result = await validateImage(file, {
    mode: 'profile-photo',
    strictness: 'high',
    minWidth: 400,
    minHeight: 400,
    maxSizeMB: 8,
  })

  if (!result.ok) {
    return { error: result.message, tips: result.recommendations }
  }

  return { success: true }
}

Document Scan Upload

import { validateUpload } from 'blurry-check'

async function handleDocument(file: File) {
  const result = await validateUpload(file, {
    mode: 'document',
    strictness: 'high',
    minScore: 78,
  })

  if (!result.ok) {
    return { error: result.message, issues: result.issues }
  }

  return { success: true }
}

Receipt Upload

import { validateUpload } from 'blurry-check'

async function handleReceipt(file: File) {
  const result = await validateUpload(file, {
    mode: 'receipt',
    strictness: 'medium',
    minScore: 72,
  })

  if (!result.ok) {
    showError(result.message)
    showRecommendations(result.recommendations)
    return
  }

  uploadReceipt(file)
}

KYC / ID Card

import { validateImage } from 'blurry-check'

async function handleIDCard(file: File) {
  const result = await validateImage(file, {
    mode: 'id-card',
    strictness: 'high',
    minScore: 80,
  })

  if (!result.ok) {
    return { rejected: true, reason: result.message }
  }

  return { accepted: true }
}

PDF OCR Preflight

import { validateUpload } from 'blurry-check'

async function preflightPDF(file: File) {
  const result = await validateUpload(file, {
    mode: 'ocr',
    strictness: 'high',
    samplePages: 'smart',
    timeoutMs: 10000,
  })

  if (result.type !== 'pdf') {
    return { error: 'Not a PDF file' }
  }

  // Check for issues that would block OCR
  const ocrBlockers = result.issues.filter(i =>
    ['blurry', 'too_dark', 'low_contrast', 'rotated', 'corrupted_page'].includes(i)
  )

  return {
    ready: ocrBlockers.length === 0,
    score: result.score,
    issues: result.issues,
    pages: result.pages,
  }
}