Compare commits
36 Commits
master
...
v1.0.0-bet
Author | SHA1 | Date |
---|---|---|
![]() |
0d1d2a0b32 | 9 years ago |
![]() |
9221529e16 | 9 years ago |
![]() |
23a3481f7d | 9 years ago |
![]() |
31d2a086cd | 9 years ago |
![]() |
2dca130bee | 9 years ago |
![]() |
031995dae1 | 9 years ago |
![]() |
e42fc6d221 | 9 years ago |
![]() |
cb8502da30 | 9 years ago |
![]() |
5ee361bcff | 9 years ago |
![]() |
435430b8fa | 9 years ago |
![]() |
bad79020a9 | 9 years ago |
![]() |
7236bb7b53 | 9 years ago |
![]() |
5d4aa1b519 | 9 years ago |
![]() |
0b1f1dc3bd | 9 years ago |
![]() |
d6f1ed59ad | 9 years ago |
![]() |
308544c0dd | 9 years ago |
![]() |
45331c0d8e | 9 years ago |
![]() |
5231824d98 | 9 years ago |
![]() |
b9efb07456 | 9 years ago |
![]() |
ec8f4377c1 | 9 years ago |
![]() |
e7c62221c1 | 9 years ago |
![]() |
2f2ed46cf3 | 9 years ago |
![]() |
be70d72e0b | 9 years ago |
![]() |
5c2295e103 | 9 years ago |
![]() |
1f3ff9bc70 | 9 years ago |
![]() |
bc2d8c5f5e | 9 years ago |
![]() |
6c3772eda3 | 9 years ago |
![]() |
b44dd76a07 | 9 years ago |
![]() |
c277903dac | 9 years ago |
![]() |
19f74e6106 | 9 years ago |
![]() |
f1a963f4a0 | 9 years ago |
![]() |
f8bc6a705c | 9 years ago |
![]() |
12a39e28e5 | 9 years ago |
![]() |
633eabe86c | 9 years ago |
![]() |
b733c2b7fd | 9 years ago |
![]() |
f3f656f3e7 | 9 years ago |
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
|
||||
|
||||
<title>index</title>
|
||||
<meta name="description" content=""/>
|
||||
<meta name="author" content="Christoph Oberhofer"/>
|
||||
|
||||
<meta name="viewport" content="width=device-width; initial-scale=1.0"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<section id="container" class="container">
|
||||
<div id="interactive" class="viewport"></div>
|
||||
<div id="debug" class="detection"></div>
|
||||
</section>
|
||||
<script src="../dist/quagga.js" type="text/javascript"></script>
|
||||
<script src="api-test.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,67 @@
|
||||
console.log(typeof Quagga);
|
||||
|
||||
|
||||
// creates a new instance!
|
||||
var code128Scanner = Quagga
|
||||
.decoder({readers: ['code_128_reader']})
|
||||
.locator({patchSize: 'medium'});
|
||||
|
||||
var eanScanner = Quagga
|
||||
.decoder({readers: ['ean_reader']})
|
||||
.locator({patchSize: 'medium'});
|
||||
|
||||
var i2of5Scanner = Quagga
|
||||
.decoder({readers: ['i2of5_reader']})
|
||||
.locator({patchSize: 'small', halfSample: false});
|
||||
|
||||
/* eanScanner
|
||||
.fromImage('../test/fixtures/ean/image-001.jpg', {size: 640})
|
||||
.toPromise().then((result) => {
|
||||
console.log(result.codeResult.code);
|
||||
}).catch(() => {
|
||||
console.log("EAN not found!");
|
||||
});
|
||||
|
||||
i2of5Scanner
|
||||
.fromImage('../test/fixtures/i2of5/image-001.jpg', {size: 800})
|
||||
.addEventListener('detected', (result) => {
|
||||
console.log("Detected: " + result.codeResult.code);
|
||||
})
|
||||
.addEventListener('processed', (result) => {
|
||||
console.log("Image Processed");
|
||||
})
|
||||
.start(); */
|
||||
|
||||
/* imageReader.addEventListener('processed', (result) => {
|
||||
console.log(result);
|
||||
}); */
|
||||
|
||||
// or
|
||||
|
||||
// uses same canvas?
|
||||
// queue image requests?
|
||||
/*imageReader = customScanner
|
||||
.fromImage('../test/fixtures/ean/image-002.jpg', {size: 640}); */
|
||||
|
||||
/*imageReader.addEventListener('processed', (result) => {
|
||||
console.log(result.codeResult.code);
|
||||
});*/
|
||||
|
||||
code128Scanner = code128Scanner
|
||||
.config({frequency: 2})
|
||||
.fromVideo({
|
||||
constraints: {
|
||||
width: 800,
|
||||
height: 600,
|
||||
facingMode: "environment"
|
||||
}
|
||||
});
|
||||
|
||||
code128Scanner
|
||||
.addEventListener('detected', (result) => {
|
||||
console.log(result);
|
||||
})
|
||||
.addEventListener('processed', result => {
|
||||
console.log("Processed");
|
||||
})
|
||||
.start();
|
@ -1,16 +0,0 @@
|
||||
@charset "UTF-8";
|
||||
/* LESS - http://lesscss.org style sheet */
|
||||
/* Palette color codes */
|
||||
/* Palette URL: http://paletton.com/#uid=31g0q0kHZAviRSkrHLOGomVNzac */
|
||||
/* Feel free to copy&paste color codes to your application */
|
||||
/* MIXINS */
|
||||
/* As hex codes */
|
||||
/* Main Primary color */
|
||||
/* Main Secondary color (1) */
|
||||
/* Main Secondary color (2) */
|
||||
/* As RGBa codes */
|
||||
/* Main Primary color */
|
||||
/* Main Secondary color (1) */
|
||||
/* Main Secondary color (2) */
|
||||
/* Generated by Paletton.com ├é┬® 2002-2014 */
|
||||
/* http://paletton.com */
|
@ -1 +1,31 @@
|
||||
@import url("https://fonts.googleapis.com/css?family=Ubuntu:400,700|Cabin+Condensed:400,600");
|
||||
|
||||
@font-face {
|
||||
font-family: 'icomoon';
|
||||
src: url('../fonts/icomoon.eot?tad2ln');
|
||||
src: url('../fonts/icomoon.eot?tad2ln#iefix') format('embedded-opentype'),
|
||||
url('../fonts/icomoon.ttf?tad2ln') format('truetype'),
|
||||
url('../fonts/icomoon.woff?tad2ln') format('woff'),
|
||||
url('../fonts/icomoon.svg?tad2ln#icomoon') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
[class^="icon-"]:before, [class*=" icon-"]:before {
|
||||
/* use !important to prevent issues with browser extensions that change fonts */
|
||||
font-family: 'icomoon' !important;
|
||||
speak: none;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
|
||||
/* Better Font Rendering =========== */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-barcode:before {
|
||||
content: "\e937";
|
||||
}
|
||||
|
@ -0,0 +1,139 @@
|
||||
/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+jsx */
|
||||
/**
|
||||
* prism.js default theme for JavaScript, CSS and HTML
|
||||
* Based on dabblet (http://dabblet.com)
|
||||
* @author Lea Verou
|
||||
*/
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: black;
|
||||
background: none;
|
||||
text-shadow: 0 1px white;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection, code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
@media print {
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #f5f2f0;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #905;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #690;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: #a67f59;
|
||||
background: hsla(0, 0%, 100%, .5);
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: #07a;
|
||||
}
|
||||
|
||||
.token.function {
|
||||
color: #DD4A68;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
@ -0,0 +1,122 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
|
||||
<title>index</title>
|
||||
<meta name="description" content="" />
|
||||
<meta name="author" content="Christoph Oberhofer" />
|
||||
|
||||
<meta name="viewport" content="width=device-width; initial-scale=1.0" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/fonts.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/styles.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/prism.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<div class="headline">
|
||||
<h1>QuaggaJS</h1>
|
||||
<h2>An advanced barcode-scanner written in JavaScript</h2>
|
||||
</div>
|
||||
</header>
|
||||
<section id="container" class="container">
|
||||
<h3>Scan barcode to input-field</h3>
|
||||
<p>Click the <strong>button</strong> next to the input-field
|
||||
to select a file or snap a picture</p>
|
||||
<div>
|
||||
<form>
|
||||
<div class="input-field">
|
||||
<label for="isbn_input">EAN:</label>
|
||||
<input id="isbn_input" class="isbn" type="text" />
|
||||
<button type="button" class="icon-barcode button scan"> </button>
|
||||
<input type="file" id="file" capture/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<p>This example demonstrates the following features:
|
||||
<ul>
|
||||
<li>Use static image as source</li>
|
||||
<li>Configuring EAN-Reader</li>
|
||||
<li>Use custom mount-point (Query-Selector)</li>
|
||||
</ul>
|
||||
</p>
|
||||
<div class="source-code">
|
||||
<h4>Source</h4>
|
||||
<div class="collapsable-source">
|
||||
<pre>
|
||||
<code class="language-javascript">
|
||||
var Quagga = window.Quagga;
|
||||
var App = {
|
||||
_scanner: null,
|
||||
init: function() {
|
||||
this.attachListeners();
|
||||
},
|
||||
decode: function(src) {
|
||||
Quagga
|
||||
.decoder({readers: ['ean_reader']})
|
||||
.locator({patchSize: 'medium'})
|
||||
.fromImage(src, {size: 800})
|
||||
.toPromise()
|
||||
.then(function(result) {
|
||||
document.querySelector('input.isbn').value = result.codeResult.code;
|
||||
})
|
||||
.catch(function() {
|
||||
document.querySelector('input.isbn').value = "Not Found";
|
||||
})
|
||||
.then(function() {
|
||||
this.attachListeners();
|
||||
}.bind(this));
|
||||
},
|
||||
attachListeners: function() {
|
||||
var self = this,
|
||||
button = document.querySelector('.input-field input + .button.scan'),
|
||||
fileInput = document.querySelector('.input-field input[type=file]');
|
||||
|
||||
button.addEventListener("click", function onClick(e) {
|
||||
e.preventDefault();
|
||||
button.removeEventListener("click", onClick);
|
||||
document.querySelector('.input-field input[type=file]').click();
|
||||
});
|
||||
|
||||
fileInput.addEventListener("change", function onChange(e) {
|
||||
e.preventDefault();
|
||||
fileInput.removeEventListener("change", onChange);
|
||||
if (e.target.files && e.target.files.length) {
|
||||
self.decode(URL.createObjectURL(e.target.files[0]));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
App.init();
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
<div class="collapsable-source">
|
||||
<pre>
|
||||
<code class="language-html">
|
||||
<form>
|
||||
<div class="input-field">
|
||||
<label for="isbn_input">EAN:</label>
|
||||
<input id="isbn_input" class="isbn" type="text" />
|
||||
<button type="button" class="icon-barcode button scan">&nbsp;</button>
|
||||
<input type="file" id="file" capture/>
|
||||
</div>
|
||||
</form>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer>
|
||||
<p>
|
||||
© Copyright by Christoph Oberhofer
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<script src="../../dist/quagga.js" type="text/javascript"></script>
|
||||
<script src="index.js" type="text/javascript"></script>
|
||||
<script src="../vendor/prism.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,43 @@
|
||||
var Quagga = window.Quagga;
|
||||
var App = {
|
||||
_scanner: null,
|
||||
init: function() {
|
||||
this.attachListeners();
|
||||
},
|
||||
decode: function(file) {
|
||||
Quagga
|
||||
.decoder({readers: ['ean_reader']})
|
||||
.locator({patchSize: 'medium'})
|
||||
.fromSource(file, {size: 800})
|
||||
.toPromise()
|
||||
.then(function(result) {
|
||||
document.querySelector('input.isbn').value = result.codeResult.code;
|
||||
})
|
||||
.catch(function() {
|
||||
document.querySelector('input.isbn').value = "Not Found";
|
||||
})
|
||||
.then(function() {
|
||||
this.attachListeners();
|
||||
}.bind(this));
|
||||
},
|
||||
attachListeners: function() {
|
||||
var self = this,
|
||||
button = document.querySelector('.input-field input + .button.scan'),
|
||||
fileInput = document.querySelector('.input-field input[type=file]');
|
||||
|
||||
button.addEventListener("click", function onClick(e) {
|
||||
e.preventDefault();
|
||||
button.removeEventListener("click", onClick);
|
||||
document.querySelector('.input-field input[type=file]').click();
|
||||
});
|
||||
|
||||
fileInput.addEventListener("change", function onChange(e) {
|
||||
e.preventDefault();
|
||||
fileInput.removeEventListener("change", onChange);
|
||||
if (e.target.files && e.target.files.length) {
|
||||
self.decode(e.target.files[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
App.init();
|
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Generated by IcoMoon</metadata>
|
||||
<defs>
|
||||
<font id="icomoon" horiz-adv-x="1024">
|
||||
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
||||
<missing-glyph horiz-adv-x="1024" />
|
||||
<glyph unicode=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" glyph-name="barcode" d="M0 832h128v-640h-128zM192 832h64v-640h-64zM320 832h64v-640h-64zM512 832h64v-640h-64zM768 832h64v-640h-64zM960 832h64v-640h-64zM640 832h32v-640h-32zM448 832h32v-640h-32zM864 832h32v-640h-32zM0 128h64v-64h-64zM192 128h64v-64h-64zM320 128h64v-64h-64zM640 128h64v-64h-64zM960 128h64v-64h-64zM768 128h128v-64h-128zM448 128h128v-64h-128z" />
|
||||
</font></defs></svg>
|
After Width: | Height: | Size: 821 B |
@ -0,0 +1,80 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
|
||||
<title>index</title>
|
||||
<meta name="description" content="" />
|
||||
<meta name="author" content="Christoph Oberhofer" />
|
||||
|
||||
<meta name="viewport" content="width=device-width; initial-scale=1.0" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/fonts.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/styles.css" />
|
||||
<link rel="stylesheet" type="text/css" href="./styles.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/prism.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<div class="headline">
|
||||
<h1>QuaggaJS</h1>
|
||||
<h2>An advanced barcode-scanner written in JavaScript</h2>
|
||||
</div>
|
||||
</header>
|
||||
<section id="container" class="container">
|
||||
<div class="controls">
|
||||
<h3>Scan various barcodes continously</h3>
|
||||
<button type="button" class="icon-barcode button scan">Start Scanning</button>
|
||||
<div class="readers">
|
||||
<label>
|
||||
<span>EAN-13</span>
|
||||
<input type="checkbox" checked name="ean_reader" />
|
||||
</label>
|
||||
<label>
|
||||
<span>EAN-8</span>
|
||||
<input type="checkbox" name="ean_8_reader" />
|
||||
</label>
|
||||
<label>
|
||||
<span>UPC-E</span>
|
||||
<input type="checkbox" name="upc_e_reader" />
|
||||
</label>
|
||||
<label>
|
||||
<span>Code 39</span>
|
||||
<input type="checkbox" checked name="code_39_reader" />
|
||||
</label>
|
||||
<label>
|
||||
<span>Codabar</span>
|
||||
<input type="checkbox" name="codabar_reader" />
|
||||
</label>
|
||||
<label>
|
||||
<span>Code 128</span>
|
||||
<input type="checkbox" checked name="code_128_reader" />
|
||||
</label>
|
||||
<label>
|
||||
<span>Interleaved 2 of 5</span>
|
||||
<input type="checkbox" name="i2of5_reader" />
|
||||
</label>
|
||||
</div>
|
||||
<p>
|
||||
Be aware that not all combinations play together nicely. Some
|
||||
codes do not have a checksum (e.g. ITF).
|
||||
</p>
|
||||
</div>
|
||||
<div class="overlay overlay--inline">
|
||||
<div class="overlay__content">
|
||||
<div class="overlay__close">X</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<ul class="results"></ul>
|
||||
|
||||
<script src="../../dist/quagga.js" type="text/javascript"></script>
|
||||
<script src="index.js" type="text/javascript"></script>
|
||||
<script src="../vendor/prism.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -0,0 +1,96 @@
|
||||
var Quagga = window.Quagga;
|
||||
var App = {
|
||||
_lastResult: null,
|
||||
init: function() {
|
||||
this.attachListeners();
|
||||
},
|
||||
activateScanner: function() {
|
||||
var scanner = this.configureScanner('.overlay__content'),
|
||||
onDetected = function (result) {
|
||||
this.addToResults(result);
|
||||
}.bind(this),
|
||||
stop = function() {
|
||||
scanner.stop(); // should also clear all event-listeners?
|
||||
scanner.removeEventListener('detected', onDetected);
|
||||
this.hideOverlay();
|
||||
this.attachListeners();
|
||||
}.bind(this);
|
||||
|
||||
this.showOverlay(stop);
|
||||
console.log("activateScanner");
|
||||
scanner.addEventListener('detected', onDetected).start();
|
||||
},
|
||||
addToResults: function(result) {
|
||||
if (this._lastResult === result.codeResult.code) {
|
||||
return;
|
||||
}
|
||||
this._lastResult = result.codeResult.code;
|
||||
var results = document.querySelector('ul.results'),
|
||||
li = document.createElement('li'),
|
||||
format = document.createElement('span'),
|
||||
code = document.createElement('span');
|
||||
|
||||
li.className = "result";
|
||||
format.className = "format";
|
||||
code.className = "code";
|
||||
|
||||
li.appendChild(format);
|
||||
li.appendChild(code);
|
||||
|
||||
format.appendChild(document.createTextNode(result.codeResult.format));
|
||||
code.appendChild(document.createTextNode(result.codeResult.code));
|
||||
|
||||
results.insertBefore(li, results.firstChild);
|
||||
},
|
||||
attachListeners: function() {
|
||||
var button = document.querySelector('button.scan'),
|
||||
self = this;
|
||||
|
||||
button.addEventListener("click", function clickListener (e) {
|
||||
e.preventDefault();
|
||||
button.removeEventListener("click", clickListener);
|
||||
self.activateScanner();
|
||||
});
|
||||
},
|
||||
showOverlay: function(cancelCb) {
|
||||
document.querySelector('.container .controls')
|
||||
.classList.add('hide');
|
||||
document.querySelector('.overlay--inline')
|
||||
.classList.add('show');
|
||||
var closeButton = document.querySelector('.overlay__close');
|
||||
closeButton.addEventListener('click', function closeHandler() {
|
||||
closeButton.removeEventListener("click", closeHandler);
|
||||
cancelCb();
|
||||
});
|
||||
},
|
||||
hideOverlay: function() {
|
||||
document.querySelector('.container .controls')
|
||||
.classList.remove('hide');
|
||||
document.querySelector('.overlay--inline')
|
||||
.classList.remove('show');
|
||||
},
|
||||
querySelectedReaders: function() {
|
||||
return Array.prototype.slice.call(document.querySelectorAll('.readers input[type=checkbox]'))
|
||||
.filter(function(element) {
|
||||
return !!element.checked;
|
||||
})
|
||||
.map(function(element) {
|
||||
return element.getAttribute("name");
|
||||
});
|
||||
},
|
||||
configureScanner: function(selector) {
|
||||
var scanner = Quagga
|
||||
.decoder({readers: this.querySelectedReaders()})
|
||||
.locator({patchSize: 'medium'})
|
||||
.fromSource({
|
||||
target: selector,
|
||||
constraints: {
|
||||
width: 600,
|
||||
height: 600,
|
||||
facingMode: "environment"
|
||||
}
|
||||
});
|
||||
return scanner;
|
||||
}
|
||||
};
|
||||
App.init();
|
@ -0,0 +1,140 @@
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 0;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body > header {
|
||||
flex: 0 0 auto;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
header > .headline {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.headline h1, .headline h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.headline h2 {
|
||||
font-size: 1.3rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
body > .container {
|
||||
margin: 0;
|
||||
flex: 1 1 auto;
|
||||
overflow-y: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Controls */
|
||||
|
||||
/* Scan! Button */
|
||||
|
||||
.icon-barcode.scan {
|
||||
font-size: x-large;
|
||||
padding: 0.5rem;
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.icon-barcode.scan:before {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
/* Reader Checkboxes */
|
||||
|
||||
.controls .readers {
|
||||
font-size: 1.3rem;
|
||||
width: 70%;
|
||||
margin: 2rem auto 0;
|
||||
}
|
||||
|
||||
.controls .readers label {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.controls .readers input {
|
||||
flex: 0 0 auto;
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.controls .readers span {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
/* Results */
|
||||
|
||||
body > .results {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
background-color: #DDD;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
padding: 0.25rem;
|
||||
overflow-x: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
border-top: 1px solid #777;
|
||||
}
|
||||
|
||||
.results > li {
|
||||
flex: 0 1 auto;
|
||||
margin: 0.25rem;
|
||||
background: #fff;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
|
||||
}
|
||||
|
||||
.results:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.result {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.result > .format {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.result > .code {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Scanner */
|
||||
|
||||
.container .controls.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.overlay--inline {
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.overlay--inline.show {
|
||||
display: block;
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
var Quagga = require('../lib/quagga').default;
|
||||
|
||||
var buffer = require('fs').readFileSync('../test/fixtures/code_128/image-001.jpg');
|
||||
|
||||
decode(buffer);
|
||||
function decode(buff){
|
||||
Quagga.decodeSingle({
|
||||
src: buff,
|
||||
numOfWorkers: 0,
|
||||
inputStream: {
|
||||
mime: "image/jpeg",
|
||||
size: 800,
|
||||
area: {
|
||||
top: "10%",
|
||||
right: "5%",
|
||||
left: "5%",
|
||||
bottom: "10%"
|
||||
}
|
||||
}
|
||||
}, function(result) {
|
||||
if (result.codeResult) {
|
||||
console.log("result", result.codeResult.code);
|
||||
} else {
|
||||
console.log("not detected");
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"plugins": ["transform-class-properties"],
|
||||
"presets": ["es2015-webpack", "stage-0", "react"]
|
||||
}
|
@ -0,0 +1 @@
|
||||
static/
|
@ -0,0 +1,9 @@
|
||||
# Quagga meets React
|
||||
|
||||
This example uses webpack & babel to transpile/bundle the required files.
|
||||
|
||||
## How to run the example
|
||||
|
||||
```console
|
||||
> npm install && webpack
|
||||
```
|
@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
|
||||
<title>index</title>
|
||||
<meta name="description" content="" />
|
||||
<meta name="author" content="Christoph Oberhofer" />
|
||||
|
||||
<meta name="viewport" content="width=device-width; initial-scale=1.0" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/fonts.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/styles.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/prism.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<div class="headline">
|
||||
<h1>QuaggaJS</h1>
|
||||
<h2>An advanced barcode-scanner written in JavaScript</h2>
|
||||
</div>
|
||||
</header>
|
||||
<section id="container" class="container">
|
||||
<h3>Scan barcode to input-field</h3>
|
||||
<p>Click the <strong>button</strong> next to the input-field
|
||||
to start scanning an <strong>EAN-13</strong> barcode</p>
|
||||
<div id="react">
|
||||
|
||||
</div>
|
||||
<p>This example demonstrates the following features:
|
||||
<ul>
|
||||
<li>Access to user's camera</li>
|
||||
<li>Configuring EAN-Reader</li>
|
||||
<li>Use custom mount-point (Query-Selector)</li>
|
||||
<li>Start/Stop scanning based on certain events</li>
|
||||
</ul>
|
||||
</p>
|
||||
</section>
|
||||
<footer>
|
||||
<p>
|
||||
© Copyright by Christoph Oberhofer
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<script src="static/bundle.js" type="text/javascript"></script>
|
||||
<script src="../vendor/prism.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "quagga-react",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Christoph Oberhofer <ch.oberhofer@gmail.com>",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.9.0",
|
||||
"babel-loader": "^6.2.4",
|
||||
"babel-plugin-transform-class-properties": "^6.9.0",
|
||||
"babel-preset-es2015-webpack": "^6.4.1",
|
||||
"babel-preset-react": "^6.5.0",
|
||||
"babel-preset-stage-0": "^6.5.0",
|
||||
"webpack": "^2.1.0-beta.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^15.0.2",
|
||||
"react-dom": "^15.0.2"
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import Scanner from './Scanner';
|
||||
|
||||
export default class App extends React.Component {
|
||||
state = {
|
||||
scanning: false
|
||||
};
|
||||
|
||||
_startScanning = (e) => {
|
||||
e.preventDefault();
|
||||
if (!this.state.scanning) {
|
||||
this.setState({scanning: true});
|
||||
}
|
||||
};
|
||||
|
||||
_stopScanning = () => {
|
||||
this.setState({scanning: false});
|
||||
};
|
||||
|
||||
_handleResult = (result) => {
|
||||
this._stopScanning();
|
||||
this.refs.eanInput.value = result.codeResult.code;
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<form>
|
||||
<div className="input-field">
|
||||
<label>EAN:</label>
|
||||
<input className="ean" ref="eanInput" type="text" />
|
||||
<button type="button" onClick={this._startScanning} className="icon-barcode button scan"></button>
|
||||
</div>
|
||||
</form>
|
||||
{this.state.scanning &&
|
||||
<Scanner onDetected={this._handleResult} onCancel={this._stopScanning} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import Quagga from '../../../../dist/quagga';
|
||||
|
||||
export default class Scanner extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._scanner = Quagga
|
||||
.decoder({readers: ['ean_reader']})
|
||||
.locator({patchSize: 'medium'})
|
||||
.fromSource({
|
||||
target: '.overlay__content',
|
||||
constraints: {
|
||||
width: 800,
|
||||
height: 600,
|
||||
facingMode: "environment"
|
||||
}
|
||||
});
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
}
|
||||
_onCancel(e) {
|
||||
e.preventDefault();
|
||||
this.props.onCancel();
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div className="overlay">
|
||||
<div className="overlay__content">
|
||||
<div className="overlay__close" onClick={this._onCancel}>X</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
componentDidMount() {
|
||||
this._scanner
|
||||
.addEventListener('detected', this.props.onDetected)
|
||||
.start();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._scanner
|
||||
.removeEventListener('detected', this.props.onDetected)
|
||||
.stop();
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './components/App';
|
||||
|
||||
|
||||
ReactDOM.render(
|
||||
<App />,
|
||||
document.getElementById('react')
|
||||
);
|
@ -0,0 +1,23 @@
|
||||
module.exports = {
|
||||
entry: [
|
||||
'./src/index.js'
|
||||
],
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /(node_modules|quagga\.js)/,
|
||||
loader: 'babel-loader'
|
||||
}]
|
||||
},
|
||||
resolve: {
|
||||
modules: [
|
||||
'node_modules'
|
||||
]
|
||||
},
|
||||
output: {
|
||||
path: __dirname + '/static',
|
||||
publicPath: '/',
|
||||
filename: 'bundle.js'
|
||||
}
|
||||
};
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"plugins": ["transform-class-properties"],
|
||||
"presets": ["es2015-webpack", "stage-0", "react"]
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": "./node_modules/eslint-config-netconomy/react.js",
|
||||
"parser": "babel-eslint",
|
||||
"rules": {
|
||||
"comma-dangle": [2, "always-multiline"],
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
static/*.js*
|
@ -0,0 +1,9 @@
|
||||
# Quagga meets React
|
||||
|
||||
This example uses webpack & babel to transpile/bundle the required files.
|
||||
|
||||
## How to run the example
|
||||
|
||||
```console
|
||||
> npm install && webpack
|
||||
```
|
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
|
||||
<title>index</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<meta name="description" content="" />
|
||||
<meta name="author" content="Christoph Oberhofer" />
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="static/style.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
|
||||
<link href='https://fonts.googleapis.com/css?family=Roboto:400,300,500' rel='stylesheet' type='text/css'>
|
||||
|
||||
</head>
|
||||
|
||||
<body style="margin: 0">
|
||||
<div id="react"></div>
|
||||
<script src="static/bundle.min.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"short_name": "Quagga Sandbox",
|
||||
"name": "Sandbox for Barcode-Scanning with Quagga",
|
||||
"icons": [
|
||||
{
|
||||
"src": "static/icon.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"start_url": "index.html",
|
||||
"display": "standalone",
|
||||
"orientation": "portrait"
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "quagga-react",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build:watch": "webpack --config webpack.config.js --watch",
|
||||
"build:dev": "webpack --config webpack.config.js",
|
||||
"build:prod": "webpack --config webpack.config.min.js"
|
||||
},
|
||||
"author": "Christoph Oberhofer <ch.oberhofer@gmail.com>",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.10.4",
|
||||
"babel-eslint": "^6.1.2",
|
||||
"babel-loader": "^6.2.4",
|
||||
"babel-plugin-transform-class-properties": "^6.10.2",
|
||||
"babel-preset-es2015-webpack": "^6.4.1",
|
||||
"babel-preset-react": "^6.11.1",
|
||||
"babel-preset-stage-0": "^6.5.0",
|
||||
"eslint": "^3.1.1",
|
||||
"eslint-config-netconomy": "^300.0.1",
|
||||
"eslint-plugin-react": "^5.2.2",
|
||||
"json-loader": "^0.5.4",
|
||||
"webpack": "^2.1.0-beta.18"
|
||||
},
|
||||
"dependencies": {
|
||||
"material-ui": "^0.15.2",
|
||||
"react": "^15.2.1",
|
||||
"react-dom": "^15.2.1",
|
||||
"react-tap-event-plugin": "^1.0.0"
|
||||
}
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
import React from 'react';
|
||||
import AppBar from 'material-ui/AppBar';
|
||||
import Drawer from 'material-ui/Drawer';
|
||||
import FloatingActionButton from 'material-ui/FloatingActionButton';
|
||||
import Dialog from 'material-ui/Dialog';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import {Card, CardText} from 'material-ui/Card';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import TuneIcon from 'material-ui/svg-icons/image/tune';
|
||||
|
||||
import Scanner from './Scanner';
|
||||
import ScanIcon from './ScanIcon';
|
||||
import ScannedCode from './ScannedCode';
|
||||
import ConfigView from './ConfigView';
|
||||
import {persist, load} from '../utils/Storage';
|
||||
|
||||
const cleanConfig = config => {
|
||||
if (typeof config.inputStream.constraints.deviceId === 'number') {
|
||||
config.inputStream.constraints.deviceId = null;
|
||||
}
|
||||
return config;
|
||||
};
|
||||
|
||||
const defaultConfig = {
|
||||
frequency: 5,
|
||||
numOfWorkers: 2,
|
||||
locate: true,
|
||||
inputStream: {
|
||||
name: "Live",
|
||||
type: "LiveStream",
|
||||
constraints: {
|
||||
width: 800,
|
||||
height: 600,
|
||||
deviceId: 0,
|
||||
facingMode: "environment",
|
||||
},
|
||||
area: {
|
||||
top: "0%",
|
||||
right: "0%",
|
||||
left: "0%",
|
||||
bottom: "0%",
|
||||
},
|
||||
},
|
||||
decoder: {
|
||||
readers: [
|
||||
'ean_reader',
|
||||
'code_39_reader',
|
||||
'code_128_reader',
|
||||
],
|
||||
},
|
||||
locator: {
|
||||
halfSample: true,
|
||||
patchSize: "medium",
|
||||
},
|
||||
};
|
||||
|
||||
export default class App extends React.Component {
|
||||
state = {
|
||||
drawerOpen: false,
|
||||
scanning: false,
|
||||
currentView: 'root',
|
||||
config: load("config") || defaultConfig,
|
||||
scannedCodes: load('scannedCodes') || [],
|
||||
};
|
||||
|
||||
_handleToggle = () => {
|
||||
this.setState({drawerOpen: !this.state.drawerOpen});
|
||||
}
|
||||
_handleClose = () => this.setState({drawerOpen: false});
|
||||
_onRequestChange = drawerOpen => {
|
||||
this.setState({drawerOpen});
|
||||
};
|
||||
|
||||
_startScanning = (e) => {
|
||||
e.preventDefault();
|
||||
if (!this.state.scanning) {
|
||||
this.setState({scanning: true});
|
||||
}
|
||||
};
|
||||
|
||||
_stopScanning = () => {
|
||||
this.setState({scanning: false});
|
||||
};
|
||||
|
||||
_handleResult = (result) => {
|
||||
this._stopScanning();
|
||||
const scannedCodes =
|
||||
[{
|
||||
angle: result.angle,
|
||||
box: result.box,
|
||||
codeResult: {
|
||||
code: result.codeResult.code,
|
||||
direction: result.codeResult.direction,
|
||||
format: result.codeResult.format,
|
||||
},
|
||||
line: result.line,
|
||||
}]
|
||||
.concat(this.state.scannedCodes);
|
||||
this.setState({scannedCodes});
|
||||
persist('scannedCodes', scannedCodes);
|
||||
}
|
||||
|
||||
_navigateTo = (route) => {
|
||||
this.setState({
|
||||
drawerOpen: false,
|
||||
currentView: route,
|
||||
});
|
||||
}
|
||||
|
||||
_handleConfigChange = config => {
|
||||
this.setState({config: cleanConfig(config)});
|
||||
persist("config", config);
|
||||
}
|
||||
|
||||
_handleDelete = (scannedCode) => {
|
||||
const index = this.state.scannedCodes.indexOf(scannedCode);
|
||||
if (index !== -1) {
|
||||
const newArray = this.state.scannedCodes.slice();
|
||||
newArray.splice(index, 1);
|
||||
this.setState({scannedCodes: newArray});
|
||||
persist('scannedCodes', newArray);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Drawer
|
||||
docked={false}
|
||||
open={this.state.drawerOpen}
|
||||
onRequestChange={this._onRequestChange}
|
||||
>
|
||||
<ConfigView config={this.state.config} onChange={this._handleConfigChange} />
|
||||
</Drawer>
|
||||
<AppBar
|
||||
style={{position: 'fixed', top: '0px'}}
|
||||
title="QuaggaJS"
|
||||
iconElementLeft={<IconButton onTouchTap={this._handleToggle}><TuneIcon /></IconButton>}
|
||||
onLeftIconButtonTouchTap={this._handleToggle}
|
||||
/>
|
||||
<Dialog
|
||||
style={{paddingTop: '0px'}}
|
||||
bodyStyle={{padding: '0.5rem'}}
|
||||
repositionOnUpdate={false}
|
||||
actions={[
|
||||
<FlatButton
|
||||
label="Cancel"
|
||||
primary={true}
|
||||
onTouchTap={this._stopScanning}/>,
|
||||
]}
|
||||
modal={true}
|
||||
contentStyle={{width: '95%', maxWidth: '95%', height: '95%', maxHeight: '95%'}}
|
||||
open={this.state.scanning}
|
||||
>
|
||||
<Scanner
|
||||
config={this.state.config}
|
||||
onDetected={this._handleResult}
|
||||
onCancel={this._stopScanning} />
|
||||
</Dialog>
|
||||
<div style={{paddingTop: '64px'}}>
|
||||
{this.state.currentView === 'root' && this.state.scannedCodes.length === 0 &&
|
||||
<Card style={{margin: '0.5em 0.25em 0em'}}>
|
||||
<CardText>
|
||||
Nothing scanned yet
|
||||
</CardText>
|
||||
</Card>
|
||||
}
|
||||
{this.state.currentView === 'root' && this.state.scannedCodes.map((scannedCode, i) => (
|
||||
<ScannedCode
|
||||
key={i}
|
||||
scannedCode={scannedCode}
|
||||
onDelete={this._handleDelete.bind(this, scannedCode)} />
|
||||
))}
|
||||
</div>
|
||||
<FloatingActionButton
|
||||
secondary={true}
|
||||
onMouseDown={this._startScanning}
|
||||
style={{position: 'fixed', right: 0, bottom: 0, margin: '0 1em 1em 0'}}
|
||||
>
|
||||
<ScanIcon />
|
||||
</FloatingActionButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
|
||||
export default (Component) => (class ConfigOption extends React.Component {
|
||||
static propTypes = {
|
||||
path: React.PropTypes.array.isRequired,
|
||||
onChange: React.PropTypes.func,
|
||||
onToggle: React.PropTypes.func,
|
||||
};
|
||||
|
||||
_handleChange = (event, key, payload) => {
|
||||
this.props.onChange(event, key, payload, this.props.path);
|
||||
}
|
||||
|
||||
_handleToggle = (event, value) => {
|
||||
this.props.onToggle(event, value, this.props.path);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
path,
|
||||
onChange,
|
||||
onToggle,
|
||||
...rest,
|
||||
} = this.props;
|
||||
if (onChange) {
|
||||
return <Component {...rest} onChange={this._handleChange} />;
|
||||
} else if (onToggle) {
|
||||
return <Component {...rest} onToggle={this._handleToggle} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
@ -0,0 +1,193 @@
|
||||
import React from 'react';
|
||||
import {List, ListItem} from 'material-ui/List';
|
||||
import Subheader from 'material-ui/Subheader';
|
||||
import Toggle from 'material-ui/Toggle';
|
||||
import SelectField from 'material-ui/SelectField';
|
||||
import MenuItem from 'material-ui/MenuItem';
|
||||
import createConfigOption from './ConfigOption';
|
||||
|
||||
let configSections = [{
|
||||
name: "Constraints",
|
||||
path: ["inputStream", "constraints"],
|
||||
options: {
|
||||
width: [[640, "640px"], [800, "800px"], [1280, "1280px"], [1920, "1920px"]],
|
||||
height: [[480, "480px"], [600, "600px"], [720, "720px"], [1080, "1080px"]],
|
||||
facingMode: [["user", "user"], ["environment", "environment"]],
|
||||
aspectRatio: [[1, '1/1'], [4 / 3, '4/3'], [16 / 9, '16/9'], [21 / 9, '21/9']],
|
||||
deviceId: [],
|
||||
},
|
||||
}, {
|
||||
name: "General",
|
||||
path: [],
|
||||
options: {
|
||||
locate: true,
|
||||
numOfWorkers: [[0, "0"], [1, "1"], [2, "2"], [4, "4"], [8, "8"]],
|
||||
frequency: [[0, "full"], [1, "1Hz"], [2, "2Hz"], [5, "5Hz"],
|
||||
[10, "10Hz"], [15, "15Hz"], [20, "20Hz"]],
|
||||
},
|
||||
}, {
|
||||
name: "Reader",
|
||||
path: ["decoder", "readers"],
|
||||
options: {
|
||||
ean_reader: false,
|
||||
ean_8_reader: false,
|
||||
upc_e_reader: false,
|
||||
code_39_reader: false,
|
||||
codabar_reader: false,
|
||||
code_128_reader: false,
|
||||
i2of5_reader: false,
|
||||
},
|
||||
}, {
|
||||
name: "Locator",
|
||||
path: ["locator"],
|
||||
options: {
|
||||
halfSample: true,
|
||||
patchSize: [
|
||||
["x-small", "x-small"],
|
||||
["small", "small"],
|
||||
["medium", "medium"],
|
||||
["large", "large"],
|
||||
["x-large", "x-large"],
|
||||
],
|
||||
},
|
||||
}];
|
||||
|
||||
const selectStyle = {
|
||||
};
|
||||
|
||||
const selectedItemStyle = {
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
};
|
||||
|
||||
const reducer = function reduce(state, path, value) {
|
||||
if (path.length === 1 && typeof value === 'boolean' && Array.isArray(state)) {
|
||||
const index = state.indexOf(path[0]);
|
||||
if (index !== -1 && value || index === -1 && !value) {
|
||||
return state;
|
||||
} else if (index !== -1 && !value) {
|
||||
const newArray = state.slice();
|
||||
newArray.splice(index, 1);
|
||||
return newArray;
|
||||
} else {
|
||||
return state.concat([path[0]]);
|
||||
}
|
||||
}
|
||||
if (path.length === 0) {
|
||||
return value;
|
||||
}
|
||||
const [part, ...rest] = path;
|
||||
return Object.assign({}, state, {[part]: reduce(state[part], rest, value)});
|
||||
};
|
||||
|
||||
const get = function get(object, path) {
|
||||
if (Array.isArray(object) && (typeof path[0] === 'string')) {
|
||||
return object.indexOf(path[0]) !== -1;
|
||||
}
|
||||
if (path.length === 0) {
|
||||
return object;
|
||||
}
|
||||
const [part, ...rest] = path;
|
||||
return get(object[part], rest);
|
||||
};
|
||||
|
||||
const getConfigByPath = function(object, path) {
|
||||
return get(object, ['config'].concat(path));
|
||||
};
|
||||
|
||||
const SelectConfigOption = createConfigOption(SelectField);
|
||||
const ToggleConfigOption = createConfigOption(Toggle);
|
||||
|
||||
export default class ConfigView extends React.Component {
|
||||
static propTypes = {
|
||||
onChange: React.PropTypes.func,
|
||||
config: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
devicesFetched: false,
|
||||
};
|
||||
|
||||
handleChange = (event, index, value, path) => {
|
||||
const newState = reducer(this.props.config, path, value);
|
||||
this.props.onChange(newState);
|
||||
}
|
||||
|
||||
handleToggle = (event, value, path) => {
|
||||
return this.handleChange(event, 0, value, path);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
navigator.mediaDevices
|
||||
.enumerateDevices()
|
||||
.then((devices) => {
|
||||
const videoDevices = devices
|
||||
.filter(info => info.kind === 'videoinput')
|
||||
.map(videoDevice => ([
|
||||
videoDevice.deviceId,
|
||||
videoDevice.label,
|
||||
]));
|
||||
const constraints = configSections
|
||||
.map((section, index) => (section.name === "Constraints" ? {section, index} : null))
|
||||
.filter(sectionIndex => !!sectionIndex)[0];
|
||||
configSections = configSections.slice();
|
||||
constraints.section = {
|
||||
...constraints.section,
|
||||
options: {
|
||||
...constraints.section.options,
|
||||
deviceId: [[0, "no preference"]].concat(videoDevices),
|
||||
},
|
||||
};
|
||||
configSections[constraints.index] = constraints.section;
|
||||
this.setState({devicesFetched: true});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<div>{
|
||||
configSections.map(section => (
|
||||
<List>
|
||||
<Subheader>{section.name}</Subheader>
|
||||
{Object.keys(section.options).map(option => {
|
||||
const path = section.path.concat([option]);
|
||||
if (typeof section.options[option] === 'boolean') {
|
||||
return (
|
||||
<ListItem
|
||||
key={option}
|
||||
path={path}
|
||||
primaryText={option}
|
||||
rightToggle={
|
||||
<ToggleConfigOption
|
||||
onToggle={this.handleToggle}
|
||||
path={path}
|
||||
toggled={!!getConfigByPath(this.props, path)} />
|
||||
}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div style={{paddingLeft: 16, paddingRight: 16}}>
|
||||
<SelectConfigOption
|
||||
fullWidth={true}
|
||||
key={option}
|
||||
path={path}
|
||||
style={selectStyle}
|
||||
labelStyle={selectedItemStyle}
|
||||
value={getConfigByPath(this.props, path)}
|
||||
onChange={this.handleChange}
|
||||
floatingLabelText={option}
|
||||
>
|
||||
{section.options[option].map(([value, label]) => (
|
||||
<MenuItem key={value} value={value} primaryText={label} />
|
||||
))}
|
||||
</SelectConfigOption>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</List>
|
||||
))
|
||||
}</div>);
|
||||
}
|
||||
};
|
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import pure from 'recompose/pure';
|
||||
import SvgIcon from 'material-ui/SvgIcon';
|
||||
|
||||
let ActionScanBarcode = (props) => (
|
||||
<SvgIcon {...props}>
|
||||
<path transform="translate(2, -3)" d="M0 4h4v20h-4zM6 4h2v20h-2zM10 4h2v20h-2zM16 4h2v20h-2zM24 4h2v20h-2zM30 4h2v20h-2zM20 4h1v20h-1zM14 4h1v20h-1zM27 4h1v20h-1zM0 26h2v2h-2zM6 26h2v2h-2zM10 26h2v2h-2zM20 26h2v2h-2zM30 26h2v2h-2zM24 26h4v2h-4zM14 26h4v2h-4z"/>
|
||||
</SvgIcon>
|
||||
);
|
||||
|
||||
ActionScanBarcode = pure(ActionScanBarcode);
|
||||
ActionScanBarcode.displayName = 'ActionAccessibility';
|
||||
ActionScanBarcode.muiName = 'SvgIcon';
|
||||
|
||||
export default ActionScanBarcode;
|
@ -0,0 +1,99 @@
|
||||
import React from 'react';
|
||||
import {Card, CardActions, CardText, CardHeader} from 'material-ui/Card';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import DeleteIcon from 'material-ui/svg-icons/action/delete';
|
||||
import Divider from 'material-ui/Divider';
|
||||
|
||||
const pointGenerator = points => i => `(${points[i][0].toFixed(0)}, ${points[i][1].toFixed(0)})`;
|
||||
const lineGenerator = line => i => `(${line[i].x.toFixed(0)}, ${line[i].y.toFixed(0)})`;
|
||||
|
||||
const renderLineSegments = items => (
|
||||
<div style={{display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', flexBasis: '100%'}}>
|
||||
{items.map((item, i) => (
|
||||
<div key={i} style={{width: `30%`, textAlign: 'center', padding: '0.2rem'}} >{item}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderBox = box => {
|
||||
const p = pointGenerator(box);
|
||||
const items = [p(1), `→`, p(2), `↑`, ' ', `↓`, p(0), `←`, p(3)];
|
||||
return renderLineSegments(items);
|
||||
};
|
||||
|
||||
const renderLine = line => {
|
||||
const l = lineGenerator(line);
|
||||
const items = [l(0), `→`, l(1)];
|
||||
return renderLineSegments(items);
|
||||
};
|
||||
|
||||
const renderAngle = angle => (
|
||||
<span>
|
||||
{(angle * 180 / Math.PI).toFixed(2)}
|
||||
</span>
|
||||
);
|
||||
|
||||
const renderDirection = direction => (
|
||||
<span>
|
||||
{direction === -1 ? 'forward' : `reverse`}
|
||||
</span>
|
||||
);
|
||||
|
||||
const keyStyle = {
|
||||
fontWeight: 'bold',
|
||||
flex: '0 1 70px',
|
||||
};
|
||||
|
||||
const lineStyle = {
|
||||
marginBottom: '0.5rem',
|
||||
marginTop: '0.5rem',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
};
|
||||
|
||||
const ScannedCode = ({scannedCode, onDelete}) => {
|
||||
return (
|
||||
<Card
|
||||
style={{margin: '0.5em 0.25em 0em'}}>
|
||||
<CardHeader
|
||||
textStyle={{paddingRight: '20px', maxWidth: '100%', boxSizing: 'border-box'}}
|
||||
titleStyle={{fontSize: '18px', wordWrap: 'break-word'}}
|
||||
title={scannedCode.codeResult.code}
|
||||
subtitle={scannedCode.codeResult.format}
|
||||
actAsExpander={true}
|
||||
showExpandableButton={true}
|
||||
/>
|
||||
<CardText expandable={true}>
|
||||
<div style={lineStyle}>
|
||||
<div style={keyStyle}>Direction: </div><div>{renderDirection(scannedCode.codeResult.direction)}</div>
|
||||
</div>
|
||||
<Divider />
|
||||
<div style={lineStyle}>
|
||||
<div style={keyStyle}>Angle: </div><div>{renderAngle(scannedCode.angle)} deg</div>
|
||||
</div>
|
||||
<Divider />
|
||||
<div style={lineStyle}>
|
||||
<div style={keyStyle}>Line: </div>{renderLine(scannedCode.line)}
|
||||
</div>
|
||||
<Divider />
|
||||
<div style={lineStyle}>
|
||||
<div style={keyStyle}>Box: </div>
|
||||
{renderBox(scannedCode.box)}
|
||||
</div>
|
||||
</CardText>
|
||||
<CardActions expandable={true}>
|
||||
<FlatButton
|
||||
label=""
|
||||
style={{minWidth: '36px', color: '#aaa'}}
|
||||
onClick={onDelete} icon={<DeleteIcon />} />
|
||||
</CardActions>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
ScannedCode.propTypes = {
|
||||
scannedCode: React.PropTypes.object.isRequired,
|
||||
onDelete: React.PropTypes.func,
|
||||
};
|
||||
|
||||
export default ScannedCode;
|
@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import Quagga from '../../../../dist/quagga';
|
||||
|
||||
export default class Scanner extends React.Component {
|
||||
static propTypes = {
|
||||
onDetected: React.PropTypes.func,
|
||||
onCancel: React.PropTypes.func,
|
||||
config: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._scanner = Quagga
|
||||
.config(props.config)
|
||||
.fromSource({
|
||||
...this.props.config.inputStream,
|
||||
target: '.overlay__content',
|
||||
});
|
||||
}
|
||||
|
||||
_onCancel = (e) => {
|
||||
e.preventDefault();
|
||||
if (this._scanUntilResult) {
|
||||
this._scanUntilResult.cancel();
|
||||
this._scanUntilResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._scanUntilResult = this._scanner.toPromise();
|
||||
this._scanUntilResult.promise
|
||||
.then(this.props.onDetected)
|
||||
.catch(this.props.onCancel);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="overlay__content" />
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._scanner
|
||||
.removeEventListener('detected', this.props.onDetected)
|
||||
.stop();
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './components/App';
|
||||
import getMuiTheme from 'material-ui/styles/getMuiTheme';
|
||||
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
|
||||
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||
|
||||
injectTapEventPlugin();
|
||||
|
||||
ReactDOM.render(
|
||||
<MuiThemeProvider muiTheme={getMuiTheme()}>
|
||||
<App />
|
||||
</MuiThemeProvider>,
|
||||
document.getElementById('react')
|
||||
);
|
@ -0,0 +1,35 @@
|
||||
function storageWrapper(cb) {
|
||||
if (typeof window.localStorage !== 'undefined') {
|
||||
return cb(window.localStorage);
|
||||
} else {
|
||||
console.log("localStorage not available");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function persist(key, object) {
|
||||
storageWrapper((storage) => {
|
||||
storage.setItem(key, JSON.stringify(object));
|
||||
});
|
||||
};
|
||||
|
||||
export function push(key, object) {
|
||||
storageWrapper((storage) => {
|
||||
const item = storage.getItem() || "[]",
|
||||
parsed = JSON.parse(item);
|
||||
if (Array.isArray(parsed)) {
|
||||
parsed.push(object);
|
||||
storage.setItem(key, JSON.stringify(parsed));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function load(key) {
|
||||
return storageWrapper((storage) => {
|
||||
const item = storage.getItem(key);
|
||||
if (item) {
|
||||
return JSON.parse(item);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
After Width: | Height: | Size: 5.9 KiB |
@ -0,0 +1,20 @@
|
||||
html {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.overlay__content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.overlay__content video, .overlay__content canvas {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.overlay__content canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-width: 100%;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
module.exports = {
|
||||
entry: [
|
||||
'./src/index.js'
|
||||
],
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /(node_modules|quagga\.js)/,
|
||||
loader: 'babel-loader'
|
||||
}]
|
||||
},
|
||||
resolve: {
|
||||
modules: [
|
||||
'node_modules'
|
||||
]
|
||||
},
|
||||
output: {
|
||||
path: __dirname + '/static',
|
||||
publicPath: '/',
|
||||
filename: 'bundle.js'
|
||||
}
|
||||
};
|
@ -0,0 +1,44 @@
|
||||
var webpack = require('webpack'),
|
||||
path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: ['./src/index.js'],
|
||||
output: {
|
||||
path: __dirname + '/static',
|
||||
publicPath: '/',
|
||||
filename: 'bundle.min.js'
|
||||
},
|
||||
resolve: {
|
||||
modules: [
|
||||
'node_modules'
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {NODE_ENV: '"production"'}
|
||||
}),
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
minimize: true,
|
||||
debug: false
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
},
|
||||
output: {
|
||||
comments: /@preserve/
|
||||
},
|
||||
sourceMap: false
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /(node_modules|quagga\.js)/,
|
||||
loader: 'babel-loader',
|
||||
}, {
|
||||
test: /\.json$/,
|
||||
loader: "json-loader",
|
||||
}],
|
||||
},
|
||||
};
|
@ -0,0 +1,159 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
|
||||
<title>index</title>
|
||||
<meta name="description" content="" />
|
||||
<meta name="author" content="Christoph Oberhofer" />
|
||||
|
||||
<meta name="viewport" content="width=device-width; initial-scale=1.0" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/fonts.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/styles.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/prism.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<div class="headline">
|
||||
<h1>QuaggaJS</h1>
|
||||
<h2>An advanced barcode-scanner written in JavaScript</h2>
|
||||
</div>
|
||||
</header>
|
||||
<section id="container" class="container">
|
||||
<h3>Scan barcode to input-field</h3>
|
||||
<p>Click the <strong>button</strong> next to the input-field
|
||||
to start scanning an <strong>EAN-13</strong> barcode</p>
|
||||
<div>
|
||||
<form>
|
||||
<div class="input-field">
|
||||
<label for="isbn_input">EAN:</label>
|
||||
<input id="isbn_input" class="isbn" type="text" />
|
||||
<button type="button" class="icon-barcode button scan"> </button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<p>This example demonstrates the following features:
|
||||
<ul>
|
||||
<li>Access to user's camera</li>
|
||||
<li>Configuring EAN-Reader</li>
|
||||
<li>Use custom mount-point (Query-Selector)</li>
|
||||
<li>Start/Stop scanning based on certain events</li>
|
||||
</ul>
|
||||
</p>
|
||||
<div class="source-code">
|
||||
<h4>Source</h4>
|
||||
<div class="collapsable-source">
|
||||
<pre>
|
||||
<code class="language-javascript">
|
||||
var Quagga = window.Quagga;
|
||||
var App = {
|
||||
_scanner: null,
|
||||
init: function() {
|
||||
this.attachListeners();
|
||||
},
|
||||
activateScanner: function() {
|
||||
var scanner = this.configureScanner('.overlay__content'),
|
||||
onDetected = function (result) {
|
||||
document.querySelector('input.isbn').value = result.codeResult.code;
|
||||
stop();
|
||||
}.bind(this),
|
||||
stop = function() {
|
||||
scanner.stop(); // should also clear all event-listeners?
|
||||
scanner.removeEventListener('detected', onDetected);
|
||||
this.hideOverlay();
|
||||
this.attachListeners();
|
||||
}.bind(this);
|
||||
|
||||
this.showOverlay(stop);
|
||||
scanner.addEventListener('detected', onDetected).start();
|
||||
},
|
||||
attachListeners: function() {
|
||||
var self = this,
|
||||
button = document.querySelector('.input-field input + button.scan');
|
||||
|
||||
button.addEventListener("click", function onClick(e) {
|
||||
e.preventDefault();
|
||||
button.removeEventListener("click", onClick);
|
||||
self.activateScanner();
|
||||
});
|
||||
},
|
||||
showOverlay: function(cancelCb) {
|
||||
if (!this._overlay) {
|
||||
var content = document.createElement('div'),
|
||||
closeButton = document.createElement('div');
|
||||
|
||||
closeButton.appendChild(document.createTextNode('X'));
|
||||
content.className = 'overlay__content';
|
||||
closeButton.className = 'overlay__close';
|
||||
this._overlay = document.createElement('div');
|
||||
this._overlay.className = 'overlay';
|
||||
this._overlay.appendChild(content);
|
||||
content.appendChild(closeButton);
|
||||
closeButton.addEventListener('click', function closeClick() {
|
||||
closeButton.removeEventListener('click', closeClick);
|
||||
cancelCb();
|
||||
});
|
||||
document.body.appendChild(this._overlay);
|
||||
} else {
|
||||
var closeButton = document.querySelector('.overlay__close');
|
||||
closeButton.addEventListener('click', function closeClick() {
|
||||
closeButton.removeEventListener('click', closeClick);
|
||||
cancelCb();
|
||||
});
|
||||
}
|
||||
this._overlay.style.display = "block";
|
||||
},
|
||||
hideOverlay: function() {
|
||||
if (this._overlay) {
|
||||
this._overlay.style.display = "none";
|
||||
}
|
||||
},
|
||||
configureScanner: function(selector) {
|
||||
if (!this._scanner) {
|
||||
this._scanner = Quagga
|
||||
.decoder({readers: ['ean_reader']})
|
||||
.locator({patchSize: 'medium'})
|
||||
.fromVideo({
|
||||
target: selector,
|
||||
constraints: {
|
||||
width: 800,
|
||||
height: 600,
|
||||
facingMode: "environment"
|
||||
}
|
||||
});
|
||||
}
|
||||
return this._scanner;
|
||||
}
|
||||
};
|
||||
App.init();
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
<div class="collapsable-source">
|
||||
<pre>
|
||||
<code class="language-html">
|
||||
<form>
|
||||
<div class="input-field">
|
||||
<label for="isbn_input">EAN:</label>
|
||||
<input id="isbn_input" class="isbn" type="text" />
|
||||
<button type="button" class="icon-barcode button scan"> </button>
|
||||
</div>
|
||||
</form>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer>
|
||||
<p>
|
||||
© Copyright by Christoph Oberhofer
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<script src="../../dist/quagga.js" type="text/javascript"></script>
|
||||
<script src="index.js" type="text/javascript"></script>
|
||||
<script src="../vendor/prism.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,81 @@
|
||||
var Quagga = window.Quagga;
|
||||
var App = {
|
||||
_scanner: null,
|
||||
init: function() {
|
||||
this.attachListeners();
|
||||
},
|
||||
activateScanner: function() {
|
||||
var scanner = this.configureScanner('.overlay__content'),
|
||||
onDetected = function (result) {
|
||||
document.querySelector('input.isbn').value = result.codeResult.code;
|
||||
stop();
|
||||
}.bind(this),
|
||||
stop = function() {
|
||||
scanner.stop(); // should also clear all event-listeners?
|
||||
scanner.removeEventListener('detected', onDetected);
|
||||
this.hideOverlay();
|
||||
this.attachListeners();
|
||||
}.bind(this);
|
||||
|
||||
this.showOverlay(stop);
|
||||
scanner.addEventListener('detected', onDetected).start();
|
||||
},
|
||||
attachListeners: function() {
|
||||
var self = this,
|
||||
button = document.querySelector('.input-field input + button.scan');
|
||||
|
||||
button.addEventListener("click", function onClick(e) {
|
||||
e.preventDefault();
|
||||
button.removeEventListener("click", onClick);
|
||||
self.activateScanner();
|
||||
});
|
||||
},
|
||||
showOverlay: function(cancelCb) {
|
||||
if (!this._overlay) {
|
||||
var content = document.createElement('div'),
|
||||
closeButton = document.createElement('div');
|
||||
|
||||
closeButton.appendChild(document.createTextNode('X'));
|
||||
content.className = 'overlay__content';
|
||||
closeButton.className = 'overlay__close';
|
||||
this._overlay = document.createElement('div');
|
||||
this._overlay.className = 'overlay';
|
||||
this._overlay.appendChild(content);
|
||||
content.appendChild(closeButton);
|
||||
closeButton.addEventListener('click', function closeClick() {
|
||||
closeButton.removeEventListener('click', closeClick);
|
||||
cancelCb();
|
||||
});
|
||||
document.body.appendChild(this._overlay);
|
||||
} else {
|
||||
var closeButton = document.querySelector('.overlay__close');
|
||||
closeButton.addEventListener('click', function closeClick() {
|
||||
closeButton.removeEventListener('click', closeClick);
|
||||
cancelCb();
|
||||
});
|
||||
}
|
||||
this._overlay.style.display = "block";
|
||||
},
|
||||
hideOverlay: function() {
|
||||
if (this._overlay) {
|
||||
this._overlay.style.display = "none";
|
||||
}
|
||||
},
|
||||
configureScanner: function(selector) {
|
||||
if (!this._scanner) {
|
||||
this._scanner = Quagga
|
||||
.decoder({readers: ['ean_reader']})
|
||||
.locator({patchSize: 'medium'})
|
||||
.fromSource({
|
||||
target: selector,
|
||||
constraints: {
|
||||
width: 800,
|
||||
height: 600,
|
||||
facingMode: "environment"
|
||||
}
|
||||
});
|
||||
}
|
||||
return this._scanner;
|
||||
}
|
||||
};
|
||||
App.init();
|
@ -1,33 +0,0 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQC9ORap3LvRegtrhRc8dLdH9Bp2QokcKEsWbtvyhtjisRRm2slK
|
||||
A6Q11McB/YTb7oImFfNaCX+7vdM1oVXVLJ0ekQaNljXG5Dy7DXEcT1V6gpN4xmZJ
|
||||
8f/KZ45VBINN0Ha74L7nS4kgImh5yvNolNr4IdlSjGf09kciFy8S3kPlGQIDAQAB
|
||||
AoGAYDlaxBCC1liY3Bl3IoA7//QrTL4zGUWIQaUoZmGag1UHifJycBf/9nv4o5N3
|
||||
b5wPRSzebofsE93JPTmI+3nPf62k5rS2xOo8swwOZc+f5/v0EnUNixD7P0jBiLVR
|
||||
B8kbMvNdNn33HuynW1/MSBFE0cfeDH2i8SVl+Z+fHYIUW10CQQD0yWB8xeM8AxYB
|
||||
/ZZWClem6gf1lQAYLzid3x51pkLqRFpX+rG251cSBUouE+kVO14j2xrBqCyyOwNu
|
||||
17eazy3DAkEAxeQdWP9b11ihKOf/kjXiczltLnBsotn6K9EEAe0QuH/6iXLm86mL
|
||||
ZiQe+TrY1GWbK3ns0sfXgNJ2aeaRkeZn8wJAWF5WedTKisCmckOEwTzslbJI+0w2
|
||||
A4UQkFWa3mgOIhpY7wfunhP35+aG+AlyDJspChKwHxdCQ3lwbNRtUPLYFwJBAK8G
|
||||
9QIbUbLlPB1/HOfH6xM4rp3NZ/idzQxmISJG+GwHHaPmUekfgyEDP7X2W4N4nsbU
|
||||
XyeLA8t32q4N9aDS5gsCQDHqhsXqnY6e4IEZrvf90l2V1PpnTKfEl/F5wye3g69G
|
||||
JN57scVUBHP/KKoyfge0fytWiQN/56KvWH+G5+N/JyA=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC2zCCAkSgAwIBAgIJALUDN95Or7XlMA0GCSqGSIb3DQEBBQUAMFMxCzAJBgNV
|
||||
BAYTAkFUMQ8wDQYDVQQIEwZTdHlyaWExDTALBgNVBAcTBEdyYXoxETAPBgNVBAoT
|
||||
CFF1YWdnYUpTMREwDwYDVQQDEwhxdWFnZ2FqczAeFw0xNzAxMDgxNjI5MjhaFw0x
|
||||
ODAxMDgxNjI5MjhaMFMxCzAJBgNVBAYTAkFUMQ8wDQYDVQQIEwZTdHlyaWExDTAL
|
||||
BgNVBAcTBEdyYXoxETAPBgNVBAoTCFF1YWdnYUpTMREwDwYDVQQDEwhxdWFnZ2Fq
|
||||
czCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvTkWqdy70XoLa4UXPHS3R/Qa
|
||||
dkKJHChLFm7b8obY4rEUZtrJSgOkNdTHAf2E2+6CJhXzWgl/u73TNaFV1SydHpEG
|
||||
jZY1xuQ8uw1xHE9VeoKTeMZmSfH/ymeOVQSDTdB2u+C+50uJICJoecrzaJTa+CHZ
|
||||
Uoxn9PZHIhcvEt5D5RkCAwEAAaOBtjCBszAdBgNVHQ4EFgQUYm5+uJVOOGiYa+Vx
|
||||
2o++VHyWkwIwgYMGA1UdIwR8MHqAFGJufriVTjhomGvlcdqPvlR8lpMCoVekVTBT
|
||||
MQswCQYDVQQGEwJBVDEPMA0GA1UECBMGU3R5cmlhMQ0wCwYDVQQHEwRHcmF6MREw
|
||||
DwYDVQQKEwhRdWFnZ2FKUzERMA8GA1UEAxMIcXVhZ2dhanOCCQC1AzfeTq+15TAM
|
||||
BgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBACyzC/CKL1mgTuNgFDuUf6u+
|
||||
YMnqlc9wcnEaFuvXnkSh6fT+qMZm188C/tlZwcWTrGmoCM0K6mX1TpHOjm8vbeXZ
|
||||
diezAVGIVN3VoHqm6yJldI2rgFI9r5BfwAWYC8XNjqnT3U6cm4k8iC7jmLC+dT9r
|
||||
Ysx2ucAF6lNHayekRmNq
|
||||
-----END CERTIFICATE-----
|
@ -1,30 +0,0 @@
|
||||
# taken from http://www.piware.de/2011/01/creating-an-https-server-in-python/
|
||||
# generate server.xml with the following command:
|
||||
# openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes
|
||||
# run as follows:
|
||||
# python simple-https-server.py
|
||||
# then in your browser, visit:
|
||||
# https://localhost:4443
|
||||
|
||||
import BaseHTTPServer, SimpleHTTPServer
|
||||
import ssl
|
||||
import sys, getopt
|
||||
|
||||
host = 'localhost'
|
||||
port = 4443
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:],"",["host=", "port="])
|
||||
except getopt.GetoptError:
|
||||
print 'simple-https-server.py --host <host> --port <port>'
|
||||
sys.exit(2)
|
||||
for opt, arg in opts:
|
||||
if opt in ("--host"):
|
||||
host = arg
|
||||
elif opt in ("--port"):
|
||||
port = int(arg)
|
||||
print 'host is ', host
|
||||
print 'port is ', port
|
||||
|
||||
httpd = BaseHTTPServer.HTTPServer((host, port), SimpleHTTPServer.SimpleHTTPRequestHandler)
|
||||
httpd.socket = ssl.wrap_socket (httpd.socket, certfile='./server.pem', server_side=True)
|
||||
httpd.serve_forever()
|
@ -0,0 +1,25 @@
|
||||
const hasWindow = typeof window !== 'undefined';
|
||||
const windowRef = hasWindow ? window : {};
|
||||
|
||||
const windowObjects = [
|
||||
"MediaStream",
|
||||
"HTMLImageElement",
|
||||
"HTMLVideoElement",
|
||||
"HTMLCanvasElement",
|
||||
"FileList",
|
||||
"File",
|
||||
"URL"
|
||||
];
|
||||
|
||||
const DOMHelper = windowObjects.reduce((result, obj) => {
|
||||
return {
|
||||
...result,
|
||||
[obj]: obj in windowRef ? windowRef[obj] : () => {}
|
||||
};
|
||||
}, {});
|
||||
|
||||
DOMHelper.setObject = (key, value) => {
|
||||
DOMHelper[key] = value;
|
||||
};
|
||||
|
||||
export default DOMHelper;
|
@ -1,17 +0,0 @@
|
||||
|
||||
export function enumerateDevices() {
|
||||
if (navigator.mediaDevices
|
||||
&& typeof navigator.mediaDevices.enumerateDevices === 'function') {
|
||||
return navigator.mediaDevices.enumerateDevices();
|
||||
}
|
||||
return Promise.reject(new Error('enumerateDevices is not defined'));
|
||||
};
|
||||
|
||||
export function getUserMedia(constraints) {
|
||||
if (navigator.mediaDevices
|
||||
&& typeof navigator.mediaDevices.getUserMedia === 'function') {
|
||||
return navigator.mediaDevices
|
||||
.getUserMedia(constraints);
|
||||
}
|
||||
return Promise.reject(new Error('getUserMedia is not defined'));
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
import {merge, pick, omitBy, isEmpty} from 'lodash';
|
||||
|
||||
import DOMHelper from '../common/dom_helper';
|
||||
|
||||
const isDataURL = {regex: /^\s*data:([a-z]+\/[a-z0-9\-\+]+(;[a-z\-]+\=[a-z0-9\-]+)?)?(;base64)?,[a-z0-9\!\$\&\'\,\(\)\*\+\,\;\=\-\.\_\~\:\@\/\?\%\s]*\s*$/i}, // eslint-disable-line max-len
|
||||
isBlobURL = {regex: /^\s*blob:(.*)$/i},
|
||||
isMediaURL = {regex: /^(?:(?:http[s]?|ftp):\/)?\/?(?:(?:[^:\/\s]+)(?:(?:\/\w+)*\/))?([\w\-]+\.([^#?\s]+))(?:.*)?(?:#[\w\-]+)?$/i}, // eslint-disable-line max-len
|
||||
isImageExt = {regex: /(jpe?g|png|gif|tiff)(?:\s+|$)/i},
|
||||
isVideoExt = {regex: /(webm|ogg|mp4|m4v)/i};
|
||||
|
||||
export function createConfigFromSource(config, sourceConfig, source) {
|
||||
if (source instanceof DOMHelper.MediaStream) {
|
||||
return createConfigForStream(config, sourceConfig, {srcObject: source});
|
||||
} else if (source instanceof DOMHelper.HTMLImageElement) {
|
||||
throw new Error('Source "HTMLImageElement": not yet supported');
|
||||
// return createConfigForImage(config, inputConfig, {image: source});
|
||||
} else if (source instanceof DOMHelper.HTMLVideoElement) {
|
||||
throw new Error('Source "HTMLVideoElement": not yet supported');
|
||||
// return createConfigForVideo(config, inputConfig, {video: source});
|
||||
} else if (source instanceof DOMHelper.HTMLCanvasElement) {
|
||||
return createConfigForCanvas(config, sourceConfig, {canvas: source});
|
||||
} else if (source instanceof DOMHelper.FileList) {
|
||||
if (source.length > 0) {
|
||||
return createConfigForFile(config, sourceConfig, source[0]);
|
||||
}
|
||||
} else if (source instanceof DOMHelper.File) {
|
||||
return createConfigForFile(config, sourceConfig, source);
|
||||
} else if (typeof source === 'string') {
|
||||
return createConfigForString(config, sourceConfig, source);
|
||||
} else if (typeof source === 'object'
|
||||
&& (typeof source.constraints !== 'undefined'
|
||||
|| typeof source.area !== 'undefined')) {
|
||||
return createConfigForLiveStream(config, source);
|
||||
} else {
|
||||
throw new Error("No source given!");
|
||||
}
|
||||
}
|
||||
|
||||
function createConfigForImage(config, source, inputConfig = {}) {
|
||||
const staticImageConfig = {
|
||||
inputStream: merge({
|
||||
type: "ImageStream",
|
||||
sequence: false,
|
||||
size: 800
|
||||
}, source),
|
||||
numOfWorkers: (ENV.development && config.debug) ? 0 : 1
|
||||
};
|
||||
return merge(
|
||||
config,
|
||||
staticImageConfig,
|
||||
{numOfWorkers: typeof config.numOfWorkers === 'number' && config.numOfWorkers > 0 ? 1 : 0},
|
||||
{inputStream: omitBy(pick(config.inputStream, ['size']), isEmpty)},
|
||||
{inputStream: inputConfig});
|
||||
}
|
||||
|
||||
function createConfigForMimeType(config, inputConfig, {src, mime}) {
|
||||
const [, type] = mime.match(/^(video|image)\/(.*)$/i) || [];
|
||||
if (type === 'video') {
|
||||
return createConfigForVideo(config, {src}, inputConfig);
|
||||
} else if (type === 'image') {
|
||||
return createConfigForImage(config, {src}, inputConfig);
|
||||
}
|
||||
throw new Error(`Source with mimetype: "${type}" not supported`);
|
||||
}
|
||||
|
||||
function createConfigForFile(config, inputConfig, file) {
|
||||
const src = DOMHelper.URL.createObjectURL(file);
|
||||
return createConfigForMimeType(config, inputConfig, {
|
||||
src,
|
||||
mime: file.type
|
||||
});
|
||||
}
|
||||
|
||||
function createConfigForString(config, inputConfig = {}, source) {
|
||||
const [, mime] = source.match(isDataURL.regex) || [];
|
||||
if (mime) {
|
||||
return createConfigForMimeType(config, inputConfig, {src: source, mime});
|
||||
}
|
||||
const blobURL = source.match(isBlobURL.regex);
|
||||
if (blobURL) {
|
||||
throw new Error(`Source "objectURL": not supported`);
|
||||
}
|
||||
const [, , ext] = source.match(isMediaURL.regex) || [];
|
||||
if (ext) {
|
||||
return createConfigForMediaExtension(config, inputConfig, {src: source, ext});
|
||||
}
|
||||
throw new Error(`Source "${source}": not recognized`);
|
||||
}
|
||||
|
||||
function createConfigForMediaExtension(config, inputConfig, {src, ext}) {
|
||||
if (ext.match(isImageExt.regex)) {
|
||||
return createConfigForImage(config, {src}, inputConfig);
|
||||
} else if (ext.match(isVideoExt.regex)) {
|
||||
return createConfigForVideo(config, {src}, inputConfig);
|
||||
}
|
||||
throw new Error(`Source "MediaString": not recognized`);
|
||||
}
|
||||
|
||||
function createConfigForCanvas (config, {canvas}, inputConfig = {}) {
|
||||
// TODO: adjust stream & frame-grabber
|
||||
// once/continous
|
||||
throw new Error('Source "Canvas": not implemented!');
|
||||
}
|
||||
|
||||
function createConfigForVideo (config, source, inputConfig = {}) {
|
||||
return merge({},
|
||||
config,
|
||||
{
|
||||
inputStream: merge({
|
||||
type: "VideoStream"
|
||||
}, source)
|
||||
}, {
|
||||
inputStream: inputConfig
|
||||
});
|
||||
}
|
||||
|
||||
function createConfigForStream(config, {srcObject}, inputConfig = {}) {
|
||||
// TODO: attach to <video> element
|
||||
// wait for the video to be ready (dimensions known)
|
||||
throw new Error('Source "MediaStream": not implemented!');
|
||||
}
|
||||
|
||||
function createConfigForLiveStream(config, inputConfig = {}) {
|
||||
return merge({},
|
||||
config,
|
||||
{
|
||||
inputStream: {
|
||||
type: "LiveStream",
|
||||
constraints: {
|
||||
width: 640,
|
||||
height: 480,
|
||||
facingMode: "environment"
|
||||
}
|
||||
}
|
||||
}, {
|
||||
inputStream: inputConfig
|
||||
});
|
||||
}
|
@ -1,543 +1,137 @@
|
||||
import TypeDefs from './common/typedefs'; // eslint-disable-line no-unused-vars
|
||||
import './common/typedefs';
|
||||
import 'webrtc-adapter';
|
||||
import createScanner from './scanner';
|
||||
import ImageWrapper from './common/image_wrapper';
|
||||
import BarcodeLocator from './locator/barcode_locator';
|
||||
import BarcodeDecoder from './decoder/barcode_decoder';
|
||||
import Events from './common/events';
|
||||
import CameraAccess from './input/camera_access';
|
||||
import ImageDebug from './common/image_debug';
|
||||
import ResultCollector from './analytics/result_collector';
|
||||
import Config from './config/config';
|
||||
import InputStream from 'input_stream';
|
||||
import FrameGrabber from 'frame_grabber';
|
||||
import {merge} from 'lodash';
|
||||
const vec2 = {
|
||||
clone: require('gl-vec2/clone')
|
||||
};
|
||||
import {createConfigFromSource} from './input/config_factory';
|
||||
|
||||
var _inputStream,
|
||||
_framegrabber,
|
||||
_stopped,
|
||||
_canvasContainer = {
|
||||
ctx: {
|
||||
image: null,
|
||||
overlay: null
|
||||
function fromConfig(config) {
|
||||
const scanner = createScanner();
|
||||
let pendingStart = null;
|
||||
let initialized = false;
|
||||
return {
|
||||
addEventListener(eventType, cb) {
|
||||
scanner.subscribe(eventType, cb);
|
||||
return this;
|
||||
},
|
||||
dom: {
|
||||
image: null,
|
||||
overlay: null
|
||||
}
|
||||
},
|
||||
_inputImageWrapper,
|
||||
_boxSize,
|
||||
_decoder,
|
||||
_workerPool = [],
|
||||
_onUIThread = true,
|
||||
_resultCollector,
|
||||
_config = {};
|
||||
|
||||
function initializeData(imageWrapper) {
|
||||
initBuffers(imageWrapper);
|
||||
_decoder = BarcodeDecoder.create(_config.decoder, _inputImageWrapper);
|
||||
}
|
||||
|
||||
function initInputStream(cb) {
|
||||
var video;
|
||||
if (_config.inputStream.type === "VideoStream") {
|
||||
video = document.createElement("video");
|
||||
_inputStream = InputStream.createVideoStream(video);
|
||||
} else if (_config.inputStream.type === "ImageStream") {
|
||||
_inputStream = InputStream.createImageStream();
|
||||
} else if (_config.inputStream.type === "LiveStream") {
|
||||
var $viewport = getViewPort();
|
||||
if ($viewport) {
|
||||
video = $viewport.querySelector("video");
|
||||
if (!video) {
|
||||
video = document.createElement("video");
|
||||
$viewport.appendChild(video);
|
||||
}
|
||||
}
|
||||
_inputStream = InputStream.createLiveStream(video);
|
||||
CameraAccess.request(video, _config.inputStream.constraints)
|
||||
.then(() => {
|
||||
_inputStream.trigger("canrecord");
|
||||
}).catch((err) => {
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
_inputStream.setAttribute("preload", "auto");
|
||||
_inputStream.setInputStream(_config.inputStream);
|
||||
_inputStream.addEventListener("canrecord", canRecord.bind(undefined, cb));
|
||||
}
|
||||
|
||||
function getViewPort() {
|
||||
var target = _config.inputStream.target;
|
||||
// Check if target is already a DOM element
|
||||
if (target && target.nodeName && target.nodeType === 1) {
|
||||
return target;
|
||||
} else {
|
||||
// Use '#interactive.viewport' as a fallback selector (backwards compatibility)
|
||||
var selector = typeof target === 'string' ? target : '#interactive.viewport';
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
}
|
||||
|
||||
function canRecord(cb) {
|
||||
BarcodeLocator.checkImageConstraints(_inputStream, _config.locator);
|
||||
initCanvas(_config);
|
||||
_framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image);
|
||||
|
||||
adjustWorkerPool(_config.numOfWorkers, function() {
|
||||
if (_config.numOfWorkers === 0) {
|
||||
initializeData();
|
||||
}
|
||||
ready(cb);
|
||||
});
|
||||
}
|
||||
|
||||
function ready(cb){
|
||||
_inputStream.play();
|
||||
cb();
|
||||
}
|
||||
|
||||
function initCanvas() {
|
||||
if (typeof document !== "undefined") {
|
||||
var $viewport = getViewPort();
|
||||
_canvasContainer.dom.image = document.querySelector("canvas.imgBuffer");
|
||||
if (!_canvasContainer.dom.image) {
|
||||
_canvasContainer.dom.image = document.createElement("canvas");
|
||||
_canvasContainer.dom.image.className = "imgBuffer";
|
||||
if ($viewport && _config.inputStream.type === "ImageStream") {
|
||||
$viewport.appendChild(_canvasContainer.dom.image);
|
||||
removeEventListener(eventType, cb) {
|
||||
scanner.unsubscribe(eventType, cb);
|
||||
return this;
|
||||
},
|
||||
start() {
|
||||
if (scanner.isRunning()) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
_canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d");
|
||||
_canvasContainer.dom.image.width = _inputStream.getCanvasSize().x;
|
||||
_canvasContainer.dom.image.height = _inputStream.getCanvasSize().y;
|
||||
|
||||
_canvasContainer.dom.overlay = document.querySelector("canvas.drawingBuffer");
|
||||
if (!_canvasContainer.dom.overlay) {
|
||||
_canvasContainer.dom.overlay = document.createElement("canvas");
|
||||
_canvasContainer.dom.overlay.className = "drawingBuffer";
|
||||
if ($viewport) {
|
||||
$viewport.appendChild(_canvasContainer.dom.overlay);
|
||||
if (pendingStart) {
|
||||
return pendingStart;
|
||||
}
|
||||
var clearFix = document.createElement("br");
|
||||
clearFix.setAttribute("clear", "all");
|
||||
if ($viewport) {
|
||||
$viewport.appendChild(clearFix);
|
||||
if (initialized) {
|
||||
scanner.start();
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
_canvasContainer.ctx.overlay = _canvasContainer.dom.overlay.getContext("2d");
|
||||
_canvasContainer.dom.overlay.width = _inputStream.getCanvasSize().x;
|
||||
_canvasContainer.dom.overlay.height = _inputStream.getCanvasSize().y;
|
||||
}
|
||||
}
|
||||
|
||||
function initBuffers(imageWrapper) {
|
||||
if (imageWrapper) {
|
||||
_inputImageWrapper = imageWrapper;
|
||||
} else {
|
||||
_inputImageWrapper = new ImageWrapper({
|
||||
x: _inputStream.getWidth(),
|
||||
y: _inputStream.getHeight()
|
||||
});
|
||||
}
|
||||
|
||||
if (ENV.development) {
|
||||
console.log(_inputImageWrapper.size);
|
||||
}
|
||||
_boxSize = [
|
||||
vec2.clone([0, 0]),
|
||||
vec2.clone([0, _inputImageWrapper.size.y]),
|
||||
vec2.clone([_inputImageWrapper.size.x, _inputImageWrapper.size.y]),
|
||||
vec2.clone([_inputImageWrapper.size.x, 0])
|
||||
];
|
||||
BarcodeLocator.init(_inputImageWrapper, _config.locator);
|
||||
}
|
||||
|
||||
function getBoundingBoxes() {
|
||||
if (_config.locate) {
|
||||
return BarcodeLocator.locate();
|
||||
} else {
|
||||
return [[
|
||||
vec2.clone(_boxSize[0]),
|
||||
vec2.clone(_boxSize[1]),
|
||||
vec2.clone(_boxSize[2]),
|
||||
vec2.clone(_boxSize[3])]];
|
||||
}
|
||||
}
|
||||
|
||||
function transformResult(result) {
|
||||
var topRight = _inputStream.getTopRight(),
|
||||
xOffset = topRight.x,
|
||||
yOffset = topRight.y,
|
||||
i;
|
||||
|
||||
if (xOffset === 0 && yOffset === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.barcodes) {
|
||||
for (i = 0; i < result.barcodes.length; i++) {
|
||||
transformResult(result.barcodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.line && result.line.length === 2) {
|
||||
moveLine(result.line);
|
||||
}
|
||||
|
||||
if (result.box) {
|
||||
moveBox(result.box);
|
||||
}
|
||||
|
||||
if (result.boxes && result.boxes.length > 0) {
|
||||
for (i = 0; i < result.boxes.length; i++) {
|
||||
moveBox(result.boxes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function moveBox(box) {
|
||||
var corner = box.length;
|
||||
|
||||
while (corner--) {
|
||||
box[corner][0] += xOffset;
|
||||
box[corner][1] += yOffset;
|
||||
}
|
||||
}
|
||||
|
||||
function moveLine(line) {
|
||||
line[0].x += xOffset;
|
||||
line[0].y += yOffset;
|
||||
line[1].x += xOffset;
|
||||
line[1].y += yOffset;
|
||||
}
|
||||
}
|
||||
|
||||
function addResult (result, imageData) {
|
||||
if (!imageData || !_resultCollector) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.barcodes) {
|
||||
result.barcodes.filter(barcode => barcode.codeResult)
|
||||
.forEach(barcode => addResult(barcode, imageData));
|
||||
} else if (result.codeResult) {
|
||||
_resultCollector.addResult(imageData, _inputStream.getCanvasSize(), result.codeResult);
|
||||
}
|
||||
}
|
||||
|
||||
function hasCodeResult (result) {
|
||||
return result && (result.barcodes ?
|
||||
result.barcodes.some(barcode => barcode.codeResult) :
|
||||
result.codeResult);
|
||||
}
|
||||
|
||||
function publishResult(result, imageData) {
|
||||
let resultToPublish = result;
|
||||
|
||||
if (result && _onUIThread) {
|
||||
transformResult(result);
|
||||
addResult(result, imageData);
|
||||
resultToPublish = result.barcodes || result;
|
||||
}
|
||||
|
||||
Events.publish("processed", resultToPublish);
|
||||
if (hasCodeResult(result)) {
|
||||
Events.publish("detected", resultToPublish);
|
||||
}
|
||||
}
|
||||
|
||||
function locateAndDecode() {
|
||||
var result,
|
||||
boxes;
|
||||
|
||||
boxes = getBoundingBoxes();
|
||||
if (boxes) {
|
||||
result = _decoder.decodeFromBoundingBoxes(boxes);
|
||||
result = result || {};
|
||||
result.boxes = boxes;
|
||||
publishResult(result, _inputImageWrapper.data);
|
||||
} else {
|
||||
publishResult();
|
||||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
var availableWorker;
|
||||
|
||||
if (_onUIThread) {
|
||||
if (_workerPool.length > 0) {
|
||||
availableWorker = _workerPool.filter(function(workerThread) {
|
||||
return !workerThread.busy;
|
||||
})[0];
|
||||
if (availableWorker) {
|
||||
_framegrabber.attachData(availableWorker.imageData);
|
||||
} else {
|
||||
return; // all workers are busy
|
||||
}
|
||||
} else {
|
||||
_framegrabber.attachData(_inputImageWrapper.data);
|
||||
}
|
||||
if (_framegrabber.grab()) {
|
||||
if (availableWorker) {
|
||||
availableWorker.busy = true;
|
||||
availableWorker.worker.postMessage({
|
||||
cmd: 'process',
|
||||
imageData: availableWorker.imageData
|
||||
}, [availableWorker.imageData.buffer]);
|
||||
pendingStart = new Promise((resolve, reject) => {
|
||||
scanner.init(config, (error) => {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
reject(error);
|
||||
}
|
||||
initialized = true;
|
||||
scanner.start();
|
||||
resolve();
|
||||
pendingStart = null;
|
||||
});
|
||||
});
|
||||
return pendingStart;
|
||||
},
|
||||
stop() {
|
||||
scanner.stop();
|
||||
initialized = false;
|
||||
return this;
|
||||
},
|
||||
toPromise() {
|
||||
if (config.inputStream.type === 'LiveStream'
|
||||
|| config.inputStream.type === 'VideoStream') {
|
||||
let cancelRequested = false;
|
||||
return {
|
||||
cancel() {
|
||||
cancelRequested = true;
|
||||
},
|
||||
promise: new Promise((resolve, reject) => {
|
||||
function onProcessed(result) {
|
||||
if (result && result.codeResult && result.codeResult.code) {
|
||||
scanner.stop();
|
||||
scanner.unsubscribe("processed", onProcessed);
|
||||
resolve(result);
|
||||
}
|
||||
if (cancelRequested) {
|
||||
scanner.stop();
|
||||
scanner.unsubscribe("processed", onProcessed);
|
||||
reject("cancelled!");
|
||||
}
|
||||
}
|
||||
scanner.subscribe("processed", onProcessed);
|
||||
this.start();
|
||||
})
|
||||
};
|
||||
} else {
|
||||
locateAndDecode();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
locateAndDecode();
|
||||
}
|
||||
}
|
||||
|
||||
function startContinuousUpdate() {
|
||||
var next = null,
|
||||
delay = 1000 / (_config.frequency || 60);
|
||||
|
||||
_stopped = false;
|
||||
(function frame(timestamp) {
|
||||
next = next || timestamp;
|
||||
if (!_stopped) {
|
||||
if (timestamp >= next) {
|
||||
next += delay;
|
||||
update();
|
||||
return new Promise((resolve, reject) => {
|
||||
scanner.decodeSingle(config, (result) => {
|
||||
if (result && result.codeResult && result.codeResult.code) {
|
||||
return resolve(result);
|
||||
}
|
||||
return reject(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
window.requestAnimFrame(frame);
|
||||
}
|
||||
}(performance.now()));
|
||||
},
|
||||
registerResultCollector(resultCollector) {
|
||||
scanner.registerResultCollector(resultCollector);
|
||||
},
|
||||
getCanvas() {
|
||||
return scanner.canvas.dom.image;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (_onUIThread && _config.inputStream.type === "LiveStream") {
|
||||
startContinuousUpdate();
|
||||
} else {
|
||||
update();
|
||||
}
|
||||
function fromSource(config, source, inputConfig = {}) {
|
||||
config = createConfigFromSource(config, inputConfig, source);
|
||||
return fromConfig(config);
|
||||
}
|
||||
|
||||
function initWorker(cb) {
|
||||
var blobURL,
|
||||
workerThread = {
|
||||
worker: undefined,
|
||||
imageData: new Uint8Array(_inputStream.getWidth() * _inputStream.getHeight()),
|
||||
busy: true
|
||||
};
|
||||
|
||||
blobURL = generateWorkerBlob();
|
||||
workerThread.worker = new Worker(blobURL);
|
||||
|
||||
workerThread.worker.onmessage = function(e) {
|
||||
if (e.data.event === 'initialized') {
|
||||
URL.revokeObjectURL(blobURL);
|
||||
workerThread.busy = false;
|
||||
workerThread.imageData = new Uint8Array(e.data.imageData);
|
||||
if (ENV.development) {
|
||||
console.log("Worker initialized");
|
||||
}
|
||||
return cb(workerThread);
|
||||
} else if (e.data.event === 'processed') {
|
||||
workerThread.imageData = new Uint8Array(e.data.imageData);
|
||||
workerThread.busy = false;
|
||||
publishResult(e.data.result, workerThread.imageData);
|
||||
} else if (e.data.event === 'error') {
|
||||
if (ENV.development) {
|
||||
console.log("Worker error: " + e.data.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
workerThread.worker.postMessage({
|
||||
cmd: 'init',
|
||||
size: {x: _inputStream.getWidth(), y: _inputStream.getHeight()},
|
||||
imageData: workerThread.imageData,
|
||||
config: configForWorker(_config)
|
||||
}, [workerThread.imageData.buffer]);
|
||||
function setConfig(configuration = {}, key, config = {}) {
|
||||
var mergedConfig = merge({}, configuration, {[key]: config});
|
||||
return createApi(mergedConfig);
|
||||
}
|
||||
|
||||
function configForWorker(config) {
|
||||
function createApi(configuration = Config) {
|
||||
return {
|
||||
...config,
|
||||
inputStream: {
|
||||
...config.inputStream,
|
||||
target: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function workerInterface(factory) {
|
||||
/* eslint-disable no-undef*/
|
||||
if (factory) {
|
||||
var Quagga = factory().default;
|
||||
if (!Quagga) {
|
||||
self.postMessage({'event': 'error', message: 'Quagga could not be created'});
|
||||
return;
|
||||
}
|
||||
}
|
||||
var imageWrapper;
|
||||
|
||||
self.onmessage = function(e) {
|
||||
if (e.data.cmd === 'init') {
|
||||
var config = e.data.config;
|
||||
config.numOfWorkers = 0;
|
||||
imageWrapper = new Quagga.ImageWrapper({
|
||||
x: e.data.size.x,
|
||||
y: e.data.size.y
|
||||
}, new Uint8Array(e.data.imageData));
|
||||
Quagga.init(config, ready, imageWrapper);
|
||||
Quagga.onProcessed(onProcessed);
|
||||
} else if (e.data.cmd === 'process') {
|
||||
imageWrapper.data = new Uint8Array(e.data.imageData);
|
||||
Quagga.start();
|
||||
} else if (e.data.cmd === 'setReaders') {
|
||||
Quagga.setReaders(e.data.readers);
|
||||
fromSource(src, inputConfig) {
|
||||
return fromSource(configuration, src, inputConfig);
|
||||
},
|
||||
fromConfig(conf) {
|
||||
return fromConfig(merge({}, configuration, conf));
|
||||
},
|
||||
decoder(conf) {
|
||||
return setConfig(configuration, "decoder", conf);
|
||||
},
|
||||
locator(conf) {
|
||||
return setConfig(configuration, "locator", conf);
|
||||
},
|
||||
throttle(timeInMs) {
|
||||
return setConfig(configuration, "frequency", 1000 / parseInt(timeInMs));
|
||||
},
|
||||
config(conf) {
|
||||
return createApi(merge({}, configuration, conf));
|
||||
},
|
||||
ImageWrapper,
|
||||
ImageDebug,
|
||||
ResultCollector,
|
||||
_worker: {
|
||||
createScanner
|
||||
}
|
||||
};
|
||||
|
||||
function onProcessed(result) {
|
||||
self.postMessage({
|
||||
'event': 'processed',
|
||||
imageData: imageWrapper.data,
|
||||
result: result
|
||||
}, [imageWrapper.data.buffer]);
|
||||
}
|
||||
|
||||
function ready() { // eslint-disable-line
|
||||
self.postMessage({'event': 'initialized', imageData: imageWrapper.data}, [imageWrapper.data.buffer]);
|
||||
}
|
||||
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
function generateWorkerBlob() {
|
||||
var blob,
|
||||
factorySource;
|
||||
|
||||
/* jshint ignore:start */
|
||||
if (typeof __factorySource__ !== 'undefined') {
|
||||
factorySource = __factorySource__; // eslint-disable-line no-undef
|
||||
}
|
||||
/* jshint ignore:end */
|
||||
|
||||
blob = new Blob(['(' + workerInterface.toString() + ')(' + factorySource + ');'],
|
||||
{type: 'text/javascript'});
|
||||
|
||||
return window.URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
function setReaders(readers) {
|
||||
if (_decoder) {
|
||||
_decoder.setReaders(readers);
|
||||
} else if (_onUIThread && _workerPool.length > 0) {
|
||||
_workerPool.forEach(function(workerThread) {
|
||||
workerThread.worker.postMessage({cmd: 'setReaders', readers: readers});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function adjustWorkerPool(capacity, cb) {
|
||||
const increaseBy = capacity - _workerPool.length;
|
||||
if (increaseBy === 0) {
|
||||
return cb && cb();
|
||||
}
|
||||
if (increaseBy < 0) {
|
||||
const workersToTerminate = _workerPool.slice(increaseBy);
|
||||
workersToTerminate.forEach(function(workerThread) {
|
||||
workerThread.worker.terminate();
|
||||
if (ENV.development) {
|
||||
console.log("Worker terminated!");
|
||||
}
|
||||
});
|
||||
_workerPool = _workerPool.slice(0, increaseBy);
|
||||
return cb && cb();
|
||||
} else {
|
||||
for (var i = 0; i < increaseBy; i++) {
|
||||
initWorker(workerInitialized);
|
||||
}
|
||||
|
||||
function workerInitialized(workerThread) {
|
||||
_workerPool.push(workerThread);
|
||||
if (_workerPool.length >= capacity){
|
||||
cb && cb();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
init: function(config, cb, imageWrapper) {
|
||||
_config = merge({}, Config, config);
|
||||
if (imageWrapper) {
|
||||
_onUIThread = false;
|
||||
initializeData(imageWrapper);
|
||||
return cb();
|
||||
} else {
|
||||
initInputStream(cb);
|
||||
}
|
||||
},
|
||||
start: function() {
|
||||
start();
|
||||
},
|
||||
stop: function() {
|
||||
_stopped = true;
|
||||
adjustWorkerPool(0);
|
||||
if (_config.inputStream.type === "LiveStream") {
|
||||
CameraAccess.release();
|
||||
_inputStream.clearEventHandlers();
|
||||
}
|
||||
},
|
||||
pause: function() {
|
||||
_stopped = true;
|
||||
},
|
||||
onDetected: function(callback) {
|
||||
Events.subscribe("detected", callback);
|
||||
},
|
||||
offDetected: function(callback) {
|
||||
Events.unsubscribe("detected", callback);
|
||||
},
|
||||
onProcessed: function(callback) {
|
||||
Events.subscribe("processed", callback);
|
||||
},
|
||||
offProcessed: function(callback) {
|
||||
Events.unsubscribe("processed", callback);
|
||||
},
|
||||
setReaders: function(readers) {
|
||||
setReaders(readers);
|
||||
},
|
||||
registerResultCollector: function(resultCollector) {
|
||||
if (resultCollector && typeof resultCollector.addResult === 'function') {
|
||||
_resultCollector = resultCollector;
|
||||
}
|
||||
},
|
||||
canvas: _canvasContainer,
|
||||
decodeSingle: function(config, resultCallback) {
|
||||
config = merge({
|
||||
inputStream: {
|
||||
type: "ImageStream",
|
||||
sequence: false,
|
||||
size: 800,
|
||||
src: config.src
|
||||
},
|
||||
numOfWorkers: (ENV.development && config.debug) ? 0 : 1,
|
||||
locator: {
|
||||
halfSample: false
|
||||
}
|
||||
}, config);
|
||||
this.init(config, () => {
|
||||
Events.once("processed", (result) => {
|
||||
this.stop();
|
||||
resultCallback.call(null, result);
|
||||
}, true);
|
||||
start();
|
||||
});
|
||||
},
|
||||
ImageWrapper: ImageWrapper,
|
||||
ImageDebug: ImageDebug,
|
||||
ResultCollector: ResultCollector,
|
||||
CameraAccess: CameraAccess,
|
||||
};
|
||||
export default createApi();
|
||||
|
@ -1,257 +0,0 @@
|
||||
import BarcodeReader from './barcode_reader';
|
||||
|
||||
function TwoOfFiveReader(opts) {
|
||||
BarcodeReader.call(this, opts);
|
||||
this.barSpaceRatio = [1, 1];
|
||||
}
|
||||
|
||||
var N = 1,
|
||||
W = 3,
|
||||
properties = {
|
||||
START_PATTERN: {value: [W, N, W, N, N, N]},
|
||||
STOP_PATTERN: {value: [W, N, N, N, W]},
|
||||
CODE_PATTERN: {value: [
|
||||
[N, N, W, W, N],
|
||||
[W, N, N, N, W],
|
||||
[N, W, N, N, W],
|
||||
[W, W, N, N, N],
|
||||
[N, N, W, N, W],
|
||||
[W, N, W, N, N],
|
||||
[N, W, W, N, N],
|
||||
[N, N, N, W, W],
|
||||
[W, N, N, W, N],
|
||||
[N, W, N, W, N]
|
||||
]},
|
||||
SINGLE_CODE_ERROR: {value: 0.78, writable: true},
|
||||
AVG_CODE_ERROR: {value: 0.30, writable: true},
|
||||
FORMAT: {value: "2of5"}
|
||||
};
|
||||
|
||||
const startPatternLength = properties.START_PATTERN.value.reduce((sum, val) => sum + val, 0);
|
||||
|
||||
TwoOfFiveReader.prototype = Object.create(BarcodeReader.prototype, properties);
|
||||
TwoOfFiveReader.prototype.constructor = TwoOfFiveReader;
|
||||
|
||||
TwoOfFiveReader.prototype._findPattern = function(pattern, offset, isWhite, tryHarder) {
|
||||
var counter = [],
|
||||
self = this,
|
||||
i,
|
||||
counterPos = 0,
|
||||
bestMatch = {
|
||||
error: Number.MAX_VALUE,
|
||||
code: -1,
|
||||
start: 0,
|
||||
end: 0
|
||||
},
|
||||
error,
|
||||
j,
|
||||
sum,
|
||||
epsilon = self.AVG_CODE_ERROR;
|
||||
|
||||
isWhite = isWhite || false;
|
||||
tryHarder = tryHarder || false;
|
||||
|
||||
if (!offset) {
|
||||
offset = self._nextSet(self._row);
|
||||
}
|
||||
|
||||
for ( i = 0; i < pattern.length; i++) {
|
||||
counter[i] = 0;
|
||||
}
|
||||
|
||||
for ( i = offset; i < self._row.length; i++) {
|
||||
if (self._row[i] ^ isWhite) {
|
||||
counter[counterPos]++;
|
||||
} else {
|
||||
if (counterPos === counter.length - 1) {
|
||||
sum = 0;
|
||||
for ( j = 0; j < counter.length; j++) {
|
||||
sum += counter[j];
|
||||
}
|
||||
error = self._matchPattern(counter, pattern);
|
||||
if (error < epsilon) {
|
||||
bestMatch.error = error;
|
||||
bestMatch.start = i - sum;
|
||||
bestMatch.end = i;
|
||||
return bestMatch;
|
||||
}
|
||||
if (tryHarder) {
|
||||
for (j = 0; j < counter.length - 2; j++) {
|
||||
counter[j] = counter[j + 2];
|
||||
}
|
||||
counter[counter.length - 2] = 0;
|
||||
counter[counter.length - 1] = 0;
|
||||
counterPos--;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
counterPos++;
|
||||
}
|
||||
counter[counterPos] = 1;
|
||||
isWhite = !isWhite;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
TwoOfFiveReader.prototype._findStart = function() {
|
||||
var self = this,
|
||||
leadingWhitespaceStart,
|
||||
offset = self._nextSet(self._row),
|
||||
startInfo,
|
||||
narrowBarWidth = 1;
|
||||
|
||||
while (!startInfo) {
|
||||
startInfo = self._findPattern(self.START_PATTERN, offset, false, true);
|
||||
if (!startInfo) {
|
||||
return null;
|
||||
}
|
||||
narrowBarWidth = Math.floor((startInfo.end - startInfo.start) / startPatternLength);
|
||||
leadingWhitespaceStart = startInfo.start - narrowBarWidth * 5;
|
||||
if (leadingWhitespaceStart >= 0) {
|
||||
if (self._matchRange(leadingWhitespaceStart, startInfo.start, 0)) {
|
||||
return startInfo;
|
||||
}
|
||||
}
|
||||
offset = startInfo.end;
|
||||
startInfo = null;
|
||||
}
|
||||
};
|
||||
|
||||
TwoOfFiveReader.prototype._verifyTrailingWhitespace = function(endInfo) {
|
||||
var self = this,
|
||||
trailingWhitespaceEnd;
|
||||
|
||||
trailingWhitespaceEnd = endInfo.end + ((endInfo.end - endInfo.start) / 2);
|
||||
if (trailingWhitespaceEnd < self._row.length) {
|
||||
if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) {
|
||||
return endInfo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
TwoOfFiveReader.prototype._findEnd = function() {
|
||||
var self = this,
|
||||
endInfo,
|
||||
tmp,
|
||||
offset;
|
||||
|
||||
self._row.reverse();
|
||||
offset = self._nextSet(self._row);
|
||||
endInfo = self._findPattern(self.STOP_PATTERN, offset, false, true);
|
||||
self._row.reverse();
|
||||
|
||||
if (endInfo === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// reverse numbers
|
||||
tmp = endInfo.start;
|
||||
endInfo.start = self._row.length - endInfo.end;
|
||||
endInfo.end = self._row.length - tmp;
|
||||
|
||||
return endInfo !== null ? self._verifyTrailingWhitespace(endInfo) : null;
|
||||
};
|
||||
|
||||
TwoOfFiveReader.prototype._decodeCode = function(counter) {
|
||||
var j,
|
||||
self = this,
|
||||
sum = 0,
|
||||
normalized,
|
||||
error,
|
||||
epsilon = self.AVG_CODE_ERROR,
|
||||
code,
|
||||
bestMatch = {
|
||||
error: Number.MAX_VALUE,
|
||||
code: -1,
|
||||
start: 0,
|
||||
end: 0
|
||||
};
|
||||
|
||||
for ( j = 0; j < counter.length; j++) {
|
||||
sum += counter[j];
|
||||
}
|
||||
for (code = 0; code < self.CODE_PATTERN.length; code++) {
|
||||
error = self._matchPattern(counter, self.CODE_PATTERN[code]);
|
||||
if (error < bestMatch.error) {
|
||||
bestMatch.code = code;
|
||||
bestMatch.error = error;
|
||||
}
|
||||
}
|
||||
if (bestMatch.error < epsilon) {
|
||||
return bestMatch;
|
||||
}
|
||||
};
|
||||
|
||||
TwoOfFiveReader.prototype._decodePayload = function(counters, result, decodedCodes) {
|
||||
var i,
|
||||
self = this,
|
||||
pos = 0,
|
||||
counterLength = counters.length,
|
||||
counter = [0, 0, 0, 0, 0],
|
||||
code;
|
||||
|
||||
while (pos < counterLength) {
|
||||
for (i = 0; i < 5; i++) {
|
||||
counter[i] = counters[pos] * this.barSpaceRatio[0];
|
||||
pos += 2;
|
||||
}
|
||||
code = self._decodeCode(counter);
|
||||
if (!code) {
|
||||
return null;
|
||||
}
|
||||
result.push(code.code + "");
|
||||
decodedCodes.push(code);
|
||||
}
|
||||
return code;
|
||||
};
|
||||
|
||||
TwoOfFiveReader.prototype._verifyCounterLength = function(counters) {
|
||||
return (counters.length % 10 === 0);
|
||||
};
|
||||
|
||||
TwoOfFiveReader.prototype._decode = function() {
|
||||
var startInfo,
|
||||
endInfo,
|
||||
self = this,
|
||||
code,
|
||||
result = [],
|
||||
decodedCodes = [],
|
||||
counters;
|
||||
|
||||
startInfo = self._findStart();
|
||||
if (!startInfo) {
|
||||
return null;
|
||||
}
|
||||
decodedCodes.push(startInfo);
|
||||
|
||||
endInfo = self._findEnd();
|
||||
if (!endInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
counters = self._fillCounters(startInfo.end, endInfo.start, false);
|
||||
if (!self._verifyCounterLength(counters)) {
|
||||
return null;
|
||||
}
|
||||
code = self._decodePayload(counters, result, decodedCodes);
|
||||
if (!code) {
|
||||
return null;
|
||||
}
|
||||
if (result.length < 5) {
|
||||
return null;
|
||||
}
|
||||
|
||||
decodedCodes.push(endInfo);
|
||||
return {
|
||||
code: result.join(""),
|
||||
start: startInfo.start,
|
||||
end: endInfo.end,
|
||||
startInfo: startInfo,
|
||||
decodedCodes: decodedCodes
|
||||
};
|
||||
};
|
||||
|
||||
export default TwoOfFiveReader;
|
@ -1,251 +0,0 @@
|
||||
import BarcodeReader from './barcode_reader';
|
||||
import ArrayHelper from '../common/array_helper';
|
||||
|
||||
function Code93Reader() {
|
||||
BarcodeReader.call(this);
|
||||
}
|
||||
|
||||
const ALPHABETH_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%abcd*";
|
||||
|
||||
var properties = {
|
||||
ALPHABETH_STRING: {value: ALPHABETH_STRING},
|
||||
ALPHABET: {value: ALPHABETH_STRING.split('').map(char => char.charCodeAt(0))},
|
||||
CHARACTER_ENCODINGS: {value: [
|
||||
0x114, 0x148, 0x144, 0x142, 0x128, 0x124, 0x122, 0x150, 0x112, 0x10A,
|
||||
0x1A8, 0x1A4, 0x1A2, 0x194, 0x192, 0x18A, 0x168, 0x164, 0x162, 0x134,
|
||||
0x11A, 0x158, 0x14C, 0x146, 0x12C, 0x116, 0x1B4, 0x1B2, 0x1AC, 0x1A6,
|
||||
0x196, 0x19A, 0x16C, 0x166, 0x136, 0x13A, 0x12E, 0x1D4, 0x1D2, 0x1CA,
|
||||
0x16E, 0x176, 0x1AE, 0x126, 0x1DA, 0x1D6, 0x132, 0x15E
|
||||
]},
|
||||
ASTERISK: {value: 0x15E},
|
||||
FORMAT: {value: "code_93", writeable: false}
|
||||
};
|
||||
|
||||
Code93Reader.prototype = Object.create(BarcodeReader.prototype, properties);
|
||||
Code93Reader.prototype.constructor = Code93Reader;
|
||||
|
||||
Code93Reader.prototype._decode = function() {
|
||||
var self = this,
|
||||
counters = [0, 0, 0, 0, 0, 0],
|
||||
result = [],
|
||||
start = self._findStart(),
|
||||
decodedChar,
|
||||
lastStart,
|
||||
pattern,
|
||||
nextStart;
|
||||
|
||||
if (!start) {
|
||||
return null;
|
||||
}
|
||||
nextStart = self._nextSet(self._row, start.end);
|
||||
|
||||
do {
|
||||
counters = self._toCounters(nextStart, counters);
|
||||
pattern = self._toPattern(counters);
|
||||
if (pattern < 0) {
|
||||
return null;
|
||||
}
|
||||
decodedChar = self._patternToChar(pattern);
|
||||
if (decodedChar < 0){
|
||||
return null;
|
||||
}
|
||||
result.push(decodedChar);
|
||||
lastStart = nextStart;
|
||||
nextStart += ArrayHelper.sum(counters);
|
||||
nextStart = self._nextSet(self._row, nextStart);
|
||||
} while (decodedChar !== '*');
|
||||
result.pop();
|
||||
|
||||
if (!result.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!self._verifyEnd(lastStart, nextStart, counters)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!self._verifyChecksums(result)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
result = result.slice(0, result.length - 2);
|
||||
if ((result = self._decodeExtended(result)) === null) {
|
||||
return null;
|
||||
};
|
||||
|
||||
return {
|
||||
code: result.join(""),
|
||||
start: start.start,
|
||||
end: nextStart,
|
||||
startInfo: start,
|
||||
decodedCodes: result
|
||||
};
|
||||
};
|
||||
|
||||
Code93Reader.prototype._verifyEnd = function(lastStart, nextStart) {
|
||||
if (lastStart === nextStart || !this._row[nextStart]) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
Code93Reader.prototype._patternToChar = function(pattern) {
|
||||
var i,
|
||||
self = this;
|
||||
|
||||
for (i = 0; i < self.CHARACTER_ENCODINGS.length; i++) {
|
||||
if (self.CHARACTER_ENCODINGS[i] === pattern) {
|
||||
return String.fromCharCode(self.ALPHABET[i]);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
Code93Reader.prototype._toPattern = function(counters) {
|
||||
const numCounters = counters.length;
|
||||
let pattern = 0;
|
||||
let sum = 0;
|
||||
for (let i = 0; i < numCounters; i++) {
|
||||
sum += counters[i];
|
||||
}
|
||||
|
||||
for (let i = 0; i < numCounters; i++) {
|
||||
let normalized = Math.round(counters[i] * 9 / sum);
|
||||
if (normalized < 1 || normalized > 4) {
|
||||
return -1;
|
||||
}
|
||||
if ((i & 1) === 0) {
|
||||
for (let j = 0; j < normalized; j++) {
|
||||
pattern = (pattern << 1) | 1;
|
||||
}
|
||||
} else {
|
||||
pattern <<= normalized;
|
||||
}
|
||||
}
|
||||
|
||||
return pattern;
|
||||
};
|
||||
|
||||
Code93Reader.prototype._findStart = function() {
|
||||
var self = this,
|
||||
offset = self._nextSet(self._row),
|
||||
patternStart = offset,
|
||||
counter = [0, 0, 0, 0, 0, 0],
|
||||
counterPos = 0,
|
||||
isWhite = false,
|
||||
i,
|
||||
j,
|
||||
whiteSpaceMustStart;
|
||||
|
||||
for ( i = offset; i < self._row.length; i++) {
|
||||
if (self._row[i] ^ isWhite) {
|
||||
counter[counterPos]++;
|
||||
} else {
|
||||
if (counterPos === counter.length - 1) {
|
||||
// find start pattern
|
||||
if (self._toPattern(counter) === self.ASTERISK) {
|
||||
whiteSpaceMustStart = Math.floor(Math.max(0, patternStart - ((i - patternStart) / 4)));
|
||||
if (self._matchRange(whiteSpaceMustStart, patternStart, 0)) {
|
||||
return {
|
||||
start: patternStart,
|
||||
end: i
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
patternStart += counter[0] + counter[1];
|
||||
for ( j = 0; j < 4; j++) {
|
||||
counter[j] = counter[j + 2];
|
||||
}
|
||||
counter[4] = 0;
|
||||
counter[5] = 0;
|
||||
counterPos--;
|
||||
} else {
|
||||
counterPos++;
|
||||
}
|
||||
counter[counterPos] = 1;
|
||||
isWhite = !isWhite;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
Code93Reader.prototype._decodeExtended = function(charArray) {
|
||||
const length = charArray.length;
|
||||
const result = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
const char = charArray[i];
|
||||
if (char >= 'a' && char <= 'd') {
|
||||
if (i > (length - 2)) {
|
||||
return null;
|
||||
}
|
||||
const nextChar = charArray[++i];
|
||||
const nextCharCode = nextChar.charCodeAt(0);
|
||||
let decodedChar;
|
||||
switch (char) {
|
||||
case 'a':
|
||||
if (nextChar >= 'A' && nextChar <= 'Z') {
|
||||
decodedChar = String.fromCharCode(nextCharCode - 64);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case 'b':
|
||||
if (nextChar >= 'A' && nextChar <= 'E') {
|
||||
decodedChar = String.fromCharCode(nextCharCode - 38);
|
||||
} else if (nextChar >= 'F' && nextChar <= 'J') {
|
||||
decodedChar = String.fromCharCode(nextCharCode - 11);
|
||||
} else if (nextChar >= 'K' && nextChar <= 'O') {
|
||||
decodedChar = String.fromCharCode(nextCharCode + 16);
|
||||
} else if (nextChar >= 'P' && nextChar <= 'S') {
|
||||
decodedChar = String.fromCharCode(nextCharCode + 43);
|
||||
} else if (nextChar >= 'T' && nextChar <= 'Z') {
|
||||
decodedChar = String.fromCharCode(127);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case 'c':
|
||||
if (nextChar >= 'A' && nextChar <= 'O') {
|
||||
decodedChar = String.fromCharCode(nextCharCode - 32);
|
||||
} else if (nextChar === 'Z') {
|
||||
decodedChar = ':';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case 'd':
|
||||
if (nextChar >= 'A' && nextChar <= 'Z') {
|
||||
decodedChar = String.fromCharCode(nextCharCode + 32);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
result.push(decodedChar);
|
||||
} else {
|
||||
result.push(char);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
Code93Reader.prototype._verifyChecksums = function(charArray) {
|
||||
return this._matchCheckChar(charArray, charArray.length - 2, 20)
|
||||
&& this._matchCheckChar(charArray, charArray.length - 1, 15);
|
||||
};
|
||||
|
||||
Code93Reader.prototype._matchCheckChar = function(charArray, index, maxWeight) {
|
||||
const arrayToCheck = charArray.slice(0, index);
|
||||
const length = arrayToCheck.length;
|
||||
const weightedSums = arrayToCheck.reduce((sum, char, i) => {
|
||||
const weight = (((i * -1) + (length - 1)) % maxWeight) + 1;
|
||||
const value = this.ALPHABET.indexOf(char.charCodeAt(0));
|
||||
return sum + (weight * value);
|
||||
}, 0);
|
||||
|
||||
const checkChar = this.ALPHABET[(weightedSums % 47)];
|
||||
return checkChar === charArray[index].charCodeAt(0);
|
||||
};
|
||||
|
||||
export default Code93Reader;
|
@ -0,0 +1,508 @@
|
||||
import ImageWrapper from './common/image_wrapper';
|
||||
import createLocator, {checkImageConstraints} from './locator/barcode_locator';
|
||||
import BarcodeDecoder from './decoder/barcode_decoder';
|
||||
import createEventedElement from './common/events';
|
||||
import CameraAccess from './input/camera_access';
|
||||
import ImageDebug from './common/image_debug';
|
||||
import ResultCollector from './analytics/result_collector';
|
||||
import Config from './config/config';
|
||||
import InputStream from 'input_stream';
|
||||
import FrameGrabber from 'frame_grabber';
|
||||
import {merge} from 'lodash';
|
||||
const vec2 = {
|
||||
clone: require('gl-vec2/clone')
|
||||
};
|
||||
|
||||
|
||||
function createScanner() {
|
||||
var _inputStream,
|
||||
_framegrabber,
|
||||
_stopped = true,
|
||||
_canvasContainer = {
|
||||
ctx: {
|
||||
image: null
|
||||
},
|
||||
dom: {
|
||||
image: null
|
||||
}
|
||||
},
|
||||
_inputImageWrapper,
|
||||
_boxSize,
|
||||
_decoder,
|
||||
_workerPool = [],
|
||||
_onUIThread = true,
|
||||
_resultCollector,
|
||||
_config = {},
|
||||
_events = createEventedElement(),
|
||||
_locator;
|
||||
|
||||
function initializeData(imageWrapper) {
|
||||
initBuffers(imageWrapper);
|
||||
_decoder = BarcodeDecoder.create(_config.decoder, _inputImageWrapper);
|
||||
}
|
||||
|
||||
function initInputStream(cb) {
|
||||
var video;
|
||||
if (_config.inputStream.type === "VideoStream") {
|
||||
video = document.createElement("video");
|
||||
_inputStream = InputStream.createVideoStream(video);
|
||||
} else if (_config.inputStream.type === "ImageStream") {
|
||||
_inputStream = InputStream.createImageStream();
|
||||
} else if (_config.inputStream.type === "LiveStream") {
|
||||
var $viewport = getViewPort();
|
||||
if ($viewport) {
|
||||
video = $viewport.querySelector("video");
|
||||
if (!video) {
|
||||
video = document.createElement("video");
|
||||
$viewport.appendChild(video);
|
||||
}
|
||||
}
|
||||
_inputStream = InputStream.createLiveStream(video);
|
||||
CameraAccess.request(video, _config.inputStream.constraints)
|
||||
.then(() => {
|
||||
_inputStream.trigger("canrecord");
|
||||
}).catch((err) => {
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
_inputStream.setAttribute("preload", "auto");
|
||||
_inputStream.setInputStream(_config.inputStream);
|
||||
_inputStream.addEventListener("canrecord", canRecord.bind(undefined, cb));
|
||||
}
|
||||
|
||||
function getViewPort() {
|
||||
var target = _config.inputStream.target;
|
||||
// Check if target is already a DOM element
|
||||
if (target && target.nodeName && target.nodeType === 1) {
|
||||
return target;
|
||||
} else {
|
||||
// Use '#interactive.viewport' as a fallback selector (backwards compatibility)
|
||||
var selector = typeof target === 'string' ? target : '#interactive.viewport';
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
}
|
||||
|
||||
function canRecord(cb) {
|
||||
checkImageConstraints(_inputStream, _config.locator);
|
||||
initCanvas(_config);
|
||||
_framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image);
|
||||
|
||||
adjustWorkerPool(_config.numOfWorkers, function() {
|
||||
if (_config.numOfWorkers === 0) {
|
||||
initializeData();
|
||||
}
|
||||
ready(cb);
|
||||
});
|
||||
}
|
||||
|
||||
function ready(cb){
|
||||
_inputStream.play();
|
||||
cb();
|
||||
}
|
||||
|
||||
function initCanvas() {
|
||||
if (typeof document !== "undefined") {
|
||||
var $viewport = getViewPort();
|
||||
_canvasContainer.dom.image = document.querySelector("canvas.imgBuffer");
|
||||
if (!_canvasContainer.dom.image) {
|
||||
_canvasContainer.dom.image = document.createElement("canvas");
|
||||
_canvasContainer.dom.image.className = "imgBuffer";
|
||||
if ($viewport && _config.inputStream.type === "ImageStream") {
|
||||
$viewport.appendChild(_canvasContainer.dom.image);
|
||||
}
|
||||
}
|
||||
_canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d");
|
||||
_canvasContainer.dom.image.width = _inputStream.getCanvasSize().x;
|
||||
_canvasContainer.dom.image.height = _inputStream.getCanvasSize().y;
|
||||
}
|
||||
}
|
||||
|
||||
function initBuffers(imageWrapper) {
|
||||
if (imageWrapper) {
|
||||
_inputImageWrapper = imageWrapper;
|
||||
} else {
|
||||
_inputImageWrapper = new ImageWrapper({
|
||||
x: _inputStream.getWidth(),
|
||||
y: _inputStream.getHeight()
|
||||
});
|
||||
}
|
||||
|
||||
if (ENV.development) {
|
||||
console.log(_inputImageWrapper.size);
|
||||
}
|
||||
_boxSize = [
|
||||
vec2.clone([0, 0]),
|
||||
vec2.clone([0, _inputImageWrapper.size.y]),
|
||||
vec2.clone([_inputImageWrapper.size.x, _inputImageWrapper.size.y]),
|
||||
vec2.clone([_inputImageWrapper.size.x, 0])
|
||||
];
|
||||
_locator = createLocator(_inputImageWrapper, _config.locator);
|
||||
}
|
||||
|
||||
function getBoundingBoxes() {
|
||||
if (_config.locate) {
|
||||
return _locator.locate();
|
||||
} else {
|
||||
return [[
|
||||
vec2.clone(_boxSize[0]),
|
||||
vec2.clone(_boxSize[1]),
|
||||
vec2.clone(_boxSize[2]),
|
||||
vec2.clone(_boxSize[3])]];
|
||||
}
|
||||
}
|
||||
|
||||
function transformResult(result) {
|
||||
var topRight = _inputStream.getTopRight(),
|
||||
xOffset = topRight.x,
|
||||
yOffset = topRight.y,
|
||||
i;
|
||||
|
||||
if (xOffset === 0 && yOffset === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.barcodes) {
|
||||
for (i = 0; i < result.barcodes.length; i++) {
|
||||
transformResult(result.barcodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.line && result.line.length === 2) {
|
||||
moveLine(result.line);
|
||||
}
|
||||
|
||||
if (result.box) {
|
||||
moveBox(result.box);
|
||||
}
|
||||
|
||||
if (result.boxes && result.boxes.length > 0) {
|
||||
for (i = 0; i < result.boxes.length; i++) {
|
||||
moveBox(result.boxes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function moveBox(box) {
|
||||
var corner = box.length;
|
||||
|
||||
while (corner--) {
|
||||
box[corner][0] += xOffset;
|
||||
box[corner][1] += yOffset;
|
||||
}
|
||||
}
|
||||
|
||||
function moveLine(line) {
|
||||
line[0].x += xOffset;
|
||||
line[0].y += yOffset;
|
||||
line[1].x += xOffset;
|
||||
line[1].y += yOffset;
|
||||
}
|
||||
}
|
||||
|
||||
function addResult (result, imageData) {
|
||||
if (!imageData || !_resultCollector) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.barcodes) {
|
||||
result.barcodes.filter(barcode => barcode.codeResult)
|
||||
.forEach(barcode => addResult(barcode, imageData));
|
||||
} else if (result.codeResult) {
|
||||
_resultCollector.addResult(imageData, _inputStream.getCanvasSize(), result.codeResult);
|
||||
}
|
||||
}
|
||||
|
||||
function hasCodeResult (result) {
|
||||
return result && (result.barcodes ?
|
||||
result.barcodes.some(barcode => barcode.codeResult) :
|
||||
result.codeResult);
|
||||
}
|
||||
|
||||
function publishResult(result, imageData) {
|
||||
let resultToPublish = result;
|
||||
|
||||
if (result && _onUIThread) {
|
||||
transformResult(result);
|
||||
addResult(result, imageData);
|
||||
resultToPublish = result.barcodes || result;
|
||||
}
|
||||
|
||||
_events.publish("processed", resultToPublish);
|
||||
if (hasCodeResult(result)) {
|
||||
_events.publish("detected", resultToPublish);
|
||||
}
|
||||
}
|
||||
|
||||
function locateAndDecode() {
|
||||
var result,
|
||||
boxes;
|
||||
|
||||
boxes = getBoundingBoxes();
|
||||
if (boxes) {
|
||||
result = _decoder.decodeFromBoundingBoxes(boxes);
|
||||
result = result || {};
|
||||
result.boxes = boxes;
|
||||
publishResult(result, _inputImageWrapper.data);
|
||||
} else {
|
||||
publishResult();
|
||||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
var availableWorker;
|
||||
|
||||
if (_onUIThread) {
|
||||
if (_workerPool.length > 0) {
|
||||
availableWorker = _workerPool.filter(function(workerThread) {
|
||||
return !workerThread.busy;
|
||||
})[0];
|
||||
if (availableWorker) {
|
||||
_framegrabber.attachData(availableWorker.imageData);
|
||||
} else {
|
||||
return; // all workers are busy
|
||||
}
|
||||
} else {
|
||||
_framegrabber.attachData(_inputImageWrapper.data);
|
||||
}
|
||||
if (_framegrabber.grab()) {
|
||||
if (availableWorker) {
|
||||
availableWorker.busy = true;
|
||||
availableWorker.worker.postMessage({
|
||||
cmd: 'process',
|
||||
imageData: availableWorker.imageData
|
||||
}, [availableWorker.imageData.buffer]);
|
||||
} else {
|
||||
locateAndDecode();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
locateAndDecode();
|
||||
}
|
||||
}
|
||||
|
||||
function startContinuousUpdate() {
|
||||
var next = null,
|
||||
delay = 1000 / (_config.frequency === 0 ? 60 : (_config.frequency || 60));
|
||||
|
||||
_stopped = false;
|
||||
(function frame(timestamp) {
|
||||
next = next || timestamp;
|
||||
if (!_stopped) {
|
||||
if (timestamp >= next) {
|
||||
next += delay;
|
||||
update();
|
||||
}
|
||||
window.requestAnimFrame(frame);
|
||||
}
|
||||
}(performance.now()));
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (_onUIThread && _config.inputStream.type === "LiveStream") {
|
||||
startContinuousUpdate();
|
||||
} else {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
function initWorker(cb) {
|
||||
var blobURL,
|
||||
workerThread = {
|
||||
worker: undefined,
|
||||
imageData: new Uint8Array(_inputStream.getWidth() * _inputStream.getHeight()),
|
||||
busy: true
|
||||
};
|
||||
|
||||
blobURL = generateWorkerBlob();
|
||||
workerThread.worker = new Worker(blobURL);
|
||||
|
||||
workerThread.worker.onmessage = function(e) {
|
||||
if (e.data.event === 'initialized') {
|
||||
URL.revokeObjectURL(blobURL);
|
||||
workerThread.busy = false;
|
||||
workerThread.imageData = new Uint8Array(e.data.imageData);
|
||||
if (ENV.development) {
|
||||
console.log("Worker initialized");
|
||||
}
|
||||
return cb(workerThread);
|
||||
} else if (e.data.event === 'processed') {
|
||||
workerThread.imageData = new Uint8Array(e.data.imageData);
|
||||
workerThread.busy = false;
|
||||
publishResult(e.data.result, workerThread.imageData);
|
||||
} else if (e.data.event === 'error') {
|
||||
if (ENV.development) {
|
||||
console.log("Worker error: " + e.data.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
workerThread.worker.postMessage({
|
||||
cmd: 'init',
|
||||
size: {x: _inputStream.getWidth(), y: _inputStream.getHeight()},
|
||||
imageData: workerThread.imageData,
|
||||
config: configForWorker(_config)
|
||||
}, [workerThread.imageData.buffer]);
|
||||
}
|
||||
|
||||
function configForWorker(config) {
|
||||
return {
|
||||
...config,
|
||||
inputStream: {
|
||||
...config.inputStream,
|
||||
target: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function workerInterface(factory) {
|
||||
/* eslint-disable no-undef*/
|
||||
if (factory) {
|
||||
var Quagga = factory().default;
|
||||
if (!Quagga) {
|
||||
self.postMessage({'event': 'error', message: 'Quagga could not be created'});
|
||||
return;
|
||||
}
|
||||
}
|
||||
var imageWrapper,
|
||||
scanner = Quagga._worker.createScanner();
|
||||
|
||||
self.onmessage = function(e) {
|
||||
if (e.data.cmd === 'init') {
|
||||
var config = e.data.config;
|
||||
config.numOfWorkers = 0;
|
||||
imageWrapper = new Quagga.ImageWrapper({
|
||||
x: e.data.size.x,
|
||||
y: e.data.size.y
|
||||
}, new Uint8Array(e.data.imageData));
|
||||
scanner.init(config, ready, imageWrapper);
|
||||
scanner.subscribe("processed", onProcessed);
|
||||
} else if (e.data.cmd === 'process') {
|
||||
imageWrapper.data = new Uint8Array(e.data.imageData);
|
||||
scanner.start();
|
||||
}
|
||||
};
|
||||
|
||||
function onProcessed(result) {
|
||||
self.postMessage({
|
||||
'event': 'processed',
|
||||
imageData: imageWrapper.data,
|
||||
result: result
|
||||
}, [imageWrapper.data.buffer]);
|
||||
}
|
||||
|
||||
function ready() { // eslint-disable-line
|
||||
self.postMessage({'event': 'initialized', imageData: imageWrapper.data}, [imageWrapper.data.buffer]);
|
||||
}
|
||||
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
function generateWorkerBlob() {
|
||||
var blob,
|
||||
factorySource;
|
||||
|
||||
/* jshint ignore:start */
|
||||
if (typeof __factorySource__ !== 'undefined') {
|
||||
factorySource = __factorySource__; // eslint-disable-line no-undef
|
||||
}
|
||||
/* jshint ignore:end */
|
||||
|
||||
blob = new Blob(['(' + workerInterface.toString() + ')(' + factorySource + ');'],
|
||||
{type: 'text/javascript'});
|
||||
|
||||
return window.URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
function setReaders(readers) {
|
||||
if (_decoder) {
|
||||
_decoder.setReaders(readers);
|
||||
} else if (_onUIThread && _workerPool.length > 0) {
|
||||
_workerPool.forEach(function(workerThread) {
|
||||
workerThread.worker.postMessage({cmd: 'setReaders', readers: readers});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function adjustWorkerPool(capacity, cb) {
|
||||
const increaseBy = capacity - _workerPool.length;
|
||||
if (increaseBy === 0) {
|
||||
return cb && cb();
|
||||
}
|
||||
if (increaseBy < 0) {
|
||||
const workersToTerminate = _workerPool.slice(increaseBy);
|
||||
workersToTerminate.forEach(function(workerThread) {
|
||||
workerThread.worker.terminate();
|
||||
if (ENV.development) {
|
||||
console.log("Worker terminated!");
|
||||
}
|
||||
});
|
||||
_workerPool = _workerPool.slice(0, increaseBy);
|
||||
return cb && cb();
|
||||
} else {
|
||||
for (var i = 0; i < increaseBy; i++) {
|
||||
initWorker(workerInitialized);
|
||||
}
|
||||
|
||||
function workerInitialized(workerThread) {
|
||||
_workerPool.push(workerThread);
|
||||
if (_workerPool.length >= capacity){
|
||||
cb && cb();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
init: function(config, cb, imageWrapper) {
|
||||
_config = merge({}, Config, config);
|
||||
|
||||
if (imageWrapper) {
|
||||
_onUIThread = false;
|
||||
initializeData(imageWrapper);
|
||||
return cb();
|
||||
} else {
|
||||
initInputStream(cb);
|
||||
}
|
||||
},
|
||||
start: function() {
|
||||
start();
|
||||
},
|
||||
isRunning: function() {
|
||||
return !_stopped;
|
||||
},
|
||||
stop: function() {
|
||||
_stopped = true;
|
||||
adjustWorkerPool(0);
|
||||
if (_config.inputStream.type === "LiveStream") {
|
||||
CameraAccess.release();
|
||||
}
|
||||
_inputStream.clearEventHandlers();
|
||||
},
|
||||
pause: function() {
|
||||
_stopped = true;
|
||||
},
|
||||
subscribe(eventName, callback) {
|
||||
_events.subscribe(eventName, callback);
|
||||
},
|
||||
unsubscribe(eventName, callback) {
|
||||
_events.unsubscribe(eventName, callback);
|
||||
},
|
||||
registerResultCollector: function(resultCollector) {
|
||||
if (resultCollector && typeof resultCollector.addResult === 'function') {
|
||||
_resultCollector = resultCollector;
|
||||
}
|
||||
},
|
||||
decodeSingle: function(config, resultCallback) {
|
||||
this.init(config, () => {
|
||||
_events.once("processed", (result) => {
|
||||
this.stop();
|
||||
resultCallback.call(null, result);
|
||||
}, true);
|
||||
start();
|
||||
});
|
||||
},
|
||||
canvas: _canvasContainer
|
||||
};
|
||||
}
|
||||
|
||||
export default createScanner;
|
Before Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 178 KiB |
Before Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 162 KiB |
Before Width: | Height: | Size: 164 KiB |
Before Width: | Height: | Size: 145 KiB |
Before Width: | Height: | Size: 122 KiB |
Before Width: | Height: | Size: 168 KiB |
Before Width: | Height: | Size: 156 KiB |
Before Width: | Height: | Size: 183 KiB |
Before Width: | Height: | Size: 173 KiB |
Before Width: | Height: | Size: 169 KiB |