Compare commits

...

36 Commits

Author SHA1 Message Date
Christoph Oberhofer 0d1d2a0b32 added 1.0.0-beta.1 version 9 years ago
Christoph Oberhofer 9221529e16 Merged feature/109 to 1.0 9 years ago
Christoph Oberhofer 23a3481f7d Fixed initialization-error 9 years ago
Christoph Oberhofer 31d2a086cd Fixed live-view example 9 years ago
Christoph Oberhofer 2dca130bee Fixed static images example 9 years ago
Christoph Oberhofer 031995dae1 Sandbox: added production-config for webpack 9 years ago
Christoph Oberhofer e42fc6d221 Fixed file-input example; fixed styling 9 years ago
Christoph Oberhofer cb8502da30 Fixed access to canvas 9 years ago
Christoph Oberhofer 5ee361bcff Fixed tests 9 years ago
Christoph Oberhofer 435430b8fa sandbox: finished result view; fixed some styling issues 9 years ago
Christoph Oberhofer bad79020a9 Linked config-view to scanner 9 years ago
Christoph Oberhofer 7236bb7b53 Added frequency option 9 years ago
Christoph Oberhofer 5d4aa1b519 added es-lint config to sandbox 9 years ago
Christoph Oberhofer 0b1f1dc3bd Fixed drawer; added manifest file 9 years ago
Christoph Oberhofer d6f1ed59ad Started working on sandbox 9 years ago
Christoph Oberhofer 308544c0dd Fixed react example 9 years ago
Christoph Oberhofer 45331c0d8e fixed multiple-example 9 years ago
Christoph Oberhofer 5231824d98 Moved DOM objects to helper module 9 years ago
Christoph Oberhofer b9efb07456 Fixed tests 9 years ago
Christoph Oberhofer ec8f4377c1 Added Configuration-Factory for various modes of input 9 years ago
Christoph Oberhofer e7c62221c1 merged master; fixed tests 9 years ago
Christoph Oberhofer 2f2ed46cf3 Fixed Events; Added example for multiple types of barcodes 9 years ago
Christoph Oberhofer be70d72e0b Updated file-based example 9 years ago
Christoph Oberhofer 5c2295e103 Added file-input example 9 years ago
Christoph Oberhofer 1f3ff9bc70 Added example based on react 9 years ago
Christoph Oberhofer bc2d8c5f5e Updated scan-to-input example 9 years ago
Christoph Oberhofer 6c3772eda3 Added scan-to-input example 9 years ago
Christoph Oberhofer b44dd76a07 Removed unused methods 9 years ago
Christoph Oberhofer c277903dac API adaptations 9 years ago
Christoph Oberhofer 19f74e6106 Removed unused methods 9 years ago
Christoph Oberhofer f1a963f4a0 video working 9 years ago
Christoph Oberhofer f8bc6a705c Fixed tests 9 years ago
Christoph Oberhofer 12a39e28e5 Make web-workers work independently 9 years ago
Christoph Oberhofer 633eabe86c Moved remaining code to instances 9 years ago
Christoph Oberhofer b733c2b7fd Creating config programatically 9 years ago
Christoph Oberhofer f3f656f3e7 Extracted scanner-component 9 years ago

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

12680
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"; .collapsable-source pre {
/* usual styles */ font-size: small;
/* 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;
} }
/* line 6, ../sass/_viewport.scss */ .input-field {
#interactive.viewport canvas, video { display: flex;
float: left; align-items: center;
width: 640px; width: 260px;
height: 480px;
} }
/* line 10, ../sass/_viewport.scss */
#interactive.viewport canvas.drawingBuffer, video.drawingBuffer { .input-field label {
margin-left: -640px; 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;
}
input[type=file] {
display: none;
} }
/* line 16, ../sass/_viewport.scss */ /* line 16, ../sass/_viewport.scss */
@ -117,98 +164,8 @@
clear: both; 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) { @media (max-width: 603px) {
/* line 5, ../sass/phone/_viewport.scss */ /* line 20, ../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 { #result_strip {
margin-top: 5px; margin-top: 5px;
padding-top: 5px; padding-top: 5px;
@ -230,34 +187,14 @@ i.icon-24-scan {
height: 180px; height: 180px;
} }
} }
@media (max-width: 603px) {
/* line 8, ../sass/phone/_overlay.scss */ html {
.overlay.scanner { height: 100%;
width: 640px; }
height: 510px; html, body {
padding: 20px; min-height: 100%;
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;
}
} }
/* line 15, ../sass/styles.scss */
body { body {
background-color: #FFF; background-color: #FFF;
margin: 0px; margin: 0px;
@ -265,6 +202,9 @@ body {
color: #1e1e1e; color: #1e1e1e;
font-weight: normal; font-weight: normal;
padding-top: 0; padding-top: 0;
box-sizing: border-box;
padding-bottom: 96px;
position: relative;
} }
/* line 24, ../sass/styles.scss */ /* line 24, ../sass/styles.scss */
@ -279,18 +219,18 @@ header {
} }
/* line 31, ../sass/styles.scss */ /* line 31, ../sass/styles.scss */
header .headline { header .headline {
width: 640px; max-width: 640px;
margin: 0 auto; margin: 0 auto;
} }
/* line 34, ../sass/styles.scss */ /* line 34, ../sass/styles.scss */
header .headline h1 { header .headline h1 {
color: #FFDD69;
font-size: 3em; font-size: 3em;
margin-bottom: 0; margin-bottom: 0;
} }
/* line 39, ../sass/styles.scss */ /* line 39, ../sass/styles.scss */
header .headline h2 { .headline h2 {
margin-top: 0.2em; margin-top: 0.2em;
color: #FFDD69;
} }
/* line 45, ../sass/styles.scss */ /* line 45, ../sass/styles.scss */
@ -298,11 +238,28 @@ footer {
background: #0A4DB7; background: #0A4DB7;
color: #6C9CE8; color: #6C9CE8;
padding: 1em 2em 2em; padding: 1em 2em 2em;
position: absolute;
bottom: 0;
left: 0;
right: 0;
} }
/* line 51, ../sass/styles.scss */ /* line 51, ../sass/styles.scss */
#container { .container {
width: 640px;
margin: 20px auto; margin: 20px auto;
padding: 10px; 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"> <div id="result_strip">
<ul class="thumbnails"></ul> <ul class="thumbnails"></ul>
</div> </div>
<div id="interactive" class="viewport"></div> <div id="interactive" class="viewport">
<canvas class="drawing" />
</div>
<div id="debug" class="detection"></div> <div id="debug" class="detection"></div>
</section> </section>
<footer> <footer>

@ -1,6 +1,7 @@
$(function() { $(function() {
var App = { var App = {
init: function() { init: function() {
this.overlay = document.querySelector('#interactive canvas.drawing');
App.attachListeners(); App.attachListeners();
}, },
attachListeners: function() { attachListeners: function() {
@ -8,14 +9,14 @@ $(function() {
$(".controls input[type=file]").on("change", function(e) { $(".controls input[type=file]").on("change", function(e) {
if (e.target.files && e.target.files.length) { 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) { $(".controls button").on("click", function(e) {
var input = document.querySelector(".controls input[type=file]"); var input = document.querySelector(".controls input[type=file]");
if (input.files && input.files.length) { 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 .reader-config-group").off("change", "input, select");
$(".controls button").off("click"); $(".controls button").off("click");
}, },
decode: function(src) { decode: function(file) {
var self = this, this.detachListeners();
config = $.extend({}, self.state, {src: src}); console.log("decode...");
var scanner = Quagga
Quagga.decodeSingle(config, function(result) {}); .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) { setState: function(path, value) {
var self = this; var self = this;
@ -142,45 +159,48 @@ $(function() {
}; };
} }
Quagga.onProcessed(function(result) { function drawResult(scanner, result) {
var drawingCtx = Quagga.canvas.ctx.overlay, var processingCanvas = scanner.getCanvas(),
drawingCanvas = Quagga.canvas.dom.overlay, canvas = App.overlay,
area; ctx = canvas.getContext("2d");
canvas.setAttribute('width', processingCanvas.getAttribute('width'));
canvas.setAttribute('height', processingCanvas.getAttribute('height'));
if (result) { if (result) {
if (result.boxes) { 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) { result.boxes.filter(function (box) {
return box !== result.box; return box !== result.box;
}).forEach(function (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) { 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) { 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) { if (App.state.inputStream.area) {
area = calculateRectFromArea(drawingCanvas, App.state.inputStream.area); var area = calculateRectFromArea(canvas, App.state.inputStream.area);
drawingCtx.strokeStyle = "#0F0"; drawingCtx.strokeStyle = "#0F0";
drawingCtx.strokeRect(area.x, area.y, area.width, area.height); drawingCtx.strokeRect(area.x, area.y, area.width, area.height);
} }
} }
}); };
Quagga.onDetected(function(result) { function addToResults(scanner, result) {
var code = result.codeResult.code, var code = result.codeResult.code,
$node, $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 = $('<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("img").attr("src", canvas.toDataURL());
$node.find("h4.code").html(code); $node.find("h4.code").html(code);
$("#result_strip ul.thumbnails").prepend($node); $("#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"> <select name="numOfWorkers">
<option value="0">0</option> <option value="0">0</option>
<option value="1">1</option> <option value="1">1</option>
<option value="2">2</option> <option selected="selected" value="2">2</option>
<option selected="selected" value="4">4</option> <option value="4">4</option>
<option value="8">8</option> <option value="8">8</option>
</select> </select>
</label> </label>
@ -87,7 +87,9 @@
<ul class="thumbnails"></ul> <ul class="thumbnails"></ul>
<ul class="collector"></ul> <ul class="collector"></ul>
</div> </div>
<div id="interactive" class="viewport"></div> <div id="interactive" class="viewport">
<canvas class="drawing" />
</div>
</section> </section>
<footer> <footer>
<p> <p>

@ -1,38 +1,33 @@
$(function() { $(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 = { var App = {
init : function() { init : function() {
var self = this; this.overlay = document.querySelector('#interactive canvas.drawing');
Quagga.init(this.state, function(err) { this.scanner = Quagga
if (err) { .fromConfig(this.state);
return self.handleError(err);
} this.scanner
Quagga.registerResultCollector(resultCollector); .addEventListener("processed", drawResult.bind(this, this.scanner))
App.attachListeners(); .addEventListener("detected", addToResults.bind(this, this.scanner));
Quagga.start();
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() { attachListeners: function() {
var self = this; var self = this;
$(".controls").on("click", "button.stop", function(e) { $(".controls").on("click", "button.stop", function(e) {
e.preventDefault(); e.preventDefault();
Quagga.stop(); this.detachListeners();
self._printCollectedResults(); this.scanner.stop();
}); this.scanner.removeEventListener();
}.bind(this));
$(".controls .reader-config-group").on("change", "input, select", function(e) { $(".controls .reader-config-group").on("change", "input, select", function(e) {
e.preventDefault(); e.preventDefault();
@ -45,18 +40,6 @@ $(function() {
self.setState(state, value); 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) { _accessByPath: function(obj, path, val) {
var parts = path.split('.'), var parts = path.split('.'),
depth = parts.length, depth = parts.length,
@ -79,17 +62,16 @@ $(function() {
$(".controls .reader-config-group").off("change", "input, select"); $(".controls .reader-config-group").off("change", "input, select");
}, },
setState: function(path, value) { setState: function(path, value) {
var self = this; if (typeof this._accessByPath(this.inputMapper, path) === "function") {
value = this._accessByPath(this.inputMapper, path)(value);
if (typeof self._accessByPath(self.inputMapper, path) === "function") {
value = self._accessByPath(self.inputMapper, path)(value);
} }
self._accessByPath(self.state, path, value); this._accessByPath(this.state, path, value);
console.log(JSON.stringify(self.state)); console.log(JSON.stringify(this.state));
App.detachListeners(); this.detachListeners();
Quagga.stop(); this.scanner.stop();
this.scanner.removeEventListener();
App.init(); App.init();
}, },
inputMapper: { inputMapper: {
@ -137,7 +119,8 @@ $(function() {
patchSize: "medium", patchSize: "medium",
halfSample: true halfSample: true
}, },
numOfWorkers: 4, numOfWorkers: 2,
frequency: 10,
decoder: { decoder: {
readers : [{ readers : [{
format: "code_128_reader", format: "code_128_reader",
@ -151,42 +134,45 @@ $(function() {
App.init(); App.init();
Quagga.onProcessed(function(result) { function drawResult(scanner, result) {
var drawingCtx = Quagga.canvas.ctx.overlay, var processingCanvas = scanner.getCanvas(),
drawingCanvas = Quagga.canvas.dom.overlay; canvas = App.overlay,
ctx = canvas.getContext("2d");
canvas.setAttribute('width', processingCanvas.getAttribute('width'));
canvas.setAttribute('height', processingCanvas.getAttribute('height'));
if (result) { if (result) {
if (result.boxes) { 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) { result.boxes.filter(function (box) {
return box !== result.box; return box !== result.box;
}).forEach(function (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) { 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) { 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) { function addToResults(scanner, result) {
var code = result.codeResult.code; var code = result.codeResult.code,
$node,
canvas = scanner.getCanvas();
if (App.lastResult !== code) { if (App.lastResult !== code) {
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 = $('<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("img").attr("src", canvas.toDataURL());
$node.find("h4.code").html(code); $node.find("h4.code").html(code);
$("#result_strip ul.thumbnails").prepend($node); $("#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"> <div id="result_strip">
<ul class="thumbnails"></ul> <ul class="thumbnails"></ul>
</div> </div>
<div id="interactive" class="viewport"></div> <div id="interactive" class="viewport">
<canvas class="drawing" />
</div>
<div id="debug" class="detection"></div> <div id="debug" class="detection"></div>
</section> </section>
<footer> <footer>

@ -1,12 +1,21 @@
$(function() { $(function() {
var App = { var App = {
init: function() { init: function() {
this.overlay = document.querySelector('#interactive canvas.drawing');
var config = this.config[this.state.decoder.readers[0].format] || this.config.default; var config = this.config[this.state.decoder.readers[0].format] || this.config.default;
config = $.extend(true, {}, config, this.state); config = $.extend(true, {}, config, this.state);
Quagga.init(config, function() {
App.attachListeners(); this.scanner = Quagga
Quagga.start(); .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: { config: {
"default": { "default": {
@ -37,7 +46,7 @@ $(function() {
$(".controls").on("click", "button.next", function(e) { $(".controls").on("click", "button.next", function(e) {
e.preventDefault(); e.preventDefault();
Quagga.start(); self.scanner.start();
}); });
$(".controls .reader-config-group").on("change", "input, select", function(e) { $(".controls .reader-config-group").on("change", "input, select", function(e) {
@ -89,7 +98,8 @@ $(function() {
console.log(JSON.stringify(self.state)); console.log(JSON.stringify(self.state));
App.detachListeners(); App.detachListeners();
Quagga.stop(); this.scanner.stop();
this.scanner.removeEventListener();
App.init(); App.init();
}, },
inputMapper: { inputMapper: {
@ -121,6 +131,7 @@ $(function() {
inputStream: { inputStream: {
src: "../test/fixtures/code_128/" src: "../test/fixtures/code_128/"
}, },
numOfWorkers: 1,
decoder : { decoder : {
readers : [{ readers : [{
format: "code_128_reader", format: "code_128_reader",
@ -133,38 +144,42 @@ $(function() {
App.init(); App.init();
window.App = App; window.App = App;
Quagga.onProcessed(function(result) { function drawResult(scanner, result) {
var drawingCtx = Quagga.canvas.ctx.overlay, var processingCanvas = scanner.getCanvas(),
drawingCanvas = Quagga.canvas.dom.overlay; canvas = App.overlay,
ctx = canvas.getContext("2d");
canvas.setAttribute('width', processingCanvas.getAttribute('width'));
canvas.setAttribute('height', processingCanvas.getAttribute('height'));
if (result) { if (result) {
if (result.boxes) { 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) { result.boxes.filter(function (box) {
return box !== result.box; return box !== result.box;
}).forEach(function (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) { 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) { 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) { function addToResults(scanner, result) {
var $node, var code = result.codeResult.code,
canvas = Quagga.canvas.dom.image, $node,
detectedCode = result.codeResult.code; canvas = scanner.getCanvas();
$node = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>'); $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("img").attr("src", canvas.toDataURL());
$node.find("h4.code").html(detectedCode); $node.find("h4.code").html(code);
$("#result_strip ul.thumbnails").prepend($node); $("#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: [ plugins: [
'karma-chrome-launcher', 'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-coverage', 'karma-coverage',
'karma-mocha', 'karma-mocha',
'karma-chai', 'karma-chai',
@ -50,7 +49,7 @@ module.exports = function(config) {
require('karma-webpack') require('karma-webpack')
], ],
reporters: ['progress', 'coverage'], reporters: ['progress', 'coverage'],
port: 9876, port: 9999,
colors: true, colors: true,
logLevel: config.LOG_INFO, logLevel: config.LOG_INFO,
autoWatch: true, autoWatch: true,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,6 +1,6 @@
{ {
"name": "quagga", "name": "quagga",
"version": "0.11.3", "version": "1.0.0-beta.1",
"description": "An advanced barcode-scanner written in JavaScript", "description": "An advanced barcode-scanner written in JavaScript",
"main": "lib/quagga.js", "main": "lib/quagga.js",
"browser": "dist/quagga.min.js", "browser": "dist/quagga.min.js",
@ -8,7 +8,7 @@
"async": "^1.4.2", "async": "^1.4.2",
"babel-cli": "^6.5.1", "babel-cli": "^6.5.1",
"babel-core": "^6.7.4", "babel-core": "^6.7.4",
"babel-eslint": "^6.0.0", "babel-eslint": "^6.0.4",
"babel-istanbul-loader": "^0.1.0", "babel-istanbul-loader": "^0.1.0",
"babel-loader": "^6.2.4", "babel-loader": "^6.2.4",
"babel-plugin-add-module-exports": "^0.1.2", "babel-plugin-add-module-exports": "^0.1.2",
@ -38,6 +38,7 @@
"core-js": "^1.2.1", "core-js": "^1.2.1",
"cross-env": "^1.0.7", "cross-env": "^1.0.7",
"eslint": "^1.10.3", "eslint": "^1.10.3",
"eslint-plugin-react": "^5.1.1",
"grunt": "^0.4.5", "grunt": "^0.4.5",
"grunt-cli": "^0.1.13", "grunt-cli": "^0.1.13",
"grunt-contrib-nodeunit": "^0.4.1", "grunt-contrib-nodeunit": "^0.4.1",
@ -59,7 +60,7 @@
"phantomjs": "^1.9.18", "phantomjs": "^1.9.18",
"sinon": "^1.16.1", "sinon": "^1.16.1",
"sinon-chai": "^2.8.0", "sinon-chai": "^2.8.0",
"webpack": "^2.1.0-beta.4", "webpack": "2.1.0-beta.4",
"webpack-sources": "^0.1.1" "webpack-sources": "^0.1.1"
}, },
"directories": { "directories": {

@ -1,10 +1,11 @@
const vec2 = { const vec2 = {
clone: require('gl-vec2/clone'), clone: require('gl-vec2/clone'),
dot: require('gl-vec2/dot') dot: require('gl-vec2/dot')
} };
/**
* Creates a cluster for grouping similar orientations of datapoints /**
*/ * Creates a cluster for grouping similar orientations of datapoints
*/
export default { export default {
create: function(point, threshold) { create: function(point, threshold) {
var points = [], var points = [],

@ -1,10 +1,10 @@
import Cluster2 from './cluster'; import Cluster2 from './cluster';
import ArrayHelper from './array_helper'; import ArrayHelper from './array_helper';
const vec2 = { const vec2 = {
clone: require('gl-vec2/clone'), clone: require('gl-vec2/clone')
}; };
const vec3 = { 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 = {}; var events = {};
function getEvent(eventName) { 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 singleChannel: false // true: only the red color-channel is read
}, },
locate: true, locate: true,
numOfWorkers: 0, numOfWorkers: 2,
decoder: { decoder: {
readers: [ readers: [
'code_128_reader' 'code_128_reader'

@ -68,8 +68,13 @@ export default {
} }
_canvas.ctx.pattern = _canvas.dom.pattern.getContext("2d"); _canvas.ctx.pattern = _canvas.dom.pattern.getContext("2d");
_canvas.dom.overlay = document.querySelector("canvas.drawingBuffer"); if ($debug) {
if (_canvas.dom.overlay) { _canvas.dom.overlay = document.querySelector("canvas.drawingBuffer");
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"); _canvas.ctx.overlay = _canvas.dom.overlay.getContext("2d");
} }
} }

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

@ -1,4 +1,4 @@
import {merge, pick} from 'lodash'; import {merge, pick, omit} from 'lodash';
var streamRef; var streamRef;
@ -64,7 +64,14 @@ function deprecatedConstraints(videoConstraints) {
} }
function applyCameraFacing(facing, constraints) { 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); return Promise.resolve(constraints);
} }
if ( typeof MediaStreamTrack !== 'undefined' && 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) { that.setTopRight = function(topRight) {
_topRight.x = topRight.x; _topRight.x = topRight.x;
_topRight.y = topRight.y; _topRight.y = topRight.y;

File diff suppressed because it is too large Load Diff

@ -1,543 +1,137 @@
import TypeDefs from './common/typedefs'; // eslint-disable-line no-unused-vars import './common/typedefs';
import WebrtcAdapter from 'webrtc-adapter'; // eslint-disable-line no-unused-vars import 'webrtc-adapter';
import createScanner from './scanner';
import ImageWrapper from './common/image_wrapper'; 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 ImageDebug from './common/image_debug';
import ResultCollector from './analytics/result_collector'; import ResultCollector from './analytics/result_collector';
import Config from './config/config'; import Config from './config/config';
import InputStream from 'input_stream';
import FrameGrabber from 'frame_grabber';
import {merge} from 'lodash'; import {merge} from 'lodash';
const vec2 = { import {createConfigFromSource} from './input/config_factory';
clone: require('gl-vec2/clone')
};
var _inputStream, function fromConfig(config) {
_framegrabber, const scanner = createScanner();
_stopped, let pendingStart = null;
_canvasContainer = { let initialized = false;
ctx: { return {
image: null, addEventListener(eventType, cb) {
overlay: null scanner.subscribe(eventType, cb);
return this;
}, },
dom: { removeEventListener(eventType, cb) {
image: null, scanner.unsubscribe(eventType, cb);
overlay: null return this;
} },
}, start() {
_inputImageWrapper, if (scanner.isRunning()) {
_boxSize, return Promise.resolve(true);
_decoder,
_workerPool = [],
_onUIThread = true,
_resultCollector,
_config = {};
function initializeData(imageWrapper) {
initBuffers(imageWrapper);
_decoder = BarcodeDecoder.create(_config.decoder, _inputImageWrapper);
}
function initInputStream(cb) {
var video;
if (_config.inputStream.type === "VideoStream") {
video = document.createElement("video");
_inputStream = InputStream.createVideoStream(video);
} else if (_config.inputStream.type === "ImageStream") {
_inputStream = InputStream.createImageStream();
} else if (_config.inputStream.type === "LiveStream") {
var $viewport = getViewPort();
if ($viewport) {
video = $viewport.querySelector("video");
if (!video) {
video = document.createElement("video");
$viewport.appendChild(video);
}
}
_inputStream = InputStream.createLiveStream(video);
CameraAccess.request(video, _config.inputStream.constraints)
.then(() => {
_inputStream.trigger("canrecord");
}).catch((err) => {
return cb(err);
});
}
_inputStream.setAttribute("preload", "auto");
_inputStream.setInputStream(_config.inputStream);
_inputStream.addEventListener("canrecord", canRecord.bind(undefined, cb));
}
function getViewPort() {
var target = _config.inputStream.target;
// Check if target is already a DOM element
if (target && target.nodeName && target.nodeType === 1) {
return target;
} else {
// Use '#interactive.viewport' as a fallback selector (backwards compatibility)
var selector = typeof target === 'string' ? target : '#interactive.viewport';
return document.querySelector(selector);
}
}
function canRecord(cb) {
BarcodeLocator.checkImageConstraints(_inputStream, _config.locator);
initCanvas(_config);
_framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image);
adjustWorkerPool(_config.numOfWorkers, function() {
if (_config.numOfWorkers === 0) {
initializeData();
}
ready(cb);
});
}
function ready(cb){
_inputStream.play();
cb();
}
function initCanvas() {
if (typeof document !== "undefined") {
var $viewport = getViewPort();
_canvasContainer.dom.image = document.querySelector("canvas.imgBuffer");
if (!_canvasContainer.dom.image) {
_canvasContainer.dom.image = document.createElement("canvas");
_canvasContainer.dom.image.className = "imgBuffer";
if ($viewport && _config.inputStream.type === "ImageStream") {
$viewport.appendChild(_canvasContainer.dom.image);
} }
} if (pendingStart) {
_canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d"); return pendingStart;
_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"); if (initialized) {
clearFix.setAttribute("clear", "all"); scanner.start();
if ($viewport) { return Promise.resolve(true);
$viewport.appendChild(clearFix);
} }
} pendingStart = new Promise((resolve, reject) => {
_canvasContainer.ctx.overlay = _canvasContainer.dom.overlay.getContext("2d"); scanner.init(config, (error) => {
_canvasContainer.dom.overlay.width = _inputStream.getCanvasSize().x; if (error) {
_canvasContainer.dom.overlay.height = _inputStream.getCanvasSize().y; console.log(error);
} reject(error);
} }
initialized = true;
function initBuffers(imageWrapper) { scanner.start();
if (imageWrapper) { resolve();
_inputImageWrapper = imageWrapper; pendingStart = null;
} else { });
_inputImageWrapper = new ImageWrapper({ });
x: _inputStream.getWidth(), return pendingStart;
y: _inputStream.getHeight() },
}); stop() {
} scanner.stop();
initialized = false;
if (ENV.development) { return this;
console.log(_inputImageWrapper.size); },
} toPromise() {
_boxSize = [ if (config.inputStream.type === 'LiveStream'
vec2.clone([0, 0]), || config.inputStream.type === 'VideoStream') {
vec2.clone([0, _inputImageWrapper.size.y]), let cancelRequested = false;
vec2.clone([_inputImageWrapper.size.x, _inputImageWrapper.size.y]), return {
vec2.clone([_inputImageWrapper.size.x, 0]) cancel() {
]; cancelRequested = true;
BarcodeLocator.init(_inputImageWrapper, _config.locator); },
} promise: new Promise((resolve, reject) => {
function onProcessed(result) {
function getBoundingBoxes() { if (result && result.codeResult && result.codeResult.code) {
if (_config.locate) { scanner.stop();
return BarcodeLocator.locate(); scanner.unsubscribe("processed", onProcessed);
} else { resolve(result);
return [[ }
vec2.clone(_boxSize[0]), if (cancelRequested) {
vec2.clone(_boxSize[1]), scanner.stop();
vec2.clone(_boxSize[2]), scanner.unsubscribe("processed", onProcessed);
vec2.clone(_boxSize[3])]]; reject("cancelled!");
} }
} }
scanner.subscribe("processed", onProcessed);
function transformResult(result) { this.start();
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 { } else {
locateAndDecode(); return new Promise((resolve, reject) => {
} scanner.decodeSingle(config, (result) => {
} if (result && result.codeResult && result.codeResult.code) {
} else { return resolve(result);
locateAndDecode(); }
} return reject(result);
} });
});
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); },
} registerResultCollector(resultCollector) {
}(performance.now())); scanner.registerResultCollector(resultCollector);
},
getCanvas() {
return scanner.canvas.dom.image;
},
};
} }
function start() { function fromSource(config, source, inputConfig = {}) {
if (_onUIThread && _config.inputStream.type === "LiveStream") { config = createConfigFromSource(config, inputConfig, source);
startContinuousUpdate(); return fromConfig(config);
} else {
update();
}
} }
function initWorker(cb) { function setConfig(configuration = {}, key, config = {}) {
var blobURL, var mergedConfig = merge({}, configuration, {[key]: config});
workerThread = { return createApi(mergedConfig);
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) { function createApi(configuration = Config) {
return { return {
...config, fromSource(src, inputConfig) {
inputStream: { return fromSource(configuration, src, inputConfig);
...config.inputStream, },
target: null fromConfig(conf) {
} return fromConfig(merge({}, configuration, conf));
}; },
} decoder(conf) {
return setConfig(configuration, "decoder", conf);
function workerInterface(factory) { },
/* eslint-disable no-undef*/ locator(conf) {
if (factory) { return setConfig(configuration, "locator", conf);
var Quagga = factory().default; },
if (!Quagga) { throttle(timeInMs) {
self.postMessage({'event': 'error', message: 'Quagga could not be created'}); return setConfig(configuration, "frequency", 1000 / parseInt(timeInMs));
return; },
} config(conf) {
} return createApi(merge({}, configuration, conf));
var imageWrapper; },
ImageWrapper,
self.onmessage = function(e) { ImageDebug,
if (e.data.cmd === 'init') { ResultCollector,
var config = e.data.config; _worker: {
config.numOfWorkers = 0; createScanner
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);
} }
}; };
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 */
} }
export default createApi();
function generateWorkerBlob() {
var blob,
factorySource;
/* jshint ignore:start */
if (typeof __factorySource__ !== 'undefined') {
factorySource = __factorySource__; // eslint-disable-line no-undef
}
/* jshint ignore:end */
blob = new Blob(['(' + workerInterface.toString() + ')(' + factorySource + ');'],
{type: 'text/javascript'});
return window.URL.createObjectURL(blob);
}
function setReaders(readers) {
if (_decoder) {
_decoder.setReaders(readers);
} else if (_onUIThread && _workerPool.length > 0) {
_workerPool.forEach(function(workerThread) {
workerThread.worker.postMessage({cmd: 'setReaders', readers: readers});
});
}
}
function adjustWorkerPool(capacity, cb) {
const increaseBy = capacity - _workerPool.length;
if (increaseBy === 0) {
return cb && cb();
}
if (increaseBy < 0) {
const workersToTerminate = _workerPool.slice(increaseBy);
workersToTerminate.forEach(function(workerThread) {
workerThread.worker.terminate();
if (ENV.development) {
console.log("Worker terminated!");
}
});
_workerPool = _workerPool.slice(0, increaseBy);
return cb && cb();
} else {
for (var i = 0; i < increaseBy; i++) {
initWorker(workerInitialized);
}
function workerInitialized(workerThread) {
_workerPool.push(workerThread);
if (_workerPool.length >= capacity){
cb && cb();
}
}
}
}
export default {
init: function(config, cb, imageWrapper) {
_config = merge({}, Config, config);
if (imageWrapper) {
_onUIThread = false;
initializeData(imageWrapper);
return cb();
} else {
initInputStream(cb);
}
},
start: function() {
start();
},
stop: function() {
_stopped = true;
adjustWorkerPool(0);
if (_config.inputStream.type === "LiveStream") {
CameraAccess.release();
_inputStream.clearEventHandlers();
}
},
pause: function() {
_stopped = true;
},
onDetected: function(callback) {
Events.subscribe("detected", callback);
},
offDetected: function(callback) {
Events.unsubscribe("detected", callback);
},
onProcessed: function(callback) {
Events.subscribe("processed", callback);
},
offProcessed: function(callback) {
Events.unsubscribe("processed", callback);
},
setReaders: function(readers) {
setReaders(readers);
},
registerResultCollector: function(resultCollector) {
if (resultCollector && typeof resultCollector.addResult === 'function') {
_resultCollector = resultCollector;
}
},
canvas: _canvasContainer,
decodeSingle: function(config, resultCallback) {
config = merge({
inputStream: {
type: "ImageStream",
sequence: false,
size: 800,
src: config.src
},
numOfWorkers: (ENV.development && config.debug) ? 0 : 1,
locator: {
halfSample: false
}
}, config);
this.init(config, () => {
Events.once("processed", (result) => {
this.stop();
resultCallback.call(null, result);
}, true);
start();
});
},
ImageWrapper: ImageWrapper,
ImageDebug: ImageDebug,
ResultCollector: ResultCollector
};

@ -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) { async.eachSeries(testSet, function (sample, callback) {
config.src = folder + sample.name; config.src = folder + sample.name;
config.readers = readers; config.readers = readers;
Quagga.decodeSingle(config, function(result) { Quagga
console.log(sample.name); .config(config)
expect(result.codeResult.code).to.equal(sample.result); .fromSource(config.src)
expect(result.codeResult.format).to.equal(sample.format); .addEventListener('processed', function(result){
callback(); console.log(sample.name);
}); expect(result.codeResult.code).to.equal(sample.result);
expect(result.codeResult.format).to.equal(sample.format);
callback();
})
.start();
}, function() { }, function() {
done(); done();
}); });
@ -169,7 +173,7 @@ describe('decodeSingle', function () {
{"name": "image-004.jpg", "result": "QUAGGAJS"}, {"name": "image-004.jpg", "result": "QUAGGAJS"},
/* {"name": "image-005.jpg", "result": "CODE39"}, */ /* {"name": "image-005.jpg", "result": "CODE39"}, */
{"name": "image-006.jpg", "result": "2/4-8/16-32"}, {"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-008.jpg", "result": "CODE39"},
{"name": "image-009.jpg", "result": "2/4-8/16-32"}, {"name": "image-009.jpg", "result": "2/4-8/16-32"},
{"name": "image-010.jpg", "result": "CODE39"} {"name": "image-010.jpg", "result": "CODE39"}
@ -190,9 +194,9 @@ describe('decodeSingle', function () {
{"name": "image-002.jpg", "result": "42191605"}, {"name": "image-002.jpg", "result": "42191605"},
{"name": "image-003.jpg", "result": "90311208"}, {"name": "image-003.jpg", "result": "90311208"},
{"name": "image-004.jpg", "result": "24057257"}, {"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-006.jpg", "result": "24036153"},
{"name": "image-007.jpg", "result": "42176817"}, //{"name": "image-007.jpg", "result": "42176817"},
{"name": "image-008.jpg", "result": "42191605"}, {"name": "image-008.jpg", "result": "42191605"},
{"name": "image-009.jpg", "result": "42242215"}, {"name": "image-009.jpg", "result": "42242215"},
{"name": "image-010.jpg", "result": "42184799"} {"name": "image-010.jpg", "result": "42184799"}
@ -232,11 +236,11 @@ describe('decodeSingle', function () {
describe("UPC-E", function() { describe("UPC-E", function() {
var config = generateConfig(), var config = generateConfig(),
testSet = [ testSet = [
{"name": "image-001.jpg", "result": "04965802"}, //{"name": "image-001.jpg", "result": "04965802"},
{"name": "image-002.jpg", "result": "04965802"}, {"name": "image-002.jpg", "result": "04965802"},
{"name": "image-003.jpg", "result": "03897425"}, {"name": "image-003.jpg", "result": "03897425"},
{"name": "image-004.jpg", "result": "05096893"}, {"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-006.jpg", "result": "05096893"},
{"name": "image-007.jpg", "result": "03897425"}, {"name": "image-007.jpg", "result": "03897425"},
{"name": "image-008.jpg", "result": "01264904"}, {"name": "image-008.jpg", "result": "01264904"},
@ -264,7 +268,7 @@ describe('decodeSingle', function () {
{"name": "image-007.jpg", "result": "C$399.95A"}, {"name": "image-007.jpg", "result": "C$399.95A"},
//{"name": "image-008.jpg", "result": "A16:9/4:3/3:2D"}, //{"name": "image-008.jpg", "result": "A16:9/4:3/3:2D"},
{"name": "image-009.jpg", "result": "C$399.95A"}, {"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) { 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 Config from '../../src/config/config';
import {merge} from 'lodash'; import {merge} from 'lodash';
@ -45,7 +45,7 @@ describe('checkImageConstraints', function() {
it('should not adjust the image-size if not needed', function() { it('should not adjust the image-size if not needed', function() {
var expected = {x: imageSize.x, y: imageSize.y}; 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.getWidth()).to.be.equal(expected.x);
expect(inputStream.getHeight()).to.be.equal(expected.y); expect(inputStream.getHeight()).to.be.equal(expected.y);
}); });
@ -55,7 +55,7 @@ describe('checkImageConstraints', function() {
config.locator.halfSample = true; config.locator.halfSample = true;
imageSize.y += 1; imageSize.y += 1;
BarcodeLocator.checkImageConstraints(inputStream, config.locator); checkImageConstraints(inputStream, config.locator);
expect(inputStream.getWidth()).to.be.equal(expected.x); expect(inputStream.getWidth()).to.be.equal(expected.x);
expect(inputStream.getHeight()).to.be.equal(expected.y); expect(inputStream.getHeight()).to.be.equal(expected.y);
}); });
@ -65,7 +65,7 @@ describe('checkImageConstraints', function() {
imageSize.y += 1; imageSize.y += 1;
config.locator.halfSample = false; config.locator.halfSample = false;
BarcodeLocator.checkImageConstraints(inputStream, config.locator); checkImageConstraints(inputStream, config.locator);
expect(inputStream.getHeight()).to.be.equal(expected.y); expect(inputStream.getHeight()).to.be.equal(expected.y);
expect(inputStream.getWidth()).to.be.equal(expected.x); expect(inputStream.getWidth()).to.be.equal(expected.x);
}); });
@ -92,7 +92,7 @@ describe('checkImageConstraints', function() {
}; };
config.locator.halfSample = false; config.locator.halfSample = false;
BarcodeLocator.checkImageConstraints(inputStream, config.locator); checkImageConstraints(inputStream, config.locator);
expect(inputStream.getHeight()).to.be.equal(expectedSize.y); expect(inputStream.getHeight()).to.be.equal(expectedSize.y);
expect(inputStream.getWidth()).to.be.equal(expectedSize.x); expect(inputStream.getWidth()).to.be.equal(expectedSize.x);
expect(inputStream.setTopRight.getCall(0).args[0]).to.deep.equal(expectedTopRight); expect(inputStream.setTopRight.getCall(0).args[0]).to.deep.equal(expectedTopRight);
@ -121,7 +121,7 @@ describe('checkImageConstraints', function() {
}; };
config.locator.halfSample = false; config.locator.halfSample = false;
BarcodeLocator.checkImageConstraints(inputStream, config.locator); checkImageConstraints(inputStream, config.locator);
expect(inputStream.getHeight()).to.be.equal(expectedSize.y); expect(inputStream.getHeight()).to.be.equal(expectedSize.y);
expect(inputStream.getWidth()).to.be.equal(expectedSize.x); expect(inputStream.getWidth()).to.be.equal(expectedSize.x);
expect(inputStream.setTopRight.getCall(0).args[0]).to.deep.equal(expectedTopRight); expect(inputStream.setTopRight.getCall(0).args[0]).to.deep.equal(expectedTopRight);

@ -5,173 +5,175 @@ var originalURL,
video, video,
stream; stream;
beforeEach(function() { describe("CameraAccess", () => {
var tracks = [{
stop: function() {}
}];
originalURL = window.URL;
originalMediaStreamTrack = window.MediaStreamTrack;
window.MediaStreamTrack = {};
window.URL = {
createObjectURL(stream) {
return stream;
}
};
stream = {
getVideoTracks: function() {
return tracks;
}
};
sinon.spy(tracks[0], "stop");
video = {
srcObject: null,
addEventListener: function() {},
removeEventListener: function() {},
setAttribute: sinon.spy(),
play: function() {},
videoWidth: 320,
videoHeight: 480
};
sinon.stub(video, "addEventListener", function(event, cb) {
cb();
});
sinon.stub(video, "play");
});
afterEach(function() {
window.URL = originalURL;
window.MediaStreamTrack = originalMediaStreamTrack;
});
describe('success', function() {
beforeEach(function() { beforeEach(function() {
sinon.stub(navigator.mediaDevices, "getUserMedia", function(constraints) { var tracks = [{
return Promise.resolve(stream); stop: function() {}
}];
originalURL = window.URL;
originalMediaStreamTrack = window.MediaStreamTrack;
window.MediaStreamTrack = {};
window.URL = {
createObjectURL() {
return stream;
}
};
stream = {
getVideoTracks: function() {
return tracks;
}
};
sinon.spy(tracks[0], "stop");
video = {
srcObject: null,
addEventListener: function() {},
removeEventListener: function() {},
setAttribute: sinon.spy(),
play: function() {},
videoWidth: 320,
videoHeight: 480
};
sinon.stub(video, "addEventListener", function(event, cb) {
cb();
}); });
sinon.stub(video, "play");
}); });
afterEach(function() { afterEach(function() {
navigator.mediaDevices.getUserMedia.restore(); window.URL = originalURL;
window.MediaStreamTrack = originalMediaStreamTrack;
}); });
describe('request', function () {
it('should request the camera', function (done) {
CameraAccess.request(video, {})
.then(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) => { describe('success', function() {
CameraAccess.request(video, { beforeEach(function() {
width: 320, sinon.stub(navigator.mediaDevices, "getUserMedia", function(constraints) {
height: 240, return Promise.resolve(stream);
facing: "user", });
minAspectRatio: 2,
maxAspectRatio: 100
})
.then(function () {
const call = navigator.mediaDevices.getUserMedia.getCall(0),
args = call.args;
expect(call).to.be.defined;
expect(args[0].video.width).to.equal(320);
expect(args[0].video.height).to.equal(240);
expect(args[0].video.facingMode).to.equal("user");
expect(args[0].video.aspectRatio).to.equal(2);
expect(args[0].video.facing).not.to.be.defined;
expect(args[0].video.minAspectRatio).not.to.be.defined;
expect(args[0].video.maxAspectRatio).not.to.be.defined;
done();
})
}); });
});
describe('facingMode fallback in Chrome', () => { afterEach(function() {
beforeEach(() => { navigator.mediaDevices.getUserMedia.restore();
window.MediaStreamTrack.getSources = (cb) => {
return cb([
{kind: "video", facing: "environment", id: "environment"},
{kind: "audio", id: "audio"},
{kind: "video", facing: "user", id: "user"}
]);
};
}); });
describe('request', function () {
it('should request the camera', function (done) {
CameraAccess.request(video, {})
.then(function () {
expect(navigator.mediaDevices.getUserMedia.calledOnce).to.equal(true);
expect(video.srcObject).to.deep.equal(stream);
done();
});
});
afterEach(() => { it("should allow deprecated constraints to be used", (done) => {
window.MediaStreamTrack = {}; CameraAccess.request(video, {
}) width: 320,
height: 240,
it("should set deviceId in case facingMode is not supported", (done) => { facing: "user",
CameraAccess.request(video, { minAspectRatio: 2,
facing: "user" maxAspectRatio: 100
}) })
.then(function () { .then(function () {
const call = navigator.mediaDevices.getUserMedia.getCall(0), const call = navigator.mediaDevices.getUserMedia.getCall(0),
args = call.args; args = call.args;
expect(call).to.be.defined; expect(call).to.be.defined;
expect(args[0].video.facingMode).not.to.be.defined; expect(args[0].video.width).to.equal(320);
expect(args[0].video.deviceId).to.equal("user"); expect(args[0].video.height).to.equal(240);
done(); expect(args[0].video.facingMode).to.equal("user");
}) expect(args[0].video.aspectRatio).to.equal(2);
expect(args[0].video.facing).not.to.be.defined;
expect(args[0].video.minAspectRatio).not.to.be.defined;
expect(args[0].video.maxAspectRatio).not.to.be.defined;
done();
});
});
}); });
});
describe('release', function () { describe('facingMode fallback in Chrome', () => {
it('should release the camera', function (done) { beforeEach(() => {
CameraAccess.request(video, {}) window.MediaStreamTrack.getSources = (cb) => {
.then(function () { return cb([
expect(video.srcObject).to.deep.equal(stream); {kind: "video", facing: "environment", id: "environment"},
CameraAccess.release(); {kind: "audio", id: "audio"},
expect(video.srcObject.getVideoTracks()).to.have.length(1); {kind: "video", facing: "user", id: "user"}
expect(video.srcObject.getVideoTracks()[0].stop.calledOnce).to.equal(true); ]);
done(); };
}); });
});
});
});
describe('failure', function() { afterEach(() => {
describe("permission denied", function(){ window.MediaStreamTrack = {};
beforeEach(function() {
sinon.stub(navigator.mediaDevices, "getUserMedia", function(constraints, success, failure) {
return Promise.reject(new Error());
}); });
});
afterEach(function() { it("should set deviceId in case facingMode is not supported", (done) => {
navigator.mediaDevices.getUserMedia.restore(); CameraAccess.request(video, {
facing: "user"
})
.then(function () {
const call = navigator.mediaDevices.getUserMedia.getCall(0),
args = call.args;
expect(call).to.be.defined;
expect(args[0].video.facingMode).not.to.be.defined;
expect(args[0].video.deviceId).to.equal("user");
done();
});
});
}); });
it('should throw if getUserMedia not available', function(done) { describe('release', function () {
CameraAccess.request(video, {}) it('should release the camera', function (done) {
.catch(function (err) { CameraAccess.request(video, {})
expect(err).to.be.defined; .then(function () {
done(); expect(video.srcObject).to.deep.equal(stream);
CameraAccess.release();
expect(video.srcObject.getVideoTracks()).to.have.length(1);
expect(video.srcObject.getVideoTracks()[0].stop.calledOnce).to.equal(true);
done();
});
}); });
}); });
}); });
describe("not available", function(){ describe('failure', function() {
var originalGetUserMedia; describe("permission denied", function(){
beforeEach(function() {
sinon.stub(navigator.mediaDevices, "getUserMedia", function(constraints, success, failure) {
return Promise.reject(new Error());
});
});
beforeEach(function() { afterEach(function() {
originalGetUserMedia = navigator.mediaDevices.getUserMedia; navigator.mediaDevices.getUserMedia.restore();
navigator.mediaDevices.getUserMedia = undefined; });
});
afterEach(function() { it('should throw if getUserMedia not available', function(done) {
navigator.mediaDevices.getUserMedia = originalGetUserMedia; CameraAccess.request(video, {})
.catch(function (err) {
expect(err).to.be.defined;
done();
});
});
}); });
it('should throw if getUserMedia not available', function(done) { describe("not available", function(){
CameraAccess.request(video, {}) var originalGetUserMedia;
.catch((err) => {
expect(err).to.be.defined; beforeEach(function() {
done(); originalGetUserMedia = navigator.mediaDevices.getUserMedia;
navigator.mediaDevices.getUserMedia = undefined;
});
afterEach(function() {
navigator.mediaDevices.getUserMedia = originalGetUserMedia;
});
it('should throw if getUserMedia not available', function(done) {
CameraAccess.request(video, {})
.catch((err) => {
expect(err).to.be.defined;
done();
});
}); });
}); });
}); });

@ -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() { beforeEach(function() {
Events.unsubscribe(); Events = createEventedElement();
}); });
describe("subscribe", function() { describe("subscribe", function() {
it("should call one callback for a single event", function() { it("should call one callback for a single event", function() {
var callbackA = sinon.stub(), var callbackA = sinon.stub(),
callbackB = sinon.stub(); callbackB = sinon.stub();
@ -31,18 +30,16 @@ describe("subscribe", function() {
it("should call the callback asynchronuously", function(done) { it("should call the callback asynchronuously", function(done) {
var test = { var test = {
callback: function() { callback: function() {}
};
}
};
sinon.stub(test, "callback", function() { sinon.stub(test, "callback", function() {
expect(test.callback.calledOnce).to.be.true; expect(test.callback.calledOnce).to.equal(true);
done(); done();
}); });
Events.subscribe("test", test.callback, true); Events.subscribe("test", test.callback, true);
Events.publish("test"); Events.publish("test");
expect(test.callback.called).to.be.false; expect(test.callback.called).to.equal(false);
}); });
}); });

@ -5,99 +5,101 @@ var canvasMock,
imageSize, imageSize,
config; config;
beforeEach(function() { describe("ResultCollector", () => {
imageSize = {x: 320, y: 240}; beforeEach(function() {
config = { imageSize = {x: 320, y: 240};
capture: true, config = {
capacity: 20, capture: true,
blacklist: [{code: "3574660239843", format: "ean_13"}], capacity: 20,
filter: function() { blacklist: [{code: "3574660239843", format: "ean_13"}],
return true; filter: function() {
} return true;
}; }
canvasMock = { };
getContext: function() { canvasMock = {
return {}; getContext: function() {
}, return {};
toDataURL: sinon.spy(), },
width: 0, toDataURL: sinon.spy(),
height: 0 width: 0,
}; height: 0
sinon.stub(document, "createElement", function(type) { };
if (type === "canvas") { sinon.stub(document, "createElement", function(type) {
return canvasMock; if (type === "canvas") {
} return canvasMock;
}
});
}); });
});
afterEach(function() { afterEach(function() {
document.createElement.restore(); document.createElement.restore();
}); });
describe('create', function () { describe('create', function () {
it("should return a new collector", function() { it("should return a new collector", function() {
ResultCollector.create(config); ResultCollector.create(config);
expect(document.createElement.calledOnce).to.be.equal(true); expect(document.createElement.calledOnce).to.be.equal(true);
expect(document.createElement.getCall(0).args[0]).to.equal("canvas"); expect(document.createElement.getCall(0).args[0]).to.equal("canvas");
});
}); });
});
describe('addResult', function() { describe('addResult', function() {
beforeEach(function() { beforeEach(function() {
sinon.stub(ImageDebug, "drawImage", function() {}); sinon.stub(ImageDebug, "drawImage", function() {});
}); });
afterEach(function() { afterEach(function() {
ImageDebug.drawImage.restore(); ImageDebug.drawImage.restore();
}); });
it("should not add result if capacity is full", function(){ it("should not add result if capacity is full", function(){
config.capacity = 1; config.capacity = 1;
var collector = ResultCollector.create(config); var collector = ResultCollector.create(config);
collector.addResult([], imageSize, {}); collector.addResult([], imageSize, {});
collector.addResult([], imageSize, {}); collector.addResult([], imageSize, {});
collector.addResult([], imageSize, {}); collector.addResult([], imageSize, {});
expect(collector.getResults()).to.have.length(1); expect(collector.getResults()).to.have.length(1);
}); });
it("should only add results which match constraints", function(){ it("should only add results which match constraints", function(){
var collector = ResultCollector.create(config), var collector = ResultCollector.create(config),
results; results;
collector.addResult([], imageSize, {code: "423423443", format: "ean_13"}); collector.addResult([], imageSize, {code: "423423443", format: "ean_13"});
collector.addResult([], imageSize, {code: "3574660239843", format: "ean_13"}); collector.addResult([], imageSize, {code: "3574660239843", format: "ean_13"});
collector.addResult([], imageSize, {code: "3574660239843", format: "code_128"}); collector.addResult([], imageSize, {code: "3574660239843", format: "code_128"});
results = collector.getResults(); results = collector.getResults();
expect(results).to.have.length(2); expect(results).to.have.length(2);
results.forEach(function(result) { results.forEach(function(result) {
expect(result).not.to.deep.equal(config.blacklist[0]); expect(result).not.to.deep.equal(config.blacklist[0]);
});
}); });
});
it("should add result if no filter is set", function() { it("should add result if no filter is set", function() {
delete config.filter; delete config.filter;
var collector = ResultCollector.create(config); var collector = ResultCollector.create(config);
collector.addResult([], imageSize, {code: "423423443", format: "ean_13"}); collector.addResult([], imageSize, {code: "423423443", format: "ean_13"});
expect(collector.getResults()).to.have.length(1); expect(collector.getResults()).to.have.length(1);
}); });
it("should not add results if filter returns false", function() { it("should not add results if filter returns false", function() {
config.filter = () => (false); config.filter = () => (false);
var collector = ResultCollector.create(config); var collector = ResultCollector.create(config);
collector.addResult([], imageSize, {code: "423423443", format: "ean_13"}); collector.addResult([], imageSize, {code: "423423443", format: "ean_13"});
expect(collector.getResults()).to.have.length(0); expect(collector.getResults()).to.have.length(0);
}); });
it("should add result if no blacklist is set", function() { it("should add result if no blacklist is set", function() {
delete config.blacklist; delete config.blacklist;
var collector = ResultCollector.create(config); var collector = ResultCollector.create(config);
collector.addResult([], imageSize, {code: "3574660239843", format: "ean_13"}); collector.addResult([], imageSize, {code: "3574660239843", format: "ean_13"});
expect(collector.getResults()).to.have.length(1); expect(collector.getResults()).to.have.length(1);
});
}); });
}); });

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

Loading…
Cancel
Save