jscanify 1.0.0

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 ColonelParrot
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,97 @@
1
+ <p align="center">
2
+ <img src="docs/images/logo-full.png" height="100">
3
+ </p>
4
+
5
+ <p align="center">
6
+ 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/><br/>
7
+ Available on <a href="https://www.npmjs.com/package/jscanify">npm</a> or via cdn
8
+ </p>
9
+
10
+ **Features**:
11
+
12
+ - paper detection & highlighting
13
+ - paper scanning with distortion correction
14
+
15
+ | Image Highlighting | Scanned Result |
16
+ | ------------------------------------------------- | ----------------------------------------------- |
17
+ | <img src="docs/images/highlight-paper1.png"> | <img src="docs/images/scanned-paper1.png"> |
18
+ | <img src="docs/images/highlight-paper2.png"> | <img src="docs/images/scanned-paper2.png"> |
19
+
20
+ ## Quickstart
21
+
22
+ ### Import
23
+
24
+ npm:
25
+
26
+ ```js
27
+ $ npm i jscanify
28
+ import jscanify from 'jscanify'
29
+ ```
30
+
31
+ cdn:
32
+
33
+ ```html
34
+ <script src="https://docs.opencv.org/4.7.0/opencv.js" async></script>
35
+ <!-- warning: loading OpenCV can take some time. Load asynchronously -->
36
+ <script src="https://cdn.jsdelivr.net/gh/ColonelParrot/jscanify@master/src/jscanify.min.js"></script>
37
+ ```
38
+
39
+ ### Highlight Paper in Image
40
+
41
+ ```html
42
+ <img src="/path/to/your/image.png" id="image" />
43
+ ```
44
+
45
+ ```js
46
+ const scanner = new jscanify();
47
+ image.onload = function () {
48
+ const highlightedCanvas = scanner.highlightPaper(image);
49
+ document.body.appendChild(highlightedCanvas);
50
+ };
51
+ ```
52
+
53
+ ### Extract Paper
54
+
55
+ ```js
56
+ const scanner = new jscanify();
57
+ const paperWidth = 500;
58
+ const paperHeight = 1000;
59
+ image.onload = function () {
60
+ scanner.extractPaper(image, paperWidth, paperHeight, (resultCanvas) => {
61
+ document.body.appendChild(resultCanvas);
62
+ });
63
+ };
64
+ ```
65
+
66
+ ### Highlighting Paper in User Camera
67
+
68
+ The following code continuously reads from the user's camera and highlights the paper:
69
+
70
+ ```html
71
+ <video id="video"></video>
72
+ <canvas id="canvas"></canvas> <!-- original video -->
73
+ <canvas id="result"></canvas> <!-- highlighted video -->
74
+ ```
75
+
76
+ ```js
77
+ const scanner = new jscanify();
78
+ const canvasCtx = canvas.getContext("2d");
79
+ const resultCtx = result.getContext("2d");
80
+ navigator.mediaDevices.getUserMedia({ video: true }).then((stream) => {
81
+ video.srcObject = stream;
82
+ video.onloadedmetadata = () => {
83
+ video.play();
84
+
85
+ setInterval(() => {
86
+ canvasCtx.drawImage(video, 0, 0);
87
+ const resultCanvas = scanner.highlightPaper(canvas);
88
+ resultCtx.drawImage(resultCanvas, 0, 0);
89
+ }, 10);
90
+ };
91
+ });
92
+ ```
93
+
94
+ To export the paper to a PDF, see [here](https://stackoverflow.com/questions/23681325/convert-canvas-to-pdf)
95
+ ### Notes
96
+ - for optimal paper detection, the paper should be placed on a flat surface with a solid background color
97
+ - we recommend wrapping your code using `jscanify` in a window `load` event listener to ensure OpenCV is loaded
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/docs/index.css ADDED
@@ -0,0 +1,142 @@
1
+ @import url("https://fonts.googleapis.com/css?family=Lato:100,200,300,400,500,600,700,800,900");
2
+
3
+ html,
4
+ body {
5
+ margin: 0;
6
+ font-family: "Lato";
7
+ }
8
+
9
+ #hero {
10
+ width: 100%;
11
+ overflow: hidden;
12
+ background-image: url("images/galaxy.png");
13
+ background-size: cover;
14
+ background-position: 0 -170px;
15
+ text-align: center;
16
+ color: white;
17
+ padding: 50px 0;
18
+ }
19
+
20
+ #hero h2 {
21
+ font-weight: 300;
22
+ margin: 0 20px;
23
+ margin-top: 15px;
24
+ }
25
+
26
+ #hero img {
27
+ height: 100px;
28
+ max-width: 80%;
29
+ height: auto;
30
+ }
31
+
32
+ .view-on .view-on-option {
33
+ padding: 11px 16px;
34
+ border-radius: 8px;
35
+ background: white;
36
+ color: black;
37
+ text-decoration: none;
38
+ font-family: "Lato";
39
+ font-size: 16px;
40
+ display: inline-flex;
41
+ align-items: center;
42
+ }
43
+
44
+ .view-on .view-on-option svg {
45
+ margin-right: 8px;
46
+ }
47
+
48
+ #content {
49
+ max-width: 700px;
50
+ margin: auto;
51
+ margin-top: 30px;
52
+ padding: 0 30px;
53
+ padding-bottom: 30px;
54
+ }
55
+
56
+ #demo #demo-images .image-container {
57
+ width: 150px;
58
+ height: 150px;
59
+ border-radius: 5px;
60
+ overflow: hidden;
61
+ cursor: pointer;
62
+ margin-bottom: 20px;
63
+ }
64
+
65
+ #demo #demo-images .image-container.selected {
66
+ box-shadow: 0 0 0 6px #1e85ff;
67
+ }
68
+
69
+ #demo #demo-images .image-container img {
70
+ width: 150px;
71
+ height: 150px;
72
+ }
73
+
74
+ #demo {
75
+ display: flex;
76
+ }
77
+
78
+ #arrow {
79
+ margin: 20px;
80
+ display: flex;
81
+ align-items: center;
82
+ }
83
+
84
+ #arrow::before {
85
+ content: "\2192";
86
+ font-size: 35px;
87
+ }
88
+
89
+ #demo-result {
90
+ flex: 1;
91
+ border: 3px solid #ededed;
92
+ border-radius: 5px;
93
+ background-color: #f7f7f7;
94
+ padding: 20px;
95
+ box-sizing: border-box;
96
+
97
+ height: 320px;
98
+ overflow: auto;
99
+ }
100
+
101
+ #demo-result canvas {
102
+ max-width: 100%;
103
+ }
104
+
105
+ code {
106
+ background-color: #f1f1f1 !important;
107
+ border-radius: 5px;
108
+ }
109
+
110
+ /* content: 2193 when screen smol */
111
+
112
+ @media only screen and (max-width: 600px) {
113
+ #demo{
114
+ display: block;
115
+ }
116
+
117
+ #demo-images{
118
+ display: flex;
119
+ justify-content: center;
120
+ }
121
+
122
+ #demo #demo-images .image-container, #demo #demo-images .image-container img{
123
+ height: 100px;
124
+ width: 100px;
125
+ }
126
+
127
+ #demo #demo-images .image-container:first-of-type{
128
+ margin-right: 50px;
129
+ }
130
+
131
+ #demo-title{
132
+ text-align: center;
133
+ }
134
+ #arrow{
135
+ margin-top: 0;
136
+ justify-content: center;
137
+ }
138
+
139
+ #arrow::before{
140
+ content: "\2193";
141
+ }
142
+ }
@@ -0,0 +1,106 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <script async src="https://www.googletagmanager.com/gtag/js?id=G-32CWY3SB1G"></script><script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","G-32CWY3SB1G")</script>
6
+ <meta charset="UTF-8" />
7
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
+ <title>jscanify - Javascript mobile document scanner</title>
10
+ <meta name="description" content="Open-source pure Javascript implemented mobile document scanner." />
11
+ <meta property="og:title" content="jscanify" />
12
+ <meta property="og:description" content="Open-source pure Javascript implemented mobile document scanner." />
13
+ <meta property="og:url" content="https://colonelparrot.github.io/jscanify/" />
14
+ <meta property="og:image" content="https://colonelparrot.github.io/jscanify/images/logo.png" />
15
+ <meta property="og:locale" content="en_US" />
16
+ <link rel="icon" type="image/x-icon" href="favicon.ico" />
17
+ <link rel="stylesheet" href="index.css" />
18
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/a11y-light.min.css"
19
+ integrity="sha512-WDk6RzwygsN9KecRHAfm9HTN87LQjqdygDmkHSJxVkVI7ErCZ8ZWxP6T8RvBujY1n2/E4Ac+bn2ChXnp5rnnHA=="
20
+ crossorigin="anonymous" referrerpolicy="no-referrer" async />
21
+ </head>
22
+
23
+ <body>
24
+ <div id="hero">
25
+ <img src="images/logo-full-small.png" />
26
+ <h2>Open-source pure Javascript implemented mobile document scanner.</h2>
27
+ <br />
28
+ <div class="view-on">
29
+ <a class="view-on-option" href="https://github.com/ColonelParrot/jscanify" target="_blank" style="margin: 10px">
30
+ <svg width="16" height="16" aria-hidden="true" viewBox="0 0 16 16">
31
+ <path fill-rule="evenodd"
32
+ 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">
33
+ </path>
34
+ </svg>
35
+ View on Github
36
+ </a>
37
+ <a class="view-on-option" href="https://nodei.co/npm/jscanify/" target="_blank">
38
+ <svg xmlns="http://www.w3.org/2000/svg" width="41" height="16" viewBox="0 0 256 100"
39
+ preserveAspectRatio="xMinYMin meet">
40
+ <path d="M0 0v85.498h71.166V99.83H128V85.498h128V0H0z" fill="#CB3837"></path>
41
+ <path
42
+ d="M42.502 14.332h-28.17v56.834h28.17V28.664h14.332v42.502h14.332V14.332H42.502zM85.498 14.332v71.166h28.664V71.166h28.17V14.332H85.498zM128 56.834h-13.838v-28.17H128v28.17zM184.834 14.332h-28.17v56.834h28.17V28.664h14.332v42.502h14.332V28.664h14.332v42.502h14.332V14.332h-57.328z"
43
+ fill="#FFF"></path>
44
+ </svg>
45
+ View on npm
46
+ </a>
47
+ </div>
48
+ </div>
49
+ <div id="content">
50
+ <b>jscanify</b> is an open-source pure Javascript implemented mobile
51
+ document scanner designed to run in any Javascript environment
52
+ <u>for free</u>. <br /><br />
53
+ <b>jscanify</b> is capable of detecting & highlighting documents in an
54
+ image, as well as undistorting it. It is fast and easy to learn.<br /><br />
55
+ It can run in the <b>browser</b> or on a server with <b>NodeJS</b>.
56
+ <br /><br />
57
+ <hr />
58
+ <div id="demo-title">
59
+ <h1 style="margin-bottom: 0">Demo</h1>
60
+ <p style="margin-top: 0">Select an image below to scan</p>
61
+ </div>
62
+ <div id="demo">
63
+ <div id="demo-images">
64
+ <div class="image-container">
65
+ <img src="images/test/test.png" />
66
+ </div>
67
+ <div class="image-container" style="margin-bottom: 0">
68
+ <img src="images/test/test2.avif" />
69
+ </div>
70
+ </div>
71
+ <div id="arrow"></div>
72
+ <div id="demo-result">
73
+ Scan results will appear here
74
+ </div>
75
+ </div>
76
+ <br /><br />
77
+ <hr />
78
+ <h1>Installation</h1>
79
+ <pre><code class="language-js">$ npm i jscanify
80
+ import jscanify from 'jscanify'</code></pre>
81
+ OR
82
+ <pre><code class="language-html">&lt;script src=&quot;https://docs.opencv.org/4.7.0/opencv.js&quot; async&gt;&lt;/script&gt;
83
+ &lt;script src=&quot;https://cdn.jsdelivr.net/gh/ColonelParrot/jscanify@master/src/jscanify.min.js&quot;&gt;&lt;/script&gt;</code></pre>
84
+ <h1>Usage</h1>
85
+ <pre><code class="language-js">const scanner = new jscanify();
86
+ const paperWidth = 500;
87
+ const paperHeight = 1000;
88
+ image.onload = function () {
89
+ scanner.extractPaper(image, paperWidth, paperHeight, (resultCanvas) => {
90
+ document.body.appendChild(resultCanvas);
91
+ });
92
+ };</code></pre>
93
+ It's that easy! Come check out the <a href="https://github.com/ColonelParrot/jscanify/wiki" target="_blank">documentation</a>!
94
+ </div>
95
+ <script src="../src/jscanify.js"></script>
96
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
97
+ <script src="script.js"></script>
98
+
99
+
100
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"
101
+ integrity="sha512-bgHRAiTjGrzHzLyKOnpFvaEpGzJet3z4tZnXGjpsCcqOnAH6VGUx9frc5bcIhKTVLEiCO6vEhNAgx5jtLUYrfA=="
102
+ crossorigin="anonymous" referrerpolicy="no-referrer"></script>
103
+ <script>hljs.highlightAll();</script>
104
+ </body>
105
+
106
+ </html>
package/docs/script.js ADDED
@@ -0,0 +1,42 @@
1
+ let loadedOpenCV = false
2
+
3
+ const openCvURL = "https://docs.opencv.org/4.7.0/opencv.js"
4
+
5
+ function loadOpenCV(onComplete) {
6
+ if (loadedOpenCV) {
7
+ onComplete()
8
+ } else {
9
+ $('#demo-result').text('Loading OpenCV...')
10
+ const script = document.createElement("script")
11
+ script.src = openCvURL
12
+
13
+ script.onload = function () {
14
+ setTimeout(function () {
15
+ onComplete()
16
+ }, 1000)
17
+ loadedOpenCV = true
18
+ }
19
+ document.body.appendChild(script)
20
+ }
21
+ }
22
+
23
+ const scanner = new jscanify()
24
+ $('#demo-images .image-container').click(function () {
25
+ $('.image-container.selected').removeClass('selected')
26
+ $(this).addClass('selected')
27
+ const imageSrc = $(this).find('img')[0].src
28
+ console.log(imageSrc)
29
+ loadOpenCV(function () {
30
+ $('#demo-result').empty()
31
+
32
+ const newImg = document.createElement("img")
33
+ newImg.src = imageSrc
34
+
35
+ scanner.extractPaper(newImg, 386, 500, (resultCanvas) => {
36
+ $('#demo-result').append(resultCanvas);
37
+
38
+ const highlightedCanvas = scanner.highlightPaper(newImg)
39
+ $('#demo-result').append(highlightedCanvas);
40
+ });
41
+ })
42
+ })
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "jscanify",
3
+ "version": "1.0.0",
4
+ "description": "Open-source Javascript mobile document scanner.",
5
+ "main": "src/jscanify.js",
6
+ "directories": {
7
+ "doc": "docs"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/ColonelParrot/jscanify.git"
15
+ },
16
+ "keywords": [
17
+ "js",
18
+ "scanner",
19
+ "document-scanner"
20
+ ],
21
+ "author": "ColonelParrot",
22
+ "license": "MIT",
23
+ "bugs": {
24
+ "url": "https://github.com/ColonelParrot/jscanify/issues"
25
+ },
26
+ "homepage": "https://colonelparrot.github.io/jscanify/"
27
+ }
@@ -0,0 +1,275 @@
1
+ /*! jscanify v1.0.0 | (c) ColonelParrot and other contributors | MIT License */
2
+
3
+ (function (global, factory) {
4
+ typeof exports === "object" && typeof module !== "undefined"
5
+ ? (module.exports = factory())
6
+ : typeof define === "function" && define.amd
7
+ ? define(factory)
8
+ : (global.jscanify = factory());
9
+ })(this, function () {
10
+ "use strict";
11
+
12
+ /**
13
+ * Calculates distance between two points. Each point must have `x` and `y` property
14
+ * @param {*} p1 point 1
15
+ * @param {*} p2 point 2
16
+ * @returns distance between two points
17
+ */
18
+ function distance(p1, p2) {
19
+ return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
20
+ }
21
+
22
+ class jscanify {
23
+ constructor() { }
24
+
25
+ /**
26
+ * Finds the contour of the paper within the image
27
+ * @param {*} img image to process
28
+ * @returns the biggest contour inside the image
29
+ */
30
+ findPaperContour(img) {
31
+ const imgGray = new cv.Mat();
32
+ cv.cvtColor(img, imgGray, cv.COLOR_RGBA2GRAY);
33
+
34
+ const imgBlur = new cv.Mat();
35
+ cv.GaussianBlur(
36
+ imgGray,
37
+ imgBlur,
38
+ new cv.Size(5, 5),
39
+ 0,
40
+ 0,
41
+ cv.BORDER_DEFAULT
42
+ );
43
+
44
+ const imgThresh = new cv.Mat();
45
+ cv.threshold(
46
+ imgBlur,
47
+ imgThresh,
48
+ 0,
49
+ 255,
50
+ cv.THRESH_BINARY + cv.THRESH_OTSU
51
+ );
52
+
53
+ let contours = new cv.MatVector();
54
+ let hierarchy = new cv.Mat();
55
+
56
+ cv.findContours(
57
+ imgThresh,
58
+ contours,
59
+ hierarchy,
60
+ cv.RETR_CCOMP,
61
+ cv.CHAIN_APPROX_SIMPLE
62
+ );
63
+ let maxArea = 0;
64
+ let maxContourIndex = -1;
65
+ for (let i = 0; i < contours.size(); ++i) {
66
+ let contourArea = cv.contourArea(contours.get(i));
67
+ if (contourArea > maxArea) {
68
+ maxArea = contourArea;
69
+ maxContourIndex = i;
70
+ }
71
+ }
72
+
73
+ const maxContour = contours.get(maxContourIndex);
74
+
75
+ imgGray.delete();
76
+ imgBlur.delete();
77
+ imgThresh.delete();
78
+ contours.delete();
79
+ hierarchy.delete();
80
+ return maxContour;
81
+ }
82
+
83
+ /**
84
+ * Highlights the paper detected inside the image.
85
+ * @param {*} image image to process
86
+ * @param {*} options options for highlighting. Accepts `color` and `thickness` parameter
87
+ * @returns `HTMLCanvasElement` with original image and paper highlighted
88
+ */
89
+ highlightPaper(image, options) {
90
+ options = options || {};
91
+ options.color = options.color || "orange";
92
+ options.thickness = options.thickness || 10;
93
+ const canvas = document.createElement("canvas");
94
+ const ctx = canvas.getContext("2d");
95
+ const img = cv.imread(image);
96
+
97
+ const maxContour = this.findPaperContour(img);
98
+ if (maxContour) {
99
+ const {
100
+ topLeftCorner,
101
+ topRightCorner,
102
+ bottomLeftCorner,
103
+ bottomRightCorner,
104
+ } = this.getCornerPoints(maxContour, img);
105
+
106
+ cv.imshow(canvas, img);
107
+
108
+ if (
109
+ topLeftCorner &&
110
+ topRightCorner &&
111
+ bottomLeftCorner &&
112
+ bottomRightCorner
113
+ ) {
114
+ ctx.strokeStyle = options.color;
115
+ ctx.lineWidth = options.thickness;
116
+ ctx.beginPath();
117
+ ctx.moveTo(...Object.values(topLeftCorner));
118
+ ctx.lineTo(...Object.values(topRightCorner));
119
+ ctx.lineTo(...Object.values(bottomRightCorner));
120
+ ctx.lineTo(...Object.values(bottomLeftCorner));
121
+ ctx.lineTo(...Object.values(topLeftCorner));
122
+ ctx.stroke();
123
+ }
124
+ }
125
+
126
+ img.delete();
127
+ return canvas;
128
+ }
129
+
130
+ /**
131
+ * Extracts and undistorts the image detected within the frame.
132
+ * @param {*} image image to process
133
+ * @param {*} resultWidth desired result paper width
134
+ * @param {*} resultHeight desired result paper height
135
+ * @param {*} onComplete callback with `HTMLCanvasElement` passed - the unwarped paper
136
+ * @param {*} cornerPoints optional custom corner points, in case automatic corner points are incorrect
137
+ */
138
+ extractPaper(image, resultWidth, resultHeight, onComplete, cornerPoints) {
139
+ const canvas = document.createElement("canvas");
140
+
141
+ const img = cv.imread(image);
142
+
143
+ const maxContour = this.findPaperContour(img);
144
+
145
+ const {
146
+ topLeftCorner,
147
+ topRightCorner,
148
+ bottomLeftCorner,
149
+ bottomRightCorner,
150
+ } = cornerPoints || this.getCornerPoints(maxContour, img);
151
+ let warpedDst = new cv.Mat();
152
+
153
+ let dsize = new cv.Size(resultWidth, resultHeight);
154
+ let srcTri = cv.matFromArray(4, 1, cv.CV_32FC2, [
155
+ topLeftCorner.x,
156
+ topLeftCorner.y,
157
+ topRightCorner.x,
158
+ topRightCorner.y,
159
+ bottomLeftCorner.x,
160
+ bottomLeftCorner.y,
161
+ bottomRightCorner.x,
162
+ bottomRightCorner.y,
163
+ ]);
164
+
165
+ let dstTri = cv.matFromArray(4, 1, cv.CV_32FC2, [
166
+ 0,
167
+ 0,
168
+ resultWidth,
169
+ 0,
170
+ 0,
171
+ resultHeight,
172
+ resultWidth,
173
+ resultHeight,
174
+ ]);
175
+
176
+ let M = cv.getPerspectiveTransform(srcTri, dstTri);
177
+ cv.warpPerspective(
178
+ img,
179
+ warpedDst,
180
+ M,
181
+ dsize,
182
+ cv.INTER_LINEAR,
183
+ cv.BORDER_CONSTANT,
184
+ new cv.Scalar()
185
+ );
186
+
187
+ cv.imshow(canvas, warpedDst);
188
+
189
+ const newImg = document.createElement("img");
190
+ newImg.src = canvas.toDataURL();
191
+ newImg.onload = function () {
192
+ // flip unwarped image
193
+
194
+ let ctx = canvas.getContext("2d");
195
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
196
+ canvas.width = resultWidth;
197
+ canvas.height = resultHeight;
198
+ ctx.setTransform(1, 0, 0, -1, 0, canvas.height);
199
+
200
+ ctx.drawImage(newImg, 0, 0);
201
+
202
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
203
+
204
+ img.delete();
205
+ warpedDst.delete();
206
+ onComplete(canvas);
207
+ };
208
+ }
209
+
210
+ /**
211
+ * Calculates the corner points of a contour.
212
+ * @param {*} contour contour from {@link findPaperContour}
213
+ * @returns object with properties `topLeftCorner`, `topRightCorner`, `bottomLeftCorner`, `bottomRightCorner`, each with `x` and `y` property
214
+ */
215
+ getCornerPoints(contour) {
216
+ let rect = cv.minAreaRect(contour);
217
+ const center = rect.center;
218
+
219
+ let topLeftCorner;
220
+ let topLeftCornerDist = 0;
221
+
222
+ let topRightCorner;
223
+ let topRightCornerDist = 0;
224
+
225
+ let bottomLeftCorner;
226
+ let bottomLeftCornerDist = 0;
227
+
228
+ let bottomRightCorner;
229
+ let bottomRightCornerDist = 0;
230
+
231
+ for (let i = 0; i < contour.data32S.length; i += 2) {
232
+ const point = { x: contour.data32S[i], y: contour.data32S[i + 1] };
233
+ const dist = distance(point, center);
234
+ if (point.x < center.x && point.y > center.y) {
235
+ // top left
236
+ if (dist > topLeftCornerDist) {
237
+ topLeftCorner = point;
238
+ topLeftCornerDist = dist;
239
+ }
240
+ } else if (point.x > center.x && point.y > center.y) {
241
+ // top right
242
+ if (dist > topRightCornerDist) {
243
+ topRightCorner = point;
244
+ topRightCornerDist = dist;
245
+ }
246
+ } else if (point.x < center.x && point.y < center.y) {
247
+ // bottom left
248
+ if (dist > bottomLeftCornerDist) {
249
+ bottomLeftCorner = point;
250
+ bottomLeftCornerDist = dist;
251
+ }
252
+ } else if (point.x > center.x && point.y < center.y) {
253
+ // bottom right
254
+ if (dist > bottomRightCornerDist) {
255
+ bottomRightCorner = point;
256
+ bottomRightCornerDist = dist;
257
+ }
258
+ }
259
+ }
260
+
261
+ return {
262
+ topLeftCorner,
263
+ topRightCorner,
264
+ bottomLeftCorner,
265
+ bottomRightCorner,
266
+ };
267
+ }
268
+
269
+ }
270
+
271
+ if (typeof module !== "undefined") {
272
+ module.exports = { jscanify };
273
+ }
274
+ return jscanify;
275
+ });