Compare commits

...

18 Commits

Author SHA1 Message Date
ColonelParrot
1138b6216e
Update README.md 2025-01-27 20:05:13 -05:00
ColonelParrot
04b2d3a4c5 bump for npm 2025-01-27 19:56:16 -05:00
ColonelParrot
78ab8fceb6 fix typo 2025-01-27 18:40:29 -05:00
ColonelParrot
d0687658c7 update feature list 2025-01-27 18:38:11 -05:00
ColonelParrot
976ff20489 Modernize 2025-01-27 18:22:17 -05:00
ColonelParrot
ccb883656e Modernize readme 2025-01-27 17:56:09 -05:00
ColonelParrot
6c218b974d height update 2025-01-27 17:11:21 -05:00
ColonelParrot
7a01112dfc update readme logo 2025-01-27 17:10:32 -05:00
ColonelParrot
a742946aa2 website style changes 2025-01-27 17:01:22 -05:00
ColonelParrot
4dd40c2b8f Logo change 2025-01-27 16:57:21 -05:00
ColonelParrot
81cc013b83 fix license link 2025-01-27 16:39:27 -05:00
ColonelParrot
e45ad55c3e Bump to v1.3.1 2025-01-27 16:37:28 -05:00
ColonelParrot
e8e86830e9 Reduce noise filter 2025-01-27 16:35:59 -05:00
ColonelParrot
42f28b2bc8 Bump to v1.3.0 2025-01-27 16:01:47 -05:00
ColonelParrot
084b33ff76 Improve recognition algorithm 2025-01-27 16:00:22 -05:00
ColonelParrot
b6ba8178fc Update test.js to process all test images 2025-01-27 16:00:10 -05:00
ColonelParrot
eff1b752fe add additional test pictures (andrewdcampbell/OpenCV-Document-Scanner) 2025-01-27 15:31:24 -05:00
ColonelParrot
1670c7f7c2
Update old links 2024-12-26 02:07:02 -05:00
22 changed files with 77 additions and 175 deletions

119
README.md
View File

@ -1,118 +1 @@
<p align="center">
<img src="docs/images/logo-full.png" height="100">
</p>
<p align="center">
<a href="https://www.jsdelivr.com/package/gh/ColonelParrot/jscanify"><img src="https://data.jsdelivr.com/v1/package/gh/ColonelParrot/jscanify/badge"></a>
<a href="https://cdnjs.com/libraries/jscanify"><img src="https://img.shields.io/cdnjs/v/jscanify"></a>
<a href="https://npmjs.com/package/jscanify"><img src="https://badgen.net/npm/dw/jscanify"></a>
<br />
<a href="https://github.com/ColonelParrot/puffinsoft/blob/master/LICENSE"><img src="https://img.shields.io/github/license/puffinsoft/jscanify.svg"></a>
<a href="https://npmjs.com/package/jscanify"><img src="https://badgen.net/npm/v/jscanify"></a>
</p>
<p align="center">
<a href="https://nodei.co/npm/jscanify/"><img src="https://nodei.co/npm/jscanify.png"></a>
</p>
<p align="center">
Open-source pure Javascript implemented mobile document scanner. Powered with <a href="https://docs.opencv.org/3.4/d5/d10/tutorial_js_root.html">opencv.js</a><br/>
Supports the web, NodeJS, <a href="https://github.com/ColonelParrot/react-scanify-demo">React</a>, and others.
<br/><br/>
Available on <a href="https://www.npmjs.com/package/jscanify">npm</a> or via <a href="https://www.jsdelivr.com/package/gh/ColonelParrot/jscanify">cdn</a><br/>
</p>
**Features**:
- paper detection & highlighting
- paper scanning with distortion correction
| Image Highlighting | Scanned Result |
| -------------------------------------------- | ------------------------------------------ |
| <img src="docs/images/highlight-paper1.png"> | <img src="docs/images/scanned-paper1.png"> |
| <img src="docs/images/highlight-paper2.png"> | <img src="docs/images/scanned-paper2.png"> |
## Quickstart
> **Developers Note**: you can now use the [jscanify debugging tool](https://colonelparrot.github.io/jscanify/tester.html) to observe the result (highlighting, extraction) on test images.
### Import
npm:
```js
$ npm i jscanify
import jscanify from 'jscanify'
```
cdn:
```html
<script src="https://docs.opencv.org/4.7.0/opencv.js" async></script>
<!-- warning: loading OpenCV can take some time. Load asynchronously -->
<script src="https://cdn.jsdelivr.net/gh/ColonelParrot/jscanify@master/src/jscanify.min.js"></script>
```
> **Note**: jscanify on NodeJS is slightly different. See [wiki: use on NodeJS](https://github.com/ColonelParrot/jscanify/wiki#use-on-nodejs).
### Highlight Paper in Image
```html
<img src="/path/to/your/image.png" id="image" />
```
```js
const scanner = new jscanify();
image.onload = function () {
const highlightedCanvas = scanner.highlightPaper(image);
document.body.appendChild(highlightedCanvas);
};
```
### Extract Paper
```js
const scanner = new jscanify();
const paperWidth = 500;
const paperHeight = 1000;
image.onload = function () {
const resultCanvas = scanner.extractPaper(image, paperWidth, paperHeight);
document.body.appendChild(resultCanvas);
};
```
### Highlighting Paper in User Camera
The following code continuously reads from the user's camera and highlights the paper:
```html
<video id="video"></video> <canvas id="canvas"></canvas>
<!-- original video -->
<canvas id="result"></canvas>
<!-- highlighted video -->
```
```js
const scanner = new jscanify();
const canvasCtx = canvas.getContext("2d");
const resultCtx = result.getContext("2d");
navigator.mediaDevices.getUserMedia({ video: true }).then((stream) => {
video.srcObject = stream;
video.onloadedmetadata = () => {
video.play();
setInterval(() => {
canvasCtx.drawImage(video, 0, 0);
const resultCanvas = scanner.highlightPaper(canvas);
resultCtx.drawImage(resultCanvas, 0, 0);
}, 10);
};
});
```
To export the paper to a PDF, see [here](https://stackoverflow.com/questions/23681325/convert-canvas-to-pdf)
### Notes
- for optimal paper detection, the paper should be placed on a flat surface with a solid background color
- we recommend wrapping your code using `jscanify` in a window `load` event listener to ensure OpenCV is loaded
# this repository has been moved to [puffinsoft/jscanify](https://github.com/puffinsoft/jscanify)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

BIN
docs/images/galaxy.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 51 KiB

BIN
docs/images/logo-github.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

BIN
docs/images/test/test10.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
docs/images/test/test3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
docs/images/test/test4.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
docs/images/test/test5.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
docs/images/test/test6.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
docs/images/test/test7.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
docs/images/test/test8.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 998 KiB

BIN
docs/images/test/test9.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

@ -9,7 +9,7 @@ body {
#hero {
width: 100%;
overflow: hidden;
background-image: url("images/galaxy.png");
background-image: url("images/galaxy.webp");
background-size: cover;
background-position: 0 -170px;
background-attachment: fixed;

View File

@ -20,7 +20,7 @@
<body>
<div id="hero" style="position: relative">
<a href="https://github.com/ColonelParrot/jscanify" aria-label="View the library on GitHub" target="_blank" style="position: absolute; top: 0; right: 0">
<a href="https://github.com/puffinsoft/jscanify" aria-label="View the library on GitHub" target="_blank" style="position: absolute; top: 0; right: 0">
<svg width="80" height="80" viewBox="0 0 250 250"
style="fill:white; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
@ -32,11 +32,11 @@
fill="black" class="octo-body"></path>
</svg>
</a>
<img src="images/logo-full-small.png" alt="jscanify logo" />
<h2>Open-source pure Javascript implemented mobile document scanner.</h2>
<img src="images/logo-full.png" alt="jscanify logo" style="height: 100px" />
<h2>the javascript document scanning library.</h2>
<br />
<div class="view-on">
<a class="view-on-option" href="https://github.com/ColonelParrot/jscanify" target="_blank" style="margin: 10px">
<a class="view-on-option" href="https://github.com/puffinsoft/jscanify" target="_blank" style="margin: 10px">
<svg width="16" height="16" aria-hidden="true" viewBox="0 0 16 16">
<path fill-rule="evenodd"
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z">

View File

@ -1,17 +1,17 @@
{
"name": "jscanify",
"version": "1.2.0",
"version": "1.3.2",
"description": "Open-source Javascript mobile document scanner.",
"main": "src/jscanify-node.js",
"directories": {
"doc": "docs"
},
"scripts": {
"test": "mocha"
"test": "mocha --trace-uncaught"
},
"repository": {
"type": "git",
"url": "https://github.com/ColonelParrot/jscanify.git"
"url": "https://github.com/puffinsoft/jscanify.git"
},
"keywords": [
"js",
@ -21,7 +21,7 @@
"author": "ColonelParrot",
"license": "MIT",
"bugs": {
"url": "https://github.com/ColonelParrot/jscanify/issues"
"url": "https://github.com/puffinsoft/jscanify/issues"
},
"homepage": "https://colonelparrot.github.io/jscanify/",
"dependencies": {

View File

@ -1,4 +1,4 @@
/*! jscanify v1.2.0 | (c) ColonelParrot and other contributors | MIT License */
/*! jscanify v1.3.2 | (c) ColonelParrot and other contributors | MIT License */
const { Canvas, createCanvas, Image, ImageData } = require("canvas");
const { JSDOM } = require("jsdom");
@ -44,13 +44,13 @@ class jscanify {
*/
findPaperContour(img) {
const imgGray = new cv.Mat();
cv.cvtColor(img, imgGray, cv.COLOR_RGBA2GRAY);
cv.Canny(img, imgGray, 50, 200);
const imgBlur = new cv.Mat();
cv.GaussianBlur(
imgGray,
imgBlur,
new cv.Size(5, 5),
new cv.Size(3, 3),
0,
0,
cv.BORDER_DEFAULT
@ -69,6 +69,7 @@ class jscanify {
cv.RETR_CCOMP,
cv.CHAIN_APPROX_SIMPLE
);
let maxArea = 0;
let maxContourIndex = -1;
for (let i = 0; i < contours.size(); ++i) {

View File

@ -1,11 +1,11 @@
/*! jscanify v1.2.0 | (c) ColonelParrot and other contributors | MIT License */
/*! jscanify v1.3.2 | (c) ColonelParrot and other contributors | MIT License */
(function (global, factory) {
typeof exports === "object" && typeof module !== "undefined"
? (module.exports = factory())
: typeof define === "function" && define.amd
? define(factory)
: (global.jscanify = factory());
? define(factory)
: (global.jscanify = factory());
})(this, function () {
"use strict";
@ -20,7 +20,7 @@
}
class jscanify {
constructor() {}
constructor() { }
/**
* Finds the contour of the paper within the image
@ -29,13 +29,13 @@
*/
findPaperContour(img) {
const imgGray = new cv.Mat();
cv.cvtColor(img, imgGray, cv.COLOR_RGBA2GRAY);
cv.Canny(img, imgGray, 50, 200);
const imgBlur = new cv.Mat();
cv.GaussianBlur(
imgGray,
imgBlur,
new cv.Size(5, 5),
new cv.Size(3, 3),
0,
0,
cv.BORDER_DEFAULT
@ -47,7 +47,7 @@
imgThresh,
0,
255,
cv.THRESH_BINARY + cv.THRESH_OTSU
cv.THRESH_OTSU
);
let contours = new cv.MatVector();
@ -60,6 +60,7 @@
cv.RETR_CCOMP,
cv.CHAIN_APPROX_SIMPLE
);
let maxArea = 0;
let maxContourIndex = -1;
for (let i = 0; i < contours.size(); ++i) {

View File

@ -6,71 +6,75 @@ console.log("RUNNING JSCANIFY TESTS");
console.log("Warning: This may take a bit");
const { loadImage, createCanvas } = require("canvas");
const { mkdirSync, writeFileSync, unlinkSync, existsSync } = require("fs");
const { mkdirSync, writeFileSync, unlinkSync, existsSync, readdirSync } = require("fs");
const assert = require("assert");
const jscanify = require("../src/jscanify-node");
const path = require("path");
const outputPaths = {
highlight: __dirname + "/output/highlighted.jpg",
extracted: __dirname + "/output/extracted.jpg",
cornerPoints: __dirname + "/output/corner_points.jpg",
};
const baseFolder = __dirname.replaceAll("\\", "/") + "/output/";
const OUTPUT_FOLDER = __dirname.replaceAll("\\", "/") + "/output/";
const TEST_IMAGE_PATH = path.join(
const TEST_IMAGE_DIRECTORY = path.join(
__dirname,
"..",
"docs",
"images",
"test",
"test.png"
"test"
);
/**
* delete previously generated output images
*/
function setup() {
console.log("=== setting up tests ===");
console.log("Deleting previously generated images");
Object.values(outputPaths).forEach((path) => {
if (existsSync(path)) {
unlinkSync(path);
}
});
if (!existsSync(baseFolder)) {
mkdirSync(baseFolder);
if (!existsSync(OUTPUT_FOLDER)) {
mkdirSync(OUTPUT_FOLDER);
}
readdirSync(OUTPUT_FOLDER).forEach((file) => {
unlinkSync(path.join(OUTPUT_FOLDER, file));
})
}
function test() {
const scanner = new jscanify();
console.log("=== beginning tests ===");
console.log("loading OpenCV.js...");
console.log("=== beginning tests ===");
console.log("loading OpenCV.js...");
scanner.loadOpenCV(function (cv) {
console.log("Finished loading OpenCV.js");
console.log("Writing test images to: " + baseFolder);
describe("feature tests", function () {
const scanner = new jscanify();
scanner.loadOpenCV(function (cv) {
console.log("Finished loading OpenCV.js");
console.log("Writing test images to: " + OUTPUT_FOLDER);
/**
* tests an individual image
*/
function test(testImage, imageCount) {
describe("image #" + imageCount, function () {
it("should highlight paper", function (done) {
const highlighted = scanner.highlightPaper(testImage);
const higlightedOutputPath = OUTPUT_FOLDER + "highlighted-" + imageCount + ".jpg";
writeFileSync(
outputPaths.highlight,
higlightedOutputPath,
highlighted.toBuffer("image/jpeg")
);
assert.ok(existsSync(outputPaths.highlight));
assert.ok(existsSync(higlightedOutputPath));
done();
});
it("should extract paper", function (done) {
const extracted = scanner.extractPaper(testImage, 386, 500);
const extractedOutputPath = OUTPUT_FOLDER + "extracted-" + imageCount + ".jpg";
writeFileSync(
outputPaths.extracted,
extractedOutputPath,
extracted.toBuffer("image/jpeg")
);
assert.ok(existsSync(outputPaths.extracted));
assert.ok(existsSync(extractedOutputPath));
done();
});
@ -103,18 +107,31 @@ function test() {
ctx.fill();
});
writeFileSync(outputPaths.cornerPoints, canvas.toBuffer("image/jpeg"));
const cornerPointsOutputPath = OUTPUT_FOLDER + "corner_points-" + imageCount + ".jpg";
writeFileSync(cornerPointsOutputPath, canvas.toBuffer("image/jpeg"));
assert.ok(existsSync(outputPaths.cornerPoints));
assert.ok(existsSync(cornerPointsOutputPath));
done();
});
});
});
}
}
let testImage;
loadImage(TEST_IMAGE_PATH).then(function (image) {
testImage = image;
setup();
test();
});
let imageCount = 1;
/*
* go through all images in test image directory
*/
readdirSync(TEST_IMAGE_DIRECTORY).forEach((file) => {
const TEST_IMAGE_PATH = path.join(TEST_IMAGE_DIRECTORY, file);
if(!file.endsWith("-sized.png")){ // these images are for the website, not testing
let tempCount = imageCount++;
loadImage(TEST_IMAGE_PATH).then(function (image) {
test(image, tempCount);
});
}
})
});