scanic 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +334 -0
- package/dist/favicon.ico +0 -0
- package/dist/index.html +17 -0
- package/dist/scanic-logo-bg.png +0 -0
- package/dist/scanic.js +940 -0
- package/dist/scanic.js.map +1 -0
- package/dist/scanic.umd.cjs +2 -0
- package/dist/scanic.umd.cjs.map +1 -0
- package/package.json +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 marquaye
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="#">
|
|
3
|
+
<img src="./public/scanic-logo-bg.png" alt="scanic logo" height="400">
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
# Scanic
|
|
8
|
+
|
|
9
|
+
**Modern Document Scanner for the Web**
|
|
10
|
+
|
|
11
|
+
Scanic is a blazing-fast, lightweight, and modern document scanner library written in JavaScript and rust (WASM). It enables developers to detect, scan, and process documents from images directly in the browser or Node.js, with no dependencies or external services.
|
|
12
|
+
|
|
13
|
+
## Why Scanic?
|
|
14
|
+
|
|
15
|
+
I always wanted to use document scanning features within web environments for years. While OpenCV makes this easy, it comes at the cost of a 30+ MB download.
|
|
16
|
+
|
|
17
|
+
Scanic combines pure JavaScript algorithms with **Rust-compiled WebAssembly** for performance-critical operations like Gaussian blur, Canny edge detection, and gradient calculations. This hybrid approach delivers near-native performance while maintaining JavaScript's accessibility and a lightweight footprint.
|
|
18
|
+
|
|
19
|
+
Performance-wise, I'm working to match OpenCV solutions while maintaining the lightweight footprint - this is an ongoing area of improvement.
|
|
20
|
+
|
|
21
|
+
This library is heavily inspired by [jscanify](https://github.com/puffinsoft/jscanify)
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- 📄 **Document Detection**: Accurately finds and extracts document contours from images
|
|
26
|
+
- ⚡ **Pure JavaScript**: Works everywhere JavaScript runs
|
|
27
|
+
- 🦀 **Rust WebAssembly**: Performance-critical operations optimized with Rust-compiled WASM
|
|
28
|
+
- 🛠️ **Easy Integration**: Simple API for web apps, Electron, or Node.js applications
|
|
29
|
+
- 🏷️ **MIT Licensed**: Free for personal and commercial use
|
|
30
|
+
- 📦 **Lightweight**: Small bundle size compared to OpenCV-based solutions
|
|
31
|
+
|
|
32
|
+
## Demo
|
|
33
|
+
|
|
34
|
+
Try the live demo: [Open Demo](https://marquaye.github.io/scanic/demo.html)
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install scanic
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or use via CDN:
|
|
43
|
+
|
|
44
|
+
```html
|
|
45
|
+
<script src="https://unpkg.com/scanic/dist/scanic.js"></script>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
import { scanDocument, LiveScanner, checkWebcamAvailability } from 'scanic';
|
|
52
|
+
|
|
53
|
+
// Simple usage - just detect document
|
|
54
|
+
const result = await scanDocument(imageElement);
|
|
55
|
+
if (result.success) {
|
|
56
|
+
console.log('Document found at corners:', result.corners);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Extract the document (perspective correction)
|
|
60
|
+
const extracted = await scanDocument(imageElement, { mode: 'extract' });
|
|
61
|
+
if (extracted.success) {
|
|
62
|
+
document.body.appendChild(extracted.output); // Display extracted document
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Complete Example
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
import { scanDocument } from 'scanic';
|
|
70
|
+
|
|
71
|
+
async function processDocument() {
|
|
72
|
+
// Get image from file input or any source
|
|
73
|
+
const imageFile = document.getElementById('fileInput').files[0];
|
|
74
|
+
const img = new Image();
|
|
75
|
+
|
|
76
|
+
img.onload = async () => {
|
|
77
|
+
try {
|
|
78
|
+
// Extract and display the scanned document
|
|
79
|
+
const result = await scanDocument(img, {
|
|
80
|
+
mode: 'extract',
|
|
81
|
+
output: 'canvas'
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (result.success) {
|
|
85
|
+
// Add the extracted document to the page
|
|
86
|
+
document.getElementById('output').appendChild(result.output);
|
|
87
|
+
|
|
88
|
+
// Or get as data URL for download/display
|
|
89
|
+
const dataUrl = result.output.toDataURL('image/png');
|
|
90
|
+
console.log('Extracted document as data URL:', dataUrl);
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error('Error processing document:', error);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
img.src = URL.createObjectURL(imageFile);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// HTML setup
|
|
101
|
+
// <input type="file" id="fileInput" accept="image/*" onchange="processDocument()">
|
|
102
|
+
// <div id="output"></div>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## API Reference
|
|
106
|
+
|
|
107
|
+
### Core Function
|
|
108
|
+
|
|
109
|
+
#### `scanDocument(image, options?)`
|
|
110
|
+
Main entry point for document scanning with flexible modes and output options.
|
|
111
|
+
|
|
112
|
+
**Parameters:**
|
|
113
|
+
- `image`: HTMLImageElement, HTMLCanvasElement, or ImageData
|
|
114
|
+
- `options`: Optional configuration object
|
|
115
|
+
- `mode`: String - 'detect' (default), 'highlight', or 'extract'
|
|
116
|
+
- `'detect'`: Only detect document, return corners/contour info (no image processing)
|
|
117
|
+
- `'highlight'`: Draw outline on original image
|
|
118
|
+
- `'extract'`: Extract/warp the document region
|
|
119
|
+
- `output`: String - 'canvas' (default), 'imagedata', or 'dataurl'
|
|
120
|
+
- `debug`: Boolean (default: false) - Enable debug information
|
|
121
|
+
- Detection options:
|
|
122
|
+
- `maxProcessingDimension`: Number (default: 800) - Maximum dimension for processing
|
|
123
|
+
- `lowThreshold`: Number (default: 75) - Lower threshold for Canny edge detection
|
|
124
|
+
- `highThreshold`: Number (default: 200) - Upper threshold for Canny edge detection
|
|
125
|
+
- `dilationKernelSize`: Number (default: 3) - Kernel size for dilation
|
|
126
|
+
- `dilationIterations`: Number (default: 1) - Number of dilation iterations
|
|
127
|
+
- `minArea`: Number (default: 1000) - Minimum contour area for document detection
|
|
128
|
+
- `epsilon`: Number - Epsilon for polygon approximation
|
|
129
|
+
|
|
130
|
+
**Returns:** `Promise<{ output, corners, contour, debug, success, message }>`
|
|
131
|
+
|
|
132
|
+
- `output`: Processed image (null for 'detect' mode)
|
|
133
|
+
- `corners`: Object with `{ topLeft, topRight, bottomRight, bottomLeft }` coordinates
|
|
134
|
+
- `contour`: Array of contour points
|
|
135
|
+
- `success`: Boolean indicating if document was detected
|
|
136
|
+
- `message`: Status message
|
|
137
|
+
|
|
138
|
+
### Live Scanner
|
|
139
|
+
|
|
140
|
+
#### `LiveScanner`
|
|
141
|
+
Real-time document scanner for webcam integration.
|
|
142
|
+
|
|
143
|
+
**Constructor Options:**
|
|
144
|
+
- `targetFPS`: Number (default: 10) - Target frames per second
|
|
145
|
+
- `detectionInterval`: Number (default: 150) - Milliseconds between detections
|
|
146
|
+
- `confidenceThreshold`: Number (default: 0.7) - Confidence threshold for detections
|
|
147
|
+
- `stabilizationFrames`: Number (default: 3) - Frames needed for stable detection
|
|
148
|
+
- `maxProcessingDimension`: Number (default: 500) - Max dimension for live processing
|
|
149
|
+
|
|
150
|
+
**Methods:**
|
|
151
|
+
- `init(outputElement, constraints)` - Initialize webcam and start scanning
|
|
152
|
+
- `stop()` - Stop scanning and release resources
|
|
153
|
+
- `pause()` - Pause scanning
|
|
154
|
+
- `resume()` - Resume scanning
|
|
155
|
+
- `capture()` - Capture current frame
|
|
156
|
+
|
|
157
|
+
**Events:**
|
|
158
|
+
- `onDetection(result)` - Called when document is detected
|
|
159
|
+
- `onFPSUpdate(fps)` - Called with current FPS
|
|
160
|
+
- `onError(error)` - Called on errors
|
|
161
|
+
|
|
162
|
+
#### `checkWebcamAvailability()`
|
|
163
|
+
Checks if webcam is available and lists video devices.
|
|
164
|
+
|
|
165
|
+
**Returns:** `Promise<{ available: boolean, deviceCount?: number, devices?: Array, error?: string }>`
|
|
166
|
+
|
|
167
|
+
All functions work in both browser and Node.js environments. For Node.js, use a compatible canvas/image implementation like `canvas` or `node-canvas`.
|
|
168
|
+
|
|
169
|
+
## Examples
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
const options = {
|
|
173
|
+
mode: 'extract',
|
|
174
|
+
maxProcessingDimension: 1000, // Higher quality, slower processing
|
|
175
|
+
lowThreshold: 50, // More sensitive edge detection
|
|
176
|
+
highThreshold: 150,
|
|
177
|
+
dilationKernelSize: 5, // Larger dilation kernel
|
|
178
|
+
minArea: 2000, // Larger minimum document area
|
|
179
|
+
debug: true // Enable debug information
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const result = await scanDocument(imageElement, options);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Different Modes and Output Formats
|
|
186
|
+
|
|
187
|
+
```js
|
|
188
|
+
// Just detect (no image processing)
|
|
189
|
+
const detection = await scanDocument(imageElement, { mode: 'detect' });
|
|
190
|
+
|
|
191
|
+
// Extract as canvas
|
|
192
|
+
const extracted = await scanDocument(imageElement, {
|
|
193
|
+
mode: 'extract',
|
|
194
|
+
output: 'canvas'
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Highlight as data URL
|
|
198
|
+
const highlighted = await scanDocument(imageElement, {
|
|
199
|
+
mode: 'highlight',
|
|
200
|
+
output: 'dataurl'
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Extract as ImageData
|
|
204
|
+
const rawData = await scanDocument(imageElement, {
|
|
205
|
+
mode: 'extract',
|
|
206
|
+
output: 'imagedata'
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Live Scanner Usage
|
|
211
|
+
|
|
212
|
+
```js
|
|
213
|
+
import { LiveScanner, checkWebcamAvailability } from 'scanic';
|
|
214
|
+
|
|
215
|
+
// Check if webcam is available
|
|
216
|
+
const webcamStatus = await checkWebcamAvailability();
|
|
217
|
+
if (!webcamStatus.available) {
|
|
218
|
+
console.error('No webcam available');
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Create live scanner
|
|
223
|
+
const liveScanner = new LiveScanner({
|
|
224
|
+
targetFPS: 15,
|
|
225
|
+
detectionInterval: 100,
|
|
226
|
+
maxProcessingDimension: 600
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Set up event handlers
|
|
230
|
+
liveScanner.onDetection = (result) => {
|
|
231
|
+
if (result.success) {
|
|
232
|
+
console.log('Document detected:', result.corners);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
liveScanner.onFPSUpdate = (fps) => {
|
|
237
|
+
console.log('Current FPS:', fps);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// Start scanning
|
|
241
|
+
const outputCanvas = document.getElementById('scanner-output');
|
|
242
|
+
await liveScanner.init(outputCanvas);
|
|
243
|
+
|
|
244
|
+
// Stop scanning when done
|
|
245
|
+
// liveScanner.stop();
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Development
|
|
249
|
+
|
|
250
|
+
Clone the repository and set up the development environment:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
git clone https://github.com/marquaye/scanic.git
|
|
254
|
+
cd scanic
|
|
255
|
+
npm install
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Start the development server:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
npm run dev
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Build for production:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
npm run build
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
The built files will be available in the `dist/` directory.
|
|
271
|
+
|
|
272
|
+
### Building the WebAssembly Module
|
|
273
|
+
|
|
274
|
+
The Rust WASM module is pre-compiled and included in the repository. If you need to rebuild it:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
npm run build:wasm
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
This uses Docker to build the WASM module without requiring local Rust installation.
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
### Performance Architecture
|
|
284
|
+
|
|
285
|
+
Scanic uses a **hybrid JavaScript + WebAssembly approach**:
|
|
286
|
+
|
|
287
|
+
- **JavaScript Layer**: High-level API, DOM manipulation, and workflow coordination
|
|
288
|
+
- **WebAssembly Layer**: CPU-intensive operations like:
|
|
289
|
+
- Gaussian blur with SIMD optimizations
|
|
290
|
+
- Canny edge detection with hysteresis thresholding
|
|
291
|
+
- Gradient calculations using Sobel operators
|
|
292
|
+
- Non-maximum suppression for edge thinning
|
|
293
|
+
- Morphological operations (dilation/erosion)
|
|
294
|
+
|
|
295
|
+
The WASM module is compiled from Rust using `wasm-bindgen` and includes fixed-point arithmetic optimizations for better performance on integer operations.
|
|
296
|
+
|
|
297
|
+
## Contributing
|
|
298
|
+
|
|
299
|
+
Contributions are welcome! Here's how you can help:
|
|
300
|
+
|
|
301
|
+
1. **Report Issues**: Found a bug? Open an issue with details and reproduction steps
|
|
302
|
+
2. **Feature Requests**: Have an idea? Create an issue to discuss it
|
|
303
|
+
3. **Pull Requests**: Ready to contribute code?
|
|
304
|
+
- Fork the repository
|
|
305
|
+
- Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
306
|
+
- Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
307
|
+
- Push to the branch (`git push origin feature/amazing-feature`)
|
|
308
|
+
- Open a Pull Request
|
|
309
|
+
|
|
310
|
+
Please ensure your code follows the existing style and includes appropriate tests.
|
|
311
|
+
|
|
312
|
+
## Sponsors
|
|
313
|
+
|
|
314
|
+
[zeugnisprofi](https://zeugnisprofi.com)
|
|
315
|
+
|
|
316
|
+
[zeugnisprofi.de] (https://zeugnisprofi.de)
|
|
317
|
+
|
|
318
|
+
[verlingo](https://www.verlingo.de)
|
|
319
|
+
|
|
320
|
+
## Roadmap
|
|
321
|
+
|
|
322
|
+
- [ ] Performance optimizations to match OpenCV speed
|
|
323
|
+
- [ ] Enhanced WASM module with additional Rust-optimized algorithms
|
|
324
|
+
- [ ] SIMD vectorization for more image processing operations
|
|
325
|
+
- [ ] TypeScript definitions
|
|
326
|
+
- [ ] Additional image enhancement filters
|
|
327
|
+
- [ ] Mobile-optimized processing
|
|
328
|
+
- [ ] WebGPU acceleration for supported browsers
|
|
329
|
+
|
|
330
|
+
## License
|
|
331
|
+
|
|
332
|
+
MIT License © [marquaye](https://github.com/marquaye)
|
|
333
|
+
|
|
334
|
+
See [LICENSE](LICENSE) for more details.
|
package/dist/favicon.ico
ADDED
|
Binary file
|
package/dist/index.html
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
6
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
7
|
+
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
|
8
|
+
<title><%= htmlWebpackPlugin.options.title %></title>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<noscript>
|
|
12
|
+
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
|
13
|
+
</noscript>
|
|
14
|
+
<div id="app"></div>
|
|
15
|
+
<!-- built files will be auto injected -->
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
Binary file
|