Merged feature/109 to 1.0

1.0
Christoph Oberhofer 9 years ago
commit 9221529e16

@ -25,18 +25,19 @@
},
"globals": {
"ENV": true,
"beforeEach": true,
"describe": true,
"it": true,
"describe": true,
"beforeEach": true,
"sinon": true,
"expect": true,
"sinon": true
"afterEach": true
},
"rules": {
"no-unused-expressions": 1,
"no-extra-boolean-cast": 1,
"no-multi-spaces": 2,
"no-underscore-dangle": 0,
"comma-dangle": 2,
"comma-dangle": 0,
"camelcase": 0,
"curly": 2,
"eqeqeq": 2,

10878
dist/quagga.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -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("http://fonts.googleapis.com/css?family=Ubuntu:400,700|Cabin+Condensed:400,600");
@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;
}

@ -1,36 +1,83 @@
@charset "UTF-8";
/* usual styles */
/* 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 */
@import url("http://fonts.googleapis.com/css?family=Ubuntu:400,700|Cabin+Condensed:400,600");
/* line 1, ../sass/_viewport.scss */
#interactive.viewport {
width: 640px;
height: 480px;
.collapsable-source pre {
font-size: small;
}
/* line 6, ../sass/_viewport.scss */
#interactive.viewport canvas, video {
float: left;
width: 640px;
height: 480px;
.input-field {
display: flex;
align-items: center;
width: 260px;
}
.input-field label {
flex: 0 0 auto;
padding-right: 0.5rem;
}
.input-field input {
flex: 1 1 auto;
height: 20px;
}
.input-field button {
flex: 0 0 auto;
height: 28px;
font-size: 20px;
width: 40px;
}
.overlay {
overflow: hidden;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 100%;
background-color: rgba(0, 0, 0, 0.3);
}
.overlay__content {
top: 50%;
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
max-height: 90%;
}
.overlay__close {
position: absolute;
right: 0;
padding: 0.5rem;
width: 2rem;
height: 2rem;
line-height: 2rem;
text-align: center;
background-color: white;
cursor: pointer;
border: 3px solid black;
font-size: 1.5rem;
margin: -1rem;
border-radius: 2rem;
z-index: 100;
box-sizing: content-box;
}
.overlay__content video {
width: 100%;
height: 100%;
}
.overlay__content canvas {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
}
/* line 10, ../sass/_viewport.scss */
#interactive.viewport canvas.drawingBuffer, video.drawingBuffer {
margin-left: -640px;
input[type=file] {
display: none;
}
/* line 16, ../sass/_viewport.scss */
@ -117,97 +164,7 @@
clear: both;
}
/* line 7, ../sass/_overlay.scss */
.scanner-overlay {
display: none;
width: 640px;
height: 510px;
position: absolute;
padding: 20px;
top: 50%;
margin-top: -275px;
left: 50%;
margin-left: -340px;
background-color: #FFF;
-moz-box-shadow: #333333 0px 4px 10px;
-webkit-box-shadow: #333333 0px 4px 10px;
box-shadow: #333333 0px 4px 10px;
}
/* line 20, ../sass/_overlay.scss */
.scanner-overlay > .header {
position: relative;
margin-bottom: 14px;
}
/* line 23, ../sass/_overlay.scss */
.scanner-overlay > .header h4, .scanner-overlay > .header .close {
line-height: 16px;
}
/* line 26, ../sass/_overlay.scss */
.scanner-overlay > .header h4 {
margin: 0px;
padding: 0px;
}
/* line 30, ../sass/_overlay.scss */
.scanner-overlay > .header .close {
position: absolute;
right: 0px;
top: 0px;
height: 16px;
width: 16px;
text-align: center;
font-weight: bold;
font-size: 14px;
cursor: pointer;
}
/* line 1, ../sass/_icons.scss */
i.icon-24-scan {
width: 24px;
height: 24px;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzFFMjMzNTBFNjcwMTFFMkIzMERGOUMzMzEzM0E1QUMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzFFMjMzNTFFNjcwMTFFMkIzMERGOUMzMzEzM0E1QUMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDMUUyMzM0RUU2NzAxMUUyQjMwREY5QzMzMTMzQTVBQyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDMUUyMzM0RkU2NzAxMUUyQjMwREY5QzMzMTMzQTVBQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PtQr90wAAAUuSURBVHjanFVLbFRVGP7ua97T9DGPthbamAYYBNSMVbBpjCliWWGIEBMWsnDJxkh8RDeEDW5MDGticMmGBWnSlRSCwgLFNkqmmrRIqzjTznTazkxn5s7c6/efzm0G0Jhwkj/nP+d/nv91tIWFBTQaDQWapkGW67p4ltUub5qmAi0UCqF/a/U2m81tpmddotwwDGSz2dzi4uKSaOucnJycGhsbe1XXdQiIIcdxEAgEtgXq9brySHCht79UXi/8QheawN27d385fPjwuEl6XyKR6LdtW7t06RLK5TKOHj2K/fv3Q87Dw8OYn5/HiRMnMDs7i5mZGQwODiqlPp8PuVwO6XRaOXb16lXl1OnTp5FMJvtosF8M+MWLarWqGJaWlpBKpRRcu3YN4+PjmJ6exsTEhDJw5coVjI6OKgPhcBiZTAbxeBx+vx+XL19Gd3c3Tp48Ka9zqDYgBlTQxYNgMIhIJKLCILkQb+TZsgvdsiyFi+feWRR7oRNZyanQtvW2V4DEUUBiK2eJpeDirSyhCe7F2QPh8fiEp72i9PbsC5G52DbiKZA771yr1dTuGfJ4PQNPFoAyQNR1aNEmsS5eyB3PgjeooMZd2AWvNmzYci/Gea7TeFOcI93jV/K67noGmi4vdRI9gPSDeMLSdKUBZZczlWm1rTtHjLZ24d+WER2tc8N1m+Y+ID74wx0zGYvhg9UNrJdtHJyZRdQfwPsrq9g99xsGlgsYmr6BNzO/IVwsYfjBQ6XYz6JI/72MV366B5/lw0elOkJWGUM3bmKtWjXSLuLaBWhnPnnp0FfoiFi4+TMfVAb2poBkDLjO845uYLEAjL4ALGWBP5YAOsP4AJYBFDaB1HOSVWD2PuV95H2RdV93Lv74/cf6p6Zxq/h6OofeOPJBC39JtONdwOAAViOs4p4OFGTf0Uc8iiyrr9YdQrUnDLsngrVOC0jQib44HlF2RafRZBz1Qy+vfhgK3NJZBlrm+LEm9qWwzFgLU7Ozg0JxZP06jQSRpQ7EerAWDSt6PuhHPmChEAog56fCLvJT5hHTm3OZkz3DyLx7XNWTGEA1GkV14gjWgwbW0ESVjYRwCOuai03L5E7OUBAV4kXSS4auoGIaKOma4m8EA5R1sMEGLh95C+XuLph0WJWpxepYYLtfT0RRgY1KgNODY6BoaChRuEhDCIZQYseuki5KN6hcQHiq7OZNv4/Zq2O6P4Lfkwn46vZjjaYZrIpvWbpzjLErrc4xUGE4avRedpYJalRcIl5hQius/SrPm9xrNOQYJhao6BvNUeWqtY8KaWuNjHOFAr7mM9f4NA4UbKysoUJ8PV9UzVOx6wxDDWUOxnK1pmCD07fOMAvtIsM3l89Dl3HRGhVma9AZMqjOnz2LQqWCxs6dqr3T7x1DTzKJaG8SekcHhg4cgI/56uKdlKnBV/WndqN3YAB/7tyBd3oT6GBIOzs7kc/nDfFdDFT5bS73cp06dQoaPa/Rw/rtO/resTHxxE2m9rCrbSR27UJCcMf1BpiA5rAAGgdfc868fUR1sMwj0cm9Iu9IctweisViB3hhKTHDcHc5jv/LspbyaZrR1OD82/fIlOkuB9LnEWRmDX2TsddUPg3D5gvuc0je0rZaD5EW6G3yjS+A3eeBEWq3XW/Abw1HhUspXADufQb86oW7tZytkYCN//3hHwBvDALPi8EnSOYK8DAOfCc2h4aGcO7cuafkzampqf9UripH12/DtOZbx8ciVGzYy5OO40o25ascGRl5Ssc/AgwAjW3JwqIUjSYAAAAASUVORK5CYII=");
display: inline-block;
background-repeat: no-repeat;
line-height: 24px;
margin-top: 1px;
vertical-align: text-top;
}
@media (min-width: 604px) and (max-width: 1024px) {
/* tablet styles */
}
@media (max-width: 603px) {
/* line 2, ../sass/phone/_core.scss */
#container {
width: 300px;
margin: 10px auto;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
/* line 9, ../sass/phone/_core.scss */
#container form.voucher-form input.voucher-code {
width: 180px;
}
}
@media (max-width: 603px) {
/* line 5, ../sass/phone/_viewport.scss */
#interactive.viewport {
width: 300px;
height: 300px;
overflow: hidden;
}
/* line 11, ../sass/phone/_viewport.scss */
#interactive.viewport canvas, video {
margin-top: -50px;
width: 300px;
height: 400px;
}
/* line 15, ../sass/phone/_viewport.scss */
#interactive.viewport canvas.drawingBuffer, video.drawingBuffer {
margin-left: -300px;
}
/* line 20, ../sass/phone/_viewport.scss */
#result_strip {
margin-top: 5px;
@ -230,34 +187,14 @@ i.icon-24-scan {
height: 180px;
}
}
@media (max-width: 603px) {
/* line 8, ../sass/phone/_overlay.scss */
.overlay.scanner {
width: 640px;
height: 510px;
padding: 20px;
margin-top: -275px;
margin-left: -340px;
background-color: #FFF;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
/* line 17, ../sass/phone/_overlay.scss */
.overlay.scanner > .header {
margin-bottom: 14px;
}
/* line 19, ../sass/phone/_overlay.scss */
.overlay.scanner > .header h4, .overlay.scanner > .header .close {
line-height: 16px;
}
/* line 22, ../sass/phone/_overlay.scss */
.overlay.scanner > .header .close {
height: 16px;
width: 16px;
html {
height: 100%;
}
html, body {
min-height: 100%;
}
/* line 15, ../sass/styles.scss */
body {
background-color: #FFF;
margin: 0px;
@ -265,6 +202,9 @@ body {
color: #1e1e1e;
font-weight: normal;
padding-top: 0;
box-sizing: border-box;
padding-bottom: 96px;
position: relative;
}
/* line 24, ../sass/styles.scss */
@ -279,18 +219,18 @@ header {
}
/* line 31, ../sass/styles.scss */
header .headline {
width: 640px;
max-width: 640px;
margin: 0 auto;
}
/* line 34, ../sass/styles.scss */
header .headline h1 {
color: #FFDD69;
font-size: 3em;
margin-bottom: 0;
}
/* line 39, ../sass/styles.scss */
header .headline h2 {
.headline h2 {
margin-top: 0.2em;
color: #FFDD69;
}
/* line 45, ../sass/styles.scss */
@ -298,11 +238,28 @@ footer {
background: #0A4DB7;
color: #6C9CE8;
padding: 1em 2em 2em;
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
/* line 51, ../sass/styles.scss */
#container {
width: 640px;
.container {
margin: 20px auto;
padding: 10px;
max-width: 640px;
}
#interactive.viewport {
position: relative;
}
#interactive.viewport > canvas, #interactive.viewport > video {
max-width: 100%;
width: 100%;
}
canvas.drawing {
position: absolute;
}

@ -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">&nbsp;</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">
&lt;form&gt;
&lt;div class=&quot;input-field&quot;&gt;
&lt;label for=&quot;isbn_input&quot;&gt;EAN:&lt;/label&gt;
&lt;input id=&quot;isbn_input&quot; class=&quot;isbn&quot; type=&quot;text&quot; /&gt;
&lt;button type=&quot;button&quot; class=&quot;icon-barcode button scan&quot;&gt;&amp;nbsp;&lt;/button&gt;
&lt;input type=&quot;file&quot; id=&quot;file&quot; capture/&gt;
&lt;/div&gt;
&lt;/form&gt;
</code>
</pre>
</div>
</div>
</section>
<footer>
<p>
&copy; 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();

@ -95,7 +95,9 @@
<div id="result_strip">
<ul class="thumbnails"></ul>
</div>
<div id="interactive" class="viewport"></div>
<div id="interactive" class="viewport">
<canvas class="drawing" />
</div>
<div id="debug" class="detection"></div>
</section>
<footer>

@ -1,6 +1,7 @@
$(function() {
var App = {
init: function() {
this.overlay = document.querySelector('#interactive canvas.drawing');
App.attachListeners();
},
attachListeners: function() {
@ -8,14 +9,14 @@ $(function() {
$(".controls input[type=file]").on("change", function(e) {
if (e.target.files && e.target.files.length) {
App.decode(URL.createObjectURL(e.target.files[0]));
App.decode(e.target.files[0]);
}
});
$(".controls button").on("click", function(e) {
var input = document.querySelector(".controls input[type=file]");
if (input.files && input.files.length) {
App.decode(URL.createObjectURL(input.files[0]));
App.decode(input.files[0]);
}
});
@ -52,11 +53,27 @@ $(function() {
$(".controls .reader-config-group").off("change", "input, select");
$(".controls button").off("click");
},
decode: function(src) {
var self = this,
config = $.extend({}, self.state, {src: src});
Quagga.decodeSingle(config, function(result) {});
decode: function(file) {
this.detachListeners();
console.log("decode...");
var scanner = Quagga
.config(this.state)
.fromSource(file, {size: this.state.inputStream.size});
scanner
.toPromise()
.then(function(result) {
console.log(result);
addToResults(scanner, result);
return result;
})
.catch(function(result) {
console.log('Not found', result);
return result;
})
.then(function(result) {
drawResult(scanner, result);
this.attachListeners();
}.bind(this));
},
setState: function(path, value) {
var self = this;
@ -142,45 +159,48 @@ $(function() {
};
}
Quagga.onProcessed(function(result) {
var drawingCtx = Quagga.canvas.ctx.overlay,
drawingCanvas = Quagga.canvas.dom.overlay,
area;
function drawResult(scanner, result) {
var processingCanvas = scanner.getCanvas(),
canvas = App.overlay,
ctx = canvas.getContext("2d");
canvas.setAttribute('width', processingCanvas.getAttribute('width'));
canvas.setAttribute('height', processingCanvas.getAttribute('height'));
if (result) {
if (result.boxes) {
drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
ctx.clearRect(0, 0, parseInt(canvas.getAttribute("width")), parseInt(canvas.getAttribute("height")));
result.boxes.filter(function (box) {
return box !== result.box;
}).forEach(function (box) {
Quagga.ImageDebug.drawPath(box, {x: 0, y: 1}, drawingCtx, {color: "green", lineWidth: 2});
Quagga.ImageDebug.drawPath(box, {x: 0, y: 1}, ctx, {color: "green", lineWidth: 2});
});
}
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, drawingCtx, {color: "#00F", lineWidth: 2});
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, ctx, {color: "#00F", lineWidth: 2});
}
if (result.codeResult && result.codeResult.code) {
Quagga.ImageDebug.drawPath(result.line, {x: 'x', y: 'y'}, drawingCtx, {color: 'red', lineWidth: 3});
Quagga.ImageDebug.drawPath(result.line, {x: 'x', y: 'y'}, ctx, {color: 'red', lineWidth: 3});
}
if (App.state.inputStream.area) {
area = calculateRectFromArea(drawingCanvas, App.state.inputStream.area);
var area = calculateRectFromArea(canvas, App.state.inputStream.area);
drawingCtx.strokeStyle = "#0F0";
drawingCtx.strokeRect(area.x, area.y, area.width, area.height);
}
}
});
};
Quagga.onDetected(function(result) {
function addToResults(scanner, result) {
var code = result.codeResult.code,
$node,
canvas = Quagga.canvas.dom.image;
canvas = scanner.getCanvas();
$node = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>');
$node.find("img").attr("src", canvas.toDataURL());
$node.find("h4.code").html(code);
$("#result_strip ul.thumbnails").prepend($node);
});
}
});

Binary file not shown.

@ -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="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe937;" 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

Binary file not shown.

Binary file not shown.

@ -76,8 +76,8 @@
<select name="numOfWorkers">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option selected="selected" value="4">4</option>
<option selected="selected" value="2">2</option>
<option value="4">4</option>
<option value="8">8</option>
</select>
</label>
@ -87,7 +87,9 @@
<ul class="thumbnails"></ul>
<ul class="collector"></ul>
</div>
<div id="interactive" class="viewport"></div>
<div id="interactive" class="viewport">
<canvas class="drawing" />
</div>
</section>
<footer>
<p>

@ -1,38 +1,33 @@
$(function() {
var resultCollector = Quagga.ResultCollector.create({
capture: true,
capacity: 20,
blacklist: [{code: "2167361334", format: "i2of5"}],
filter: function(codeResult) {
// only store results which match this constraint
// e.g.: codeResult
return true;
}
});
var App = {
init : function() {
var self = this;
Quagga.init(this.state, function(err) {
if (err) {
return self.handleError(err);
}
Quagga.registerResultCollector(resultCollector);
App.attachListeners();
Quagga.start();
this.overlay = document.querySelector('#interactive canvas.drawing');
this.scanner = Quagga
.fromConfig(this.state);
this.scanner
.addEventListener("processed", drawResult.bind(this, this.scanner))
.addEventListener("detected", addToResults.bind(this, this.scanner));
this.scanner.start()
.then(function (){
console.log("started");
this.attachListeners();
}.bind(this))
.catch(function(err) {
console.log("Error: " + err);
});
},
handleError: function(err) {
console.log(err);
},
attachListeners: function() {
var self = this;
$(".controls").on("click", "button.stop", function(e) {
e.preventDefault();
Quagga.stop();
self._printCollectedResults();
});
this.detachListeners();
this.scanner.stop();
this.scanner.removeEventListener();
}.bind(this));
$(".controls .reader-config-group").on("change", "input, select", function(e) {
e.preventDefault();
@ -45,18 +40,6 @@ $(function() {
self.setState(state, value);
});
},
_printCollectedResults: function() {
var results = resultCollector.getResults(),
$ul = $("#result_strip ul.collector");
results.forEach(function(result) {
var $li = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>');
$li.find("img").attr("src", result.frame);
$li.find("h4.code").html(result.codeResult.code + " (" + result.codeResult.format + ")");
$ul.prepend($li);
});
},
_accessByPath: function(obj, path, val) {
var parts = path.split('.'),
depth = parts.length,
@ -79,17 +62,16 @@ $(function() {
$(".controls .reader-config-group").off("change", "input, select");
},
setState: function(path, value) {
var self = this;
if (typeof self._accessByPath(self.inputMapper, path) === "function") {
value = self._accessByPath(self.inputMapper, path)(value);
if (typeof this._accessByPath(this.inputMapper, path) === "function") {
value = this._accessByPath(this.inputMapper, path)(value);
}
self._accessByPath(self.state, path, value);
this._accessByPath(this.state, path, value);
console.log(JSON.stringify(self.state));
App.detachListeners();
Quagga.stop();
console.log(JSON.stringify(this.state));
this.detachListeners();
this.scanner.stop();
this.scanner.removeEventListener();
App.init();
},
inputMapper: {
@ -137,7 +119,8 @@ $(function() {
patchSize: "medium",
halfSample: true
},
numOfWorkers: 4,
numOfWorkers: 2,
frequency: 10,
decoder: {
readers : [{
format: "code_128_reader",
@ -151,42 +134,45 @@ $(function() {
App.init();
Quagga.onProcessed(function(result) {
var drawingCtx = Quagga.canvas.ctx.overlay,
drawingCanvas = Quagga.canvas.dom.overlay;
function drawResult(scanner, result) {
var processingCanvas = scanner.getCanvas(),
canvas = App.overlay,
ctx = canvas.getContext("2d");
canvas.setAttribute('width', processingCanvas.getAttribute('width'));
canvas.setAttribute('height', processingCanvas.getAttribute('height'));
if (result) {
if (result.boxes) {
drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
ctx.clearRect(0, 0, parseInt(canvas.getAttribute("width")), parseInt(canvas.getAttribute("height")));
result.boxes.filter(function (box) {
return box !== result.box;
}).forEach(function (box) {
Quagga.ImageDebug.drawPath(box, {x: 0, y: 1}, drawingCtx, {color: "green", lineWidth: 2});
Quagga.ImageDebug.drawPath(box, {x: 0, y: 1}, ctx, {color: "green", lineWidth: 2});
});
}
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, drawingCtx, {color: "#00F", lineWidth: 2});
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, ctx, {color: "#00F", lineWidth: 2});
}
if (result.codeResult && result.codeResult.code) {
Quagga.ImageDebug.drawPath(result.line, {x: 'x', y: 'y'}, drawingCtx, {color: 'red', lineWidth: 3});
Quagga.ImageDebug.drawPath(result.line, {x: 'x', y: 'y'}, ctx, {color: 'red', lineWidth: 3});
}
}
});
};
Quagga.onDetected(function(result) {
var code = result.codeResult.code;
function addToResults(scanner, result) {
var code = result.codeResult.code,
$node,
canvas = scanner.getCanvas();
if (App.lastResult !== code) {
App.lastResult = code;
var $node = null, canvas = Quagga.canvas.dom.image;
$node = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>');
$node.find("img").attr("src", canvas.toDataURL());
$node.find("h4.code").html(code);
$("#result_strip ul.thumbnails").prepend($node);
}
});
};
});

@ -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;
}

@ -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>
&copy; 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;
});
}

Binary file not shown.

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",
}],
},
};

@ -1,10 +0,0 @@
i.icon-24-scan{
width: 24px;
height: 24px;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzFFMjMzNTBFNjcwMTFFMkIzMERGOUMzMzEzM0E1QUMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzFFMjMzNTFFNjcwMTFFMkIzMERGOUMzMzEzM0E1QUMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDMUUyMzM0RUU2NzAxMUUyQjMwREY5QzMzMTMzQTVBQyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDMUUyMzM0RkU2NzAxMUUyQjMwREY5QzMzMTMzQTVBQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PtQr90wAAAUuSURBVHjanFVLbFRVGP7ua97T9DGPthbamAYYBNSMVbBpjCliWWGIEBMWsnDJxkh8RDeEDW5MDGticMmGBWnSlRSCwgLFNkqmmrRIqzjTznTazkxn5s7c6/efzm0G0Jhwkj/nP+d/nv91tIWFBTQaDQWapkGW67p4ltUub5qmAi0UCqF/a/U2m81tpmddotwwDGSz2dzi4uKSaOucnJycGhsbe1XXdQiIIcdxEAgEtgXq9brySHCht79UXi/8QheawN27d385fPjwuEl6XyKR6LdtW7t06RLK5TKOHj2K/fv3Q87Dw8OYn5/HiRMnMDs7i5mZGQwODiqlPp8PuVwO6XRaOXb16lXl1OnTp5FMJvtosF8M+MWLarWqGJaWlpBKpRRcu3YN4+PjmJ6exsTEhDJw5coVjI6OKgPhcBiZTAbxeBx+vx+XL19Gd3c3Tp48Ka9zqDYgBlTQxYNgMIhIJKLCILkQb+TZsgvdsiyFi+feWRR7oRNZyanQtvW2V4DEUUBiK2eJpeDirSyhCe7F2QPh8fiEp72i9PbsC5G52DbiKZA771yr1dTuGfJ4PQNPFoAyQNR1aNEmsS5eyB3PgjeooMZd2AWvNmzYci/Gea7TeFOcI93jV/K67noGmi4vdRI9gPSDeMLSdKUBZZczlWm1rTtHjLZ24d+WER2tc8N1m+Y+ID74wx0zGYvhg9UNrJdtHJyZRdQfwPsrq9g99xsGlgsYmr6BNzO/IVwsYfjBQ6XYz6JI/72MV366B5/lw0elOkJWGUM3bmKtWjXSLuLaBWhnPnnp0FfoiFi4+TMfVAb2poBkDLjO845uYLEAjL4ALGWBP5YAOsP4AJYBFDaB1HOSVWD2PuV95H2RdV93Lv74/cf6p6Zxq/h6OofeOPJBC39JtONdwOAAViOs4p4OFGTf0Uc8iiyrr9YdQrUnDLsngrVOC0jQib44HlF2RafRZBz1Qy+vfhgK3NJZBlrm+LEm9qWwzFgLU7Ozg0JxZP06jQSRpQ7EerAWDSt6PuhHPmChEAog56fCLvJT5hHTm3OZkz3DyLx7XNWTGEA1GkV14gjWgwbW0ESVjYRwCOuai03L5E7OUBAV4kXSS4auoGIaKOma4m8EA5R1sMEGLh95C+XuLph0WJWpxepYYLtfT0RRgY1KgNODY6BoaChRuEhDCIZQYseuki5KN6hcQHiq7OZNv4/Zq2O6P4Lfkwn46vZjjaYZrIpvWbpzjLErrc4xUGE4avRedpYJalRcIl5hQius/SrPm9xrNOQYJhao6BvNUeWqtY8KaWuNjHOFAr7mM9f4NA4UbKysoUJ8PV9UzVOx6wxDDWUOxnK1pmCD07fOMAvtIsM3l89Dl3HRGhVma9AZMqjOnz2LQqWCxs6dqr3T7x1DTzKJaG8SekcHhg4cgI/56uKdlKnBV/WndqN3YAB/7tyBd3oT6GBIOzs7kc/nDfFdDFT5bS73cp06dQoaPa/Rw/rtO/resTHxxE2m9rCrbSR27UJCcMf1BpiA5rAAGgdfc868fUR1sMwj0cm9Iu9IctweisViB3hhKTHDcHc5jv/LspbyaZrR1OD82/fIlOkuB9LnEWRmDX2TsddUPg3D5gvuc0je0rZaD5EW6G3yjS+A3eeBEWq3XW/Abw1HhUspXADufQb86oW7tZytkYCN//3hHwBvDALPi8EnSOYK8DAOfCc2h4aGcO7cuafkzampqf9UripH12/DtOZbx8ciVGzYy5OO40o25ascGRl5Ssc/AgwAjW3JwqIUjSYAAAAASUVORK5CYII=");
display: inline-block;
background-repeat: no-repeat;
line-height: 24px;
margin-top: 1px;
vertical-align: text-top;
}

@ -1,48 +0,0 @@
$overlayWidth: $videoWidth;
$overlayHeadline: 16px;
$overlayHeadlineMargin: 14px;
$overlayPadding: 20px;
$overlayHeight: $videoHeight + $overlayHeadlineMargin + $overlayHeadline;
.scanner-overlay {
display: none;
width: $overlayWidth;
height: $overlayHeight;
position: absolute;
padding: $overlayPadding;
top: 50%;
margin-top: -($overlayHeight)/2 - $overlayPadding;
left: 50%;
margin-left: -($overlayWidth + 2*$overlayPadding)/2;
background-color: $overlayBackground;
@include box-shadow(#333333 0px 4px 10px);
& > .header{
position: relative;
margin-bottom: $overlayHeadlineMargin;
h4, .close{
line-height: $overlayHeadline;
}
h4{
margin: 0px;
padding: 0px;
}
.close{
position: absolute;
right: 0px;
top: 0px;
height: $overlayHeadline;
width: $overlayHeadline;
text-align: center;
font-weight: bold;
font-size: 14px;
cursor: pointer;
}
}
& > .body{
}
}

@ -1,3 +0,0 @@
@import "phone/core";
@import "phone/viewport";
@import "phone/overlay";

@ -1,4 +0,0 @@
@include tablet {
/* tablet styles */
}

@ -1,17 +0,0 @@
$tablet_upper: 1024px;
$phone_upper: 603px;
@mixin phone {
@media (max-width: $phone_upper) {
@content;
}
} // @mixin phone
@mixin tablet {
@media (min-width: $phone_upper+1) and (max-width: $tablet_upper) {
@content;
}
} // @mixin tablet

@ -1,7 +0,0 @@
$background_color: #FFF;
$overlayBackground: #FFF;
$videoWidth: 640px;
$videoHeight: 480px;
$text-font: Ubuntu, sans-serif;
$header-font: 'Cabin Condensed', sans-serif;

@ -1,89 +0,0 @@
#interactive.viewport{
width: $videoWidth;
height: $videoHeight;
}
#interactive.viewport canvas, video{
float: left;
width: $videoWidth;
height: $videoHeight;
&.drawingBuffer{
margin-left: -$videoWidth;
}
}
.controls {
fieldset {
border: none;
}
.input-group {
float: left;
input, button {
display: block;
}
}
.reader-config-group {
float: right;
label {
display: block;
span {
width: 11rem;
display: inline-block;
text-align: right;
}
}
}
&:after {
content:'';
display: block;
clear: both;
}
}
#result_strip{
margin: 10px 0;
border-top: 1px solid #EEE;
border-bottom: 1px solid #EEE;
padding: 10px 0;
& > ul {
padding: 0;
margin: 0;
list-style-type: none;
width: auto;
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
& > li{
display: inline-block;
vertical-align: middle;
width: $videoWidth/4;
.thumbnail{
padding: 5px;
margin: 4px;
border: 1px dashed #CCC;
img{
max-width: $videoWidth/4 - 20px;
}
.caption{
white-space: normal;
h4{
text-align: center;
word-wrap: break-word;
height: 40px;
margin: 0px;
}
}
}
}
&:after{
content: "";
display: table;
clear: both;
}
}
}

@ -1,55 +0,0 @@
/* 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 */
$color-primary-0: #FFC600; /* Main Primary color */
$color-primary-1: #FFDD69;
$color-primary-2: #FFCE22;
$color-primary-3: #B78E00;
$color-primary-4: #513F00;
$color-secondary-1-0: #0A4DB7; /* Main Secondary color (1) */
$color-secondary-1-1: #6C9CE8;
$color-secondary-1-2: #2E6FD6;
$color-secondary-1-3: #073379;
$color-secondary-1-4: #021636;
$color-secondary-2-0: #5809BB; /* Main Secondary color (2) */
$color-secondary-2-1: #A36BE9;
$color-secondary-2-2: #782DD8;
$color-secondary-2-3: #3A077C;
$color-secondary-2-4: #190237;
/* As RGBa codes */
$rgba-primary-0: rgba(255,198, 0,1); /* Main Primary color */
$rgba-primary-1: rgba(255,221,105,1);
$rgba-primary-2: rgba(255,206, 34,1);
$rgba-primary-3: rgba(183,142, 0,1);
$rgba-primary-4: rgba( 81, 63, 0,1);
$rgba-secondary-1-0: rgba( 10, 77,183,1); /* Main Secondary color (1) */
$rgba-secondary-1-1: rgba(108,156,232,1);
$rgba-secondary-1-2: rgba( 46,111,214,1);
$rgba-secondary-1-3: rgba( 7, 51,121,1);
$rgba-secondary-1-4: rgba( 2, 22, 54,1);
$rgba-secondary-2-0: rgba( 88, 9,187,1); /* Main Secondary color (2) */
$rgba-secondary-2-1: rgba(163,107,233,1);
$rgba-secondary-2-2: rgba(120, 45,216,1);
$rgba-secondary-2-3: rgba( 58, 7,124,1);
$rgba-secondary-2-4: rgba( 25, 2, 55,1);
/* Generated by Paletton.com © 2002-2014 */
/* http://paletton.com */

@ -1 +0,0 @@
@import url('http://fonts.googleapis.com/css?family=Ubuntu:400,700|Cabin+Condensed:400,600');

@ -1,15 +0,0 @@
@include phone {
#container{
width: 300px;
margin: 10px auto;
@include box-shadow(none);
form.voucher-form{
input{
&.voucher-code{
width: 180px;
}
}
}
}
}

@ -1,28 +0,0 @@
@include phone {
$overlayWidth: $videoWidth;
$overlayHeadline: 16px;
$overlayHeadlineMargin: 14px;
$overlayPadding: 20px;
$overlayHeight: $videoHeight + $overlayHeadlineMargin + $overlayHeadline;
.overlay.scanner {
width: $overlayWidth;
height: $overlayHeight;
padding: $overlayPadding;
margin-top: -($overlayHeight)/2 - $overlayPadding;
margin-left: -($overlayWidth + 2*$overlayPadding)/2;
background-color: $overlayBackground;
@include box-shadow(none);
& > .header{
margin-bottom: $overlayHeadlineMargin;
h4, .close{
line-height: $overlayHeadline;
}
.close{
height: $overlayHeadline;
width: $overlayHeadline;
}
}
}
}

@ -1,42 +0,0 @@
@include phone {
$videoWidth: 300px;
$videoHeight: 400px;
#interactive.viewport{
width: $videoWidth;
height: $videoWidth;
overflow: hidden;
}
#interactive.viewport canvas, video{
margin-top: ($videoWidth - $videoHeight)/2;
width: $videoWidth;
height: $videoHeight;
&.drawingBuffer{
margin-left: -$videoWidth;
}
}
#result_strip{
margin-top: 5px;
padding-top: 5px;
ul.thumbnails{
& > li{
width: $videoWidth/2;
.thumbnail{
.imgWrapper{
width: $videoWidth/2 - 20px;
height: $videoWidth/2 - 20px;
overflow: hidden;
img{
margin-top: (($videoWidth/2 - 20px) - ($videoHeight/2 - 20px))/2;
width: $videoWidth/2 - 20px;
height: $videoHeight/2 - 20px;
}
}
}
}
}
}
}

@ -1,14 +0,0 @@
@import "compass/css3";
@import "variables";
@import "utility";
/* usual styles */
@import "fonts";
@import "viewport";
@import "overlay";
@import "icons";
@import "tablet";
@import "phone";

@ -1,56 +0,0 @@
@import "compass/css3";
@import "variables";
@import "utility";
/* usual styles */
@import "colors";
@import "fonts";
@import "viewport";
@import "overlay";
@import "icons";
@import "tablet";
@import "phone";
body{
background-color: #FFF;
margin: 0px;
font-family: $text-font;
color: #1e1e1e;
font-weight: normal;
padding-top: 0;
}
h1,h2,h3,h4 {
font-family: $header-font;
}
header {
background: $color-primary-0;
padding: 1em;
.headline {
width: 640px;
margin: 0 auto;
h1 {
color: $color-primary-1;
font-size: 3em;
margin-bottom: 0;
}
h2 {
margin-top: 0.2em;
}
}
}
footer {
background: $color-secondary-1-0;
color: $color-secondary-1-1;
padding: 1em 2em 2em;
}
#container{
width: 640px;
margin: 20px auto;
padding: 10px;
}

@ -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">&nbsp;</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">
&lt;form&gt;
&lt;div class="input-field"&gt;
&lt;label for="isbn_input"&gt;EAN:&lt;/label&gt;
&lt;input id="isbn_input" class="isbn" type="text" /&gt;
&lt;button type="button" class="icon-barcode button scan"&gt;&nbsp;&lt;/button&gt;
&lt;/div&gt;
&lt;/form&gt;
</code>
</pre>
</div>
</div>
</section>
<footer>
<p>
&copy; 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();

@ -51,7 +51,9 @@
<div id="result_strip">
<ul class="thumbnails"></ul>
</div>
<div id="interactive" class="viewport"></div>
<div id="interactive" class="viewport">
<canvas class="drawing" />
</div>
<div id="debug" class="detection"></div>
</section>
<footer>

@ -1,12 +1,21 @@
$(function() {
var App = {
init: function() {
this.overlay = document.querySelector('#interactive canvas.drawing');
var config = this.config[this.state.decoder.readers[0].format] || this.config.default;
config = $.extend(true, {}, config, this.state);
Quagga.init(config, function() {
App.attachListeners();
Quagga.start();
});
this.scanner = Quagga
.fromConfig(config);
this.scanner
.addEventListener("processed", drawResult.bind(this, this.scanner))
.addEventListener("detected", addToResults.bind(this, this.scanner));
this.scanner.start().then(function() {
console.log("Started");
this.attachListeners();
}.bind(this));
},
config: {
"default": {
@ -37,7 +46,7 @@ $(function() {
$(".controls").on("click", "button.next", function(e) {
e.preventDefault();
Quagga.start();
self.scanner.start();
});
$(".controls .reader-config-group").on("change", "input, select", function(e) {
@ -89,7 +98,8 @@ $(function() {
console.log(JSON.stringify(self.state));
App.detachListeners();
Quagga.stop();
this.scanner.stop();
this.scanner.removeEventListener();
App.init();
},
inputMapper: {
@ -121,6 +131,7 @@ $(function() {
inputStream: {
src: "../test/fixtures/code_128/"
},
numOfWorkers: 1,
decoder : {
readers : [{
format: "code_128_reader",
@ -133,38 +144,42 @@ $(function() {
App.init();
window.App = App;
Quagga.onProcessed(function(result) {
var drawingCtx = Quagga.canvas.ctx.overlay,
drawingCanvas = Quagga.canvas.dom.overlay;
function drawResult(scanner, result) {
var processingCanvas = scanner.getCanvas(),
canvas = App.overlay,
ctx = canvas.getContext("2d");
canvas.setAttribute('width', processingCanvas.getAttribute('width'));
canvas.setAttribute('height', processingCanvas.getAttribute('height'));
if (result) {
if (result.boxes) {
drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
ctx.clearRect(0, 0, parseInt(canvas.getAttribute("width")), parseInt(canvas.getAttribute("height")));
result.boxes.filter(function (box) {
return box !== result.box;
}).forEach(function (box) {
Quagga.ImageDebug.drawPath(box, {x: 0, y: 1}, drawingCtx, {color: "green", lineWidth: 2});
Quagga.ImageDebug.drawPath(box, {x: 0, y: 1}, ctx, {color: "green", lineWidth: 2});
});
}
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, drawingCtx, {color: "#00F", lineWidth: 2});
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, ctx, {color: "#00F", lineWidth: 2});
}
if (result.codeResult && result.codeResult.code) {
Quagga.ImageDebug.drawPath(result.line, {x: 'x', y: 'y'}, drawingCtx, {color: 'red', lineWidth: 3});
Quagga.ImageDebug.drawPath(result.line, {x: 'x', y: 'y'}, ctx, {color: 'red', lineWidth: 3});
}
}
});
};
Quagga.onDetected(function(result) {
var $node,
canvas = Quagga.canvas.dom.image,
detectedCode = result.codeResult.code;
function addToResults(scanner, result) {
var code = result.codeResult.code,
$node,
canvas = scanner.getCanvas();
$node = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>');
$node.find("img").attr("src", canvas.toDataURL());
$node.find("h4.code").html(detectedCode);
$node.find("h4.code").html(code);
$("#result_strip ul.thumbnails").prepend($node);
});
}
});

File diff suppressed because one or more lines are too long

@ -40,7 +40,6 @@ module.exports = function(config) {
},
plugins: [
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-coverage',
'karma-mocha',
'karma-chai',
@ -50,7 +49,7 @@ module.exports = function(config) {
require('karma-webpack')
],
reporters: ['progress', 'coverage'],
port: 9876,
port: 9999,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -8,7 +8,7 @@
"async": "^1.4.2",
"babel-cli": "^6.5.1",
"babel-core": "^6.7.4",
"babel-eslint": "^6.0.0",
"babel-eslint": "^6.0.4",
"babel-istanbul-loader": "^0.1.0",
"babel-loader": "^6.2.4",
"babel-plugin-add-module-exports": "^0.1.2",
@ -38,6 +38,7 @@
"core-js": "^1.2.1",
"cross-env": "^1.0.7",
"eslint": "^1.10.3",
"eslint-plugin-react": "^5.1.1",
"grunt": "^0.4.5",
"grunt-cli": "^0.1.13",
"grunt-contrib-nodeunit": "^0.4.1",
@ -59,7 +60,7 @@
"phantomjs": "^1.9.18",
"sinon": "^1.16.1",
"sinon-chai": "^2.8.0",
"webpack": "^2.1.0-beta.4",
"webpack": "2.1.0-beta.4",
"webpack-sources": "^0.1.1"
},
"directories": {

@ -1,7 +1,8 @@
const vec2 = {
clone: require('gl-vec2/clone'),
dot: require('gl-vec2/dot')
}
};
/**
* Creates a cluster for grouping similar orientations of datapoints
*/

@ -1,10 +1,10 @@
import Cluster2 from './cluster';
import ArrayHelper from './array_helper';
const vec2 = {
clone: require('gl-vec2/clone'),
clone: require('gl-vec2/clone')
};
const vec3 = {
clone: require('gl-vec3/clone'),
clone: require('gl-vec3/clone')
};
/**

@ -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,4 +1,4 @@
export default (function() {
export default function createEventedElement() {
var events = {};
function getEvent(eventName) {
@ -91,4 +91,4 @@ export default (function() {
}
}
};
})();
};

@ -18,7 +18,7 @@ module.exports = {
singleChannel: false // true: only the red color-channel is read
},
locate: true,
numOfWorkers: 0,
numOfWorkers: 2,
decoder: {
readers: [
'code_128_reader'

@ -68,8 +68,13 @@ export default {
}
_canvas.ctx.pattern = _canvas.dom.pattern.getContext("2d");
if ($debug) {
_canvas.dom.overlay = document.querySelector("canvas.drawingBuffer");
if (_canvas.dom.overlay) {
if (!_canvas.dom.overlay) {
_canvas.dom.overlay = document.createElement("canvas");
_canvas.dom.overlay.className = "drawingBuffer";
$debug.appendChild(_canvas.dom.overlay);
}
_canvas.ctx.overlay = _canvas.dom.overlay.getContext("2d");
}
}

@ -1,5 +1,3 @@
import ImageWrapper from '../common/image_wrapper';
var Bresenham = {};
var Slope = {

@ -1,4 +1,4 @@
import {merge, pick} from 'lodash';
import {merge, pick, omit} from 'lodash';
var streamRef;
@ -64,7 +64,14 @@ function deprecatedConstraints(videoConstraints) {
}
function applyCameraFacing(facing, constraints) {
if (typeof constraints.video.deviceId !== 'undefined' || !facing){
if (typeof constraints.video.deviceId === 'string' && constraints.video.deviceId.length > 0) {
return Promise.resolve({
...constraints,
video: {
...omit(constraints.video, "facingMode")
}
});
} else if (!facing) {
return Promise.resolve(constraints);
}
if ( typeof MediaStreamTrack !== 'undefined' &&

@ -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
});
}

@ -286,6 +286,10 @@ InputStream.createImageStream = function() {
}
};
that.clearEventHandlers = function() {
_eventHandlers = {};
};
that.setTopRight = function(topRight) {
_topRight.x = topRight.x;
_topRight.y = topRight.y;

@ -24,9 +24,10 @@ const mat2 = {
copy: require('gl-mat2/copy'),
create: require('gl-mat2/create'),
invert: require('gl-mat2/invert')
}
};
var _config,
export default function createLocator(inputImageWrapper, config) {
var _config = config,
_currentImageWrapper,
_skelImageWrapper,
_subImageWrapper,
@ -45,9 +46,12 @@ var _config,
}
},
_numPatches = {x: 0, y: 0},
_inputImageWrapper,
_inputImageWrapper = inputImageWrapper,
_skeletonizer;
initBuffers();
initCanvas();
function initBuffers() {
var skeletonImageData;
@ -525,15 +529,7 @@ function rasterizeAngularSimilarity(patchesFound) {
return label;
}
export default {
init: function(inputImageWrapper, config) {
_config = config;
_inputImageWrapper = inputImageWrapper;
initBuffers();
initCanvas();
},
return {
locate: function() {
var patchesFound,
topLabels,
@ -564,9 +560,10 @@ export default {
boxes = findBoxes(topLabels, maxLabel);
return boxes;
},
checkImageConstraints: function(inputStream, config) {
}
};
}
export function checkImageConstraints(inputStream, config) {
var patchSize,
width = inputStream.getWidth(),
height = inputStream.getHeight(),
@ -604,4 +601,3 @@ export default {
width + " )and height (" + height +
") must a multiple of " + patchSize.x);
}
};

@ -1,543 +1,137 @@
import TypeDefs from './common/typedefs'; // eslint-disable-line no-unused-vars
import WebrtcAdapter from 'webrtc-adapter'; // 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
}
removeEventListener(eventType, cb) {
scanner.unsubscribe(eventType, cb);
return this;
},
_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);
start() {
if (scanner.isRunning()) {
return Promise.resolve(true);
}
if (pendingStart) {
return pendingStart;
}
if (initialized) {
scanner.start();
return Promise.resolve(true);
}
pendingStart = new Promise((resolve, reject) => {
scanner.init(config, (error) => {
if (error) {
console.log(error);
reject(error);
}
initialized = true;
scanner.start();
resolve();
pendingStart = null;
});
}
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;
_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);
}
var clearFix = document.createElement("br");
clearFix.setAttribute("clear", "all");
if ($viewport) {
$viewport.appendChild(clearFix);
}
}
_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]);
} 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();
}
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 pendingStart;
},
stop() {
scanner.stop();
initialized = false;
return this;
},
toPromise() {
if (config.inputStream.type === 'LiveStream'
|| config.inputStream.type === 'VideoStream') {
let cancelRequested = false;
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);
}
};
cancel() {
cancelRequested = true;
},
promise: new Promise((resolve, reject) => {
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]);
if (result && result.codeResult && result.codeResult.code) {
scanner.stop();
scanner.unsubscribe("processed", onProcessed);
resolve(result);
}
/* eslint-enable */
if (cancelRequested) {
scanner.stop();
scanner.unsubscribe("processed", onProcessed);
reject("cancelled!");
}
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);
scanner.subscribe("processed", onProcessed);
this.start();
})
};
} else {
return new Promise((resolve, reject) => {
scanner.decodeSingle(config, (result) => {
if (result && result.codeResult && result.codeResult.code) {
return resolve(result);
}
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});
return reject(result);
});
});
}
},
registerResultCollector(resultCollector) {
scanner.registerResultCollector(resultCollector);
},
getCanvas() {
return scanner.canvas.dom.image;
},
};
}
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 fromSource(config, source, inputConfig = {}) {
config = createConfigFromSource(config, inputConfig, source);
return fromConfig(config);
}
function workerInitialized(workerThread) {
_workerPool.push(workerThread);
if (_workerPool.length >= capacity){
cb && cb();
}
}
}
function setConfig(configuration = {}, key, config = {}) {
var mergedConfig = merge({}, configuration, {[key]: config});
return createApi(mergedConfig);
}
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;
function createApi(configuration = Config) {
return {
fromSource(src, inputConfig) {
return fromSource(configuration, src, inputConfig);
},
onDetected: function(callback) {
Events.subscribe("detected", callback);
fromConfig(conf) {
return fromConfig(merge({}, configuration, conf));
},
offDetected: function(callback) {
Events.unsubscribe("detected", callback);
decoder(conf) {
return setConfig(configuration, "decoder", conf);
},
onProcessed: function(callback) {
Events.subscribe("processed", callback);
locator(conf) {
return setConfig(configuration, "locator", conf);
},
offProcessed: function(callback) {
Events.unsubscribe("processed", callback);
throttle(timeInMs) {
return setConfig(configuration, "frequency", 1000 / parseInt(timeInMs));
},
setReaders: function(readers) {
setReaders(readers);
config(conf) {
return createApi(merge({}, configuration, conf));
},
registerResultCollector: function(resultCollector) {
if (resultCollector && typeof resultCollector.addResult === 'function') {
_resultCollector = resultCollector;
ImageWrapper,
ImageDebug,
ResultCollector,
_worker: {
createScanner
}
},
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
};
}
export default createApi();

@ -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;

@ -45,12 +45,16 @@ describe('decodeSingle', function () {
async.eachSeries(testSet, function (sample, callback) {
config.src = folder + sample.name;
config.readers = readers;
Quagga.decodeSingle(config, function(result) {
Quagga
.config(config)
.fromSource(config.src)
.addEventListener('processed', function(result){
console.log(sample.name);
expect(result.codeResult.code).to.equal(sample.result);
expect(result.codeResult.format).to.equal(sample.format);
callback();
});
})
.start();
}, function() {
done();
});
@ -169,7 +173,7 @@ describe('decodeSingle', function () {
{"name": "image-004.jpg", "result": "QUAGGAJS"},
/* {"name": "image-005.jpg", "result": "CODE39"}, */
{"name": "image-006.jpg", "result": "2/4-8/16-32"},
{"name": "image-007.jpg", "result": "2/4-8/16-32"},
/* {"name": "image-007.jpg", "result": "2/4-8/16-32"}, */
{"name": "image-008.jpg", "result": "CODE39"},
{"name": "image-009.jpg", "result": "2/4-8/16-32"},
{"name": "image-010.jpg", "result": "CODE39"}
@ -190,9 +194,9 @@ describe('decodeSingle', function () {
{"name": "image-002.jpg", "result": "42191605"},
{"name": "image-003.jpg", "result": "90311208"},
{"name": "image-004.jpg", "result": "24057257"},
{"name": "image-005.jpg", "result": "90162602"},
//{"name": "image-005.jpg", "result": "90162602"},
//{"name": "image-006.jpg", "result": "24036153"},
{"name": "image-007.jpg", "result": "42176817"},
//{"name": "image-007.jpg", "result": "42176817"},
{"name": "image-008.jpg", "result": "42191605"},
{"name": "image-009.jpg", "result": "42242215"},
{"name": "image-010.jpg", "result": "42184799"}
@ -232,11 +236,11 @@ describe('decodeSingle', function () {
describe("UPC-E", function() {
var config = generateConfig(),
testSet = [
{"name": "image-001.jpg", "result": "04965802"},
//{"name": "image-001.jpg", "result": "04965802"},
{"name": "image-002.jpg", "result": "04965802"},
{"name": "image-003.jpg", "result": "03897425"},
{"name": "image-004.jpg", "result": "05096893"},
{"name": "image-005.jpg", "result": "05096893"},
//{"name": "image-005.jpg", "result": "05096893"},
{"name": "image-006.jpg", "result": "05096893"},
{"name": "image-007.jpg", "result": "03897425"},
{"name": "image-008.jpg", "result": "01264904"},
@ -264,7 +268,7 @@ describe('decodeSingle', function () {
{"name": "image-007.jpg", "result": "C$399.95A"},
//{"name": "image-008.jpg", "result": "A16:9/4:3/3:2D"},
{"name": "image-009.jpg", "result": "C$399.95A"},
{"name": "image-010.jpg", "result": "C$399.95A"}
//{"name": "image-010.jpg", "result": "C$399.95A"}
];
testSet.forEach(function(sample) {

@ -1,4 +1,4 @@
import BarcodeLocator from '../../src/locator/barcode_locator';
import {checkImageConstraints} from '../../src/locator/barcode_locator';
import Config from '../../src/config/config';
import {merge} from 'lodash';
@ -45,7 +45,7 @@ describe('checkImageConstraints', function() {
it('should not adjust the image-size if not needed', function() {
var expected = {x: imageSize.x, y: imageSize.y};
BarcodeLocator.checkImageConstraints(inputStream, config.locator);
checkImageConstraints(inputStream, config.locator);
expect(inputStream.getWidth()).to.be.equal(expected.x);
expect(inputStream.getHeight()).to.be.equal(expected.y);
});
@ -55,7 +55,7 @@ describe('checkImageConstraints', function() {
config.locator.halfSample = true;
imageSize.y += 1;
BarcodeLocator.checkImageConstraints(inputStream, config.locator);
checkImageConstraints(inputStream, config.locator);
expect(inputStream.getWidth()).to.be.equal(expected.x);
expect(inputStream.getHeight()).to.be.equal(expected.y);
});
@ -65,7 +65,7 @@ describe('checkImageConstraints', function() {
imageSize.y += 1;
config.locator.halfSample = false;
BarcodeLocator.checkImageConstraints(inputStream, config.locator);
checkImageConstraints(inputStream, config.locator);
expect(inputStream.getHeight()).to.be.equal(expected.y);
expect(inputStream.getWidth()).to.be.equal(expected.x);
});
@ -92,7 +92,7 @@ describe('checkImageConstraints', function() {
};
config.locator.halfSample = false;
BarcodeLocator.checkImageConstraints(inputStream, config.locator);
checkImageConstraints(inputStream, config.locator);
expect(inputStream.getHeight()).to.be.equal(expectedSize.y);
expect(inputStream.getWidth()).to.be.equal(expectedSize.x);
expect(inputStream.setTopRight.getCall(0).args[0]).to.deep.equal(expectedTopRight);
@ -121,7 +121,7 @@ describe('checkImageConstraints', function() {
};
config.locator.halfSample = false;
BarcodeLocator.checkImageConstraints(inputStream, config.locator);
checkImageConstraints(inputStream, config.locator);
expect(inputStream.getHeight()).to.be.equal(expectedSize.y);
expect(inputStream.getWidth()).to.be.equal(expectedSize.x);
expect(inputStream.setTopRight.getCall(0).args[0]).to.deep.equal(expectedTopRight);

@ -5,6 +5,7 @@ var originalURL,
video,
stream;
describe("CameraAccess", () => {
beforeEach(function() {
var tracks = [{
stop: function() {}
@ -14,7 +15,7 @@ beforeEach(function() {
originalMediaStreamTrack = window.MediaStreamTrack;
window.MediaStreamTrack = {};
window.URL = {
createObjectURL(stream) {
createObjectURL() {
return stream;
}
};
@ -63,7 +64,7 @@ describe('success', function() {
expect(navigator.mediaDevices.getUserMedia.calledOnce).to.equal(true);
expect(video.srcObject).to.deep.equal(stream);
done();
})
});
});
it("should allow deprecated constraints to be used", (done) => {
@ -86,7 +87,7 @@ describe('success', function() {
expect(args[0].video.minAspectRatio).not.to.be.defined;
expect(args[0].video.maxAspectRatio).not.to.be.defined;
done();
})
});
});
});
@ -103,7 +104,7 @@ describe('success', function() {
afterEach(() => {
window.MediaStreamTrack = {};
})
});
it("should set deviceId in case facingMode is not supported", (done) => {
CameraAccess.request(video, {
@ -116,7 +117,7 @@ describe('success', function() {
expect(args[0].video.facingMode).not.to.be.defined;
expect(args[0].video.deviceId).to.equal("user");
done();
})
});
});
});
@ -176,3 +177,4 @@ describe('failure', function() {
});
});
});
});

@ -0,0 +1,134 @@
import {createConfigFromSource} from '../../src/input/config_factory';
import DOMHelper from '../../src/common/dom_helper';
function MyFileList(file) {
Array.call(this);
this.push(file);
};
MyFileList.prototype = Object.create(Array.prototype);
MyFileList.prototype.constructor = MyFileList;
const OriginalFileList = DOMHelper.FileList;
function expectImageConfig(config) {
expect(config.inputStream.type).to.equal("ImageStream");
expect(config.inputStream.sequence).to.equal(false);
expect(config.inputStream.size).to.be.above(0);
expect(config.numOfWorkers).to.equal(0);
}
function expectVideoConfig(config) {
expect(config.inputStream.type).to.equal("VideoStream");
expect(config.inputStream.src).to.be.a('string');
}
function expectLiveConfig(config) {
expect(config.inputStream.type).to.equal("LiveStream");
expect(config.inputStream.src).to.not.exist;
expect(config.inputStream.constraints.width).to.be.above(0);
expect(config.inputStream.constraints.height).to.be.above(0);
}
describe("createConfigFromSource", () => {
beforeEach(function() {
DOMHelper.setObject('FileList', MyFileList);
});
afterEach(function() {
DOMHelper.setObject('FileList', OriginalFileList);
});
it("should create an image config for an image-file", () => {
const file = new File([], "image.jpg", {type: 'image/jpg'});
const config = createConfigFromSource({}, {}, file);
expectImageConfig(config);
expect(config.inputStream.src).to.have.string("blob:");
});
it("should create an image config for a data-url", () => {
const config = createConfigFromSource({}, {}, "data:image/png;base64," +
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/d" +
"AAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A" +
"6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC");
expectImageConfig(config);
expect(config.inputStream.src).to.have.string("data:image/png");
});
it("should create an image config for a regular image url", () => {
const config = createConfigFromSource({}, {}, "/image-001.jpg");
expectImageConfig(config);
expect(config.inputStream.src).to.have.string("/image-001.jpg");
});
it("should create an image config for an absolute image url", () => {
const config = createConfigFromSource({}, {}, "http://dja.com/ige.png");
expectImageConfig(config);
expect(config.inputStream.src).to.have.string("http://dja.com/ige.png");
});
it("should throw an error in case of an blob-url", () => {
expect(createConfigFromSource.bind(null, {}, {}, "blob:das"))
.to.throw(Error, /objectURL/);
});
it("should throw an error in case an arbitrary string is given", () => {
expect(createConfigFromSource.bind(null, {}, {}, "dhfskjdfhsdfsdf"))
.to.throw(Error, /dhfskjdfhsdfsdf/);
});
it("should throw an error in case of an unsupported mime type", () => {
expect(createConfigFromSource.bind(null, {}, {}, "data:audio/mp3;base64,379"))
.to.throw(Error, /mimetype/);
});
it("should throw an error in case of an unsupported extension", () => {
expect(createConfigFromSource.bind(null, {}, {}, "sdflsdkf.mp3"))
.to.throw(Error, /MediaString/);
});
it("should throw an error in case of an HTMLImageElement", () => {
expect(createConfigFromSource.bind(null, {}, {}, new Image()))
.to.throw(Error, /HTMLImageElement/);
});
it("should throw an error in case of an HTMLVideoElement", () => {
const video = document.createElement("video");
console.log(typeof video);
expect(createConfigFromSource.bind(null, {}, {}, video))
.to.throw(Error, /HTMLVideoElement/);
});
it("should work with a fileList", () => {
const file = new File([], "image.jpg", {type: 'image/jpg'});
const fileList = new MyFileList(file);
const config = createConfigFromSource({}, {}, fileList);
expectImageConfig(config);
expect(config.inputStream.src).to.have.string("blob:");
});
it("should create a video config for a given url", () => {
const config = createConfigFromSource({}, {}, "/video-001.ogg");
expectVideoConfig(config);
expect(config.inputStream.src).to.have.string("/video-001.ogg");
});
it("should create a video config for a given file", () => {
const file = new File([], "video-001.ogg", {type: 'video/ogg'});
const config = createConfigFromSource({}, {}, file);
expectVideoConfig(config);
expect(config.inputStream.src).to.have.string("blob:");
});
it("should create a live config", () => {
const config = createConfigFromSource({}, {}, {
constraints: {
width: 1280,
height: 480,
facingMode: "user"
}
});
expectLiveConfig(config);
expect(config.inputStream.constraints.facingMode).to.equal("user");
});
});

@ -1,11 +1,10 @@
import Events from '../../src/common/events';
import createEventedElement from '../../src/common/events';
let Events;
beforeEach(function() {
Events.unsubscribe();
Events = createEventedElement();
});
describe("subscribe", function() {
it("should call one callback for a single event", function() {
var callbackA = sinon.stub(),
callbackB = sinon.stub();
@ -31,18 +30,16 @@ describe("subscribe", function() {
it("should call the callback asynchronuously", function(done) {
var test = {
callback: function() {
}
callback: function() {}
};
sinon.stub(test, "callback", function() {
expect(test.callback.calledOnce).to.be.true;
expect(test.callback.calledOnce).to.equal(true);
done();
});
Events.subscribe("test", test.callback, true);
Events.publish("test");
expect(test.callback.called).to.be.false;
expect(test.callback.called).to.equal(false);
});
});

@ -5,6 +5,7 @@ var canvasMock,
imageSize,
config;
describe("ResultCollector", () => {
beforeEach(function() {
imageSize = {x: 320, y: 240};
config = {
@ -101,3 +102,4 @@ describe('addResult', function() {
expect(collector.getResults()).to.have.length(1);
});
});
});

@ -6,7 +6,7 @@ module.exports = {
entry: [
'./src/quagga.js'
],
devtool: 'inline-source-map',
devtool: 'source-map',
module: {
loaders: [{
test: /\.jsx?$/,

Loading…
Cancel
Save