Compare commits

...

105 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
Christoph Oberhofer 9a2a98bd90 0.11.3 9 years ago
Christoph Oberhofer 79a2b019f4 Merge pull request #122 from serratus/feature/121
Recognizing EXIF orientation
9 years ago
Christoph Oberhofer 94db7bfc02 Updated README 9 years ago
Christoph Oberhofer ce4d4a976a Added tests for exif-helper 9 years ago
Christoph Oberhofer 6d5fd9d7ac Added helper module for extracting EXIF; Rotating the processing image according to EXIF orientation; Relates to #121 9 years ago
Christoph Oberhofer ee0fec3321 0.11.2 9 years ago
Christoph Oberhofer 3df696a1f7 Fixed bug in event-handling; Updated webrtc-adapter 9 years ago
Christoph Oberhofer 36205f8ceb 0.11.1 9 years ago
Christoph Oberhofer 187919609b added typescript definitions d.ts; Relates to #42 9 years ago
Christoph Oberhofer 6cf7c2f57e 0.11.0 9 years ago
Christoph Oberhofer 926983115f Build for 0.11.0 9 years ago
Christoph Oberhofer 9bbcfc2f99 Removed unused variable 9 years ago
Christoph Oberhofer 827c4d6497 Updated readme 9 years ago
Christoph Oberhofer 9ba118bcd0 Removed unused files 9 years ago
Christoph Oberhofer af3c505882 preparing ean-extended release 9 years ago
Christoph Oberhofer 8fa0c69bc1 Fixed Code128 Reader 9 years ago
Christoph Oberhofer dadb8dbc9f Improved code-matching 9 years ago
Christoph Oberhofer a9584bae77 Fixed EAN-readers 9 years ago
Christoph Oberhofer eb9a4ca4cb Initial commit on EAN supplements; Relates to #71 9 years ago
Christoph Oberhofer 318681d66b 0.10.4 9 years ago
Christoph Oberhofer e9113c48ad Updated builds to 0.10.4 9 years ago
Christoph Oberhofer f87c16e58f Updated changelog 9 years ago
Christoph Oberhofer 1b9d5451ed Reorganizing functions/methods 9 years ago
Christoph Oberhofer 8cae60cdee Fixed integrationtests 9 years ago
Christoph Oberhofer 7a9f146278 Improving robustness of Code 128 decoder; Relates to #104 9 years ago
Christoph Oberhofer 9c212838cc 0.10.3 9 years ago
Christoph Oberhofer c851635522 Fixes #105 9 years ago
Christoph Oberhofer 7ec6eb28d3 0.10.2 9 years ago
Christoph Oberhofer 4de93469a5 Updated builds to newest version 9 years ago
Christoph Oberhofer 7bead78de8 Fixes #102; Relates to #103 and #96 9 years ago
Christoph Oberhofer cd8148d05d 0.10.1 9 years ago
Christoph Oberhofer 0d528d4c98 Updated build 9 years ago
Christoph Oberhofer 29b98ea442 Fixed #100 9 years ago
Christoph Oberhofer 117ab05c09 Added docs on browser-support; closes #87 9 years ago
Christoph Oberhofer 01193b969c Added documentation on patchSize and halfSample. Closes #73 9 years ago
Christoph Oberhofer bdd71a8353 0.10.0 9 years ago
Christoph Oberhofer 317abd34c1 updated builds to 0.10.0 9 years ago
Christoph Oberhofer f830482ab7 Updated docs 9 years ago
Christoph Oberhofer 168d7e4321 Updated docs 9 years ago
Christoph Oberhofer ca3e4b673a Fixed: Workers are terminated instead of re-usedin order to initialize them correctly 9 years ago
Christoph Oberhofer da08ae3eaf Upgrade to webpack 2 and babel 6 9 years ago
Christoph Oberhofer ce81e9169c Fixed test-runner 9 years ago
Christoph Oberhofer 9e6134cc07 Added tests for video-access 9 years ago
Christoph Oberhofer 4019ea867f Implemented new MediaDevices API; Fallback for facingMode on Chrome-Mobile 9 years ago
Christoph Oberhofer 322413b3c5 Updated bundles; Fixes #95 9 years ago
Christoph Oberhofer a83940daa1 Merge branch 'JauernigIT-scanDelay' 9 years ago
Matthias Jauernig 3cf474d484 scan frequency as proposed by serratus 9 years ago
Matthias Jauernig 9b1e697609 new config variable scanDelay to add some time between two scans 9 years ago
Christoph Oberhofer 82ef5b9397 0.9.2 9 years ago
Christoph Oberhofer 040f3e5704 Minor bugfixes 9 years ago
Christoph Oberhofer 49f1e3db32 Updated Readme to point to HTTPS examples 10 years ago

@ -0,0 +1,33 @@
{
"plugins": [
"check-es2015-constants",
"transform-es2015-arrow-functions",
"transform-es2015-block-scoped-functions",
"transform-es2015-block-scoping",
["transform-es2015-classes", { "loose": true }],
["transform-es2015-computed-properties", { "loose": true }],
["transform-es2015-destructuring", { "loose": true }],
["transform-es2015-for-of", { "loose": true }],
"transform-es2015-function-name",
"transform-es2015-literals",
"transform-es2015-object-super",
"transform-es2015-parameters",
"transform-es2015-shorthand-properties",
["transform-es2015-spread", { "loose": true }],
"transform-es2015-sticky-regex",
["transform-es2015-template-literals", { "loose": true }],
"transform-es2015-unicode-regex",
"transform-es2015-typeof-symbol",
"transform-object-rest-spread",
"lodash"
],
"env": {
"commonjs": {
"plugins": [
["transform-es2015-modules-commonjs", { "loose": true }],
"add-module-exports"
]
}
}
}

@ -24,7 +24,12 @@
"objectLiteralComputedProperties": true "objectLiteralComputedProperties": true
}, },
"globals": { "globals": {
"ENV": true "ENV": true,
"beforeEach": true,
"describe": true,
"it": true,
"expect": true,
"sinon": true
}, },
"rules": { "rules": {
"no-unused-expressions": 1, "no-unused-expressions": 1,

1
.gitignore vendored

@ -4,3 +4,4 @@ coverage/
.project .project
_site/ _site/
.idea/ .idea/
.vscode/

@ -1,23 +1,28 @@
quaggaJS quaggaJS
======== ========
- [Changelog](#changelog) (2016-02-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) - [Installing](#installing)
- [Getting Started](#gettingstarted) - [Getting Started](#gettingstarted)
- [API](#api) - [API](#api)
- [Configuration](#configobject) - [Configuration](#configobject)
- [Tips & Tricks](#tipsandtricks)
- [Sponsors](#sponsors)
## What is QuaggaJS? ## What is QuaggaJS?
QuaggaJS is a barcode-scanner entirely written in JavaScript supporting real- QuaggaJS is a barcode-scanner entirely written in JavaScript supporting real-
time localization and decoding of various types of barcodes such as __EAN__, time localization and decoding of various types of barcodes such as __EAN__,
__CODE 128__, __CODE 39__, __EAN 8__, __UPC-A__, __UPC-C__, __I2of5__ and __CODE 128__, __CODE 39__, __EAN 8__, __UPC-A__, __UPC-C__, __I2of5__,
__CODABAR__. The library is also capable of using `getUserMedia` to get direct __2of5__, __CODE 93__ and __CODABAR__. The library is also capable of using
access to the user's camera stream. Although the code relies on heavy image- `getUserMedia` to get direct access to the user's camera stream. Although the
processing even recent smartphones are capable of locating and decoding code relies on heavy image-processing even recent smartphones are capable of
barcodes in real-time. locating and decoding barcodes in real-time.
Try some [examples](http://serratus.github.io/quaggaJS/examples) and check out Try some [examples](https://serratus.github.io/quaggaJS/examples) and check out
the blog post ([How barcode-localization works in QuaggaJS][oberhofer_co_how]) the blog post ([How barcode-localization works in QuaggaJS][oberhofer_co_how])
if you want to dive deeper into this topic. if you want to dive deeper into this topic.
@ -34,18 +39,59 @@ invariant to scale and rotation, whereas other libraries require the barcode to
be aligned with the viewport. be aligned with the viewport.
## Requirements ## <a name="browser-support">Browser Support</a>
Quagga makes use of many modern Web-APIs which are not implemented by all
browsers yet. There are two modes in which Quagga operates: 1. analyzing static
images and 2. using a camera to decode the images from a live-stream. The latter
requires the presence of the MediaDevices API. You can track the compatibility
of the used Web-APIs for each mode:
- [Static Images](http://caniuse.com/#feat=webworkers,canvas,typedarrays,bloburls,blobbuilder)
- [Live Stream](http://caniuse.com/#feat=webworkers,canvas,typedarrays,bloburls,blobbuilder,stream)
### Static Images
The following APIs need to be implemented in your browser:
- [webworkers](http://caniuse.com/#feat=webworkers)
- [canvas](http://caniuse.com/#feat=canvas)
- [typedarrays](http://caniuse.com/#feat=typedarrays)
- [bloburls](http://caniuse.com/#feat=bloburls)
- [blobbuilder](http://caniuse.com/#feat=blobbuilder)
### Live Stream
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`
}
```
In order to take full advantage of quaggaJS, the browser needs to support the The above condition evaluates to:
`getUserMedia` API which is already implemented in recent versions of Firefox,
Chrome, IE (Edge) and Opera. The API is also available on their mobile
counterparts installed on Android (except IE). Safari does not allow the access
to the camera yet, neither on desktop, nor on mobile. You can check
[caniuse][caniuse_getusermedia] for updates.
In cases where real-time decoding is not needed, or the platform does not | Browser | result |
support `getUserMedia` QuaggaJS is also capable of decoding image-files using | ------------- |:-------:|
the File API or other URL sources. | Edge | `true` |
| Chrome | `true` |
| Firefox | `true` |
| IE 11 | `false` |
| Safari iOS | `true` |
## <a name="installing">Installing</a> ## <a name="installing">Installing</a>
@ -61,7 +107,8 @@ the __script__ tag.
And then import it as dependency in your project: And then import it as dependency in your project:
```javascript ```javascript
var Quagga = require('quagga'); import Quagga from 'quagga'; // ES6
const Quagga = require('quagga').default; // Common JS (important: default)
``` ```
Currently, the full functionality is only available through the browser. When Currently, the full functionality is only available through the browser. When
@ -79,7 +126,7 @@ You can also install QuaggaJS through __bower__:
### Script-Tag Anno 1998 ### Script-Tag Anno 1998
You can simply include `dist/quagga.min.js` in your project and you are ready You can simply include `dist/quagga.min.js` in your project and you are ready
to go. to go. The script exposes the library on the global namespace under `Quagga`.
## <a name="gettingstarted">Getting Started</a> ## <a name="gettingstarted">Getting Started</a>
@ -278,6 +325,7 @@ high-level properties:
numOfWorkers: 4, numOfWorkers: 4,
locate: true, locate: true,
inputStream: {...}, inputStream: {...},
frequency: 10,
decoder:{...}, decoder:{...},
locator: {...}, locator: {...},
debug: false, debug: false,
@ -323,7 +371,8 @@ The `inputStream` property defines the sources of images/videos within QuaggaJS.
constraints: { constraints: {
width: 640, width: 640,
height: 480, height: 480,
facing: "environment" facingMode: "environment",
deviceId: "7832475934759384534"
}, },
area: { // defines rectangle of the detection/localization area area: { // defines rectangle of the detection/localization area
top: "0%", // top offset top: "0%", // top offset
@ -340,8 +389,11 @@ First, the `type` property can be set to three different values:
depending on the use-case. Most probably, the default value is sufficient. depending on the use-case. Most probably, the default value is sufficient.
Second, the `constraint` key defines the physical dimensions of the input image Second, the `constraint` key defines the physical dimensions of the input image
and additional properties, such as `facing` which sets the source of the user's and additional properties, such as `facingMode` which sets the source of the
camera in case of multiple attached devices. user's camera in case of multiple attached devices. Additionally, if required,
the `deviceId` can be set if the selection of the camera is given to the user.
This can be easily achieved via
[MediaDevices.enumerateDevices()][enumerateDevices]
Thirdly, the `area` prop restricts the decoding area of the image. The values Thirdly, the `area` prop restricts the decoding area of the image. The values
are given in percentage, similar to the CSS style property when using are given in percentage, similar to the CSS style property when using
@ -354,6 +406,13 @@ color-channel is read instead of calculating the gray-scale values of the
source's RGB. This is useful in combination with the `ResultCollector` where source's RGB. This is useful in combination with the `ResultCollector` where
the gray-scale representations of the wrongly identified images are saved. the gray-scale representations of the wrongly identified images are saved.
### frequency
This top-level property controls the scan-frequency of the video-stream. It's
optional and defines the maximum number of scans per second. This renders
useful for cases where the scan-session is long-running and resources such as
CPU power are of concern.
### decoder ### decoder
QuaggaJS usually runs in a two-stage manner (`locate` is set to `true`) where, QuaggaJS usually runs in a two-stage manner (`locate` is set to `true`) where,
@ -363,13 +422,15 @@ options within the `decoder` are for debugging/visualization purposes only.
```javascript ```javascript
{ {
drawBoundingBox: false,
showFrequency: false,
drawScanline: true,
showPattern: false,
readers: [ readers: [
'code_128_reader' 'code_128_reader'
], ],
debug: {
drawBoundingBox: false,
showFrequency: false,
drawScanline: false,
showPattern: false
}
multiple: false multiple: false
} }
``` ```
@ -386,6 +447,8 @@ barcodes which should be decoded during the session. Possible values are:
- upc_reader - upc_reader
- upc_e_reader - upc_e_reader
- i2of5_reader - i2of5_reader
- 2of5_reader
- code_93_reader
Why are not all types activated by default? Simply because one should 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 explicitly define the set of barcodes for their use-case. More decoders means
@ -402,27 +465,84 @@ individual box.
The remaining properties `drawBoundingBox`, `showFrequency`, `drawScanline` and The remaining properties `drawBoundingBox`, `showFrequency`, `drawScanline` and
`showPattern` are mostly of interest during debugging and visualization. `showPattern` are mostly of interest during debugging and visualization.
#### <a name="ean_extended">Enabling extended EAN</a>
The default setting for `ean_reader` is not capable of reading extensions such
as [EAN-2](https://en.wikipedia.org/wiki/EAN_2) or
[EAN-5](https://en.wikipedia.org/wiki/EAN_5). In order to activate those
supplements you have to provide them in the configuration as followed:
```javascript
decoder: {
readers: [{
format: "ean_reader",
config: {
supplements: [
'ean_5_reader', 'ean_2_reader'
]
}
}]
}
```
Beware that the order of the `supplements` matters in such that the reader stops
decoding when the first supplement was found. So if you are interested in EAN-2
and EAN-5 extensions, use the order depicted above.
It's important to mention that, if supplements are supplied, regular EAN-13
codes cannot be read any more with the same reader. If you want to read EAN-13
with and without extensions you have to add another `ean_reader` reader to the
configuration.
### locator ### locator
The `locator` config is only relevant if the `locate` flag is set to `true`.
It controls the behavior of the localization-process and needs to be adjusted
for each specific use-case. The default settings are simply a combination of
values which worked best during development.
Only two properties are relevant for the use in Quagga (`halfSample` and
`patchSize`) whereas the rest is only needed for development and debugging.
```javascript ```javascript
{ {
halfSample: true, halfSample: true,
patchSize: "medium", // x-small, small, medium, large, x-large patchSize: "medium", // x-small, small, medium, large, x-large
showCanvas: false, debug: {
showPatches: false, showCanvas: false,
showFoundPatches: false, showPatches: false,
showSkeleton: false, showFoundPatches: false,
showLabels: false, showSkeleton: false,
showPatchLabels: false, showLabels: false,
showRemainingPatchLabels: false, showPatchLabels: false,
boxFromPatches: { showRemainingPatchLabels: false,
showTransformed: false, boxFromPatches: {
showTransformedBox: false, showTransformed: false,
showBB: false showTransformedBox: false,
showBB: false
}
} }
} }
``` ```
The `halfSample` flag tells the locator-process whether it should operate on an
image scaled down (half width/height, quarter pixel-count ) or not. Turning
`halfSample` on reduces the processing-time significantly and also helps
finding a barcode pattern due to implicit smoothing.
It should be turned off in cases where the barcode is really small and the full
resolution is needed to find the position. It's recommended to keep it turned
on and use a higher resolution video-image if needed.
The second property `patchSize` defines the density of the search-grid. The
property accepts strings of the value `x-small`, `small`, `medium`, `large` and
`x-large`. The `patchSize` is proportional to the size of the scanned barcodes.
If you have really large barcodes which can be read close-up, then the use of
`large` or `x-large` is recommended. In cases where the barcode is further away
from the camera lens (lack of auto-focus, or small barcodes) then it's advised
to set the size to `small` or even `x-small`. For the latter it's also
recommended to crank up the resolution in order to find a barcode.
## Examples ## Examples
The following example takes an image `src` as input and prints the result on the The following example takes an image `src` as input and prints the result on the
@ -453,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`. property `numOfWorkers` must be explicitly set to `0`.
```javascript ```javascript
var Quagga = require('quagga'); var Quagga = require('quagga').default;
Quagga.decodeSingle({ Quagga.decodeSingle({
src: "image-abc-123.jpg", src: "image-abc-123.jpg",
@ -473,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 ## Tests
Unit Tests can be run with [Karma][karmaUrl] and written using Unit Tests can be run with [Karma][karmaUrl] and written using
@ -549,8 +699,65 @@ 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 . 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``. 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> ## <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`
(see [#121](https://github.com/serratus/quaggaJS/issues/121))
### 2016-04-24
- Features
- EAN-13 extended codes can now be decoded (See
[\#71](https://github.com/serratus/quaggaJS/issues/71))
Take a look at the release-notes (
[0.11.0](https://github.com/serratus/quaggaJS/releases/tag/v0.11.0))
### 2016-04-19
- Improvements
- Reducing false-positives for Code 128 barcodes (
addresses [\#104](https://github.com/serratus/quaggaJS/issues/104))
### 2016-03-31
Take a look at the release-notes (
[0.10.0](https://github.com/serratus/quaggaJS/releases/tag/v0.10.0))
### 2016-02-18 ### 2016-02-18
- Internal Changes - Internal Changes
@ -559,8 +766,8 @@ on the ``singleChannel`` flag in the configuration when using ``decodeSingle``.
### 2016-02-15 ### 2016-02-15
Take a look at the release-notes ([0.9.0] Take a look at the release-notes (
(https://github.com/serratus/quaggaJS/releases/tag/v0.9.0)) [0.9.0](https://github.com/serratus/quaggaJS/releases/tag/v0.9.0))
### 2015-11-22 ### 2015-11-22
@ -713,5 +920,6 @@ introduced to the API.
[upc_wiki]: http://en.wikipedia.org/wiki/Universal_Product_Code [upc_wiki]: http://en.wikipedia.org/wiki/Universal_Product_Code
[ean_8_wiki]: http://en.wikipedia.org/wiki/EAN-8 [ean_8_wiki]: http://en.wikipedia.org/wiki/EAN-8
[oberhofer_co_how]: http://www.oberhofer.co/how-barcode-localization-works-in-quaggajs/ [oberhofer_co_how]: http://www.oberhofer.co/how-barcode-localization-works-in-quaggajs/
[github_examples]: http://serratus.github.io/quaggaJS/examples [github_examples]: https://serratus.github.io/quaggaJS/examples
[i2of5_wiki]: https://en.wikipedia.org/wiki/Interleaved_2_of_5 [i2of5_wiki]: https://en.wikipedia.org/wiki/Interleaved_2_of_5
[enumerateDevices]: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices

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

24197
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

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

@ -1,79 +1,109 @@
@charset "UTF-8"; @charset "UTF-8";
/* usual styles */ @import url("https://fonts.googleapis.com/css?family=Ubuntu:400,700|Cabin+Condensed:400,600");
/* LESS - http://lesscss.org style sheet */
/* Palette color codes */ body {
/* Palette URL: http://paletton.com/#uid=31g0q0kHZAviRSkrHLOGomVNzac */ background-color: #FFF;
/* Feel free to copy&paste color codes to your application */ margin: 0px;
/* MIXINS */ font-family: Ubuntu, sans-serif;
/* As hex codes */ color: #1e1e1e;
/* Main Primary color */ font-weight: normal;
/* Main Secondary color (1) */ padding-top: 0;
/* Main Secondary color (2) */ }
/* As RGBa codes */
/* Main Primary color */ h1, h2, h3, h4 {
/* Main Secondary color (1) */ font-family: "Cabin Condensed", sans-serif;
/* Main Secondary color (2) */ }
/* Generated by Paletton.com ├é┬® 2002-2014 */
/* http://paletton.com */ header {
@import url("http://fonts.googleapis.com/css?family=Ubuntu:400,700|Cabin+Condensed:400,600"); background: #FFC600;
/* line 1, ../sass/_viewport.scss */ padding: 1em;
}
header .headline {
max-width: 640px;
margin: 0 auto;
}
header .headline h1 {
color: #FFDD69;
font-size: 3em;
margin-bottom: 0;
}
header .headline h2 {
margin-top: 0.2em;
}
footer {
background: #0A4DB7;
color: #6C9CE8;
padding: 1em 2em 2em;
}
#container {
width: 640px;
margin: 20px auto;
padding: 10px;
}
#interactive.viewport { #interactive.viewport {
width: 640px; width: 640px;
height: 480px; height: 480px;
} }
/* line 6, ../sass/_viewport.scss */
#interactive.viewport canvas, video { #interactive.viewport canvas, video {
float: left; float: left;
width: 640px; width: 640px;
height: 480px; height: 480px;
} }
/* line 10, ../sass/_viewport.scss */
#interactive.viewport canvas.drawingBuffer, video.drawingBuffer { #interactive.viewport canvas.drawingBuffer, video.drawingBuffer {
margin-left: -640px; margin-left: -640px;
} }
/* line 16, ../sass/_viewport.scss */
.controls fieldset { .controls fieldset {
border: none; border: none;
margin: 0;
padding: 0;
} }
/* line 19, ../sass/_viewport.scss */
.controls .input-group { .controls .input-group {
float: left; float: left;
} }
/* line 21, ../sass/_viewport.scss */
.controls .input-group input, .controls .input-group button { .controls .input-group input, .controls .input-group button {
display: block; display: block;
} }
/* line 25, ../sass/_viewport.scss */
.controls .reader-config-group { .controls .reader-config-group {
float: right; float: right;
} }
/* line 28, ../sass/_viewport.scss */
.controls .reader-config-group label { .controls .reader-config-group label {
display: block; display: block;
} }
/* line 30, ../sass/_viewport.scss */
.controls .reader-config-group label span { .controls .reader-config-group label span {
width: 11rem; width: 9rem;
display: inline-block; display: inline-block;
text-align: right; text-align: right;
} }
/* line 37, ../sass/_viewport.scss */
.controls:after { .controls:after {
content: ''; content: '';
display: block; display: block;
clear: both; clear: both;
} }
/* line 44, ../sass/_viewport.scss */
#result_strip { #result_strip {
margin: 10px 0; margin: 10px 0;
border-top: 1px solid #EEE; border-top: 1px solid #EEE;
border-bottom: 1px solid #EEE; border-bottom: 1px solid #EEE;
padding: 10px 0; padding: 10px 0;
} }
/* line 50, ../sass/_viewport.scss */
#result_strip > ul { #result_strip > ul {
padding: 0; padding: 0;
margin: 0; margin: 0;
@ -83,41 +113,41 @@
overflow-y: hidden; overflow-y: hidden;
white-space: nowrap; white-space: nowrap;
} }
/* line 59, ../sass/_viewport.scss */
#result_strip > ul > li { #result_strip > ul > li {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
width: 160px; width: 160px;
} }
/* line 63, ../sass/_viewport.scss */
#result_strip > ul > li .thumbnail { #result_strip > ul > li .thumbnail {
padding: 5px; padding: 5px;
margin: 4px; margin: 4px;
border: 1px dashed #CCC; border: 1px dashed #CCC;
} }
/* line 68, ../sass/_viewport.scss */
#result_strip > ul > li .thumbnail img { #result_strip > ul > li .thumbnail img {
max-width: 140px; max-width: 140px;
} }
/* line 71, ../sass/_viewport.scss */
#result_strip > ul > li .thumbnail .caption { #result_strip > ul > li .thumbnail .caption {
white-space: normal; white-space: normal;
} }
/* line 73, ../sass/_viewport.scss */
#result_strip > ul > li .thumbnail .caption h4 { #result_strip > ul > li .thumbnail .caption h4 {
text-align: center; text-align: center;
word-wrap: break-word; word-wrap: break-word;
height: 40px; height: 40px;
margin: 0px; margin: 0px;
} }
/* line 83, ../sass/_viewport.scss */
#result_strip > ul:after { #result_strip > ul:after {
content: ""; content: "";
display: table; display: table;
clear: both; clear: both;
} }
/* line 7, ../sass/_overlay.scss */
.scanner-overlay { .scanner-overlay {
display: none; display: none;
width: 640px; width: 640px;
@ -133,21 +163,21 @@
-webkit-box-shadow: #333333 0px 4px 10px; -webkit-box-shadow: #333333 0px 4px 10px;
box-shadow: #333333 0px 4px 10px; box-shadow: #333333 0px 4px 10px;
} }
/* line 20, ../sass/_overlay.scss */
.scanner-overlay > .header { .scanner-overlay > .header {
position: relative; position: relative;
margin-bottom: 14px; margin-bottom: 14px;
} }
/* line 23, ../sass/_overlay.scss */
.scanner-overlay > .header h4, .scanner-overlay > .header .close { .scanner-overlay > .header h4, .scanner-overlay > .header .close {
line-height: 16px; line-height: 16px;
} }
/* line 26, ../sass/_overlay.scss */
.scanner-overlay > .header h4 { .scanner-overlay > .header h4 {
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
} }
/* line 30, ../sass/_overlay.scss */
.scanner-overlay > .header .close { .scanner-overlay > .header .close {
position: absolute; position: absolute;
right: 0px; right: 0px;
@ -160,7 +190,7 @@
cursor: pointer; cursor: pointer;
} }
/* line 1, ../sass/_icons.scss */
i.icon-24-scan { i.icon-24-scan {
width: 24px; width: 24px;
height: 24px; height: 24px;
@ -172,11 +202,8 @@ i.icon-24-scan {
vertical-align: text-top; vertical-align: text-top;
} }
@media (min-width: 604px) and (max-width: 1024px) {
/* tablet styles */
}
@media (max-width: 603px) { @media (max-width: 603px) {
/* line 2, ../sass/phone/_core.scss */
#container { #container {
width: 300px; width: 300px;
margin: 10px auto; margin: 10px auto;
@ -184,46 +211,58 @@ i.icon-24-scan {
-webkit-box-shadow: none; -webkit-box-shadow: none;
box-shadow: none; box-shadow: none;
} }
/* line 9, ../sass/phone/_core.scss */
#container form.voucher-form input.voucher-code { #container form.voucher-form input.voucher-code {
width: 180px; width: 180px;
} }
} }
@media (max-width: 603px) { @media (max-width: 603px) {
/* line 5, ../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 { #interactive.viewport {
width: 300px; width: 300px;
height: 300px; height: 300px;
overflow: hidden; overflow: hidden;
} }
/* line 11, ../sass/phone/_viewport.scss */
#interactive.viewport canvas, video { #interactive.viewport canvas, video {
margin-top: -50px; margin-top: -50px;
width: 300px; width: 300px;
height: 400px; height: 400px;
} }
/* line 15, ../sass/phone/_viewport.scss */
#interactive.viewport canvas.drawingBuffer, video.drawingBuffer { #interactive.viewport canvas.drawingBuffer, video.drawingBuffer {
margin-left: -300px; margin-left: -300px;
} }
/* line 20, ../sass/phone/_viewport.scss */
#result_strip { #result_strip {
margin-top: 5px; margin-top: 5px;
padding-top: 5px; padding-top: 5px;
} }
/* line 24, ../sass/phone/_viewport.scss */
#result_strip ul.thumbnails > li { #result_strip ul.thumbnails > li {
width: 150px; width: 150px;
} }
/* line 27, ../sass/phone/_viewport.scss */
#result_strip ul.thumbnails > li .thumbnail .imgWrapper { #result_strip ul.thumbnails > li .thumbnail .imgWrapper {
width: 130px; width: 130px;
height: 130px; height: 130px;
overflow: hidden; overflow: hidden;
} }
/* line 31, ../sass/phone/_viewport.scss */
#result_strip ul.thumbnails > li .thumbnail .imgWrapper img { #result_strip ul.thumbnails > li .thumbnail .imgWrapper img {
margin-top: -25px; margin-top: -25px;
width: 130px; width: 130px;
@ -231,7 +270,7 @@ i.icon-24-scan {
} }
} }
@media (max-width: 603px) { @media (max-width: 603px) {
/* line 8, ../sass/phone/_overlay.scss */
.overlay.scanner { .overlay.scanner {
width: 640px; width: 640px;
height: 510px; height: 510px;
@ -243,66 +282,17 @@ i.icon-24-scan {
-webkit-box-shadow: none; -webkit-box-shadow: none;
box-shadow: none; box-shadow: none;
} }
/* line 17, ../sass/phone/_overlay.scss */
.overlay.scanner > .header { .overlay.scanner > .header {
margin-bottom: 14px; margin-bottom: 14px;
} }
/* line 19, ../sass/phone/_overlay.scss */
.overlay.scanner > .header h4, .overlay.scanner > .header .close { .overlay.scanner > .header h4, .overlay.scanner > .header .close {
line-height: 16px; line-height: 16px;
} }
/* line 22, ../sass/phone/_overlay.scss */
.overlay.scanner > .header .close { .overlay.scanner > .header .close {
height: 16px; height: 16px;
width: 16px; width: 16px;
} }
} }
/* line 15, ../sass/styles.scss */
body {
background-color: #FFF;
margin: 0px;
font-family: Ubuntu, sans-serif;
color: #1e1e1e;
font-weight: normal;
padding-top: 0;
}
/* 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 {
width: 640px;
margin: 0 auto;
}
/* line 34, ../sass/styles.scss */
header .headline h1 {
color: #FFDD69;
font-size: 3em;
margin-bottom: 0;
}
/* line 39, ../sass/styles.scss */
header .headline h2 {
margin-top: 0.2em;
}
/* line 45, ../sass/styles.scss */
footer {
background: #0A4DB7;
color: #6C9CE8;
padding: 1em 2em 2em;
}
/* line 51, ../sass/styles.scss */
#container {
width: 640px;
margin: 20px auto;
padding: 10px;
}

@ -35,7 +35,7 @@
<div class="controls"> <div class="controls">
<fieldset class="input-group"> <fieldset class="input-group">
<input type="file" capture/> <input type="file" accept="image/*" capture="camera"/>
<button>Rerun</button> <button>Rerun</button>
</fieldset> </fieldset>
<fieldset class="reader-config-group"> <fieldset class="reader-config-group">
@ -46,19 +46,22 @@
<option value="code_39">Code 39</option> <option value="code_39">Code 39</option>
<option value="code_39_vin">Code 39 VIN</option> <option value="code_39_vin">Code 39 VIN</option>
<option value="ean">EAN</option> <option value="ean">EAN</option>
<option value="ean_extended">EAN-extended</option>
<option value="ean_8">EAN-8</option> <option value="ean_8">EAN-8</option>
<option value="upc">UPC</option> <option value="upc">UPC</option>
<option value="upc_e">UPC-E</option> <option value="upc_e">UPC-E</option>
<option value="codabar">Codabar</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> </select>
</label> </label>
<label> <label>
<span>Resolution (long side)</span> <span>Resolution (long side)</span>
<select name="input-stream_size"> <select name="input-stream_size">
<option value="320">320px</option> <option value="320">320px</option>
<option selected="selected" value="640">640px</option> <option value="640">640px</option>
<option value="800">800px</option> <option selected="selected" value="800">800px</option>
<option value="1280">1280px</option> <option value="1280">1280px</option>
<option value="1600">1600px</option> <option value="1600">1600px</option>
<option value="1920">1920px</option> <option value="1920">1920px</option>

@ -82,21 +82,37 @@ $(function() {
}, },
decoder: { decoder: {
readers: function(value) { readers: function(value) {
return [value + "_reader"]; if (value === 'ean_extended') {
return [{
format: "ean_reader",
config: {
supplements: [
'ean_5_reader', 'ean_2_reader'
]
}
}];
}
return [{
format: value + "_reader",
config: {}
}];
} }
} }
}, },
state: { state: {
inputStream: { inputStream: {
size: 640, size: 800,
singleChannel: false singleChannel: false
}, },
locator: { locator: {
patchSize: "large", patchSize: "medium",
halfSample: false halfSample: true
}, },
decoder: { decoder: {
readers: ["code_128_reader"] readers: [{
format: "code_128_reader",
config: {}
}]
}, },
locate: true, locate: true,
src: null src: null

@ -8,7 +8,7 @@
<meta name="description" content="" /> <meta name="description" content="" />
<meta name="author" content="Christoph Oberhofer" /> <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" /> <link rel="stylesheet" type="text/css" href="css/styles.css" />
</head> </head>
@ -38,15 +38,18 @@
<option value="code_39">Code 39</option> <option value="code_39">Code 39</option>
<option value="code_39_vin">Code 39 VIN</option> <option value="code_39_vin">Code 39 VIN</option>
<option value="ean">EAN</option> <option value="ean">EAN</option>
<option value="ean_extended">EAN-extended</option>
<option value="ean_8">EAN-8</option> <option value="ean_8">EAN-8</option>
<option value="upc">UPC</option> <option value="upc">UPC</option>
<option value="upc_e">UPC-E</option> <option value="upc_e">UPC-E</option>
<option value="codabar">Codabar</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> </select>
</label> </label>
<label> <label>
<span>Resolution (long side)</span> <span>Resolution (width)</span>
<select name="input-stream_constraints"> <select name="input-stream_constraints">
<option value="320x240">320px</option> <option value="320x240">320px</option>
<option selected="selected" value="640x480">640px</option> <option selected="selected" value="640x480">640px</option>
@ -80,6 +83,19 @@
<option value="8">8</option> <option value="8">8</option>
</select> </select>
</label> </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> </fieldset>
</div> </div>
<div id="result_strip"> <div id="result_strip">
@ -90,11 +106,12 @@
</section> </section>
<footer> <footer>
<p> <p>
&copy; Copyright by Christoph Oberhofer &copy; Made with ❤️ by Christoph Oberhofer
</p> </p>
</footer> </footer>
<script src="vendor/jquery-1.9.0.min.js" type="text/javascript"></script> <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="../dist/quagga.js" type="text/javascript"></script>
<script src="live_w_locator.js" type="text/javascript"></script> <script src="live_w_locator.js" type="text/javascript"></script>
</body> </body>

@ -2,7 +2,19 @@ $(function() {
var resultCollector = Quagga.ResultCollector.create({ var resultCollector = Quagga.ResultCollector.create({
capture: true, capture: true,
capacity: 20, capacity: 20,
blacklist: [{code: "2167361334", format: "i2of5"}], 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) { filter: function(codeResult) {
// only store results which match this constraint // only store results which match this constraint
// e.g.: codeResult // e.g.: codeResult
@ -10,24 +22,91 @@ $(function() {
} }
}); });
var App = { var App = {
init : function() { init: function() {
var self = this; var self = this;
Quagga.init(this.state, function(err) { Quagga.init(this.state, function(err) {
if (err) { if (err) {
return self.handleError(err); return self.handleError(err);
} }
Quagga.registerResultCollector(resultCollector); //Quagga.registerResultCollector(resultCollector);
App.attachListeners(); App.attachListeners();
App.checkCapabilities();
Quagga.start(); Quagga.start();
}); });
}, },
handleError: function(err) { handleError: function(err) {
console.log(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() { attachListeners: function() {
var self = this; var self = this;
self.initCameraSelection();
$(".controls").on("click", "button.stop", function(e) { $(".controls").on("click", "button.stop", function(e) {
e.preventDefault(); e.preventDefault();
Quagga.stop(); Quagga.stop();
@ -64,7 +143,11 @@ $(function() {
return parts.reduce(function(o, key, i) { return parts.reduce(function(o, key, i) {
if (setter && (i + 1) === depth) { 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] : {}; return key in o ? o[key] : {};
}, obj); }, obj);
@ -78,6 +161,17 @@ $(function() {
$(".controls").off("click", "button.stop"); $(".controls").off("click", "button.stop");
$(".controls .reader-config-group").off("change", "input, select"); $(".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) { setState: function(path, value) {
var self = this; var self = this;
@ -85,6 +179,10 @@ $(function() {
value = self._accessByPath(self.inputMapper, path)(value); value = self._accessByPath(self.inputMapper, path)(value);
} }
if (path.startsWith('settings.')) {
var setting = path.substring(9);
return self.applySetting(setting, value);
}
self._accessByPath(self.state, path, value); self._accessByPath(self.state, path, value);
console.log(JSON.stringify(self.state)); console.log(JSON.stringify(self.state));
@ -95,12 +193,16 @@ $(function() {
inputMapper: { inputMapper: {
inputStream: { inputStream: {
constraints: function(value){ constraints: function(value){
var values = value.split('x'); if (/^(\d+)x(\d+)$/.test(value)) {
return { var values = value.split('x');
width: parseInt(values[0]), return {
height: parseInt(values[1]), width: {min: parseInt(values[0])},
facing: "environment" height: {min: parseInt(values[1])}
};
} }
return {
deviceId: value
};
} }
}, },
numOfWorkers: function(value) { numOfWorkers: function(value) {
@ -108,7 +210,20 @@ $(function() {
}, },
decoder: { decoder: {
readers: function(value) { readers: function(value) {
return [value + "_reader"]; if (value === 'ean_extended') {
return [{
format: "ean_reader",
config: {
supplements: [
'ean_5_reader', 'ean_2_reader'
]
}
}];
}
return [{
format: value + "_reader",
config: {}
}];
} }
} }
}, },
@ -116,18 +231,23 @@ $(function() {
inputStream: { inputStream: {
type : "LiveStream", type : "LiveStream",
constraints: { constraints: {
width: 640, width: {min: 640},
height: 480, height: {min: 480},
facing: "environment" // or user facingMode: "environment",
aspectRatio: {min: 1, max: 2}
} }
}, },
locator: { locator: {
patchSize: "medium", patchSize: "medium",
halfSample: true halfSample: true
}, },
numOfWorkers: 4, numOfWorkers: 2,
frequency: 10,
decoder: { decoder: {
readers : [ "code_128_reader"] readers : [{
format: "code_128_reader",
config: {}
}]
}, },
locate: true locate: true
}, },

@ -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 +1,4 @@
var Quagga = require('../lib/quagga'); var Quagga = require('../lib/quagga').default;
Quagga.decodeSingle({ Quagga.decodeSingle({
src: "../test/fixtures/code_128/image-001.jpg", src: "../test/fixtures/code_128/image-001.jpg",

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

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

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

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

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

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

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

@ -1,55 +0,0 @@
/* LESS - http://lesscss.org style sheet */
/* Palette color codes */
/* Palette URL: http://paletton.com/#uid=31g0q0kHZAviRSkrHLOGomVNzac */
/* Feel free to copy&paste color codes to your application */
/* MIXINS */
/* As hex codes */
$color-primary-0: #FFC600; /* Main Primary color */
$color-primary-1: #FFDD69;
$color-primary-2: #FFCE22;
$color-primary-3: #B78E00;
$color-primary-4: #513F00;
$color-secondary-1-0: #0A4DB7; /* Main Secondary color (1) */
$color-secondary-1-1: #6C9CE8;
$color-secondary-1-2: #2E6FD6;
$color-secondary-1-3: #073379;
$color-secondary-1-4: #021636;
$color-secondary-2-0: #5809BB; /* Main Secondary color (2) */
$color-secondary-2-1: #A36BE9;
$color-secondary-2-2: #782DD8;
$color-secondary-2-3: #3A077C;
$color-secondary-2-4: #190237;
/* As RGBa codes */
$rgba-primary-0: rgba(255,198, 0,1); /* Main Primary color */
$rgba-primary-1: rgba(255,221,105,1);
$rgba-primary-2: rgba(255,206, 34,1);
$rgba-primary-3: rgba(183,142, 0,1);
$rgba-primary-4: rgba( 81, 63, 0,1);
$rgba-secondary-1-0: rgba( 10, 77,183,1); /* Main Secondary color (1) */
$rgba-secondary-1-1: rgba(108,156,232,1);
$rgba-secondary-1-2: rgba( 46,111,214,1);
$rgba-secondary-1-3: rgba( 7, 51,121,1);
$rgba-secondary-1-4: rgba( 2, 22, 54,1);
$rgba-secondary-2-0: rgba( 88, 9,187,1); /* Main Secondary color (2) */
$rgba-secondary-2-1: rgba(163,107,233,1);
$rgba-secondary-2-2: rgba(120, 45,216,1);
$rgba-secondary-2-3: rgba( 58, 7,124,1);
$rgba-secondary-2-4: rgba( 25, 2, 55,1);
/* Generated by Paletton.com © 2002-2014 */
/* http://paletton.com */

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

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

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

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

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

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

@ -39,11 +39,15 @@
<option value="code_39">Code 39</option> <option value="code_39">Code 39</option>
<option value="code_39_vin">Code 39 VIN</option> <option value="code_39_vin">Code 39 VIN</option>
<option value="ean">EAN</option> <option value="ean">EAN</option>
<option value="ean_extended">EAN-extended</option>
<option value="ean_8">EAN-8</option> <option value="ean_8">EAN-8</option>
<option value="upc">UPC</option> <option value="upc">UPC</option>
<option value="upc_e">UPC-E</option> <option value="upc_e">UPC-E</option>
<option value="codabar">Codabar</option> <option value="codabar">Codabar</option>
<option value="i2of5">I2of5</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> </select>
</fieldset> </fieldset>
</div> </div>

@ -1,7 +1,7 @@
$(function() { $(function() {
var App = { var App = {
init: function() { init: function() {
var config = this.config[this.state.decoder.readers[0]] || this.config.default; var config = this.config[this.state.decoder.readers[0].format] || this.config.default;
config = $.extend(true, {}, config, this.state); config = $.extend(true, {}, config, this.state);
Quagga.init(config, function() { Quagga.init(config, function() {
App.attachListeners(); App.attachListeners();
@ -95,7 +95,20 @@ $(function() {
inputMapper: { inputMapper: {
decoder: { decoder: {
readers: function(value) { readers: function(value) {
return [value + "_reader"]; if (value === 'ean_extended') {
return [{
format: "ean_reader",
config: {
supplements: [
'ean_5_reader', 'ean_2_reader'
]
}
}];
}
return [{
format: value + "_reader",
config: {}
}];
} }
}, },
inputStream: { inputStream: {
@ -109,7 +122,10 @@ $(function() {
src: "../test/fixtures/code_128/" src: "../test/fixtures/code_128/"
}, },
decoder : { decoder : {
readers : ["code_128_reader"] readers : [{
format: "code_128_reader",
config: {}
}]
} }
} }
}; };

@ -14,24 +14,22 @@ module.exports = function(config) {
'test/test-main-integration.js': ['webpack'] 'test/test-main-integration.js': ['webpack']
}, },
webpack: { webpack: {
entry: [
'./src/quagga.js'
],
module: { module: {
preLoaders: [ loaders: [{
{ test: /\.jsx?$/,
test: /\.js$/, exclude: /node_modules/,
exclude: [ loader: 'babel-loader'
/node_modules/ }]
],
loader: 'babel'
}
]
}, },
resolve: { resolve: {
extensions: ['', '.js', '.jsx'], modules: [
root: path.resolve(__dirname), path.resolve('./src/input/'),
alias: { path.resolve('./src/common/'),
'input_stream$': 'src/input/input_stream', 'node_modules'
'frame_grabber$': 'src/input/frame_grabber' ]
}
}, },
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
@ -42,7 +40,6 @@ module.exports = function(config) {
plugins: [ plugins: [
'karma-chrome-launcher', 'karma-chrome-launcher',
'karma-mocha', 'karma-mocha',
'karma-requirejs',
'karma-chai', 'karma-chai',
'karma-sinon', 'karma-sinon',
'karma-sinon-chai', 'karma-sinon-chai',
@ -51,7 +48,7 @@ module.exports = function(config) {
reporters: ['progress'], reporters: ['progress'],
port: 9876, port: 9876,
colors: true, colors: true,
logLevel: config.LOG_INFO, logLevel: config.LOG_INFO, // LOG_DEBUG
autoWatch: true, autoWatch: true,
browsers: ['Chrome'], browsers: ['Chrome'],
singleRun: false singleRun: false

@ -13,32 +13,28 @@ module.exports = function(config) {
'test/test-main.js': ['webpack'] 'test/test-main.js': ['webpack']
}, },
webpack: { webpack: {
entry: [
'./src/quagga.js'
],
module: { module: {
preLoaders: [ loaders: [{
{ test: /\.jsx?$/,
test: /\.js$/, exclude: [
exclude: [ path.resolve('node_modules/')
/node_modules/ ],
], loader: 'babel-loader'
loader: 'babel' }, {
}, test: /\.js$/,
{ include: path.resolve('src'),
test: /\.js$/, loader: 'babel-istanbul-loader'
include: [ }]
path.resolve('src')
],
exclude: /node_modules/,
loader: 'isparta'
}
]
}, },
resolve: { resolve: {
extensions: ['', '.js', '.jsx'], modules: [
root: path.resolve(__dirname), path.resolve('./src/input/'),
alias: { path.resolve('./test/mocks/'),
'input_stream$': 'src/input/input_stream', 'node_modules'
'frame_grabber$': 'src/input/frame_grabber' ]
}
}, },
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
@ -48,6 +44,7 @@ module.exports = function(config) {
}, },
plugins: [ plugins: [
'karma-chrome-launcher', 'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-coverage', 'karma-coverage',
'karma-mocha', 'karma-mocha',
'karma-chai', 'karma-chai',

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

File diff suppressed because one or more lines are too long

@ -1,48 +1,79 @@
{ {
"name": "quagga", "name": "quagga",
"version": "0.9.1", "version": "0.12.1",
"description": "An advanced barcode-scanner written in JavaScript", "description": "An advanced barcode-scanner written in JavaScript",
"main": "lib/quagga.js", "main": "lib/quagga.js",
"typings": "type-definitions/quagga.d.ts",
"browser": "dist/quagga.min.js", "browser": "dist/quagga.min.js",
"typings": "type-definitions/quagga.d.ts",
"devDependencies": { "devDependencies": {
"async": "^1.4.2", "async": "^1.4.2",
"babel-core": "^5.8.25", "babel-cli": "^6.5.1",
"babel-eslint": "^4.1.3", "babel-core": "^6.21.0",
"babel-loader": "^5.3.2", "babel-eslint": "^7.1.1",
"babel-istanbul": "^0.8.0",
"babel-istanbul-loader": "^0.1.0",
"babel-loader": "^6.2.10",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-check-es2015-constants": "^6.3.13",
"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.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",
"babel-plugin-transform-es2015-for-of": "^6.3.13",
"babel-plugin-transform-es2015-function-name": "^6.3.13",
"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.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",
"babel-plugin-transform-es2015-template-literals": "^6.3.13",
"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.21.0",
"chai": "^3.4.1", "chai": "^3.4.1",
"core-js": "^1.2.1", "core-js": "^2.4.1",
"cross-env": "^1.0.7", "cross-env": "^3.1.4",
"eslint": "^1.10.3", "eslint": "^1.10.3",
"grunt": "^0.4.5", "grunt": "^0.4.5",
"grunt-cli": "^0.1.13", "grunt-cli": "^0.1.13",
"grunt-contrib-nodeunit": "^0.4.1", "grunt-contrib-nodeunit": "^0.4.1",
"grunt-karma": "^0.12.1", "grunt-karma": "^2.0.0",
"isparta-loader": "^1.0.0", "isparta-loader": "^2.0.0",
"karma": "^0.13.9", "karma": "^1.3.0",
"karma-chai": "0.1.0", "karma-chai": "0.1.0",
"karma-chrome-launcher": "^0.2.0", "karma-chrome-launcher": "^2.0.0",
"karma-coverage": "^0.5.2", "karma-coverage": "^1.1.1",
"karma-firefox-launcher": "^0.1.7",
"karma-mocha": "~0.2.0", "karma-mocha": "~0.2.0",
"karma-phantomjs-launcher": "^0.2.1", "karma-phantomjs-launcher": "^0.2.1",
"karma-sinon": "^1.0.4", "karma-sinon": "^1.0.4",
"karma-sinon-chai": "^1.1.0", "karma-sinon-chai": "^1.1.0",
"karma-source-map-support": "^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", "mocha": "^2.3.2",
"phantomjs": "^1.9.18", "phantomjs": "^1.9.18",
"sinon": "^1.16.1", "sinon": "^1.17.7",
"webpack": "^1.12.2", "sinon-chai": "^2.8.0",
"webpack-core": "^0.6.8" "webpack": "^2.2.1",
"webpack-sources": "^0.1.4"
}, },
"directories": { "directories": {
"doc": "doc" "doc": "doc"
}, },
"scripts": { "scripts": {
"test": "grunt test", "test": "cross-env BABEL_ENV=commonjs grunt test",
"integrationtest": "grunt integrationtest", "integrationtest": "grunt integrationtest",
"build:dev": "cross-env BUILD_ENV=development webpack", "build:dev": "cross-env BUILD_ENV=development webpack",
"build:prod": "cross-env BUILD_ENV=production webpack --config webpack.config.min.js && grunt uglyasm", "build:prod": "cross-env BUILD_ENV=production webpack --config webpack.config.min.js && grunt uglyasm",
"build:node": "cross-env BUILD_ENV=node webpack --config webpack.node.config.js", "build:node": "cross-env BABEL_ENV=commonjs BUILD_ENV=node webpack --config webpack.node.config.js",
"build": "npm run build:dev && npm run build:prod && npm run build:node", "build": "npm run build:dev && npm run build:prod && npm run build:node",
"watch": "cross-env BUILD_ENV=development webpack --watch", "watch": "cross-env BUILD_ENV=development webpack --watch",
"lint": "eslint src" "lint": "eslint src"
@ -74,8 +105,10 @@
}, },
"dependencies": { "dependencies": {
"get-pixels": "^3.2.3", "get-pixels": "^3.2.3",
"gl-matrix": "^2.3.1", "gl-mat2": "^1.0.0",
"lodash": "^3.10.1", "gl-vec2": "^1.0.0",
"gl-vec3": "^1.0.3",
"lodash": "^4.17.4",
"ndarray": "^1.0.18", "ndarray": "^1.0.18",
"ndarray-linear-interpolate": "^1.0.0" "ndarray-linear-interpolate": "^1.0.0"
} }

@ -1,5 +1,5 @@
var ConcatSource = require("webpack-core/lib/ConcatSource"); var ConcatSource = require("webpack-sources").ConcatSource;
var OriginalSource = require("webpack-core/lib/OriginalSource"); var OriginalSource = require("webpack-sources").OriginalSource;
function MyUmdPlugin(options) { function MyUmdPlugin(options) {
this.name = options.library; this.name = options.library;
@ -13,11 +13,11 @@ MyUmdPlugin.prototype.apply = function(compiler) {
return new ConcatSource(new OriginalSource( return new ConcatSource(new OriginalSource(
"(function webpackUniversalModuleDefinition(root, factory) {\n" + "(function webpackUniversalModuleDefinition(root, factory) {\n" +
" if(typeof exports === 'object' && typeof module === 'object')\n" + " if(typeof exports === 'object' && typeof module === 'object')\n" +
" module.exports = factory(factory.toString());\n" + " module.exports = factory(factory.toString()).default;\n" +
" else if(typeof exports === 'object')\n" + " else if(typeof exports === 'object')\n" +
" exports[\"" + this.name + "\"] = factory(factory.toString());\n" + " exports[\"" + this.name + "\"] = factory(factory.toString()).default;\n" +
" else\n" + " else\n" +
" root[\"" + this.name + "\"] = factory(factory.toString());\n" + " root[\"" + this.name + "\"] = factory(factory.toString()).default;\n" +
"})(this, function(__factorySource__) {\nreturn ", "webpack/myModuleDefinition"), source, "\n});\n"); "})(this, function(__factorySource__) {\nreturn ", "webpack/myModuleDefinition"), source, "\n});\n");
}.bind(this)); }.bind(this));
mainTemplate.plugin("global-hash-paths", function(paths) { mainTemplate.plugin("global-hash-paths", function(paths) {

@ -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,4 +1,7 @@
import {vec2} from 'gl-matrix'; 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
*/ */

@ -1,15 +1,18 @@
import Cluster2 from './cluster'; import Cluster2 from './cluster';
import ArrayHelper from './array_helper'; import ArrayHelper from './array_helper';
import {vec2, vec3} from 'gl-matrix'; const vec2 = {
clone: require('gl-vec2/clone'),
var CVUtils = {}; };
const vec3 = {
clone: require('gl-vec3/clone'),
};
/** /**
* @param x x-coordinate * @param x x-coordinate
* @param y y-coordinate * @param y y-coordinate
* @return ImageReference {x,y} Coordinate * @return ImageReference {x,y} Coordinate
*/ */
CVUtils.imageRef = function(x, y) { export function imageRef(x, y) {
var that = { var that = {
x: x, x: x,
y: y, y: y,
@ -32,7 +35,7 @@ CVUtils.imageRef = function(x, y) {
* Computes an integral image of a given grayscale image. * Computes an integral image of a given grayscale image.
* @param imageDataContainer {ImageDataContainer} the image to be integrated * @param imageDataContainer {ImageDataContainer} the image to be integrated
*/ */
CVUtils.computeIntegralImage2 = function(imageWrapper, integralWrapper) { export function computeIntegralImage2(imageWrapper, integralWrapper) {
var imageData = imageWrapper.data; var imageData = imageWrapper.data;
var width = imageWrapper.size.x; var width = imageWrapper.size.x;
var height = imageWrapper.size.y; var height = imageWrapper.size.y;
@ -75,7 +78,7 @@ CVUtils.computeIntegralImage2 = function(imageWrapper, integralWrapper) {
} }
}; };
CVUtils.computeIntegralImage = function(imageWrapper, integralWrapper) { export function computeIntegralImage(imageWrapper, integralWrapper) {
var imageData = imageWrapper.data; var imageData = imageWrapper.data;
var width = imageWrapper.size.x; var width = imageWrapper.size.x;
var height = imageWrapper.size.y; var height = imageWrapper.size.y;
@ -97,7 +100,7 @@ CVUtils.computeIntegralImage = function(imageWrapper, integralWrapper) {
} }
}; };
CVUtils.thresholdImage = function(imageWrapper, threshold, targetWrapper) { export function thresholdImage(imageWrapper, threshold, targetWrapper) {
if (!targetWrapper) { if (!targetWrapper) {
targetWrapper = imageWrapper; targetWrapper = imageWrapper;
} }
@ -108,7 +111,7 @@ CVUtils.thresholdImage = function(imageWrapper, threshold, targetWrapper) {
} }
}; };
CVUtils.computeHistogram = function(imageWrapper, bitsPerPixel) { export function computeHistogram(imageWrapper, bitsPerPixel) {
if (!bitsPerPixel) { if (!bitsPerPixel) {
bitsPerPixel = 8; bitsPerPixel = 8;
} }
@ -124,7 +127,7 @@ CVUtils.computeHistogram = function(imageWrapper, bitsPerPixel) {
return hist; return hist;
}; };
CVUtils.sharpenLine = function(line) { export function sharpenLine(line) {
var i, var i,
length = line.length, length = line.length,
left = line[0], left = line[0],
@ -141,7 +144,7 @@ CVUtils.sharpenLine = function(line) {
return line; return line;
}; };
CVUtils.determineOtsuThreshold = function(imageWrapper, bitsPerPixel) { export function determineOtsuThreshold(imageWrapper, bitsPerPixel) {
if (!bitsPerPixel) { if (!bitsPerPixel) {
bitsPerPixel = 8; bitsPerPixel = 8;
} }
@ -171,7 +174,7 @@ CVUtils.determineOtsuThreshold = function(imageWrapper, bitsPerPixel) {
var vet = [0], p1, p2, p12, k, m1, m2, m12, var vet = [0], p1, p2, p12, k, m1, m2, m12,
max = (1 << bitsPerPixel) - 1; max = (1 << bitsPerPixel) - 1;
hist = CVUtils.computeHistogram(imageWrapper, bitsPerPixel); hist = computeHistogram(imageWrapper, bitsPerPixel);
for ( k = 1; k < max; k++) { for ( k = 1; k < max; k++) {
p1 = px(0, k); p1 = px(0, k);
p2 = px(k + 1, max); p2 = px(k + 1, max);
@ -191,16 +194,16 @@ CVUtils.determineOtsuThreshold = function(imageWrapper, bitsPerPixel) {
return threshold << bitShift; return threshold << bitShift;
}; };
CVUtils.otsuThreshold = function(imageWrapper, targetWrapper) { export function otsuThreshold(imageWrapper, targetWrapper) {
var threshold = CVUtils.determineOtsuThreshold(imageWrapper); var threshold = determineOtsuThreshold(imageWrapper);
CVUtils.thresholdImage(imageWrapper, threshold, targetWrapper); thresholdImage(imageWrapper, threshold, targetWrapper);
return threshold; return threshold;
}; };
// local thresholding // local thresholding
CVUtils.computeBinaryImage = function(imageWrapper, integralWrapper, targetWrapper) { export function computeBinaryImage(imageWrapper, integralWrapper, targetWrapper) {
CVUtils.computeIntegralImage(imageWrapper, integralWrapper); computeIntegralImage(imageWrapper, integralWrapper);
if (!targetWrapper) { if (!targetWrapper) {
targetWrapper = imageWrapper; targetWrapper = imageWrapper;
@ -241,7 +244,7 @@ CVUtils.computeBinaryImage = function(imageWrapper, integralWrapper, targetWrapp
} }
}; };
CVUtils.cluster = function(points, threshold, property) { export function cluster(points, threshold, property) {
var i, k, cluster, point, clusters = []; var i, k, cluster, point, clusters = [];
if (!property) { if (!property) {
@ -270,7 +273,7 @@ CVUtils.cluster = function(points, threshold, property) {
return clusters; return clusters;
}; };
CVUtils.Tracer = { export const Tracer = {
trace: function(points, vec) { trace: function(points, vec) {
var iteration, maxIterations = 10, top = [], result = [], centerPos = 0, currentPos = 0; var iteration, maxIterations = 10, top = [], result = [], centerPos = 0, currentPos = 0;
@ -340,10 +343,10 @@ CVUtils.Tracer = {
} }
}; };
CVUtils.DILATE = 1; export const DILATE = 1;
CVUtils.ERODE = 2; export const ERODE = 2;
CVUtils.dilate = function(inImageWrapper, outImageWrapper) { export function dilate(inImageWrapper, outImageWrapper) {
var v, var v,
u, u,
inImageData = inImageWrapper.data, inImageData = inImageWrapper.data,
@ -370,7 +373,7 @@ CVUtils.dilate = function(inImageWrapper, outImageWrapper) {
} }
}; };
CVUtils.erode = function(inImageWrapper, outImageWrapper) { export function erode(inImageWrapper, outImageWrapper) {
var v, var v,
u, u,
inImageData = inImageWrapper.data, inImageData = inImageWrapper.data,
@ -397,7 +400,7 @@ CVUtils.erode = function(inImageWrapper, outImageWrapper) {
} }
}; };
CVUtils.subtract = function(aImageWrapper, bImageWrapper, resultImageWrapper) { export function subtract(aImageWrapper, bImageWrapper, resultImageWrapper) {
if (!resultImageWrapper) { if (!resultImageWrapper) {
resultImageWrapper = aImageWrapper; resultImageWrapper = aImageWrapper;
} }
@ -411,7 +414,7 @@ CVUtils.subtract = function(aImageWrapper, bImageWrapper, resultImageWrapper) {
} }
}; };
CVUtils.bitwiseOr = function(aImageWrapper, bImageWrapper, resultImageWrapper) { export function bitwiseOr(aImageWrapper, bImageWrapper, resultImageWrapper) {
if (!resultImageWrapper) { if (!resultImageWrapper) {
resultImageWrapper = aImageWrapper; resultImageWrapper = aImageWrapper;
} }
@ -425,7 +428,7 @@ CVUtils.bitwiseOr = function(aImageWrapper, bImageWrapper, resultImageWrapper) {
} }
}; };
CVUtils.countNonZero = function(imageWrapper) { export function countNonZero(imageWrapper) {
var length = imageWrapper.data.length, data = imageWrapper.data, sum = 0; var length = imageWrapper.data.length, data = imageWrapper.data, sum = 0;
while (length--) { while (length--) {
@ -434,7 +437,7 @@ CVUtils.countNonZero = function(imageWrapper) {
return sum; return sum;
}; };
CVUtils.topGeneric = function(list, top, scoreFunc) { export function topGeneric(list, top, scoreFunc) {
var i, minIdx = 0, min = 0, queue = [], score, hit, pos; var i, minIdx = 0, min = 0, queue = [], score, hit, pos;
for ( i = 0; i < top; i++) { for ( i = 0; i < top; i++) {
@ -463,18 +466,18 @@ CVUtils.topGeneric = function(list, top, scoreFunc) {
return queue; return queue;
}; };
CVUtils.grayArrayFromImage = function(htmlImage, offsetX, ctx, array) { export function grayArrayFromImage(htmlImage, offsetX, ctx, array) {
ctx.drawImage(htmlImage, offsetX, 0, htmlImage.width, htmlImage.height); ctx.drawImage(htmlImage, offsetX, 0, htmlImage.width, htmlImage.height);
var ctxData = ctx.getImageData(offsetX, 0, htmlImage.width, htmlImage.height).data; var ctxData = ctx.getImageData(offsetX, 0, htmlImage.width, htmlImage.height).data;
CVUtils.computeGray(ctxData, array); computeGray(ctxData, array);
}; };
CVUtils.grayArrayFromContext = function(ctx, size, offset, array) { export function grayArrayFromContext(ctx, size, offset, array) {
var ctxData = ctx.getImageData(offset.x, offset.y, size.x, size.y).data; var ctxData = ctx.getImageData(offset.x, offset.y, size.x, size.y).data;
CVUtils.computeGray(ctxData, array); computeGray(ctxData, array);
}; };
CVUtils.grayAndHalfSampleFromCanvasData = function(canvasData, size, outArray) { export function grayAndHalfSampleFromCanvasData(canvasData, size, outArray) {
var topRowIdx = 0; var topRowIdx = 0;
var bottomRowIdx = size.x; var bottomRowIdx = size.x;
var endIdx = Math.floor(canvasData.length / 4); var endIdx = Math.floor(canvasData.length / 4);
@ -485,7 +488,7 @@ CVUtils.grayAndHalfSampleFromCanvasData = function(canvasData, size, outArray) {
while (bottomRowIdx < endIdx) { while (bottomRowIdx < endIdx) {
for ( i = 0; i < outWidth; i++) { for ( i = 0; i < outWidth; i++) {
outArray[outImgIdx] = Math.floor(( outArray[outImgIdx] = (
(0.299 * canvasData[topRowIdx * 4 + 0] + (0.299 * canvasData[topRowIdx * 4 + 0] +
0.587 * canvasData[topRowIdx * 4 + 1] + 0.587 * canvasData[topRowIdx * 4 + 1] +
0.114 * canvasData[topRowIdx * 4 + 2]) + 0.114 * canvasData[topRowIdx * 4 + 2]) +
@ -497,7 +500,7 @@ CVUtils.grayAndHalfSampleFromCanvasData = function(canvasData, size, outArray) {
0.114 * canvasData[(bottomRowIdx) * 4 + 2]) + 0.114 * canvasData[(bottomRowIdx) * 4 + 2]) +
(0.299 * canvasData[(bottomRowIdx + 1) * 4 + 0] + (0.299 * canvasData[(bottomRowIdx + 1) * 4 + 0] +
0.587 * canvasData[(bottomRowIdx + 1) * 4 + 1] + 0.587 * canvasData[(bottomRowIdx + 1) * 4 + 1] +
0.114 * canvasData[(bottomRowIdx + 1) * 4 + 2])) / 4); 0.114 * canvasData[(bottomRowIdx + 1) * 4 + 2])) / 4;
outImgIdx++; outImgIdx++;
topRowIdx = topRowIdx + 2; topRowIdx = topRowIdx + 2;
bottomRowIdx = bottomRowIdx + 2; bottomRowIdx = bottomRowIdx + 2;
@ -507,7 +510,7 @@ CVUtils.grayAndHalfSampleFromCanvasData = function(canvasData, size, outArray) {
} }
}; };
CVUtils.computeGray = function(imageData, outArray, config) { export function computeGray(imageData, outArray, config) {
var l = (imageData.length / 4) | 0, var l = (imageData.length / 4) | 0,
i, i,
singleChannel = config && config.singleChannel === true; singleChannel = config && config.singleChannel === true;
@ -518,13 +521,13 @@ CVUtils.computeGray = function(imageData, outArray, config) {
} }
} else { } else {
for (i = 0; i < l; i++) { for (i = 0; i < l; i++) {
outArray[i] = Math.floor( outArray[i] =
0.299 * imageData[i * 4 + 0] + 0.587 * imageData[i * 4 + 1] + 0.114 * imageData[i * 4 + 2]); 0.299 * imageData[i * 4 + 0] + 0.587 * imageData[i * 4 + 1] + 0.114 * imageData[i * 4 + 2];
} }
} }
}; };
CVUtils.loadImageArray = function(src, callback, canvas) { export function loadImageArray(src, callback, canvas) {
if (!canvas) { if (!canvas) {
canvas = document.createElement('canvas'); canvas = document.createElement('canvas');
} }
@ -538,7 +541,7 @@ CVUtils.loadImageArray = function(src, callback, canvas) {
var array = new Uint8Array(this.width * this.height); var array = new Uint8Array(this.width * this.height);
ctx.drawImage(this, 0, 0); ctx.drawImage(this, 0, 0);
var data = ctx.getImageData(0, 0, this.width, this.height).data; var data = ctx.getImageData(0, 0, this.width, this.height).data;
CVUtils.computeGray(data, array); computeGray(data, array);
this.callback(array, { this.callback(array, {
x: this.width, x: this.width,
y: this.height y: this.height
@ -551,7 +554,7 @@ CVUtils.loadImageArray = function(src, callback, canvas) {
* @param inImg {ImageWrapper} input image to be sampled * @param inImg {ImageWrapper} input image to be sampled
* @param outImg {ImageWrapper} to be stored in * @param outImg {ImageWrapper} to be stored in
*/ */
CVUtils.halfSample = function(inImgWrapper, outImgWrapper) { export function halfSample(inImgWrapper, outImgWrapper) {
var inImg = inImgWrapper.data; var inImg = inImgWrapper.data;
var inWidth = inImgWrapper.size.x; var inWidth = inImgWrapper.size.x;
var outImg = outImgWrapper.data; var outImg = outImgWrapper.data;
@ -573,7 +576,7 @@ CVUtils.halfSample = function(inImgWrapper, outImgWrapper) {
} }
}; };
CVUtils.hsv2rgb = function(hsv, rgb) { export function hsv2rgb(hsv, rgb) {
var h = hsv[0], var h = hsv[0],
s = hsv[1], s = hsv[1],
v = hsv[2], v = hsv[2],
@ -611,7 +614,7 @@ CVUtils.hsv2rgb = function(hsv, rgb) {
return rgb; return rgb;
}; };
CVUtils._computeDivisors = function(n) { export function _computeDivisors(n) {
var largeDivisors = [], var largeDivisors = [],
divisors = [], divisors = [],
i; i;
@ -627,7 +630,7 @@ CVUtils._computeDivisors = function(n) {
return divisors.concat(largeDivisors); return divisors.concat(largeDivisors);
}; };
CVUtils._computeIntersection = function(arr1, arr2) { function _computeIntersection(arr1, arr2) {
var i = 0, var i = 0,
j = 0, j = 0,
result = []; result = [];
@ -646,11 +649,11 @@ CVUtils._computeIntersection = function(arr1, arr2) {
return result; return result;
}; };
CVUtils.calculatePatchSize = function(patchSize, imgSize) { export function calculatePatchSize(patchSize, imgSize) {
var divisorsX = this._computeDivisors(imgSize.x), var divisorsX = _computeDivisors(imgSize.x),
divisorsY = this._computeDivisors(imgSize.y), divisorsY = _computeDivisors(imgSize.y),
wideSide = Math.max(imgSize.x, imgSize.y), wideSide = Math.max(imgSize.x, imgSize.y),
common = this._computeIntersection(divisorsX, divisorsY), common = _computeIntersection(divisorsX, divisorsY),
nrOfPatchesList = [8, 10, 15, 20, 32, 60, 80], nrOfPatchesList = [8, 10, 15, 20, 32, 60, 80],
nrOfPatchesMap = { nrOfPatchesMap = {
"x-small": 5, "x-small": 5,
@ -687,15 +690,15 @@ CVUtils.calculatePatchSize = function(patchSize, imgSize) {
optimalPatchSize = findPatchSizeForDivisors(common); optimalPatchSize = findPatchSizeForDivisors(common);
if (!optimalPatchSize) { if (!optimalPatchSize) {
optimalPatchSize = findPatchSizeForDivisors(this._computeDivisors(wideSide)); optimalPatchSize = findPatchSizeForDivisors(_computeDivisors(wideSide));
if (!optimalPatchSize) { if (!optimalPatchSize) {
optimalPatchSize = findPatchSizeForDivisors((this._computeDivisors(desiredPatchSize * nrOfPatches))); optimalPatchSize = findPatchSizeForDivisors((_computeDivisors(desiredPatchSize * nrOfPatches)));
} }
} }
return optimalPatchSize; return optimalPatchSize;
}; };
CVUtils._parseCSSDimensionValues = function(value) { export function _parseCSSDimensionValues(value) {
var dimension = { var dimension = {
value: parseFloat(value), value: parseFloat(value),
unit: value.indexOf("%") === value.length - 1 ? "%" : "%" unit: value.indexOf("%") === value.length - 1 ? "%" : "%"
@ -704,7 +707,7 @@ CVUtils._parseCSSDimensionValues = function(value) {
return dimension; return dimension;
}; };
CVUtils._dimensionsConverters = { export const _dimensionsConverters = {
top: function(dimension, context) { top: function(dimension, context) {
if (dimension.unit === "%") { if (dimension.unit === "%") {
return Math.floor(context.height * (dimension.value / 100)); return Math.floor(context.height * (dimension.value / 100));
@ -727,13 +730,13 @@ CVUtils._dimensionsConverters = {
} }
}; };
CVUtils.computeImageArea = function(inputWidth, inputHeight, area) { export function computeImageArea(inputWidth, inputHeight, area) {
var context = {width: inputWidth, height: inputHeight}; var context = {width: inputWidth, height: inputHeight};
var parsedArea = Object.keys(area).reduce(function(result, key) { var parsedArea = Object.keys(area).reduce(function(result, key) {
var value = area[key], var value = area[key],
parsed = CVUtils._parseCSSDimensionValues(value), parsed = _parseCSSDimensionValues(value),
calculated = CVUtils._dimensionsConverters[key](parsed, context); calculated = _dimensionsConverters[key](parsed, context);
result[key] = calculated; result[key] = calculated;
return result; return result;
@ -746,5 +749,3 @@ CVUtils.computeImageArea = function(inputWidth, inputHeight, area) {
sh: parsedArea.bottom - parsedArea.top sh: parsedArea.bottom - parsedArea.top
}; };
}; };
export default CVUtils;

@ -50,10 +50,22 @@ export default (function() {
var event = getEvent(eventName), var event = getEvent(eventName),
subscribers = event.subscribers; subscribers = event.subscribers;
event.subscribers = subscribers.filter(function(subscriber) { // Publish one-time subscriptions
subscribers.filter(function(subscriber) {
return !!subscriber.once;
}).forEach((subscriber) => {
publishSubscription(subscriber, data); publishSubscription(subscriber, data);
});
// remove them from the subscriber
event.subscribers = subscribers.filter(function(subscriber) {
return !subscriber.once; return !subscriber.once;
}); });
// publish the rest
event.subscribers.forEach((subscriber) => {
publishSubscription(subscriber, data);
});
}, },
once: function(event, callback, async) { once: function(event, callback, async) {
subscribe(event, { subscribe(event, {

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

@ -1,7 +1,9 @@
import SubImage from './subImage'; import SubImage from './subImage';
import CVUtils from '../common/cv_utils'; import {hsv2rgb} from '../common/cv_utils';
import ArrayHelper from '../common/array_helper'; import ArrayHelper from '../common/array_helper';
import {vec2} from 'gl-matrix'; const vec2 = {
clone: require('gl-vec2/clone'),
};
/** /**
* Represents a basic image combining the data and size. * Represents a basic image combining the data and size.
@ -335,7 +337,7 @@ ImageWrapper.prototype.overlay = function(canvas, scale, from) {
var length = this.data.length; var length = this.data.length;
while (length--) { while (length--) {
hsv[0] = this.data[length] * scale; hsv[0] = this.data[length] * scale;
result = hsv[0] <= 0 ? whiteRgb : hsv[0] >= 360 ? blackRgb : CVUtils.hsv2rgb(hsv, rgb); result = hsv[0] <= 0 ? whiteRgb : hsv[0] >= 360 ? blackRgb : hsv2rgb(hsv, rgb);
data[length * 4 + 0] = result[0]; data[length * 4 + 0] = result[0];
data[length * 4 + 1] = result[1]; data[length * 4 + 1] = result[1];
data[length * 4 + 2] = result[2]; data[length * 4 + 2] = result[2];

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

@ -14,10 +14,6 @@ if (typeof window !== 'undefined') {
window.setTimeout(callback, 1000 / 60); window.setTimeout(callback, 1000 / 60);
}; };
})(); })();
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
} }
Math.imul = Math.imul || function(a, b) { Math.imul = Math.imul || function(a, b) {
var ah = (a >>> 16) & 0xffff, var ah = (a >>> 16) & 0xffff,
@ -28,3 +24,28 @@ Math.imul = Math.imul || function(a, b) {
// the final |0 converts the unsigned value into a signed value // the final |0 converts the unsigned value into a signed value
return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0) | 0); 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;
};
}

@ -5,9 +5,9 @@ module.exports = {
constraints: { constraints: {
width: 640, width: 640,
height: 480, height: 480,
minAspectRatio: 0, // aspectRatio: 640/480, // optional
maxAspectRatio: 100, facingMode: "environment", // or user
facing: "environment" // or user // deviceId: "38745983457387598375983759834"
}, },
area: { area: {
top: "0%", top: "0%",

@ -5,9 +5,9 @@ module.exports = {
constraints: { constraints: {
width: 640, width: 640,
height: 480, height: 480,
minAspectRatio: 0, // aspectRatio: 640/480, // optional
maxAspectRatio: 100, facingMode: "environment", // or user
facing: "environment" // or user // deviceId: "38745983457387598375983759834"
}, },
area: { area: {
top: "0%", top: "0%",

@ -7,19 +7,27 @@ import Code39VINReader from '../reader/code_39_vin_reader';
import CodabarReader from '../reader/codabar_reader'; import CodabarReader from '../reader/codabar_reader';
import UPCReader from '../reader/upc_reader'; import UPCReader from '../reader/upc_reader';
import EAN8Reader from '../reader/ean_8_reader'; import EAN8Reader from '../reader/ean_8_reader';
import EAN2Reader from '../reader/ean_2_reader';
import EAN5Reader from '../reader/ean_5_reader';
import UPCEReader from '../reader/upc_e_reader'; import UPCEReader from '../reader/upc_e_reader';
import I2of5Reader from '../reader/i2of5_reader'; import I2of5Reader from '../reader/i2of5_reader';
import TwoOfFiveReader from '../reader/2of5_reader';
import Code93Reader from '../reader/code_93_reader';
const READERS = { const READERS = {
code_128_reader: Code128Reader, code_128_reader: Code128Reader,
ean_reader: EANReader, ean_reader: EANReader,
ean_5_reader: EAN5Reader,
ean_2_reader: EAN2Reader,
ean_8_reader: EAN8Reader, ean_8_reader: EAN8Reader,
code_39_reader: Code39Reader, code_39_reader: Code39Reader,
code_39_vin_reader: Code39VINReader, code_39_vin_reader: Code39VINReader,
codabar_reader: CodabarReader, codabar_reader: CodabarReader,
upc_reader: UPCReader, upc_reader: UPCReader,
upc_e_reader: UPCEReader, upc_e_reader: UPCEReader,
i2of5_reader: I2of5Reader i2of5_reader: I2of5Reader,
'2of5_reader': TwoOfFiveReader,
code_93_reader: Code93Reader
}; };
export default { export default {
create: function(config, inputImageWrapper) { create: function(config, inputImageWrapper) {
@ -74,7 +82,8 @@ export default {
function initReaders() { function initReaders() {
config.readers.forEach(function(readerConfig) { config.readers.forEach(function(readerConfig) {
var reader, var reader,
configuration = {}; configuration = {},
supplements = [];
if (typeof readerConfig === 'object') { if (typeof readerConfig === 'object') {
reader = readerConfig.format; reader = readerConfig.format;
@ -85,7 +94,13 @@ export default {
if (ENV.development) { if (ENV.development) {
console.log("Before registering reader: ", reader); console.log("Before registering reader: ", reader);
} }
_barcodeReaders.push(new READERS[reader](configuration)); if (configuration.supplements) {
supplements = configuration
.supplements.map((supplement) => {
return new READERS[supplement]();
});
}
_barcodeReaders.push(new READERS[reader](configuration, supplements));
}); });
if (ENV.development) { if (ENV.development) {
console.log("Registered Readers: " + _barcodeReaders console.log("Registered Readers: " + _barcodeReaders

@ -1,4 +1,3 @@
import CVUtils from '../common/cv_utils';
import ImageWrapper from '../common/image_wrapper'; import ImageWrapper from '../common/image_wrapper';
var Bresenham = {}; var Bresenham = {};
@ -90,20 +89,6 @@ Bresenham.getBarcodeLine = function(imageWrapper, p1, p2) {
}; };
}; };
Bresenham.toOtsuBinaryLine = function(result) {
var line = result.line,
image = new ImageWrapper({x: line.length - 1, y: 1}, line),
threshold = CVUtils.determineOtsuThreshold(image, 5);
line = CVUtils.sharpenLine(line);
CVUtils.thresholdImage(image, threshold);
return {
line: line,
threshold: threshold
};
};
/** /**
* Converts the result from getBarcodeLine into a binary representation * Converts the result from getBarcodeLine into a binary representation
* also considering the frequency and slope of the signal for more robust results * also considering the frequency and slope of the signal for more robust results

@ -1,45 +1,34 @@
const merge = require('lodash/object/merge'); import {omit, pick} from 'lodash';
import {getUserMedia, enumerateDevices} from 'mediaDevices';
var streamRef, const facingMatching = {
loadedDataHandler; "user": /front/i,
"environment": /back/i
};
/** var streamRef;
* Wraps browser-specific getUserMedia
* @param {Object} constraints
* @param {Object} success Callback
* @param {Object} failure Callback
*/
function getUserMedia(constraints, success, failure) {
if (typeof navigator.getUserMedia !== 'undefined') {
navigator.getUserMedia(constraints, function (stream) {
streamRef = stream;
var videoSrc = (window.URL && window.URL.createObjectURL(stream)) || stream;
success.apply(null, [videoSrc]);
}, failure);
} else {
failure(new TypeError("getUserMedia not available"));
}
}
function loadedData(video, callback) { function waitForVideo(video) {
var attempts = 10; return new Promise((resolve, reject) => {
let attempts = 10;
function checkVideo() { function checkVideo() {
if (attempts > 0) { if (attempts > 0) {
if (video.videoWidth > 0 && video.videoHeight > 0) { if (video.videoWidth > 10 && video.videoHeight > 10) {
if (ENV.development) { if (ENV.development) {
console.log(video.videoWidth + "px x " + video.videoHeight + "px"); console.log(video.videoWidth + "px x " + video.videoHeight + "px");
}
resolve();
} else {
window.setTimeout(checkVideo, 500);
} }
callback();
} else { } else {
window.setTimeout(checkVideo, 500); reject('Unable to play video stream. Is webcam working?');
} }
} else { attempts--;
callback('Unable to play video stream. Is webcam working?');
} }
attempts--; checkVideo();
} });
checkVideo();
} }
/** /**
@ -47,95 +36,84 @@ function loadedData(video, callback) {
* and calls the callback function when the content is ready * and calls the callback function when the content is ready
* @param {Object} constraints * @param {Object} constraints
* @param {Object} video * @param {Object} video
* @param {Object} callback
*/ */
function initCamera(constraints, video, callback) { function initCamera(video, constraints) {
getUserMedia(constraints, function(src) { return getUserMedia(constraints)
video.src = src; .then((stream) => {
if (loadedDataHandler) { return new Promise((resolve) => {
video.removeEventListener("loadeddata", loadedDataHandler, false); streamRef = stream;
} video.setAttribute("autoplay", true);
loadedDataHandler = loadedData.bind(null, video, callback); video.setAttribute('muted', true);
video.addEventListener('loadeddata', loadedDataHandler, false); video.setAttribute('playsinline', true);
video.play(); video.srcObject = stream;
}, function(e) { video.addEventListener('loadedmetadata', () => {
callback(e); video.play();
}); resolve();
});
});
})
.then(waitForVideo.bind(null, video));
} }
/** function deprecatedConstraints(videoConstraints) {
* Normalizes the incoming constraints to satisfy the current browser const normalized = pick(videoConstraints, ["width", "height", "facingMode",
* @param config "aspectRatio", "deviceId"]);
* @param cb Callback which is called whenever constraints are created
* @returns {*}
*/
function normalizeConstraints(config, cb) {
var constraints = {
audio: false,
video: true
},
videoConstraints = merge({
width: 640,
height: 480,
minAspectRatio: 0,
maxAspectRatio: 100,
facing: "environment"
}, config);
if ( typeof MediaStreamTrack !== 'undefined' && typeof MediaStreamTrack.getSources !== 'undefined') { if (typeof videoConstraints.minAspectRatio !== 'undefined' &&
MediaStreamTrack.getSources(function(sourceInfos) { videoConstraints.minAspectRatio > 0) {
var videoSourceId; normalized.aspectRatio = videoConstraints.minAspectRatio;
for (var i = 0; i < sourceInfos.length; ++i) { console.log("WARNING: Constraint 'minAspectRatio' is deprecated; Use 'aspectRatio' instead");
var sourceInfo = sourceInfos[i]; }
if (sourceInfo.kind === "video" && sourceInfo.facing === videoConstraints.facing) { if (typeof videoConstraints.facing !== 'undefined') {
videoSourceId = sourceInfo.id; normalized.facingMode = videoConstraints.facing;
} console.log("WARNING: Constraint 'facing' is deprecated. Use 'facingMode' instead'");
}
constraints.video = {
mandatory: {
minWidth: videoConstraints.width,
minHeight: videoConstraints.height,
minAspectRatio: videoConstraints.minAspectRatio,
maxAspectRatio: videoConstraints.maxAspectRatio
},
optional: [{
sourceId: videoSourceId
}]
};
return cb(constraints);
});
} else {
constraints.video = {
mediaSource: "camera",
width: { min: videoConstraints.width, max: videoConstraints.width },
height: { min: videoConstraints.height, max: videoConstraints.height },
require: ["width", "height"]
};
return cb(constraints);
} }
return normalized;
} }
/** export function pickConstraints(videoConstraints) {
* Requests the back-facing camera of the user. The callback is called const normalizedConstraints = {
* whenever the stream is ready to be consumed, or if an error occures. audio: false,
* @param {Object} video video: deprecatedConstraints(videoConstraints)
* @param {Object} callback };
*/
function request(video, videoConstraints, callback) { if (normalizedConstraints.video.deviceId
normalizeConstraints(videoConstraints, function(constraints) { && normalizedConstraints.video.facingMode) {
initCamera(constraints, video, callback); 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 { export default {
request: function(video, constraints, callback) { request: function(video, videoConstraints) {
request(video, constraints, callback); return pickConstraints(videoConstraints)
.then(initCamera.bind(null, video));
}, },
release: function() { release: function() {
var tracks = streamRef && streamRef.getVideoTracks(); var tracks = streamRef && streamRef.getVideoTracks();
if (tracks.length) { if (tracks && tracks.length) {
tracks[0].stop(); tracks[0].stop();
} }
streamRef = null; streamRef = null;
} },
enumerateVideoDevices,
getActiveStreamLabel: function() {
const track = getActiveTrack();
return track ? track.label : '';
},
getActiveTrack
}; };

@ -0,0 +1,146 @@
// Scraped from https://github.com/exif-js/exif-js
const ExifTags = {0x0112: "orientation"};
export const AvailableTags = Object.keys(ExifTags).map(key => ExifTags[key]);
export function findTagsInObjectURL(src, tags = AvailableTags) {
if (/^blob\:/i.test(src)) {
return objectURLToBlob(src)
.then(readToBuffer)
.then(buffer => findTagsInBuffer(buffer, tags));
}
return Promise.resolve(null);
}
export function base64ToArrayBuffer(dataUrl) {
const base64 = dataUrl.replace(/^data\:([^\;]+)\;base64,/gmi, ''),
binary = atob(base64),
len = binary.length,
buffer = new ArrayBuffer(len),
view = new Uint8Array(buffer);
for (let i = 0; i < len; i++) {
view[i] = binary.charCodeAt(i);
}
return buffer;
}
function readToBuffer(blob) {
return new Promise(resolve => {
const fileReader = new FileReader();
fileReader.onload = function(e) {
return resolve(e.target.result);
};
fileReader.readAsArrayBuffer(blob);
});
}
function objectURLToBlob(url) {
return new Promise((resolve, reject) => {
const http = new XMLHttpRequest();
http.open("GET", url, true);
http.responseType = "blob";
http.onreadystatechange = function () {
if (http.readyState === XMLHttpRequest.DONE && (http.status === 200 || http.status === 0)) {
resolve(this.response);
}
};
http.onerror = reject;
http.send();
});
}
export function findTagsInBuffer(file, selectedTags = AvailableTags) {
const dataView = new DataView(file),
length = file.byteLength,
exifTags = selectedTags.reduce((result, selectedTag) => {
const exifTag = Object.keys(ExifTags).filter(tag => ExifTags[tag] === selectedTag)[0];
if (exifTag) {
result[exifTag] = selectedTag;
}
return result;
}, {});
let offset = 2,
marker;
if ((dataView.getUint8(0) !== 0xFF) || (dataView.getUint8(1) !== 0xD8)) {
return false;
}
while (offset < length) {
if (dataView.getUint8(offset) !== 0xFF) {
return false;
}
marker = dataView.getUint8(offset + 1);
if (marker === 0xE1) {
return readEXIFData(dataView, offset + 4, exifTags);
} else {
offset += 2 + dataView.getUint16(offset + 2);
}
}
}
function readEXIFData(file, start, exifTags) {
if (getStringFromBuffer(file, start, 4) !== "Exif") {
return false;
}
const tiffOffset = start + 6;
let bigEnd,
tags;
if (file.getUint16(tiffOffset) === 0x4949) {
bigEnd = false;
} else if (file.getUint16(tiffOffset) === 0x4D4D) {
bigEnd = true;
} else {
return false;
}
if (file.getUint16(tiffOffset + 2, !bigEnd) !== 0x002A) {
return false;
}
const firstIFDOffset = file.getUint32(tiffOffset + 4, !bigEnd);
if (firstIFDOffset < 0x00000008) {
return false;
}
tags = readTags(file, tiffOffset, tiffOffset + firstIFDOffset, exifTags, bigEnd);
return tags;
}
function readTags(file, tiffStart, dirStart, strings, bigEnd) {
const entries = file.getUint16(dirStart, !bigEnd),
tags = {};
for (let i = 0; i < entries; i++) {
const entryOffset = dirStart + i * 12 + 2,
tag = strings[file.getUint16(entryOffset, !bigEnd)];
if (tag) {
tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd);
}
}
return tags;
}
function readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd) {
const type = file.getUint16(entryOffset + 2, !bigEnd),
numValues = file.getUint32(entryOffset + 4, !bigEnd);
switch (type) {
case 3:
if (numValues === 1) {
return file.getUint16(entryOffset + 8, !bigEnd);
}
}
}
function getStringFromBuffer(buffer, start, length) {
let outstr = "";
for (let n = start; n < start + length; n++) {
outstr += String.fromCharCode(buffer.getUint8(n));
}
return outstr;
}

@ -1,13 +1,34 @@
import CVUtils from '../common/cv_utils'; import {
imageRef,
grayAndHalfSampleFromCanvasData,
computeGray
} from '../common/cv_utils';
const TO_RADIANS = Math.PI / 180;
function adjustCanvasSize(canvas, targetSize) {
if (canvas.width !== targetSize.x) {
if (ENV.development) {
console.log("WARNING: canvas-size needs to be adjusted");
}
canvas.width = targetSize.x;
}
if (canvas.height !== targetSize.y) {
if (ENV.development) {
console.log("WARNING: canvas-size needs to be adjusted");
}
canvas.height = targetSize.y;
}
}
var FrameGrabber = {}; var FrameGrabber = {};
FrameGrabber.create = function(inputStream, canvas) { FrameGrabber.create = function(inputStream, canvas) {
var _that = {}, var _that = {},
_streamConfig = inputStream.getConfig(), _streamConfig = inputStream.getConfig(),
_video_size = CVUtils.imageRef(inputStream.getRealWidth(), inputStream.getRealHeight()), _video_size = imageRef(inputStream.getRealWidth(), inputStream.getRealHeight()),
_canvasSize = inputStream.getCanvasSize(), _canvasSize = inputStream.getCanvasSize(),
_size = CVUtils.imageRef(inputStream.getWidth(), inputStream.getHeight()), _size = imageRef(inputStream.getWidth(), inputStream.getHeight()),
topRight = inputStream.getTopRight(), topRight = inputStream.getTopRight(),
_sx = topRight.x, _sx = topRight.x,
_sy = topRight.y, _sy = topRight.y,
@ -50,14 +71,40 @@ FrameGrabber.create = function(inputStream, canvas) {
_that.grab = function() { _that.grab = function() {
var doHalfSample = _streamConfig.halfSample, var doHalfSample = _streamConfig.halfSample,
frame = inputStream.getFrame(), frame = inputStream.getFrame(),
drawable = frame,
drawAngle = 0,
ctxData; ctxData;
if (frame) { if (drawable) {
_ctx.drawImage(frame, 0, 0, _canvasSize.x, _canvasSize.y); adjustCanvasSize(_canvas, _canvasSize);
if (_streamConfig.type === 'ImageStream') {
drawable = frame.img;
if (frame.tags && frame.tags.orientation) {
switch (frame.tags.orientation) {
case 6:
drawAngle = 90 * TO_RADIANS;
break;
case 8:
drawAngle = -90 * TO_RADIANS;
break;
}
}
}
if (drawAngle !== 0) {
_ctx.translate(_canvasSize.x / 2, _canvasSize.y / 2);
_ctx.rotate(drawAngle);
_ctx.drawImage(drawable, -_canvasSize.y / 2, -_canvasSize.x / 2, _canvasSize.y, _canvasSize.x);
_ctx.rotate(-drawAngle);
_ctx.translate(-_canvasSize.x / 2, -_canvasSize.y / 2);
} else {
_ctx.drawImage(drawable, 0, 0, _canvasSize.x, _canvasSize.y);
}
ctxData = _ctx.getImageData(_sx, _sy, _size.x, _size.y).data; ctxData = _ctx.getImageData(_sx, _sy, _size.x, _size.y).data;
if (doHalfSample){ if (doHalfSample){
CVUtils.grayAndHalfSampleFromCanvasData(ctxData, _size, _data); grayAndHalfSampleFromCanvasData(ctxData, _size, _data);
} else { } else {
CVUtils.computeGray(ctxData, _data, _streamConfig); computeGray(ctxData, _data, _streamConfig);
} }
return true; return true;
} else { } else {

@ -1,3 +1,5 @@
import {findTagsInObjectURL} from './exif_helper';
var ImageLoader = {}; var ImageLoader = {};
ImageLoader.load = function(directory, callback, offset, size, sequence) { ImageLoader.load = function(directory, callback, offset, size, sequence) {
var htmlImagesSrcArray = new Array(size), var htmlImagesSrcArray = new Array(size),
@ -26,7 +28,7 @@ ImageLoader.load = function(directory, callback, offset, size, sequence) {
for (var y = 0; y < htmlImagesSrcArray.length; y++) { for (var y = 0; y < htmlImagesSrcArray.length; y++) {
var imgName = htmlImagesSrcArray[y].substr(htmlImagesSrcArray[y].lastIndexOf("/")); var imgName = htmlImagesSrcArray[y].substr(htmlImagesSrcArray[y].lastIndexOf("/"));
if (loadedImg.src.lastIndexOf(imgName) !== -1) { if (loadedImg.src.lastIndexOf(imgName) !== -1) {
htmlImagesArray[y] = loadedImg; htmlImagesArray[y] = {img: loadedImg};
break; break;
} }
} }
@ -37,7 +39,18 @@ ImageLoader.load = function(directory, callback, offset, size, sequence) {
if (ENV.development) { if (ENV.development) {
console.log("Images loaded"); console.log("Images loaded");
} }
callback.apply(null, [htmlImagesArray]); if (sequence === false) {
findTagsInObjectURL(directory, ['orientation'])
.then(tags => {
htmlImagesArray[0].tags = tags;
callback(htmlImagesArray);
}).catch(e => {
console.log(e);
callback(htmlImagesArray);
});
} else {
callback(htmlImagesArray);
}
} }
}; };

@ -176,8 +176,21 @@ InputStream.createImageStream = function() {
loaded = false; loaded = false;
ImageLoader.load(baseUrl, function(imgs) { ImageLoader.load(baseUrl, function(imgs) {
imgArray = imgs; imgArray = imgs;
width = imgs[0].width; if (imgs[0].tags && imgs[0].tags.orientation) {
height = imgs[0].height; switch (imgs[0].tags.orientation) {
case 6:
case 8:
width = imgs[0].img.height;
height = imgs[0].img.width;
break;
default:
width = imgs[0].img.width;
height = imgs[0].img.height;
}
} else {
width = imgs[0].img.width;
height = imgs[0].img.height;
}
calculatedWidth = calculatedWidth =
_config.size ? width / height > 1 ? _config.size : Math.floor((width / height) * _config.size) : width; _config.size ? width / height > 1 ? _config.size : Math.floor((width / height) * _config.size) : width;
calculatedHeight = calculatedHeight =

@ -1,11 +1,30 @@
import ImageWrapper from '../common/image_wrapper'; import ImageWrapper from '../common/image_wrapper';
import CVUtils from '../common/cv_utils'; import {
calculatePatchSize,
otsuThreshold,
hsv2rgb,
cluster,
topGeneric,
imageRef,
halfSample,
computeImageArea
} from '../common/cv_utils';
import ArrayHelper from '../common/array_helper'; import ArrayHelper from '../common/array_helper';
import ImageDebug from '../common/image_debug'; import ImageDebug from '../common/image_debug';
import Rasterizer from './rasterizer'; import Rasterizer from './rasterizer';
import Tracer from './tracer'; import Tracer from './tracer';
import skeletonizer from './skeletonizer'; import skeletonizer from './skeletonizer';
import {vec2, mat2} from 'gl-matrix'; const vec2 = {
clone: require('gl-vec2/clone'),
dot: require('gl-vec2/dot'),
scale: require('gl-vec2/scale'),
transformMat2: require('gl-vec2/transformMat2')
};
const mat2 = {
copy: require('gl-mat2/copy'),
create: require('gl-mat2/create'),
invert: require('gl-mat2/invert')
}
var _config, var _config,
_currentImageWrapper, _currentImageWrapper,
@ -41,7 +60,7 @@ function initBuffers() {
_currentImageWrapper = _inputImageWrapper; _currentImageWrapper = _inputImageWrapper;
} }
_patchSize = CVUtils.calculatePatchSize(_config.patchSize, _currentImageWrapper.size); _patchSize = calculatePatchSize(_config.patchSize, _currentImageWrapper.size);
_numPatches.x = _currentImageWrapper.size.x / _patchSize.x | 0; _numPatches.x = _currentImageWrapper.size.x / _patchSize.x | 0;
_numPatches.y = _currentImageWrapper.size.y / _patchSize.y | 0; _numPatches.y = _currentImageWrapper.size.y / _patchSize.y | 0;
@ -117,7 +136,7 @@ function boxFromPatches(patches) {
} }
overAvg = (180 - overAvg) * Math.PI / 180; overAvg = (180 - overAvg) * Math.PI / 180;
transMat = mat2.clone([Math.cos(overAvg), Math.sin(overAvg), -Math.sin(overAvg), Math.cos(overAvg)]); transMat = mat2.copy(mat2.create(), [Math.cos(overAvg), Math.sin(overAvg), -Math.sin(overAvg), Math.cos(overAvg)]);
// iterate over patches and rotate by angle // iterate over patches and rotate by angle
for ( i = 0; i < patches.length; i++) { for ( i = 0; i < patches.length; i++) {
@ -178,9 +197,9 @@ function boxFromPatches(patches) {
* Creates a binary image of the current image * Creates a binary image of the current image
*/ */
function binarizeImage() { function binarizeImage() {
CVUtils.otsuThreshold(_currentImageWrapper, _binaryImageWrapper); otsuThreshold(_currentImageWrapper, _binaryImageWrapper);
_binaryImageWrapper.zeroBorder(); _binaryImageWrapper.zeroBorder();
if (_config.showCanvas) { if (ENV.development && _config.debug.showCanvas) {
_binaryImageWrapper.show(_canvasContainer.dom.binary, 255); _binaryImageWrapper.show(_canvasContainer.dom.binary, 255);
} }
} }
@ -309,7 +328,7 @@ function findBoxes(topLabels, maxLabel) {
for ( j = 0; j < patches.length; j++) { for ( j = 0; j < patches.length; j++) {
patch = patches[j]; patch = patches[j];
hsv[0] = (topLabels[i].label / (maxLabel + 1)) * 360; hsv[0] = (topLabels[i].label / (maxLabel + 1)) * 360;
CVUtils.hsv2rgb(hsv, rgb); hsv2rgb(hsv, rgb);
ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary,
{color: "rgb(" + rgb.join(",") + ")", lineWidth: 2}); {color: "rgb(" + rgb.join(",") + ")", lineWidth: 2});
} }
@ -324,8 +343,8 @@ function findBoxes(topLabels, maxLabel) {
* @param {Object} moments * @param {Object} moments
*/ */
function similarMoments(moments) { function similarMoments(moments) {
var clusters = CVUtils.cluster(moments, 0.90); var clusters = cluster(moments, 0.90);
var topCluster = CVUtils.topGeneric(clusters, 1, function(e) { var topCluster = topGeneric(clusters, 1, function(e) {
return e.getPoints().length; return e.getPoints().length;
}); });
var points = [], result = []; var points = [], result = [];
@ -339,12 +358,12 @@ function similarMoments(moments) {
} }
function skeletonize(x, y) { function skeletonize(x, y) {
_binaryImageWrapper.subImageAsCopy(_subImageWrapper, CVUtils.imageRef(x, y)); _binaryImageWrapper.subImageAsCopy(_subImageWrapper, imageRef(x, y));
_skeletonizer.skeletonize(); _skeletonizer.skeletonize();
// Show skeleton if requested // Show skeleton if requested
if (ENV.development && _config.debug.showSkeleton) { if (ENV.development && _config.debug.showSkeleton) {
_skelImageWrapper.overlay(_canvasContainer.dom.binary, 360, CVUtils.imageRef(x, y)); _skelImageWrapper.overlay(_canvasContainer.dom.binary, 360, imageRef(x, y));
} }
} }
@ -496,7 +515,7 @@ function rasterizeAngularSimilarity(patchesFound) {
if (_patchLabelGrid.data[j] > 0 && _patchLabelGrid.data[j] <= label) { if (_patchLabelGrid.data[j] > 0 && _patchLabelGrid.data[j] <= label) {
patch = _imageToPatchGrid.data[j]; patch = _imageToPatchGrid.data[j];
hsv[0] = (_patchLabelGrid.data[j] / (label + 1)) * 360; hsv[0] = (_patchLabelGrid.data[j] / (label + 1)) * 360;
CVUtils.hsv2rgb(hsv, rgb); hsv2rgb(hsv, rgb);
ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary, ImageDebug.drawRect(patch.pos, _subImageWrapper.size, _canvasContainer.ctx.binary,
{color: "rgb(" + rgb.join(",") + ")", lineWidth: 2}); {color: "rgb(" + rgb.join(",") + ")", lineWidth: 2});
} }
@ -521,7 +540,7 @@ export default {
boxes; boxes;
if (_config.halfSample) { if (_config.halfSample) {
CVUtils.halfSample(_inputImageWrapper, _currentImageWrapper); halfSample(_inputImageWrapper, _currentImageWrapper);
} }
binarizeImage(); binarizeImage();
@ -557,7 +576,7 @@ export default {
// calculate width and height based on area // calculate width and height based on area
if (inputStream.getConfig().area) { if (inputStream.getConfig().area) {
area = CVUtils.computeImageArea(width, height, inputStream.getConfig().area); area = computeImageArea(width, height, inputStream.getConfig().area);
inputStream.setTopRight({x: area.sx, y: area.sy}); inputStream.setTopRight({x: area.sx, y: area.sy});
inputStream.setCanvasSize({x: width, y: height}); inputStream.setCanvasSize({x: width, y: height});
width = area.sw; width = area.sw;
@ -569,7 +588,7 @@ export default {
y: Math.floor(height * halfSample) y: Math.floor(height * halfSample)
}; };
patchSize = CVUtils.calculatePatchSize(config.patchSize, size); patchSize = calculatePatchSize(config.patchSize, size);
if (ENV.development) { if (ENV.development) {
console.log("Patch-Size: " + JSON.stringify(patchSize)); console.log("Patch-Size: " + JSON.stringify(patchSize));
} }

@ -198,12 +198,10 @@ function Skeletonizer(stdlib, foreign, buffer) {
done = ((sum | 0) == 0 | 0); done = ((sum | 0) == 0 | 0);
} while (!done); } while (!done);
} }
return { return {
skeletonize: skeletonize skeletonize: skeletonize
}; };
} }
/* @preserve ASM END */
export default Skeletonizer; export default Skeletonizer;
/* eslint-enable eqeqeq*/ /* eslint-enable eqeqeq*/
/* @preserve ASM END */

@ -5,13 +5,14 @@ import BarcodeDecoder from './decoder/barcode_decoder';
import Events from './common/events'; import Events from './common/events';
import CameraAccess from './input/camera_access'; import CameraAccess from './input/camera_access';
import ImageDebug from './common/image_debug'; import ImageDebug from './common/image_debug';
import {vec2} from 'gl-matrix';
import ResultCollector from './analytics/result_collector'; import ResultCollector from './analytics/result_collector';
import Config from './config/config'; import Config from './config/config';
import InputStream from 'input_stream'; import InputStream from 'input_stream';
import FrameGrabber from 'frame_grabber'; import FrameGrabber from 'frame_grabber';
import {merge} from 'lodash';
const merge = require('lodash/object/merge'); const vec2 = {
clone: require('gl-vec2/clone')
};
var _inputStream, var _inputStream,
_framegrabber, _framegrabber,
@ -56,17 +57,15 @@ function initInputStream(cb) {
} }
} }
_inputStream = InputStream.createLiveStream(video); _inputStream = InputStream.createLiveStream(video);
CameraAccess.request(video, _config.inputStream.constraints, function(err) { CameraAccess.request(video, _config.inputStream.constraints)
if (!err) { .then(() => {
_inputStream.trigger("canrecord"); _inputStream.trigger("canrecord");
} else { }).catch((err) => {
return cb(err); return cb(err);
}
}); });
} }
_inputStream.setAttribute("preload", "auto"); _inputStream.setAttribute("preload", "auto");
_inputStream.setAttribute("autoplay", true);
_inputStream.setInputStream(_config.inputStream); _inputStream.setInputStream(_config.inputStream);
_inputStream.addEventListener("canrecord", canRecord.bind(undefined, cb)); _inputStream.addEventListener("canrecord", canRecord.bind(undefined, cb));
} }
@ -88,17 +87,12 @@ function canRecord(cb) {
initCanvas(_config); initCanvas(_config);
_framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image); _framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image);
if (_config.numOfWorkers > 0) { adjustWorkerPool(_config.numOfWorkers, function() {
initWorkers(function() { if (_config.numOfWorkers === 0) {
if (ENV.development) { initializeData();
console.log("Workers created"); }
}
ready(cb);
});
} else {
initializeData();
ready(cb); ready(cb);
} });
} }
function ready(cb){ function ready(cb){
@ -241,11 +235,12 @@ function hasCodeResult (result) {
} }
function publishResult(result, imageData) { function publishResult(result, imageData) {
const resultToPublish = result && (result.barcodes || result); let resultToPublish = result;
if (result && _onUIThread) { if (result && _onUIThread) {
transformResult(result); transformResult(result);
addResult(result, imageData); addResult(result, imageData);
resultToPublish = result.barcodes || result;
} }
Events.publish("processed", resultToPublish); Events.publish("processed", resultToPublish);
@ -301,31 +296,28 @@ function update() {
} }
} }
function start() { function startContinuousUpdate() {
var next = null,
delay = 1000 / (_config.frequency || 60);
_stopped = false; _stopped = false;
( function frame() { (function frame(timestamp) {
next = next || timestamp;
if (!_stopped) { if (!_stopped) {
update(); if (timestamp >= next) {
if (_onUIThread && _config.inputStream.type === "LiveStream") { next += delay;
window.requestAnimFrame(frame); update();
} }
window.requestAnimFrame(frame);
} }
}()); }(performance.now()));
} }
function initWorkers(cb) { function start() {
var i; if (_onUIThread && _config.inputStream.type === "LiveStream") {
_workerPool = []; startContinuousUpdate();
} else {
for (i = 0; i < _config.numOfWorkers; i++) { update();
initWorker(workerInitialized);
}
function workerInitialized(workerThread) {
_workerPool.push(workerThread);
if (_workerPool.length >= _config.numOfWorkers){
cb();
}
} }
} }
@ -364,15 +356,24 @@ function initWorker(cb) {
cmd: 'init', cmd: 'init',
size: {x: _inputStream.getWidth(), y: _inputStream.getHeight()}, size: {x: _inputStream.getWidth(), y: _inputStream.getHeight()},
imageData: workerThread.imageData, imageData: workerThread.imageData,
config: _config config: configForWorker(_config)
}, [workerThread.imageData.buffer]); }, [workerThread.imageData.buffer]);
} }
function configForWorker(config) {
return {
...config,
inputStream: {
...config.inputStream,
target: null
}
};
}
function workerInterface(factory) { function workerInterface(factory) {
/* eslint-disable no-undef*/ /* eslint-disable no-undef*/
if (factory) { if (factory) {
var Quagga = factory(); var Quagga = factory().default;
if (!Quagga) { if (!Quagga) {
self.postMessage({'event': 'error', message: 'Quagga could not be created'}); self.postMessage({'event': 'error', message: 'Quagga could not be created'});
return; return;
@ -439,6 +440,35 @@ function setReaders(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 { export default {
init: function(config, cb, imageWrapper) { init: function(config, cb, imageWrapper) {
_config = merge({}, Config, config); _config = merge({}, Config, config);
@ -455,13 +485,7 @@ export default {
}, },
stop: function() { stop: function() {
_stopped = true; _stopped = true;
_workerPool.forEach(function(workerThread) { adjustWorkerPool(0);
workerThread.worker.terminate();
if (ENV.development) {
console.log("Worker terminated!");
}
});
_workerPool.length = 0;
if (_config.inputStream.type === "LiveStream") { if (_config.inputStream.type === "LiveStream") {
CameraAccess.release(); CameraAccess.release();
_inputStream.clearEventHandlers(); _inputStream.clearEventHandlers();
@ -504,9 +528,9 @@ export default {
halfSample: false halfSample: false
} }
}, config); }, config);
this.init(config, function() { this.init(config, () => {
Events.once("processed", function(result) { Events.once("processed", (result) => {
_stopped = true; this.stop();
resultCallback.call(null, result); resultCallback.call(null, result);
}, true); }, true);
start(); start();
@ -514,5 +538,6 @@ export default {
}, },
ImageWrapper: ImageWrapper, ImageWrapper: ImageWrapper,
ImageDebug: ImageDebug, ImageDebug: ImageDebug,
ResultCollector: ResultCollector 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,6 +1,9 @@
function BarcodeReader(config) { import ArrayHelper from '../common/array_helper';
function BarcodeReader(config, supplements) {
this._row = []; this._row = [];
this.config = config || {}; this.config = config || {};
this.supplements = supplements;
return this; return this;
} }
@ -18,15 +21,32 @@ BarcodeReader.prototype._nextUnset = function(line, start) {
return line.length; return line.length;
}; };
BarcodeReader.prototype._matchPattern = function(counter, code) { BarcodeReader.prototype._matchPattern = function(counter, code, maxSingleError) {
var i, var i,
error = 0, error = 0,
singleError = 0, singleError = 0,
modulo = this.MODULO, sum = 0,
maxSingleError = this.SINGLE_CODE_ERROR || 1; modulo = 0,
barWidth,
count,
scaled;
maxSingleError = maxSingleError || this.SINGLE_CODE_ERROR || 1;
for (i = 0; i < counter.length; i++) { for (i = 0; i < counter.length; i++) {
singleError = Math.abs(code[i] - counter[i]); sum += counter[i];
modulo += code[i];
}
if (sum < modulo) {
return Number.MAX_VALUE;
}
barWidth = sum / modulo;
maxSingleError *= barWidth;
for (i = 0; i < counter.length; i++) {
count = counter[i];
scaled = code[i] * barWidth;
singleError = Math.abs(count - scaled) / scaled;
if (singleError > maxSingleError) { if (singleError > maxSingleError) {
return Number.MAX_VALUE; return Number.MAX_VALUE;
} }
@ -47,40 +67,16 @@ BarcodeReader.prototype._nextSet = function(line, offset) {
return line.length; return line.length;
}; };
BarcodeReader.prototype._normalize = function(counter, modulo) { BarcodeReader.prototype._correctBars = function(counter, correction, indices) {
var i, var length = indices.length,
self = this, tmp = 0;
sum = 0, while(length--) {
ratio, tmp = counter[indices[length]] * (1 - ((1 - correction) / 2));
numOnes = 0, if (tmp > 1) {
normalized = [], counter[indices[length]] = tmp;
norm = 0;
if (!modulo) {
modulo = self.MODULO;
}
for (i = 0; i < counter.length; i++) {
if (counter[i] === 1) {
numOnes++;
} else {
sum += counter[i];
}
}
ratio = sum / (modulo - numOnes);
if (ratio > 1.0) {
for (i = 0; i < counter.length; i++) {
norm = counter[i] === 1 ? counter[i] : counter[i] / ratio;
normalized.push(norm);
}
} else {
ratio = (sum + numOnes) / modulo;
for (i = 0; i < counter.length; i++) {
norm = counter[i] / ratio;
normalized.push(norm);
} }
} }
return normalized; }
};
BarcodeReader.prototype._matchTrace = function(cmpCounter, epsilon) { BarcodeReader.prototype._matchTrace = function(cmpCounter, epsilon) {
var counter = [], var counter = [],
@ -201,6 +197,33 @@ BarcodeReader.prototype._fillCounters = function(offset, end, isWhite) {
return counters; 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", { Object.defineProperty(BarcodeReader.prototype, "FORMAT", {
value: 'unknown', value: 'unknown',
writeable: false writeable: false

@ -13,7 +13,6 @@ var properties = {
START_CODE_B: {value: 104}, START_CODE_B: {value: 104},
START_CODE_C: {value: 105}, START_CODE_C: {value: 105},
STOP_CODE: {value: 106}, STOP_CODE: {value: 106},
MODULO: {value: 11},
CODE_PATTERN: {value: [ CODE_PATTERN: {value: [
[2, 1, 2, 2, 2, 2], [2, 1, 2, 2, 2, 2],
[2, 2, 2, 1, 2, 2], [2, 2, 2, 1, 2, 2],
@ -123,15 +122,16 @@ var properties = {
[2, 1, 1, 2, 3, 2], [2, 1, 1, 2, 3, 2],
[2, 3, 3, 1, 1, 1, 2] [2, 3, 3, 1, 1, 1, 2]
]}, ]},
SINGLE_CODE_ERROR: {value: 1}, SINGLE_CODE_ERROR: {value: 0.64},
AVG_CODE_ERROR: {value: 0.5}, AVG_CODE_ERROR: {value: 0.30},
FORMAT: {value: "code_128", writeable: false} FORMAT: {value: "code_128", writeable: false},
MODULE_INDICES: {value: {bar: [0, 2, 4], space: [1, 3, 5]}}
}; };
Code128Reader.prototype = Object.create(BarcodeReader.prototype, properties); Code128Reader.prototype = Object.create(BarcodeReader.prototype, properties);
Code128Reader.prototype.constructor = Code128Reader; Code128Reader.prototype.constructor = Code128Reader;
Code128Reader.prototype._decodeCode = function(start) { Code128Reader.prototype._decodeCode = function(start, correction) {
var counter = [0, 0, 0, 0, 0, 0], var counter = [0, 0, 0, 0, 0, 0],
i, i,
self = this, self = this,
@ -142,29 +142,43 @@ Code128Reader.prototype._decodeCode = function(start) {
error: Number.MAX_VALUE, error: Number.MAX_VALUE,
code: -1, code: -1,
start: start, start: start,
end: start end: start,
correction: {
bar: 1,
space: 1
}
}, },
code, code,
error, error;
normalized;
for ( i = offset; i < self._row.length; i++) { for ( i = offset; i < self._row.length; i++) {
if (self._row[i] ^ isWhite) { if (self._row[i] ^ isWhite) {
counter[counterPos]++; counter[counterPos]++;
} else { } else {
if (counterPos === counter.length - 1) { if (counterPos === counter.length - 1) {
normalized = self._normalize(counter); if (correction) {
if (normalized) { self._correct(counter, correction);
for (code = 0; code < self.CODE_PATTERN.length; code++) { }
error = self._matchPattern(normalized, self.CODE_PATTERN[code]); for (code = 0; code < self.CODE_PATTERN.length; code++) {
if (error < bestMatch.error) { error = self._matchPattern(counter, self.CODE_PATTERN[code]);
bestMatch.code = code; if (error < bestMatch.error) {
bestMatch.error = error; bestMatch.code = code;
} bestMatch.error = error;
} }
bestMatch.end = i;
return bestMatch;
} }
bestMatch.end = i;
if (bestMatch.code === -1 || bestMatch.error > self.AVG_CODE_ERROR) {
return null;
}
if (self.CODE_PATTERN[bestMatch.code]) {
bestMatch.correction.bar = calculateCorrection(
self.CODE_PATTERN[bestMatch.code], counter,
this.MODULE_INDICES.bar);
bestMatch.correction.space = calculateCorrection(
self.CODE_PATTERN[bestMatch.code], counter,
this.MODULE_INDICES.space);
}
return bestMatch;
} else { } else {
counterPos++; counterPos++;
} }
@ -175,6 +189,11 @@ Code128Reader.prototype._decodeCode = function(start) {
return null; return null;
}; };
Code128Reader.prototype._correct = function(counter, correction) {
this._correctBars(counter, correction.bar, this.MODULE_INDICES.bar);
this._correctBars(counter, correction.space, this.MODULE_INDICES.space);
};
Code128Reader.prototype._findStart = function() { Code128Reader.prototype._findStart = function() {
var counter = [0, 0, 0, 0, 0, 0], var counter = [0, 0, 0, 0, 0, 0],
i, i,
@ -186,13 +205,16 @@ Code128Reader.prototype._findStart = function() {
error: Number.MAX_VALUE, error: Number.MAX_VALUE,
code: -1, code: -1,
start: 0, start: 0,
end: 0 end: 0,
correction: {
bar: 1,
space: 1
}
}, },
code, code,
error, error,
j, j,
sum, sum;
normalized;
for ( i = offset; i < self._row.length; i++) { for ( i = offset; i < self._row.length; i++) {
if (self._row[i] ^ isWhite) { if (self._row[i] ^ isWhite) {
@ -203,21 +225,24 @@ Code128Reader.prototype._findStart = function() {
for ( j = 0; j < counter.length; j++) { for ( j = 0; j < counter.length; j++) {
sum += counter[j]; sum += counter[j];
} }
normalized = self._normalize(counter); for (code = self.START_CODE_A; code <= self.START_CODE_C; code++) {
if (normalized) { error = self._matchPattern(counter, self.CODE_PATTERN[code]);
for (code = self.START_CODE_A; code <= self.START_CODE_C; code++) { if (error < bestMatch.error) {
error = self._matchPattern(normalized, self.CODE_PATTERN[code]); bestMatch.code = code;
if (error < bestMatch.error) { bestMatch.error = error;
bestMatch.code = code;
bestMatch.error = error;
}
}
if (bestMatch.error < self.AVG_CODE_ERROR) {
bestMatch.start = i - sum;
bestMatch.end = i;
return bestMatch;
} }
} }
if (bestMatch.error < self.AVG_CODE_ERROR) {
bestMatch.start = i - sum;
bestMatch.end = i;
bestMatch.correction.bar = calculateCorrection(
self.CODE_PATTERN[bestMatch.code], counter,
this.MODULE_INDICES.bar);
bestMatch.correction.space = calculateCorrection(
self.CODE_PATTERN[bestMatch.code], counter,
this.MODULE_INDICES.space);
return bestMatch;
}
for ( j = 0; j < 4; j++) { for ( j = 0; j < 4; j++) {
counter[j] = counter[j + 2]; counter[j] = counter[j + 2];
@ -256,7 +281,11 @@ Code128Reader.prototype._decode = function() {
code = { code = {
code: startInfo.code, code: startInfo.code,
start: startInfo.start, start: startInfo.start,
end: startInfo.end end: startInfo.end,
correction: {
bar: startInfo.correction.bar,
space: startInfo.correction.space
}
}; };
decodedCodes.push(code); decodedCodes.push(code);
checksum = code.code; checksum = code.code;
@ -277,7 +306,7 @@ Code128Reader.prototype._decode = function() {
while (!done) { while (!done) {
unshift = shiftNext; unshift = shiftNext;
shiftNext = false; shiftNext = false;
code = self._decodeCode(code.end); code = self._decodeCode(code.end, code.correction);
if (code !== null) { if (code !== null) {
if (code.code !== self.STOP_CODE) { if (code.code !== self.STOP_CODE) {
removeLastCharacter = true; removeLastCharacter = true;
@ -419,4 +448,16 @@ BarcodeReader.prototype._verifyTrailingWhitespace = function(endInfo) {
return null; return null;
}; };
function calculateCorrection(expected, normalized, indices) {
var length = indices.length,
sumNormalized = 0,
sumExpected = 0;
while(length--) {
sumExpected += expected[indices[length]];
sumNormalized += normalized[indices[length]];
}
return sumExpected/sumNormalized;
}
export default Code128Reader; export default Code128Reader;

@ -20,33 +20,6 @@ var properties = {
Code39Reader.prototype = Object.create(BarcodeReader.prototype, properties); Code39Reader.prototype = Object.create(BarcodeReader.prototype, properties);
Code39Reader.prototype.constructor = Code39Reader; 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() { Code39Reader.prototype._decode = function() {
var self = this, var self = this,
counters = [0, 0, 0, 0, 0, 0, 0, 0, 0], 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;

@ -0,0 +1,51 @@
import EANReader from './ean_reader';
function EAN2Reader() {
EANReader.call(this);
}
var properties = {
FORMAT: {value: "ean_2", writeable: false}
};
EAN2Reader.prototype = Object.create(EANReader.prototype, properties);
EAN2Reader.prototype.constructor = EAN2Reader;
EAN2Reader.prototype.decode = function(row, start) {
this._row = row;
var counters = [0, 0, 0, 0],
codeFrequency = 0,
i = 0,
offset = start,
end = this._row.length,
code,
result = [],
decodedCodes = [];
for (i = 0; i < 2 && offset < end; i++) {
code = this._decodeCode(offset);
if (!code) {
return null;
}
decodedCodes.push(code);
result.push(code.code % 10);
if (code.code >= this.CODE_G_START) {
codeFrequency |= 1 << (1 - i);
}
if (i != 1) {
offset = this._nextSet(this._row, code.end);
offset = this._nextUnset(this._row, offset);
}
}
if (result.length != 2 || (parseInt(result.join("")) % 4) !== codeFrequency) {
return null;
}
return {
code: result.join(""),
decodedCodes,
end: code.end
};
};
export default EAN2Reader;

@ -0,0 +1,84 @@
import EANReader from './ean_reader';
function EAN5Reader() {
EANReader.call(this);
}
var properties = {
FORMAT: {value: "ean_5", writeable: false}
};
const CHECK_DIGIT_ENCODINGS = [24, 20, 18, 17, 12, 6, 3, 10, 9, 5];
EAN5Reader.prototype = Object.create(EANReader.prototype, properties);
EAN5Reader.prototype.constructor = EAN5Reader;
EAN5Reader.prototype.decode = function(row, start) {
this._row = row;
var counters = [0, 0, 0, 0],
codeFrequency = 0,
i = 0,
offset = start,
end = this._row.length,
code,
result = [],
decodedCodes = [];
for (i = 0; i < 5 && offset < end; i++) {
code = this._decodeCode(offset);
if (!code) {
return null;
}
decodedCodes.push(code);
result.push(code.code % 10);
if (code.code >= this.CODE_G_START) {
codeFrequency |= 1 << (4 - i);
}
if (i != 4) {
offset = this._nextSet(this._row, code.end);
offset = this._nextUnset(this._row, offset);
}
}
if (result.length != 5) {
return null;
}
if (extensionChecksum(result) !== determineCheckDigit(codeFrequency)) {
return null;
}
return {
code: result.join(""),
decodedCodes,
end: code.end
};
};
function determineCheckDigit(codeFrequency) {
var i;
for (i = 0; i < 10; i++) {
if (codeFrequency === CHECK_DIGIT_ENCODINGS[i]) {
return i;
}
}
return null;
}
function extensionChecksum(result) {
var length = result.length,
sum = 0,
i;
for (i = length - 2; i >= 0; i -= 2) {
sum += result[i];
}
sum *= 3;
for (i = length - 1; i >= 0; i -= 2) {
sum += result[i];
}
sum *= 3;
return sum % 10;
}
export default EAN5Reader;

@ -1,7 +1,7 @@
import EANReader from './ean_reader'; import EANReader from './ean_reader';
function EAN8Reader() { function EAN8Reader(opts, supplements) {
EANReader.call(this); EANReader.call(this, opts, supplements);
} }
var properties = { var properties = {

@ -1,16 +1,27 @@
import BarcodeReader from './barcode_reader'; import BarcodeReader from './barcode_reader';
import {merge} from 'lodash';
function EANReader(opts) { function EANReader(opts, supplements) {
BarcodeReader.call(this, opts); opts = merge(getDefaulConfig(), opts);
BarcodeReader.call(this, opts, supplements);
}
function getDefaulConfig() {
var config = {};
Object.keys(EANReader.CONFIG_KEYS).forEach(function(key) {
config[key] = EANReader.CONFIG_KEYS[key].default;
});
return config;
} }
var properties = { var properties = {
CODE_L_START: {value: 0}, CODE_L_START: {value: 0},
MODULO: {value: 7},
CODE_G_START: {value: 10}, CODE_G_START: {value: 10},
START_PATTERN: {value: [1 / 3 * 7, 1 / 3 * 7, 1 / 3 * 7]}, START_PATTERN: {value: [1, 1, 1]},
STOP_PATTERN: {value: [1 / 3 * 7, 1 / 3 * 7, 1 / 3 * 7]}, STOP_PATTERN: {value: [1, 1, 1]},
MIDDLE_PATTERN: {value: [1 / 5 * 7, 1 / 5 * 7, 1 / 5 * 7, 1 / 5 * 7, 1 / 5 * 7]}, MIDDLE_PATTERN: {value: [1, 1, 1, 1, 1]},
EXTENSION_START_PATTERN: {value: [1, 1, 2]},
CODE_PATTERN: {value: [ CODE_PATTERN: {value: [
[3, 2, 1, 1], [3, 2, 1, 1],
[2, 2, 2, 1], [2, 2, 2, 1],
@ -34,8 +45,8 @@ var properties = {
[2, 1, 1, 3] [2, 1, 1, 3]
]}, ]},
CODE_FREQUENCY: {value: [0, 11, 13, 14, 19, 25, 28, 21, 22, 26]}, CODE_FREQUENCY: {value: [0, 11, 13, 14, 19, 25, 28, 21, 22, 26]},
SINGLE_CODE_ERROR: {value: 0.67}, SINGLE_CODE_ERROR: {value: 0.70},
AVG_CODE_ERROR: {value: 0.27}, AVG_CODE_ERROR: {value: 0.48},
FORMAT: {value: "ean_13", writeable: false} FORMAT: {value: "ean_13", writeable: false}
}; };
@ -56,8 +67,7 @@ EANReader.prototype._decodeCode = function(start, coderange) {
end: start end: start
}, },
code, code,
error, error;
normalized;
if (!coderange) { if (!coderange) {
coderange = self.CODE_PATTERN.length; coderange = self.CODE_PATTERN.length;
@ -68,21 +78,18 @@ EANReader.prototype._decodeCode = function(start, coderange) {
counter[counterPos]++; counter[counterPos]++;
} else { } else {
if (counterPos === counter.length - 1) { if (counterPos === counter.length - 1) {
normalized = self._normalize(counter); for (code = 0; code < coderange; code++) {
if (normalized) { error = self._matchPattern(counter, self.CODE_PATTERN[code]);
for (code = 0; code < coderange; code++) { if (error < bestMatch.error) {
error = self._matchPattern(normalized, self.CODE_PATTERN[code]); bestMatch.code = code;
if (error < bestMatch.error) { bestMatch.error = error;
bestMatch.code = code;
bestMatch.error = error;
}
}
bestMatch.end = i;
if (bestMatch.error > self.AVG_CODE_ERROR) {
return null;
} }
return bestMatch;
} }
bestMatch.end = i;
if (bestMatch.error > self.AVG_CODE_ERROR) {
return null;
}
return bestMatch;
} else { } else {
counterPos++; counterPos++;
} }
@ -106,8 +113,7 @@ EANReader.prototype._findPattern = function(pattern, offset, isWhite, tryHarder,
}, },
error, error,
j, j,
sum, sum;
normalized;
if (!offset) { if (!offset) {
offset = self._nextSet(self._row); offset = self._nextSet(self._row);
@ -138,16 +144,13 @@ EANReader.prototype._findPattern = function(pattern, offset, isWhite, tryHarder,
for ( j = 0; j < counter.length; j++) { for ( j = 0; j < counter.length; j++) {
sum += counter[j]; sum += counter[j];
} }
normalized = self._normalize(counter); error = self._matchPattern(counter, pattern);
if (normalized) {
error = self._matchPattern(normalized, pattern);
if (error < epsilon) { if (error < epsilon) {
bestMatch.error = error; bestMatch.error = error;
bestMatch.start = i - sum; bestMatch.start = i - sum;
bestMatch.end = i; bestMatch.end = i;
return bestMatch; return bestMatch;
}
} }
if (tryHarder) { if (tryHarder) {
for ( j = 0; j < counter.length - 2; j++) { for ( j = 0; j < counter.length - 2; j++) {
@ -273,7 +276,8 @@ EANReader.prototype._decode = function() {
self = this, self = this,
code, code,
result = [], result = [],
decodedCodes = []; decodedCodes = [],
resultInfo = {};
startInfo = self._findStart(); startInfo = self._findStart();
if (!startInfo) { if (!startInfo) {
@ -301,16 +305,62 @@ EANReader.prototype._decode = function() {
return null; return null;
} }
if (this.supplements.length > 0) {
let ext = this._decodeExtensions(code.end);
if (!ext) {
return null;
}
let lastCode = ext.decodedCodes[ext.decodedCodes.length-1],
endInfo = {
start: lastCode.start + (((lastCode.end - lastCode.start) / 2) | 0),
end: lastCode.end
};
if(!self._verifyTrailingWhitespace(endInfo)) {
return null;
}
resultInfo = {
supplement: ext,
code: result.join("") + ext.code
}
}
return { return {
code: result.join(""), code: result.join(""),
start: startInfo.start, start: startInfo.start,
end: code.end, end: code.end,
codeset: "", codeset: "",
startInfo: startInfo, startInfo: startInfo,
decodedCodes: decodedCodes decodedCodes: decodedCodes,
...resultInfo
}; };
}; };
EANReader.prototype._decodeExtensions = function(offset) {
var i,
start = this._nextSet(this._row, offset),
startInfo = this._findPattern(this.EXTENSION_START_PATTERN, start, false, false),
result;
if (startInfo === null) {
return null;
}
for (i = 0; i < this.supplements.length; i++) {
result = this.supplements[i].decode(this._row, startInfo.end);
if (result !== null) {
return {
code: result.code,
start,
startInfo,
end: result.end,
codeset: "",
decodedCodes: result.decodedCodes
}
}
}
return null;
};
EANReader.prototype._checksum = function(result) { EANReader.prototype._checksum = function(result) {
var sum = 0, i; var sum = 0, i;
@ -324,4 +374,12 @@ EANReader.prototype._checksum = function(result) {
return sum % 10 === 0; return sum % 10 === 0;
}; };
EANReader.CONFIG_KEYS = {
supplements: {
'type': 'arrayOf(string)',
'default': [],
'description': 'Allowed extensions to be decoded (2 and/or 5)'
}
};
export default (EANReader); export default (EANReader);

@ -1,5 +1,5 @@
import BarcodeReader from './barcode_reader'; import BarcodeReader from './barcode_reader';
const merge = require('lodash/object/merge'); import {merge} from 'lodash';
function I2of5Reader(opts) { function I2of5Reader(opts) {
opts = merge(getDefaulConfig(), opts); opts = merge(getDefaulConfig(), opts);
@ -23,9 +23,8 @@ function getDefaulConfig() {
var N = 1, var N = 1,
W = 3, W = 3,
properties = { properties = {
MODULO: {value: 10}, START_PATTERN: {value: [N, N, N, N]},
START_PATTERN: {value: [N * 2.5, N * 2.5, N * 2.5, N * 2.5]}, STOP_PATTERN: {value: [N, N, W]},
STOP_PATTERN: {value: [N * 2, N * 2, W * 2]},
CODE_PATTERN: {value: [ CODE_PATTERN: {value: [
[N, N, W, W, N], [N, N, W, W, N],
[W, N, N, N, W], [W, N, N, N, W],
@ -110,16 +109,12 @@ I2of5Reader.prototype._findPattern = function(pattern, offset, isWhite, tryHarde
for ( j = 0; j < counter.length; j++) { for ( j = 0; j < counter.length; j++) {
sum += counter[j]; sum += counter[j];
} }
normalized = self._normalize(counter); error = self._matchPattern(counter, pattern);
if (normalized) { if (error < epsilon) {
error = self._matchPattern(normalized, pattern); bestMatch.error = error;
bestMatch.start = i - sum;
if (error < epsilon) { bestMatch.end = i;
bestMatch.error = error; return bestMatch;
bestMatch.start = i - sum;
bestMatch.end = i;
return bestMatch;
}
} }
if (tryHarder) { if (tryHarder) {
for (j = 0; j < counter.length - 2; j++) { for (j = 0; j < counter.length - 2; j++) {
@ -233,20 +228,16 @@ I2of5Reader.prototype._decodeCode = function(counter) {
for ( j = 0; j < counter.length; j++) { for ( j = 0; j < counter.length; j++) {
sum += counter[j]; sum += counter[j];
} }
normalized = self._normalize(counter); for (code = 0; code < self.CODE_PATTERN.length; code++) {
if (normalized) { error = self._matchPattern(counter, self.CODE_PATTERN[code]);
for (code = 0; code < self.CODE_PATTERN.length; code++) { if (error < bestMatch.error) {
error = self._matchPattern(normalized, self.CODE_PATTERN[code]); bestMatch.code = code;
if (error < bestMatch.error) { bestMatch.error = error;
bestMatch.code = code;
bestMatch.error = error;
}
}
if (bestMatch.error < epsilon) {
return bestMatch;
} }
} }
return null; if (bestMatch.error < epsilon) {
return bestMatch;
}
}; };
I2of5Reader.prototype._decodePayload = function(counters, result, decodedCodes) { I2of5Reader.prototype._decodePayload = function(counters, result, decodedCodes) {

@ -1,7 +1,7 @@
import EANReader from './ean_reader'; import EANReader from './ean_reader';
function UPCEReader() { function UPCEReader(opts, supplements) {
EANReader.call(this); EANReader.call(this, opts, supplements);
} }
var properties = { var properties = {

@ -1,7 +1,7 @@
import EANReader from './ean_reader'; import EANReader from './ean_reader';
function UPCReader() { function UPCReader(opts, supplements) {
EANReader.call(this); EANReader.call(this, opts, supplements);
} }
var properties = { var properties = {

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

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

Loading…
Cancel
Save