Compare commits

..

54 Commits

Author SHA1 Message Date
Eric Blade 862df88970
Merge pull request #369 from LasseRafn/patch-1
Update README.md
6 years ago
Lasse Rafn 262217b924
Update README.md
Modern iOS Safari supports this
6 years ago
Eric Blade e96eb9f788
Merge pull request #344 from xvzf/add-typescript-module
Declare module so webpack can do its work
7 years ago
Matthias Riegler 4c73d76692
Declare module so webpack can do its work 7 years ago
Eric Blade 42b5382510
Merge pull request #234 from danmana/typescript
Update typings to match v0.12.1
7 years ago
kieat 78179ce570 Allow drawRect to accept and handle style.lineWidth (#315)
Allow drawRect to accept and handle style.lineWidth
7 years ago
Tomáš Hübelbauer 271be5d3ff
Merge branch 'master' into typescript 7 years ago
Rudolph Gottesheim 0a295f537e Add "typings" key to package.json (#170)
https://www.typescriptlang.org/docs/handbook/module-resolution.html#how-typescript-resolves-modules
7 years ago
José Pedro Silva 1adba48ddc Bower & Typings Support (#171)
* chore(bower): update bower definitions

* chore(typings): update typings pointer on package.json
7 years ago
Tomáš Hübelbauer 5817a3daba
Harmonize Node and Bower versions (#304) 7 years ago
Eric Blade a4de46f9a9
Merge pull request #302 from gitter-badger/gitter-badge
Add a Gitter chat badge to README.md
7 years ago
The Gitter Badger 9c24fc78b2 Add Gitter badge 7 years ago
Dan.Manastireanu 12a74d03d4 Add missing typing for inputStream target 8 years ago
Dan Manastireanu a6d370a9c1 Update typings to match v0.12.1 8 years ago
Christoph Oberhofer eff0c5ad12 0.12.1 8 years ago
Christoph Oberhofer e8352c7017 Fixed error in live_example; Working for iOS in Safari 11 beta 8 years ago
Christoph Oberhofer 74ed8c0b60 0.12.0 8 years ago
Christoph Oberhofer f25c0e17d8 Updated README 8 years ago
Christoph Oberhofer fffa63ca2b Updated Readme 8 years ago
Christoph Oberhofer 42f2b70e64 Added zoom & torch if available 8 years ago
Christoph Oberhofer 012924fbe2 Exposing active track to API 8 years ago
Christoph Oberhofer ddc385641a examples: CSS cleanup 8 years ago
Christoph Oberhofer 620292c509 Added dist-versions for #195 8 years ago
Christoph Oberhofer 0420d5e093 Merge pull request #197 from serratus/feature/195
Added support for Code 93 barcodes.
Closes #195
8 years ago
Christoph Oberhofer 57312a0638 195: Extracted common method to BarcodeReader; 8 years ago
Christoph Oberhofer e2b37c32b1 #195: Added Code93 barcode reader; 8 years ago
Christoph Oberhofer 2e4b14b92f Merge pull request #196 from serratus/feature/194
Added basic support for 2of5 barcodes
Closes #194
8 years ago
Christoph Oberhofer 55d2eb971b 194: Refactoring/Review 8 years ago
Christoph Oberhofer d01cd6d527 194: Added dist files 8 years ago
Christoph Oberhofer 06bcda3157 194: Added basic support for 2of5 barcodes; fixed integration-tests 8 years ago
Christoph Oberhofer f9a19e0a02 Merge pull request #176 from nicolashenry/fix-math-floor
Performance: remove useless Math.floor
8 years ago
E020873 25dd873d9c remove useless Math.floor 8 years ago
Christoph Oberhofer 80a9f61b6f Merge pull request #144 from Mtillmann/patch-1
fixed node example
8 years ago
Christoph Oberhofer a0e55f235c Merge branch 'xStarman-node_with_buffer' 8 years ago
Christoph Oberhofer a4c0efc0df Moved mime-type definition to inputStream config 8 years ago
Christoph Oberhofer c2d583f19a Closes #167; Removed dependency on webrtc-adapter; Removed workaround for facing-mode that is not needed any more when using the latest version of webrtc-adapter; 8 years ago
Edson Alves 4ee36d1612 Add example to folder 8 years ago
Edson Alves 15bf53e14d Add buffer option to getPixels using mime 8 years ago
Christoph Oberhofer b88a7b71a7 0.11.6 9 years ago
Christoph Oberhofer d9b56d2dc1 Updated readme to 0.11.6 9 years ago
Christoph Oberhofer 810f9370ca Build version for 0.11.6 9 years ago
Christoph Oberhofer 036fc30d48 Added camera selection to example; Upgrade to webpack 2.2 9 years ago
Christoph Oberhofer 821d04a78b Dependency updates; fixed uglyasm task for newer webpack versions 9 years ago
Martin Tillmann 942104916b fixed node example
.default was missing
9 years ago
Christoph Oberhofer edea60fe71 0.11.5 9 years ago
Christoph Oberhofer 68af856b35 Added build for 0.11.5 9 years ago
Christoph Oberhofer aa61f18265 Merge pull request #131 from serratus/issue/128
Camera Facing Issue in Chrome >= 53
9 years ago
Christoph Oberhofer 4363c85149 Added changelog entry; Related to #128 9 years ago
Christoph Oberhofer c4cbf10976 Added tests for selecting camera; related to #128 9 years ago
Christoph Oberhofer 473f4366a5 Selecting camera based on facingMode; Relates to #128 9 years ago
Christoph Oberhofer 2c45929800 Updated README; Fixes #114 9 years ago
Christoph Oberhofer ed794e98a3 0.11.4 9 years ago
Christoph Oberhofer 5e90f25b0b Updated webrtc-adapter; Removed workaround for Chrome 9 years ago
Christoph Oberhofer 9a91962027 Added information about getUserMedia; Relates to #116 9 years ago

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

@ -1,22 +1,26 @@
quaggaJS
========
- [Changelog](#changelog) (2016-08-15)
[![Join the chat at https://gitter.im/quaggaJS/Lobby](https://badges.gitter.im/quaggaJS/Lobby.svg)](https://gitter.im/quaggaJS/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
- [Changelog](#changelog) (2017-06-07)
- [Browser Support](#browser-support)
- [Installing](#installing)
- [Getting Started](#gettingstarted)
- [API](#api)
- [Configuration](#configobject)
- [Tips & Tricks](#tipsandtricks)
- [Sponsors](#sponsors)
## What is QuaggaJS?
QuaggaJS is a barcode-scanner entirely written in JavaScript supporting real-
time localization and decoding of various types of barcodes such as __EAN__,
__CODE 128__, __CODE 39__, __EAN 8__, __UPC-A__, __UPC-C__, __I2of5__ and
__CODABAR__. The library is also capable of using `getUserMedia` to get direct
access to the user's camera stream. Although the code relies on heavy image-
processing even recent smartphones are capable of locating and decoding
barcodes in real-time.
__CODE 128__, __CODE 39__, __EAN 8__, __UPC-A__, __UPC-C__, __I2of5__,
__2of5__, __CODE 93__ and __CODABAR__. The library is also capable of using
`getUserMedia` to get direct access to the user's camera stream. Although the
code relies on heavy image-processing even recent smartphones are capable of
locating and decoding barcodes in real-time.
Try some [examples](https://serratus.github.io/quaggaJS/examples) and check out
the blog post ([How barcode-localization works in QuaggaJS][oberhofer_co_how])
@ -60,6 +64,35 @@ The following APIs need to be implemented in your browser:
In addition to the APIs mentioned above:
- [MediaDevices](http://caniuse.com/#feat=stream)
__Important:__ Accessing `getUserMedia` requires a secure origin in most
browsers, meaning that `http://` can only be used on `localhost`. All other
hostnames need to be served via `https://`. You can find more information in the
[Chrome M47 WebRTC Release Notes](https://groups.google.com/forum/#!topic/discuss-webrtc/sq5CVmY69sc).
### Feature-detection of getUserMedia
Every browser seems to differently implement the `mediaDevices.getUserMedia`
API. Therefore it's highly recommended to include
[webrtc-adapter](https://github.com/webrtc/adapter) in your project.
Here's how you can test your browser's capabilities:
```javascript
if (navigator.mediaDevices && typeof navigator.mediaDevices.getUserMedia === 'function') {
// safely access `navigator.mediaDevices.getUserMedia`
}
```
The above condition evaluates to:
| Browser | result |
| ------------- |:-------:|
| Edge | `true` |
| Chrome | `true` |
| Firefox | `true` |
| IE 11 | `false` |
| Safari iOS | `true` |
## <a name="installing">Installing</a>
QuaggaJS can be installed using __npm__, __bower__, or by including it with
@ -414,6 +447,8 @@ barcodes which should be decoded during the session. Possible values are:
- upc_reader
- upc_e_reader
- i2of5_reader
- 2of5_reader
- code_93_reader
Why are not all types activated by default? Simply because one should
explicitly define the set of barcodes for their use-case. More decoders means
@ -538,7 +573,7 @@ that node does not support web-workers out of the box. Therefore the config
property `numOfWorkers` must be explicitly set to `0`.
```javascript
var Quagga = require('quagga');
var Quagga = require('quagga').default;
Quagga.decodeSingle({
src: "image-abc-123.jpg",
@ -558,6 +593,36 @@ Quagga.decodeSingle({
});
```
## <a name="tipsandtricks">Tips & Tricks</a>
A growing collection of tips & tricks to improve the various aspects of Quagga.
### Barcodes too small?
Barcodes too far away from the camera, or a lens too close to the object
result in poor recognition rates and Quagga might respond with a lot of
false-positives.
Starting in Chrome 59 you can now make use of `capabilities` and directly
control the zoom of the camera. Head over to the
[web-cam demo](https://serratus.github.io/quaggaJS/examples/live_w_locator.html)
and check out the __Zoom__ feature.
You can read more about those `capabilities` in
[Let's light a torch and explore MediaStreamTrack's capabilities](https://www.oberhofer.co/mediastreamtrack-and-its-capabilities)
### Video too dark?
Dark environments usually result in noisy images and therefore mess with the
recognition logic.
Since Chrome 59 you can turn on/off the __Torch__ of our device and vastly
improve the quality of the images. Head over to the
[web-cam demo](https://serratus.github.io/quaggaJS/examples/live_w_locator.html)
and check out the __Torch__ feature.
To find out more about this feature [read on](https://www.oberhofer.co/mediastreamtrack-and-its-capabilities).
## Tests
Unit Tests can be run with [Karma][karmaUrl] and written using
@ -634,8 +699,43 @@ calling ``decodeSingle`` with the same configuration as used during recording
. In order to reproduce the exact same result, you have to make sure to turn
on the ``singleChannel`` flag in the configuration when using ``decodeSingle``.
## <a name="sponsors">Sponsors</a>
- [Maintenance Connection Canada (Asset Pro Solutions Inc.](http://maintenanceconnection.ca/)
## <a name="changelog">Changelog</a>
### 2017-06-07
- Improvements
- added `muted` and `playsinline` to `<video/>` to make it work for Safari 11
Beta (even iOS)
- Fixes
- Fixed [example/live_w_locator.js](https://github.com/serratus/quaggaJS/blob/master/example/live_w_locator.js)
### 2017-06-06
- Features
- Support for Standard 2of5 barcodes (See
[\#194](https://github.com/serratus/quaggaJS/issues/194))
- Support for Code 93 barcodes (See
[\#194](https://github.com/serratus/quaggaJS/issues/195))
- Exposing `Quagga.CameraAccess.getActiveTrack()` to get access to the
currently used `MediaStreamTrack`
- Example can be viewed here: [example/live_w_locator.js](https://github.com/serratus/quaggaJS/blob/master/example/live_w_locator.js) and a [demo](https://serratus.github.io/quaggaJS/examples/live_w_locator.html)
Take a look at the release-notes (
[0.12.0](https://github.com/serratus/quaggaJS/releases/tag/v0.12.0))
### 2017-01-08
- Improvements
- Exposing `CameraAccess` module to get access to methods like
`enumerateVideoDevices` and `getActiveStreamLabel`
(see `example/live_w_locator`)
- Update to webpack 2.2 (API is still unstable)
### 2016-10-03
- Fixes
- Fixed `facingMode` issue with Chrome >= 53 (see [#128](https://github.com/serratus/quaggaJS/issues/128))
### 2016-08-15
- Features
- Proper handling of EXIF orientation when using `Quagga.decodeSingle`

@ -1,6 +1,6 @@
{
"name": "quagga",
"version": "0.6.14",
"version": "0.12.1",
"description": "An advanced barcode-scanner written in JavaScript",
"main": "dist/quagga.js",
"ignore": [

26867
dist/quagga.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,22 +0,0 @@
<!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>

@ -1,67 +0,0 @@
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();

@ -0,0 +1,16 @@
@charset "UTF-8";
/* LESS - http://lesscss.org style sheet */
/* Palette color codes */
/* Palette URL: http://paletton.com/#uid=31g0q0kHZAviRSkrHLOGomVNzac */
/* Feel free to copy&paste color codes to your application */
/* MIXINS */
/* As hex codes */
/* Main Primary color */
/* Main Secondary color (1) */
/* Main Secondary color (2) */
/* As RGBa codes */
/* Main Primary color */
/* Main Secondary color (1) */
/* Main Secondary color (2) */
/* Generated by Paletton.com ├é┬® 2002-2014 */
/* http://paletton.com */

@ -1,31 +1 @@
@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";
}

@ -1,139 +0,0 @@
/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+jsx */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #a67f59;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

@ -1,126 +1,109 @@
.collapsable-source pre {
font-size: small;
@charset "UTF-8";
@import url("https://fonts.googleapis.com/css?family=Ubuntu:400,700|Cabin+Condensed:400,600");
body {
background-color: #FFF;
margin: 0px;
font-family: Ubuntu, sans-serif;
color: #1e1e1e;
font-weight: normal;
padding-top: 0;
}
.input-field {
display: flex;
align-items: center;
width: 260px;
h1, h2, h3, h4 {
font-family: "Cabin Condensed", sans-serif;
}
.input-field label {
flex: 0 0 auto;
padding-right: 0.5rem;
header {
background: #FFC600;
padding: 1em;
}
.input-field input {
flex: 1 1 auto;
height: 20px;
header .headline {
max-width: 640px;
margin: 0 auto;
}
.input-field button {
flex: 0 0 auto;
height: 28px;
font-size: 20px;
width: 40px;
header .headline h1 {
color: #FFDD69;
font-size: 3em;
margin-bottom: 0;
}
.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%;
header .headline h2 {
margin-top: 0.2em;
}
.overlay__content canvas {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
footer {
background: #0A4DB7;
color: #6C9CE8;
padding: 1em 2em 2em;
}
#container {
width: 640px;
margin: 20px auto;
padding: 10px;
}
#interactive.viewport {
width: 640px;
height: 480px;
}
#interactive.viewport canvas, video {
float: left;
width: 640px;
height: 480px;
}
input[type=file] {
display: none;
#interactive.viewport canvas.drawingBuffer, video.drawingBuffer {
margin-left: -640px;
}
/* line 16, ../sass/_viewport.scss */
.controls fieldset {
border: none;
margin: 0;
padding: 0;
}
/* line 19, ../sass/_viewport.scss */
.controls .input-group {
float: left;
}
/* line 21, ../sass/_viewport.scss */
.controls .input-group input, .controls .input-group button {
display: block;
}
/* line 25, ../sass/_viewport.scss */
.controls .reader-config-group {
float: right;
}
/* line 28, ../sass/_viewport.scss */
.controls .reader-config-group label {
display: block;
}
/* line 30, ../sass/_viewport.scss */
.controls .reader-config-group label span {
width: 11rem;
width: 9rem;
display: inline-block;
text-align: right;
}
/* line 37, ../sass/_viewport.scss */
.controls:after {
content: '';
display: block;
clear: both;
}
/* line 44, ../sass/_viewport.scss */
#result_strip {
margin: 10px 0;
border-top: 1px solid #EEE;
border-bottom: 1px solid #EEE;
padding: 10px 0;
}
/* line 50, ../sass/_viewport.scss */
#result_strip > ul {
padding: 0;
margin: 0;
@ -130,136 +113,186 @@ input[type=file] {
overflow-y: hidden;
white-space: nowrap;
}
/* line 59, ../sass/_viewport.scss */
#result_strip > ul > li {
display: inline-block;
vertical-align: middle;
width: 160px;
}
/* line 63, ../sass/_viewport.scss */
#result_strip > ul > li .thumbnail {
padding: 5px;
margin: 4px;
border: 1px dashed #CCC;
}
/* line 68, ../sass/_viewport.scss */
#result_strip > ul > li .thumbnail img {
max-width: 140px;
}
/* line 71, ../sass/_viewport.scss */
#result_strip > ul > li .thumbnail .caption {
white-space: normal;
}
/* line 73, ../sass/_viewport.scss */
#result_strip > ul > li .thumbnail .caption h4 {
text-align: center;
word-wrap: break-word;
height: 40px;
margin: 0px;
}
/* line 83, ../sass/_viewport.scss */
#result_strip > ul:after {
content: "";
display: table;
clear: both;
}
.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;
}
.scanner-overlay > .header {
position: relative;
margin-bottom: 14px;
}
.scanner-overlay > .header h4, .scanner-overlay > .header .close {
line-height: 16px;
}
.scanner-overlay > .header h4 {
margin: 0px;
padding: 0px;
}
.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;
}
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 (max-width: 603px) {
#container {
width: 300px;
margin: 10px auto;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
#container form.voucher-form input.voucher-code {
width: 180px;
}
}
@media (max-width: 603px) {
/* line 20, ../sass/phone/_viewport.scss */
.reader-config-group {
width: 100%;
}
.reader-config-group label > span {
width: 50%;
}
.reader-config-group label > select, .reader-config-group label > input {
max-width: calc(50% - 2px);
}
#interactive.viewport {
width: 300px;
height: 300px;
overflow: hidden;
}
#interactive.viewport canvas, video {
margin-top: -50px;
width: 300px;
height: 400px;
}
#interactive.viewport canvas.drawingBuffer, video.drawingBuffer {
margin-left: -300px;
}
#result_strip {
margin-top: 5px;
padding-top: 5px;
}
/* line 24, ../sass/phone/_viewport.scss */
#result_strip ul.thumbnails > li {
width: 150px;
}
/* line 27, ../sass/phone/_viewport.scss */
#result_strip ul.thumbnails > li .thumbnail .imgWrapper {
width: 130px;
height: 130px;
overflow: hidden;
}
/* line 31, ../sass/phone/_viewport.scss */
#result_strip ul.thumbnails > li .thumbnail .imgWrapper img {
margin-top: -25px;
width: 130px;
height: 180px;
}
}
@media (max-width: 603px) {
html {
height: 100%;
}
html, body {
min-height: 100%;
}
body {
background-color: #FFF;
margin: 0px;
font-family: Ubuntu, sans-serif;
color: #1e1e1e;
font-weight: normal;
padding-top: 0;
box-sizing: border-box;
padding-bottom: 96px;
position: relative;
}
/* line 24, ../sass/styles.scss */
h1, h2, h3, h4 {
font-family: "Cabin Condensed", sans-serif;
}
/* line 28, ../sass/styles.scss */
header {
background: #FFC600;
padding: 1em;
}
/* line 31, ../sass/styles.scss */
header .headline {
max-width: 640px;
margin: 0 auto;
}
/* line 34, ../sass/styles.scss */
header .headline h1 {
font-size: 3em;
margin-bottom: 0;
}
/* line 39, ../sass/styles.scss */
.headline h2 {
margin-top: 0.2em;
color: #FFDD69;
}
/* line 45, ../sass/styles.scss */
footer {
background: #0A4DB7;
color: #6C9CE8;
padding: 1em 2em 2em;
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
/* line 51, ../sass/styles.scss */
.container {
margin: 20px auto;
padding: 10px;
max-width: 640px;
}
.overlay.scanner {
width: 640px;
height: 510px;
padding: 20px;
margin-top: -275px;
margin-left: -340px;
background-color: #FFF;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
#interactive.viewport {
position: relative;
}
.overlay.scanner > .header {
margin-bottom: 14px;
}
#interactive.viewport > canvas, #interactive.viewport > video {
max-width: 100%;
width: 100%;
}
.overlay.scanner > .header h4, .overlay.scanner > .header .close {
line-height: 16px;
}
canvas.drawing {
position: absolute;
.overlay.scanner > .header .close {
height: 16px;
width: 16px;
}
}

@ -1,122 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<title>index</title>
<meta name="description" content="" />
<meta name="author" content="Christoph Oberhofer" />
<meta name="viewport" content="width=device-width; initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="../css/fonts.css" />
<link rel="stylesheet" type="text/css" href="../css/styles.css" />
<link rel="stylesheet" type="text/css" href="../css/prism.css" />
</head>
<body>
<header>
<div class="headline">
<h1>QuaggaJS</h1>
<h2>An advanced barcode-scanner written in JavaScript</h2>
</div>
</header>
<section id="container" class="container">
<h3>Scan barcode to input-field</h3>
<p>Click the <strong>button</strong> next to the input-field
to select a file or snap a picture</p>
<div>
<form>
<div class="input-field">
<label for="isbn_input">EAN:</label>
<input id="isbn_input" class="isbn" type="text" />
<button type="button" class="icon-barcode button scan">&nbsp;</button>
<input type="file" id="file" capture/>
</div>
</form>
</div>
<p>This example demonstrates the following features:
<ul>
<li>Use static image as source</li>
<li>Configuring EAN-Reader</li>
<li>Use custom mount-point (Query-Selector)</li>
</ul>
</p>
<div class="source-code">
<h4>Source</h4>
<div class="collapsable-source">
<pre>
<code class="language-javascript">
var Quagga = window.Quagga;
var App = {
_scanner: null,
init: function() {
this.attachListeners();
},
decode: function(src) {
Quagga
.decoder({readers: ['ean_reader']})
.locator({patchSize: 'medium'})
.fromImage(src, {size: 800})
.toPromise()
.then(function(result) {
document.querySelector('input.isbn').value = result.codeResult.code;
})
.catch(function() {
document.querySelector('input.isbn').value = "Not Found";
})
.then(function() {
this.attachListeners();
}.bind(this));
},
attachListeners: function() {
var self = this,
button = document.querySelector('.input-field input + .button.scan'),
fileInput = document.querySelector('.input-field input[type=file]');
button.addEventListener("click", function onClick(e) {
e.preventDefault();
button.removeEventListener("click", onClick);
document.querySelector('.input-field input[type=file]').click();
});
fileInput.addEventListener("change", function onChange(e) {
e.preventDefault();
fileInput.removeEventListener("change", onChange);
if (e.target.files && e.target.files.length) {
self.decode(URL.createObjectURL(e.target.files[0]));
}
});
}
};
App.init();
</code>
</pre>
</div>
<div class="collapsable-source">
<pre>
<code class="language-html">
&lt;form&gt;
&lt;div class=&quot;input-field&quot;&gt;
&lt;label for=&quot;isbn_input&quot;&gt;EAN:&lt;/label&gt;
&lt;input id=&quot;isbn_input&quot; class=&quot;isbn&quot; type=&quot;text&quot; /&gt;
&lt;button type=&quot;button&quot; class=&quot;icon-barcode button scan&quot;&gt;&amp;nbsp;&lt;/button&gt;
&lt;input type=&quot;file&quot; id=&quot;file&quot; capture/&gt;
&lt;/div&gt;
&lt;/form&gt;
</code>
</pre>
</div>
</div>
</section>
<footer>
<p>
&copy; Copyright by Christoph Oberhofer
</p>
</footer>
<script src="../../dist/quagga.js" type="text/javascript"></script>
<script src="index.js" type="text/javascript"></script>
<script src="../vendor/prism.js"></script>
</body>
</html>

@ -1,43 +0,0 @@
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();

@ -51,7 +51,9 @@
<option value="upc">UPC</option>
<option value="upc_e">UPC-E</option>
<option value="codabar">Codabar</option>
<option value="i2of5">ITF</option>
<option value="i2of5">Interleaved 2 of 5</option>
<option value="2of5">Standard 2 of 5</option>
<option value="code_93">Code 93</option>
</select>
</label>
<label>
@ -95,9 +97,7 @@
<div id="result_strip">
<ul class="thumbnails"></ul>
</div>
<div id="interactive" class="viewport">
<canvas class="drawing" />
</div>
<div id="interactive" class="viewport"></div>
<div id="debug" class="detection"></div>
</section>
<footer>

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

Binary file not shown.

@ -1,11 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="icomoon" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe937;" glyph-name="barcode" d="M0 832h128v-640h-128zM192 832h64v-640h-64zM320 832h64v-640h-64zM512 832h64v-640h-64zM768 832h64v-640h-64zM960 832h64v-640h-64zM640 832h32v-640h-32zM448 832h32v-640h-32zM864 832h32v-640h-32zM0 128h64v-64h-64zM192 128h64v-64h-64zM320 128h64v-64h-64zM640 128h64v-64h-64zM960 128h64v-64h-64zM768 128h128v-64h-128zM448 128h128v-64h-128z" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 821 B

Binary file not shown.

Binary file not shown.

@ -8,7 +8,7 @@
<meta name="description" content="" />
<meta name="author" content="Christoph Oberhofer" />
<meta name="viewport" content="width=device-width; initial-scale=1.0" />
<meta name="viewport" content="width=device-width; initial-scale=1.0; user-scalable=no" />
<link rel="stylesheet" type="text/css" href="css/styles.css" />
</head>
@ -43,11 +43,13 @@
<option value="upc">UPC</option>
<option value="upc_e">UPC-E</option>
<option value="codabar">Codabar</option>
<option value="i2of5">ITF</option>
<option value="i2of5">Interleaved 2 of 5</option>
<option value="2of5">Standard 2 of 5</option>
<option value="code_93">Code 93</option>
</select>
</label>
<label>
<span>Resolution (long side)</span>
<span>Resolution (width)</span>
<select name="input-stream_constraints">
<option value="320x240">320px</option>
<option selected="selected" value="640x480">640px</option>
@ -76,28 +78,40 @@
<select name="numOfWorkers">
<option value="0">0</option>
<option value="1">1</option>
<option selected="selected" value="2">2</option>
<option value="4">4</option>
<option value="2">2</option>
<option selected="selected" value="4">4</option>
<option value="8">8</option>
</select>
</label>
<label>
<span>Camera</span>
<select name="input-stream_constraints" id="deviceSelection">
</select>
</label>
<label style="display: none">
<span>Zoom</span>
<select name="settings_zoom"></select>
</label>
<label style="display: none">
<span>Torch</span>
<input type="checkbox" name="settings_torch" />
</label>
</fieldset>
</div>
<div id="result_strip">
<ul class="thumbnails"></ul>
<ul class="collector"></ul>
</div>
<div id="interactive" class="viewport">
<canvas class="drawing" />
</div>
<div id="interactive" class="viewport"></div>
</section>
<footer>
<p>
&copy; Copyright by Christoph Oberhofer
&copy; Made with ❤️ by Christoph Oberhofer
</p>
</footer>
<script src="vendor/jquery-1.9.0.min.js" type="text/javascript"></script>
<script src="//webrtc.github.io/adapter/adapter-latest.js" type="text/javascript"></script>
<script src="../dist/quagga.js" type="text/javascript"></script>
<script src="live_w_locator.js" type="text/javascript"></script>
</body>

@ -1,33 +1,117 @@
$(function() {
var resultCollector = Quagga.ResultCollector.create({
capture: true,
capacity: 20,
blacklist: [{
code: "WIWV8ETQZ1", format: "code_93"
}, {
code: "EH3C-%GU23RK3", format: "code_93"
}, {
code: "O308SIHQOXN5SA/PJ", format: "code_93"
}, {
code: "DG7Q$TV8JQ/EN", format: "code_93"
}, {
code: "VOFD1DB5A.1F6QU", format: "code_93"
}, {
code: "4SO64P4X8 U4YUU1T-", format: "code_93"
}],
filter: function(codeResult) {
// only store results which match this constraint
// e.g.: codeResult
return true;
}
});
var App = {
init : function() {
this.overlay = document.querySelector('#interactive canvas.drawing');
this.scanner = Quagga
.fromConfig(this.state);
this.scanner
.addEventListener("processed", drawResult.bind(this, this.scanner))
.addEventListener("detected", addToResults.bind(this, this.scanner));
this.scanner.start()
.then(function (){
console.log("started");
this.attachListeners();
}.bind(this))
.catch(function(err) {
console.log("Error: " + err);
init: function() {
var self = this;
Quagga.init(this.state, function(err) {
if (err) {
return self.handleError(err);
}
//Quagga.registerResultCollector(resultCollector);
App.attachListeners();
App.checkCapabilities();
Quagga.start();
});
},
handleError: function(err) {
console.log(err);
},
checkCapabilities: function() {
var track = Quagga.CameraAccess.getActiveTrack();
var capabilities = {};
if (typeof track.getCapabilities === 'function') {
capabilities = track.getCapabilities();
}
this.applySettingsVisibility('zoom', capabilities.zoom);
this.applySettingsVisibility('torch', capabilities.torch);
},
updateOptionsForMediaRange: function(node, range) {
console.log('updateOptionsForMediaRange', node, range);
var NUM_STEPS = 6;
var stepSize = (range.max - range.min) / NUM_STEPS;
var option;
var value;
while (node.firstChild) {
node.removeChild(node.firstChild);
}
for (var i = 0; i <= NUM_STEPS; i++) {
value = range.min + (stepSize * i);
option = document.createElement('option');
option.value = value;
option.innerHTML = value;
node.appendChild(option);
}
},
applySettingsVisibility: function(setting, capability) {
// depending on type of capability
if (typeof capability === 'boolean') {
var node = document.querySelector('input[name="settings_' + setting + '"]');
if (node) {
node.parentNode.style.display = capability ? 'block' : 'none';
}
return;
}
if (window.MediaSettingsRange && capability instanceof window.MediaSettingsRange) {
var node = document.querySelector('select[name="settings_' + setting + '"]');
if (node) {
this.updateOptionsForMediaRange(node, capability);
node.parentNode.style.display = 'block';
}
return;
}
},
initCameraSelection: function(){
var streamLabel = Quagga.CameraAccess.getActiveStreamLabel();
return Quagga.CameraAccess.enumerateVideoDevices()
.then(function(devices) {
function pruneText(text) {
return text.length > 30 ? text.substr(0, 30) : text;
}
var $deviceSelection = document.getElementById("deviceSelection");
while ($deviceSelection.firstChild) {
$deviceSelection.removeChild($deviceSelection.firstChild);
}
devices.forEach(function(device) {
var $option = document.createElement("option");
$option.value = device.deviceId || device.id;
$option.appendChild(document.createTextNode(pruneText(device.label || device.deviceId || device.id)));
$option.selected = streamLabel === device.label;
$deviceSelection.appendChild($option);
});
});
},
attachListeners: function() {
var self = this;
self.initCameraSelection();
$(".controls").on("click", "button.stop", function(e) {
e.preventDefault();
this.detachListeners();
this.scanner.stop();
this.scanner.removeEventListener();
}.bind(this));
Quagga.stop();
self._printCollectedResults();
});
$(".controls .reader-config-group").on("change", "input, select", function(e) {
e.preventDefault();
@ -40,6 +124,18 @@ $(function() {
self.setState(state, value);
});
},
_printCollectedResults: function() {
var results = resultCollector.getResults(),
$ul = $("#result_strip ul.collector");
results.forEach(function(result) {
var $li = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>');
$li.find("img").attr("src", result.frame);
$li.find("h4.code").html(result.codeResult.code + " (" + result.codeResult.format + ")");
$ul.prepend($li);
});
},
_accessByPath: function(obj, path, val) {
var parts = path.split('.'),
depth = parts.length,
@ -47,7 +143,11 @@ $(function() {
return parts.reduce(function(o, key, i) {
if (setter && (i + 1) === depth) {
o[key] = val;
if (typeof o[key] === "object" && typeof val === "object") {
Object.assign(o[key], val);
} else {
o[key] = val;
}
}
return key in o ? o[key] : {};
}, obj);
@ -61,27 +161,48 @@ $(function() {
$(".controls").off("click", "button.stop");
$(".controls .reader-config-group").off("change", "input, select");
},
applySetting: function(setting, value) {
var track = Quagga.CameraAccess.getActiveTrack();
if (track && typeof track.getCapabilities === 'function') {
switch (setting) {
case 'zoom':
return track.applyConstraints({advanced: [{zoom: parseFloat(value)}]});
case 'torch':
return track.applyConstraints({advanced: [{torch: !!value}]});
}
}
},
setState: function(path, value) {
if (typeof this._accessByPath(this.inputMapper, path) === "function") {
value = this._accessByPath(this.inputMapper, path)(value);
var self = this;
if (typeof self._accessByPath(self.inputMapper, path) === "function") {
value = self._accessByPath(self.inputMapper, path)(value);
}
this._accessByPath(this.state, path, value);
if (path.startsWith('settings.')) {
var setting = path.substring(9);
return self.applySetting(setting, value);
}
self._accessByPath(self.state, path, value);
console.log(JSON.stringify(this.state));
this.detachListeners();
this.scanner.stop();
this.scanner.removeEventListener();
console.log(JSON.stringify(self.state));
App.detachListeners();
Quagga.stop();
App.init();
},
inputMapper: {
inputStream: {
constraints: function(value){
var values = value.split('x');
return {
width: parseInt(values[0]),
height: parseInt(values[1])
if (/^(\d+)x(\d+)$/.test(value)) {
var values = value.split('x');
return {
width: {min: parseInt(values[0])},
height: {min: parseInt(values[1])}
};
}
return {
deviceId: value
};
}
},
numOfWorkers: function(value) {
@ -110,9 +231,10 @@ $(function() {
inputStream: {
type : "LiveStream",
constraints: {
width: 640,
height: 480,
facingMode: "environment"
width: {min: 640},
height: {min: 480},
facingMode: "environment",
aspectRatio: {min: 1, max: 2}
}
},
locator: {
@ -134,45 +256,42 @@ $(function() {
App.init();
function drawResult(scanner, result) {
var processingCanvas = scanner.getCanvas(),
canvas = App.overlay,
ctx = canvas.getContext("2d");
canvas.setAttribute('width', processingCanvas.getAttribute('width'));
canvas.setAttribute('height', processingCanvas.getAttribute('height'));
Quagga.onProcessed(function(result) {
var drawingCtx = Quagga.canvas.ctx.overlay,
drawingCanvas = Quagga.canvas.dom.overlay;
if (result) {
if (result.boxes) {
ctx.clearRect(0, 0, parseInt(canvas.getAttribute("width")), parseInt(canvas.getAttribute("height")));
drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
result.boxes.filter(function (box) {
return box !== result.box;
}).forEach(function (box) {
Quagga.ImageDebug.drawPath(box, {x: 0, y: 1}, ctx, {color: "green", lineWidth: 2});
Quagga.ImageDebug.drawPath(box, {x: 0, y: 1}, drawingCtx, {color: "green", lineWidth: 2});
});
}
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, ctx, {color: "#00F", lineWidth: 2});
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, drawingCtx, {color: "#00F", lineWidth: 2});
}
if (result.codeResult && result.codeResult.code) {
Quagga.ImageDebug.drawPath(result.line, {x: 'x', y: 'y'}, ctx, {color: 'red', lineWidth: 3});
Quagga.ImageDebug.drawPath(result.line, {x: 'x', y: 'y'}, drawingCtx, {color: 'red', lineWidth: 3});
}
}
};
});
function addToResults(scanner, result) {
var code = result.codeResult.code,
$node,
canvas = scanner.getCanvas();
Quagga.onDetected(function(result) {
var code = result.codeResult.code;
if (App.lastResult !== code) {
App.lastResult = code;
var $node = null, canvas = Quagga.canvas.dom.image;
$node = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>');
$node.find("img").attr("src", canvas.toDataURL());
$node.find("h4.code").html(code);
$("#result_strip ul.thumbnails").prepend($node);
}
};
});
});

@ -1,80 +0,0 @@
<!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>

@ -1,96 +0,0 @@
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();

@ -1,140 +0,0 @@
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,27 @@
var Quagga = require('../lib/quagga').default;
var buffer = require('fs').readFileSync('../test/fixtures/code_128/image-001.jpg');
decode(buffer);
function decode(buff){
Quagga.decodeSingle({
src: buff,
numOfWorkers: 0,
inputStream: {
mime: "image/jpeg",
size: 800,
area: {
top: "10%",
right: "5%",
left: "5%",
bottom: "10%"
}
}
}, function(result) {
if (result.codeResult) {
console.log("result", result.codeResult.code);
} else {
console.log("not detected");
}
});
}

@ -1,4 +0,0 @@
{
"plugins": ["transform-class-properties"],
"presets": ["es2015-webpack", "stage-0", "react"]
}

@ -1 +0,0 @@
static/

@ -1,9 +0,0 @@
# Quagga meets React
This example uses webpack & babel to transpile/bundle the required files.
## How to run the example
```console
> npm install && webpack
```

@ -1,49 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<title>index</title>
<meta name="description" content="" />
<meta name="author" content="Christoph Oberhofer" />
<meta name="viewport" content="width=device-width; initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="../css/fonts.css" />
<link rel="stylesheet" type="text/css" href="../css/styles.css" />
<link rel="stylesheet" type="text/css" href="../css/prism.css" />
</head>
<body>
<header>
<div class="headline">
<h1>QuaggaJS</h1>
<h2>An advanced barcode-scanner written in JavaScript</h2>
</div>
</header>
<section id="container" class="container">
<h3>Scan barcode to input-field</h3>
<p>Click the <strong>button</strong> next to the input-field
to start scanning an <strong>EAN-13</strong> barcode</p>
<div id="react">
</div>
<p>This example demonstrates the following features:
<ul>
<li>Access to user's camera</li>
<li>Configuring EAN-Reader</li>
<li>Use custom mount-point (Query-Selector)</li>
<li>Start/Stop scanning based on certain events</li>
</ul>
</p>
</section>
<footer>
<p>
&copy; Copyright by Christoph Oberhofer
</p>
</footer>
<script src="static/bundle.js" type="text/javascript"></script>
<script src="../vendor/prism.js"></script>
</body>
</html>

@ -1,24 +0,0 @@
{
"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"
}
}

@ -1,39 +0,0 @@
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>
);
}
}

@ -1,44 +0,0 @@
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();
}
}

@ -1,9 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
ReactDOM.render(
<App />,
document.getElementById('react')
);

@ -1,23 +0,0 @@
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'
}
};

@ -1,4 +0,0 @@
{
"plugins": ["transform-class-properties"],
"presets": ["es2015-webpack", "stage-0", "react"]
}

@ -1,8 +0,0 @@
{
"root": true,
"extends": "./node_modules/eslint-config-netconomy/react.js",
"parser": "babel-eslint",
"rules": {
"comma-dangle": [2, "always-multiline"],
}
}

@ -1 +0,0 @@
static/*.js*

@ -1,9 +0,0 @@
# Quagga meets React
This example uses webpack & babel to transpile/bundle the required files.
## How to run the example
```console
> npm install && webpack
```

@ -1,22 +0,0 @@
<!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>

@ -1,14 +0,0 @@
{
"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"
}

@ -1,33 +0,0 @@
{
"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"
}
}

@ -1,185 +0,0 @@
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>
);
}
}

@ -1,32 +0,0 @@
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;
}
});

@ -1,193 +0,0 @@
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>);
}
};

@ -1,15 +0,0 @@
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;

@ -1,99 +0,0 @@
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;

@ -1,47 +0,0 @@
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();
}
}

@ -1,15 +0,0 @@
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')
);

@ -1,35 +0,0 @@
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.

Before

Width:  |  Height:  |  Size: 5.9 KiB

@ -1,20 +0,0 @@
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%;
}

@ -1,23 +0,0 @@
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'
}
};

@ -1,44 +0,0 @@
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,159 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<title>index</title>
<meta name="description" content="" />
<meta name="author" content="Christoph Oberhofer" />
<meta name="viewport" content="width=device-width; initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="../css/fonts.css" />
<link rel="stylesheet" type="text/css" href="../css/styles.css" />
<link rel="stylesheet" type="text/css" href="../css/prism.css" />
</head>
<body>
<header>
<div class="headline">
<h1>QuaggaJS</h1>
<h2>An advanced barcode-scanner written in JavaScript</h2>
</div>
</header>
<section id="container" class="container">
<h3>Scan barcode to input-field</h3>
<p>Click the <strong>button</strong> next to the input-field
to start scanning an <strong>EAN-13</strong> barcode</p>
<div>
<form>
<div class="input-field">
<label for="isbn_input">EAN:</label>
<input id="isbn_input" class="isbn" type="text" />
<button type="button" class="icon-barcode button scan">&nbsp;</button>
</div>
</form>
</div>
<p>This example demonstrates the following features:
<ul>
<li>Access to user's camera</li>
<li>Configuring EAN-Reader</li>
<li>Use custom mount-point (Query-Selector)</li>
<li>Start/Stop scanning based on certain events</li>
</ul>
</p>
<div class="source-code">
<h4>Source</h4>
<div class="collapsable-source">
<pre>
<code class="language-javascript">
var Quagga = window.Quagga;
var App = {
_scanner: null,
init: function() {
this.attachListeners();
},
activateScanner: function() {
var scanner = this.configureScanner('.overlay__content'),
onDetected = function (result) {
document.querySelector('input.isbn').value = result.codeResult.code;
stop();
}.bind(this),
stop = function() {
scanner.stop(); // should also clear all event-listeners?
scanner.removeEventListener('detected', onDetected);
this.hideOverlay();
this.attachListeners();
}.bind(this);
this.showOverlay(stop);
scanner.addEventListener('detected', onDetected).start();
},
attachListeners: function() {
var self = this,
button = document.querySelector('.input-field input + button.scan');
button.addEventListener("click", function onClick(e) {
e.preventDefault();
button.removeEventListener("click", onClick);
self.activateScanner();
});
},
showOverlay: function(cancelCb) {
if (!this._overlay) {
var content = document.createElement('div'),
closeButton = document.createElement('div');
closeButton.appendChild(document.createTextNode('X'));
content.className = 'overlay__content';
closeButton.className = 'overlay__close';
this._overlay = document.createElement('div');
this._overlay.className = 'overlay';
this._overlay.appendChild(content);
content.appendChild(closeButton);
closeButton.addEventListener('click', function closeClick() {
closeButton.removeEventListener('click', closeClick);
cancelCb();
});
document.body.appendChild(this._overlay);
} else {
var closeButton = document.querySelector('.overlay__close');
closeButton.addEventListener('click', function closeClick() {
closeButton.removeEventListener('click', closeClick);
cancelCb();
});
}
this._overlay.style.display = "block";
},
hideOverlay: function() {
if (this._overlay) {
this._overlay.style.display = "none";
}
},
configureScanner: function(selector) {
if (!this._scanner) {
this._scanner = Quagga
.decoder({readers: ['ean_reader']})
.locator({patchSize: 'medium'})
.fromVideo({
target: selector,
constraints: {
width: 800,
height: 600,
facingMode: "environment"
}
});
}
return this._scanner;
}
};
App.init();
</code>
</pre>
</div>
<div class="collapsable-source">
<pre>
<code class="language-html">
&lt;form&gt;
&lt;div class="input-field"&gt;
&lt;label for="isbn_input"&gt;EAN:&lt;/label&gt;
&lt;input id="isbn_input" class="isbn" type="text" /&gt;
&lt;button type="button" class="icon-barcode button scan"&gt;&nbsp;&lt;/button&gt;
&lt;/div&gt;
&lt;/form&gt;
</code>
</pre>
</div>
</div>
</section>
<footer>
<p>
&copy; Copyright by Christoph Oberhofer
</p>
</footer>
<script src="../../dist/quagga.js" type="text/javascript"></script>
<script src="index.js" type="text/javascript"></script>
<script src="../vendor/prism.js"></script>
</body>
</html>

@ -1,81 +0,0 @@
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();

@ -45,15 +45,16 @@
<option value="upc_e">UPC-E</option>
<option value="codabar">Codabar</option>
<option value="i2of5">I2of5</option>
<option value="i2of5">Interleaved 2 of 5</option>
<option value="2of5">Standard 2 of 5</option>
<option value="code_93">Code 93</option>
</select>
</fieldset>
</div>
<div id="result_strip">
<ul class="thumbnails"></ul>
</div>
<div id="interactive" class="viewport">
<canvas class="drawing" />
</div>
<div id="interactive" class="viewport"></div>
<div id="debug" class="detection"></div>
</section>
<footer>

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

File diff suppressed because one or more lines are too long

@ -14,6 +14,9 @@ module.exports = function(config) {
'test/test-main-integration.js': ['webpack']
},
webpack: {
entry: [
'./src/quagga.js'
],
module: {
loaders: [{
test: /\.jsx?$/,
@ -24,6 +27,7 @@ module.exports = function(config) {
resolve: {
modules: [
path.resolve('./src/input/'),
path.resolve('./src/common/'),
'node_modules'
]
},
@ -36,7 +40,6 @@ module.exports = function(config) {
plugins: [
'karma-chrome-launcher',
'karma-mocha',
'karma-requirejs',
'karma-chai',
'karma-sinon',
'karma-sinon-chai',
@ -45,7 +48,7 @@ module.exports = function(config) {
reporters: ['progress'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
logLevel: config.LOG_INFO, // LOG_DEBUG
autoWatch: true,
browsers: ['Chrome'],
singleRun: false

@ -13,6 +13,9 @@ module.exports = function(config) {
'test/test-main.js': ['webpack']
},
webpack: {
entry: [
'./src/quagga.js'
],
module: {
loaders: [{
test: /\.jsx?$/,
@ -23,12 +26,13 @@ module.exports = function(config) {
}, {
test: /\.js$/,
include: path.resolve('src'),
loader: 'babel-istanbul'
loader: 'babel-istanbul-loader'
}]
},
resolve: {
modules: [
path.resolve('./src/input/'),
path.resolve('./test/mocks/'),
'node_modules'
]
},
@ -40,6 +44,7 @@ module.exports = function(config) {
},
plugins: [
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-coverage',
'karma-mocha',
'karma-chai',
@ -49,7 +54,7 @@ module.exports = function(config) {
require('karma-webpack')
],
reporters: ['progress', 'coverage'],
port: 9999,
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,

@ -24,7 +24,7 @@ InputStream.createImageStream = function() {
function loadImages() {
loaded = false;
GetPixels(baseUrl, function(err, pixels) {
GetPixels(baseUrl, _config.mime, function(err, pixels) {
if (err) {
console.log(err);
exit(1);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,22 +1,25 @@
{
"name": "quagga",
"version": "1.0.0-beta.1",
"version": "0.12.1",
"description": "An advanced barcode-scanner written in JavaScript",
"main": "lib/quagga.js",
"typings": "type-definitions/quagga.d.ts",
"browser": "dist/quagga.min.js",
"typings": "type-definitions/quagga.d.ts",
"devDependencies": {
"async": "^1.4.2",
"babel-cli": "^6.5.1",
"babel-core": "^6.7.4",
"babel-eslint": "^6.0.4",
"babel-core": "^6.21.0",
"babel-eslint": "^7.1.1",
"babel-istanbul": "^0.8.0",
"babel-istanbul-loader": "^0.1.0",
"babel-loader": "^6.2.4",
"babel-plugin-add-module-exports": "^0.1.2",
"babel-loader": "^6.2.10",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-check-es2015-constants": "^6.3.13",
"babel-plugin-lodash": "^2.2.1",
"babel-plugin-lodash": "^3.2.11",
"babel-plugin-transform-es2015-arrow-functions": "^6.3.13",
"babel-plugin-transform-es2015-block-scoped-functions": "^6.3.13",
"babel-plugin-transform-es2015-block-scoping": "^6.3.13",
"babel-plugin-transform-es2015-block-scoping": "^6.21.0",
"babel-plugin-transform-es2015-classes": "^6.3.13",
"babel-plugin-transform-es2015-computed-properties": "^6.3.13",
"babel-plugin-transform-es2015-destructuring": "^6.3.13",
@ -25,7 +28,7 @@
"babel-plugin-transform-es2015-literals": "^6.3.13",
"babel-plugin-transform-es2015-modules-commonjs": "^6.3.13",
"babel-plugin-transform-es2015-object-super": "^6.3.13",
"babel-plugin-transform-es2015-parameters": "^6.3.13",
"babel-plugin-transform-es2015-parameters": "^6.21.0",
"babel-plugin-transform-es2015-shorthand-properties": "^6.3.13",
"babel-plugin-transform-es2015-spread": "^6.3.13",
"babel-plugin-transform-es2015-sticky-regex": "^6.3.13",
@ -33,35 +36,34 @@
"babel-plugin-transform-es2015-typeof-symbol": "^6.3.13",
"babel-plugin-transform-es2015-unicode-regex": "^6.3.13",
"babel-plugin-transform-object-rest-spread": "^6.5.0",
"babel-plugin-transform-regenerator": "^6.3.13",
"babel-plugin-transform-regenerator": "^6.21.0",
"chai": "^3.4.1",
"core-js": "^1.2.1",
"cross-env": "^1.0.7",
"core-js": "^2.4.1",
"cross-env": "^3.1.4",
"eslint": "^1.10.3",
"eslint-plugin-react": "^5.1.1",
"grunt": "^0.4.5",
"grunt-cli": "^0.1.13",
"grunt-contrib-nodeunit": "^0.4.1",
"grunt-karma": "^0.12.1",
"grunt-karma": "^2.0.0",
"isparta-loader": "^2.0.0",
"karma": "^0.13.9",
"karma": "^1.3.0",
"karma-chai": "0.1.0",
"karma-chrome-launcher": "^0.2.0",
"karma-coverage": "^0.5.2",
"karma-chrome-launcher": "^2.0.0",
"karma-coverage": "^1.1.1",
"karma-firefox-launcher": "^0.1.7",
"karma-mocha": "~0.2.0",
"karma-phantomjs-launcher": "^0.2.1",
"karma-sinon": "^1.0.4",
"karma-sinon-chai": "^1.1.0",
"karma-source-map-support": "^1.1.0",
"karma-webpack": "^1.7.0",
"karma-webpack": "^1.8.1",
"lolex": "^1.4.0",
"mocha": "^2.3.2",
"phantomjs": "^1.9.18",
"sinon": "^1.16.1",
"sinon": "^1.17.7",
"sinon-chai": "^2.8.0",
"webpack": "2.1.0-beta.4",
"webpack-sources": "^0.1.1"
"webpack": "^2.2.1",
"webpack-sources": "^0.1.4"
},
"directories": {
"doc": "doc"
@ -106,9 +108,8 @@
"gl-mat2": "^1.0.0",
"gl-vec2": "^1.0.0",
"gl-vec3": "^1.0.3",
"lodash": "^4.6.1",
"lodash": "^4.17.4",
"ndarray": "^1.0.18",
"ndarray-linear-interpolate": "^1.0.0",
"webrtc-adapter": "^1.3.0"
"ndarray-linear-interpolate": "^1.0.0"
}
}

@ -0,0 +1,33 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC9ORap3LvRegtrhRc8dLdH9Bp2QokcKEsWbtvyhtjisRRm2slK
A6Q11McB/YTb7oImFfNaCX+7vdM1oVXVLJ0ekQaNljXG5Dy7DXEcT1V6gpN4xmZJ
8f/KZ45VBINN0Ha74L7nS4kgImh5yvNolNr4IdlSjGf09kciFy8S3kPlGQIDAQAB
AoGAYDlaxBCC1liY3Bl3IoA7//QrTL4zGUWIQaUoZmGag1UHifJycBf/9nv4o5N3
b5wPRSzebofsE93JPTmI+3nPf62k5rS2xOo8swwOZc+f5/v0EnUNixD7P0jBiLVR
B8kbMvNdNn33HuynW1/MSBFE0cfeDH2i8SVl+Z+fHYIUW10CQQD0yWB8xeM8AxYB
/ZZWClem6gf1lQAYLzid3x51pkLqRFpX+rG251cSBUouE+kVO14j2xrBqCyyOwNu
17eazy3DAkEAxeQdWP9b11ihKOf/kjXiczltLnBsotn6K9EEAe0QuH/6iXLm86mL
ZiQe+TrY1GWbK3ns0sfXgNJ2aeaRkeZn8wJAWF5WedTKisCmckOEwTzslbJI+0w2
A4UQkFWa3mgOIhpY7wfunhP35+aG+AlyDJspChKwHxdCQ3lwbNRtUPLYFwJBAK8G
9QIbUbLlPB1/HOfH6xM4rp3NZ/idzQxmISJG+GwHHaPmUekfgyEDP7X2W4N4nsbU
XyeLA8t32q4N9aDS5gsCQDHqhsXqnY6e4IEZrvf90l2V1PpnTKfEl/F5wye3g69G
JN57scVUBHP/KKoyfge0fytWiQN/56KvWH+G5+N/JyA=
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIC2zCCAkSgAwIBAgIJALUDN95Or7XlMA0GCSqGSIb3DQEBBQUAMFMxCzAJBgNV
BAYTAkFUMQ8wDQYDVQQIEwZTdHlyaWExDTALBgNVBAcTBEdyYXoxETAPBgNVBAoT
CFF1YWdnYUpTMREwDwYDVQQDEwhxdWFnZ2FqczAeFw0xNzAxMDgxNjI5MjhaFw0x
ODAxMDgxNjI5MjhaMFMxCzAJBgNVBAYTAkFUMQ8wDQYDVQQIEwZTdHlyaWExDTAL
BgNVBAcTBEdyYXoxETAPBgNVBAoTCFF1YWdnYUpTMREwDwYDVQQDEwhxdWFnZ2Fq
czCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvTkWqdy70XoLa4UXPHS3R/Qa
dkKJHChLFm7b8obY4rEUZtrJSgOkNdTHAf2E2+6CJhXzWgl/u73TNaFV1SydHpEG
jZY1xuQ8uw1xHE9VeoKTeMZmSfH/ymeOVQSDTdB2u+C+50uJICJoecrzaJTa+CHZ
Uoxn9PZHIhcvEt5D5RkCAwEAAaOBtjCBszAdBgNVHQ4EFgQUYm5+uJVOOGiYa+Vx
2o++VHyWkwIwgYMGA1UdIwR8MHqAFGJufriVTjhomGvlcdqPvlR8lpMCoVekVTBT
MQswCQYDVQQGEwJBVDEPMA0GA1UECBMGU3R5cmlhMQ0wCwYDVQQHEwRHcmF6MREw
DwYDVQQKEwhRdWFnZ2FKUzERMA8GA1UEAxMIcXVhZ2dhanOCCQC1AzfeTq+15TAM
BgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBACyzC/CKL1mgTuNgFDuUf6u+
YMnqlc9wcnEaFuvXnkSh6fT+qMZm188C/tlZwcWTrGmoCM0K6mX1TpHOjm8vbeXZ
diezAVGIVN3VoHqm6yJldI2rgFI9r5BfwAWYC8XNjqnT3U6cm4k8iC7jmLC+dT9r
Ysx2ucAF6lNHayekRmNq
-----END CERTIFICATE-----

@ -0,0 +1,30 @@
# taken from http://www.piware.de/2011/01/creating-an-https-server-in-python/
# generate server.xml with the following command:
# openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes
# run as follows:
# python simple-https-server.py
# then in your browser, visit:
# https://localhost:4443
import BaseHTTPServer, SimpleHTTPServer
import ssl
import sys, getopt
host = 'localhost'
port = 4443
try:
opts, args = getopt.getopt(sys.argv[1:],"",["host=", "port="])
except getopt.GetoptError:
print 'simple-https-server.py --host <host> --port <port>'
sys.exit(2)
for opt, arg in opts:
if opt in ("--host"):
host = arg
elif opt in ("--port"):
port = int(arg)
print 'host is ', host
print 'port is ', port
httpd = BaseHTTPServer.HTTPServer((host, port), SimpleHTTPServer.SimpleHTTPRequestHandler)
httpd.socket = ssl.wrap_socket (httpd.socket, certfile='./server.pem', server_side=True)
httpd.serve_forever()

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

@ -1,10 +1,10 @@
import Cluster2 from './cluster';
import ArrayHelper from './array_helper';
const vec2 = {
clone: require('gl-vec2/clone')
clone: require('gl-vec2/clone'),
};
const vec3 = {
clone: require('gl-vec3/clone')
clone: require('gl-vec3/clone'),
};
/**
@ -488,7 +488,7 @@ export function grayAndHalfSampleFromCanvasData(canvasData, size, outArray) {
while (bottomRowIdx < endIdx) {
for ( i = 0; i < outWidth; i++) {
outArray[outImgIdx] = Math.floor((
outArray[outImgIdx] = (
(0.299 * canvasData[topRowIdx * 4 + 0] +
0.587 * canvasData[topRowIdx * 4 + 1] +
0.114 * canvasData[topRowIdx * 4 + 2]) +
@ -500,7 +500,7 @@ export function grayAndHalfSampleFromCanvasData(canvasData, size, outArray) {
0.114 * canvasData[(bottomRowIdx) * 4 + 2]) +
(0.299 * canvasData[(bottomRowIdx + 1) * 4 + 0] +
0.587 * canvasData[(bottomRowIdx + 1) * 4 + 1] +
0.114 * canvasData[(bottomRowIdx + 1) * 4 + 2])) / 4);
0.114 * canvasData[(bottomRowIdx + 1) * 4 + 2])) / 4;
outImgIdx++;
topRowIdx = topRowIdx + 2;
bottomRowIdx = bottomRowIdx + 2;
@ -521,8 +521,8 @@ export function computeGray(imageData, outArray, config) {
}
} else {
for (i = 0; i < l; i++) {
outArray[i] = Math.floor(
0.299 * imageData[i * 4 + 0] + 0.587 * imageData[i * 4 + 1] + 0.114 * imageData[i * 4 + 2]);
outArray[i] =
0.299 * imageData[i * 4 + 0] + 0.587 * imageData[i * 4 + 1] + 0.114 * imageData[i * 4 + 2];
}
}
};

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

@ -2,7 +2,7 @@ export default {
drawRect: function(pos, size, ctx, style){
ctx.strokeStyle = style.color;
ctx.fillStyle = style.color;
ctx.lineWidth = 1;
ctx.lineWidth = style.lineWidth || 1;
ctx.beginPath();
ctx.strokeRect(pos.x, pos.y, size.x, size.y);
},

@ -0,0 +1,17 @@
export function enumerateDevices() {
if (navigator.mediaDevices
&& typeof navigator.mediaDevices.enumerateDevices === 'function') {
return navigator.mediaDevices.enumerateDevices();
}
return Promise.reject(new Error('enumerateDevices is not defined'));
};
export function getUserMedia(constraints) {
if (navigator.mediaDevices
&& typeof navigator.mediaDevices.getUserMedia === 'function') {
return navigator.mediaDevices
.getUserMedia(constraints);
}
return Promise.reject(new Error('getUserMedia is not defined'));
}

@ -24,3 +24,28 @@ Math.imul = Math.imul || function(a, b) {
// the final |0 converts the unsigned value into a signed value
return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0) | 0);
};
if (typeof Object.assign !== 'function') {
Object.assign = function(target) { // .length of function is 2
'use strict';
if (target === null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource !== null) { // Skip over if undefined or null
for (var nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
};
}

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

@ -11,6 +11,8 @@ import EAN2Reader from '../reader/ean_2_reader';
import EAN5Reader from '../reader/ean_5_reader';
import UPCEReader from '../reader/upc_e_reader';
import I2of5Reader from '../reader/i2of5_reader';
import TwoOfFiveReader from '../reader/2of5_reader';
import Code93Reader from '../reader/code_93_reader';
const READERS = {
code_128_reader: Code128Reader,
@ -23,7 +25,9 @@ const READERS = {
codabar_reader: CodabarReader,
upc_reader: UPCReader,
upc_e_reader: UPCEReader,
i2of5_reader: I2of5Reader
i2of5_reader: I2of5Reader,
'2of5_reader': TwoOfFiveReader,
code_93_reader: Code93Reader
};
export default {
create: function(config, inputImageWrapper) {
@ -68,13 +72,8 @@ export default {
}
_canvas.ctx.pattern = _canvas.dom.pattern.getContext("2d");
if ($debug) {
_canvas.dom.overlay = document.querySelector("canvas.drawingBuffer");
if (!_canvas.dom.overlay) {
_canvas.dom.overlay = document.createElement("canvas");
_canvas.dom.overlay.className = "drawingBuffer";
$debug.appendChild(_canvas.dom.overlay);
}
_canvas.dom.overlay = document.querySelector("canvas.drawingBuffer");
if (_canvas.dom.overlay) {
_canvas.ctx.overlay = _canvas.dom.overlay.getContext("2d");
}
}

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

@ -1,4 +1,10 @@
import {merge, pick, omit} from 'lodash';
import {omit, pick} from 'lodash';
import {getUserMedia, enumerateDevices} from 'mediaDevices';
const facingMatching = {
"user": /front/i,
"environment": /back/i
};
var streamRef;
@ -8,7 +14,7 @@ function waitForVideo(video) {
function checkVideo() {
if (attempts > 0) {
if (video.videoWidth > 0 && video.videoHeight > 0) {
if (video.videoWidth > 10 && video.videoHeight > 10) {
if (ENV.development) {
console.log(video.videoWidth + "px x " + video.videoHeight + "px");
}
@ -32,11 +38,13 @@ function waitForVideo(video) {
* @param {Object} video
*/
function initCamera(video, constraints) {
return navigator.mediaDevices.getUserMedia(constraints)
return getUserMedia(constraints)
.then((stream) => {
return new Promise((resolve) => {
streamRef = stream;
video.setAttribute("autoplay", 'true');
video.setAttribute("autoplay", true);
video.setAttribute('muted', true);
video.setAttribute('playsinline', true);
video.srcObject = stream;
video.addEventListener('loadedmetadata', () => {
video.play();
@ -63,41 +71,31 @@ function deprecatedConstraints(videoConstraints) {
return normalized;
}
function applyCameraFacing(facing, constraints) {
if (typeof constraints.video.deviceId === 'string' && constraints.video.deviceId.length > 0) {
return Promise.resolve({
...constraints,
video: {
...omit(constraints.video, "facingMode")
}
});
} else if (!facing) {
return Promise.resolve(constraints);
}
if ( typeof MediaStreamTrack !== 'undefined' &&
typeof MediaStreamTrack.getSources !== 'undefined') {
return new Promise((resolve) => {
MediaStreamTrack.getSources((sourceInfos) => {
const videoSource = sourceInfos.filter((sourceInfo) => (
sourceInfo.kind === "video" && sourceInfo.facing === facing
))[0];
if (videoSource) {
return resolve(merge({}, constraints,
{video: {deviceId: videoSource.id}}));
}
return resolve(constraints);
});
});
}
return Promise.resolve(merge({}, constraints, {video: {facingMode: facing}}));
}
function pickConstraints(videoConstraints) {
const constraints = {
export function pickConstraints(videoConstraints) {
const normalizedConstraints = {
audio: false,
video: deprecatedConstraints(videoConstraints)
};
return applyCameraFacing(constraints.video.facingMode, constraints);
if (normalizedConstraints.video.deviceId
&& normalizedConstraints.video.facingMode) {
delete normalizedConstraints.video.facingMode;
}
return Promise.resolve(normalizedConstraints);
}
function enumerateVideoDevices() {
return enumerateDevices()
.then(devices => devices.filter(device => device.kind === 'videoinput'));
}
function getActiveTrack() {
if (streamRef) {
const tracks = streamRef.getVideoTracks();
if (tracks && tracks.length) {
return tracks[0];
}
}
}
export default {
@ -111,5 +109,11 @@ export default {
tracks[0].stop();
}
streamRef = null;
}
},
enumerateVideoDevices,
getActiveStreamLabel: function() {
const track = getActiveTrack();
return track ? track.label : '';
},
getActiveTrack
};

@ -1,138 +0,0 @@
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,10 +286,6 @@ InputStream.createImageStream = function() {
}
};
that.clearEventHandlers = function() {
_eventHandlers = {};
};
that.setTopRight = function(topRight) {
_topRight.x = topRight.x;
_topRight.y = topRight.y;

File diff suppressed because it is too large Load Diff

@ -1,137 +1,543 @@
import './common/typedefs';
import 'webrtc-adapter';
import createScanner from './scanner';
import TypeDefs from './common/typedefs'; // eslint-disable-line no-unused-vars
import ImageWrapper from './common/image_wrapper';
import BarcodeLocator from './locator/barcode_locator';
import BarcodeDecoder from './decoder/barcode_decoder';
import Events from './common/events';
import CameraAccess from './input/camera_access';
import ImageDebug from './common/image_debug';
import ResultCollector from './analytics/result_collector';
import Config from './config/config';
import InputStream from 'input_stream';
import FrameGrabber from 'frame_grabber';
import {merge} from 'lodash';
import {createConfigFromSource} from './input/config_factory';
const vec2 = {
clone: require('gl-vec2/clone')
};
function fromConfig(config) {
const scanner = createScanner();
let pendingStart = null;
let initialized = false;
return {
addEventListener(eventType, cb) {
scanner.subscribe(eventType, cb);
return this;
},
removeEventListener(eventType, cb) {
scanner.unsubscribe(eventType, cb);
return this;
var _inputStream,
_framegrabber,
_stopped,
_canvasContainer = {
ctx: {
image: null,
overlay: null
},
start() {
if (scanner.isRunning()) {
return Promise.resolve(true);
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);
}
if (pendingStart) {
return pendingStart;
}
_canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d");
_canvasContainer.dom.image.width = _inputStream.getCanvasSize().x;
_canvasContainer.dom.image.height = _inputStream.getCanvasSize().y;
_canvasContainer.dom.overlay = document.querySelector("canvas.drawingBuffer");
if (!_canvasContainer.dom.overlay) {
_canvasContainer.dom.overlay = document.createElement("canvas");
_canvasContainer.dom.overlay.className = "drawingBuffer";
if ($viewport) {
$viewport.appendChild(_canvasContainer.dom.overlay);
}
if (initialized) {
scanner.start();
return Promise.resolve(true);
var clearFix = document.createElement("br");
clearFix.setAttribute("clear", "all");
if ($viewport) {
$viewport.appendChild(clearFix);
}
pendingStart = new Promise((resolve, reject) => {
scanner.init(config, (error) => {
if (error) {
console.log(error);
reject(error);
}
initialized = true;
scanner.start();
resolve();
pendingStart = null;
});
});
return pendingStart;
},
stop() {
scanner.stop();
initialized = false;
return this;
},
toPromise() {
if (config.inputStream.type === 'LiveStream'
|| config.inputStream.type === 'VideoStream') {
let cancelRequested = false;
return {
cancel() {
cancelRequested = true;
},
promise: new Promise((resolve, reject) => {
function onProcessed(result) {
if (result && result.codeResult && result.codeResult.code) {
scanner.stop();
scanner.unsubscribe("processed", onProcessed);
resolve(result);
}
if (cancelRequested) {
scanner.stop();
scanner.unsubscribe("processed", onProcessed);
reject("cancelled!");
}
}
scanner.subscribe("processed", onProcessed);
this.start();
})
};
}
_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 new Promise((resolve, reject) => {
scanner.decodeSingle(config, (result) => {
if (result && result.codeResult && result.codeResult.code) {
return resolve(result);
}
return reject(result);
});
});
return; // all workers are busy
}
},
registerResultCollector(resultCollector) {
scanner.registerResultCollector(resultCollector);
},
getCanvas() {
return scanner.canvas.dom.image;
},
};
} 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 fromSource(config, source, inputConfig = {}) {
config = createConfigFromSource(config, inputConfig, source);
return fromConfig(config);
function start() {
if (_onUIThread && _config.inputStream.type === "LiveStream") {
startContinuousUpdate();
} else {
update();
}
}
function setConfig(configuration = {}, key, config = {}) {
var mergedConfig = merge({}, configuration, {[key]: config});
return createApi(mergedConfig);
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 createApi(configuration = Config) {
function configForWorker(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
...config,
inputStream: {
...config.inputStream,
target: null
}
};
}
export default createApi();
function workerInterface(factory) {
/* eslint-disable no-undef*/
if (factory) {
var Quagga = factory().default;
if (!Quagga) {
self.postMessage({'event': 'error', message: 'Quagga could not be created'});
return;
}
}
var imageWrapper;
self.onmessage = function(e) {
if (e.data.cmd === 'init') {
var config = e.data.config;
config.numOfWorkers = 0;
imageWrapper = new Quagga.ImageWrapper({
x: e.data.size.x,
y: e.data.size.y
}, new Uint8Array(e.data.imageData));
Quagga.init(config, ready, imageWrapper);
Quagga.onProcessed(onProcessed);
} else if (e.data.cmd === 'process') {
imageWrapper.data = new Uint8Array(e.data.imageData);
Quagga.start();
} else if (e.data.cmd === 'setReaders') {
Quagga.setReaders(e.data.readers);
}
};
function onProcessed(result) {
self.postMessage({
'event': 'processed',
imageData: imageWrapper.data,
result: result
}, [imageWrapper.data.buffer]);
}
function ready() { // eslint-disable-line
self.postMessage({'event': 'initialized', imageData: imageWrapper.data}, [imageWrapper.data.buffer]);
}
/* eslint-enable */
}
function generateWorkerBlob() {
var blob,
factorySource;
/* jshint ignore:start */
if (typeof __factorySource__ !== 'undefined') {
factorySource = __factorySource__; // eslint-disable-line no-undef
}
/* jshint ignore:end */
blob = new Blob(['(' + workerInterface.toString() + ')(' + factorySource + ');'],
{type: 'text/javascript'});
return window.URL.createObjectURL(blob);
}
function setReaders(readers) {
if (_decoder) {
_decoder.setReaders(readers);
} else if (_onUIThread && _workerPool.length > 0) {
_workerPool.forEach(function(workerThread) {
workerThread.worker.postMessage({cmd: 'setReaders', readers: readers});
});
}
}
function adjustWorkerPool(capacity, cb) {
const increaseBy = capacity - _workerPool.length;
if (increaseBy === 0) {
return cb && cb();
}
if (increaseBy < 0) {
const workersToTerminate = _workerPool.slice(increaseBy);
workersToTerminate.forEach(function(workerThread) {
workerThread.worker.terminate();
if (ENV.development) {
console.log("Worker terminated!");
}
});
_workerPool = _workerPool.slice(0, increaseBy);
return cb && cb();
} else {
for (var i = 0; i < increaseBy; i++) {
initWorker(workerInitialized);
}
function workerInitialized(workerThread) {
_workerPool.push(workerThread);
if (_workerPool.length >= capacity){
cb && cb();
}
}
}
}
export default {
init: function(config, cb, imageWrapper) {
_config = merge({}, Config, config);
if (imageWrapper) {
_onUIThread = false;
initializeData(imageWrapper);
return cb();
} else {
initInputStream(cb);
}
},
start: function() {
start();
},
stop: function() {
_stopped = true;
adjustWorkerPool(0);
if (_config.inputStream.type === "LiveStream") {
CameraAccess.release();
_inputStream.clearEventHandlers();
}
},
pause: function() {
_stopped = true;
},
onDetected: function(callback) {
Events.subscribe("detected", callback);
},
offDetected: function(callback) {
Events.unsubscribe("detected", callback);
},
onProcessed: function(callback) {
Events.subscribe("processed", callback);
},
offProcessed: function(callback) {
Events.unsubscribe("processed", callback);
},
setReaders: function(readers) {
setReaders(readers);
},
registerResultCollector: function(resultCollector) {
if (resultCollector && typeof resultCollector.addResult === 'function') {
_resultCollector = resultCollector;
}
},
canvas: _canvasContainer,
decodeSingle: function(config, resultCallback) {
config = merge({
inputStream: {
type: "ImageStream",
sequence: false,
size: 800,
src: config.src
},
numOfWorkers: (ENV.development && config.debug) ? 0 : 1,
locator: {
halfSample: false
}
}, config);
this.init(config, () => {
Events.once("processed", (result) => {
this.stop();
resultCallback.call(null, result);
}, true);
start();
});
},
ImageWrapper: ImageWrapper,
ImageDebug: ImageDebug,
ResultCollector: ResultCollector,
CameraAccess: CameraAccess,
};

@ -0,0 +1,257 @@
import BarcodeReader from './barcode_reader';
function TwoOfFiveReader(opts) {
BarcodeReader.call(this, opts);
this.barSpaceRatio = [1, 1];
}
var N = 1,
W = 3,
properties = {
START_PATTERN: {value: [W, N, W, N, N, N]},
STOP_PATTERN: {value: [W, N, N, N, W]},
CODE_PATTERN: {value: [
[N, N, W, W, N],
[W, N, N, N, W],
[N, W, N, N, W],
[W, W, N, N, N],
[N, N, W, N, W],
[W, N, W, N, N],
[N, W, W, N, N],
[N, N, N, W, W],
[W, N, N, W, N],
[N, W, N, W, N]
]},
SINGLE_CODE_ERROR: {value: 0.78, writable: true},
AVG_CODE_ERROR: {value: 0.30, writable: true},
FORMAT: {value: "2of5"}
};
const startPatternLength = properties.START_PATTERN.value.reduce((sum, val) => sum + val, 0);
TwoOfFiveReader.prototype = Object.create(BarcodeReader.prototype, properties);
TwoOfFiveReader.prototype.constructor = TwoOfFiveReader;
TwoOfFiveReader.prototype._findPattern = function(pattern, offset, isWhite, tryHarder) {
var counter = [],
self = this,
i,
counterPos = 0,
bestMatch = {
error: Number.MAX_VALUE,
code: -1,
start: 0,
end: 0
},
error,
j,
sum,
epsilon = self.AVG_CODE_ERROR;
isWhite = isWhite || false;
tryHarder = tryHarder || false;
if (!offset) {
offset = self._nextSet(self._row);
}
for ( i = 0; i < pattern.length; i++) {
counter[i] = 0;
}
for ( i = offset; i < self._row.length; i++) {
if (self._row[i] ^ isWhite) {
counter[counterPos]++;
} else {
if (counterPos === counter.length - 1) {
sum = 0;
for ( j = 0; j < counter.length; j++) {
sum += counter[j];
}
error = self._matchPattern(counter, pattern);
if (error < epsilon) {
bestMatch.error = error;
bestMatch.start = i - sum;
bestMatch.end = i;
return bestMatch;
}
if (tryHarder) {
for (j = 0; j < counter.length - 2; j++) {
counter[j] = counter[j + 2];
}
counter[counter.length - 2] = 0;
counter[counter.length - 1] = 0;
counterPos--;
} else {
return null;
}
} else {
counterPos++;
}
counter[counterPos] = 1;
isWhite = !isWhite;
}
}
return null;
};
TwoOfFiveReader.prototype._findStart = function() {
var self = this,
leadingWhitespaceStart,
offset = self._nextSet(self._row),
startInfo,
narrowBarWidth = 1;
while (!startInfo) {
startInfo = self._findPattern(self.START_PATTERN, offset, false, true);
if (!startInfo) {
return null;
}
narrowBarWidth = Math.floor((startInfo.end - startInfo.start) / startPatternLength);
leadingWhitespaceStart = startInfo.start - narrowBarWidth * 5;
if (leadingWhitespaceStart >= 0) {
if (self._matchRange(leadingWhitespaceStart, startInfo.start, 0)) {
return startInfo;
}
}
offset = startInfo.end;
startInfo = null;
}
};
TwoOfFiveReader.prototype._verifyTrailingWhitespace = function(endInfo) {
var self = this,
trailingWhitespaceEnd;
trailingWhitespaceEnd = endInfo.end + ((endInfo.end - endInfo.start) / 2);
if (trailingWhitespaceEnd < self._row.length) {
if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) {
return endInfo;
}
}
return null;
};
TwoOfFiveReader.prototype._findEnd = function() {
var self = this,
endInfo,
tmp,
offset;
self._row.reverse();
offset = self._nextSet(self._row);
endInfo = self._findPattern(self.STOP_PATTERN, offset, false, true);
self._row.reverse();
if (endInfo === null) {
return null;
}
// reverse numbers
tmp = endInfo.start;
endInfo.start = self._row.length - endInfo.end;
endInfo.end = self._row.length - tmp;
return endInfo !== null ? self._verifyTrailingWhitespace(endInfo) : null;
};
TwoOfFiveReader.prototype._decodeCode = function(counter) {
var j,
self = this,
sum = 0,
normalized,
error,
epsilon = self.AVG_CODE_ERROR,
code,
bestMatch = {
error: Number.MAX_VALUE,
code: -1,
start: 0,
end: 0
};
for ( j = 0; j < counter.length; j++) {
sum += counter[j];
}
for (code = 0; code < self.CODE_PATTERN.length; code++) {
error = self._matchPattern(counter, self.CODE_PATTERN[code]);
if (error < bestMatch.error) {
bestMatch.code = code;
bestMatch.error = error;
}
}
if (bestMatch.error < epsilon) {
return bestMatch;
}
};
TwoOfFiveReader.prototype._decodePayload = function(counters, result, decodedCodes) {
var i,
self = this,
pos = 0,
counterLength = counters.length,
counter = [0, 0, 0, 0, 0],
code;
while (pos < counterLength) {
for (i = 0; i < 5; i++) {
counter[i] = counters[pos] * this.barSpaceRatio[0];
pos += 2;
}
code = self._decodeCode(counter);
if (!code) {
return null;
}
result.push(code.code + "");
decodedCodes.push(code);
}
return code;
};
TwoOfFiveReader.prototype._verifyCounterLength = function(counters) {
return (counters.length % 10 === 0);
};
TwoOfFiveReader.prototype._decode = function() {
var startInfo,
endInfo,
self = this,
code,
result = [],
decodedCodes = [],
counters;
startInfo = self._findStart();
if (!startInfo) {
return null;
}
decodedCodes.push(startInfo);
endInfo = self._findEnd();
if (!endInfo) {
return null;
}
counters = self._fillCounters(startInfo.end, endInfo.start, false);
if (!self._verifyCounterLength(counters)) {
return null;
}
code = self._decodePayload(counters, result, decodedCodes);
if (!code) {
return null;
}
if (result.length < 5) {
return null;
}
decodedCodes.push(endInfo);
return {
code: result.join(""),
start: startInfo.start,
end: endInfo.end,
startInfo: startInfo,
decodedCodes: decodedCodes
};
};
export default TwoOfFiveReader;

@ -1,3 +1,5 @@
import ArrayHelper from '../common/array_helper';
function BarcodeReader(config, supplements) {
this._row = [];
this.config = config || {};
@ -195,6 +197,33 @@ BarcodeReader.prototype._fillCounters = function(offset, end, isWhite) {
return counters;
};
BarcodeReader.prototype._toCounters = function(start, counter) {
var self = this,
numCounters = counter.length,
end = self._row.length,
isWhite = !self._row[start],
i,
counterPos = 0;
ArrayHelper.init(counter, 0);
for ( i = start; i < end; i++) {
if (self._row[i] ^ isWhite) {
counter[counterPos]++;
} else {
counterPos++;
if (counterPos === numCounters) {
break;
} else {
counter[counterPos] = 1;
isWhite = !isWhite;
}
}
}
return counter;
};
Object.defineProperty(BarcodeReader.prototype, "FORMAT", {
value: 'unknown',
writeable: false

@ -20,33 +20,6 @@ var properties = {
Code39Reader.prototype = Object.create(BarcodeReader.prototype, properties);
Code39Reader.prototype.constructor = Code39Reader;
Code39Reader.prototype._toCounters = function(start, counter) {
var self = this,
numCounters = counter.length,
end = self._row.length,
isWhite = !self._row[start],
i,
counterPos = 0;
ArrayHelper.init(counter, 0);
for ( i = start; i < end; i++) {
if (self._row[i] ^ isWhite) {
counter[counterPos]++;
} else {
counterPos++;
if (counterPos === numCounters) {
break;
} else {
counter[counterPos] = 1;
isWhite = !isWhite;
}
}
}
return counter;
};
Code39Reader.prototype._decode = function() {
var self = this,
counters = [0, 0, 0, 0, 0, 0, 0, 0, 0],

@ -0,0 +1,251 @@
import BarcodeReader from './barcode_reader';
import ArrayHelper from '../common/array_helper';
function Code93Reader() {
BarcodeReader.call(this);
}
const ALPHABETH_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%abcd*";
var properties = {
ALPHABETH_STRING: {value: ALPHABETH_STRING},
ALPHABET: {value: ALPHABETH_STRING.split('').map(char => char.charCodeAt(0))},
CHARACTER_ENCODINGS: {value: [
0x114, 0x148, 0x144, 0x142, 0x128, 0x124, 0x122, 0x150, 0x112, 0x10A,
0x1A8, 0x1A4, 0x1A2, 0x194, 0x192, 0x18A, 0x168, 0x164, 0x162, 0x134,
0x11A, 0x158, 0x14C, 0x146, 0x12C, 0x116, 0x1B4, 0x1B2, 0x1AC, 0x1A6,
0x196, 0x19A, 0x16C, 0x166, 0x136, 0x13A, 0x12E, 0x1D4, 0x1D2, 0x1CA,
0x16E, 0x176, 0x1AE, 0x126, 0x1DA, 0x1D6, 0x132, 0x15E
]},
ASTERISK: {value: 0x15E},
FORMAT: {value: "code_93", writeable: false}
};
Code93Reader.prototype = Object.create(BarcodeReader.prototype, properties);
Code93Reader.prototype.constructor = Code93Reader;
Code93Reader.prototype._decode = function() {
var self = this,
counters = [0, 0, 0, 0, 0, 0],
result = [],
start = self._findStart(),
decodedChar,
lastStart,
pattern,
nextStart;
if (!start) {
return null;
}
nextStart = self._nextSet(self._row, start.end);
do {
counters = self._toCounters(nextStart, counters);
pattern = self._toPattern(counters);
if (pattern < 0) {
return null;
}
decodedChar = self._patternToChar(pattern);
if (decodedChar < 0){
return null;
}
result.push(decodedChar);
lastStart = nextStart;
nextStart += ArrayHelper.sum(counters);
nextStart = self._nextSet(self._row, nextStart);
} while (decodedChar !== '*');
result.pop();
if (!result.length) {
return null;
}
if (!self._verifyEnd(lastStart, nextStart, counters)) {
return null;
}
if (!self._verifyChecksums(result)) {
return null;
}
result = result.slice(0, result.length - 2);
if ((result = self._decodeExtended(result)) === null) {
return null;
};
return {
code: result.join(""),
start: start.start,
end: nextStart,
startInfo: start,
decodedCodes: result
};
};
Code93Reader.prototype._verifyEnd = function(lastStart, nextStart) {
if (lastStart === nextStart || !this._row[nextStart]) {
return false;
}
return true;
};
Code93Reader.prototype._patternToChar = function(pattern) {
var i,
self = this;
for (i = 0; i < self.CHARACTER_ENCODINGS.length; i++) {
if (self.CHARACTER_ENCODINGS[i] === pattern) {
return String.fromCharCode(self.ALPHABET[i]);
}
}
return -1;
};
Code93Reader.prototype._toPattern = function(counters) {
const numCounters = counters.length;
let pattern = 0;
let sum = 0;
for (let i = 0; i < numCounters; i++) {
sum += counters[i];
}
for (let i = 0; i < numCounters; i++) {
let normalized = Math.round(counters[i] * 9 / sum);
if (normalized < 1 || normalized > 4) {
return -1;
}
if ((i & 1) === 0) {
for (let j = 0; j < normalized; j++) {
pattern = (pattern << 1) | 1;
}
} else {
pattern <<= normalized;
}
}
return pattern;
};
Code93Reader.prototype._findStart = function() {
var self = this,
offset = self._nextSet(self._row),
patternStart = offset,
counter = [0, 0, 0, 0, 0, 0],
counterPos = 0,
isWhite = false,
i,
j,
whiteSpaceMustStart;
for ( i = offset; i < self._row.length; i++) {
if (self._row[i] ^ isWhite) {
counter[counterPos]++;
} else {
if (counterPos === counter.length - 1) {
// find start pattern
if (self._toPattern(counter) === self.ASTERISK) {
whiteSpaceMustStart = Math.floor(Math.max(0, patternStart - ((i - patternStart) / 4)));
if (self._matchRange(whiteSpaceMustStart, patternStart, 0)) {
return {
start: patternStart,
end: i
};
}
}
patternStart += counter[0] + counter[1];
for ( j = 0; j < 4; j++) {
counter[j] = counter[j + 2];
}
counter[4] = 0;
counter[5] = 0;
counterPos--;
} else {
counterPos++;
}
counter[counterPos] = 1;
isWhite = !isWhite;
}
}
return null;
};
Code93Reader.prototype._decodeExtended = function(charArray) {
const length = charArray.length;
const result = [];
for (let i = 0; i < length; i++) {
const char = charArray[i];
if (char >= 'a' && char <= 'd') {
if (i > (length - 2)) {
return null;
}
const nextChar = charArray[++i];
const nextCharCode = nextChar.charCodeAt(0);
let decodedChar;
switch (char) {
case 'a':
if (nextChar >= 'A' && nextChar <= 'Z') {
decodedChar = String.fromCharCode(nextCharCode - 64);
} else {
return null;
}
break;
case 'b':
if (nextChar >= 'A' && nextChar <= 'E') {
decodedChar = String.fromCharCode(nextCharCode - 38);
} else if (nextChar >= 'F' && nextChar <= 'J') {
decodedChar = String.fromCharCode(nextCharCode - 11);
} else if (nextChar >= 'K' && nextChar <= 'O') {
decodedChar = String.fromCharCode(nextCharCode + 16);
} else if (nextChar >= 'P' && nextChar <= 'S') {
decodedChar = String.fromCharCode(nextCharCode + 43);
} else if (nextChar >= 'T' && nextChar <= 'Z') {
decodedChar = String.fromCharCode(127);
} else {
return null;
}
break;
case 'c':
if (nextChar >= 'A' && nextChar <= 'O') {
decodedChar = String.fromCharCode(nextCharCode - 32);
} else if (nextChar === 'Z') {
decodedChar = ':';
} else {
return null;
}
break;
case 'd':
if (nextChar >= 'A' && nextChar <= 'Z') {
decodedChar = String.fromCharCode(nextCharCode + 32);
} else {
return null;
}
break;
}
result.push(decodedChar);
} else {
result.push(char);
}
}
return result;
};
Code93Reader.prototype._verifyChecksums = function(charArray) {
return this._matchCheckChar(charArray, charArray.length - 2, 20)
&& this._matchCheckChar(charArray, charArray.length - 1, 15);
};
Code93Reader.prototype._matchCheckChar = function(charArray, index, maxWeight) {
const arrayToCheck = charArray.slice(0, index);
const length = arrayToCheck.length;
const weightedSums = arrayToCheck.reduce((sum, char, i) => {
const weight = (((i * -1) + (length - 1)) % maxWeight) + 1;
const value = this.ALPHABET.indexOf(char.charCodeAt(0));
return sum + (weight * value);
}, 0);
const checkChar = this.ALPHABET[(weightedSums % 47)];
return checkChar === charArray[index].charCodeAt(0);
};
export default Code93Reader;

@ -1,508 +0,0 @@
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;

@ -10,9 +10,9 @@ module.exports = function(grunt) {
grunt.registerTask('uglyasm', function() {
var code = fs.readFileSync('dist/quagga.js', 'utf-8'),
minifiedCode = fs.readFileSync('dist/quagga.min.js', 'utf-8'),
commentEnd = '/* @preserve ASM END */',
moduleFunctionRegex = /function\s*\((\w+,\s*\w+,\s*\w+)\)\s*\{\s*\/\* \@preserve ASM BEGIN \*\//,
commentStartIdx = code.indexOf("/* @preserve ASM BEGIN */"),
commentEnd = '@preserve ASM END',
moduleFunctionRegex = /function\s*\((\w+,\s*\w+,\s*\w+)\)\s*\{(\n?\s*\"use strict\";?)*\n?\/\*\s*\@preserve ASM BEGIN/,
commentStartIdx = code.indexOf("@preserve ASM BEGIN"),
asmEndIdxTmp = code.indexOf(commentEnd),
asmEndIdx = code.indexOf("}", asmEndIdxTmp),
asmCodeTmp = code.substring(commentStartIdx - Math.min(500, commentStartIdx),
@ -30,8 +30,6 @@ module.exports = function(grunt) {
.replace(/ ([+=^|&]|>+|<+) /g, '$1') // remove spaces around operators
.replace(/[\r\n/]/g, ''); // remove new lines
grunt.log.debug(asmCodeMinified);
asmModule = moduleFunctionRegex.exec(asmCode);
if (!asmModule) {
grunt.log.error("No ASM module found");

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save