mirror of
https://github.com/serratus/quaggaJS.git
synced 2026-08-12 21:21:42 +08:00
Merged feature/109 to 1.0
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
Vendored
+5597
-3053
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+4
-3
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 */
|
|
||||||
+31
-1
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
+108
-151
@@ -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,97 +164,7 @@
|
|||||||
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) {
|
@media (max-width: 603px) {
|
||||||
/* line 2, ../sass/phone/_core.scss */
|
|
||||||
#container {
|
|
||||||
width: 300px;
|
|
||||||
margin: 10px auto;
|
|
||||||
-moz-box-shadow: none;
|
|
||||||
-webkit-box-shadow: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
/* line 9, ../sass/phone/_core.scss */
|
|
||||||
#container form.voucher-form input.voucher-code {
|
|
||||||
width: 180px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (max-width: 603px) {
|
|
||||||
/* line 5, ../sass/phone/_viewport.scss */
|
|
||||||
#interactive.viewport {
|
|
||||||
width: 300px;
|
|
||||||
height: 300px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* line 11, ../sass/phone/_viewport.scss */
|
|
||||||
#interactive.viewport canvas, video {
|
|
||||||
margin-top: -50px;
|
|
||||||
width: 300px;
|
|
||||||
height: 400px;
|
|
||||||
}
|
|
||||||
/* line 15, ../sass/phone/_viewport.scss */
|
|
||||||
#interactive.viewport canvas.drawingBuffer, video.drawingBuffer {
|
|
||||||
margin-left: -300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* line 20, ../sass/phone/_viewport.scss */
|
/* line 20, ../sass/phone/_viewport.scss */
|
||||||
#result_strip {
|
#result_strip {
|
||||||
margin-top: 5px;
|
margin-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;
|
|
||||||
padding: 20px;
|
|
||||||
margin-top: -275px;
|
|
||||||
margin-left: -340px;
|
|
||||||
background-color: #FFF;
|
|
||||||
-moz-box-shadow: none;
|
|
||||||
-webkit-box-shadow: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
}
|
||||||
/* line 17, ../sass/phone/_overlay.scss */
|
html, body {
|
||||||
.overlay.scanner > .header {
|
min-height: 100%;
|
||||||
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"> </button>
|
||||||
|
<input type="file" id="file" capture/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<p>This example demonstrates the following features:
|
||||||
|
<ul>
|
||||||
|
<li>Use static image as source</li>
|
||||||
|
<li>Configuring EAN-Reader</li>
|
||||||
|
<li>Use custom mount-point (Query-Selector)</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
<div class="source-code">
|
||||||
|
<h4>Source</h4>
|
||||||
|
<div class="collapsable-source">
|
||||||
|
<pre>
|
||||||
|
<code class="language-javascript">
|
||||||
|
var Quagga = window.Quagga;
|
||||||
|
var App = {
|
||||||
|
_scanner: null,
|
||||||
|
init: function() {
|
||||||
|
this.attachListeners();
|
||||||
|
},
|
||||||
|
decode: function(src) {
|
||||||
|
Quagga
|
||||||
|
.decoder({readers: ['ean_reader']})
|
||||||
|
.locator({patchSize: 'medium'})
|
||||||
|
.fromImage(src, {size: 800})
|
||||||
|
.toPromise()
|
||||||
|
.then(function(result) {
|
||||||
|
document.querySelector('input.isbn').value = result.codeResult.code;
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
document.querySelector('input.isbn').value = "Not Found";
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
this.attachListeners();
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
attachListeners: function() {
|
||||||
|
var self = this,
|
||||||
|
button = document.querySelector('.input-field input + .button.scan'),
|
||||||
|
fileInput = document.querySelector('.input-field input[type=file]');
|
||||||
|
|
||||||
|
button.addEventListener("click", function onClick(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
button.removeEventListener("click", onClick);
|
||||||
|
document.querySelector('.input-field input[type=file]').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
fileInput.addEventListener("change", function onChange(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
fileInput.removeEventListener("change", onChange);
|
||||||
|
if (e.target.files && e.target.files.length) {
|
||||||
|
self.decode(URL.createObjectURL(e.target.files[0]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
App.init();
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="collapsable-source">
|
||||||
|
<pre>
|
||||||
|
<code class="language-html">
|
||||||
|
<form>
|
||||||
|
<div class="input-field">
|
||||||
|
<label for="isbn_input">EAN:</label>
|
||||||
|
<input id="isbn_input" class="isbn" type="text" />
|
||||||
|
<button type="button" class="icon-barcode button scan">&nbsp;</button>
|
||||||
|
<input type="file" id="file" capture/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<footer>
|
||||||
|
<p>
|
||||||
|
© Copyright by Christoph Oberhofer
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="../../dist/quagga.js" type="text/javascript"></script>
|
||||||
|
<script src="index.js" type="text/javascript"></script>
|
||||||
|
<script src="../vendor/prism.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
var Quagga = window.Quagga;
|
||||||
|
var App = {
|
||||||
|
_scanner: null,
|
||||||
|
init: function() {
|
||||||
|
this.attachListeners();
|
||||||
|
},
|
||||||
|
decode: function(file) {
|
||||||
|
Quagga
|
||||||
|
.decoder({readers: ['ean_reader']})
|
||||||
|
.locator({patchSize: 'medium'})
|
||||||
|
.fromSource(file, {size: 800})
|
||||||
|
.toPromise()
|
||||||
|
.then(function(result) {
|
||||||
|
document.querySelector('input.isbn').value = result.codeResult.code;
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
document.querySelector('input.isbn').value = "Not Found";
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
this.attachListeners();
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
attachListeners: function() {
|
||||||
|
var self = this,
|
||||||
|
button = document.querySelector('.input-field input + .button.scan'),
|
||||||
|
fileInput = document.querySelector('.input-field input[type=file]');
|
||||||
|
|
||||||
|
button.addEventListener("click", function onClick(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
button.removeEventListener("click", onClick);
|
||||||
|
document.querySelector('.input-field input[type=file]').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
fileInput.addEventListener("change", function onChange(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
fileInput.removeEventListener("change", onChange);
|
||||||
|
if (e.target.files && e.target.files.length) {
|
||||||
|
self.decode(e.target.files[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
App.init();
|
||||||
@@ -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>
|
||||||
|
|||||||
+40
-20
@@ -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=" " horiz-adv-x="512" d="" />
|
||||||
|
<glyph unicode="" glyph-name="barcode" d="M0 832h128v-640h-128zM192 832h64v-640h-64zM320 832h64v-640h-64zM512 832h64v-640h-64zM768 832h64v-640h-64zM960 832h64v-640h-64zM640 832h32v-640h-32zM448 832h32v-640h-32zM864 832h32v-640h-32zM0 128h64v-64h-64zM192 128h64v-64h-64zM320 128h64v-64h-64zM640 128h64v-64h-64zM960 128h64v-64h-64zM768 128h128v-64h-128zM448 128h128v-64h-128z" />
|
||||||
|
</font></defs></svg>
|
||||||
|
After Width: | Height: | Size: 821 B |
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>
|
||||||
|
|||||||
+45
-59
@@ -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>
|
||||||
|
© 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'
|
||||||
|
}
|
||||||
|
};
|
||||||
+44
@@ -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"> </button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<p>This example demonstrates the following features:
|
||||||
|
<ul>
|
||||||
|
<li>Access to user's camera</li>
|
||||||
|
<li>Configuring EAN-Reader</li>
|
||||||
|
<li>Use custom mount-point (Query-Selector)</li>
|
||||||
|
<li>Start/Stop scanning based on certain events</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
<div class="source-code">
|
||||||
|
<h4>Source</h4>
|
||||||
|
<div class="collapsable-source">
|
||||||
|
<pre>
|
||||||
|
<code class="language-javascript">
|
||||||
|
var Quagga = window.Quagga;
|
||||||
|
var App = {
|
||||||
|
_scanner: null,
|
||||||
|
init: function() {
|
||||||
|
this.attachListeners();
|
||||||
|
},
|
||||||
|
activateScanner: function() {
|
||||||
|
var scanner = this.configureScanner('.overlay__content'),
|
||||||
|
onDetected = function (result) {
|
||||||
|
document.querySelector('input.isbn').value = result.codeResult.code;
|
||||||
|
stop();
|
||||||
|
}.bind(this),
|
||||||
|
stop = function() {
|
||||||
|
scanner.stop(); // should also clear all event-listeners?
|
||||||
|
scanner.removeEventListener('detected', onDetected);
|
||||||
|
this.hideOverlay();
|
||||||
|
this.attachListeners();
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
this.showOverlay(stop);
|
||||||
|
scanner.addEventListener('detected', onDetected).start();
|
||||||
|
},
|
||||||
|
attachListeners: function() {
|
||||||
|
var self = this,
|
||||||
|
button = document.querySelector('.input-field input + button.scan');
|
||||||
|
|
||||||
|
button.addEventListener("click", function onClick(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
button.removeEventListener("click", onClick);
|
||||||
|
self.activateScanner();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showOverlay: function(cancelCb) {
|
||||||
|
if (!this._overlay) {
|
||||||
|
var content = document.createElement('div'),
|
||||||
|
closeButton = document.createElement('div');
|
||||||
|
|
||||||
|
closeButton.appendChild(document.createTextNode('X'));
|
||||||
|
content.className = 'overlay__content';
|
||||||
|
closeButton.className = 'overlay__close';
|
||||||
|
this._overlay = document.createElement('div');
|
||||||
|
this._overlay.className = 'overlay';
|
||||||
|
this._overlay.appendChild(content);
|
||||||
|
content.appendChild(closeButton);
|
||||||
|
closeButton.addEventListener('click', function closeClick() {
|
||||||
|
closeButton.removeEventListener('click', closeClick);
|
||||||
|
cancelCb();
|
||||||
|
});
|
||||||
|
document.body.appendChild(this._overlay);
|
||||||
|
} else {
|
||||||
|
var closeButton = document.querySelector('.overlay__close');
|
||||||
|
closeButton.addEventListener('click', function closeClick() {
|
||||||
|
closeButton.removeEventListener('click', closeClick);
|
||||||
|
cancelCb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._overlay.style.display = "block";
|
||||||
|
},
|
||||||
|
hideOverlay: function() {
|
||||||
|
if (this._overlay) {
|
||||||
|
this._overlay.style.display = "none";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
configureScanner: function(selector) {
|
||||||
|
if (!this._scanner) {
|
||||||
|
this._scanner = Quagga
|
||||||
|
.decoder({readers: ['ean_reader']})
|
||||||
|
.locator({patchSize: 'medium'})
|
||||||
|
.fromVideo({
|
||||||
|
target: selector,
|
||||||
|
constraints: {
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
facingMode: "environment"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this._scanner;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
App.init();
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div class="collapsable-source">
|
||||||
|
<pre>
|
||||||
|
<code class="language-html">
|
||||||
|
<form>
|
||||||
|
<div class="input-field">
|
||||||
|
<label for="isbn_input">EAN:</label>
|
||||||
|
<input id="isbn_input" class="isbn" type="text" />
|
||||||
|
<button type="button" class="icon-barcode button scan"> </button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<footer>
|
||||||
|
<p>
|
||||||
|
© Copyright by Christoph Oberhofer
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="../../dist/quagga.js" type="text/javascript"></script>
|
||||||
|
<script src="index.js" type="text/javascript"></script>
|
||||||
|
<script src="../vendor/prism.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
var Quagga = window.Quagga;
|
||||||
|
var App = {
|
||||||
|
_scanner: null,
|
||||||
|
init: function() {
|
||||||
|
this.attachListeners();
|
||||||
|
},
|
||||||
|
activateScanner: function() {
|
||||||
|
var scanner = this.configureScanner('.overlay__content'),
|
||||||
|
onDetected = function (result) {
|
||||||
|
document.querySelector('input.isbn').value = result.codeResult.code;
|
||||||
|
stop();
|
||||||
|
}.bind(this),
|
||||||
|
stop = function() {
|
||||||
|
scanner.stop(); // should also clear all event-listeners?
|
||||||
|
scanner.removeEventListener('detected', onDetected);
|
||||||
|
this.hideOverlay();
|
||||||
|
this.attachListeners();
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
this.showOverlay(stop);
|
||||||
|
scanner.addEventListener('detected', onDetected).start();
|
||||||
|
},
|
||||||
|
attachListeners: function() {
|
||||||
|
var self = this,
|
||||||
|
button = document.querySelector('.input-field input + button.scan');
|
||||||
|
|
||||||
|
button.addEventListener("click", function onClick(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
button.removeEventListener("click", onClick);
|
||||||
|
self.activateScanner();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showOverlay: function(cancelCb) {
|
||||||
|
if (!this._overlay) {
|
||||||
|
var content = document.createElement('div'),
|
||||||
|
closeButton = document.createElement('div');
|
||||||
|
|
||||||
|
closeButton.appendChild(document.createTextNode('X'));
|
||||||
|
content.className = 'overlay__content';
|
||||||
|
closeButton.className = 'overlay__close';
|
||||||
|
this._overlay = document.createElement('div');
|
||||||
|
this._overlay.className = 'overlay';
|
||||||
|
this._overlay.appendChild(content);
|
||||||
|
content.appendChild(closeButton);
|
||||||
|
closeButton.addEventListener('click', function closeClick() {
|
||||||
|
closeButton.removeEventListener('click', closeClick);
|
||||||
|
cancelCb();
|
||||||
|
});
|
||||||
|
document.body.appendChild(this._overlay);
|
||||||
|
} else {
|
||||||
|
var closeButton = document.querySelector('.overlay__close');
|
||||||
|
closeButton.addEventListener('click', function closeClick() {
|
||||||
|
closeButton.removeEventListener('click', closeClick);
|
||||||
|
cancelCb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._overlay.style.display = "block";
|
||||||
|
},
|
||||||
|
hideOverlay: function() {
|
||||||
|
if (this._overlay) {
|
||||||
|
this._overlay.style.display = "none";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
configureScanner: function(selector) {
|
||||||
|
if (!this._scanner) {
|
||||||
|
this._scanner = Quagga
|
||||||
|
.decoder({readers: ['ean_reader']})
|
||||||
|
.locator({patchSize: 'medium'})
|
||||||
|
.fromSource({
|
||||||
|
target: selector,
|
||||||
|
constraints: {
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
facingMode: "environment"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this._scanner;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
App.init();
|
||||||
@@ -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>
|
||||||
|
|||||||
+35
-20
@@ -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);
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Vendored
+7
File diff suppressed because one or more lines are too long
+1
-2
@@ -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,
|
||||||
|
|||||||
+5677
-3117
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3
-2
@@ -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,7 +1,8 @@
|
|||||||
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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|
||||||
|
if ($debug) {
|
||||||
_canvas.dom.overlay = document.querySelector("canvas.drawingBuffer");
|
_canvas.dom.overlay = document.querySelector("canvas.drawingBuffer");
|
||||||
if (_canvas.dom.overlay) {
|
if (!_canvas.dom.overlay) {
|
||||||
|
_canvas.dom.overlay = document.createElement("canvas");
|
||||||
|
_canvas.dom.overlay.className = "drawingBuffer";
|
||||||
|
$debug.appendChild(_canvas.dom.overlay);
|
||||||
|
}
|
||||||
_canvas.ctx.overlay = _canvas.dom.overlay.getContext("2d");
|
_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;
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ const mat2 = {
|
|||||||
copy: require('gl-mat2/copy'),
|
copy: require('gl-mat2/copy'),
|
||||||
create: require('gl-mat2/create'),
|
create: require('gl-mat2/create'),
|
||||||
invert: require('gl-mat2/invert')
|
invert: require('gl-mat2/invert')
|
||||||
}
|
};
|
||||||
|
|
||||||
var _config,
|
export default function createLocator(inputImageWrapper, config) {
|
||||||
|
var _config = config,
|
||||||
_currentImageWrapper,
|
_currentImageWrapper,
|
||||||
_skelImageWrapper,
|
_skelImageWrapper,
|
||||||
_subImageWrapper,
|
_subImageWrapper,
|
||||||
@@ -45,9 +46,12 @@ var _config,
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
_numPatches = {x: 0, y: 0},
|
_numPatches = {x: 0, y: 0},
|
||||||
_inputImageWrapper,
|
_inputImageWrapper = inputImageWrapper,
|
||||||
_skeletonizer;
|
_skeletonizer;
|
||||||
|
|
||||||
|
initBuffers();
|
||||||
|
initCanvas();
|
||||||
|
|
||||||
function initBuffers() {
|
function initBuffers() {
|
||||||
var skeletonImageData;
|
var skeletonImageData;
|
||||||
|
|
||||||
@@ -525,15 +529,7 @@ function rasterizeAngularSimilarity(patchesFound) {
|
|||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
return {
|
||||||
init: function(inputImageWrapper, config) {
|
|
||||||
_config = config;
|
|
||||||
_inputImageWrapper = inputImageWrapper;
|
|
||||||
|
|
||||||
initBuffers();
|
|
||||||
initCanvas();
|
|
||||||
},
|
|
||||||
|
|
||||||
locate: function() {
|
locate: function() {
|
||||||
var patchesFound,
|
var patchesFound,
|
||||||
topLabels,
|
topLabels,
|
||||||
@@ -564,9 +560,10 @@ export default {
|
|||||||
|
|
||||||
boxes = findBoxes(topLabels, maxLabel);
|
boxes = findBoxes(topLabels, maxLabel);
|
||||||
return boxes;
|
return boxes;
|
||||||
},
|
}
|
||||||
|
};
|
||||||
checkImageConstraints: function(inputStream, config) {
|
}
|
||||||
|
export function checkImageConstraints(inputStream, config) {
|
||||||
var patchSize,
|
var patchSize,
|
||||||
width = inputStream.getWidth(),
|
width = inputStream.getWidth(),
|
||||||
height = inputStream.getHeight(),
|
height = inputStream.getHeight(),
|
||||||
@@ -604,4 +601,3 @@ export default {
|
|||||||
width + " )and height (" + height +
|
width + " )and height (" + height +
|
||||||
") must a multiple of " + patchSize.x);
|
") must a multiple of " + patchSize.x);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|||||||
+121
-527
@@ -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: {
|
|
||||||
image: null,
|
|
||||||
overlay: null
|
|
||||||
},
|
|
||||||
dom: {
|
|
||||||
image: null,
|
|
||||||
overlay: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_inputImageWrapper,
|
|
||||||
_boxSize,
|
|
||||||
_decoder,
|
|
||||||
_workerPool = [],
|
|
||||||
_onUIThread = true,
|
|
||||||
_resultCollector,
|
|
||||||
_config = {};
|
|
||||||
|
|
||||||
function initializeData(imageWrapper) {
|
|
||||||
initBuffers(imageWrapper);
|
|
||||||
_decoder = BarcodeDecoder.create(_config.decoder, _inputImageWrapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
function initInputStream(cb) {
|
|
||||||
var video;
|
|
||||||
if (_config.inputStream.type === "VideoStream") {
|
|
||||||
video = document.createElement("video");
|
|
||||||
_inputStream = InputStream.createVideoStream(video);
|
|
||||||
} else if (_config.inputStream.type === "ImageStream") {
|
|
||||||
_inputStream = InputStream.createImageStream();
|
|
||||||
} else if (_config.inputStream.type === "LiveStream") {
|
|
||||||
var $viewport = getViewPort();
|
|
||||||
if ($viewport) {
|
|
||||||
video = $viewport.querySelector("video");
|
|
||||||
if (!video) {
|
|
||||||
video = document.createElement("video");
|
|
||||||
$viewport.appendChild(video);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_inputStream = InputStream.createLiveStream(video);
|
|
||||||
CameraAccess.request(video, _config.inputStream.constraints)
|
|
||||||
.then(() => {
|
|
||||||
_inputStream.trigger("canrecord");
|
|
||||||
}).catch((err) => {
|
|
||||||
return cb(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_inputStream.setAttribute("preload", "auto");
|
|
||||||
_inputStream.setInputStream(_config.inputStream);
|
|
||||||
_inputStream.addEventListener("canrecord", canRecord.bind(undefined, cb));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getViewPort() {
|
|
||||||
var target = _config.inputStream.target;
|
|
||||||
// Check if target is already a DOM element
|
|
||||||
if (target && target.nodeName && target.nodeType === 1) {
|
|
||||||
return target;
|
|
||||||
} else {
|
|
||||||
// Use '#interactive.viewport' as a fallback selector (backwards compatibility)
|
|
||||||
var selector = typeof target === 'string' ? target : '#interactive.viewport';
|
|
||||||
return document.querySelector(selector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function canRecord(cb) {
|
|
||||||
BarcodeLocator.checkImageConstraints(_inputStream, _config.locator);
|
|
||||||
initCanvas(_config);
|
|
||||||
_framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image);
|
|
||||||
|
|
||||||
adjustWorkerPool(_config.numOfWorkers, function() {
|
|
||||||
if (_config.numOfWorkers === 0) {
|
|
||||||
initializeData();
|
|
||||||
}
|
|
||||||
ready(cb);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function ready(cb){
|
|
||||||
_inputStream.play();
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
|
|
||||||
function initCanvas() {
|
|
||||||
if (typeof document !== "undefined") {
|
|
||||||
var $viewport = getViewPort();
|
|
||||||
_canvasContainer.dom.image = document.querySelector("canvas.imgBuffer");
|
|
||||||
if (!_canvasContainer.dom.image) {
|
|
||||||
_canvasContainer.dom.image = document.createElement("canvas");
|
|
||||||
_canvasContainer.dom.image.className = "imgBuffer";
|
|
||||||
if ($viewport && _config.inputStream.type === "ImageStream") {
|
|
||||||
$viewport.appendChild(_canvasContainer.dom.image);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d");
|
|
||||||
_canvasContainer.dom.image.width = _inputStream.getCanvasSize().x;
|
|
||||||
_canvasContainer.dom.image.height = _inputStream.getCanvasSize().y;
|
|
||||||
|
|
||||||
_canvasContainer.dom.overlay = document.querySelector("canvas.drawingBuffer");
|
|
||||||
if (!_canvasContainer.dom.overlay) {
|
|
||||||
_canvasContainer.dom.overlay = document.createElement("canvas");
|
|
||||||
_canvasContainer.dom.overlay.className = "drawingBuffer";
|
|
||||||
if ($viewport) {
|
|
||||||
$viewport.appendChild(_canvasContainer.dom.overlay);
|
|
||||||
}
|
|
||||||
var clearFix = document.createElement("br");
|
|
||||||
clearFix.setAttribute("clear", "all");
|
|
||||||
if ($viewport) {
|
|
||||||
$viewport.appendChild(clearFix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_canvasContainer.ctx.overlay = _canvasContainer.dom.overlay.getContext("2d");
|
|
||||||
_canvasContainer.dom.overlay.width = _inputStream.getCanvasSize().x;
|
|
||||||
_canvasContainer.dom.overlay.height = _inputStream.getCanvasSize().y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initBuffers(imageWrapper) {
|
|
||||||
if (imageWrapper) {
|
|
||||||
_inputImageWrapper = imageWrapper;
|
|
||||||
} else {
|
|
||||||
_inputImageWrapper = new ImageWrapper({
|
|
||||||
x: _inputStream.getWidth(),
|
|
||||||
y: _inputStream.getHeight()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ENV.development) {
|
|
||||||
console.log(_inputImageWrapper.size);
|
|
||||||
}
|
|
||||||
_boxSize = [
|
|
||||||
vec2.clone([0, 0]),
|
|
||||||
vec2.clone([0, _inputImageWrapper.size.y]),
|
|
||||||
vec2.clone([_inputImageWrapper.size.x, _inputImageWrapper.size.y]),
|
|
||||||
vec2.clone([_inputImageWrapper.size.x, 0])
|
|
||||||
];
|
|
||||||
BarcodeLocator.init(_inputImageWrapper, _config.locator);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBoundingBoxes() {
|
|
||||||
if (_config.locate) {
|
|
||||||
return BarcodeLocator.locate();
|
|
||||||
} else {
|
|
||||||
return [[
|
|
||||||
vec2.clone(_boxSize[0]),
|
|
||||||
vec2.clone(_boxSize[1]),
|
|
||||||
vec2.clone(_boxSize[2]),
|
|
||||||
vec2.clone(_boxSize[3])]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function transformResult(result) {
|
|
||||||
var topRight = _inputStream.getTopRight(),
|
|
||||||
xOffset = topRight.x,
|
|
||||||
yOffset = topRight.y,
|
|
||||||
i;
|
|
||||||
|
|
||||||
if (xOffset === 0 && yOffset === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.barcodes) {
|
|
||||||
for (i = 0; i < result.barcodes.length; i++) {
|
|
||||||
transformResult(result.barcodes[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.line && result.line.length === 2) {
|
|
||||||
moveLine(result.line);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.box) {
|
|
||||||
moveBox(result.box);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.boxes && result.boxes.length > 0) {
|
|
||||||
for (i = 0; i < result.boxes.length; i++) {
|
|
||||||
moveBox(result.boxes[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveBox(box) {
|
|
||||||
var corner = box.length;
|
|
||||||
|
|
||||||
while (corner--) {
|
|
||||||
box[corner][0] += xOffset;
|
|
||||||
box[corner][1] += yOffset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveLine(line) {
|
|
||||||
line[0].x += xOffset;
|
|
||||||
line[0].y += yOffset;
|
|
||||||
line[1].x += xOffset;
|
|
||||||
line[1].y += yOffset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addResult (result, imageData) {
|
|
||||||
if (!imageData || !_resultCollector) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.barcodes) {
|
|
||||||
result.barcodes.filter(barcode => barcode.codeResult)
|
|
||||||
.forEach(barcode => addResult(barcode, imageData));
|
|
||||||
} else if (result.codeResult) {
|
|
||||||
_resultCollector.addResult(imageData, _inputStream.getCanvasSize(), result.codeResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasCodeResult (result) {
|
|
||||||
return result && (result.barcodes ?
|
|
||||||
result.barcodes.some(barcode => barcode.codeResult) :
|
|
||||||
result.codeResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
function publishResult(result, imageData) {
|
|
||||||
let resultToPublish = result;
|
|
||||||
|
|
||||||
if (result && _onUIThread) {
|
|
||||||
transformResult(result);
|
|
||||||
addResult(result, imageData);
|
|
||||||
resultToPublish = result.barcodes || result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Events.publish("processed", resultToPublish);
|
|
||||||
if (hasCodeResult(result)) {
|
|
||||||
Events.publish("detected", resultToPublish);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function locateAndDecode() {
|
|
||||||
var result,
|
|
||||||
boxes;
|
|
||||||
|
|
||||||
boxes = getBoundingBoxes();
|
|
||||||
if (boxes) {
|
|
||||||
result = _decoder.decodeFromBoundingBoxes(boxes);
|
|
||||||
result = result || {};
|
|
||||||
result.boxes = boxes;
|
|
||||||
publishResult(result, _inputImageWrapper.data);
|
|
||||||
} else {
|
|
||||||
publishResult();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
var availableWorker;
|
|
||||||
|
|
||||||
if (_onUIThread) {
|
|
||||||
if (_workerPool.length > 0) {
|
|
||||||
availableWorker = _workerPool.filter(function(workerThread) {
|
|
||||||
return !workerThread.busy;
|
|
||||||
})[0];
|
|
||||||
if (availableWorker) {
|
|
||||||
_framegrabber.attachData(availableWorker.imageData);
|
|
||||||
} else {
|
|
||||||
return; // all workers are busy
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_framegrabber.attachData(_inputImageWrapper.data);
|
|
||||||
}
|
|
||||||
if (_framegrabber.grab()) {
|
|
||||||
if (availableWorker) {
|
|
||||||
availableWorker.busy = true;
|
|
||||||
availableWorker.worker.postMessage({
|
|
||||||
cmd: 'process',
|
|
||||||
imageData: availableWorker.imageData
|
|
||||||
}, [availableWorker.imageData.buffer]);
|
|
||||||
} else {
|
|
||||||
locateAndDecode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
locateAndDecode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function startContinuousUpdate() {
|
|
||||||
var next = null,
|
|
||||||
delay = 1000 / (_config.frequency || 60);
|
|
||||||
|
|
||||||
_stopped = false;
|
|
||||||
(function frame(timestamp) {
|
|
||||||
next = next || timestamp;
|
|
||||||
if (!_stopped) {
|
|
||||||
if (timestamp >= next) {
|
|
||||||
next += delay;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
window.requestAnimFrame(frame);
|
|
||||||
}
|
|
||||||
}(performance.now()));
|
|
||||||
}
|
|
||||||
|
|
||||||
function start() {
|
|
||||||
if (_onUIThread && _config.inputStream.type === "LiveStream") {
|
|
||||||
startContinuousUpdate();
|
|
||||||
} else {
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initWorker(cb) {
|
|
||||||
var blobURL,
|
|
||||||
workerThread = {
|
|
||||||
worker: undefined,
|
|
||||||
imageData: new Uint8Array(_inputStream.getWidth() * _inputStream.getHeight()),
|
|
||||||
busy: true
|
|
||||||
};
|
|
||||||
|
|
||||||
blobURL = generateWorkerBlob();
|
|
||||||
workerThread.worker = new Worker(blobURL);
|
|
||||||
|
|
||||||
workerThread.worker.onmessage = function(e) {
|
|
||||||
if (e.data.event === 'initialized') {
|
|
||||||
URL.revokeObjectURL(blobURL);
|
|
||||||
workerThread.busy = false;
|
|
||||||
workerThread.imageData = new Uint8Array(e.data.imageData);
|
|
||||||
if (ENV.development) {
|
|
||||||
console.log("Worker initialized");
|
|
||||||
}
|
|
||||||
return cb(workerThread);
|
|
||||||
} else if (e.data.event === 'processed') {
|
|
||||||
workerThread.imageData = new Uint8Array(e.data.imageData);
|
|
||||||
workerThread.busy = false;
|
|
||||||
publishResult(e.data.result, workerThread.imageData);
|
|
||||||
} else if (e.data.event === 'error') {
|
|
||||||
if (ENV.development) {
|
|
||||||
console.log("Worker error: " + e.data.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
workerThread.worker.postMessage({
|
|
||||||
cmd: 'init',
|
|
||||||
size: {x: _inputStream.getWidth(), y: _inputStream.getHeight()},
|
|
||||||
imageData: workerThread.imageData,
|
|
||||||
config: configForWorker(_config)
|
|
||||||
}, [workerThread.imageData.buffer]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function configForWorker(config) {
|
|
||||||
return {
|
return {
|
||||||
...config,
|
addEventListener(eventType, cb) {
|
||||||
inputStream: {
|
scanner.subscribe(eventType, cb);
|
||||||
...config.inputStream,
|
return this;
|
||||||
target: null
|
},
|
||||||
|
removeEventListener(eventType, cb) {
|
||||||
|
scanner.unsubscribe(eventType, cb);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
start() {
|
||||||
|
if (scanner.isRunning()) {
|
||||||
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
};
|
if (pendingStart) {
|
||||||
|
return pendingStart;
|
||||||
}
|
}
|
||||||
|
if (initialized) {
|
||||||
function workerInterface(factory) {
|
scanner.start();
|
||||||
/* eslint-disable no-undef*/
|
return Promise.resolve(true);
|
||||||
if (factory) {
|
|
||||||
var Quagga = factory().default;
|
|
||||||
if (!Quagga) {
|
|
||||||
self.postMessage({'event': 'error', message: 'Quagga could not be created'});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
pendingStart = new Promise((resolve, reject) => {
|
||||||
|
scanner.init(config, (error) => {
|
||||||
|
if (error) {
|
||||||
|
console.log(error);
|
||||||
|
reject(error);
|
||||||
}
|
}
|
||||||
var imageWrapper;
|
initialized = true;
|
||||||
|
scanner.start();
|
||||||
self.onmessage = function(e) {
|
resolve();
|
||||||
if (e.data.cmd === 'init') {
|
pendingStart = null;
|
||||||
var config = e.data.config;
|
});
|
||||||
config.numOfWorkers = 0;
|
});
|
||||||
imageWrapper = new Quagga.ImageWrapper({
|
return pendingStart;
|
||||||
x: e.data.size.x,
|
},
|
||||||
y: e.data.size.y
|
stop() {
|
||||||
}, new Uint8Array(e.data.imageData));
|
scanner.stop();
|
||||||
Quagga.init(config, ready, imageWrapper);
|
initialized = false;
|
||||||
Quagga.onProcessed(onProcessed);
|
return this;
|
||||||
} else if (e.data.cmd === 'process') {
|
},
|
||||||
imageWrapper.data = new Uint8Array(e.data.imageData);
|
toPromise() {
|
||||||
Quagga.start();
|
if (config.inputStream.type === 'LiveStream'
|
||||||
} else if (e.data.cmd === 'setReaders') {
|
|| config.inputStream.type === 'VideoStream') {
|
||||||
Quagga.setReaders(e.data.readers);
|
let cancelRequested = false;
|
||||||
}
|
return {
|
||||||
};
|
cancel() {
|
||||||
|
cancelRequested = true;
|
||||||
|
},
|
||||||
|
promise: new Promise((resolve, reject) => {
|
||||||
function onProcessed(result) {
|
function onProcessed(result) {
|
||||||
self.postMessage({
|
if (result && result.codeResult && result.codeResult.code) {
|
||||||
'event': 'processed',
|
scanner.stop();
|
||||||
imageData: imageWrapper.data,
|
scanner.unsubscribe("processed", onProcessed);
|
||||||
result: result
|
resolve(result);
|
||||||
}, [imageWrapper.data.buffer]);
|
|
||||||
}
|
}
|
||||||
|
if (cancelRequested) {
|
||||||
function ready() { // eslint-disable-line
|
scanner.stop();
|
||||||
self.postMessage({'event': 'initialized', imageData: imageWrapper.data}, [imageWrapper.data.buffer]);
|
scanner.unsubscribe("processed", onProcessed);
|
||||||
}
|
reject("cancelled!");
|
||||||
|
|
||||||
/* 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});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
scanner.subscribe("processed", onProcessed);
|
||||||
function adjustWorkerPool(capacity, cb) {
|
this.start();
|
||||||
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
|
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
scanner.decodeSingle(config, (result) => {
|
||||||
|
if (result && result.codeResult && result.codeResult.code) {
|
||||||
|
return resolve(result);
|
||||||
|
}
|
||||||
|
return reject(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
registerResultCollector(resultCollector) {
|
||||||
|
scanner.registerResultCollector(resultCollector);
|
||||||
|
},
|
||||||
|
getCanvas() {
|
||||||
|
return scanner.canvas.dom.image;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromSource(config, source, inputConfig = {}) {
|
||||||
|
config = createConfigFromSource(config, inputConfig, source);
|
||||||
|
return fromConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setConfig(configuration = {}, key, config = {}) {
|
||||||
|
var mergedConfig = merge({}, configuration, {[key]: config});
|
||||||
|
return createApi(mergedConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createApi(configuration = Config) {
|
||||||
|
return {
|
||||||
|
fromSource(src, inputConfig) {
|
||||||
|
return fromSource(configuration, src, inputConfig);
|
||||||
|
},
|
||||||
|
fromConfig(conf) {
|
||||||
|
return fromConfig(merge({}, configuration, conf));
|
||||||
|
},
|
||||||
|
decoder(conf) {
|
||||||
|
return setConfig(configuration, "decoder", conf);
|
||||||
|
},
|
||||||
|
locator(conf) {
|
||||||
|
return setConfig(configuration, "locator", conf);
|
||||||
|
},
|
||||||
|
throttle(timeInMs) {
|
||||||
|
return setConfig(configuration, "frequency", 1000 / parseInt(timeInMs));
|
||||||
|
},
|
||||||
|
config(conf) {
|
||||||
|
return createApi(merge({}, configuration, conf));
|
||||||
|
},
|
||||||
|
ImageWrapper,
|
||||||
|
ImageDebug,
|
||||||
|
ResultCollector,
|
||||||
|
_worker: {
|
||||||
|
createScanner
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export default createApi();
|
||||||
|
|||||||
+508
@@ -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
|
||||||
|
.config(config)
|
||||||
|
.fromSource(config.src)
|
||||||
|
.addEventListener('processed', function(result){
|
||||||
console.log(sample.name);
|
console.log(sample.name);
|
||||||
expect(result.codeResult.code).to.equal(sample.result);
|
expect(result.codeResult.code).to.equal(sample.result);
|
||||||
expect(result.codeResult.format).to.equal(sample.format);
|
expect(result.codeResult.format).to.equal(sample.format);
|
||||||
callback();
|
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,6 +5,7 @@ var originalURL,
|
|||||||
video,
|
video,
|
||||||
stream;
|
stream;
|
||||||
|
|
||||||
|
describe("CameraAccess", () => {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
var tracks = [{
|
var tracks = [{
|
||||||
stop: function() {}
|
stop: function() {}
|
||||||
@@ -14,7 +15,7 @@ beforeEach(function() {
|
|||||||
originalMediaStreamTrack = window.MediaStreamTrack;
|
originalMediaStreamTrack = window.MediaStreamTrack;
|
||||||
window.MediaStreamTrack = {};
|
window.MediaStreamTrack = {};
|
||||||
window.URL = {
|
window.URL = {
|
||||||
createObjectURL(stream) {
|
createObjectURL() {
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -63,7 +64,7 @@ describe('success', function() {
|
|||||||
expect(navigator.mediaDevices.getUserMedia.calledOnce).to.equal(true);
|
expect(navigator.mediaDevices.getUserMedia.calledOnce).to.equal(true);
|
||||||
expect(video.srcObject).to.deep.equal(stream);
|
expect(video.srcObject).to.deep.equal(stream);
|
||||||
done();
|
done();
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow deprecated constraints to be used", (done) => {
|
it("should allow deprecated constraints to be used", (done) => {
|
||||||
@@ -86,7 +87,7 @@ describe('success', function() {
|
|||||||
expect(args[0].video.minAspectRatio).not.to.be.defined;
|
expect(args[0].video.minAspectRatio).not.to.be.defined;
|
||||||
expect(args[0].video.maxAspectRatio).not.to.be.defined;
|
expect(args[0].video.maxAspectRatio).not.to.be.defined;
|
||||||
done();
|
done();
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -103,7 +104,7 @@ describe('success', function() {
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
window.MediaStreamTrack = {};
|
window.MediaStreamTrack = {};
|
||||||
})
|
});
|
||||||
|
|
||||||
it("should set deviceId in case facingMode is not supported", (done) => {
|
it("should set deviceId in case facingMode is not supported", (done) => {
|
||||||
CameraAccess.request(video, {
|
CameraAccess.request(video, {
|
||||||
@@ -116,7 +117,7 @@ describe('success', function() {
|
|||||||
expect(args[0].video.facingMode).not.to.be.defined;
|
expect(args[0].video.facingMode).not.to.be.defined;
|
||||||
expect(args[0].video.deviceId).to.equal("user");
|
expect(args[0].video.deviceId).to.equal("user");
|
||||||
done();
|
done();
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -176,3 +177,4 @@ describe('failure', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
import {createConfigFromSource} from '../../src/input/config_factory';
|
||||||
|
import DOMHelper from '../../src/common/dom_helper';
|
||||||
|
|
||||||
|
function MyFileList(file) {
|
||||||
|
Array.call(this);
|
||||||
|
this.push(file);
|
||||||
|
};
|
||||||
|
MyFileList.prototype = Object.create(Array.prototype);
|
||||||
|
MyFileList.prototype.constructor = MyFileList;
|
||||||
|
|
||||||
|
const OriginalFileList = DOMHelper.FileList;
|
||||||
|
|
||||||
|
function expectImageConfig(config) {
|
||||||
|
expect(config.inputStream.type).to.equal("ImageStream");
|
||||||
|
expect(config.inputStream.sequence).to.equal(false);
|
||||||
|
expect(config.inputStream.size).to.be.above(0);
|
||||||
|
expect(config.numOfWorkers).to.equal(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectVideoConfig(config) {
|
||||||
|
expect(config.inputStream.type).to.equal("VideoStream");
|
||||||
|
expect(config.inputStream.src).to.be.a('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectLiveConfig(config) {
|
||||||
|
expect(config.inputStream.type).to.equal("LiveStream");
|
||||||
|
expect(config.inputStream.src).to.not.exist;
|
||||||
|
expect(config.inputStream.constraints.width).to.be.above(0);
|
||||||
|
expect(config.inputStream.constraints.height).to.be.above(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("createConfigFromSource", () => {
|
||||||
|
beforeEach(function() {
|
||||||
|
DOMHelper.setObject('FileList', MyFileList);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
DOMHelper.setObject('FileList', OriginalFileList);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create an image config for an image-file", () => {
|
||||||
|
const file = new File([], "image.jpg", {type: 'image/jpg'});
|
||||||
|
const config = createConfigFromSource({}, {}, file);
|
||||||
|
expectImageConfig(config);
|
||||||
|
expect(config.inputStream.src).to.have.string("blob:");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create an image config for a data-url", () => {
|
||||||
|
const config = createConfigFromSource({}, {}, "data:image/png;base64," +
|
||||||
|
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/d" +
|
||||||
|
"AAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A" +
|
||||||
|
"6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC");
|
||||||
|
|
||||||
|
expectImageConfig(config);
|
||||||
|
expect(config.inputStream.src).to.have.string("data:image/png");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create an image config for a regular image url", () => {
|
||||||
|
const config = createConfigFromSource({}, {}, "/image-001.jpg");
|
||||||
|
expectImageConfig(config);
|
||||||
|
expect(config.inputStream.src).to.have.string("/image-001.jpg");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create an image config for an absolute image url", () => {
|
||||||
|
const config = createConfigFromSource({}, {}, "http://dja.com/ige.png");
|
||||||
|
expectImageConfig(config);
|
||||||
|
expect(config.inputStream.src).to.have.string("http://dja.com/ige.png");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error in case of an blob-url", () => {
|
||||||
|
expect(createConfigFromSource.bind(null, {}, {}, "blob:das"))
|
||||||
|
.to.throw(Error, /objectURL/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error in case an arbitrary string is given", () => {
|
||||||
|
expect(createConfigFromSource.bind(null, {}, {}, "dhfskjdfhsdfsdf"))
|
||||||
|
.to.throw(Error, /dhfskjdfhsdfsdf/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error in case of an unsupported mime type", () => {
|
||||||
|
expect(createConfigFromSource.bind(null, {}, {}, "data:audio/mp3;base64,379"))
|
||||||
|
.to.throw(Error, /mimetype/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error in case of an unsupported extension", () => {
|
||||||
|
expect(createConfigFromSource.bind(null, {}, {}, "sdflsdkf.mp3"))
|
||||||
|
.to.throw(Error, /MediaString/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error in case of an HTMLImageElement", () => {
|
||||||
|
expect(createConfigFromSource.bind(null, {}, {}, new Image()))
|
||||||
|
.to.throw(Error, /HTMLImageElement/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error in case of an HTMLVideoElement", () => {
|
||||||
|
const video = document.createElement("video");
|
||||||
|
console.log(typeof video);
|
||||||
|
expect(createConfigFromSource.bind(null, {}, {}, video))
|
||||||
|
.to.throw(Error, /HTMLVideoElement/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should work with a fileList", () => {
|
||||||
|
const file = new File([], "image.jpg", {type: 'image/jpg'});
|
||||||
|
const fileList = new MyFileList(file);
|
||||||
|
const config = createConfigFromSource({}, {}, fileList);
|
||||||
|
expectImageConfig(config);
|
||||||
|
expect(config.inputStream.src).to.have.string("blob:");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create a video config for a given url", () => {
|
||||||
|
const config = createConfigFromSource({}, {}, "/video-001.ogg");
|
||||||
|
expectVideoConfig(config);
|
||||||
|
expect(config.inputStream.src).to.have.string("/video-001.ogg");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create a video config for a given file", () => {
|
||||||
|
const file = new File([], "video-001.ogg", {type: 'video/ogg'});
|
||||||
|
const config = createConfigFromSource({}, {}, file);
|
||||||
|
expectVideoConfig(config);
|
||||||
|
expect(config.inputStream.src).to.have.string("blob:");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create a live config", () => {
|
||||||
|
const config = createConfigFromSource({}, {}, {
|
||||||
|
constraints: {
|
||||||
|
width: 1280,
|
||||||
|
height: 480,
|
||||||
|
facingMode: "user"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expectLiveConfig(config);
|
||||||
|
expect(config.inputStream.constraints.facingMode).to.equal("user");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import Events from '../../src/common/events';
|
import createEventedElement from '../../src/common/events';
|
||||||
|
|
||||||
|
let Events;
|
||||||
beforeEach(function() {
|
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,6 +5,7 @@ var canvasMock,
|
|||||||
imageSize,
|
imageSize,
|
||||||
config;
|
config;
|
||||||
|
|
||||||
|
describe("ResultCollector", () => {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
imageSize = {x: 320, y: 240};
|
imageSize = {x: 320, y: 240};
|
||||||
config = {
|
config = {
|
||||||
@@ -101,3 +102,4 @@ describe('addResult', function() {
|
|||||||
expect(collector.getResults()).to.have.length(1);
|
expect(collector.getResults()).to.have.length(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|||||||
+1
-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?$/,
|
||||||
|
|||||||
Reference in New Issue
Block a user