Merge pull request #72 from serratus/feature/webpack

From RequireJS to Webpack
pull/79/head v0.8.0
Christoph Oberhofer 10 years ago
commit d23e824876

@ -0,0 +1,80 @@
{
"settings" : {
"ecmascript": 6,
"jsx": true
},
"env": {
"browser": true,
"node": true
},
"ecmaFeatures": {
"blockBindings": true,
"forOf": true,
"blockBindings": true,
"defaultParams": true,
"globalReturn": false,
"modules": true,
"objectLiteralShorthandMethods": true,
"objectLiteralShorthandProperties": true,
"templateStrings": true,
"spread": true,
"jsx": true,
"arrowFunctions": true,
"classes": true,
"destructuring": true,
"objectLiteralComputedProperties": true
},
"globals": {},
"rules": {
"no-unused-expressions": 1,
"no-extra-boolean-cast": 1,
"no-multi-spaces": 2,
"no-underscore-dangle": 0,
"comma-dangle": 2,
"camelcase": 0,
"curly": 2,
"eqeqeq": 2,
"guard-for-in": 2,
"wrap-iife": 0,
"no-use-before-define": [1, "nofunc"],
"new-cap": 2,
"quotes": 0,
"strict": 0,
"no-caller": 2,
"no-empty": 1,
"no-new": 2,
"no-plusplus": 0,
"no-unused-vars": 1,
"no-trailing-spaces": 2,
// STYLE
"max-params": [2, 7],
"key-spacing": [1, {
beforeColon: false,
afterColon: true
}],
"indent": [2, 4],
"brace-style": [2, "1tbs"],
"comma-spacing": [2, {before: false, after: true}],
"comma-style": [2, "last"],
"consistent-this": [1, "self"],
"eol-last": 0,
"new-cap": 0,
"new-parens": 2,
"no-array-constructor": 2,
"no-mixed-spaces-and-tabs": 2,
"no-multiple-empty-lines": 2,
"semi-spacing": 2,
"dot-notation": 2,
"no-spaced-func": 1,
"no-shadow": 2,
"no-undef": 2,
"padded-blocks": [2, "never"],
"semi": [2, "always"],
"space-after-keywords": [2, "always"],
"space-infix-ops": 2,
"max-len" : [1, 120],
"consistent-return": 2,
"yoda": 2
}
}

@ -1,8 +1,7 @@
module.exports = function(grunt) { module.exports = function(grunt) {
// Project configuration. // Project configuration.
grunt.initConfig({ grunt.initConfig({
pkg : grunt.file.readJSON('package.json'), pkg: grunt.file.readJSON('package.json'),
karma: { karma: {
unit: { unit: {
configFile: 'karma.conf.js' configFile: 'karma.conf.js'
@ -10,67 +9,14 @@ module.exports = function(grunt) {
integration: { integration: {
configFile: 'karma-integration.conf.js' configFile: 'karma-integration.conf.js'
} }
},
uglify : {
options : {
banner : '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n',
preserveComments: 'some'
},
build : {
src : 'dist/<%= pkg.name %>.js',
dest : 'dist/<%= pkg.name %>.min.js'
}
},
jshint : {
all : ['Gruntfile.js', 'src/*.js']
},
requirejs : {
compile : {
options : {
almond : true,
wrap : {
startFile : 'build/start.frag',
endFile : 'build/end.frag'
},
"baseUrl" : "src",
"name" : "quagga",
"useStrict": true,
"out" : "dist/quagga.js",
"include" : ['quagga'],
"optimize" : "none",
"findNestedDependencies" : true,
"skipSemiColonInsertion" : true,
"shim" : {
"typedefs" : {
"deps" : [],
"exports" : "typedefs"
}
},
"paths" : {
"typedefs" : "typedefs",
"gl-matrix": "../node_modules/gl-matrix/dist/gl-matrix-min"
}
}
}
} }
}); });
// Load the plugin that provides the "uglify" task.
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-requirejs'); grunt.loadNpmTasks('grunt-requirejs');
grunt.loadNpmTasks('grunt-karma'); grunt.loadNpmTasks('grunt-karma');
grunt.loadTasks('tasks'); grunt.loadTasks('tasks');
grunt.registerTask('build', ['check', 'requirejs']);
grunt.registerTask('check', ['jshint']);
grunt.registerTask('dist', ['build', 'uglify', 'uglyasm']);
grunt.registerTask('test', ['karma']); grunt.registerTask('test', ['karma']);
grunt.registerTask('integrationtest', ['karma:integration']); grunt.registerTask('integrationtest', ['karma:integration']);
};
grunt.registerTask('default', ['build']);
};

@ -1,7 +1,7 @@
quaggaJS quaggaJS
======== ========
- [Changelog](#changelog) (2015-09-15) - [Changelog](#changelog) (2015-10-13)
## What is QuaggaJS? ## What is QuaggaJS?
@ -34,9 +34,9 @@ be aligned with the viewport.
In order to take full advantage of quaggaJS, the browser needs to support the In order to take full advantage of quaggaJS, the browser needs to support the
`getUserMedia` API which is already implemented in recent versions of Firefox, `getUserMedia` API which is already implemented in recent versions of Firefox,
Chrome, IE (Edge) and Opera. The API is also available on their mobile 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 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 to the camera yet, neither on desktop, nor on mobile. You can check
[caniuse][caniuse_getusermedia] for updates. [caniuse][caniuse_getusermedia] for updates.
In cases where real-time decoding is not needed, or the platform does not In cases where real-time decoding is not needed, or the platform does not
@ -90,11 +90,15 @@ You can build the library yourself by simply cloning the repo and typing:
```console ```console
> npm install > npm install
> grunt dist > npm run build
``` ```
This grunt task builds a non optimized version `quagga.js` and a minified This npm script builds a non optimized version `quagga.js` and a minified
version `quagga.min.js` and places both files in the `dist` folder. version `quagga.min.js` and places both files in the `dist` folder.
Additionally, a `quagga.map` source-map is placed alongside these files. This
file is only valid for the non-uglified version `quagga.js` because the
minified version is altered after compression and does not align with the map
file any more.
## API ## API
@ -360,7 +364,7 @@ automatically generated in the coverage/ folder.
```console ```console
> npm install > npm install
> grunt test > npm run test
``` ```
## Image Debugging ## Image Debugging
@ -430,6 +434,13 @@ on the ``singleChannel`` flag in the configuration when using ``decodeSingle``.
## <a name="changelog">Changelog</a> ## <a name="changelog">Changelog</a>
### 2015-10-13
Take a look at the release-notes ([0.8.0]
(https://github.com/serratus/quaggaJS/releases/tag/v0.8.0))
- Improvements
- Replaced RequireJS with webpack
### 2015-09-15 ### 2015-09-15
Take a look at the release-notes ([0.7.0] Take a look at the release-notes ([0.7.0]
(https://github.com/serratus/quaggaJS/releases/tag/v0.7.0)) (https://github.com/serratus/quaggaJS/releases/tag/v0.7.0))

19977
dist/quagga.js vendored

File diff suppressed because one or more lines are too long

13
dist/quagga.min.js vendored

File diff suppressed because one or more lines are too long

@ -101,8 +101,8 @@
&copy; Copyright by Christoph Oberhofer &copy; Copyright by Christoph Oberhofer
</p> </p>
</footer> </footer>
<script src="../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="../dist/quagga.js" type="text/javascript"></script> <script src="../dist/quagga.js" type="text/javascript"></script>
<script src="file_input.js" type="text/javascript"></script> <script src="file_input.js" type="text/javascript"></script>
</body> </body>
</html> </html>

@ -1,101 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<title>index</title>
<meta name="description" content=""/>
<meta name="author" content="Christoph Oberhofer"/>
<meta name="viewport" content="width=device-width; initial-scale=1.0"/>
<link rel="stylesheet" type="text/css" href="css/styles.css"/>
</head>
<body>
<header>
<div class="headline">
<h1>QuaggaJS</h1>
<h2>An advanced barcode-scanner written in JavaScript</h2>
</div>
</header>
<section id="container" class="container">
<h3>Working with file-input</h3>
<p>This example let's you select an image from your local filesystem.
QuaggaJS then tries to decode the barcode using
the preferred method (<strong>Code128</strong> or <strong>EAN</strong>).
There is no server interaction needed as the
file is simply accessed through the <a
href="http://www.w3.org/TR/file-upload/">File API</a>.</p>
<p>This also works great on a wide range of mobile-phones where the camera
access through <code>getUserMedia</code> is still very limited.</p>
<div class="controls">
<fieldset class="input-group">
<input type="file" capture/>
<button>Rerun</button>
</fieldset>
<fieldset class="reader-config-group">
<label>
<span>Barcode-Type</span>
<select name="decoder_readers">
<option value="code_128" selected="selected">Code 128</option>
<option value="code_39">Code 39</option>
<option value="code_39_vin">Code 39 VIN</option>
<option value="ean">EAN</option>
<option value="ean_8">EAN-8</option>
<option value="upc">UPC</option>
<option value="upc_e">UPC-E</option>
<option value="codabar">Codabar</option>
</select>
</label>
<label>
<span>Resolution (long side)</span>
<select name="input-stream_size">
<option value="320">320px</option>
<option selected="selected" value="640">640px</option>
<option value="800">800px</option>
<option value="1280">1280px</option>
<option value="1600">1600px</option>
<option value="1920">1920px</option>
</select>
</label>
<label>
<span>Patch-Size</span>
<select name="locator_patch-size">
<option value="x-small">x-small</option>
<option value="small">small</option>
<option selected="selected" value="medium">medium</option>
<option value="large">large</option>
<option value="x-large">x-large</option>
</select>
</label>
<label>
<span>Half-Sample</span>
<input type="checkbox" checked="checked"
name="locator_half-sample" />
</label>
<label>
<span>Single Channel</span>
<input type="checkbox" name="input-stream_single-channel" />
</label>
</fieldset>
</div>
<div id="result_strip">
<ul class="thumbnails"></ul>
</div>
<div id="interactive" class="viewport"></div>
<div id="debug" class="detection"></div>
</section>
<footer>
<p>
&copy; Copyright by Christoph Oberhofer
</p>
</footer>
<script src="../src/vendor/jquery-1.9.0.min.js" type="text/javascript"></script>
<script data-main="file_input_require.js" src="../node_modules/requirejs/require.js"></script>
</body>
</html>

@ -1,161 +0,0 @@
requirejs.config({
"baseUrl" : "../src",
"shim" : {
"typedefs" : {
"deps" : [],
"exports" : "typedefs"
}
},
"paths" : {
"typedefs" : "typedefs",
"gl-matrix": "../node_modules/gl-matrix/dist/gl-matrix-min"
}
});
requirejs(['quagga'], function(Quagga) {
var App = {
init: function() {
App.attachListeners();
},
attachListeners: function() {
var self = this;
$(".controls input[type=file]").on("change", function(e) {
if (e.target.files && e.target.files.length) {
App.decode(URL.createObjectURL(e.target.files[0]));
}
});
$(".controls button").on("click", function(e) {
var input = document.querySelector(".controls input[type=file]");
if (input.files && input.files.length) {
App.decode(URL.createObjectURL(input.files[0]));
}
});
$(".controls .reader-config-group").on("change", "input, select", function(e) {
e.preventDefault();
var $target = $(e.target),
value = $target.attr("type") === "checkbox" ? $target.prop("checked") : $target.val(),
name = $target.attr("name"),
state = self._convertNameToState(name);
console.log("Value of "+ state + " changed to " + value);
self.setState(state, value);
});
},
_accessByPath: function(obj, path, val) {
var parts = path.split('.'),
depth = parts.length,
setter = (typeof val !== "undefined") ? true : false;
return parts.reduce(function(o, key, i) {
if (setter && (i + 1) === depth) {
o[key] = val;
}
return key in o ? o[key] : {};
}, obj);
},
_convertNameToState: function(name) {
return name.replace("_", ".").split("-").reduce(function(result, value) {
return result + value.charAt(0).toUpperCase() + value.substring(1);
});
},
detachListeners: function() {
$(".controls input[type=file]").off("change");
$(".controls .reader-config-group").off("change", "input, select");
$(".controls button").off("click");
},
decode: function(src) {
var self = this,
config = $.extend({}, self.state, {src: src});
Quagga.decodeSingle(config, function(result) {});
},
setState: function(path, value) {
var self = this;
if (typeof self._accessByPath(self.inputMapper, path) === "function") {
value = self._accessByPath(self.inputMapper, path)(value);
}
self._accessByPath(self.state, path, value);
console.log(JSON.stringify(self.state));
App.detachListeners();
App.init();
},
inputMapper: {
inputStream: {
size: function(value){
return parseInt(value);
}
},
numOfWorkers: function(value) {
return parseInt(value);
},
decoder: {
readers: function(value) {
return [value + "_reader"];
}
}
},
state: {
inputStream: {
size: 640,
singleChannel: false
},
locator: {
patchSize: "medium",
halfSample: true,
showCanvas: true
},
numOfWorkers: 0,
decoder: {
readers: ["code_128_reader"],
showFrequency: true,
showPattern: true
},
locate: true,
src: null
}
};
App.init();
Quagga.onProcessed(function(result) {
var drawingCtx = Quagga.canvas.ctx.overlay,
drawingCanvas = Quagga.canvas.dom.overlay;
if (result) {
if (result.boxes) {
drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
result.boxes.filter(function (box) {
return box !== result.box;
}).forEach(function (box) {
Quagga.ImageDebug.drawPath(box, {x: 0, y: 1}, drawingCtx, {color: "green", lineWidth: 2});
});
}
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, drawingCtx, {color: "#00F", lineWidth: 2});
}
if (result.codeResult && result.codeResult.code) {
Quagga.ImageDebug.drawPath(result.line, {x: 'x', y: 'y'}, drawingCtx, {color: 'red', lineWidth: 3});
}
}
});
Quagga.onDetected(function(result) {
var code = result.codeResult.code,
$node,
canvas = Quagga.canvas.dom.image;
$node = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>');
$node.find("img").attr("src", canvas.toDataURL());
$node.find("h4.code").html(code + " (" + result.codeResult.format + ")");
$("#result_strip ul.thumbnails").prepend($node);
});
});

@ -1,32 +1,52 @@
var path = require('path');
module.exports = function(config) { module.exports = function(config) {
config.set({ config.set({
basePath: '', basePath: '',
frameworks: ['mocha', 'requirejs', 'chai', 'sinon', 'sinon-chai'], frameworks: ['mocha', 'chai', 'sinon', 'sinon-chai'],
files: [ files: [
'test-main.js', 'test/test-main-integration.js',
'src/typedefs.js', {pattern: 'test/integration/**/*.js', included: false},
{pattern: 'node_modules/async/lib/async.js', included: false}, {pattern: 'test/fixtures/**/*.*', included: false}
{pattern: 'node_modules/gl-matrix/dist/gl-matrix-min.js', included: false}, ],
{pattern: 'src/*.js', included: false}, preprocessors: {
{pattern: 'spec/**/*integration.spec.js', included: false}, 'test/test-main-integration.js': ['webpack']
{pattern: 'test/**/*.*', included: false} },
], webpack: {
exclude: [ module: {
], preLoaders: [
plugins: [ {
'karma-chrome-launcher', test: /\.js$/,
'karma-mocha', exclude: [
'karma-requirejs', /node_modules/
'karma-chai', ],
'karma-sinon', loader: 'babel'
'karma-sinon-chai' }
], ]
reporters: ['progress'], },
port: 9876, resolve: {
colors: true, extensions: ['', '.js', '.jsx'],
logLevel: config.LOG_INFO, alias: {
autoWatch: true, 'input_stream$': path.resolve(__dirname, 'src/input_stream'),
browsers: ['Chrome'], 'frame_grabber$': path.resolve(__dirname, 'src/frame_grabber')
singleRun: false }
}); }
},
plugins: [
'karma-chrome-launcher',
'karma-mocha',
'karma-requirejs',
'karma-chai',
'karma-sinon',
'karma-sinon-chai',
require('karma-webpack')
],
reporters: ['progress'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
}; };

@ -1,42 +1,64 @@
var path = require('path');
module.exports = function(config) { module.exports = function(config) {
config.set({ config.set({
basePath: '', basePath: '',
frameworks: ['mocha', 'requirejs', 'chai', 'sinon', 'sinon-chai'], frameworks: ['source-map-support', 'mocha', 'chai', 'sinon', 'sinon-chai'],
files: [ files: [
'test-main.js', 'test/test-main.js',
'src/typedefs.js', {pattern: 'test/spec/**/*.js', included: false}
{pattern: 'node_modules/async/lib/async.js', included: false}, ],
{pattern: 'node_modules/gl-matrix/dist/gl-matrix-min.js', included: false}, preprocessors: {
{pattern: 'src/*.js', included: false}, 'test/test-main.js': ['webpack']
{pattern: 'spec/**/*.js', included: false}, },
{pattern: 'test/**/*.*', included: false} webpack: {
], module: {
exclude: [ preLoaders: [
'spec/**/*integration.spec.js' {
], test: /\.js$/,
preprocessors: { exclude: [
'src/*.js': ['coverage'] /node_modules/
}, ],
plugins: [ loader: 'babel'
'karma-chrome-launcher', },
'karma-coverage', {
'karma-mocha', test: /\.js$/,
'karma-requirejs', include: [
'karma-chai', path.resolve('src')
'karma-sinon', ],
'karma-sinon-chai', exclude: /node_modules/,
'karma-phantomjs-launcher' loader: 'isparta'
], }
reporters: ['progress', 'coverage'], ]
port: 9876, },
colors: true, resolve: {
logLevel: config.LOG_INFO, extensions: ['', '.js', '.jsx'],
autoWatch: true, alias: {
browsers: ['Chrome'], 'input_stream$': path.resolve(__dirname, 'src/input_stream'),
singleRun: false, 'frame_grabber$': path.resolve(__dirname, 'src/frame_grabber')
coverageReporter: { }
type : 'html', },
dir : 'coverage/' },
} plugins: [
}); 'karma-chrome-launcher',
'karma-coverage',
'karma-mocha',
'karma-chai',
'karma-sinon',
'karma-sinon-chai',
'karma-source-map-support',
require('karma-webpack')
],
reporters: ['progress', 'coverage'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
coverageReporter: {
type: 'html',
dir: 'coverage/'
}
});
}; };

@ -1,101 +1,95 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ const CVUtils = require('../src/cv_utils'),
/* global define */ Ndarray = require("ndarray"),
Interp2D = require("ndarray-linear-interpolate").d2;
define(["cv_utils"], function(CVUtils) {
"use strict"; var FrameGrabber = {};
var Ndarray = require("ndarray"), FrameGrabber.create = function(inputStream) {
Interp2D = require("ndarray-linear-interpolate").d2; var _that = {},
_streamConfig = inputStream.getConfig(),
var FrameGrabber = {}; _video_size = CVUtils.imageRef(inputStream.getRealWidth(), inputStream.getRealHeight()),
_canvasSize = inputStream.getCanvasSize(),
FrameGrabber.create = function(inputStream) { _size = CVUtils.imageRef(inputStream.getWidth(), inputStream.getHeight()),
var _that = {}, _topRight = inputStream.getTopRight(),
_streamConfig = inputStream.getConfig(), _data = new Uint8Array(_size.x * _size.y),
_video_size = CVUtils.imageRef(inputStream.getRealWidth(), inputStream.getRealHeight()), _grayData = new Uint8Array(_video_size.x * _video_size.y),
_canvasSize = inputStream.getCanvasSize(), _canvasData = new Uint8Array(_canvasSize.x * _canvasSize.y),
_size = CVUtils.imageRef(inputStream.getWidth(), inputStream.getHeight()), _grayImageArray = Ndarray(_grayData, [_video_size.y, _video_size.x]).transpose(1, 0),
_topRight = inputStream.getTopRight(), _canvasImageArray = Ndarray(_canvasData, [_canvasSize.y, _canvasSize.x]).transpose(1, 0),
_data = new Uint8Array(_size.x * _size.y), _targetImageArray = _canvasImageArray.hi(_topRight.x + _size.x, _topRight.y + _size.y).lo(_topRight.x, _topRight.y),
_grayData = new Uint8Array(_video_size.x * _video_size.y), _stepSizeX = _video_size.x/_canvasSize.x,
_canvasData = new Uint8Array(_canvasSize.x * _canvasSize.y), _stepSizeY = _video_size.y/_canvasSize.y;
_grayImageArray = Ndarray(_grayData, [_video_size.y, _video_size.x]).transpose(1, 0),
_canvasImageArray = Ndarray(_canvasData, [_canvasSize.y, _canvasSize.x]).transpose(1, 0), console.log("FrameGrabber", JSON.stringify({
_targetImageArray = _canvasImageArray.hi(_topRight.x + _size.x, _topRight.y + _size.y).lo(_topRight.x, _topRight.y), videoSize: _grayImageArray.shape,
_stepSizeX = _video_size.x/_canvasSize.x, canvasSize: _canvasImageArray.shape,
_stepSizeY = _video_size.y/_canvasSize.y; stepSize: [_stepSizeX, _stepSizeY],
size: _targetImageArray.shape,
console.log("FrameGrabber", JSON.stringify({ topRight: _topRight
videoSize: _grayImageArray.shape, }));
canvasSize: _canvasImageArray.shape,
stepSize: [_stepSizeX, _stepSizeY], /**
size: _targetImageArray.shape, * Uses the given array as frame-buffer
topRight: _topRight */
})); _that.attachData = function(data) {
_data = data;
/** };
* Uses the given array as frame-buffer
*/
_that.attachData = function(data) {
_data = data;
};
/**
* Returns the used frame-buffer
*/
_that.getData = function() {
return _data;
};
/**
* Fetches a frame from the input-stream and puts into the frame-buffer.
* The image-data is converted to gray-scale and then half-sampled if configured.
*/
_that.grab = function() {
var frame = inputStream.getFrame();
if (frame) { /**
this.scaleAndCrop(frame); * Returns the used frame-buffer
return true; */
} else { _that.getData = function() {
return false; return _data;
} };
};
_that.scaleAndCrop = function(frame) { /**
var x, * Fetches a frame from the input-stream and puts into the frame-buffer.
y; * The image-data is converted to gray-scale and then half-sampled if configured.
*/
_that.grab = function() {
var frame = inputStream.getFrame();
if (frame) {
this.scaleAndCrop(frame);
return true;
} else {
return false;
}
};
// 1. compute full-sized gray image _that.scaleAndCrop = function(frame) {
CVUtils.computeGray(frame.data, _grayData); var x,
y;
// 2. interpolate // 1. compute full-sized gray image
for (y = 0; y < _canvasSize.y; y++) { CVUtils.computeGray(frame.data, _grayData);
for (x = 0; x < _canvasSize.x; x++) {
_canvasImageArray.set(x, y, (Interp2D(_grayImageArray, x * _stepSizeX, y * _stepSizeY)) | 0);
}
}
// targetImageArray must be equal to targetSize // 2. interpolate
if (_targetImageArray.shape[0] !== _size.x || for (y = 0; y < _canvasSize.y; y++) {
_targetImageArray.shape[1] !== _size.y) { for (x = 0; x < _canvasSize.x; x++) {
throw new Error("Shapes do not match!"); _canvasImageArray.set(x, y, (Interp2D(_grayImageArray, x * _stepSizeX, y * _stepSizeY)) | 0);
} }
}
// 3. crop
for (y = 0; y < _size.y; y++) { // targetImageArray must be equal to targetSize
for (x = 0; x < _size.x; x++) { if (_targetImageArray.shape[0] !== _size.x ||
_data[y * _size.x + x] = _targetImageArray.get(x, y); _targetImageArray.shape[1] !== _size.y) {
} throw new Error("Shapes do not match!");
}
// 3. crop
for (y = 0; y < _size.y; y++) {
for (x = 0; x < _size.x; x++) {
_data[y * _size.x + x] = _targetImageArray.get(x, y);
} }
}, }
},
_that.getSize = function() { _that.getSize = function() {
return _size; return _size;
};
return _that;
}; };
return (FrameGrabber); return _that;
}); };
module.exports = FrameGrabber;

@ -1,160 +1,153 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ const GetPixels = require("get-pixels");
/* global define */
var InputStream = {};
define(function() {
"use strict"; InputStream.createImageStream = function() {
var that = {};
var GetPixels = require("get-pixels"); var _config = null;
var InputStream = {}; var width = 0,
height = 0,
InputStream.createImageStream = function() { frameIdx = 0,
var that = {}; paused = true,
var _config = null; loaded = false,
frame = null,
var width = 0, baseUrl,
height = 0, ended = false,
frameIdx = 0, size,
paused = true, calculatedWidth,
loaded = false, calculatedHeight,
frame = null, _eventNames = ['canrecord', 'ended'],
baseUrl, _eventHandlers = {},
ended = false, _topRight = {x: 0, y: 0},
size, _canvasSize = {x: 0, y: 0};
calculatedWidth,
calculatedHeight, function loadImages() {
_eventNames = ['canrecord', 'ended'], loaded = false;
_eventHandlers = {}, GetPixels(baseUrl, function(err, pixels) {
_topRight = {x: 0, y: 0}, if (err) {
_canvasSize = {x: 0, y: 0}; console.log(err);
exit(1);
function loadImages() { }
loaded = false; loaded = true;
GetPixels(baseUrl, function(err, pixels) { console.log(pixels.shape);
if (err) { frame = pixels;
console.log(err); width = pixels.shape[0];
exit(1); height = pixels.shape[1];
} calculatedWidth = _config.size ? width/height > 1 ? _config.size : Math.floor((width/height) * _config.size) : width;
loaded = true; calculatedHeight = _config.size ? width/height > 1 ? Math.floor((height/width) * _config.size) : _config.size : height;
console.log(pixels.shape);
frame = pixels; _canvasSize.x = calculatedWidth;
width = pixels.shape[0]; _canvasSize.y = calculatedHeight;
height = pixels.shape[1];
calculatedWidth = _config.size ? width/height > 1 ? _config.size : Math.floor((width/height) * _config.size) : width; setTimeout(function() {
calculatedHeight = _config.size ? width/height > 1 ? Math.floor((height/width) * _config.size) : _config.size : height; publishEvent("canrecord", []);
}, 0);
_canvasSize.x = calculatedWidth; });
_canvasSize.y = calculatedHeight; }
setTimeout(function() { function publishEvent(eventName, args) {
publishEvent("canrecord", []); var j,
}, 0); handlers = _eventHandlers[eventName];
});
} if (handlers && handlers.length > 0) {
for ( j = 0; j < handlers.length; j++) {
function publishEvent(eventName, args) { handlers[j].apply(that, args);
var j,
handlers = _eventHandlers[eventName];
if (handlers && handlers.length > 0) {
for ( j = 0; j < handlers.length; j++) {
handlers[j].apply(that, args);
}
} }
} }
}
that.trigger = publishEvent; that.trigger = publishEvent;
that.getWidth = function() { that.getWidth = function() {
return calculatedWidth; return calculatedWidth;
}; };
that.getHeight = function() { that.getHeight = function() {
return calculatedHeight; return calculatedHeight;
}; };
that.setWidth = function(width) { that.setWidth = function(width) {
calculatedWidth = width; calculatedWidth = width;
}; };
that.setHeight = function(height) { that.setHeight = function(height) {
calculatedHeight = height; calculatedHeight = height;
}; };
that.getRealWidth = function() { that.getRealWidth = function() {
return width; return width;
}; };
that.getRealHeight = function() { that.getRealHeight = function() {
return height; return height;
}; };
that.setInputStream = function(stream) { that.setInputStream = function(stream) {
_config = stream; _config = stream;
baseUrl = _config.src; baseUrl = _config.src;
size = 1; size = 1;
loadImages(); loadImages();
}; };
that.ended = function() { that.ended = function() {
return ended; return ended;
}; };
that.setAttribute = function() {}; that.setAttribute = function() {};
that.getConfig = function() { that.getConfig = function() {
return _config; return _config;
}; };
that.pause = function() { that.pause = function() {
paused = true; paused = true;
}; };
that.play = function() { that.play = function() {
paused = false; paused = false;
}; };
that.setCurrentTime = function(time) { that.setCurrentTime = function(time) {
frameIdx = time; frameIdx = time;
}; };
that.addEventListener = function(event, f) { that.addEventListener = function(event, f) {
if (_eventNames.indexOf(event) !== -1) { if (_eventNames.indexOf(event) !== -1) {
if (!_eventHandlers[event]) { if (!_eventHandlers[event]) {
_eventHandlers[event] = []; _eventHandlers[event] = [];
}
_eventHandlers[event].push(f);
} }
}; _eventHandlers[event].push(f);
}
that.setTopRight = function(topRight) { };
_topRight.x = topRight.x;
_topRight.y = topRight.y;
};
that.getTopRight = function() { that.setTopRight = function(topRight) {
return _topRight; _topRight.x = topRight.x;
}; _topRight.y = topRight.y;
};
that.setCanvasSize = function(size) { that.getTopRight = function() {
_canvasSize.x = size.x; return _topRight;
_canvasSize.y = size.y; };
};
that.getCanvasSize = function() { that.setCanvasSize = function(size) {
return _canvasSize; _canvasSize.x = size.x;
}; _canvasSize.y = size.y;
};
that.getFrame = function() { that.getCanvasSize = function() {
if (!loaded){ return _canvasSize;
return null; };
}
return frame;
};
return that; that.getFrame = function() {
if (!loaded){
return null;
}
return frame;
}; };
return (InputStream); return that;
}); };
module.exports = InputStream;

File diff suppressed because it is too large Load Diff

@ -1,18 +1,23 @@
{ {
"name": "quagga", "name": "quagga",
"version": "0.7.0", "version": "0.8.0",
"description": "An advanced barcode-scanner written in JavaScript", "description": "An advanced barcode-scanner written in JavaScript",
"main": "lib/quagga.js", "main": "lib/quagga.js",
"browser": "dist/quagga.js", "browser": "dist/quagga.min.js",
"devDependencies": { "devDependencies": {
"async": "^1.4.2", "async": "^1.4.2",
"babel-core": "^5.8.25",
"babel-eslint": "^4.1.3",
"babel-loader": "^5.3.2",
"chai": "^3.2.0", "chai": "^3.2.0",
"core-js": "^1.2.1",
"grunt": "^0.4.5", "grunt": "^0.4.5",
"grunt-contrib-jshint": "^0.11.3", "grunt-contrib-jshint": "^0.11.3",
"grunt-contrib-nodeunit": "^0.4.1", "grunt-contrib-nodeunit": "^0.4.1",
"grunt-contrib-uglify": "^0.9.2", "grunt-contrib-uglify": "^0.9.2",
"grunt-karma": "^0.12.1", "grunt-karma": "^0.12.1",
"grunt-requirejs": "^0.4.2", "grunt-requirejs": "^0.4.2",
"isparta-loader": "^1.0.0",
"karma": "^0.13.9", "karma": "^0.13.9",
"karma-chai": "0.1.0", "karma-chai": "0.1.0",
"karma-chrome-launcher": "^0.2.0", "karma-chrome-launcher": "^0.2.0",
@ -22,14 +27,20 @@
"karma-requirejs": "~0.2.2", "karma-requirejs": "~0.2.2",
"karma-sinon": "^1.0.4", "karma-sinon": "^1.0.4",
"karma-sinon-chai": "~0.2.0", "karma-sinon-chai": "~0.2.0",
"karma-source-map-support": "^1.1.0",
"karma-webpack": "^1.7.0",
"mocha": "^2.3.2", "mocha": "^2.3.2",
"sinon": "^1.16.1" "sinon": "^1.16.1",
"webpack": "^1.12.2"
}, },
"directories": { "directories": {
"doc": "doc" "doc": "doc"
}, },
"scripts": { "scripts": {
"test": "karma start" "test": "grunt test",
"integrationtest": "grunt integrationtest",
"build": "webpack && webpack --config webpack.config.min.js && grunt uglyasm && webpack --config webpack.node.config.js",
"watch": "webpack --watch"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -56,8 +67,8 @@
"dependencies": { "dependencies": {
"get-pixels": "^3.2.3", "get-pixels": "^3.2.3",
"gl-matrix": "^2.3.1", "gl-matrix": "^2.3.1",
"lodash": "^3.10.1",
"ndarray": "^1.0.18", "ndarray": "^1.0.18",
"ndarray-linear-interpolate": "^1.0.0", "ndarray-linear-interpolate": "^1.0.0"
"requirejs": "^2.1.20"
} }
} }

@ -0,0 +1,33 @@
var ConcatSource = require("webpack-core/lib/ConcatSource");
var OriginalSource = require("webpack-core/lib/OriginalSource");
function MyUmdPlugin(options) {
this.name = options.library;
}
module.exports = MyUmdPlugin;
MyUmdPlugin.prototype.apply = function(compiler) {
compiler.plugin("this-compilation", function(compilation) {
var mainTemplate = compilation.mainTemplate;
compilation.templatesPlugin("render-with-entry", function(source, chunk, hash) {
var amdFactory = "factory";
return new ConcatSource(new OriginalSource(
"(function webpackUniversalModuleDefinition(root, factory) {\n" +
" if(typeof exports === 'object' && typeof module === 'object')\n" +
" module.exports = factory(factory.toString());\n" +
" else if(typeof exports === 'object')\n" +
" exports[\"" + this.name + "\"] = factory(factory.toString());\n" +
" else\n" +
" root[\"" + this.name + "\"] = factory(factory.toString());\n" +
"})(this, function(__factorySource__) {\nreturn ", "webpack/myModuleDefinition"), source, "\n});\n");
}.bind(this));
mainTemplate.plugin("global-hash-paths", function(paths) {
if(this.name) paths = paths.concat(this.name);
return paths;
}.bind(this));
mainTemplate.plugin("hash", function(hash) {
hash.update("umd");
hash.update(this.name + "");
}.bind(this));
}.bind(this));
};

@ -1,53 +0,0 @@
define(['array_helper'], function(ArrayHelper){
describe('init', function() {
it('initializes an array with the given value', function() {
var input = [0, 0, 0];
ArrayHelper.init(input, 5);
expect(input).to.deep.equal([5, 5, 5]);
});
});
describe('shuffle', function() {
before(function() {
sinon.stub(Math, 'random').returns(0.5);
});
after(function() {
sinon.restore(Math);
});
it('shuffles the content of an array', function() {
var input = [1, 2, 3];
expect(ArrayHelper.shuffle(input)).to.deep.equal([3, 1, 2]);
});
});
describe('toPointList', function() {
it('converts an Array to a List of poitns', function() {
var input = [[1, 2], [2, 2], [3, 2]];
expect(ArrayHelper.toPointList(input)).to.equal("[[1,2],\r\n[2,2],\r\n[3,2]]");
});
});
describe('threshold', function() {
it('returns all elements above the given threshold', function() {
var input = [1, 2, 3];
expect(ArrayHelper.threshold(input, 2, function(score) {
return score * 1.5;
})).to.deep.equal([2, 3]);
});
});
describe('maxIndex', function() {
it('gets the index of the biggest element in the array', function() {
var input = [1, 2, 3];
expect(ArrayHelper.maxIndex(input)).to.equal(2);
});
});
describe('max', function() {
it('gets the biggest element in the array', function() {
var input = [1, 3, 2];
expect(ArrayHelper.max(input)).to.equal(3);
});
});
});

@ -1,131 +0,0 @@
define(['barcode_locator', 'config', 'html_utils'],
function(BarcodeLocator, Config, HtmlUtils){
describe('checkImageConstraints', function() {
var config,
inputStream,
imageSize,
streamConfig = {};
beforeEach(function() {
imageSize = {
x: 640, y: 480
};
config = HtmlUtils.mergeObjects({}, Config);
inputStream = {
getWidth: function() {
return imageSize.x;
},
getHeight: function() {
return imageSize.y;
},
setWidth: function() {},
setHeight: function() {},
setTopRight: function() {},
setCanvasSize: function() {},
getConfig: function() {
return streamConfig;
}
};
sinon.stub(inputStream, "setWidth", function(width) {
imageSize.x = width;
});
sinon.stub(inputStream, "setHeight", function(height) {
imageSize.y = height;
});
sinon.stub(inputStream, "setTopRight");
sinon.stub(inputStream, "setCanvasSize");
});
afterEach(function() {
inputStream.setWidth.restore();
inputStream.setHeight.restore();
});
it('should not adjust the image-size if not needed', function() {
var expected = {x: imageSize.x, y: imageSize.y};
BarcodeLocator.checkImageConstraints(inputStream, config.locator);
expect(inputStream.getWidth()).to.be.equal(expected.x);
expect(inputStream.getHeight()).to.be.equal(expected.y);
});
it('should adjust the image-size', function() {
var expected = {x: imageSize.x, y: imageSize.y};
config.locator.halfSample = true;
imageSize.y += 1;
BarcodeLocator.checkImageConstraints(inputStream, config.locator);
expect(inputStream.getWidth()).to.be.equal(expected.x);
expect(inputStream.getHeight()).to.be.equal(expected.y);
});
it('should adjust the image-size', function() {
var expected = {x: imageSize.x, y: imageSize.y};
imageSize.y += 1;
config.locator.halfSample = false;
BarcodeLocator.checkImageConstraints(inputStream, config.locator);
expect(inputStream.getHeight()).to.be.equal(expected.y);
expect(inputStream.getWidth()).to.be.equal(expected.x);
});
it("should take the defined area into account", function() {
var expectedSize = {
x: 420,
y: 315
},
expectedTopRight = {
x: 115,
y: 52
},
expectedCanvasSize = {
x: 640,
y: 480
};
streamConfig.area = {
top: "11%",
right: "15%",
bottom: "20%",
left: "18%"
};
config.locator.halfSample = false;
BarcodeLocator.checkImageConstraints(inputStream, config.locator);
expect(inputStream.getHeight()).to.be.equal(expectedSize.y);
expect(inputStream.getWidth()).to.be.equal(expectedSize.x);
expect(inputStream.setTopRight.getCall(0).args[0]).to.deep.equal(expectedTopRight);
expect(inputStream.setCanvasSize.getCall(0).args[0]).to.deep.equal(expectedCanvasSize);
});
it("should return the original size if set to full image", function() {
var expectedSize = {
x: 640,
y: 480
},
expectedTopRight = {
x: 0,
y: 0
},
expectedCanvasSize = {
x: 640,
y: 480
};
streamConfig.area = {
top: "0%",
right: "0%",
bottom: "0%",
left: "0%"
};
config.locator.halfSample = false;
BarcodeLocator.checkImageConstraints(inputStream, config.locator);
expect(inputStream.getHeight()).to.be.equal(expectedSize.y);
expect(inputStream.getWidth()).to.be.equal(expectedSize.x);
expect(inputStream.setTopRight.getCall(0).args[0]).to.deep.equal(expectedTopRight);
expect(inputStream.setCanvasSize.getCall(0).args[0]).to.deep.equal(expectedCanvasSize);
});
});
});

@ -1,123 +0,0 @@
define(['camera_access'], function(CameraAccess){
var originalURL,
originalMediaStreamTrack,
video,
stream;
beforeEach(function() {
var tracks = [{
stop: function() {}
}];
originalURL = window.URL;
originalMediaStreamTrack = window.MediaStreamTrack;
window.MediaStreamTrack = {};
window.URL = null;
stream = {
getVideoTracks: function() {
return tracks;
}
};
sinon.spy(tracks[0], "stop");
video = {
src: null,
addEventListener: function() {
},
removeEventListener: function() {
},
play: function() {
},
videoWidth: 320,
videoHeight: 480
};
sinon.stub(video, "addEventListener", function(event, cb) {
cb();
});
sinon.stub(video, "play");
});
afterEach(function() {
window.URL = originalURL;
window.MediaStreamTrack = originalMediaStreamTrack;
});
describe('success', function() {
beforeEach(function() {
sinon.stub(navigator, "getUserMedia", function(constraints, success) {
success(stream);
});
});
afterEach(function() {
navigator.getUserMedia.restore();
});
describe('request', function () {
it('should request the camera', function (done) {
CameraAccess.request(video, {}, function () {
expect(navigator.getUserMedia.calledOnce).to.equal(true);
expect(video.src).to.deep.equal(stream);
done();
});
});
});
describe('release', function () {
it('should release the camera', function (done) {
CameraAccess.request(video, {}, function () {
expect(video.src).to.deep.equal(stream);
CameraAccess.release();
expect(video.src.getVideoTracks()).to.have.length(1);
expect(video.src.getVideoTracks()[0].stop.calledOnce).to.equal(true);
done();
});
});
});
});
describe('failure', function() {
describe("permission denied", function(){
before(function() {
sinon.stub(navigator, "getUserMedia", function(constraints, success, failure) {
failure(new Error());
});
});
after(function() {
navigator.getUserMedia.restore();
});
it('should throw if getUserMedia not available', function(done) {
CameraAccess.request(video, {}, function(err) {
expect(err).to.be.defined;
done();
});
});
});
describe("not available", function(){
var originalGetUserMedia;
before(function() {
originalGetUserMedia = navigator.getUserMedia;
navigator.getUserMedia = undefined;
});
after(function() {
navigator.getUserMedia = originalGetUserMedia;
});
it('should throw if getUserMedia not available', function(done) {
CameraAccess.request(video, {}, function(err) {
expect(err).to.be.defined;
done();
});
});
});
});
});

@ -1,145 +0,0 @@
define(['cv_utils'], function(CVUtils){
describe('imageRef', function() {
it('gets the image Reference for a coordinate', function() {
var res = CVUtils.imageRef(1, 2);
expect(res.x).to.equal(1);
expect(res.y).to.equal(2);
expect(res.toVec2()[0]).to.equal(1);
});
});
describe('calculatePatchSize', function() {
it('should not throw an error in case of valid image size', function() {
var expected = {x: 32, y: 32},
patchSize = CVUtils.calculatePatchSize("medium", {x: 640, y: 480});
expect(patchSize).to.be.deep.equal(expected);
});
it('should thow an error if image size it not valid', function() {
var expected = {x: 32, y: 32},
patchSize = CVUtils.calculatePatchSize("medium", {x: 640, y: 480});
expect(patchSize).to.be.deep.equal(expected);
});
});
describe('_parseCSSDimensionValues', function() {
it("should convert a percentual value correctly", function() {
var expected = {
value: 10,
unit: "%"
},
result = CVUtils._parseCSSDimensionValues("10%");
expect(result).to.be.deep.equal(expected);
});
it("should convert a 0% value correctly", function() {
var expected = {
value: 100,
unit: "%"
},
result = CVUtils._parseCSSDimensionValues("100%");
expect(result).to.be.deep.equal(expected);
});
it("should convert a 100% value correctly", function() {
var expected = {
value: 0,
unit: "%"
},
result = CVUtils._parseCSSDimensionValues("0%");
expect(result).to.be.deep.equal(expected);
});
it("should convert a pixel value to percentage", function() {
var expected = {
value: 26.3,
unit: "%"
},
result = CVUtils._parseCSSDimensionValues("26.3px");
console.log(result);
expect(result).to.be.deep.equal(expected);
});
});
describe("_dimensionsConverters", function(){
var context;
beforeEach(function() {
context = {
width: 640,
height: 480
};
});
it("should convert a top-value correclty", function() {
var expected = 48,
result = CVUtils._dimensionsConverters.top({value: 10, unit: "%"}, context);
expect(result).to.be.equal(expected);
});
it("should convert a right-value correclty", function() {
var expected = 640 - 128,
result = CVUtils._dimensionsConverters.right({value: 20, unit: "%"}, context);
expect(result).to.be.equal(expected);
});
it("should convert a bottom-value correclty", function() {
var expected = 480 - 77,
result = CVUtils._dimensionsConverters.bottom({value: 16, unit: "%"}, context);
expect(result).to.be.equal(expected);
});
it("should convert a left-value correclty", function() {
var expected = 57,
result = CVUtils._dimensionsConverters.left({value: 9, unit: "%"}, context);
expect(result).to.be.equal(expected);
});
});
describe("computeImageArea", function() {
it("should calculate an image-area", function() {
var expected = {
sx: 115,
sy: 48,
sw: 429,
sh: 336
},
result = CVUtils.computeImageArea(640, 480, {
top: "10%",
right: "15%",
bottom: "20%",
left: "18%"
});
expect(result).to.be.deep.equal(expected);
});
it("should calculate full image-area", function() {
var expected = {
sx: 0,
sy: 0,
sw: 640,
sh: 480
},
result = CVUtils.computeImageArea(640, 480, {
top: "0%",
right: "0%",
bottom: "0%",
left: "0%"
});
expect(result).to.be.deep.equal(expected);
});
});
});

@ -1,114 +0,0 @@
define(['events'], function(Events){
beforeEach(function() {
Events.unsubscribe();
});
describe("subscribe", function() {
it("should call one callback for a single event", function() {
var callbackA = sinon.stub(),
callbackB = sinon.stub();
Events.subscribe("test", callbackA);
Events.subscribe("test", callbackB);
Events.publish("test");
expect(callbackA.calledOnce).to.be.equal(true);
expect(callbackB.calledOnce).to.be.equal(true);
});
it("should call one callback twice if published twice", function() {
var callback = sinon.stub();
Events.subscribe("test", callback);
Events.publish("test");
Events.publish("test");
expect(callback.calledTwice).to.be.equal(true);
});
it("should call the callback asynchronuously", function(done) {
var test = {
callback: function() {
}
};
sinon.stub(test, "callback", function() {
expect(test.callback.calledOnce).to.be.true;
done();
});
Events.subscribe("test", test.callback, true);
Events.publish("test");
expect(test.callback.called).to.be.false;
});
});
describe("once", function() {
it("should call the callback once, even when published twice", function() {
var callbackA = sinon.stub(),
callbackB = sinon.stub();
Events.once("test", callbackA);
Events.subscribe("test", callbackB);
Events.publish("test");
Events.publish("test");
expect(callbackA.calledOnce).to.be.equal(true);
expect(callbackB.calledTwice).to.be.equal(true);
});
});
describe("unsubscribe", function() {
it("should unsubscribe all callbacks from a single event", function() {
var callbackA = sinon.stub(),
callbackB = sinon.stub(),
callbackC = sinon.stub();
Events.subscribe("test", callbackA);
Events.subscribe("test", callbackB);
Events.subscribe("testC", callbackC);
Events.publish("test");
expect(callbackC.called).to.be.equal(false);
expect(callbackA.calledOnce).to.be.equal(true);
expect(callbackB.calledOnce).to.be.equal(true);
Events.publish("testC");
expect(callbackC.calledOnce).to.be.equal(true);
expect(callbackA.calledOnce).to.be.equal(true);
expect(callbackB.calledOnce).to.be.equal(true);
Events.unsubscribe("test");
Events.publish("test");
expect(callbackC.calledOnce).to.be.equal(true);
expect(callbackA.calledOnce).to.be.equal(true);
expect(callbackB.calledOnce).to.be.equal(true);
});
it("should unsubscribe a single callback from a single event", function() {
var callbackA = sinon.stub(),
callbackB = sinon.stub();
Events.subscribe("test", callbackA);
Events.subscribe("test", callbackB);
Events.publish("test");
expect(callbackA.calledOnce).to.be.equal(true);
expect(callbackB.calledOnce).to.be.equal(true);
Events.unsubscribe("test", callbackB);
Events.publish("test");
expect(callbackA.calledTwice).to.be.equal(true);
expect(callbackB.calledOnce).to.be.equal(true);
});
});
});

@ -1,248 +0,0 @@
define(['quagga', 'async'], function(Quagga, async) {
describe('decodeSingle', function () {
var baseFolder = "base/test/fixtures/";
function generateConfig() {
return {
inputStream: {
size: 640
},
locator: {
patchSize: "medium",
halfSample: true
},
numOfWorkers: 0,
decoder: {
readers: ["ean_reader"]
},
locate: true,
src: null
};
}
this.timeout(10000);
function _runTestSet(testSet, config) {
var readers = config.decoder.readers.slice(),
format,
folder;
if (typeof readers[0] === 'string'){
format = readers[0];
} else {
format = readers[0].format;
}
folder = baseFolder + format.split('_').slice(0, -1).join('_') + "/";
it('should decode ' + folder + " correctly", function(done) {
async.eachSeries(testSet, function (sample, callback) {
config.src = folder + sample.name;
config.readers = readers;
Quagga.decodeSingle(config, function(result) {
console.log(sample.name);
expect(result.codeResult.code).to.equal(sample.result);
expect(result.codeResult.format).to.equal(sample.format);
callback();
});
}, function() {
done();
});
});
}
describe("EAN", function() {
var config = generateConfig(),
testSet = [
{"name": "image-001.jpg", "result": "3574660239843"},
{"name": "image-002.jpg", "result": "8032754490297"},
{"name": "image-003.jpg", "result": "4006209700068"},
/* {"name": "image-004.jpg", "result": "9002233139084"}, */
/* {"name": "image-005.jpg", "result": "8004030044005"}, */
{"name": "image-006.jpg", "result": "4003626011159"},
{"name": "image-007.jpg", "result": "2111220009686"},
{"name": "image-008.jpg", "result": "9000275609022"},
{"name": "image-009.jpg", "result": "9004593978587"},
{"name": "image-010.jpg", "result": "9002244845578"}
];
testSet.forEach(function(sample) {
sample.format = "ean_13";
});
config.decoder.readers = ['ean_reader'];
_runTestSet(testSet, config);
});
describe("Code128", function() {
var config = generateConfig(),
testSet = [
{"name": "image-001.jpg", "result": "0001285112001000040801"},
// {"name": "image-002.jpg", "result": "FANAVF1461710"},
// {"name": "image-003.jpg", "result": "673023"},
// {"name": "image-004.jpg", "result": "010210150301625334"},
{"name": "image-005.jpg", "result": "419055603900009001012999"},
{"name": "image-006.jpg", "result": "419055603900009001012999"},
{"name": "image-007.jpg", "result": "T 000003552345"},
{"name": "image-008.jpg", "result": "FANAVF1461710"},
{"name": "image-009.jpg", "result": "0001285112001000040801"},
{"name": "image-010.jpg", "result": "673023"}
];
testSet.forEach(function(sample) {
sample.format = "code_128";
});
config.decoder.readers = ['code_128_reader'];
_runTestSet(testSet, config);
});
describe("Code39", function() {
var config = generateConfig(),
testSet = [
{"name": "image-001.jpg", "result": "B3% $DAD$"},
{"name": "image-003.jpg", "result": "CODE39"},
{"name": "image-004.jpg", "result": "QUAGGAJS"},
/* {"name": "image-005.jpg", "result": "CODE39"}, */
{"name": "image-006.jpg", "result": "2/4-8/16-32"},
{"name": "image-007.jpg", "result": "2/4-8/16-32"},
{"name": "image-008.jpg", "result": "CODE39"},
{"name": "image-009.jpg", "result": "2/4-8/16-32"},
{"name": "image-010.jpg", "result": "CODE39"}
];
testSet.forEach(function(sample) {
sample.format = "code_39";
});
config.decoder.readers = ['code_39_reader'];
_runTestSet(testSet, config);
});
describe("EAN-8", function() {
var config = generateConfig(),
testSet = [
{"name": "image-001.jpg", "result": "42191605"},
{"name": "image-002.jpg", "result": "42191605"},
{"name": "image-003.jpg", "result": "90311208"},
{"name": "image-004.jpg", "result": "24057257"},
{"name": "image-005.jpg", "result": "90162602"},
{"name": "image-006.jpg", "result": "24036153"},
{"name": "image-007.jpg", "result": "42176817"},
{"name": "image-008.jpg", "result": "42191605"},
{"name": "image-009.jpg", "result": "42242215"},
{"name": "image-010.jpg", "result": "42184799"}
];
testSet.forEach(function(sample) {
sample.format = "ean_8";
});
config.decoder.readers = ['ean_8_reader'];
_runTestSet(testSet, config);
});
describe("UPC", function() {
var config = generateConfig(),
testSet = [
{"name": "image-001.jpg", "result": "882428015268"},
{"name": "image-002.jpg", "result": "882428015268"},
{"name": "image-003.jpg", "result": "882428015084"},
{"name": "image-004.jpg", "result": "882428015343"},
{"name": "image-005.jpg", "result": "882428015343"},
/* {"name": "image-006.jpg", "result": "882428015046"}, */
{"name": "image-007.jpg", "result": "882428015084"},
{"name": "image-008.jpg", "result": "882428015046"},
{"name": "image-009.jpg", "result": "039047013551"},
{"name": "image-010.jpg", "result": "039047013551"}
];
testSet.forEach(function(sample) {
sample.format = "upc_a";
});
config.decoder.readers = ['upc_reader'];
_runTestSet(testSet, config);
});
describe("UPC-E", function() {
var config = generateConfig(),
testSet = [
{"name": "image-001.jpg", "result": "04965802"},
{"name": "image-002.jpg", "result": "04965802"},
{"name": "image-003.jpg", "result": "03897425"},
{"name": "image-004.jpg", "result": "05096893"},
{"name": "image-005.jpg", "result": "05096893"},
{"name": "image-006.jpg", "result": "05096893"},
{"name": "image-007.jpg", "result": "03897425"},
{"name": "image-008.jpg", "result": "01264904"},
/*{"name": "image-009.jpg", "result": "01264904"},*/
{"name": "image-010.jpg", "result": "01264904"}
];
testSet.forEach(function(sample) {
sample.format = "upc_e";
});
config.decoder.readers = ['upc_e_reader'];
_runTestSet(testSet, config);
});
describe("Codabar", function() {
var config = generateConfig(),
testSet = [
{"name": "image-001.jpg", "result": "A10/53+17-70D"},
{"name": "image-002.jpg", "result": "B546745735B"},
{"name": "image-003.jpg", "result": "C$399.95A"},
{"name": "image-004.jpg", "result": "B546745735B"},
{"name": "image-005.jpg", "result": "C$399.95A"},
{"name": "image-006.jpg", "result": "B546745735B"},
{"name": "image-007.jpg", "result": "C$399.95A"},
{"name": "image-008.jpg", "result": "A16:9/4:3/3:2D"},
{"name": "image-009.jpg", "result": "C$399.95A"},
{"name": "image-010.jpg", "result": "C$399.95A"}
];
testSet.forEach(function(sample) {
sample.format = "codabar";
});
config.decoder.readers = ['codabar_reader'];
_runTestSet(testSet, config);
});
describe("I2of5 with localization", function() {
var config = {
inputStream: {
size: 800,
singleChannel: false
},
locator: {
patchSize: "small",
halfSample: false
},
numOfWorkers: 0,
decoder: {
readers: ["i2of5_reader"],
},
locate: true,
src: null
}, testSet = [
{"name": "image-001.jpg", "result": "2167361334"},
{"name": "image-002.jpg", "result": "2167361334"},
{"name": "image-003.jpg", "result": "2167361334"},
{"name": "image-004.jpg", "result": "2167361334"},
{"name": "image-005.jpg", "result": "2167361334"}
];
testSet.forEach(function(sample) {
sample.format = "i2of5";
});
_runTestSet(testSet, config);
});
});
});

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

@ -1,86 +1,79 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ export default {
/* global define */ init: function(arr, val) {
var l = arr.length;
define(function() { while (l--) {
"use strict"; arr[l] = val;
}
return { },
init : function(arr, val) {
var l = arr.length;
while (l--) {
arr[l] = val;
}
},
/** /**
* Shuffles the content of an array * Shuffles the content of an array
* @return {Array} the array itself shuffled * @return {Array} the array itself shuffled
*/ */
shuffle : function(arr) { shuffle: function(arr) {
var i = arr.length - 1, j, x; var i = arr.length - 1, j, x;
for (i; i >= 0; i--) { for (i; i >= 0; i--) {
j = Math.floor(Math.random() * i); j = Math.floor(Math.random() * i);
x = arr[i]; x = arr[i];
arr[i] = arr[j]; arr[i] = arr[j];
arr[j] = x; arr[j] = x;
} }
return arr; return arr;
}, },
toPointList : function(arr) { toPointList: function(arr) {
var i, j, row = [], rows = []; var i, j, row = [], rows = [];
for ( i = 0; i < arr.length; i++) { for ( i = 0; i < arr.length; i++) {
row = []; row = [];
for ( j = 0; j < arr[i].length; j++) { for ( j = 0; j < arr[i].length; j++) {
row[j] = arr[i][j]; row[j] = arr[i][j];
}
rows[i] = "[" + row.join(",") + "]";
} }
return "[" + rows.join(",\r\n") + "]"; rows[i] = "[" + row.join(",") + "]";
}, }
return "[" + rows.join(",\r\n") + "]";
},
/** /**
* returns the elements which's score is bigger than the threshold * returns the elements which's score is bigger than the threshold
* @return {Array} the reduced array * @return {Array} the reduced array
*/ */
threshold : function(arr, threshold, scoreFunc) { threshold: function(arr, threshold, scoreFunc) {
var i, queue = []; var i, queue = [];
for ( i = 0; i < arr.length; i++) { for ( i = 0; i < arr.length; i++) {
if (scoreFunc.apply(arr, [arr[i]]) >= threshold) { if (scoreFunc.apply(arr, [arr[i]]) >= threshold) {
queue.push(arr[i]); queue.push(arr[i]);
}
} }
return queue; }
}, return queue;
},
maxIndex : function(arr) { maxIndex: function(arr) {
var i, max = 0; var i, max = 0;
for ( i = 0; i < arr.length; i++) { for ( i = 0; i < arr.length; i++) {
if (arr[i] > arr[max]) { if (arr[i] > arr[max]) {
max = i; max = i;
}
} }
return max; }
}, return max;
},
max : function(arr) { max: function(arr) {
var i, max = 0; var i, max = 0;
for ( i = 0; i < arr.length; i++) { for ( i = 0; i < arr.length; i++) {
if (arr[i] > max) { if (arr[i] > max) {
max = arr[i]; max = arr[i];
}
} }
return max; }
}, return max;
},
sum: function(arr) { sum: function(arr) {
var length = arr.length, var length = arr.length,
sum = 0; sum = 0;
while(length--) { while (length--) {
sum += arr[length]; sum += arr[length];
}
return sum;
} }
}; return sum;
}); }
};

@ -1,305 +1,286 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ import Bresenham from './bresenham';
/* global define */ import ImageDebug from './image_debug';
import Code128Reader from './code_128_reader';
import EANReader from './ean_reader';
import Code39Reader from './code_39_reader';
import Code39VINReader from './code_39_vin_reader';
import CodabarReader from './codabar_reader';
import UPCReader from './upc_reader';
import EAN8Reader from './ean_8_reader';
import UPCEReader from './upc_e_reader';
import I2of5Reader from './i2of5_reader';
define([ const READERS = {
"bresenham", code_128_reader: Code128Reader,
"image_debug", ean_reader: EANReader,
'code_128_reader', ean_8_reader: EAN8Reader,
'ean_reader', code_39_reader: Code39Reader,
'code_39_reader', code_39_vin_reader: Code39VINReader,
'code_39_vin_reader', codabar_reader: CodabarReader,
'codabar_reader', upc_reader: UPCReader,
'upc_reader', upc_e_reader: UPCEReader,
'ean_8_reader', i2of5_reader: I2of5Reader
'upc_e_reader', };
'i2of5_reader' export default {
], function( create: function(config, inputImageWrapper) {
Bresenham, var _canvas = {
ImageDebug, ctx: {
Code128Reader, frequency: null,
EANReader, pattern: null,
Code39Reader, overlay: null
Code39VINReader,
CodabarReader,
UPCReader,
EAN8Reader,
UPCEReader,
I2of5Reader) {
"use strict";
var readers = {
code_128_reader: Code128Reader,
ean_reader: EANReader,
ean_8_reader: EAN8Reader,
code_39_reader: Code39Reader,
code_39_vin_reader: Code39VINReader,
codabar_reader: CodabarReader,
upc_reader: UPCReader,
upc_e_reader: UPCEReader,
i2of5_reader: I2of5Reader
};
var BarcodeDecoder = {
create : function(config, inputImageWrapper) {
var _canvas = {
ctx : {
frequency : null,
pattern : null,
overlay : null
},
dom : {
frequency : null,
pattern : null,
overlay : null
}
}, },
_barcodeReaders = []; dom: {
frequency: null,
pattern: null,
overlay: null
}
},
_barcodeReaders = [];
initCanvas(); initCanvas();
initReaders(); initReaders();
initConfig(); initConfig();
function initCanvas() { function initCanvas() {
if (typeof document !== 'undefined') { if (typeof document !== 'undefined') {
var $debug = document.querySelector("#debug.detection"); var $debug = document.querySelector("#debug.detection");
_canvas.dom.frequency = document.querySelector("canvas.frequency"); _canvas.dom.frequency = document.querySelector("canvas.frequency");
if (!_canvas.dom.frequency) { if (!_canvas.dom.frequency) {
_canvas.dom.frequency = document.createElement("canvas"); _canvas.dom.frequency = document.createElement("canvas");
_canvas.dom.frequency.className = "frequency"; _canvas.dom.frequency.className = "frequency";
if($debug) { if ($debug) {
$debug.appendChild(_canvas.dom.frequency); $debug.appendChild(_canvas.dom.frequency);
}
} }
_canvas.ctx.frequency = _canvas.dom.frequency.getContext("2d"); }
_canvas.ctx.frequency = _canvas.dom.frequency.getContext("2d");
_canvas.dom.pattern = document.querySelector("canvas.patternBuffer"); _canvas.dom.pattern = document.querySelector("canvas.patternBuffer");
if (!_canvas.dom.pattern) { if (!_canvas.dom.pattern) {
_canvas.dom.pattern = document.createElement("canvas"); _canvas.dom.pattern = document.createElement("canvas");
_canvas.dom.pattern.className = "patternBuffer"; _canvas.dom.pattern.className = "patternBuffer";
if($debug) { if ($debug) {
$debug.appendChild(_canvas.dom.pattern); $debug.appendChild(_canvas.dom.pattern);
}
} }
_canvas.ctx.pattern = _canvas.dom.pattern.getContext("2d"); }
_canvas.ctx.pattern = _canvas.dom.pattern.getContext("2d");
_canvas.dom.overlay = document.querySelector("canvas.drawingBuffer"); _canvas.dom.overlay = document.querySelector("canvas.drawingBuffer");
if (_canvas.dom.overlay) { if (_canvas.dom.overlay) {
_canvas.ctx.overlay = _canvas.dom.overlay.getContext("2d"); _canvas.ctx.overlay = _canvas.dom.overlay.getContext("2d");
}
} }
} }
}
function initReaders() { function initReaders() {
config.readers.forEach(function(readerConfig) { config.readers.forEach(function(readerConfig) {
var reader, var reader,
config = {}; configuration = {};
if (typeof readerConfig === 'object') { if (typeof readerConfig === 'object') {
reader = readerConfig.format; reader = readerConfig.format;
config = readerConfig.config; configuration = readerConfig.config;
} else if (typeof readerConfig === 'string') { } else if (typeof readerConfig === 'string') {
reader = readerConfig; reader = readerConfig;
} }
_barcodeReaders.push(new readers[reader](config)); console.log("Before registering reader: ", reader);
}); _barcodeReaders.push(new READERS[reader](configuration));
console.log("Registered Readers: " + _barcodeReaders });
.map(function(reader) {return JSON.stringify({format: reader.FORMAT, config: reader.config});}) console.log("Registered Readers: " + _barcodeReaders
.join(', ')); .map((reader) => JSON.stringify({format: reader.FORMAT, config: reader.config}))
} .join(', '));
}
function initConfig() { function initConfig() {
if (typeof document !== 'undefined') { if (typeof document !== 'undefined') {
var i, var i,
vis = [{ vis = [{
node : _canvas.dom.frequency, node: _canvas.dom.frequency,
prop : config.showFrequency prop: config.showFrequency
}, { }, {
node : _canvas.dom.pattern, node: _canvas.dom.pattern,
prop : config.showPattern prop: config.showPattern
}]; }];
for (i = 0; i < vis.length; i++) { for (i = 0; i < vis.length; i++) {
if (vis[i].prop === true) { if (vis[i].prop === true) {
vis[i].node.style.display = "block"; vis[i].node.style.display = "block";
} else { } else {
vis[i].node.style.display = "none"; vis[i].node.style.display = "none";
}
} }
} }
} }
}
/** /**
* extend the line on both ends * extend the line on both ends
* @param {Array} line * @param {Array} line
* @param {Number} angle * @param {Number} angle
*/ */
function getExtendedLine(line, angle, ext) { function getExtendedLine(line, angle, ext) {
function extendLine(amount) { function extendLine(amount) {
var extension = { var extension = {
y : amount * Math.sin(angle), y: amount * Math.sin(angle),
x : amount * Math.cos(angle) x: amount * Math.cos(angle)
}; };
line[0].y -= extension.y;
line[0].x -= extension.x;
line[1].y += extension.y;
line[1].x += extension.x;
}
// check if inside image line[0].y -= extension.y;
extendLine(ext); line[0].x -= extension.x;
while (ext > 1 && (!inputImageWrapper.inImageWithBorder(line[0], 0) || !inputImageWrapper.inImageWithBorder(line[1], 0))) { line[1].y += extension.y;
ext -= Math.ceil(ext/2); line[1].x += extension.x;
extendLine(-ext);
}
return line;
} }
function getLine(box) { // check if inside image
return [{ extendLine(ext);
x : (box[1][0] - box[0][0]) / 2 + box[0][0], while (ext > 1 && (!inputImageWrapper.inImageWithBorder(line[0], 0)
y : (box[1][1] - box[0][1]) / 2 + box[0][1] || !inputImageWrapper.inImageWithBorder(line[1], 0))) {
}, { ext -= Math.ceil(ext / 2);
x : (box[3][0] - box[2][0]) / 2 + box[2][0], extendLine(-ext);
y : (box[3][1] - box[2][1]) / 2 + box[2][1]
}];
} }
return line;
}
function tryDecode(line) { function getLine(box) {
var result = null, return [{
i, x: (box[1][0] - box[0][0]) / 2 + box[0][0],
barcodeLine = Bresenham.getBarcodeLine(inputImageWrapper, line[0], line[1]); y: (box[1][1] - box[0][1]) / 2 + box[0][1]
}, {
x: (box[3][0] - box[2][0]) / 2 + box[2][0],
y: (box[3][1] - box[2][1]) / 2 + box[2][1]
}];
}
if (config.showFrequency) { function tryDecode(line) {
ImageDebug.drawPath(line, {x: 'x', y: 'y'}, _canvas.ctx.overlay, {color: 'red', lineWidth: 3}); var result = null,
Bresenham.debug.printFrequency(barcodeLine.line, _canvas.dom.frequency); i,
} barcodeLine = Bresenham.getBarcodeLine(inputImageWrapper, line[0], line[1]);
Bresenham.toBinaryLine(barcodeLine);
if (config.showPattern) {
Bresenham.debug.printPattern(barcodeLine.line, _canvas.dom.pattern);
}
for ( i = 0; i < _barcodeReaders.length && result === null; i++) {
result = _barcodeReaders[i].decodePattern(barcodeLine.line);
}
if(result === null){
return null;
}
return {
codeResult: result,
barcodeLine: barcodeLine
};
if (config.showFrequency) {
ImageDebug.drawPath(line, {x: 'x', y: 'y'}, _canvas.ctx.overlay, {color: 'red', lineWidth: 3});
Bresenham.debug.printFrequency(barcodeLine.line, _canvas.dom.frequency);
}
Bresenham.toBinaryLine(barcodeLine);
if (config.showPattern) {
Bresenham.debug.printPattern(barcodeLine.line, _canvas.dom.pattern);
} }
/** for ( i = 0; i < _barcodeReaders.length && result === null; i++) {
* This method slices the given area apart and tries to detect a barcode-pattern result = _barcodeReaders[i].decodePattern(barcodeLine.line);
* for each slice. It returns the decoded barcode, or null if nothing was found }
* @param {Array} box if (result === null){
* @param {Array} line return null;
* @param {Number} lineAngle }
*/ return {
function tryDecodeBruteForce(box, line, lineAngle) { codeResult: result,
var sideLength = Math.sqrt(Math.pow(box[1][0] - box[0][0], 2) + Math.pow((box[1][1] - box[0][1]), 2)), barcodeLine: barcodeLine
i, };
slices = 16, }
result = null,
dir,
extension,
xdir = Math.sin(lineAngle),
ydir = Math.cos(lineAngle);
for ( i = 1; i < slices && result === null; i++) { /**
// move line perpendicular to angle * This method slices the given area apart and tries to detect a barcode-pattern
dir = sideLength / slices * i * (i % 2 === 0 ? -1 : 1); * for each slice. It returns the decoded barcode, or null if nothing was found
extension = { * @param {Array} box
y : dir * xdir, * @param {Array} line
x : dir * ydir * @param {Number} lineAngle
}; */
line[0].y += extension.x; function tryDecodeBruteForce(box, line, lineAngle) {
line[0].x -= extension.y; var sideLength = Math.sqrt(Math.pow(box[1][0] - box[0][0], 2) + Math.pow((box[1][1] - box[0][1]), 2)),
line[1].y += extension.x; i,
line[1].x -= extension.y; slices = 16,
result = null,
dir,
extension,
xdir = Math.sin(lineAngle),
ydir = Math.cos(lineAngle);
result = tryDecode(line); for ( i = 1; i < slices && result === null; i++) {
} // move line perpendicular to angle
return result; dir = sideLength / slices * i * (i % 2 === 0 ? -1 : 1);
} extension = {
y: dir * xdir,
x: dir * ydir
};
line[0].y += extension.x;
line[0].x -= extension.y;
line[1].y += extension.x;
line[1].x -= extension.y;
function getLineLength(line) { result = tryDecode(line);
return Math.sqrt(
Math.pow(Math.abs(line[1].y - line[0].y), 2) +
Math.pow(Math.abs(line[1].x - line[0].x), 2));
} }
return result;
}
/** function getLineLength(line) {
* With the help of the configured readers (Code128 or EAN) this function tries to detect a return Math.sqrt(
* valid barcode pattern within the given area. Math.pow(Math.abs(line[1].y - line[0].y), 2) +
* @param {Object} box The area to search in Math.pow(Math.abs(line[1].x - line[0].x), 2));
* @returns {Object} the result {codeResult, line, angle, pattern, threshold} }
*/
function decodeFromBoundingBox(box) {
var line,
lineAngle,
ctx = _canvas.ctx.overlay,
result,
lineLength;
if (config.drawBoundingBox && ctx) { /**
ImageDebug.drawPath(box, {x: 0, y: 1}, ctx, {color: "blue", lineWidth: 2}); * With the help of the configured readers (Code128 or EAN) this function tries to detect a
} * valid barcode pattern within the given area.
* @param {Object} box The area to search in
* @returns {Object} the result {codeResult, line, angle, pattern, threshold}
*/
function decodeFromBoundingBox(box) {
var line,
lineAngle,
ctx = _canvas.ctx.overlay,
result,
lineLength;
line = getLine(box); if (config.drawBoundingBox && ctx) {
lineLength = getLineLength(line); ImageDebug.drawPath(box, {x: 0, y: 1}, ctx, {color: "blue", lineWidth: 2});
lineAngle = Math.atan2(line[1].y - line[0].y, line[1].x - line[0].x); }
line = getExtendedLine(line, lineAngle, Math.floor(lineLength*0.1));
if(line === null){
return null;
}
result = tryDecode(line); line = getLine(box);
if(result === null) { lineLength = getLineLength(line);
result = tryDecodeBruteForce(box, line, lineAngle); lineAngle = Math.atan2(line[1].y - line[0].y, line[1].x - line[0].x);
} line = getExtendedLine(line, lineAngle, Math.floor(lineLength * 0.1));
if (line === null){
return null;
}
if(result === null) { result = tryDecode(line);
return null; if (result === null) {
} result = tryDecodeBruteForce(box, line, lineAngle);
}
if (result && config.drawScanline && ctx) { if (result === null) {
ImageDebug.drawPath(line, {x: 'x', y: 'y'}, ctx, {color: 'red', lineWidth: 3}); return null;
} }
return { if (result && config.drawScanline && ctx) {
codeResult : result.codeResult, ImageDebug.drawPath(line, {x: 'x', y: 'y'}, ctx, {color: 'red', lineWidth: 3});
line : line,
angle : lineAngle,
pattern : result.barcodeLine.line,
threshold : result.barcodeLine.threshold
};
} }
return { return {
decodeFromBoundingBox : function(box) { codeResult: result.codeResult,
return decodeFromBoundingBox(box); line: line,
}, angle: lineAngle,
decodeFromBoundingBoxes : function(boxes) { pattern: result.barcodeLine.line,
var i, result; threshold: result.barcodeLine.threshold
for ( i = 0; i < boxes.length; i++) {
result = decodeFromBoundingBox(boxes[i]);
if (result && result.codeResult) {
result.box = boxes[i];
return result;
}
}
},
setReaders: function(readers) {
config.readers = readers;
_barcodeReaders.length = 0;
initReaders();
}
}; };
} }
};
return (BarcodeDecoder); return {
}); decodeFromBoundingBox: function(box) {
return decodeFromBoundingBox(box);
},
decodeFromBoundingBoxes: function(boxes) {
var i, result;
for ( i = 0; i < boxes.length; i++) {
result = decodeFromBoundingBox(boxes[i]);
if (result && result.codeResult) {
result.box = boxes[i];
return result;
}
}
},
setReaders: function(readers) {
config.readers = readers;
_barcodeReaders.length = 0;
initReaders();
}
};
}
};

File diff suppressed because it is too large Load Diff

@ -1,231 +1,222 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ function BarcodeReader(config) {
/* global define */ this._row = [];
this.config = config || {};
define( return this;
function() { }
"use strict";
BarcodeReader.prototype._nextUnset = function(line, start) {
function BarcodeReader(config) { var i;
this._row = [];
this.config = config || {}; if (start === undefined) {
return this; start = 0;
}
for (i = start; i < line.length; i++) {
if (!line[i]) {
return i;
} }
}
BarcodeReader.prototype._nextUnset = function(line, start) { return line.length;
var i; };
if (start === undefined) {
start = 0;
}
for (i = start; i < line.length; i++) {
if (!line[i]) {
return i;
}
}
return line.length;
};
BarcodeReader.prototype._matchPattern = function(counter, code) {
var i,
error = 0,
singleError = 0,
modulo = this.MODULO,
maxSingleError = this.SINGLE_CODE_ERROR || 1;
for (i = 0; i < counter.length; i++) {
singleError = Math.abs(code[i] - counter[i]);
if (singleError > maxSingleError) {
return Number.MAX_VALUE;
}
error += singleError;
}
return error/modulo;
};
BarcodeReader.prototype._nextSet = function(line, offset) { BarcodeReader.prototype._matchPattern = function(counter, code) {
var i; var i,
error = 0,
singleError = 0,
modulo = this.MODULO,
maxSingleError = this.SINGLE_CODE_ERROR || 1;
offset = offset || 0; for (i = 0; i < counter.length; i++) {
for (i = offset; i < line.length; i++) { singleError = Math.abs(code[i] - counter[i]);
if (line[i]) { if (singleError > maxSingleError) {
return i; return Number.MAX_VALUE;
} }
} error += singleError;
return line.length; }
}; return error / modulo;
};
BarcodeReader.prototype._normalize = function(counter, modulo) {
var i, BarcodeReader.prototype._nextSet = function(line, offset) {
self = this, var i;
sum = 0,
ratio, offset = offset || 0;
numOnes = 0, for (i = offset; i < line.length; i++) {
normalized = [], if (line[i]) {
norm = 0; return i;
}
if (!modulo) { }
modulo = self.MODULO; return line.length;
} };
for (i = 0; i < counter.length; i++) {
if (counter[i] === 1) { BarcodeReader.prototype._normalize = function(counter, modulo) {
numOnes++; var i,
} else { self = this,
sum += counter[i]; sum = 0,
} ratio,
} numOnes = 0,
ratio = sum / (modulo - numOnes); normalized = [],
if (ratio > 1.0) { norm = 0;
for (i = 0; i < counter.length; i++) {
norm = counter[i] === 1 ? counter[i] : counter[i] / ratio; if (!modulo) {
normalized.push(norm); 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) {
var counter = [],
i,
self = this,
offset = self._nextSet(self._row),
isWhite = !self._row[offset],
counterPos = 0,
bestMatch = {
error: Number.MAX_VALUE,
code: -1,
start: 0
},
error;
if (cmpCounter) {
for ( i = 0; i < cmpCounter.length; i++) {
counter.push(0);
}
for ( i = offset; i < self._row.length; i++) {
if (self._row[i] ^ isWhite) {
counter[counterPos]++;
} else { } else {
ratio = (sum + numOnes)/modulo; if (counterPos === counter.length - 1) {
for (i = 0; i < counter.length; i++) { error = self._matchPattern(counter, cmpCounter);
norm = counter[i] / ratio;
normalized.push(norm); if (error < epsilon) {
} bestMatch.start = i - offset;
} bestMatch.end = i;
return normalized; bestMatch.counter = counter;
}; return bestMatch;
BarcodeReader.prototype._matchTrace = function(cmpCounter, epsilon) {
var counter = [],
i,
self = this,
offset = self._nextSet(self._row),
isWhite = !self._row[offset],
counterPos = 0,
bestMatch = {
error : Number.MAX_VALUE,
code : -1,
start : 0
},
error;
if (cmpCounter) {
for ( i = 0; i < cmpCounter.length; i++) {
counter.push(0);
}
for ( i = offset; i < self._row.length; i++) {
if (self._row[i] ^ isWhite) {
counter[counterPos]++;
} else { } else {
if (counterPos === counter.length - 1) { return null;
error = self._matchPattern(counter, cmpCounter);
if (error < epsilon) {
bestMatch.start = i - offset;
bestMatch.end = i;
bestMatch.counter = counter;
return bestMatch;
} else {
return null;
}
} else {
counterPos++;
}
counter[counterPos] = 1;
isWhite = !isWhite;
} }
} else {
counterPos++;
} }
counter[counterPos] = 1;
isWhite = !isWhite;
}
}
} else {
counter.push(0);
for ( i = offset; i < self._row.length; i++) {
if (self._row[i] ^ isWhite) {
counter[counterPos]++;
} else { } else {
counterPos++;
counter.push(0); counter.push(0);
for ( i = offset; i < self._row.length; i++) { counter[counterPos] = 1;
if (self._row[i] ^ isWhite) { isWhite = !isWhite;
counter[counterPos]++;
} else {
counterPos++;
counter.push(0);
counter[counterPos] = 1;
isWhite = !isWhite;
}
}
} }
}
}
// if cmpCounter was not given // if cmpCounter was not given
bestMatch.start = offset; bestMatch.start = offset;
bestMatch.end = self._row.length - 1; bestMatch.end = self._row.length - 1;
bestMatch.counter = counter; bestMatch.counter = counter;
return bestMatch; return bestMatch;
}; };
BarcodeReader.prototype.decodePattern = function(pattern) {
var self = this,
result;
self._row = pattern;
result = self._decode();
if (result === null) {
self._row.reverse();
result = self._decode();
if (result) {
result.direction = BarcodeReader.DIRECTION.REVERSE;
result.start = self._row.length - result.start;
result.end = self._row.length - result.end;
}
} else {
result.direction = BarcodeReader.DIRECTION.FORWARD;
}
if (result) {
result.format = self.FORMAT;
}
return result;
};
BarcodeReader.prototype._matchRange = function(start, end, value) { BarcodeReader.prototype.decodePattern = function(pattern) {
var i; var self = this,
result;
start = start < 0 ? 0 : start; self._row = pattern;
for (i = start; i < end; i++) { result = self._decode();
if (this._row[i] !== value) { if (result === null) {
return false; self._row.reverse();
} result = self._decode();
} if (result) {
return true; result.direction = BarcodeReader.DIRECTION.REVERSE;
}; result.start = self._row.length - result.start;
result.end = self._row.length - result.end;
BarcodeReader.prototype._fillCounters = function(offset, end, isWhite) { }
var self = this, } else {
counterPos = 0, result.direction = BarcodeReader.DIRECTION.FORWARD;
i, }
counters = []; if (result) {
result.format = self.FORMAT;
isWhite = (typeof isWhite !== 'undefined') ? isWhite : true; }
offset = (typeof offset !== 'undefined') ? offset : self._nextUnset(self._row); return result;
end = end || self._row.length; };
counters[counterPos] = 0; BarcodeReader.prototype._matchRange = function(start, end, value) {
for (i = offset; i < end; i++) { var i;
if (self._row[i] ^ isWhite) {
counters[counterPos]++; start = start < 0 ? 0 : start;
} else { for (i = start; i < end; i++) {
counterPos++; if (this._row[i] !== value) {
counters[counterPos] = 1; return false;
isWhite = !isWhite; }
} }
} return true;
return counters; };
};
BarcodeReader.prototype._fillCounters = function(offset, end, isWhite) {
Object.defineProperty(BarcodeReader.prototype, "FORMAT", { var self = this,
value: 'unknown', counterPos = 0,
writeable: false i,
}); counters = [];
BarcodeReader.DIRECTION = { isWhite = (typeof isWhite !== 'undefined') ? isWhite : true;
FORWARD : 1, offset = (typeof offset !== 'undefined') ? offset : self._nextUnset(self._row);
REVERSE : -1 end = end || self._row.length;
};
counters[counterPos] = 0;
BarcodeReader.Exception = { for (i = offset; i < end; i++) {
StartNotFoundException : "Start-Info was not found!", if (self._row[i] ^ isWhite) {
CodeNotFoundException : "Code could not be found!", counters[counterPos]++;
PatternNotFoundException : "Pattern could not be found!" } else {
}; counterPos++;
counters[counterPos] = 1;
BarcodeReader.CONFIG_KEYS = {}; isWhite = !isWhite;
}
return (BarcodeReader);
} }
); return counters;
};
Object.defineProperty(BarcodeReader.prototype, "FORMAT", {
value: 'unknown',
writeable: false
});
BarcodeReader.DIRECTION = {
FORWARD: 1,
REVERSE: -1
};
BarcodeReader.Exception = {
StartNotFoundException: "Start-Info was not found!",
CodeNotFoundException: "Code could not be found!",
PatternNotFoundException: "Pattern could not be found!"
};
BarcodeReader.CONFIG_KEYS = {};
export default BarcodeReader;

@ -1,217 +1,213 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ import CVUtils from './cv_utils';
/* global define */ import ImageWrapper from './image_wrapper';
define(["cv_utils", "image_wrapper"], function(CVUtils, ImageWrapper) { var Bresenham = {};
"use strict";
var Bresenham = {}; var Slope = {
DIR: {
var Slope = { UP: 1,
DIR : { DOWN: -1
UP : 1, }
DOWN : -1 };
} /**
}; * Scans a line of the given image from point p1 to p2 and returns a result object containing
/** * gray-scale values (0-255) of the underlying pixels in addition to the min
* Scans a line of the given image from point p1 to p2 and returns a result object containing * and max values.
* gray-scale values (0-255) of the underlying pixels in addition to the min * @param {Object} imageWrapper
* and max values. * @param {Object} p1 The start point {x,y}
* @param {Object} imageWrapper * @param {Object} p2 The end point {x,y}
* @param {Object} p1 The start point {x,y} * @returns {line, min, max}
* @param {Object} p2 The end point {x,y} */
* @returns {line, min, max} Bresenham.getBarcodeLine = function(imageWrapper, p1, p2) {
*/ var x0 = p1.x | 0,
Bresenham.getBarcodeLine = function(imageWrapper, p1, p2) { y0 = p1.y | 0,
var x0 = p1.x | 0, x1 = p2.x | 0,
y0 = p1.y | 0, y1 = p2.y | 0,
x1 = p2.x | 0, steep = Math.abs(y1 - y0) > Math.abs(x1 - x0),
y1 = p2.y | 0, deltax,
steep = Math.abs(y1 - y0) > Math.abs(x1 - x0), deltay,
deltax, error,
deltay, ystep,
error, y,
ystep, tmp,
y, x,
tmp, line = [],
x, imageData = imageWrapper.data,
line = [], width = imageWrapper.size.x,
imageData = imageWrapper.data, sum = 0,
width = imageWrapper.size.x, val,
sum = 0, min = 255,
val, max = 0;
min = 255,
max = 0; function read(a, b) {
val = imageData[b * width + a];
function read(a, b) { sum += val;
val = imageData[b * width + a]; min = val < min ? val : min;
sum += val; max = val > max ? val : max;
min = val < min ? val : min; line.push(val);
max = val > max ? val : max; }
line.push(val);
} if (steep) {
tmp = x0;
if (steep) { x0 = y0;
tmp = x0; y0 = tmp;
x0 = y0;
y0 = tmp; tmp = x1;
x1 = y1;
tmp = x1; y1 = tmp;
x1 = y1; }
y1 = tmp; if (x0 > x1) {
} tmp = x0;
if (x0 > x1) { x0 = x1;
tmp = x0; x1 = tmp;
x0 = x1;
x1 = tmp; tmp = y0;
y0 = y1;
tmp = y0; y1 = tmp;
y0 = y1; }
y1 = tmp; deltax = x1 - x0;
deltay = Math.abs(y1 - y0);
error = (deltax / 2) | 0;
y = y0;
ystep = y0 < y1 ? 1 : -1;
for ( x = x0; x < x1; x++) {
if (steep){
read(y, x);
} else {
read(x, y);
} }
deltax = x1 - x0; error = error - deltay;
deltay = Math.abs(y1 - y0); if (error < 0) {
error = (deltax / 2) | 0; y = y + ystep;
y = y0; error = error + deltax;
ystep = y0 < y1 ? 1 : -1;
for ( x = x0; x < x1; x++) {
if(steep){
read(y, x);
} else {
read(x, y);
}
error = error - deltay;
if (error < 0) {
y = y + ystep;
error = error + deltax;
}
} }
}
return { return {
line : line, line: line,
min : min, min: min,
max : max max: max
};
}; };
};
Bresenham.toOtsuBinaryLine = function(result) { Bresenham.toOtsuBinaryLine = function(result) {
var line = result.line, var line = result.line,
image = new ImageWrapper({x: line.length - 1, y: 1}, line), image = new ImageWrapper({x: line.length - 1, y: 1}, line),
threshold = CVUtils.determineOtsuThreshold(image, 5); threshold = CVUtils.determineOtsuThreshold(image, 5);
line = CVUtils.sharpenLine(line); line = CVUtils.sharpenLine(line);
CVUtils.thresholdImage(image, threshold); CVUtils.thresholdImage(image, threshold);
return { return {
line: line, line: line,
threshold: threshold threshold: threshold
};
}; };
};
/**
* Converts the result from getBarcodeLine into a binary representation /**
* also considering the frequency and slope of the signal for more robust results * Converts the result from getBarcodeLine into a binary representation
* @param {Object} result {line, min, max} * also considering the frequency and slope of the signal for more robust results
*/ * @param {Object} result {line, min, max}
Bresenham.toBinaryLine = function(result) { */
Bresenham.toBinaryLine = function(result) {
var min = result.min, var min = result.min,
max = result.max, max = result.max,
line = result.line, line = result.line,
slope, slope,
slope2, slope2,
center = min + (max - min) / 2, center = min + (max - min) / 2,
extrema = [], extrema = [],
currentDir, currentDir,
dir, dir,
threshold = (max - min) / 12, threshold = (max - min) / 12,
rThreshold = -threshold, rThreshold = -threshold,
i, i,
j; j;
// 1. find extrema // 1. find extrema
currentDir = line[0] > center ? Slope.DIR.UP : Slope.DIR.DOWN; currentDir = line[0] > center ? Slope.DIR.UP : Slope.DIR.DOWN;
extrema.push({ extrema.push({
pos : 0, pos: 0,
val : line[0] val: line[0]
}); });
for ( i = 0; i < line.length - 2; i++) { for ( i = 0; i < line.length - 2; i++) {
slope = (line[i + 1] - line[i]); slope = (line[i + 1] - line[i]);
slope2 = (line[i + 2] - line[i + 1]); slope2 = (line[i + 2] - line[i + 1]);
if ((slope + slope2) < rThreshold && line[i + 1] < (center*1.5)) { if ((slope + slope2) < rThreshold && line[i + 1] < (center * 1.5)) {
dir = Slope.DIR.DOWN; dir = Slope.DIR.DOWN;
} else if ((slope + slope2) > threshold && line[i + 1] > (center*0.5)) { } else if ((slope + slope2) > threshold && line[i + 1] > (center * 0.5)) {
dir = Slope.DIR.UP; dir = Slope.DIR.UP;
} else { } else {
dir = currentDir; dir = currentDir;
}
if (currentDir !== dir) {
extrema.push({
pos : i,
val : line[i]
});
currentDir = dir;
}
} }
extrema.push({
pos : line.length,
val : line[line.length - 1]
});
for ( j = extrema[0].pos; j < extrema[1].pos; j++) { if (currentDir !== dir) {
line[j] = line[j] > center ? 0 : 1; extrema.push({
pos: i,
val: line[i]
});
currentDir = dir;
}
}
extrema.push({
pos: line.length,
val: line[line.length - 1]
});
for ( j = extrema[0].pos; j < extrema[1].pos; j++) {
line[j] = line[j] > center ? 0 : 1;
}
// iterate over extrema and convert to binary based on avg between minmax
for ( i = 1; i < extrema.length - 1; i++) {
if (extrema[i + 1].val > extrema[i].val) {
threshold = (extrema[i].val + ((extrema[i + 1].val - extrema[i].val) / 3) * 2) | 0;
} else {
threshold = (extrema[i + 1].val + ((extrema[i].val - extrema[i + 1].val) / 3)) | 0;
} }
// iterate over extrema and convert to binary based on avg between minmax for ( j = extrema[i].pos; j < extrema[i + 1].pos; j++) {
for ( i = 1; i < extrema.length - 1; i++) { line[j] = line[j] > threshold ? 0 : 1;
if (extrema[i + 1].val > extrema[i].val) {
threshold = (extrema[i].val + ((extrema[i + 1].val - extrema[i].val) / 3) * 2) | 0;
} else {
threshold = (extrema[i + 1].val + ((extrema[i].val - extrema[i + 1].val) / 3)) | 0;
}
for ( j = extrema[i].pos; j < extrema[i + 1].pos; j++) {
line[j] = line[j] > threshold ? 0 : 1;
}
} }
}
return { return {
line : line, line: line,
threshold : threshold threshold: threshold
};
}; };
};
/**
* Used for development only /**
*/ * Used for development only
Bresenham.debug = { */
printFrequency: function(line, canvas) { Bresenham.debug = {
var i, printFrequency: function(line, canvas) {
ctx = canvas.getContext("2d"); var i,
canvas.width = line.length; ctx = canvas.getContext("2d");
canvas.height = 256; canvas.width = line.length;
canvas.height = 256;
ctx.beginPath();
ctx.strokeStyle = "blue"; ctx.beginPath();
for ( i = 0; i < line.length; i++) { ctx.strokeStyle = "blue";
ctx.moveTo(i, 255); for ( i = 0; i < line.length; i++) {
ctx.lineTo(i, 255 - line[i]); ctx.moveTo(i, 255);
} ctx.lineTo(i, 255 - line[i]);
ctx.stroke(); }
ctx.closePath(); ctx.stroke();
}, ctx.closePath();
},
printPattern: function(line, canvas) {
var ctx = canvas.getContext("2d"), i; printPattern: function(line, canvas) {
var ctx = canvas.getContext("2d"), i;
canvas.width = line.length;
ctx.fillColor = "black"; canvas.width = line.length;
for ( i = 0; i < line.length; i++) { ctx.fillColor = "black";
if (line[i] === 1) { for ( i = 0; i < line.length; i++) {
ctx.fillRect(i, 0, 1, 100); if (line[i] === 1) {
} ctx.fillRect(i, 0, 1, 100);
} }
} }
}; }
};
return (Bresenham); export default Bresenham;
});

@ -1,143 +1,139 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ const merge = require('lodash/object/merge');
/* global define, MediaStreamTrack */
define(["html_utils"], function(HtmlUtils) { var streamRef,
"use strict"; loadedDataHandler;
var streamRef,
loadedDataHandler;
/** /**
* Wraps browser-specific getUserMedia * Wraps browser-specific getUserMedia
* @param {Object} constraints * @param {Object} constraints
* @param {Object} success Callback * @param {Object} success Callback
* @param {Object} failure Callback * @param {Object} failure Callback
*/ */
function getUserMedia(constraints, success, failure) { function getUserMedia(constraints, success, failure) {
if (typeof navigator.getUserMedia !== 'undefined') { if (typeof navigator.getUserMedia !== 'undefined') {
navigator.getUserMedia(constraints, function (stream) { navigator.getUserMedia(constraints, function (stream) {
streamRef = stream; streamRef = stream;
var videoSrc = (window.URL && window.URL.createObjectURL(stream)) || stream; var videoSrc = (window.URL && window.URL.createObjectURL(stream)) || stream;
success.apply(null, [videoSrc]); success.apply(null, [videoSrc]);
}, failure); }, failure);
} else { } else {
failure(new TypeError("getUserMedia not available")); failure(new TypeError("getUserMedia not available"));
}
} }
}
function loadedData(video, callback) { function loadedData(video, callback) {
var attempts = 10; var attempts = 10;
function checkVideo() { function checkVideo() {
if (attempts > 0) { if (attempts > 0) {
if (video.videoWidth > 0 && video.videoHeight > 0) { if (video.videoWidth > 0 && video.videoHeight > 0) {
console.log(video.videoWidth + "px x " + video.videoHeight + "px"); console.log(video.videoWidth + "px x " + video.videoHeight + "px");
callback(); callback();
} else {
window.setTimeout(checkVideo, 500);
}
} else { } else {
callback('Unable to play video stream. Is webcam working?'); window.setTimeout(checkVideo, 500);
} }
attempts--; } else {
callback('Unable to play video stream. Is webcam working?');
} }
checkVideo(); attempts--;
} }
checkVideo();
}
/** /**
* Tries to attach the camera-stream to a given video-element * Tries to attach the camera-stream to a given video-element
* 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 * @param {Object} callback
*/ */
function initCamera(constraints, video, callback) { function initCamera(constraints, video, callback) {
getUserMedia(constraints, function(src) { getUserMedia(constraints, function(src) {
video.src = src; video.src = src;
if (loadedDataHandler) { if (loadedDataHandler) {
video.removeEventListener("loadeddata", loadedDataHandler, false); video.removeEventListener("loadeddata", loadedDataHandler, false);
} }
loadedDataHandler = loadedData.bind(null, video, callback); loadedDataHandler = loadedData.bind(null, video, callback);
video.addEventListener('loadeddata', loadedDataHandler, false); video.addEventListener('loadeddata', loadedDataHandler, false);
video.play(); video.play();
}, function(e) { }, function(e) {
callback(e); callback(e);
}); });
} }
/** /**
* Normalizes the incoming constraints to satisfy the current browser * Normalizes the incoming constraints to satisfy the current browser
* @param config * @param config
* @param cb Callback which is called whenever constraints are created * @param cb Callback which is called whenever constraints are created
* @returns {*} * @returns {*}
*/ */
function normalizeConstraints(config, cb) { function normalizeConstraints(config, cb) {
var constraints = { var constraints = {
audio: false, audio: false,
video: true video: true
}, },
videoConstraints = HtmlUtils.mergeObjects({ videoConstraints = merge({
width: 640, width: 640,
height: 480, height: 480,
minAspectRatio: 0, minAspectRatio: 0,
maxAspectRatio: 100, maxAspectRatio: 100,
facing: "environment" facing: "environment"
}, config); }, config);
if ( typeof MediaStreamTrack !== 'undefined' && typeof MediaStreamTrack.getSources !== 'undefined') { if ( typeof MediaStreamTrack !== 'undefined' && typeof MediaStreamTrack.getSources !== 'undefined') {
MediaStreamTrack.getSources(function(sourceInfos) { MediaStreamTrack.getSources(function(sourceInfos) {
var videoSourceId; var videoSourceId;
for (var i = 0; i != sourceInfos.length; ++i) { for (var i = 0; i < sourceInfos.length; ++i) {
var sourceInfo = sourceInfos[i]; var sourceInfo = sourceInfos[i];
if (sourceInfo.kind == "video" && sourceInfo.facing == videoConstraints.facing) { if (sourceInfo.kind === "video" && sourceInfo.facing === videoConstraints.facing) {
videoSourceId = sourceInfo.id; videoSourceId = sourceInfo.id;
}
} }
constraints.video = { }
mandatory: {
minWidth: videoConstraints.width,
minHeight: videoConstraints.height,
minAspectRatio: videoConstraints.minAspectRatio,
maxAspectRatio: videoConstraints.maxAspectRatio
},
optional: [{
sourceId: videoSourceId
}]
};
return cb(constraints);
});
} else {
constraints.video = { constraints.video = {
mediaSource: "camera", mandatory: {
width: { min: videoConstraints.width, max: videoConstraints.width }, minWidth: videoConstraints.width,
height: { min: videoConstraints.height, max: videoConstraints.height }, minHeight: videoConstraints.height,
require: ["width", "height"] minAspectRatio: videoConstraints.minAspectRatio,
maxAspectRatio: videoConstraints.maxAspectRatio
},
optional: [{
sourceId: videoSourceId
}]
}; };
return cb(constraints); return cb(constraints);
}
}
/**
* Requests the back-facing camera of the user. The callback is called
* whenever the stream is ready to be consumed, or if an error occures.
* @param {Object} video
* @param {Object} callback
*/
function request(video, videoConstraints, callback) {
normalizeConstraints(videoConstraints, function(constraints) {
initCamera(constraints, video, callback);
}); });
} 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 { /**
request : function(video, constraints, callback) { * Requests the back-facing camera of the user. The callback is called
request(video, constraints, callback); * whenever the stream is ready to be consumed, or if an error occures.
}, * @param {Object} video
release : function() { * @param {Object} callback
var tracks = streamRef && streamRef.getVideoTracks(); */
if (tracks.length) { function request(video, videoConstraints, callback) {
tracks[0].stop(); normalizeConstraints(videoConstraints, function(constraints) {
} initCamera(constraints, video, callback);
streamRef = null; });
}
export default {
request: function(video, constraints, callback) {
request(video, constraints, callback);
},
release: function() {
var tracks = streamRef && streamRef.getVideoTracks();
if (tracks.length) {
tracks[0].stop();
} }
}; streamRef = null;
}); }
};

@ -1,72 +1,65 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ import {vec2} from 'gl-matrix';
/* global define */
define(["gl-matrix"], function(glMatrix) {
"use strict";
var vec2 = glMatrix.vec2;
/** /**
* Creates a cluster for grouping similar orientations of datapoints * Creates a cluster for grouping similar orientations of datapoints
*/ */
var Cluster = { export default {
create : function(point, threshold) { create: function(point, threshold) {
var points = [], center = { var points = [],
rad : 0, center = {
vec : vec2.clone([0, 0]) rad: 0,
}, pointMap = {}; vec: vec2.clone([0, 0])
},
pointMap = {};
function init() { function init() {
add(point); add(point);
updateCenter(); updateCenter();
} }
function add(point) { function add(pointToAdd) {
pointMap[point.id] = point; pointMap[pointToAdd.id] = pointToAdd;
points.push(point); points.push(pointToAdd);
} }
function updateCenter() { function updateCenter() {
var i, sum = 0; var i, sum = 0;
for ( i = 0; i < points.length; i++) { for ( i = 0; i < points.length; i++) {
sum += points[i].rad; sum += points[i].rad;
}
center.rad = sum / points.length;
center.vec = vec2.clone([Math.cos(center.rad), Math.sin(center.rad)]);
} }
center.rad = sum / points.length;
center.vec = vec2.clone([Math.cos(center.rad), Math.sin(center.rad)]);
}
init(); init();
return { return {
add : function(point) { add: function(pointToAdd) {
if (!pointMap[point.id]) { if (!pointMap[pointToAdd.id]) {
add(point); add(pointToAdd);
updateCenter(); updateCenter();
}
},
fits : function(point) {
// check cosine similarity to center-angle
var similarity = Math.abs(vec2.dot(point.point.vec, center.vec));
if (similarity > threshold) {
return true;
}
return false;
},
getPoints : function() {
return points;
},
getCenter : function() {
return center;
} }
}; },
}, fits: function(otherPoint) {
createPoint : function(point, id, property) { // check cosine similarity to center-angle
return { var similarity = Math.abs(vec2.dot(otherPoint.point.vec, center.vec));
rad : point[property], if (similarity > threshold) {
point : point, return true;
id : id }
}; return false;
} },
}; getPoints: function() {
return points;
return (Cluster); },
}); getCenter: function() {
return center;
}
};
},
createPoint: function(newPoint, id, property) {
return {
rad: newPoint[property],
point: newPoint,
id: id
};
}
};

@ -1,294 +1,288 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ import BarcodeReader from './barcode_reader';
/* global define */
function CodabarReader() {
define( BarcodeReader.call(this);
[ this._counters = [];
"./barcode_reader" }
],
function(BarcodeReader) { var properties = {
"use strict"; ALPHABETH_STRING: {value: "0123456789-$:/.+ABCD"},
ALPHABET: {value: [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 45, 36, 58, 47, 46, 43, 65, 66, 67, 68]},
function CodabarReader() { CHARACTER_ENCODINGS: {value: [0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, 0x00c, 0x018,
BarcodeReader.call(this); 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E]},
this._counters = []; START_END: {value: [0x01A, 0x029, 0x00B, 0x00E]},
} MIN_ENCODED_CHARS: {value: 4},
MAX_ACCEPTABLE: {value: 2.0},
var properties = { PADDING: {value: 1.5},
ALPHABETH_STRING: {value: "0123456789-$:/.+ABCD"}, FORMAT: {value: "codabar", writeable: false}
ALPHABET: {value: [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 45, 36, 58, 47, 46, 43, 65, 66, 67, 68]}, };
CHARACTER_ENCODINGS: {value: [0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, 0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E]},
START_END: {value: [0x01A, 0x029, 0x00B, 0x00E]}, CodabarReader.prototype = Object.create(BarcodeReader.prototype, properties);
MIN_ENCODED_CHARS: {value: 4}, CodabarReader.prototype.constructor = CodabarReader;
MAX_ACCEPTABLE: {value: 2.0},
PADDING: {value: 1.5}, CodabarReader.prototype._decode = function() {
FORMAT: {value: "codabar", writeable: false} var self = this,
}; result = [],
start,
CodabarReader.prototype = Object.create(BarcodeReader.prototype, properties); decodedChar,
CodabarReader.prototype.constructor = CodabarReader; pattern,
nextStart,
CodabarReader.prototype._decode = function() { end;
var self = this,
result = [], this._counters = self._fillCounters();
start, start = self._findStart();
decodedChar, if (!start) {
pattern, return null;
nextStart, }
end; nextStart = start.startCounter;
this._counters = self._fillCounters();
start = self._findStart();
if (!start) {
return null;
}
nextStart = start.startCounter;
do {
pattern = self._toPattern(nextStart);
if (pattern < 0) {
return null;
}
decodedChar = self._patternToChar(pattern);
if (decodedChar < 0){
return null;
}
result.push(decodedChar);
nextStart += 8;
if (result.length > 1 && self._isStartEnd(pattern)) {
break;
}
} while(nextStart < self._counters.length);
// verify end
if ((result.length - 2) < self.MIN_ENCODED_CHARS || !self._isStartEnd(pattern)) {
return null;
}
// verify end white space do {
if (!self._verifyWhitespace(start.startCounter, nextStart - 8)){ pattern = self._toPattern(nextStart);
return null; if (pattern < 0) {
} return null;
}
decodedChar = self._patternToChar(pattern);
if (decodedChar < 0){
return null;
}
result.push(decodedChar);
nextStart += 8;
if (result.length > 1 && self._isStartEnd(pattern)) {
break;
}
} while (nextStart < self._counters.length);
if (!self._validateResult(result, start.startCounter)){ // verify end
return null; if ((result.length - 2) < self.MIN_ENCODED_CHARS || !self._isStartEnd(pattern)) {
} return null;
}
nextStart = nextStart > self._counters.length ? self._counters.length : nextStart; // verify end white space
end = start.start + self._sumCounters(start.startCounter, nextStart - 8); if (!self._verifyWhitespace(start.startCounter, nextStart - 8)){
return null;
}
return { if (!self._validateResult(result, start.startCounter)){
code : result.join(""), return null;
start : start.start, }
end : end,
startInfo : start,
decodedCodes : result
};
};
CodabarReader.prototype._verifyWhitespace = function(startCounter, endCounter) { nextStart = nextStart > self._counters.length ? self._counters.length : nextStart;
if ((startCounter - 1 <= 0) || this._counters[startCounter-1] >= (this._calculatePatternLength(startCounter) / 2.0)) { end = start.start + self._sumCounters(start.startCounter, nextStart - 8);
if ((endCounter + 8 >= this._counters.length) || this._counters[endCounter+7] >= (this._calculatePatternLength(endCounter) / 2.0)) {
return true; return {
} code: result.join(""),
} start: start.start,
return false; end: end,
}; startInfo: start,
decodedCodes: result
};
};
CodabarReader.prototype._verifyWhitespace = function(startCounter, endCounter) {
if ((startCounter - 1 <= 0)
|| this._counters[startCounter - 1] >= (this._calculatePatternLength(startCounter) / 2.0)) {
if ((endCounter + 8 >= this._counters.length)
|| this._counters[endCounter + 7] >= (this._calculatePatternLength(endCounter) / 2.0)) {
return true;
}
}
return false;
};
CodabarReader.prototype._calculatePatternLength = function(offset) { CodabarReader.prototype._calculatePatternLength = function(offset) {
var i, var i,
sum = 0; sum = 0;
for (i = offset; i < offset + 7; i++) { for (i = offset; i < offset + 7; i++) {
sum += this._counters[i]; sum += this._counters[i];
} }
return sum; return sum;
}; };
CodabarReader.prototype._thresholdResultPattern = function(result, startCounter){ CodabarReader.prototype._thresholdResultPattern = function(result, startCounter){
var self = this, var self = this,
categorization = { categorization = {
space: { space: {
narrow: { size: 0, counts: 0, min: 0, max: Number.MAX_VALUE}, narrow: { size: 0, counts: 0, min: 0, max: Number.MAX_VALUE},
wide: {size: 0, counts: 0, min: 0, max: Number.MAX_VALUE} wide: {size: 0, counts: 0, min: 0, max: Number.MAX_VALUE}
}, },
bar: { bar: {
narrow: { size: 0, counts: 0, min: 0, max: Number.MAX_VALUE}, narrow: { size: 0, counts: 0, min: 0, max: Number.MAX_VALUE},
wide: { size: 0, counts: 0, min: 0, max: Number.MAX_VALUE} wide: { size: 0, counts: 0, min: 0, max: Number.MAX_VALUE}
}
},
kind,
cat,
i,
j,
pos = startCounter,
pattern;
for (i = 0; i < result.length; i++){
pattern = self._charToPattern(result[i]);
for (j = 6; j >= 0; j--) {
kind = (j & 1) === 2 ? categorization.bar : categorization.space;
cat = (pattern & 1) === 1 ? kind.wide : kind.narrow;
cat.size += self._counters[pos + j];
cat.counts++;
pattern >>= 1;
}
pos += 8;
} }
},
kind,
cat,
i,
j,
pos = startCounter,
pattern;
for (i = 0; i < result.length; i++){
pattern = self._charToPattern(result[i]);
for (j = 6; j >= 0; j--) {
kind = (j & 1) === 2 ? categorization.bar : categorization.space;
cat = (pattern & 1) === 1 ? kind.wide : kind.narrow;
cat.size += self._counters[pos + j];
cat.counts++;
pattern >>= 1;
}
pos += 8;
}
["space", "bar"].forEach(function(key) { ["space", "bar"].forEach(function(key) {
var kind = categorization[key]; var newkind = categorization[key];
kind.wide.min = Math.floor((kind.narrow.size/kind.narrow.counts + kind.wide.size / kind.wide.counts) / 2); newkind.wide.min =
kind.narrow.max = Math.ceil(kind.wide.min); Math.floor((newkind.narrow.size / newkind.narrow.counts + newkind.wide.size / newkind.wide.counts) / 2);
kind.wide.max = Math.ceil((kind.wide.size * self.MAX_ACCEPTABLE + self.PADDING) / kind.wide.counts); newkind.narrow.max = Math.ceil(newkind.wide.min);
}); newkind.wide.max = Math.ceil((newkind.wide.size * self.MAX_ACCEPTABLE + self.PADDING) / newkind.wide.counts);
});
return categorization;
}; return categorization;
};
CodabarReader.prototype._charToPattern = function(char) {
var self = this, CodabarReader.prototype._charToPattern = function(char) {
charCode = char.charCodeAt(0), var self = this,
i; charCode = char.charCodeAt(0),
i;
for (i = 0; i < self.ALPHABET.length; i++) {
if (self.ALPHABET[i] === charCode){ for (i = 0; i < self.ALPHABET.length; i++) {
return self.CHARACTER_ENCODINGS[i]; if (self.ALPHABET[i] === charCode){
} return self.CHARACTER_ENCODINGS[i];
} }
return 0x0; }
}; return 0x0;
};
CodabarReader.prototype._validateResult = function(result, startCounter) {
var self = this, CodabarReader.prototype._validateResult = function(result, startCounter) {
thresholds = self._thresholdResultPattern(result, startCounter), var self = this,
i, thresholds = self._thresholdResultPattern(result, startCounter),
j, i,
kind, j,
cat, kind,
size, cat,
pos = startCounter, size,
pattern; pos = startCounter,
pattern;
for (i = 0; i < result.length; i++) {
pattern = self._charToPattern(result[i]); for (i = 0; i < result.length; i++) {
for (j = 6; j >= 0; j--) { pattern = self._charToPattern(result[i]);
kind = (j & 1) === 0 ? thresholds.bar : thresholds.space; for (j = 6; j >= 0; j--) {
cat = (pattern & 1) === 1 ? kind.wide : kind.narrow; kind = (j & 1) === 0 ? thresholds.bar : thresholds.space;
size = self._counters[pos + j]; cat = (pattern & 1) === 1 ? kind.wide : kind.narrow;
if (size < cat.min || size > cat.max) { size = self._counters[pos + j];
return false; if (size < cat.min || size > cat.max) {
} return false;
pattern >>= 1;
}
pos += 8;
} }
return true; pattern >>= 1;
}; }
pos += 8;
}
return true;
};
CodabarReader.prototype._patternToChar = function(pattern) { CodabarReader.prototype._patternToChar = function(pattern) {
var i, var i,
self = this; self = this;
for (i = 0; i < self.CHARACTER_ENCODINGS.length; i++) { for (i = 0; i < self.CHARACTER_ENCODINGS.length; i++) {
if (self.CHARACTER_ENCODINGS[i] === pattern) { if (self.CHARACTER_ENCODINGS[i] === pattern) {
return String.fromCharCode(self.ALPHABET[i]); return String.fromCharCode(self.ALPHABET[i]);
} }
} }
return -1; return -1;
}; };
CodabarReader.prototype._computeAlternatingThreshold = function(offset, end) { CodabarReader.prototype._computeAlternatingThreshold = function(offset, end) {
var i, var i,
min = Number.MAX_VALUE, min = Number.MAX_VALUE,
max = 0, max = 0,
counter; counter;
for (i = offset; i < end; i += 2){ for (i = offset; i < end; i += 2){
counter = this._counters[i]; counter = this._counters[i];
if (counter > max) { if (counter > max) {
max = counter; max = counter;
} }
if (counter < min) { if (counter < min) {
min = counter; min = counter;
} }
} }
return ((min + max) / 2.0) | 0;
};
CodabarReader.prototype._toPattern = function(offset) {
var numCounters = 7,
end = offset + numCounters,
barThreshold,
spaceThreshold,
bitmask = 1 << (numCounters - 1),
pattern = 0,
i,
threshold;
if (end > this._counters.length) {
return -1;
}
barThreshold = this._computeAlternatingThreshold(offset, end); return ((min + max) / 2.0) | 0;
spaceThreshold = this._computeAlternatingThreshold(offset + 1, end); };
CodabarReader.prototype._toPattern = function(offset) {
var numCounters = 7,
end = offset + numCounters,
barThreshold,
spaceThreshold,
bitmask = 1 << (numCounters - 1),
pattern = 0,
i,
threshold;
if (end > this._counters.length) {
return -1;
}
for (i = 0; i < numCounters; i++){ barThreshold = this._computeAlternatingThreshold(offset, end);
threshold = (i & 1) === 0 ? barThreshold : spaceThreshold; spaceThreshold = this._computeAlternatingThreshold(offset + 1, end);
if (this._counters[offset + i] > threshold) {
pattern |= bitmask;
}
bitmask >>= 1;
}
return pattern; for (i = 0; i < numCounters; i++){
}; threshold = (i & 1) === 0 ? barThreshold : spaceThreshold;
if (this._counters[offset + i] > threshold) {
pattern |= bitmask;
}
bitmask >>= 1;
}
CodabarReader.prototype._isStartEnd = function(pattern) { return pattern;
var i; };
for (i = 0; i < this.START_END.length; i++) { CodabarReader.prototype._isStartEnd = function(pattern) {
if (this.START_END[i] === pattern) { var i;
return true;
}
}
return false;
};
CodabarReader.prototype._sumCounters = function(start, end) { for (i = 0; i < this.START_END.length; i++) {
var i, if (this.START_END[i] === pattern) {
sum = 0; return true;
}
}
return false;
};
for (i = start; i < end; i++) { CodabarReader.prototype._sumCounters = function(start, end) {
sum += this._counters[i]; var i,
} sum = 0;
return sum;
};
CodabarReader.prototype._findStart = function() {
var self = this,
i,
pattern,
start = self._nextUnset(self._row),
end;
for (i = 1; i < this._counters.length; i++) {
pattern = self._toPattern(i);
if (pattern !== -1 && self._isStartEnd(pattern)) {
// TODO: Look for whitespace ahead
start += self._sumCounters(0, i);
end = start + self._sumCounters(i, i + 8);
return {
start: start,
end: end,
startCounter: i,
endCounter: i + 8
};
}
}
};
return (CodabarReader); for (i = start; i < end; i++) {
sum += this._counters[i];
}
return sum;
};
CodabarReader.prototype._findStart = function() {
var self = this,
i,
pattern,
start = self._nextUnset(self._row),
end;
for (i = 1; i < this._counters.length; i++) {
pattern = self._toPattern(i);
if (pattern !== -1 && self._isStartEnd(pattern)) {
// TODO: Look for whitespace ahead
start += self._sumCounters(0, i);
end = start + self._sumCounters(i, i + 8);
return {
start: start,
end: end,
startCounter: i,
endCounter: i + 8
};
}
} }
); };
export default CodabarReader;

@ -1,423 +1,407 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ import BarcodeReader from './barcode_reader';
/* global define */
define( function Code128Reader() {
[ BarcodeReader.call(this);
"./barcode_reader" }
],
function(BarcodeReader) {
"use strict";
function Code128Reader() {
BarcodeReader.call(this);
}
var properties = {
CODE_SHIFT : {value: 98},
CODE_C : {value: 99},
CODE_B : {value: 100},
CODE_A : {value: 101},
START_CODE_A : {value: 103},
START_CODE_B : {value: 104},
START_CODE_C : {value: 105},
STOP_CODE : {value: 106},
MODULO : {value: 11},
CODE_PATTERN : {value: [
[2, 1, 2, 2, 2, 2],
[2, 2, 2, 1, 2, 2],
[2, 2, 2, 2, 2, 1],
[1, 2, 1, 2, 2, 3],
[1, 2, 1, 3, 2, 2],
[1, 3, 1, 2, 2, 2],
[1, 2, 2, 2, 1, 3],
[1, 2, 2, 3, 1, 2],
[1, 3, 2, 2, 1, 2],
[2, 2, 1, 2, 1, 3],
[2, 2, 1, 3, 1, 2],
[2, 3, 1, 2, 1, 2],
[1, 1, 2, 2, 3, 2],
[1, 2, 2, 1, 3, 2],
[1, 2, 2, 2, 3, 1],
[1, 1, 3, 2, 2, 2],
[1, 2, 3, 1, 2, 2],
[1, 2, 3, 2, 2, 1],
[2, 2, 3, 2, 1, 1],
[2, 2, 1, 1, 3, 2],
[2, 2, 1, 2, 3, 1],
[2, 1, 3, 2, 1, 2],
[2, 2, 3, 1, 1, 2],
[3, 1, 2, 1, 3, 1],
[3, 1, 1, 2, 2, 2],
[3, 2, 1, 1, 2, 2],
[3, 2, 1, 2, 2, 1],
[3, 1, 2, 2, 1, 2],
[3, 2, 2, 1, 1, 2],
[3, 2, 2, 2, 1, 1],
[2, 1, 2, 1, 2, 3],
[2, 1, 2, 3, 2, 1],
[2, 3, 2, 1, 2, 1],
[1, 1, 1, 3, 2, 3],
[1, 3, 1, 1, 2, 3],
[1, 3, 1, 3, 2, 1],
[1, 1, 2, 3, 1, 3],
[1, 3, 2, 1, 1, 3],
[1, 3, 2, 3, 1, 1],
[2, 1, 1, 3, 1, 3],
[2, 3, 1, 1, 1, 3],
[2, 3, 1, 3, 1, 1],
[1, 1, 2, 1, 3, 3],
[1, 1, 2, 3, 3, 1],
[1, 3, 2, 1, 3, 1],
[1, 1, 3, 1, 2, 3],
[1, 1, 3, 3, 2, 1],
[1, 3, 3, 1, 2, 1],
[3, 1, 3, 1, 2, 1],
[2, 1, 1, 3, 3, 1],
[2, 3, 1, 1, 3, 1],
[2, 1, 3, 1, 1, 3],
[2, 1, 3, 3, 1, 1],
[2, 1, 3, 1, 3, 1],
[3, 1, 1, 1, 2, 3],
[3, 1, 1, 3, 2, 1],
[3, 3, 1, 1, 2, 1],
[3, 1, 2, 1, 1, 3],
[3, 1, 2, 3, 1, 1],
[3, 3, 2, 1, 1, 1],
[3, 1, 4, 1, 1, 1],
[2, 2, 1, 4, 1, 1],
[4, 3, 1, 1, 1, 1],
[1, 1, 1, 2, 2, 4],
[1, 1, 1, 4, 2, 2],
[1, 2, 1, 1, 2, 4],
[1, 2, 1, 4, 2, 1],
[1, 4, 1, 1, 2, 2],
[1, 4, 1, 2, 2, 1],
[1, 1, 2, 2, 1, 4],
[1, 1, 2, 4, 1, 2],
[1, 2, 2, 1, 1, 4],
[1, 2, 2, 4, 1, 1],
[1, 4, 2, 1, 1, 2],
[1, 4, 2, 2, 1, 1],
[2, 4, 1, 2, 1, 1],
[2, 2, 1, 1, 1, 4],
[4, 1, 3, 1, 1, 1],
[2, 4, 1, 1, 1, 2],
[1, 3, 4, 1, 1, 1],
[1, 1, 1, 2, 4, 2],
[1, 2, 1, 1, 4, 2],
[1, 2, 1, 2, 4, 1],
[1, 1, 4, 2, 1, 2],
[1, 2, 4, 1, 1, 2],
[1, 2, 4, 2, 1, 1],
[4, 1, 1, 2, 1, 2],
[4, 2, 1, 1, 1, 2],
[4, 2, 1, 2, 1, 1],
[2, 1, 2, 1, 4, 1],
[2, 1, 4, 1, 2, 1],
[4, 1, 2, 1, 2, 1],
[1, 1, 1, 1, 4, 3],
[1, 1, 1, 3, 4, 1],
[1, 3, 1, 1, 4, 1],
[1, 1, 4, 1, 1, 3],
[1, 1, 4, 3, 1, 1],
[4, 1, 1, 1, 1, 3],
[4, 1, 1, 3, 1, 1],
[1, 1, 3, 1, 4, 1],
[1, 1, 4, 1, 3, 1],
[3, 1, 1, 1, 4, 1],
[4, 1, 1, 1, 3, 1],
[2, 1, 1, 4, 1, 2],
[2, 1, 1, 2, 1, 4],
[2, 1, 1, 2, 3, 2],
[2, 3, 3, 1, 1, 1, 2]
]},
SINGLE_CODE_ERROR: {value: 1},
AVG_CODE_ERROR: {value: 0.5},
FORMAT: {value: "code_128", writeable: false}
};
Code128Reader.prototype = Object.create(BarcodeReader.prototype, properties);
Code128Reader.prototype.constructor = Code128Reader;
Code128Reader.prototype._decodeCode = function(start) {
var counter = [0, 0, 0, 0, 0, 0],
i,
self = this,
offset = start,
isWhite = !self._row[offset],
counterPos = 0,
bestMatch = {
error : Number.MAX_VALUE,
code : -1,
start : start,
end : start
},
code,
error,
normalized;
for ( i = offset; i < self._row.length; i++) { var properties = {
if (self._row[i] ^ isWhite) { CODE_SHIFT: {value: 98},
counter[counterPos]++; CODE_C: {value: 99},
} else { CODE_B: {value: 100},
if (counterPos === counter.length - 1) { CODE_A: {value: 101},
normalized = self._normalize(counter); START_CODE_A: {value: 103},
if (normalized) { START_CODE_B: {value: 104},
for (code = 0; code < self.CODE_PATTERN.length; code++) { START_CODE_C: {value: 105},
error = self._matchPattern(normalized, self.CODE_PATTERN[code]); STOP_CODE: {value: 106},
if (error < bestMatch.error) { MODULO: {value: 11},
bestMatch.code = code; CODE_PATTERN: {value: [
bestMatch.error = error; [2, 1, 2, 2, 2, 2],
} [2, 2, 2, 1, 2, 2],
} [2, 2, 2, 2, 2, 1],
bestMatch.end = i; [1, 2, 1, 2, 2, 3],
return bestMatch; [1, 2, 1, 3, 2, 2],
[1, 3, 1, 2, 2, 2],
[1, 2, 2, 2, 1, 3],
[1, 2, 2, 3, 1, 2],
[1, 3, 2, 2, 1, 2],
[2, 2, 1, 2, 1, 3],
[2, 2, 1, 3, 1, 2],
[2, 3, 1, 2, 1, 2],
[1, 1, 2, 2, 3, 2],
[1, 2, 2, 1, 3, 2],
[1, 2, 2, 2, 3, 1],
[1, 1, 3, 2, 2, 2],
[1, 2, 3, 1, 2, 2],
[1, 2, 3, 2, 2, 1],
[2, 2, 3, 2, 1, 1],
[2, 2, 1, 1, 3, 2],
[2, 2, 1, 2, 3, 1],
[2, 1, 3, 2, 1, 2],
[2, 2, 3, 1, 1, 2],
[3, 1, 2, 1, 3, 1],
[3, 1, 1, 2, 2, 2],
[3, 2, 1, 1, 2, 2],
[3, 2, 1, 2, 2, 1],
[3, 1, 2, 2, 1, 2],
[3, 2, 2, 1, 1, 2],
[3, 2, 2, 2, 1, 1],
[2, 1, 2, 1, 2, 3],
[2, 1, 2, 3, 2, 1],
[2, 3, 2, 1, 2, 1],
[1, 1, 1, 3, 2, 3],
[1, 3, 1, 1, 2, 3],
[1, 3, 1, 3, 2, 1],
[1, 1, 2, 3, 1, 3],
[1, 3, 2, 1, 1, 3],
[1, 3, 2, 3, 1, 1],
[2, 1, 1, 3, 1, 3],
[2, 3, 1, 1, 1, 3],
[2, 3, 1, 3, 1, 1],
[1, 1, 2, 1, 3, 3],
[1, 1, 2, 3, 3, 1],
[1, 3, 2, 1, 3, 1],
[1, 1, 3, 1, 2, 3],
[1, 1, 3, 3, 2, 1],
[1, 3, 3, 1, 2, 1],
[3, 1, 3, 1, 2, 1],
[2, 1, 1, 3, 3, 1],
[2, 3, 1, 1, 3, 1],
[2, 1, 3, 1, 1, 3],
[2, 1, 3, 3, 1, 1],
[2, 1, 3, 1, 3, 1],
[3, 1, 1, 1, 2, 3],
[3, 1, 1, 3, 2, 1],
[3, 3, 1, 1, 2, 1],
[3, 1, 2, 1, 1, 3],
[3, 1, 2, 3, 1, 1],
[3, 3, 2, 1, 1, 1],
[3, 1, 4, 1, 1, 1],
[2, 2, 1, 4, 1, 1],
[4, 3, 1, 1, 1, 1],
[1, 1, 1, 2, 2, 4],
[1, 1, 1, 4, 2, 2],
[1, 2, 1, 1, 2, 4],
[1, 2, 1, 4, 2, 1],
[1, 4, 1, 1, 2, 2],
[1, 4, 1, 2, 2, 1],
[1, 1, 2, 2, 1, 4],
[1, 1, 2, 4, 1, 2],
[1, 2, 2, 1, 1, 4],
[1, 2, 2, 4, 1, 1],
[1, 4, 2, 1, 1, 2],
[1, 4, 2, 2, 1, 1],
[2, 4, 1, 2, 1, 1],
[2, 2, 1, 1, 1, 4],
[4, 1, 3, 1, 1, 1],
[2, 4, 1, 1, 1, 2],
[1, 3, 4, 1, 1, 1],
[1, 1, 1, 2, 4, 2],
[1, 2, 1, 1, 4, 2],
[1, 2, 1, 2, 4, 1],
[1, 1, 4, 2, 1, 2],
[1, 2, 4, 1, 1, 2],
[1, 2, 4, 2, 1, 1],
[4, 1, 1, 2, 1, 2],
[4, 2, 1, 1, 1, 2],
[4, 2, 1, 2, 1, 1],
[2, 1, 2, 1, 4, 1],
[2, 1, 4, 1, 2, 1],
[4, 1, 2, 1, 2, 1],
[1, 1, 1, 1, 4, 3],
[1, 1, 1, 3, 4, 1],
[1, 3, 1, 1, 4, 1],
[1, 1, 4, 1, 1, 3],
[1, 1, 4, 3, 1, 1],
[4, 1, 1, 1, 1, 3],
[4, 1, 1, 3, 1, 1],
[1, 1, 3, 1, 4, 1],
[1, 1, 4, 1, 3, 1],
[3, 1, 1, 1, 4, 1],
[4, 1, 1, 1, 3, 1],
[2, 1, 1, 4, 1, 2],
[2, 1, 1, 2, 1, 4],
[2, 1, 1, 2, 3, 2],
[2, 3, 3, 1, 1, 1, 2]
]},
SINGLE_CODE_ERROR: {value: 1},
AVG_CODE_ERROR: {value: 0.5},
FORMAT: {value: "code_128", writeable: false}
};
Code128Reader.prototype = Object.create(BarcodeReader.prototype, properties);
Code128Reader.prototype.constructor = Code128Reader;
Code128Reader.prototype._decodeCode = function(start) {
var counter = [0, 0, 0, 0, 0, 0],
i,
self = this,
offset = start,
isWhite = !self._row[offset],
counterPos = 0,
bestMatch = {
error: Number.MAX_VALUE,
code: -1,
start: start,
end: start
},
code,
error,
normalized;
for ( i = offset; i < self._row.length; i++) {
if (self._row[i] ^ isWhite) {
counter[counterPos]++;
} else {
if (counterPos === counter.length - 1) {
normalized = self._normalize(counter);
if (normalized) {
for (code = 0; code < self.CODE_PATTERN.length; code++) {
error = self._matchPattern(normalized, self.CODE_PATTERN[code]);
if (error < bestMatch.error) {
bestMatch.code = code;
bestMatch.error = error;
} }
} else {
counterPos++;
} }
counter[counterPos] = 1; bestMatch.end = i;
isWhite = !isWhite; return bestMatch;
} }
} else {
counterPos++;
} }
return null; counter[counterPos] = 1;
}; isWhite = !isWhite;
}
}
return null;
};
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,
self = this, self = this,
offset = self._nextSet(self._row), offset = self._nextSet(self._row),
isWhite = false, isWhite = false,
counterPos = 0, counterPos = 0,
bestMatch = { bestMatch = {
error : Number.MAX_VALUE, error: Number.MAX_VALUE,
code : -1, code: -1,
start : 0, start: 0,
end : 0 end: 0
}, },
code, code,
error, error,
j, j,
sum, sum,
normalized; normalized;
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];
}
normalized = self._normalize(counter);
if (normalized) {
for (code = self.START_CODE_A; code <= self.START_CODE_C; code++) {
error = self._matchPattern(normalized, self.CODE_PATTERN[code]);
if (error < bestMatch.error) {
bestMatch.code = code;
bestMatch.error = error;
}
}
if (bestMatch.error < self.AVG_CODE_ERROR) {
bestMatch.start = i - sum;
bestMatch.end = i;
return bestMatch;
}
}
for ( j = 0; j < 4; j++) { for ( i = offset; i < self._row.length; i++) {
counter[j] = counter[j + 2]; 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];
}
normalized = self._normalize(counter);
if (normalized) {
for (code = self.START_CODE_A; code <= self.START_CODE_C; code++) {
error = self._matchPattern(normalized, self.CODE_PATTERN[code]);
if (error < bestMatch.error) {
bestMatch.code = code;
bestMatch.error = error;
} }
counter[4] = 0;
counter[5] = 0;
counterPos--;
} else {
counterPos++;
} }
counter[counterPos] = 1; if (bestMatch.error < self.AVG_CODE_ERROR) {
isWhite = !isWhite; bestMatch.start = i - sum;
bestMatch.end = i;
return bestMatch;
}
} }
for ( j = 0; j < 4; j++) {
counter[j] = counter[j + 2];
}
counter[4] = 0;
counter[5] = 0;
counterPos--;
} else {
counterPos++;
} }
return null; counter[counterPos] = 1;
}; isWhite = !isWhite;
}
}
return null;
};
Code128Reader.prototype._decode = function() { Code128Reader.prototype._decode = function() {
var self = this, var self = this,
startInfo = self._findStart(), startInfo = self._findStart(),
code = null, code = null,
done = false, done = false,
result = [], result = [],
multiplier = 0, multiplier = 0,
checksum = 0, checksum = 0,
codeset, codeset,
rawResult = [], rawResult = [],
decodedCodes = [], decodedCodes = [],
shiftNext = false, shiftNext = false,
unshift, unshift;
lastCharacterWasPrintable;
if (startInfo === null) { if (startInfo === null) {
return null; return null;
}
code = {
code: startInfo.code,
start: startInfo.start,
end: startInfo.end
};
decodedCodes.push(code);
checksum = code.code;
switch (code.code) {
case self.START_CODE_A:
codeset = self.CODE_A;
break;
case self.START_CODE_B:
codeset = self.CODE_B;
break;
case self.START_CODE_C:
codeset = self.CODE_C;
break;
default:
return null;
}
while (!done) {
unshift = shiftNext;
shiftNext = false;
code = self._decodeCode(code.end);
if (code !== null) {
if (code.code !== self.STOP_CODE) {
rawResult.push(code.code);
multiplier++;
checksum += multiplier * code.code;
} }
code = {
code : startInfo.code,
start : startInfo.start,
end : startInfo.end
};
decodedCodes.push(code); decodedCodes.push(code);
checksum = code.code;
switch(code.code) {
case self.START_CODE_A:
codeset = self.CODE_A;
break;
case self.START_CODE_B:
codeset = self.CODE_B;
break;
case self.START_CODE_C:
codeset = self.CODE_C;
break;
default:
return null;
}
while (!done) {
unshift = shiftNext;
shiftNext = false;
code = self._decodeCode(code.end);
if (code !== null) {
if (code.code !== self.STOP_CODE) {
rawResult.push(code.code);
multiplier++;
checksum += multiplier * code.code;
}
decodedCodes.push(code);
switch(codeset) { switch (codeset) {
case self.CODE_A: case self.CODE_A:
if (code.code < 64) { if (code.code < 64) {
result.push(String.fromCharCode(32 + code.code)); result.push(String.fromCharCode(32 + code.code));
} else if (code.code < 96) { } else if (code.code < 96) {
result.push(String.fromCharCode(code.code - 64)); result.push(String.fromCharCode(code.code - 64));
} else { } else {
switch (code.code) { switch (code.code) {
case self.CODE_SHIFT: case self.CODE_SHIFT:
shiftNext = true; shiftNext = true;
codeset = self.CODE_B; codeset = self.CODE_B;
break;
case self.CODE_B:
codeset = self.CODE_B;
break;
case self.CODE_C:
codeset = self.CODE_C;
break;
case self.STOP_CODE:
done = true;
break;
}
}
break; break;
case self.CODE_B: case self.CODE_B:
if (code.code < 96) { codeset = self.CODE_B;
result.push(String.fromCharCode(32 + code.code));
} else {
if (code.code != self.STOP_CODE) {
lastCharacterWasPrintable = false;
}
switch (code.code) {
case self.CODE_SHIFT:
shiftNext = true;
codeset = self.CODE_A;
break;
case self.CODE_A:
codeset = self.CODE_A;
break;
case self.CODE_C:
codeset = self.CODE_C;
break;
case self.STOP_CODE:
done = true;
break;
}
}
break; break;
case self.CODE_C: case self.CODE_C:
if (code.code < 100) { codeset = self.CODE_C;
result.push(code.code < 10 ? "0" + code.code : code.code); break;
} case self.STOP_CODE:
switch (code.code) { done = true;
case self.CODE_A:
codeset = self.CODE_A;
break;
case self.CODE_B:
codeset = self.CODE_B;
break;
case self.STOP_CODE:
done = true;
break;
}
break; break;
} }
}
break;
case self.CODE_B:
if (code.code < 96) {
result.push(String.fromCharCode(32 + code.code));
} else { } else {
done = true; switch (code.code) {
case self.CODE_SHIFT:
shiftNext = true;
codeset = self.CODE_A;
break;
case self.CODE_A:
codeset = self.CODE_A;
break;
case self.CODE_C:
codeset = self.CODE_C;
break;
case self.STOP_CODE:
done = true;
break;
}
} }
if (unshift) { break;
codeset = codeset == self.CODE_A ? self.CODE_B : self.CODE_A; case self.CODE_C:
if (code.code < 100) {
result.push(code.code < 10 ? "0" + code.code : code.code);
} }
switch (code.code) {
case self.CODE_A:
codeset = self.CODE_A;
break;
case self.CODE_B:
codeset = self.CODE_B;
break;
case self.STOP_CODE:
done = true;
break;
}
break;
} }
} else {
done = true;
}
if (unshift) {
codeset = codeset === self.CODE_A ? self.CODE_B : self.CODE_A;
}
}
if (code === null) { if (code === null) {
return null; return null;
} }
// find end bar
code.end = self._nextUnset(self._row, code.end);
if(!self._verifyTrailingWhitespace(code)){
return null;
}
// checksum
// Does not work correctly yet!!! startcode - endcode?
checksum -= multiplier * rawResult[rawResult.length - 1];
if (checksum % 103 != rawResult[rawResult.length - 1]) {
return null;
}
if (!result.length) { // find end bar
return null; code.end = self._nextUnset(self._row, code.end);
} if (!self._verifyTrailingWhitespace(code)){
return null;
}
// remove last code from result (checksum) // checksum
result.splice(result.length - 1, 1); // Does not work correctly yet!!! startcode - endcode?
checksum -= multiplier * rawResult[rawResult.length - 1];
if (checksum % 103 !== rawResult[rawResult.length - 1]) {
return null;
}
if (!result.length) {
return null;
}
// remove last code from result (checksum)
result.splice(result.length - 1, 1);
return { return {
code : result.join(""), code: result.join(""),
start : startInfo.start, start: startInfo.start,
end : code.end, end: code.end,
codeset : codeset, codeset: codeset,
startInfo : startInfo, startInfo: startInfo,
decodedCodes : decodedCodes, decodedCodes: decodedCodes,
endInfo : code endInfo: code
}; };
}; };
BarcodeReader.prototype._verifyTrailingWhitespace = function(endInfo) { BarcodeReader.prototype._verifyTrailingWhitespace = function(endInfo) {
var self = this, var self = this,
trailingWhitespaceEnd; trailingWhitespaceEnd;
trailingWhitespaceEnd = endInfo.end + ((endInfo.end - endInfo.start) / 2); trailingWhitespaceEnd = endInfo.end + ((endInfo.end - endInfo.start) / 2);
if (trailingWhitespaceEnd < self._row.length) { if (trailingWhitespaceEnd < self._row.length) {
if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) { if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) {
return endInfo; return endInfo;
} }
}
return null;
};
return (Code128Reader);
} }
); return null;
};
export default Code128Reader;

@ -1,221 +1,214 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ import BarcodeReader from './barcode_reader';
/* global define */ import ArrayHelper from './array_helper';
define( function Code39Reader() {
[ BarcodeReader.call(this);
"./barcode_reader", }
"./array_helper"
], var properties = {
function(BarcodeReader, ArrayHelper) { ALPHABETH_STRING: {value: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *$/+%"},
"use strict"; ALPHABET: {value: [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78,
79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 45, 46, 32, 42, 36, 47, 43, 37]},
function Code39Reader() { CHARACTER_ENCODINGS: {value: [0x034, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064, 0x109, 0x049,
BarcodeReader.call(this); 0x148, 0x019, 0x118, 0x058, 0x00D, 0x10C, 0x04C, 0x01C, 0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106,
0x046, 0x016, 0x181, 0x0C1, 0x1C0, 0x091, 0x190, 0x0D0, 0x085, 0x184, 0x0C4, 0x094, 0x0A8, 0x0A2, 0x08A, 0x02A
]},
ASTERISK: {value: 0x094},
FORMAT: {value: "code_39", writeable: false}
};
Code39Reader.prototype = Object.create(BarcodeReader.prototype, properties);
Code39Reader.prototype.constructor = Code39Reader;
Code39Reader.prototype._toCounters = function(start, counter) {
var self = this,
numCounters = counter.length,
end = self._row.length,
isWhite = !self._row[start],
i,
counterPos = 0;
ArrayHelper.init(counter, 0);
for ( i = start; i < end; i++) {
if (self._row[i] ^ isWhite) {
counter[counterPos]++;
} else {
counterPos++;
if (counterPos === numCounters) {
break;
} else {
counter[counterPos] = 1;
isWhite = !isWhite;
}
} }
}
var properties = { return counter;
ALPHABETH_STRING: {value: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *$/+%"}, };
ALPHABET: {value: [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 45, 46, 32, 42, 36, 47, 43, 37]},
CHARACTER_ENCODINGS: {value: [0x034, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064, 0x109, 0x049, 0x148, 0x019, 0x118, 0x058, 0x00D, 0x10C, 0x04C, 0x01C, 0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106, 0x046, 0x016, 0x181, 0x0C1, 0x1C0, 0x091, 0x190, 0x0D0, 0x085, 0x184, 0x0C4, 0x094, 0x0A8, 0x0A2, 0x08A, 0x02A]}, Code39Reader.prototype._decode = function() {
ASTERISK: {value: 0x094}, var self = this,
FORMAT: {value: "code_39", writeable: false} counters = [0, 0, 0, 0, 0, 0, 0, 0, 0],
}; result = [],
start = self._findStart(),
Code39Reader.prototype = Object.create(BarcodeReader.prototype, properties); decodedChar,
Code39Reader.prototype.constructor = Code39Reader; lastStart,
pattern,
Code39Reader.prototype._toCounters = function(start, counter) { nextStart;
var self = this,
numCounters = counter.length, if (!start) {
end = self._row.length, return null;
isWhite = !self._row[start], }
i, nextStart = self._nextSet(self._row, start.end);
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; do {
}; counters = self._toCounters(nextStart, counters);
pattern = self._toPattern(counters);
Code39Reader.prototype._decode = function() { if (pattern < 0) {
var self = this, return null;
counters = [0,0,0,0,0,0,0,0,0], }
result = [], decodedChar = self._patternToChar(pattern);
start = self._findStart(), if (decodedChar < 0){
decodedChar, return null;
lastStart, }
pattern, result.push(decodedChar);
nextStart; lastStart = nextStart;
nextStart += ArrayHelper.sum(counters);
if (!start) { nextStart = self._nextSet(self._row, nextStart);
return null; } while (decodedChar !== '*');
} result.pop();
nextStart = self._nextSet(self._row, start.end);
if (!result.length) {
return null;
}
do { if (!self._verifyTrailingWhitespace(lastStart, nextStart, counters)) {
counters = self._toCounters(nextStart, counters); return null;
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._verifyTrailingWhitespace(lastStart, nextStart, counters)) { return {
return null; code: result.join(""),
} start: start.start,
end: nextStart,
startInfo: start,
decodedCodes: result
};
};
Code39Reader.prototype._verifyTrailingWhitespace = function(lastStart, nextStart, counters) {
var trailingWhitespaceEnd,
patternSize = ArrayHelper.sum(counters);
trailingWhitespaceEnd = nextStart - lastStart - patternSize;
if ((trailingWhitespaceEnd * 3) >= patternSize) {
return true;
}
return false;
};
return { Code39Reader.prototype._patternToChar = function(pattern) {
code : result.join(""), var i,
start : start.start, self = this;
end : nextStart,
startInfo : start,
decodedCodes : result
};
};
Code39Reader.prototype._verifyTrailingWhitespace = function(lastStart, nextStart, counters) {
var trailingWhitespaceEnd,
patternSize = ArrayHelper.sum(counters);
trailingWhitespaceEnd = nextStart - lastStart - patternSize;
if ((trailingWhitespaceEnd * 3) >= patternSize) {
return true;
}
return false;
};
Code39Reader.prototype._patternToChar = function(pattern) { for (i = 0; i < self.CHARACTER_ENCODINGS.length; i++) {
var i, if (self.CHARACTER_ENCODINGS[i] === pattern) {
self = this; return String.fromCharCode(self.ALPHABET[i]);
}
}
};
for (i = 0; i < self.CHARACTER_ENCODINGS.length; i++) { Code39Reader.prototype._findNextWidth = function(counters, current) {
if (self.CHARACTER_ENCODINGS[i] === pattern) { var i,
return String.fromCharCode(self.ALPHABET[i]); minWidth = Number.MAX_VALUE;
}
}
};
Code39Reader.prototype._findNextWidth = function(counters, current) { for (i = 0; i < counters.length; i++) {
var i, if (counters[i] < minWidth && counters[i] > current) {
minWidth = Number.MAX_VALUE; minWidth = counters[i];
}
}
for (i = 0; i < counters.length; i++) { return minWidth;
if (counters[i] < minWidth && counters[i] > current) { };
minWidth = counters[i];
} Code39Reader.prototype._toPattern = function(counters) {
var numCounters = counters.length,
maxNarrowWidth = 0,
numWideBars = numCounters,
wideBarWidth = 0,
self = this,
pattern,
i;
while (numWideBars > 3) {
maxNarrowWidth = self._findNextWidth(counters, maxNarrowWidth);
numWideBars = 0;
pattern = 0;
for (i = 0; i < numCounters; i++) {
if (counters[i] > maxNarrowWidth) {
pattern |= 1 << (numCounters - 1 - i);
numWideBars++;
wideBarWidth += counters[i];
} }
}
return minWidth; if (numWideBars === 3) {
}; for (i = 0; i < numCounters && numWideBars > 0; i++) {
if (counters[i] > maxNarrowWidth) {
Code39Reader.prototype._toPattern = function(counters) { numWideBars--;
var numCounters = counters.length, if ((counters[i] * 2) >= wideBarWidth) {
maxNarrowWidth = 0, return -1;
numWideBars = numCounters,
wideBarWidth = 0,
self = this,
pattern,
i;
while(numWideBars > 3) {
maxNarrowWidth = self._findNextWidth(counters, maxNarrowWidth);
numWideBars = 0;
pattern = 0;
for (i = 0; i < numCounters; i++) {
if (counters[i] > maxNarrowWidth) {
pattern |= 1 << (numCounters - 1 - i);
numWideBars++;
wideBarWidth += counters[i];
}
}
if (numWideBars === 3) {
for (i = 0; i < numCounters && numWideBars > 0; i++) {
if (counters[i] > maxNarrowWidth) {
numWideBars--;
if ((counters[i] * 2) >= wideBarWidth) {
return -1;
}
}
} }
return pattern;
} }
} }
return -1; return pattern;
}; }
}
Code39Reader.prototype._findStart = function() { return -1;
var self = this, };
offset = self._nextSet(self._row),
patternStart = offset, Code39Reader.prototype._findStart = function() {
counter = [0,0,0,0,0,0,0,0,0], var self = this,
counterPos = 0, offset = self._nextSet(self._row),
isWhite = false, patternStart = offset,
i, counter = [0, 0, 0, 0, 0, 0, 0, 0, 0],
j, counterPos = 0,
whiteSpaceMustStart; isWhite = false,
i,
for ( i = offset; i < self._row.length; i++) { j,
if (self._row[i] ^ isWhite) { whiteSpaceMustStart;
counter[counterPos]++;
} else { for ( i = offset; i < self._row.length; i++) {
if (counterPos === counter.length - 1) { if (self._row[i] ^ isWhite) {
counter[counterPos]++;
// find start pattern } else {
if (self._toPattern(counter) === self.ASTERISK) { if (counterPos === counter.length - 1) {
whiteSpaceMustStart = Math.floor(Math.max(0, patternStart - ((i - patternStart) / 4))); // find start pattern
if (self._matchRange(whiteSpaceMustStart, patternStart, 0)) { if (self._toPattern(counter) === self.ASTERISK) {
return { whiteSpaceMustStart = Math.floor(Math.max(0, patternStart - ((i - patternStart) / 4)));
start: patternStart, if (self._matchRange(whiteSpaceMustStart, patternStart, 0)) {
end: i return {
}; start: patternStart,
} end: i
} };
patternStart += counter[0] + counter[1];
for ( j = 0; j < 7; j++) {
counter[j] = counter[j + 2];
}
counter[7] = 0;
counter[8] = 0;
counterPos--;
} else {
counterPos++;
} }
counter[counterPos] = 1;
isWhite = !isWhite;
} }
}
return null;
};
return (Code39Reader); patternStart += counter[0] + counter[1];
for ( j = 0; j < 7; j++) {
counter[j] = counter[j + 2];
}
counter[7] = 0;
counter[8] = 0;
counterPos--;
} else {
counterPos++;
}
counter[counterPos] = 1;
isWhite = !isWhite;
}
} }
); return null;
};
export default Code39Reader;

@ -1,59 +1,49 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ import Code39Reader from './code_39_reader';
/* global define */
function Code39VINReader() {
define( Code39Reader.call(this);
[ }
"./code_39_reader"
], var patterns = {
function(Code39Reader) { IOQ: /[IOQ]/g,
"use strict"; AZ09: /[A-Z0-9]{17}/
};
function Code39VINReader() {
Code39Reader.call(this); Code39VINReader.prototype = Object.create(Code39Reader.prototype);
} Code39VINReader.prototype.constructor = Code39VINReader;
var patterns = { // Cribbed from:
IOQ: /[IOQ]/g, // https://github.com/zxing/zxing/blob/master/core/src/main/java/com/google/zxing/client/result/VINResultParser.java
AZ09: /[A-Z0-9]{17}/ Code39VINReader.prototype._decode = function() {
}; var result = Code39Reader.prototype._decode.apply(this);
if (!result) {
Code39VINReader.prototype = Object.create(Code39Reader.prototype); return null;
Code39VINReader.prototype.constructor = Code39VINReader;
// Cribbed from:
// https://github.com/zxing/zxing/blob/master/core/src/main/java/com/google/zxing/client/result/VINResultParser.java
Code39VINReader.prototype._decode = function() {
var result = Code39Reader.prototype._decode.apply(this);
if (!result) {
return null;
}
var code = result.code;
if (!code) {
return;
}
code = code.replace(patterns.IOQ, '');
if (!code.match(patterns.AZ09)) {
console.log('Failed AZ09 pattern code:', code);
return null;
}
if (!this._checkChecksum(code)) {
return null;
}
result.code = code;
return result;
};
Code39VINReader.prototype._checkChecksum = function(code) {
// TODO
return !!code;
};
return (Code39VINReader);
} }
);
var code = result.code;
if (!code) {
return null;
}
code = code.replace(patterns.IOQ, '');
if (!code.match(patterns.AZ09)) {
console.log('Failed AZ09 pattern code:', code);
return null;
}
if (!this._checkChecksum(code)) {
return null;
}
result.code = code;
return result;
};
Code39VINReader.prototype._checkChecksum = function(code) {
// TODO
return !!code;
};
export default Code39VINReader;

@ -1,44 +1,40 @@
/** export default {
* The basic configuration inputStream: {
*/ name: "Live",
type: "LiveStream",
define(function(){ constraints: {
var config = { width: 640,
inputStream: { name: "Live", height: 480,
type: "LiveStream", minAspectRatio: 0,
constraints: { maxAspectRatio: 100,
width: 640, facing: "environment" // or user
height: 480, },
minAspectRatio: 0, area: {
maxAspectRatio: 100, top: "0%",
facing: "environment" // or user right: "0%",
}, left: "0%",
area: { bottom: "0%"
top: "0%", },
right: "0%", singleChannel: false // true: only the red color-channel is read
left: "0%", },
bottom: "0%" tracking: false,
}, debug: false,
singleChannel: false // true: only the red color-channel is read controls: false,
}, locate: true,
tracking: false, numOfWorkers: 4,
debug: false, visual: {
controls: false,
locate: true,
numOfWorkers: 4,
visual: {
show: true show: true
}, },
decoder:{ decoder: {
drawBoundingBox: false, drawBoundingBox: false,
showFrequency: false, showFrequency: false,
drawScanline: false, drawScanline: false,
showPattern: false, showPattern: false,
readers: [ readers: [
'code_128_reader' 'code_128_reader'
] ]
}, },
locator: { locator: {
halfSample: true, halfSample: true,
patchSize: "medium", // x-small, small, medium, large, x-large patchSize: "medium", // x-small, small, medium, large, x-large
showCanvas: false, showCanvas: false,
@ -49,12 +45,9 @@ define(function(){
showPatchLabels: false, showPatchLabels: false,
showRemainingPatchLabels: false, showRemainingPatchLabels: false,
boxFromPatches: { boxFromPatches: {
showTransformed: false, showTransformed: false,
showTransformedBox: false, showTransformedBox: false,
showBB: false showBB: false
} }
} }
}; };
return config;
});

File diff suppressed because it is too large Load Diff

@ -1,55 +1,45 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ import EANReader from './ean_reader';
/* global define */
function EAN8Reader() {
define( EANReader.call(this);
[ }
"./ean_reader"
], var properties = {
function(EANReader) { FORMAT: {value: "ean_8", writeable: false}
"use strict"; };
function EAN8Reader() { EAN8Reader.prototype = Object.create(EANReader.prototype, properties);
EANReader.call(this); EAN8Reader.prototype.constructor = EAN8Reader;
EAN8Reader.prototype._decodePayload = function(code, result, decodedCodes) {
var i,
self = this;
for ( i = 0; i < 4; i++) {
code = self._decodeCode(code.end, self.CODE_G_START);
if (!code) {
return null;
} }
result.push(code.code);
decodedCodes.push(code);
}
var properties = { code = self._findPattern(self.MIDDLE_PATTERN, code.end, true, false);
FORMAT: {value: "ean_8", writeable: false} if (code === null) {
}; return null;
EAN8Reader.prototype = Object.create(EANReader.prototype, properties);
EAN8Reader.prototype.constructor = EAN8Reader;
EAN8Reader.prototype._decodePayload = function(code, result, decodedCodes) {
var i,
self = this;
for ( i = 0; i < 4; i++) {
code = self._decodeCode(code.end, self.CODE_G_START);
if (!code) {
return null;
}
result.push(code.code);
decodedCodes.push(code);
}
code = self._findPattern(self.MIDDLE_PATTERN, code.end, true, false);
if (code === null) {
return null;
}
decodedCodes.push(code);
for ( i = 0; i < 4; i++) {
code = self._decodeCode(code.end, self.CODE_G_START);
if (!code) {
return null;
}
decodedCodes.push(code);
result.push(code.code);
}
return code;
};
return (EAN8Reader);
} }
); decodedCodes.push(code);
for ( i = 0; i < 4; i++) {
code = self._decodeCode(code.end, self.CODE_G_START);
if (!code) {
return null;
}
decodedCodes.push(code);
result.push(code.code);
}
return code;
};
export default EAN8Reader;

@ -1,337 +1,327 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ import BarcodeReader from './barcode_reader';
/* global define */
function EANReader(opts) {
define( BarcodeReader.call(this, opts);
[ }
"./barcode_reader"
], var properties = {
function(BarcodeReader) { CODE_L_START: {value: 0},
"use strict"; MODULO: {value: 7},
CODE_G_START: {value: 10},
function EANReader(opts) { START_PATTERN: {value: [1 / 3 * 7, 1 / 3 * 7, 1 / 3 * 7]},
BarcodeReader.call(this, opts); STOP_PATTERN: {value: [1 / 3 * 7, 1 / 3 * 7, 1 / 3 * 7]},
} MIDDLE_PATTERN: {value: [1 / 5 * 7, 1 / 5 * 7, 1 / 5 * 7, 1 / 5 * 7, 1 / 5 * 7]},
CODE_PATTERN: {value: [
var properties = { [3, 2, 1, 1],
CODE_L_START : {value: 0}, [2, 2, 2, 1],
MODULO : {value: 7}, [2, 1, 2, 2],
CODE_G_START : {value: 10}, [1, 4, 1, 1],
START_PATTERN : {value: [1 / 3 * 7, 1 / 3 * 7, 1 / 3 * 7]}, [1, 1, 3, 2],
STOP_PATTERN : {value: [1 / 3 * 7, 1 / 3 * 7, 1 / 3 * 7]}, [1, 2, 3, 1],
MIDDLE_PATTERN : {value: [1 / 5 * 7, 1 / 5 * 7, 1 / 5 * 7, 1 / 5 * 7, 1 / 5 * 7]}, [1, 1, 1, 4],
CODE_PATTERN : {value: [ [1, 3, 1, 2],
[3, 2, 1, 1], [1, 2, 1, 3],
[2, 2, 2, 1], [3, 1, 1, 2],
[2, 1, 2, 2], [1, 1, 2, 3],
[1, 4, 1, 1], [1, 2, 2, 2],
[1, 1, 3, 2], [2, 2, 1, 2],
[1, 2, 3, 1], [1, 1, 4, 1],
[1, 1, 1, 4], [2, 3, 1, 1],
[1, 3, 1, 2], [1, 3, 2, 1],
[1, 2, 1, 3], [4, 1, 1, 1],
[3, 1, 1, 2], [2, 1, 3, 1],
[1, 1, 2, 3], [3, 1, 2, 1],
[1, 2, 2, 2], [2, 1, 1, 3]
[2, 2, 1, 2], ]},
[1, 1, 4, 1], CODE_FREQUENCY: {value: [0, 11, 13, 14, 19, 25, 28, 21, 22, 26]},
[2, 3, 1, 1], SINGLE_CODE_ERROR: {value: 0.67},
[1, 3, 2, 1], AVG_CODE_ERROR: {value: 0.27},
[4, 1, 1, 1], FORMAT: {value: "ean_13", writeable: false}
[2, 1, 3, 1], };
[3, 1, 2, 1],
[2, 1, 1, 3] EANReader.prototype = Object.create(BarcodeReader.prototype, properties);
]}, EANReader.prototype.constructor = EANReader;
CODE_FREQUENCY : {value: [0, 11, 13, 14, 19, 25, 28, 21, 22, 26]},
SINGLE_CODE_ERROR: {value: 0.67}, EANReader.prototype._decodeCode = function(start, coderange) {
AVG_CODE_ERROR: {value: 0.27}, var counter = [0, 0, 0, 0],
FORMAT: {value: "ean_13", writeable: false} i,
}; self = this,
offset = start,
EANReader.prototype = Object.create(BarcodeReader.prototype, properties); isWhite = !self._row[offset],
EANReader.prototype.constructor = EANReader; counterPos = 0,
bestMatch = {
EANReader.prototype._decodeCode = function(start, coderange) { error: Number.MAX_VALUE,
var counter = [0, 0, 0, 0], code: -1,
i, start: start,
self = this, end: start
offset = start, },
isWhite = !self._row[offset], code,
counterPos = 0, error,
bestMatch = { normalized;
error : Number.MAX_VALUE,
code : -1, if (!coderange) {
start : start, coderange = self.CODE_PATTERN.length;
end : start }
},
code,
error,
normalized;
if (!coderange) {
coderange = self.CODE_PATTERN.length;
}
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); normalized = self._normalize(counter);
if (normalized) { if (normalized) {
for (code = 0; code < coderange; code++) { for (code = 0; code < coderange; code++) {
error = self._matchPattern(normalized, self.CODE_PATTERN[code]); error = self._matchPattern(normalized, self.CODE_PATTERN[code]);
if (error < bestMatch.error) { if (error < bestMatch.error) {
bestMatch.code = code; bestMatch.code = code;
bestMatch.error = error; bestMatch.error = error;
}
}
bestMatch.end = i;
if (bestMatch.error > self.AVG_CODE_ERROR) {
return null;
}
return bestMatch;
} }
} else {
counterPos++;
} }
counter[counterPos] = 1; bestMatch.end = i;
isWhite = !isWhite; if (bestMatch.error > self.AVG_CODE_ERROR) {
return null;
}
return bestMatch;
} }
} else {
counterPos++;
} }
return null; counter[counterPos] = 1;
}; isWhite = !isWhite;
}
EANReader.prototype._findPattern = function(pattern, offset, isWhite, tryHarder, epsilon) { }
var counter = [], return null;
self = this, };
i,
counterPos = 0, EANReader.prototype._findPattern = function(pattern, offset, isWhite, tryHarder, epsilon) {
bestMatch = { var counter = [],
error : Number.MAX_VALUE, self = this,
code : -1, i,
start : 0, counterPos = 0,
end : 0 bestMatch = {
}, error: Number.MAX_VALUE,
error, code: -1,
j, start: 0,
sum, end: 0
normalized; },
error,
if (!offset) { j,
offset = self._nextSet(self._row); sum,
} normalized;
if (isWhite === undefined) { if (!offset) {
isWhite = false; offset = self._nextSet(self._row);
} }
if (tryHarder === undefined) {
tryHarder = true;
}
if ( epsilon === undefined) { if (isWhite === undefined) {
epsilon = self.AVG_CODE_ERROR; isWhite = false;
} }
for ( i = 0; i < pattern.length; i++) { if (tryHarder === undefined) {
counter[i] = 0; tryHarder = true;
} }
for ( i = offset; i < self._row.length; i++) { if ( epsilon === undefined) {
if (self._row[i] ^ isWhite) { epsilon = self.AVG_CODE_ERROR;
counter[counterPos]++; }
} else {
if (counterPos === counter.length - 1) {
sum = 0;
for ( j = 0; j < counter.length; j++) {
sum += counter[j];
}
normalized = self._normalize(counter);
if (normalized) {
error = self._matchPattern(normalized, 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;
};
EANReader.prototype._findStart = function() { for ( i = 0; i < pattern.length; i++) {
var self = this, counter[i] = 0;
leadingWhitespaceStart, }
offset = self._nextSet(self._row),
startInfo;
while(!startInfo) { for ( i = offset; i < self._row.length; i++) {
startInfo = self._findPattern(self.START_PATTERN, offset); if (self._row[i] ^ isWhite) {
if (!startInfo) { counter[counterPos]++;
return null; } else {
if (counterPos === counter.length - 1) {
sum = 0;
for ( j = 0; j < counter.length; j++) {
sum += counter[j];
} }
leadingWhitespaceStart = startInfo.start - (startInfo.end - startInfo.start); normalized = self._normalize(counter);
if (leadingWhitespaceStart >= 0) { if (normalized) {
if (self._matchRange(leadingWhitespaceStart, startInfo.start, 0)) { error = self._matchPattern(normalized, pattern);
return startInfo;
if (error < epsilon) {
bestMatch.error = error;
bestMatch.start = i - sum;
bestMatch.end = i;
return bestMatch;
} }
} }
offset = startInfo.end; if (tryHarder) {
startInfo = null; for ( j = 0; j < counter.length - 2; j++) {
} counter[j] = counter[j + 2];
}; }
counter[counter.length - 2] = 0;
EANReader.prototype._verifyTrailingWhitespace = function(endInfo) { counter[counter.length - 1] = 0;
var self = this, counterPos--;
trailingWhitespaceEnd; } else {
return null;
trailingWhitespaceEnd = endInfo.end + (endInfo.end - endInfo.start);
if (trailingWhitespaceEnd < self._row.length) {
if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) {
return endInfo;
} }
} else {
counterPos++;
} }
counter[counterPos] = 1;
isWhite = !isWhite;
}
}
return null;
};
EANReader.prototype._findStart = function() {
var self = this,
leadingWhitespaceStart,
offset = self._nextSet(self._row),
startInfo;
while (!startInfo) {
startInfo = self._findPattern(self.START_PATTERN, offset);
if (!startInfo) {
return null; return null;
}; }
leadingWhitespaceStart = startInfo.start - (startInfo.end - startInfo.start);
if (leadingWhitespaceStart >= 0) {
if (self._matchRange(leadingWhitespaceStart, startInfo.start, 0)) {
return startInfo;
}
}
offset = startInfo.end;
startInfo = null;
}
};
EANReader.prototype._findEnd = function(offset, isWhite) { EANReader.prototype._verifyTrailingWhitespace = function(endInfo) {
var self = this, var self = this,
endInfo = self._findPattern(self.STOP_PATTERN, offset, isWhite, false); trailingWhitespaceEnd;
return endInfo !== null ? self._verifyTrailingWhitespace(endInfo) : null; trailingWhitespaceEnd = endInfo.end + (endInfo.end - endInfo.start);
}; if (trailingWhitespaceEnd < self._row.length) {
if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) {
return endInfo;
}
}
return null;
};
EANReader.prototype._calculateFirstDigit = function(codeFrequency) { EANReader.prototype._findEnd = function(offset, isWhite) {
var i, var self = this,
self = this; endInfo = self._findPattern(self.STOP_PATTERN, offset, isWhite, false);
for ( i = 0; i < self.CODE_FREQUENCY.length; i++) { return endInfo !== null ? self._verifyTrailingWhitespace(endInfo) : null;
if (codeFrequency === self.CODE_FREQUENCY[i]) { };
return i;
}
}
return null;
};
EANReader.prototype._decodePayload = function(code, result, decodedCodes) { EANReader.prototype._calculateFirstDigit = function(codeFrequency) {
var i, var i,
self = this, self = this;
codeFrequency = 0x0,
firstDigit;
for ( i = 0; i < 6; i++) { for ( i = 0; i < self.CODE_FREQUENCY.length; i++) {
code = self._decodeCode(code.end); if (codeFrequency === self.CODE_FREQUENCY[i]) {
if (!code) { return i;
return null; }
} }
if (code.code >= self.CODE_G_START) { return null;
code.code = code.code - self.CODE_G_START; };
codeFrequency |= 1 << (5 - i);
} else { EANReader.prototype._decodePayload = function(code, result, decodedCodes) {
codeFrequency |= 0 << (5 - i); var i,
} self = this,
result.push(code.code); codeFrequency = 0x0,
decodedCodes.push(code); firstDigit;
}
for ( i = 0; i < 6; i++) {
code = self._decodeCode(code.end);
if (!code) {
return null;
}
if (code.code >= self.CODE_G_START) {
code.code = code.code - self.CODE_G_START;
codeFrequency |= 1 << (5 - i);
} else {
codeFrequency |= 0 << (5 - i);
}
result.push(code.code);
decodedCodes.push(code);
}
firstDigit = self._calculateFirstDigit(codeFrequency); firstDigit = self._calculateFirstDigit(codeFrequency);
if (firstDigit === null) { if (firstDigit === null) {
return null; return null;
} }
result.unshift(firstDigit); result.unshift(firstDigit);
code = self._findPattern(self.MIDDLE_PATTERN, code.end, true, false); code = self._findPattern(self.MIDDLE_PATTERN, code.end, true, false);
if (code === null) { if (code === null) {
return null; return null;
} }
decodedCodes.push(code); decodedCodes.push(code);
for ( i = 0; i < 6; i++) { for ( i = 0; i < 6; i++) {
code = self._decodeCode(code.end, self.CODE_G_START); code = self._decodeCode(code.end, self.CODE_G_START);
if (!code) { if (!code) {
return null; return null;
} }
decodedCodes.push(code); decodedCodes.push(code);
result.push(code.code); result.push(code.code);
} }
return code; return code;
}; };
EANReader.prototype._decode = function() { EANReader.prototype._decode = function() {
var startInfo, var startInfo,
self = this, self = this,
code, code,
result = [], result = [],
decodedCodes = []; decodedCodes = [];
startInfo = self._findStart(); startInfo = self._findStart();
if (!startInfo) { if (!startInfo) {
return null; return null;
} }
code = { code = {
code : startInfo.code, code: startInfo.code,
start : startInfo.start, start: startInfo.start,
end : startInfo.end end: startInfo.end
}; };
decodedCodes.push(code); decodedCodes.push(code);
code = self._decodePayload(code, result, decodedCodes); code = self._decodePayload(code, result, decodedCodes);
if (!code) { if (!code) {
return null; return null;
} }
code = self._findEnd(code.end, false); code = self._findEnd(code.end, false);
if (!code){ if (!code){
return null; return null;
} }
decodedCodes.push(code); decodedCodes.push(code);
// Checksum // Checksum
if (!self._checksum(result)) { if (!self._checksum(result)) {
return null; return null;
} }
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
}; };
}; };
EANReader.prototype._checksum = function(result) { EANReader.prototype._checksum = function(result) {
var sum = 0, i; var sum = 0, i;
for ( i = result.length - 2; i >= 0; i -= 2) { for ( i = result.length - 2; i >= 0; i -= 2) {
sum += result[i]; sum += result[i];
}
sum *= 3;
for ( i = result.length - 1; i >= 0; i -= 2) {
sum += result[i];
}
return sum % 10 === 0;
};
return (EANReader);
} }
); sum *= 3;
for ( i = result.length - 1; i >= 0; i -= 2) {
sum += result[i];
}
return sum % 10 === 0;
};
export default (EANReader);

@ -1,91 +1,82 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ export default (function() {
/* global define */ var events = {};
define(function() { function getEvent(eventName) {
"use strict"; if (!events[eventName]) {
events[eventName] = {
subscribers: []
};
}
return events[eventName];
}
var _events = function() { function clearEvents(){
var events = {}; events = {};
}
function getEvent(eventName) { function publishSubscription(subscription, data) {
if (!events[eventName]) { if (subscription.async) {
events[eventName] = { setTimeout(function() {
subscribers : [] subscription.callback(data);
}; }, 4);
} } else {
return events[eventName]; subscription.callback(data);
}
function clearEvents(){
events = {};
} }
}
function publishSubscription(subscription, data) { function subscribe(event, callback, async) {
if (subscription.async) { var subscription;
setTimeout(function() {
subscription.callback(data); if ( typeof callback === "function") {
}, 4); subscription = {
} else { callback: callback,
subscription.callback(data); async: async
};
} else {
subscription = callback;
if (!subscription.callback) {
throw "Callback was not specified on options";
} }
} }
function subscribe(event, callback, async) {
var subscription;
if ( typeof callback === "function") { getEvent(event).subscribers.push(subscription);
subscription = { }
callback : callback,
async : async
};
} else {
subscription = callback;
if (!subscription.callback) {
throw "Callback was not specified on options";
}
}
getEvent(event).subscribers.push(subscription); return {
} subscribe: function(event, callback, async) {
return subscribe(event, callback, async);
},
publish: function(eventName, data) {
var event = getEvent(eventName),
subscribers = event.subscribers;
event.subscribers = subscribers.filter(function(subscriber) {
publishSubscription(subscriber, data);
return !subscriber.once;
});
},
once: function(event, callback, async) {
subscribe(event, {
callback: callback,
async: async,
once: true
});
},
unsubscribe: function(eventName, callback) {
var event;
return { if (eventName) {
subscribe : function(event, callback, async) { event = getEvent(eventName);
return subscribe(event, callback, async); if (event && callback) {
}, event.subscribers = event.subscribers.filter(function(subscriber){
publish : function(eventName, data) { return subscriber.callback !== callback;
var event = getEvent(eventName), });
subscribers = event.subscribers;
event.subscribers = subscribers.filter(function(subscriber) {
publishSubscription(subscriber, data);
return !subscriber.once;
});
},
once: function(event, callback, async) {
subscribe(event, {
callback: callback,
async: async,
once: true
});
},
unsubscribe: function(eventName, callback) {
var event;
if (eventName) {
event = getEvent(eventName);
if (event && callback) {
event.subscribers = event.subscribers.filter(function(subscriber){
return subscriber.callback !== callback;
});
} else {
event.subscribers = [];
}
} else { } else {
clearEvents(); event.subscribers = [];
} }
} else {
clearEvents();
} }
}; }
}(); };
})();
return _events;
});

@ -1,78 +1,73 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ import CVUtils from './cv_utils';
/* global define */
define(["cv_utils"], function(CVUtils) { var FrameGrabber = {};
"use strict";
var FrameGrabber = {}; FrameGrabber.create = function(inputStream, canvas) {
var _that = {},
_streamConfig = inputStream.getConfig(),
_video_size = CVUtils.imageRef(inputStream.getRealWidth(), inputStream.getRealHeight()),
_canvasSize = inputStream.getCanvasSize(),
_size = CVUtils.imageRef(inputStream.getWidth(), inputStream.getHeight()),
topRight = inputStream.getTopRight(),
_sx = topRight.x,
_sy = topRight.y,
_canvas,
_ctx = null,
_data = null;
FrameGrabber.create = function(inputStream, canvas) { _canvas = canvas ? canvas : document.createElement("canvas");
var _that = {}, _canvas.width = _canvasSize.x;
_streamConfig = inputStream.getConfig(), _canvas.height = _canvasSize.y;
_video_size = CVUtils.imageRef(inputStream.getRealWidth(), inputStream.getRealHeight()), _ctx = _canvas.getContext("2d");
_canvasSize = inputStream.getCanvasSize(), _data = new Uint8Array(_size.x * _size.y);
_size = CVUtils.imageRef(inputStream.getWidth(), inputStream.getHeight()), console.log("FrameGrabber", JSON.stringify({
topRight = inputStream.getTopRight(), size: _size,
_sx = topRight.x, topRight: topRight,
_sy = topRight.y, videoSize: _video_size,
_canvas, canvasSize: _canvasSize
_ctx = null, }));
_data = null;
_canvas = canvas ? canvas : document.createElement("canvas"); /**
_canvas.width = _canvasSize.x; * Uses the given array as frame-buffer
_canvas.height = _canvasSize.y; */
_ctx = _canvas.getContext("2d"); _that.attachData = function(data) {
_data = new Uint8Array(_size.x * _size.y); _data = data;
console.log("FrameGrabber", JSON.stringify({ };
size: _size,
topRight: topRight,
videoSize: _video_size,
canvasSize: _canvasSize
}));
/**
* Uses the given array as frame-buffer
*/
_that.attachData = function(data) {
_data = data;
};
/** /**
* Returns the used frame-buffer * Returns the used frame-buffer
*/ */
_that.getData = function() { _that.getData = function() {
return _data; return _data;
}; };
/** /**
* Fetches a frame from the input-stream and puts into the frame-buffer. * Fetches a frame from the input-stream and puts into the frame-buffer.
* The image-data is converted to gray-scale and then half-sampled if configured. * The image-data is converted to gray-scale and then half-sampled if configured.
*/ */
_that.grab = function() { _that.grab = function() {
var doHalfSample = _streamConfig.halfSample, var doHalfSample = _streamConfig.halfSample,
frame = inputStream.getFrame(), frame = inputStream.getFrame(),
ctxData; ctxData;
if (frame) { if (frame) {
_ctx.drawImage(frame, 0, 0, _canvasSize.x, _canvasSize.y); _ctx.drawImage(frame, 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); CVUtils.grayAndHalfSampleFromCanvasData(ctxData, _size, _data);
} else {
CVUtils.computeGray(ctxData, _data, _streamConfig);
}
return true;
} else { } else {
return false; CVUtils.computeGray(ctxData, _data, _streamConfig);
} }
}; return true;
} else {
_that.getSize = function() { return false;
return _size; }
}; };
return _that; _that.getSize = function() {
return _size;
}; };
return (FrameGrabber); return _that;
}); };
export default FrameGrabber;

@ -1,40 +0,0 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
define([], function() {
"use strict";
function createNode(htmlStr) {
var temp = document.createElement('div');
temp.innerHTML = htmlStr;
while (temp.firstChild) {
return temp.firstChild;
}
}
function mergeObjects(obj1, obj2) {
for (var p in obj2) {
try {
if (obj2[p].constructor == Object) {
obj1[p] = mergeObjects(obj1[p], obj2[p]);
} else {
obj1[p] = obj2[p];
}
} catch(e) {
obj1[p] = obj2[p];
}
}
return obj1;
}
return {
createNode : function(htmlStr) {
return createNode(htmlStr);
},
mergeObjects : function(obj1, obj2) {
return mergeObjects(obj1, obj2);
}
};
});

@ -1,344 +1,334 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ import BarcodeReader from './barcode_reader';
/* global define */ const merge = require('lodash/object/merge');
define( function I2of5Reader(opts) {
[ opts = merge(getDefaulConfig(), opts);
"./barcode_reader", BarcodeReader.call(this, opts);
"./html_utils" this.barSpaceRatio = [1, 1];
], if (opts.normalizeBarSpaceWidth) {
function(BarcodeReader, HTMLUtils) { this.SINGLE_CODE_ERROR = 0.38;
"use strict"; this.AVG_CODE_ERROR = 0.09;
}
function I2of5Reader(opts) { }
opts = HTMLUtils.mergeObjects(getDefaulConfig(), opts);
BarcodeReader.call(this, opts); function getDefaulConfig() {
this.barSpaceRatio = [1, 1]; var config = {};
if (opts.normalizeBarSpaceWidth) {
this.SINGLE_CODE_ERROR = 0.38; Object.keys(I2of5Reader.CONFIG_KEYS).forEach(function(key) {
this.AVG_CODE_ERROR = 0.09; config[key] = I2of5Reader.CONFIG_KEYS[key].default;
} });
return config;
}
var N = 1,
W = 3,
properties = {
MODULO: {value: 10},
START_PATTERN: {value: [N * 2.5, N * 2.5, N * 2.5, N * 2.5]},
STOP_PATTERN: {value: [N * 2, N * 2, W * 2]},
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.38, writable: true},
MAX_CORRECTION_FACTOR: {value: 5},
FORMAT: {value: "i2of5"}
};
I2of5Reader.prototype = Object.create(BarcodeReader.prototype, properties);
I2of5Reader.prototype.constructor = I2of5Reader;
I2of5Reader.prototype._matchPattern = function(counter, code) {
if (this.config.normalizeBarSpaceWidth) {
var i,
counterSum = [0, 0],
codeSum = [0, 0],
correction = [0, 0],
correctionRatio = this.MAX_CORRECTION_FACTOR,
correctionRatioInverse = 1 / correctionRatio;
for (i = 0; i < counter.length; i++) {
counterSum[i % 2] += counter[i];
codeSum[i % 2] += code[i];
} }
correction[0] = codeSum[0] / counterSum[0];
function getDefaulConfig() { correction[1] = codeSum[1] / counterSum[1];
var config = {};
correction[0] = Math.max(Math.min(correction[0], correctionRatio), correctionRatioInverse);
Object.keys(I2of5Reader.CONFIG_KEYS).forEach(function(key) { correction[1] = Math.max(Math.min(correction[1], correctionRatio), correctionRatioInverse);
config[key] = I2of5Reader.CONFIG_KEYS[key]['default']; this.barSpaceRatio = correction;
}); for (i = 0; i < counter.length; i++) {
return config; counter[i] *= this.barSpaceRatio[i % 2];
} }
}
return BarcodeReader.prototype._matchPattern.call(this, counter, code);
};
I2of5Reader.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,
normalized,
epsilon = self.AVG_CODE_ERROR;
isWhite = isWhite || false;
tryHarder = tryHarder || false;
if (!offset) {
offset = self._nextSet(self._row);
}
var N = 1, for ( i = 0; i < pattern.length; i++) {
W = 3, counter[i] = 0;
properties = { }
MODULO : {value: 10},
START_PATTERN : {value: [N*2.5, N*2.5, N*2.5, N*2.5]},
STOP_PATTERN : {value: [N*2, N*2, W*2]},
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.38, writable: true},
MAX_CORRECTION_FACTOR: {value: 5},
FORMAT: {value: "i2of5"}
};
I2of5Reader.prototype = Object.create(BarcodeReader.prototype, properties); for ( i = offset; i < self._row.length; i++) {
I2of5Reader.prototype.constructor = I2of5Reader; if (self._row[i] ^ isWhite) {
counter[counterPos]++;
I2of5Reader.prototype._matchPattern = function(counter, code) { } else {
if (this.config.normalizeBarSpaceWidth) { if (counterPos === counter.length - 1) {
var i, sum = 0;
counterSum = [0, 0], for ( j = 0; j < counter.length; j++) {
codeSum = [0, 0], sum += counter[j];
correction = [0, 0],
correctionRatio = this.MAX_CORRECTION_FACTOR,
correctionRatioInverse = 1 / correctionRatio;
for (i = 0; i < counter.length; i++) {
counterSum[i % 2] += counter[i];
codeSum[i % 2] += code[i];
}
correction[0] = codeSum[0] / counterSum[0];
correction[1] = codeSum[1] / counterSum[1];
correction[0] = Math.max(Math.min(correction[0], correctionRatio), correctionRatioInverse);
correction[1] = Math.max(Math.min(correction[1], correctionRatio), correctionRatioInverse);
this.barSpaceRatio = correction;
for (i = 0; i < counter.length; i++) {
counter[i] *= this.barSpaceRatio[i % 2];
} }
} normalized = self._normalize(counter);
return BarcodeReader.prototype._matchPattern.call(this, counter, code); if (normalized) {
}; error = self._matchPattern(normalized, pattern);
I2of5Reader.prototype._findPattern = function(pattern, offset, isWhite, tryHarder) { if (error < epsilon) {
var counter = [], bestMatch.error = error;
self = this, bestMatch.start = i - sum;
i, bestMatch.end = i;
counterPos = 0, return bestMatch;
bestMatch = {
error : Number.MAX_VALUE,
code : -1,
start : 0,
end : 0
},
error,
j,
sum,
normalized,
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];
}
normalized = self._normalize(counter);
if (normalized) {
error = self._matchPattern(normalized, 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;
} }
} if (tryHarder) {
return null; for (j = 0; j < counter.length - 2; j++) {
}; counter[j] = counter[j + 2];
I2of5Reader.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) / 4);
leadingWhitespaceStart = startInfo.start - narrowBarWidth*10;
if (leadingWhitespaceStart >= 0) {
if (self._matchRange(leadingWhitespaceStart, startInfo.start, 0)) {
return startInfo;
} }
counter[counter.length - 2] = 0;
counter[counter.length - 1] = 0;
counterPos--;
} else {
return null;
} }
offset = startInfo.end; } else {
startInfo = null; counterPos++;
}
};
I2of5Reader.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;
}
} }
counter[counterPos] = 1;
isWhite = !isWhite;
}
}
return null;
};
I2of5Reader.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; return null;
}; }
narrowBarWidth = Math.floor((startInfo.end - startInfo.start) / 4);
I2of5Reader.prototype._findEnd = function() { leadingWhitespaceStart = startInfo.start - narrowBarWidth * 10;
var self = this, if (leadingWhitespaceStart >= 0) {
endInfo, if (self._matchRange(leadingWhitespaceStart, startInfo.start, 0)) {
tmp; return startInfo;
self._row.reverse();
endInfo = self._findPattern(self.STOP_PATTERN);
self._row.reverse();
if (endInfo === null) {
return null;
} }
}
offset = startInfo.end;
startInfo = null;
}
};
// reverse numbers I2of5Reader.prototype._verifyTrailingWhitespace = function(endInfo) {
tmp = endInfo.start; var self = this,
endInfo.start = self._row.length - endInfo.end; trailingWhitespaceEnd;
endInfo.end = self._row.length - tmp;
return endInfo !== null ? self._verifyTrailingWhitespace(endInfo) : null; trailingWhitespaceEnd = endInfo.end + ((endInfo.end - endInfo.start) / 2);
}; if (trailingWhitespaceEnd < self._row.length) {
if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) {
return endInfo;
}
}
return null;
};
I2of5Reader.prototype._decodePair = function(counterPair) { I2of5Reader.prototype._findEnd = function() {
var i, var self = this,
code, endInfo,
codes = [], tmp;
self = this;
for (i = 0; i < counterPair.length; i++) { self._row.reverse();
code = self._decodeCode(counterPair[i]); endInfo = self._findPattern(self.STOP_PATTERN);
if (!code) { self._row.reverse();
return null;
}
codes.push(code);
}
return codes;
};
I2of5Reader.prototype._decodeCode = function(counter) { if (endInfo === null) {
var j, return null;
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];
}
normalized = self._normalize(counter);
if (normalized) {
for (code = 0; code < self.CODE_PATTERN.length; code++) {
error = self._matchPattern(normalized, self.CODE_PATTERN[code]);
if (error < bestMatch.error) {
bestMatch.code = code;
bestMatch.error = error;
}
}
if (bestMatch.error < epsilon) {
return bestMatch;
}
}
return null;
};
I2of5Reader.prototype._decodePayload = function(counters, result, decodedCodes) { // reverse numbers
var i, tmp = endInfo.start;
self = this, endInfo.start = self._row.length - endInfo.end;
pos = 0, endInfo.end = self._row.length - tmp;
counterLength = counters.length,
counterPair = [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0]],
codes;
while (pos < counterLength) {
for (i = 0; i < 5; i++) {
counterPair[0][i] = counters[pos]*this.barSpaceRatio[0];
counterPair[1][i] = counters[pos + 1]*this.barSpaceRatio[1];
pos += 2;
}
codes = self._decodePair(counterPair);
if (!codes) {
return null;
}
for (i = 0; i < codes.length; i++) {
result.push(codes[i].code + "");
decodedCodes.push(codes[i]);
}
}
return codes;
};
I2of5Reader.prototype._verifyCounterLength = function(counters) { return endInfo !== null ? self._verifyTrailingWhitespace(endInfo) : null;
return (counters.length % 10 === 0); };
};
I2of5Reader.prototype._decode = function() { I2of5Reader.prototype._decodePair = function(counterPair) {
var startInfo, var i,
endInfo, code,
self = this, codes = [],
code, self = this;
result = [],
decodedCodes = [],
counters;
startInfo = self._findStart();
if (!startInfo) {
return null;
}
decodedCodes.push(startInfo);
endInfo = self._findEnd(); for (i = 0; i < counterPair.length; i++) {
if (!endInfo) { code = self._decodeCode(counterPair[i]);
return null; if (!code) {
} return null;
}
codes.push(code);
}
return codes;
};
I2of5Reader.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
};
counters = self._fillCounters(startInfo.end, endInfo.start, false); for ( j = 0; j < counter.length; j++) {
if (!self._verifyCounterLength(counters)) { sum += counter[j];
return null; }
} normalized = self._normalize(counter);
code = self._decodePayload(counters, result, decodedCodes); if (normalized) {
if (!code) { for (code = 0; code < self.CODE_PATTERN.length; code++) {
return null; error = self._matchPattern(normalized, self.CODE_PATTERN[code]);
} if (error < bestMatch.error) {
if (result.length % 2 !== 0 || bestMatch.code = code;
result.length < 6) { bestMatch.error = error;
return null;
} }
}
if (bestMatch.error < epsilon) {
return bestMatch;
}
}
return null;
};
I2of5Reader.prototype._decodePayload = function(counters, result, decodedCodes) {
var i,
self = this,
pos = 0,
counterLength = counters.length,
counterPair = [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0]],
codes;
while (pos < counterLength) {
for (i = 0; i < 5; i++) {
counterPair[0][i] = counters[pos] * this.barSpaceRatio[0];
counterPair[1][i] = counters[pos + 1] * this.barSpaceRatio[1];
pos += 2;
}
codes = self._decodePair(counterPair);
if (!codes) {
return null;
}
for (i = 0; i < codes.length; i++) {
result.push(codes[i].code + "");
decodedCodes.push(codes[i]);
}
}
return codes;
};
I2of5Reader.prototype._verifyCounterLength = function(counters) {
return (counters.length % 10 === 0);
};
I2of5Reader.prototype._decode = function() {
var startInfo,
endInfo,
self = this,
code,
result = [],
decodedCodes = [],
counters;
startInfo = self._findStart();
if (!startInfo) {
return null;
}
decodedCodes.push(startInfo);
decodedCodes.push(endInfo); endInfo = self._findEnd();
return { if (!endInfo) {
code : result.join(""), return null;
start : startInfo.start, }
end : endInfo.end,
startInfo : startInfo,
decodedCodes : decodedCodes
};
};
I2of5Reader.CONFIG_KEYS = { counters = self._fillCounters(startInfo.end, endInfo.start, false);
normalizeBarSpaceWidth: { if (!self._verifyCounterLength(counters)) {
'type': 'boolean', return null;
'default': false, }
'description': 'If true, the reader tries to normalize the' + code = self._decodePayload(counters, result, decodedCodes);
'width-difference between bars and spaces' if (!code) {
} return null;
}; }
if (result.length % 2 !== 0 ||
result.length < 6) {
return null;
}
return (I2of5Reader); decodedCodes.push(endInfo);
return {
code: result.join(""),
start: startInfo.start,
end: endInfo.end,
startInfo: startInfo,
decodedCodes: decodedCodes
};
};
I2of5Reader.CONFIG_KEYS = {
normalizeBarSpaceWidth: {
'type': 'boolean',
'default': false,
'description': 'If true, the reader tries to normalize the' +
'width-difference between bars and spaces'
} }
); };
export default I2of5Reader;

@ -1,49 +1,41 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ export default {
/* global define */ drawRect: function(pos, size, ctx, style){
ctx.strokeStyle = style.color;
define(function() { ctx.fillStyle = style.color;
"use strict"; ctx.lineWidth = 1;
ctx.beginPath();
return { ctx.strokeRect(pos.x, pos.y, size.x, size.y);
drawRect: function(pos, size, ctx, style){ },
ctx.strokeStyle = style.color; drawPath: function(path, def, ctx, style) {
ctx.fillStyle = style.color; ctx.strokeStyle = style.color;
ctx.lineWidth = 1; ctx.fillStyle = style.color;
ctx.beginPath(); ctx.lineWidth = style.lineWidth;
ctx.strokeRect(pos.x, pos.y, size.x, size.y); ctx.beginPath();
}, ctx.moveTo(path[0][def.x], path[0][def.y]);
drawPath: function(path, def, ctx, style) { for (var j = 1; j < path.length; j++) {
ctx.strokeStyle = style.color; ctx.lineTo(path[j][def.x], path[j][def.y]);
ctx.fillStyle = style.color; }
ctx.lineWidth = style.lineWidth; ctx.closePath();
ctx.beginPath(); ctx.stroke();
ctx.moveTo(path[0][def.x], path[0][def.y]); },
for (var j = 1; j < path.length; j++) { drawImage: function(imageData, size, ctx) {
ctx.lineTo(path[j][def.x], path[j][def.y]); var canvasData = ctx.getImageData(0, 0, size.x, size.y),
} data = canvasData.data,
ctx.closePath(); imageDataPos = imageData.length,
ctx.stroke(); canvasDataPos = data.length,
}, value;
drawImage: function(imageData, size, ctx) {
var canvasData = ctx.getImageData(0, 0, size.x, size.y),
data = canvasData.data,
imageDataPos = imageData.length,
canvasDataPos = data.length,
value;
if (canvasDataPos/imageDataPos !== 4) { if (canvasDataPos / imageDataPos !== 4) {
return false; return false;
} }
while(imageDataPos--){ while (imageDataPos--){
value = imageData[imageDataPos]; value = imageData[imageDataPos];
data[--canvasDataPos] = 255; data[--canvasDataPos] = 255;
data[--canvasDataPos] = value; data[--canvasDataPos] = value;
data[--canvasDataPos] = value; data[--canvasDataPos] = value;
data[--canvasDataPos] = value; data[--canvasDataPos] = value;
}
ctx.putImageData(canvasData, 0, 0);
return true;
} }
}; ctx.putImageData(canvasData, 0, 0);
return true;
}); }
};

@ -1,63 +1,56 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ var ImageLoader = {};
/* global define */ ImageLoader.load = function(directory, callback, offset, size, sequence) {
var htmlImagesSrcArray = new Array(size),
htmlImagesArray = new Array(htmlImagesSrcArray.length),
i,
img,
num;
define(function() { if (sequence === false) {
"use strict"; htmlImagesSrcArray[0] = directory;
} else {
var ImageLoader = {}; for ( i = 0; i < htmlImagesSrcArray.length; i++) {
ImageLoader.load = function(directory, callback, offset, size, sequence) { num = (offset + i);
var htmlImagesSrcArray = new Array(size), htmlImagesSrcArray[i] = directory + "image-" + ("00" + num).slice(-3) + ".jpg";
htmlImagesArray = new Array(htmlImagesSrcArray.length),
i,
img,
num;
if (sequence === false) {
htmlImagesSrcArray[0] = directory;
} else {
for ( i = 0; i < htmlImagesSrcArray.length; i++) {
num = (offset + i);
htmlImagesSrcArray[i] = directory + "image-" + ("00" + num).slice(-3) + ".jpg";
}
} }
htmlImagesArray.notLoaded = []; }
htmlImagesArray.addImage = function(img) { htmlImagesArray.notLoaded = [];
htmlImagesArray.notLoaded.push(img); htmlImagesArray.addImage = function(image) {
}; htmlImagesArray.notLoaded.push(image);
htmlImagesArray.loaded = function(loadedImg) { };
var notloadedImgs = htmlImagesArray.notLoaded; htmlImagesArray.loaded = function(loadedImg) {
for (var x = 0; x < notloadedImgs.length; x++) { var notloadedImgs = htmlImagesArray.notLoaded;
if (notloadedImgs[x] == loadedImg) { for (var x = 0; x < notloadedImgs.length; x++) {
notloadedImgs.splice(x, 1); if (notloadedImgs[x] === loadedImg) {
for (var y = 0; y < htmlImagesSrcArray.length; y++) { notloadedImgs.splice(x, 1);
var imgName = htmlImagesSrcArray[y].substr(htmlImagesSrcArray[y].lastIndexOf("/")); for (var y = 0; y < htmlImagesSrcArray.length; y++) {
if (loadedImg.src.lastIndexOf(imgName) != -1) { var imgName = htmlImagesSrcArray[y].substr(htmlImagesSrcArray[y].lastIndexOf("/"));
htmlImagesArray[y] = loadedImg; if (loadedImg.src.lastIndexOf(imgName) !== -1) {
break; htmlImagesArray[y] = loadedImg;
} break;
} }
break;
} }
break;
} }
if (notloadedImgs.length === 0) { }
console.log("Images loaded"); if (notloadedImgs.length === 0) {
callback.apply(null, [htmlImagesArray]); console.log("Images loaded");
} callback.apply(null, [htmlImagesArray]);
};
for ( i = 0; i < htmlImagesSrcArray.length; i++) {
img = new Image();
htmlImagesArray.addImage(img);
addOnloadHandler(img, htmlImagesArray);
img.src = htmlImagesSrcArray[i];
} }
}; };
function addOnloadHandler(img, htmlImagesArray) { for ( i = 0; i < htmlImagesSrcArray.length; i++) {
img.onload = function() { img = new Image();
htmlImagesArray.loaded(this); htmlImagesArray.addImage(img);
}; addOnloadHandler(img, htmlImagesArray);
img.src = htmlImagesSrcArray[i];
} }
};
function addOnloadHandler(img, htmlImagesArray) {
img.onload = function() {
htmlImagesArray.loaded(this);
};
}
return (ImageLoader); export default (ImageLoader);
});

@ -1,427 +1,347 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ import SubImage from './subImage';
/* global define */ import CVUtils from './cv_utils';
import ArrayHelper from './array_helper';
define([ import {vec2} from 'gl-matrix';
"subImage",
"cv_utils", /**
"array_helper", * Represents a basic image combining the data and size.
"gl-matrix" * In addition, some methods for manipulation are contained.
], * @param size {x,y} The size of the image in pixel
function(SubImage, CVUtils, ArrayHelper, glMatrix) { * @param data {Array} If given, a flat array containing the pixel data
* @param ArrayType {Type} If given, the desired DataType of the Array (may be typed/non-typed)
'use strict'; * @param initialize {Boolean} Indicating if the array should be initialized on creation.
var vec2 = glMatrix.vec2, * @returns {ImageWrapper}
mat2 = glMatrix.mat2; */
function ImageWrapper(size, data, ArrayType, initialize) {
/** if (!data) {
* Represents a basic image combining the data and size. if (ArrayType) {
* In addition, some methods for manipulation are contained. this.data = new ArrayType(size.x * size.y);
* @param size {x,y} The size of the image in pixel if (ArrayType === Array && initialize) {
* @param data {Array} If given, a flat array containing the pixel data ArrayHelper.init(this.data, 0);
* @param ArrayType {Type} If given, the desired DataType of the Array (may be typed/non-typed)
* @param initialize {Boolean} Indicating if the array should be initialized on creation.
* @returns {ImageWrapper}
*/
function ImageWrapper(size, data, ArrayType, initialize) {
if (!data) {
if (ArrayType) {
this.data = new ArrayType(size.x * size.y);
if (ArrayType === Array && initialize) {
ArrayHelper.init(this.data, 0);
}
} else {
this.data = new Uint8Array(size.x * size.y);
if (Uint8Array === Array && initialize) {
ArrayHelper.init(this.data, 0);
}
} }
} else { } else {
this.data = data; this.data = new Uint8Array(size.x * size.y);
} if (Uint8Array === Array && initialize) {
this.size = size; ArrayHelper.init(this.data, 0);
}
/**
* tests if a position is within the image with a given offset
* @param imgRef {x, y} The location to test
* @param border Number the padding value in pixel
* @returns {Boolean} true if location inside the image's border, false otherwise
* @see cvd/image.h
*/
ImageWrapper.prototype.inImageWithBorder = function(imgRef, border) {
return (imgRef.x >= border) && (imgRef.y >= border) && (imgRef.x < (this.size.x - border)) && (imgRef.y < (this.size.y - border));
};
/**
* Transforms an image according to the given affine-transformation matrix.
* @param inImg ImageWrapper a image containing the information to be extracted.
* @param outImg ImageWrapper the image to be filled. The whole image out image is filled by the in image.
* @param M mat2 the matrix used to map point in the out matrix to those in the in matrix
* @param inOrig vec2 origin in the in image
* @param outOrig vec2 origin in the out image
* @returns Number the number of pixels not in the in image
* @see cvd/vision.h
*/
ImageWrapper.transform = function(inImg, outImg, M, inOrig, outOrig) {
var w = outImg.size.x, h = outImg.size.y, iw = inImg.size.x, ih = inImg.size.y;
var across = vec2.clone([M[0], M[2]]);
var down = vec2.clone([M[1], M[3]]);
var defaultValue = 0;
var p0 = vec2.subtract(inOrig, mat2.xVec2(M, outOrig, vec2.clone()), vec2.clone());
var min_x = p0[0], min_y = p0[1];
var max_x = min_x, max_y = min_y;
var p, i, j;
var sampleFunc = ImageWrapper.sample;
if (across[0] < 0)
min_x += w * across[0];
else
max_x += w * across[0];
if (down[0] < 0)
min_x += h * down[0];
else
max_x += h * down[0];
if (across[1] < 0)
min_y += w * across[1];
else
max_y += w * across[1];
if (down[1] < 0)
min_y += h * down[1];
else
max_y += h * down[1];
var carrigeReturn = vec2.subtract(down, vec2.scale(across, w, vec2.clone()), vec2.clone());
if (min_x >= 0 && min_y >= 0 && max_x < iw - 1 && max_y < ih - 1) {
p = p0;
for ( i = 0; i < h; ++i, vec2.add(p, carrigeReturn))
for ( j = 0; j < w; ++j, vec2.add(p, across))
outImg.set(j, i, sampleFunc(inImg, p[0], p[1]));
return 0;
} else {
var x_bound = iw - 1;
var y_bound = ih - 1;
var count = 0;
p = p0;
for ( i = 0; i < h; ++i, vec2.add(p, carrigeReturn)) {
for ( j = 0; j < w; ++j, vec2.add(p, across)) {
if (0 <= p[0] && 0 <= p[1] && p[0] < x_bound && p[1] < y_bound) {
outImg.set(j, i, sampleFunc(inImg, p[0], p[1]));
} else {
outImg.set(j, i, defaultValue); ++count;
}
}
} }
return count;
} }
}; } else {
this.data = data;
/** }
* Performs bilinear sampling this.size = size;
* @param inImg Image to extract sample from }
* @param x the x-coordinate
* @param y the y-coordinate /**
* @returns the sampled value * tests if a position is within the image with a given offset
* @see cvd/vision.h * @param imgRef {x, y} The location to test
*/ * @param border Number the padding value in pixel
ImageWrapper.sample = function(inImg, x, y) { * @returns {Boolean} true if location inside the image's border, false otherwise
var lx = Math.floor(x); * @see cvd/image.h
var ly = Math.floor(y); */
var w = inImg.size.x; ImageWrapper.prototype.inImageWithBorder = function(imgRef, border) {
var base = ly * inImg.size.x + lx; return (imgRef.x >= border)
var a = inImg.data[base + 0]; && (imgRef.y >= border)
var b = inImg.data[base + 1]; && (imgRef.x < (this.size.x - border))
var c = inImg.data[base + w]; && (imgRef.y < (this.size.y - border));
var d = inImg.data[base + w + 1]; };
var e = a - b;
x -= lx; /**
y -= ly; * Performs bilinear sampling
* @param inImg Image to extract sample from
var result = Math.floor(x * (y * (e - c + d) - e) + y * (c - a) + a); * @param x the x-coordinate
return result; * @param y the y-coordinate
}; * @returns the sampled value
* @see cvd/vision.h
/** */
* Initializes a given array. Sets each element to zero. ImageWrapper.sample = function(inImg, x, y) {
* @param array {Array} The array to initialize var lx = Math.floor(x);
*/ var ly = Math.floor(y);
ImageWrapper.clearArray = function(array) { var w = inImg.size.x;
var l = array.length; var base = ly * inImg.size.x + lx;
while (l--) { var a = inImg.data[base + 0];
array[l] = 0; var b = inImg.data[base + 1];
} var c = inImg.data[base + w];
}; var d = inImg.data[base + w + 1];
var e = a - b;
/** x -= lx;
* Creates a {SubImage} from the current image ({this}). y -= ly;
* @param from {ImageRef} The position where to start the {SubImage} from. (top-left corner)
* @param size {ImageRef} The size of the resulting image var result = Math.floor(x * (y * (e - c + d) - e) + y * (c - a) + a);
* @returns {SubImage} A shared part of the original image return result;
*/ };
ImageWrapper.prototype.subImage = function(from, size) {
return new SubImage(from, size, this); /**
}; * Initializes a given array. Sets each element to zero.
* @param array {Array} The array to initialize
/** */
* Creates an {ImageWrapper) and copies the needed underlying image-data area ImageWrapper.clearArray = function(array) {
* @param imageWrapper {ImageWrapper} The target {ImageWrapper} where the data should be copied var l = array.length;
* @param from {ImageRef} The location where to copy from (top-left location) while (l--) {
*/ array[l] = 0;
ImageWrapper.prototype.subImageAsCopy = function(imageWrapper, from) { }
var sizeY = imageWrapper.size.y, sizeX = imageWrapper.size.x; };
var x, y;
for ( x = 0; x < sizeX; x++) { /**
for ( y = 0; y < sizeY; y++) { * Creates a {SubImage} from the current image ({this}).
imageWrapper.data[y * sizeX + x] = this.data[(from.y + y) * this.size.x + from.x + x]; * @param from {ImageRef} The position where to start the {SubImage} from. (top-left corner)
} * @param size {ImageRef} The size of the resulting image
} * @returns {SubImage} A shared part of the original image
}; */
ImageWrapper.prototype.subImage = function(from, size) {
ImageWrapper.prototype.copyTo = function(imageWrapper) { return new SubImage(from, size, this);
var length = this.data.length, srcData = this.data, dstData = imageWrapper.data; };
while (length--) { /**
dstData[length] = srcData[length]; * Creates an {ImageWrapper) and copies the needed underlying image-data area
} * @param imageWrapper {ImageWrapper} The target {ImageWrapper} where the data should be copied
}; * @param from {ImageRef} The location where to copy from (top-left location)
*/
/** ImageWrapper.prototype.subImageAsCopy = function(imageWrapper, from) {
* Retrieves a given pixel position from the image var sizeY = imageWrapper.size.y, sizeX = imageWrapper.size.x;
* @param x {Number} The x-position var x, y;
* @param y {Number} The y-position for ( x = 0; x < sizeX; x++) {
* @returns {Number} The grayscale value at the pixel-position for ( y = 0; y < sizeY; y++) {
*/ imageWrapper.data[y * sizeX + x] = this.data[(from.y + y) * this.size.x + from.x + x];
ImageWrapper.prototype.get = function(x, y) {
return this.data[y * this.size.x + x];
};
/**
* Retrieves a given pixel position from the image
* @param x {Number} The x-position
* @param y {Number} The y-position
* @returns {Number} The grayscale value at the pixel-position
*/
ImageWrapper.prototype.getSafe = function(x, y) {
var i;
if (!this.indexMapping) {
this.indexMapping = {
x : [],
y : []
};
for (i = 0; i < this.size.x; i++) {
this.indexMapping.x[i] = i;
this.indexMapping.x[i + this.size.x] = i;
}
for (i = 0; i < this.size.y; i++) {
this.indexMapping.y[i] = i;
this.indexMapping.y[i + this.size.y] = i;
}
} }
return this.data[(this.indexMapping.y[y + this.size.y]) * this.size.x + this.indexMapping.x[x + this.size.x]]; }
}; };
/** ImageWrapper.prototype.copyTo = function(imageWrapper) {
* Sets a given pixel position in the image var length = this.data.length, srcData = this.data, dstData = imageWrapper.data;
* @param x {Number} The x-position
* @param y {Number} The y-position
* @param value {Number} The grayscale value to set
* @returns {ImageWrapper} The Image itself (for possible chaining)
*/
ImageWrapper.prototype.set = function(x, y, value) {
this.data[y * this.size.x + x] = value;
return this;
};
/** while (length--) {
* Sets the border of the image (1 pixel) to zero dstData[length] = srcData[length];
*/ }
ImageWrapper.prototype.zeroBorder = function() { };
var i, width = this.size.x, height = this.size.y, data = this.data;
for ( i = 0; i < width; i++) { /**
data[i] = data[(height - 1) * width + i] = 0; * Retrieves a given pixel position from the image
} * @param x {Number} The x-position
for ( i = 1; i < height - 1; i++) { * @param y {Number} The y-position
data[i * width] = data[i * width + (width - 1)] = 0; * @returns {Number} The grayscale value at the pixel-position
*/
ImageWrapper.prototype.get = function(x, y) {
return this.data[y * this.size.x + x];
};
/**
* Retrieves a given pixel position from the image
* @param x {Number} The x-position
* @param y {Number} The y-position
* @returns {Number} The grayscale value at the pixel-position
*/
ImageWrapper.prototype.getSafe = function(x, y) {
var i;
if (!this.indexMapping) {
this.indexMapping = {
x: [],
y: []
};
for (i = 0; i < this.size.x; i++) {
this.indexMapping.x[i] = i;
this.indexMapping.x[i + this.size.x] = i;
} }
}; for (i = 0; i < this.size.y; i++) {
this.indexMapping.y[i] = i;
/** this.indexMapping.y[i + this.size.y] = i;
* Inverts a binary image in place
*/
ImageWrapper.prototype.invert = function() {
var data = this.data, length = data.length;
while (length--) {
data[length] = data[length] ? 0 : 1;
} }
}
return this.data[(this.indexMapping.y[y + this.size.y]) * this.size.x + this.indexMapping.x[x + this.size.x]];
};
/**
* Sets a given pixel position in the image
* @param x {Number} The x-position
* @param y {Number} The y-position
* @param value {Number} The grayscale value to set
* @returns {ImageWrapper} The Image itself (for possible chaining)
*/
ImageWrapper.prototype.set = function(x, y, value) {
this.data[y * this.size.x + x] = value;
return this;
};
/**
* Sets the border of the image (1 pixel) to zero
*/
ImageWrapper.prototype.zeroBorder = function() {
var i, width = this.size.x, height = this.size.y, data = this.data;
for ( i = 0; i < width; i++) {
data[i] = data[(height - 1) * width + i] = 0;
}
for ( i = 1; i < height - 1; i++) {
data[i * width] = data[i * width + (width - 1)] = 0;
}
};
}; /**
* Inverts a binary image in place
*/
ImageWrapper.prototype.invert = function() {
var data = this.data, length = data.length;
ImageWrapper.prototype.convolve = function(kernel) { while (length--) {
var x, y, kx, ky, kSize = (kernel.length / 2) | 0, accu = 0; data[length] = data[length] ? 0 : 1;
for ( y = 0; y < this.size.y; y++) { }
for ( x = 0; x < this.size.x; x++) { };
accu = 0;
for ( ky = -kSize; ky <= kSize; ky++) { ImageWrapper.prototype.convolve = function(kernel) {
for ( kx = -kSize; kx <= kSize; kx++) { var x, y, kx, ky, kSize = (kernel.length / 2) | 0, accu = 0;
accu += kernel[ky+kSize][kx + kSize] * this.getSafe(x + kx, y + ky); for ( y = 0; y < this.size.y; y++) {
} for ( x = 0; x < this.size.x; x++) {
accu = 0;
for ( ky = -kSize; ky <= kSize; ky++) {
for ( kx = -kSize; kx <= kSize; kx++) {
accu += kernel[ky + kSize][kx + kSize] * this.getSafe(x + kx, y + ky);
} }
this.data[y * this.size.x + x] = accu;
} }
this.data[y * this.size.x + x] = accu;
} }
}; }
};
ImageWrapper.prototype.moments = function(labelcount) {
var data = this.data, ImageWrapper.prototype.moments = function(labelcount) {
x, var data = this.data,
y, x,
height = this.size.y, y,
width = this.size.x, height = this.size.y,
val, width = this.size.x,
ysq, val,
labelsum = [], ysq,
i, labelsum = [],
label, i,
mu11, label,
mu02, mu11,
mu20, mu02,
x_, mu20,
y_, x_,
tmp, y_,
result = [], tmp,
PI = Math.PI, result = [],
PI_4 = PI / 4; PI = Math.PI,
PI_4 = PI / 4;
if (labelcount <= 0) {
return result; if (labelcount <= 0) {
} return result;
}
for ( i = 0; i < labelcount; i++) {
labelsum[i] = {
m00 : 0,
m01 : 0,
m10 : 0,
m11 : 0,
m02 : 0,
m20 : 0,
theta : 0,
rad : 0
};
}
for ( y = 0; y < height; y++) { for ( i = 0; i < labelcount; i++) {
ysq = y * y; labelsum[i] = {
for ( x = 0; x < width; x++) { m00: 0,
val = data[y * width + x]; m01: 0,
if (val > 0) { m10: 0,
label = labelsum[val - 1]; m11: 0,
label.m00 += 1; m02: 0,
label.m01 += y; m20: 0,
label.m10 += x; theta: 0,
label.m11 += x * y; rad: 0
label.m02 += ysq; };
label.m20 += x * x; }
}
}
}
for ( i = 0; i < labelcount; i++) { for ( y = 0; y < height; y++) {
label = labelsum[i]; ysq = y * y;
if (!isNaN(label.m00) && label.m00 !== 0) { for ( x = 0; x < width; x++) {
x_ = label.m10 / label.m00; val = data[y * width + x];
y_ = label.m01 / label.m00; if (val > 0) {
mu11 = label.m11 / label.m00 - x_ * y_; label = labelsum[val - 1];
mu02 = label.m02 / label.m00 - y_ * y_; label.m00 += 1;
mu20 = label.m20 / label.m00 - x_ * x_; label.m01 += y;
tmp = (mu02 - mu20) / (2 * mu11); label.m10 += x;
tmp = 0.5 * Math.atan(tmp) + (mu11 >= 0 ? PI_4 : -PI_4 ) + PI; label.m11 += x * y;
label.theta = (tmp * 180 / PI + 90) % 180 - 90; label.m02 += ysq;
if (label.theta < 0) { label.m20 += x * x;
label.theta += 180;
}
label.rad = tmp > PI ? tmp - PI : tmp;
label.vec = vec2.clone([Math.cos(tmp), Math.sin(tmp)]);
result.push(label);
} }
} }
}
return result; for ( i = 0; i < labelcount; i++) {
}; label = labelsum[i];
if (!isNaN(label.m00) && label.m00 !== 0) {
/** x_ = label.m10 / label.m00;
* Displays the {ImageWrapper} in a given canvas y_ = label.m01 / label.m00;
* @param canvas {Canvas} The canvas element to write to mu11 = label.m11 / label.m00 - x_ * y_;
* @param scale {Number} Scale which is applied to each pixel-value mu02 = label.m02 / label.m00 - y_ * y_;
*/ mu20 = label.m20 / label.m00 - x_ * x_;
ImageWrapper.prototype.show = function(canvas, scale) { tmp = (mu02 - mu20) / (2 * mu11);
var ctx, tmp = 0.5 * Math.atan(tmp) + (mu11 >= 0 ? PI_4 : -PI_4 ) + PI;
frame, label.theta = (tmp * 180 / PI + 90) % 180 - 90;
data, if (label.theta < 0) {
current, label.theta += 180;
pixel,
x,
y;
if (!scale) {
scale = 1.0;
}
ctx = canvas.getContext('2d');
canvas.width = this.size.x;
canvas.height = this.size.y;
frame = ctx.getImageData(0, 0, canvas.width, canvas.height);
data = frame.data;
current = 0;
for (y = 0; y < this.size.y; y++) {
for (x = 0; x < this.size.x; x++) {
pixel = y * this.size.x + x;
current = this.get(x, y) * scale;
data[pixel * 4 + 0] = current;
data[pixel * 4 + 1] = current;
data[pixel * 4 + 2] = current;
data[pixel * 4 + 3] = 255;
} }
label.rad = tmp > PI ? tmp - PI : tmp;
label.vec = vec2.clone([Math.cos(tmp), Math.sin(tmp)]);
result.push(label);
} }
//frame.data = data; }
ctx.putImageData(frame, 0, 0);
};
/** return result;
* Displays the {SubImage} in a given canvas };
* @param canvas {Canvas} The canvas element to write to
* @param scale {Number} Scale which is applied to each pixel-value /**
*/ * Displays the {ImageWrapper} in a given canvas
ImageWrapper.prototype.overlay = function(canvas, scale, from) { * @param canvas {Canvas} The canvas element to write to
if (!scale || scale < 0 || scale > 360) { * @param scale {Number} Scale which is applied to each pixel-value
scale = 360; */
} ImageWrapper.prototype.show = function(canvas, scale) {
var hsv = [0, 1, 1]; var ctx,
var rgb = [0, 0, 0]; frame,
var whiteRgb = [255, 255, 255]; data,
var blackRgb = [0, 0, 0]; current,
var result = []; pixel,
var ctx = canvas.getContext('2d'); x,
var frame = ctx.getImageData(from.x, from.y, this.size.x, this.size.y); y;
var data = frame.data;
var length = this.data.length; if (!scale) {
while (length--) { scale = 1.0;
hsv[0] = this.data[length] * scale; }
result = hsv[0] <= 0 ? whiteRgb : hsv[0] >= 360 ? blackRgb : CVUtils.hsv2rgb(hsv, rgb); ctx = canvas.getContext('2d');
data[length * 4 + 0] = result[0]; canvas.width = this.size.x;
data[length * 4 + 1] = result[1]; canvas.height = this.size.y;
data[length * 4 + 2] = result[2]; frame = ctx.getImageData(0, 0, canvas.width, canvas.height);
data[length * 4 + 3] = 255; data = frame.data;
current = 0;
for (y = 0; y < this.size.y; y++) {
for (x = 0; x < this.size.x; x++) {
pixel = y * this.size.x + x;
current = this.get(x, y) * scale;
data[pixel * 4 + 0] = current;
data[pixel * 4 + 1] = current;
data[pixel * 4 + 2] = current;
data[pixel * 4 + 3] = 255;
} }
ctx.putImageData(frame, from.x, from.y); }
}; //frame.data = data;
ctx.putImageData(frame, 0, 0);
};
/**
* Displays the {SubImage} in a given canvas
* @param canvas {Canvas} The canvas element to write to
* @param scale {Number} Scale which is applied to each pixel-value
*/
ImageWrapper.prototype.overlay = function(canvas, scale, from) {
if (!scale || scale < 0 || scale > 360) {
scale = 360;
}
var hsv = [0, 1, 1];
var rgb = [0, 0, 0];
var whiteRgb = [255, 255, 255];
var blackRgb = [0, 0, 0];
var result = [];
var ctx = canvas.getContext('2d');
var frame = ctx.getImageData(from.x, from.y, this.size.x, this.size.y);
var data = frame.data;
var length = this.data.length;
while (length--) {
hsv[0] = this.data[length] * scale;
result = hsv[0] <= 0 ? whiteRgb : hsv[0] >= 360 ? blackRgb : CVUtils.hsv2rgb(hsv, rgb);
data[length * 4 + 0] = result[0];
data[length * 4 + 1] = result[1];
data[length * 4 + 2] = result[2];
data[length * 4 + 3] = 255;
}
ctx.putImageData(frame, from.x, from.y);
};
return (ImageWrapper); export default ImageWrapper;
});

@ -1,317 +1,317 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ import ImageLoader from './image_loader';
/* global define */
var InputStream = {};
define(["image_loader"], function(ImageLoader) { InputStream.createVideoStream = function(video) {
"use strict"; var that = {},
_config = null,
var InputStream = {}; _eventNames = ['canrecord', 'ended'],
InputStream.createVideoStream = function(video) { _eventHandlers = {},
var that = {}, _calculatedWidth,
_config = null, _calculatedHeight,
_eventNames = ['canrecord', 'ended'], _topRight = {x: 0, y: 0},
_eventHandlers = {}, _canvasSize = {x: 0, y: 0};
_calculatedWidth,
_calculatedHeight, function initSize() {
_topRight = {x: 0, y: 0}, var width = video.videoWidth,
_canvasSize = {x: 0, y: 0}; height = video.videoHeight;
function initSize() { _calculatedWidth =
var width = video.videoWidth, _config.size ? width / height > 1 ? _config.size : Math.floor((width / height) * _config.size) : width;
height = video.videoHeight; _calculatedHeight =
_config.size ? width / height > 1 ? Math.floor((height / width) * _config.size) : _config.size : height;
_calculatedWidth = _config.size ? width/height > 1 ? _config.size : Math.floor((width/height) * _config.size) : width;
_calculatedHeight = _config.size ? width/height > 1 ? Math.floor((height/width) * _config.size) : _config.size : height; _canvasSize.x = _calculatedWidth;
_canvasSize.y = _calculatedHeight;
_canvasSize.x = _calculatedWidth; }
_canvasSize.y = _calculatedHeight;
that.getRealWidth = function() {
return video.videoWidth;
};
that.getRealHeight = function() {
return video.videoHeight;
};
that.getWidth = function() {
return _calculatedWidth;
};
that.getHeight = function() {
return _calculatedHeight;
};
that.setWidth = function(width) {
_calculatedWidth = width;
};
that.setHeight = function(height) {
_calculatedHeight = height;
};
that.setInputStream = function(config) {
_config = config;
video.src = (typeof config.src !== 'undefined') ? config.src : '';
};
that.ended = function() {
return video.ended;
};
that.getConfig = function() {
return _config;
};
that.setAttribute = function(name, value) {
video.setAttribute(name, value);
};
that.pause = function() {
video.pause();
};
that.play = function() {
video.play();
};
that.setCurrentTime = function(time) {
if (_config.type !== "LiveStream") {
video.currentTime = time;
} }
};
that.getRealWidth = function() { that.addEventListener = function(event, f, bool) {
return video.videoWidth; if (_eventNames.indexOf(event) !== -1) {
}; if (!_eventHandlers[event]) {
_eventHandlers[event] = [];
that.getRealHeight = function() {
return video.videoHeight;
};
that.getWidth = function() {
return _calculatedWidth;
};
that.getHeight = function() {
return _calculatedHeight;
};
that.setWidth = function(width) {
_calculatedWidth = width;
};
that.setHeight = function(height) {
_calculatedHeight = height;
};
that.setInputStream = function(config) {
_config = config;
video.src = (typeof config.src !== 'undefined') ? config.src : '';
};
that.ended = function() {
return video.ended;
};
that.getConfig = function() {
return _config;
};
that.setAttribute = function(name, value) {
video.setAttribute(name, value);
};
that.pause = function() {
video.pause();
};
that.play = function() {
video.play();
};
that.setCurrentTime = function(time) {
if (_config.type !== "LiveStream")
video.currentTime = time;
};
that.addEventListener = function(event, f, bool) {
if (_eventNames.indexOf(event) !== -1) {
if (!_eventHandlers[event]) {
_eventHandlers[event] = [];
}
_eventHandlers[event].push(f);
} else {
video.addEventListener(event, f, bool);
}
};
that.clearEventHandlers = function() {
_eventNames.forEach(function(eventName) {
var handlers = _eventHandlers[eventName];
if (handlers && handlers.length > 0) {
handlers.forEach(function(handler) {
video.removeEventListener(eventName, handler);
});
}
});
};
that.trigger = function(eventName, args) {
var j,
handlers = _eventHandlers[eventName];
if (eventName === 'canrecord') {
initSize();
} }
if (handlers && handlers.length > 0) { _eventHandlers[event].push(f);
for ( j = 0; j < handlers.length; j++) { } else {
handlers[j].apply(that, args); video.addEventListener(event, f, bool);
}
}
};
that.setTopRight = function(topRight) {
_topRight.x = topRight.x;
_topRight.y = topRight.y;
};
that.getTopRight = function() {
return _topRight;
};
that.setCanvasSize = function(size) {
_canvasSize.x = size.x;
_canvasSize.y = size.y;
};
that.getCanvasSize = function() {
return _canvasSize;
};
that.getFrame = function() {
return video;
};
return that;
};
InputStream.createLiveStream = function(video) {
video.setAttribute("autoplay", true);
var that = InputStream.createVideoStream(video);
that.ended = function() {
return false;
};
return that;
};
InputStream.createImageStream = function() {
var that = {};
var _config = null;
var width = 0,
height = 0,
frameIdx = 0,
paused = true,
loaded = false,
imgArray = null,
size = 0,
offset = 1,
baseUrl = null,
ended = false,
calculatedWidth,
calculatedHeight,
_eventNames = ['canrecord', 'ended'],
_eventHandlers = {},
_topRight = {x: 0, y: 0},
_canvasSize = {x: 0, y: 0};
function loadImages() {
loaded = false;
ImageLoader.load(baseUrl, function(imgs) {
imgArray = imgs;
width = imgs[0].width;
height = imgs[0].height;
calculatedWidth = _config.size ? width/height > 1 ? _config.size : Math.floor((width/height) * _config.size) : width;
calculatedHeight = _config.size ? width/height > 1 ? Math.floor((height/width) * _config.size) : _config.size : height;
_canvasSize.x = calculatedWidth;
_canvasSize.y = calculatedHeight;
loaded = true;
frameIdx = 0;
setTimeout(function() {
publishEvent("canrecord", []);
}, 0);
}, offset, size, _config.sequence);
} }
};
function publishEvent(eventName, args) { that.clearEventHandlers = function() {
var j, _eventNames.forEach(function(eventName) {
handlers = _eventHandlers[eventName]; var handlers = _eventHandlers[eventName];
if (handlers && handlers.length > 0) { if (handlers && handlers.length > 0) {
for ( j = 0; j < handlers.length; j++) { handlers.forEach(function(handler) {
handlers[j].apply(that, args); video.removeEventListener(eventName, handler);
} });
}
});
};
that.trigger = function(eventName, args) {
var j,
handlers = _eventHandlers[eventName];
if (eventName === 'canrecord') {
initSize();
}
if (handlers && handlers.length > 0) {
for ( j = 0; j < handlers.length; j++) {
handlers[j].apply(that, args);
} }
} }
};
that.setTopRight = function(topRight) {
_topRight.x = topRight.x;
_topRight.y = topRight.y;
};
that.trigger = publishEvent; that.getTopRight = function() {
return _topRight;
};
that.getWidth = function() { that.setCanvasSize = function(size) {
return calculatedWidth; _canvasSize.x = size.x;
}; _canvasSize.y = size.y;
};
that.getHeight = function() { that.getCanvasSize = function() {
return calculatedHeight; return _canvasSize;
}; };
that.setWidth = function(width) { that.getFrame = function() {
calculatedWidth = width; return video;
}; };
that.setHeight = function(height) { return that;
calculatedHeight = height; };
};
that.getRealWidth = function() { InputStream.createLiveStream = function(video) {
return width; video.setAttribute("autoplay", true);
}; var that = InputStream.createVideoStream(video);
that.getRealHeight = function() { that.ended = function() {
return height; return false;
}; };
that.setInputStream = function(stream) { return that;
_config = stream; };
if (stream.sequence === false) {
baseUrl = stream.src; InputStream.createImageStream = function() {
size = 1; var that = {};
} else { var _config = null;
baseUrl = stream.src;
size = stream.length; var width = 0,
height = 0,
frameIdx = 0,
paused = true,
loaded = false,
imgArray = null,
size = 0,
offset = 1,
baseUrl = null,
ended = false,
calculatedWidth,
calculatedHeight,
_eventNames = ['canrecord', 'ended'],
_eventHandlers = {},
_topRight = {x: 0, y: 0},
_canvasSize = {x: 0, y: 0};
function loadImages() {
loaded = false;
ImageLoader.load(baseUrl, function(imgs) {
imgArray = imgs;
width = imgs[0].width;
height = imgs[0].height;
calculatedWidth =
_config.size ? width / height > 1 ? _config.size : Math.floor((width / height) * _config.size) : width;
calculatedHeight =
_config.size ? width / height > 1 ? Math.floor((height / width) * _config.size) : _config.size : height;
_canvasSize.x = calculatedWidth;
_canvasSize.y = calculatedHeight;
loaded = true;
frameIdx = 0;
setTimeout(function() {
publishEvent("canrecord", []);
}, 0);
}, offset, size, _config.sequence);
}
function publishEvent(eventName, args) {
var j,
handlers = _eventHandlers[eventName];
if (handlers && handlers.length > 0) {
for ( j = 0; j < handlers.length; j++) {
handlers[j].apply(that, args);
} }
loadImages(); }
}; }
that.ended = function() {
return ended;
};
that.setAttribute = function() {}; that.trigger = publishEvent;
that.getConfig = function() { that.getWidth = function() {
return _config; return calculatedWidth;
}; };
that.pause = function() { that.getHeight = function() {
paused = true; return calculatedHeight;
}; };
that.play = function() { that.setWidth = function(newWidth) {
paused = false; calculatedWidth = newWidth;
}; };
that.setCurrentTime = function(time) { that.setHeight = function(newHeight) {
frameIdx = time; calculatedHeight = newHeight;
}; };
that.addEventListener = function(event, f) { that.getRealWidth = function() {
if (_eventNames.indexOf(event) !== -1) { return width;
if (!_eventHandlers[event]) { };
_eventHandlers[event] = [];
} that.getRealHeight = function() {
_eventHandlers[event].push(f); return height;
} };
};
that.setInputStream = function(stream) {
that.setTopRight = function(topRight) { _config = stream;
_topRight.x = topRight.x; if (stream.sequence === false) {
_topRight.y = topRight.y; baseUrl = stream.src;
}; size = 1;
} else {
that.getTopRight = function() { baseUrl = stream.src;
return _topRight; size = stream.length;
}; }
loadImages();
that.setCanvasSize = function(size) { };
_canvasSize.x = size.x;
_canvasSize.y = size.y; that.ended = function() {
}; return ended;
};
that.getCanvasSize = function() {
return _canvasSize; that.setAttribute = function() {};
};
that.getConfig = function() {
that.getFrame = function() { return _config;
var frame; };
if (!loaded){ that.pause = function() {
return null; paused = true;
} };
if (!paused) {
frame = imgArray[frameIdx]; that.play = function() {
if (frameIdx < (size - 1)) { paused = false;
frameIdx++; };
} else {
setTimeout(function() { that.setCurrentTime = function(time) {
ended = true; frameIdx = time;
publishEvent("ended", []); };
}, 0);
} that.addEventListener = function(event, f) {
if (_eventNames.indexOf(event) !== -1) {
if (!_eventHandlers[event]) {
_eventHandlers[event] = [];
} }
return frame; _eventHandlers[event].push(f);
}; }
};
that.setTopRight = function(topRight) {
_topRight.x = topRight.x;
_topRight.y = topRight.y;
};
that.getTopRight = function() {
return _topRight;
};
that.setCanvasSize = function(canvasSize) {
_canvasSize.x = canvasSize.x;
_canvasSize.y = canvasSize.y;
};
return that; that.getCanvasSize = function() {
return _canvasSize;
}; };
return (InputStream); that.getFrame = function() {
}); var frame;
if (!loaded){
return null;
}
if (!paused) {
frame = imgArray[frameIdx];
if (frameIdx < (size - 1)) {
frameIdx++;
} else {
setTimeout(function() {
ended = true;
publishEvent("ended", []);
}, 0);
}
}
return frame;
};
return that;
};
export default InputStream;

@ -1,504 +1,492 @@
/* jshint undef: true, unused: true, browser:true, devel: true, evil: true */ import TypeDefs from './typedefs'; // eslint-disable-line no-unused-vars
/* global define */ import ImageWrapper from './image_wrapper';
define([ import BarcodeLocator from './barcode_locator';
"input_stream", import BarcodeDecoder from './barcode_decoder';
"image_wrapper", import Config from './config';
"barcode_locator", import Events from './events';
"barcode_decoder", import CameraAccess from './camera_access';
"frame_grabber", import ImageDebug from './image_debug';
"html_utils", import {vec2} from 'gl-matrix';
"config", import ResultCollector from './result_collector';
"events",
"camera_access", const merge = require('lodash/object/merge');
"image_debug", const InputStream = require('input_stream');
"gl-matrix", const FrameGrabber = require('frame_grabber');
"result_collector"],
function(InputStream, var _inputStream,
ImageWrapper, _framegrabber,
BarcodeLocator, _stopped,
BarcodeDecoder, _canvasContainer = {
FrameGrabber, ctx: {
HtmlUtils, image: null,
_config, overlay: null
Events,
CameraAccess,
ImageDebug,
glMatrix,
ResultCollector) {
"use strict";
var _inputStream,
_framegrabber,
_stopped,
_canvasContainer = {
ctx : {
image : null,
overlay : null
},
dom : {
image : null,
overlay : null
}
}, },
_inputImageWrapper, dom: {
_boxSize, image: null,
_decoder, overlay: null
_workerPool = [], }
_onUIThread = true, },
vec2 = glMatrix.vec2, _inputImageWrapper,
_resultCollector; _boxSize,
_decoder,
function initializeData(imageWrapper) { _workerPool = [],
initBuffers(imageWrapper); _onUIThread = true,
_decoder = BarcodeDecoder.create(_config.decoder, _inputImageWrapper); _resultCollector,
} _config = {};
function initConfig() { function initializeData(imageWrapper) {
if (typeof document !== "undefined") { initBuffers(imageWrapper);
var vis = [{ _decoder = BarcodeDecoder.create(_config.decoder, _inputImageWrapper);
node: document.querySelector("div[data-controls]"), }
prop: _config.controls
}, { function initConfig() {
node: _canvasContainer.dom.overlay, if (typeof document !== "undefined") {
prop: _config.visual.show var vis = [{
}]; node: document.querySelector("div[data-controls]"),
prop: _config.controls
for (var i = 0; i < vis.length; i++) { }, {
if (vis[i].node) { node: _canvasContainer.dom.overlay,
if (vis[i].prop === true) { prop: _config.visual.show
vis[i].node.style.display = "block"; }];
} else {
vis[i].node.style.display = "none"; for (var i = 0; i < vis.length; i++) {
} if (vis[i].node) {
if (vis[i].prop === true) {
vis[i].node.style.display = "block";
} else {
vis[i].node.style.display = "none";
} }
} }
} }
} }
}
function initInputStream(cb) {
var video; function initInputStream(cb) {
if (_config.inputStream.type == "VideoStream") { var video;
video = document.createElement("video"); if (_config.inputStream.type === "VideoStream") {
_inputStream = InputStream.createVideoStream(video); video = document.createElement("video");
} else if (_config.inputStream.type == "ImageStream") { _inputStream = InputStream.createVideoStream(video);
_inputStream = InputStream.createImageStream(); } else if (_config.inputStream.type === "ImageStream") {
} else if (_config.inputStream.type == "LiveStream") { _inputStream = InputStream.createImageStream();
var $viewport = document.querySelector("#interactive.viewport"); } else if (_config.inputStream.type === "LiveStream") {
if ($viewport) { var $viewport = document.querySelector("#interactive.viewport");
video = $viewport.querySelector("video"); if ($viewport) {
if (!video) { video = $viewport.querySelector("video");
video = document.createElement("video"); if (!video) {
$viewport.appendChild(video); video = document.createElement("video");
} $viewport.appendChild(video);
} }
_inputStream = InputStream.createLiveStream(video);
CameraAccess.request(video, _config.inputStream.constraints, function(err) {
if (!err) {
_inputStream.trigger("canrecord");
} else {
return cb(err);
}
});
} }
_inputStream = InputStream.createLiveStream(video);
_inputStream.setAttribute("preload", "auto"); CameraAccess.request(video, _config.inputStream.constraints, function(err) {
_inputStream.setAttribute("autoplay", true); if (!err) {
_inputStream.setInputStream(_config.inputStream); _inputStream.trigger("canrecord");
_inputStream.addEventListener("canrecord", canRecord.bind(undefined, cb)); } else {
return cb(err);
}
});
} }
function canRecord(cb) { _inputStream.setAttribute("preload", "auto");
BarcodeLocator.checkImageConstraints(_inputStream, _config.locator); _inputStream.setAttribute("autoplay", true);
initCanvas(); _inputStream.setInputStream(_config.inputStream);
_framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image); _inputStream.addEventListener("canrecord", canRecord.bind(undefined, cb));
initConfig(); }
if (_config.numOfWorkers > 0) { function canRecord(cb) {
initWorkers(function() { BarcodeLocator.checkImageConstraints(_inputStream, _config.locator);
console.log("Workers created"); initCanvas();
ready(cb); _framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image);
}); initConfig();
} else {
initializeData(); if (_config.numOfWorkers > 0) {
initWorkers(function() {
console.log("Workers created");
ready(cb); ready(cb);
} });
} } else {
initializeData();
function ready(cb){ ready(cb);
_inputStream.play();
cb();
} }
}
function initCanvas() {
if (typeof document !== "undefined") { function ready(cb){
var $viewport = document.querySelector("#interactive.viewport"); _inputStream.play();
_canvasContainer.dom.image = document.querySelector("canvas.imgBuffer"); cb();
if (!_canvasContainer.dom.image) { }
_canvasContainer.dom.image = document.createElement("canvas");
_canvasContainer.dom.image.className = "imgBuffer"; function initCanvas() {
if ($viewport && _config.inputStream.type == "ImageStream") { if (typeof document !== "undefined") {
$viewport.appendChild(_canvasContainer.dom.image); var $viewport = document.querySelector("#interactive.viewport");
} _canvasContainer.dom.image = document.querySelector("canvas.imgBuffer");
if (!_canvasContainer.dom.image) {
_canvasContainer.dom.image = document.createElement("canvas");
_canvasContainer.dom.image.className = "imgBuffer";
if ($viewport && _config.inputStream.type === "ImageStream") {
$viewport.appendChild(_canvasContainer.dom.image);
} }
_canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d"); }
_canvasContainer.dom.image.width = _inputStream.getCanvasSize().x; _canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d");
_canvasContainer.dom.image.height = _inputStream.getCanvasSize().y; _canvasContainer.dom.image.width = _inputStream.getCanvasSize().x;
_canvasContainer.dom.image.height = _inputStream.getCanvasSize().y;
_canvasContainer.dom.overlay = document.querySelector("canvas.drawingBuffer");
if (!_canvasContainer.dom.overlay) { _canvasContainer.dom.overlay = document.querySelector("canvas.drawingBuffer");
_canvasContainer.dom.overlay = document.createElement("canvas"); if (!_canvasContainer.dom.overlay) {
_canvasContainer.dom.overlay.className = "drawingBuffer"; _canvasContainer.dom.overlay = document.createElement("canvas");
if ($viewport) { _canvasContainer.dom.overlay.className = "drawingBuffer";
$viewport.appendChild(_canvasContainer.dom.overlay); if ($viewport) {
} $viewport.appendChild(_canvasContainer.dom.overlay);
var clearFix = document.createElement("br"); }
clearFix.setAttribute("clear", "all"); var clearFix = document.createElement("br");
if ($viewport) { clearFix.setAttribute("clear", "all");
$viewport.appendChild(clearFix); if ($viewport) {
} $viewport.appendChild(clearFix);
} }
_canvasContainer.ctx.overlay = _canvasContainer.dom.overlay.getContext("2d");
_canvasContainer.dom.overlay.width = _inputStream.getCanvasSize().x;
_canvasContainer.dom.overlay.height = _inputStream.getCanvasSize().y;
} }
_canvasContainer.ctx.overlay = _canvasContainer.dom.overlay.getContext("2d");
_canvasContainer.dom.overlay.width = _inputStream.getCanvasSize().x;
_canvasContainer.dom.overlay.height = _inputStream.getCanvasSize().y;
} }
}
function initBuffers(imageWrapper) {
if (imageWrapper) { function initBuffers(imageWrapper) {
_inputImageWrapper = imageWrapper; if (imageWrapper) {
} else { _inputImageWrapper = imageWrapper;
_inputImageWrapper = new ImageWrapper({ } else {
x : _inputStream.getWidth(), _inputImageWrapper = new ImageWrapper({
y : _inputStream.getHeight() x: _inputStream.getWidth(),
}); y: _inputStream.getHeight()
} });
console.log(_inputImageWrapper.size);
_boxSize = [
vec2.clone([0, 0]),
vec2.clone([0, _inputImageWrapper.size.y]),
vec2.clone([_inputImageWrapper.size.x, _inputImageWrapper.size.y]),
vec2.clone([_inputImageWrapper.size.x, 0])
];
BarcodeLocator.init(_inputImageWrapper, _config.locator);
} }
function getBoundingBoxes() { console.log(_inputImageWrapper.size);
if (_config.locate) { _boxSize = [
return BarcodeLocator.locate(); vec2.clone([0, 0]),
} else { vec2.clone([0, _inputImageWrapper.size.y]),
return [[ vec2.clone([_inputImageWrapper.size.x, _inputImageWrapper.size.y]),
vec2.clone(_boxSize[0]), vec2.clone([_inputImageWrapper.size.x, 0])
vec2.clone(_boxSize[1]), ];
vec2.clone(_boxSize[2]), BarcodeLocator.init(_inputImageWrapper, _config.locator);
vec2.clone(_boxSize[3])]]; }
}
function getBoundingBoxes() {
if (_config.locate) {
return BarcodeLocator.locate();
} else {
return [[
vec2.clone(_boxSize[0]),
vec2.clone(_boxSize[1]),
vec2.clone(_boxSize[2]),
vec2.clone(_boxSize[3])]];
} }
}
function transformResult(result) { function transformResult(result) {
var topRight = _inputStream.getTopRight(), var topRight = _inputStream.getTopRight(),
xOffset = topRight.x, xOffset = topRight.x,
yOffset = topRight.y, yOffset = topRight.y,
i; i;
if (!result || (xOffset === 0 && yOffset === 0)) { if (!result || (xOffset === 0 && yOffset === 0)) {
return; return;
} }
if (result.line && result.line.length === 2) { if (result.line && result.line.length === 2) {
moveLine(result.line); moveLine(result.line);
} }
if (result.boxes && result.boxes.length > 0) { if (result.boxes && result.boxes.length > 0) {
for (i = 0; i < result.boxes.length; i++) { for (i = 0; i < result.boxes.length; i++) {
moveBox(result.boxes[i]); moveBox(result.boxes[i]);
}
} }
}
function moveBox(box) { function moveBox(box) {
var corner = box.length; var corner = box.length;
while(corner--) { while (corner--) {
box[corner][0] += xOffset; box[corner][0] += xOffset;
box[corner][1] += yOffset; box[corner][1] += yOffset;
}
}
function moveLine(line) {
line[0].x += xOffset;
line[0].y += yOffset;
line[1].x += xOffset;
line[1].y += yOffset;
} }
} }
function publishResult(result, imageData) { function moveLine(line) {
if (_onUIThread) { line[0].x += xOffset;
transformResult(result); line[0].y += yOffset;
if (imageData && result && result.codeResult) { line[1].x += xOffset;
if (_resultCollector) { line[1].y += yOffset;
_resultCollector.addResult(imageData, _inputStream.getCanvasSize(), result.codeResult); }
} }
function publishResult(result, imageData) {
if (_onUIThread) {
transformResult(result);
if (imageData && result && result.codeResult) {
if (_resultCollector) {
_resultCollector.addResult(imageData, _inputStream.getCanvasSize(), result.codeResult);
} }
} }
Events.publish("processed", result);
if (result && result.codeResult) {
Events.publish("detected", result);
}
} }
function locateAndDecode() { Events.publish("processed", result);
var result, if (result && result.codeResult) {
boxes; Events.publish("detected", result);
boxes = getBoundingBoxes();
if (boxes) {
result = _decoder.decodeFromBoundingBoxes(boxes);
result = result || {};
result.boxes = boxes;
publishResult(result, _inputImageWrapper.data);
} else {
publishResult();
}
} }
}
function update() {
var availableWorker; function locateAndDecode() {
var result,
if (_onUIThread) { boxes;
if (_workerPool.length > 0) {
availableWorker = _workerPool.filter(function(workerThread) { boxes = getBoundingBoxes();
return !workerThread.busy; if (boxes) {
})[0]; result = _decoder.decodeFromBoundingBoxes(boxes);
if (availableWorker) { result = result || {};
_framegrabber.attachData(availableWorker.imageData); result.boxes = boxes;
} else { publishResult(result, _inputImageWrapper.data);
return; // all workers are busy } else {
} publishResult();
}
}
function update() {
var availableWorker;
if (_onUIThread) {
if (_workerPool.length > 0) {
availableWorker = _workerPool.filter(function(workerThread) {
return !workerThread.busy;
})[0];
if (availableWorker) {
_framegrabber.attachData(availableWorker.imageData);
} else { } else {
_framegrabber.attachData(_inputImageWrapper.data); return; // all workers are busy
}
if (_framegrabber.grab()) {
if (availableWorker) {
availableWorker.busy = true;
availableWorker.worker.postMessage({
cmd: 'process',
imageData: availableWorker.imageData
}, [availableWorker.imageData.buffer]);
} else {
locateAndDecode();
}
} }
} else { } else {
locateAndDecode(); _framegrabber.attachData(_inputImageWrapper.data);
} }
} if (_framegrabber.grab()) {
if (availableWorker) {
function start() { availableWorker.busy = true;
_stopped = false; availableWorker.worker.postMessage({
( function frame() { cmd: 'process',
if (!_stopped) { imageData: availableWorker.imageData
update(); }, [availableWorker.imageData.buffer]);
if (_onUIThread && _config.inputStream.type == "LiveStream") { } else {
window.requestAnimFrame(frame); locateAndDecode();
}
} }
}());
}
function initWorkers(cb) {
var i;
_workerPool = [];
for (i = 0; i < _config.numOfWorkers; i++) {
initWorker(workerInitialized);
} }
} else {
function workerInitialized(workerThread) { locateAndDecode();
_workerPool.push(workerThread); }
if (_workerPool.length >= _config.numOfWorkers){ }
cb();
function start() {
_stopped = false;
( function frame() {
if (!_stopped) {
update();
if (_onUIThread && _config.inputStream.type === "LiveStream") {
window.requestAnimFrame(frame);
} }
} }
} }());
}
function initWorker(cb) { function initWorkers(cb) {
var blobURL, var i;
workerThread = { _workerPool = [];
worker: undefined,
imageData: new Uint8Array(_inputStream.getWidth() * _inputStream.getHeight()),
busy: true
};
blobURL = generateWorkerBlob();
workerThread.worker = new Worker(blobURL);
workerThread.worker.onmessage = function(e) {
if (e.data.event === 'initialized') {
URL.revokeObjectURL(blobURL);
workerThread.busy = false;
workerThread.imageData = new Uint8Array(e.data.imageData);
console.log("Worker initialized");
return cb(workerThread);
} else if (e.data.event === 'processed') {
workerThread.imageData = new Uint8Array(e.data.imageData);
workerThread.busy = false;
publishResult(e.data.result, workerThread.imageData);
} else if (e.data.event === 'error') {
console.log("Worker error: " + e.data.message);
}
};
workerThread.worker.postMessage({ for (i = 0; i < _config.numOfWorkers; i++) {
cmd: 'init', initWorker(workerInitialized);
size: {x: _inputStream.getWidth(), y: _inputStream.getHeight()},
imageData: workerThread.imageData,
config: _config
}, [workerThread.imageData.buffer]);
} }
function workerInitialized(workerThread) {
function workerInterface(factory) { _workerPool.push(workerThread);
if (factory) { if (_workerPool.length >= _config.numOfWorkers){
/* jshint ignore:start */ cb();
var Quagga = factory();
if (!Quagga) {
self.postMessage({'event': 'error', message: 'Quagga could not be created'});
return;
}
/* jshint ignore:end */
} }
/* jshint ignore:start */ }
var imageWrapper; }
self.onmessage = function(e) { function initWorker(cb) {
if (e.data.cmd === 'init') { var blobURL,
var config = e.data.config; workerThread = {
config.numOfWorkers = 0; worker: undefined,
imageWrapper = new Quagga.ImageWrapper({ imageData: new Uint8Array(_inputStream.getWidth() * _inputStream.getHeight()),
x : e.data.size.x, busy: true
y : e.data.size.y
}, new Uint8Array(e.data.imageData));
Quagga.init(config, ready, imageWrapper);
Quagga.onProcessed(onProcessed);
} else if (e.data.cmd === 'process') {
imageWrapper.data = new Uint8Array(e.data.imageData);
Quagga.start();
} else if (e.data.cmd === 'setReaders') {
Quagga.setReaders(e.data.readers);
}
}; };
function onProcessed(result) { blobURL = generateWorkerBlob();
self.postMessage({'event': 'processed', imageData: imageWrapper.data, result: result}, [imageWrapper.data.buffer]); workerThread.worker = new Worker(blobURL);
workerThread.worker.onmessage = function(e) {
if (e.data.event === 'initialized') {
URL.revokeObjectURL(blobURL);
workerThread.busy = false;
workerThread.imageData = new Uint8Array(e.data.imageData);
console.log("Worker initialized");
return cb(workerThread);
} else if (e.data.event === 'processed') {
workerThread.imageData = new Uint8Array(e.data.imageData);
workerThread.busy = false;
publishResult(e.data.result, workerThread.imageData);
} else if (e.data.event === 'error') {
console.log("Worker error: " + e.data.message);
} }
};
function ready() { workerThread.worker.postMessage({
self.postMessage({'event': 'initialized', imageData: imageWrapper.data}, [imageWrapper.data.buffer]); cmd: 'init',
size: {x: _inputStream.getWidth(), y: _inputStream.getHeight()},
imageData: workerThread.imageData,
config: _config
}, [workerThread.imageData.buffer]);
}
function workerInterface(factory) {
/* eslint-disable no-undef*/
if (factory) {
var Quagga = factory();
if (!Quagga) {
self.postMessage({'event': 'error', message: 'Quagga could not be created'});
return;
} }
/* jshint ignore:end */
} }
var imageWrapper;
self.onmessage = function(e) {
if (e.data.cmd === 'init') {
var config = e.data.config;
config.numOfWorkers = 0;
imageWrapper = new Quagga.ImageWrapper({
x: e.data.size.x,
y: e.data.size.y
}, new Uint8Array(e.data.imageData));
Quagga.init(config, ready, imageWrapper);
Quagga.onProcessed(onProcessed);
} else if (e.data.cmd === 'process') {
imageWrapper.data = new Uint8Array(e.data.imageData);
Quagga.start();
} else if (e.data.cmd === 'setReaders') {
Quagga.setReaders(e.data.readers);
}
};
function generateWorkerBlob() { function onProcessed(result) {
var blob, self.postMessage({
factorySource; 'event': 'processed',
imageData: imageWrapper.data,
result: result
}, [imageWrapper.data.buffer]);
}
/* jshint ignore:start */ function ready() { // eslint-disable-line
if (typeof __factorySource__ !== 'undefined') { self.postMessage({'event': 'initialized', imageData: imageWrapper.data}, [imageWrapper.data.buffer]);
factorySource = __factorySource__; }
}
/* jshint ignore:end */
blob = new Blob(['(' + workerInterface.toString() + ')(' + factorySource + ');'], /* eslint-enable */
{type : 'text/javascript'}); }
return window.URL.createObjectURL(blob); function generateWorkerBlob() {
var blob,
factorySource;
/* jshint ignore:start */
if (typeof __factorySource__ !== 'undefined') {
factorySource = __factorySource__; // eslint-disable-line no-undef
} }
/* jshint ignore:end */
function setReaders(readers) { blob = new Blob(['(' + workerInterface.toString() + ')(' + factorySource + ');'],
if (_decoder) { {type: 'text/javascript'});
_decoder.setReaders(readers);
} else if (_onUIThread && _workerPool.length > 0) { return window.URL.createObjectURL(blob);
_workerPool.forEach(function(workerThread) { }
workerThread.worker.postMessage({cmd: 'setReaders', readers: readers});
}); function setReaders(readers) {
} if (_decoder) {
_decoder.setReaders(readers);
} else if (_onUIThread && _workerPool.length > 0) {
_workerPool.forEach(function(workerThread) {
workerThread.worker.postMessage({cmd: 'setReaders', readers: readers});
});
} }
}
return { export default {
init : function(config, cb, imageWrapper) { init: function(config, cb, imageWrapper) {
_config = HtmlUtils.mergeObjects(_config, config); _config = merge({}, Config, config);
if (imageWrapper) { if (imageWrapper) {
_onUIThread = false; _onUIThread = false;
initializeData(imageWrapper); initializeData(imageWrapper);
return cb(); return cb();
} else { } else {
initInputStream(cb); initInputStream(cb);
}
},
start: function() {
start();
},
stop: function() {
_stopped = true;
_workerPool.forEach(function(workerThread) {
workerThread.worker.terminate();
console.log("Worker terminated!");
});
_workerPool.length = 0;
if (_config.inputStream.type === "LiveStream") {
CameraAccess.release();
_inputStream.clearEventHandlers();
}
},
pause: function() {
_stopped = true;
},
onDetected: function(callback) {
Events.subscribe("detected", callback);
},
offDetected: function(callback) {
Events.unsubscribe("detected", callback);
},
onProcessed: function(callback) {
Events.subscribe("processed", callback);
},
offProcessed: function(callback) {
Events.unsubscribe("processed", callback);
},
setReaders: function(readers) {
setReaders(readers);
},
registerResultCollector: function(resultCollector) {
if (resultCollector && typeof resultCollector.addResult === 'function') {
_resultCollector = resultCollector;
}
},
canvas: _canvasContainer,
decodeSingle: function(config, resultCallback) {
config = merge({
inputStream: {
type: "ImageStream",
sequence: false,
size: 800,
src: config.src
},
numOfWorkers: 1,
locator: {
halfSample: false
} }
}, }, config);
start : function() { this.init(config, function() {
Events.once("processed", function(result) {
_stopped = true;
resultCallback.call(null, result);
}, true);
start(); start();
}, });
stop : function() { },
_stopped = true; ImageWrapper: ImageWrapper,
_workerPool.forEach(function(workerThread) { ImageDebug: ImageDebug,
workerThread.worker.terminate(); ResultCollector: ResultCollector
console.log("Worker terminated!"); };
});
_workerPool.length = 0;
if (_config.inputStream.type === "LiveStream") {
CameraAccess.release();
_inputStream.clearEventHandlers();
}
},
pause: function() {
_stopped = true;
},
onDetected : function(callback) {
Events.subscribe("detected", callback);
},
offDetected: function(callback) {
Events.unsubscribe("detected", callback);
},
onProcessed: function(callback) {
Events.subscribe("processed", callback);
},
offProcessed: function(callback) {
Events.unsubscribe("processed", callback);
},
setReaders: function(readers) {
setReaders(readers);
},
registerResultCollector: function(resultCollector) {
if (resultCollector && typeof resultCollector.addResult === 'function') {
_resultCollector = resultCollector;
}
},
canvas : _canvasContainer,
decodeSingle : function(config, resultCallback) {
config = HtmlUtils.mergeObjects({
inputStream: {
type : "ImageStream",
sequence : false,
size: 800,
src: config.src
},
numOfWorkers: 1,
locator: {
halfSample: false
}
}, config);
this.init(config, function() {
Events.once("processed", function(result) {
_stopped = true;
resultCallback.call(null, result);
}, true);
start();
});
},
ImageWrapper: ImageWrapper,
ImageDebug: ImageDebug,
ResultCollector: ResultCollector
};
});

@ -1,198 +1,195 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ import Tracer from './tracer';
/* global define */
/** /**
* http://www.codeproject.com/Tips/407172/Connected-Component-Labeling-and-Vectorization * http://www.codeproject.com/Tips/407172/Connected-Component-Labeling-and-Vectorization
*/ */
define(["tracer"], function(Tracer) { var Rasterizer = {
"use strict"; createContour2D: function() {
return {
dir: null,
index: null,
firstVertex: null,
insideContours: null,
nextpeer: null,
prevpeer: null
};
},
CONTOUR_DIR: {
CW_DIR: 0,
CCW_DIR: 1,
UNKNOWN_DIR: 2
},
DIR: {
OUTSIDE_EDGE: -32767,
INSIDE_EDGE: -32766
},
create: function(imageWrapper, labelWrapper) {
var imageData = imageWrapper.data,
labelData = labelWrapper.data,
width = imageWrapper.size.x,
height = imageWrapper.size.y,
tracer = Tracer.create(imageWrapper, labelWrapper);
var Rasterizer = { return {
createContour2D : function() { rasterize: function(depthlabel) {
return { var color,
dir : null, bc,
index : null, lc,
firstVertex : null, labelindex,
insideContours : null, cx,
nextpeer : null, cy,
prevpeer : null colorMap = [],
}; vertex,
}, p,
CONTOUR_DIR : { cc,
CW_DIR : 0, sc,
CCW_DIR : 1, pos,
UNKNOWN_DIR : 2 connectedCount = 0,
}, i;
DIR : {
OUTSIDE_EDGE : -32767,
INSIDE_EDGE : -32766
},
create : function(imageWrapper, labelWrapper) {
var imageData = imageWrapper.data,
labelData = labelWrapper.data,
width = imageWrapper.size.x,
height = imageWrapper.size.y,
tracer = Tracer.create(imageWrapper, labelWrapper);
return { for ( i = 0; i < 400; i++) {
rasterize : function(depthlabel) { colorMap[i] = 0;
var color, }
bc,
lc,
labelindex,
cx,
cy,
colorMap = [],
vertex,
p,
cc,
sc,
pos,
connectedCount = 0,
i;
for ( i = 0; i < 400; i++) {
colorMap[i] = 0;
}
colorMap[0] = imageData[0]; colorMap[0] = imageData[0];
cc = null; cc = null;
for ( cy = 1; cy < height - 1; cy++) { for ( cy = 1; cy < height - 1; cy++) {
labelindex = 0; labelindex = 0;
bc = colorMap[0]; bc = colorMap[0];
for ( cx = 1; cx < width - 1; cx++) { for ( cx = 1; cx < width - 1; cx++) {
pos = cy * width + cx; pos = cy * width + cx;
if (labelData[pos] === 0) { if (labelData[pos] === 0) {
color = imageData[pos]; color = imageData[pos];
if (color !== bc) { if (color !== bc) {
if (labelindex === 0) { if (labelindex === 0) {
lc = connectedCount + 1; lc = connectedCount + 1;
colorMap[lc] = color; colorMap[lc] = color;
bc = color; bc = color;
vertex = tracer.contourTracing(cy, cx, lc, color, Rasterizer.DIR.OUTSIDE_EDGE); vertex = tracer.contourTracing(cy, cx, lc, color, Rasterizer.DIR.OUTSIDE_EDGE);
if (vertex !== null) { if (vertex !== null) {
connectedCount++; connectedCount++;
labelindex = lc; labelindex = lc;
p = Rasterizer.createContour2D(); p = Rasterizer.createContour2D();
p.dir = Rasterizer.CONTOUR_DIR.CW_DIR;
p.index = labelindex;
p.firstVertex = vertex;
p.nextpeer = cc;
p.insideContours = null;
if (cc !== null) {
cc.prevpeer = p;
}
cc = p;
}
} else {
vertex = tracer
.contourTracing(cy, cx, Rasterizer.DIR.INSIDE_EDGE, color, labelindex);
if (vertex !== null) {
p = Rasterizer.createContour2D();
p.firstVertex = vertex;
p.insideContours = null;
if (depthlabel === 0) {
p.dir = Rasterizer.CONTOUR_DIR.CCW_DIR;
} else {
p.dir = Rasterizer.CONTOUR_DIR.CW_DIR; p.dir = Rasterizer.CONTOUR_DIR.CW_DIR;
p.index = labelindex;
p.firstVertex = vertex;
p.nextpeer = cc;
p.insideContours = null;
if (cc !== null) {
cc.prevpeer = p;
}
cc = p;
} }
} else { p.index = depthlabel;
vertex = tracer.contourTracing(cy, cx, Rasterizer.DIR.INSIDE_EDGE, color, labelindex); sc = cc;
if (vertex !== null) { while ((sc !== null) && sc.index !== labelindex) {
p = Rasterizer.createContour2D(); sc = sc.nextpeer;
p.firstVertex = vertex; }
p.insideContours = null; if (sc !== null) {
if (depthlabel === 0) { p.nextpeer = sc.insideContours;
p.dir = Rasterizer.CONTOUR_DIR.CCW_DIR; if (sc.insideContours !== null) {
} else { sc.insideContours.prevpeer = p;
p.dir = Rasterizer.CONTOUR_DIR.CW_DIR;
}
p.index = depthlabel;
sc = cc;
while ((sc !== null) && sc.index !== labelindex) {
sc = sc.nextpeer;
}
if (sc !== null) {
p.nextpeer = sc.insideContours;
if (sc.insideContours !== null) {
sc.insideContours.prevpeer = p;
}
sc.insideContours = p;
} }
sc.insideContours = p;
} }
} }
} else {
labelData[pos] = labelindex;
}
} else if (labelData[pos] === Rasterizer.DIR.OUTSIDE_EDGE || labelData[pos] === Rasterizer.DIR.INSIDE_EDGE) {
labelindex = 0;
if (labelData[pos] === Rasterizer.DIR.INSIDE_EDGE) {
bc = imageData[pos];
} else {
bc = colorMap[0];
} }
} else { } else {
labelindex = labelData[pos]; labelData[pos] = labelindex;
bc = colorMap[labelindex]; }
} else if (labelData[pos] === Rasterizer.DIR.OUTSIDE_EDGE
|| labelData[pos] === Rasterizer.DIR.INSIDE_EDGE) {
labelindex = 0;
if (labelData[pos] === Rasterizer.DIR.INSIDE_EDGE) {
bc = imageData[pos];
} else {
bc = colorMap[0];
} }
} else {
labelindex = labelData[pos];
bc = colorMap[labelindex];
} }
} }
sc = cc; }
while (sc !== null) { sc = cc;
sc.index = depthlabel; while (sc !== null) {
sc = sc.nextpeer; sc.index = depthlabel;
sc = sc.nextpeer;
}
return {
cc: cc,
count: connectedCount
};
},
debug: {
drawContour: function(canvas, firstContour) {
var ctx = canvas.getContext("2d"),
pq = firstContour,
iq,
q,
p;
ctx.strokeStyle = "red";
ctx.fillStyle = "red";
ctx.lineWidth = 1;
if (pq !== null) {
iq = pq.insideContours;
} else {
iq = null;
} }
return {
cc : cc, while (pq !== null) {
count : connectedCount if (iq !== null) {
}; q = iq;
}, iq = iq.nextpeer;
debug: {
drawContour : function(canvas, firstContour) {
var ctx = canvas.getContext("2d"),
pq = firstContour,
iq,
q,
p;
ctx.strokeStyle = "red";
ctx.fillStyle = "red";
ctx.lineWidth = 1;
if (pq !== null) {
iq = pq.insideContours;
} else { } else {
iq = null; q = pq;
} pq = pq.nextpeer;
if (pq !== null) {
while (pq !== null) { iq = pq.insideContours;
if (iq !== null) {
q = iq;
iq = iq.nextpeer;
} else { } else {
q = pq; iq = null;
pq = pq.nextpeer;
if (pq !== null) {
iq = pq.insideContours;
} else {
iq = null;
}
}
switch(q.dir) {
case Rasterizer.CONTOUR_DIR.CW_DIR:
ctx.strokeStyle = "red";
break;
case Rasterizer.CONTOUR_DIR.CCW_DIR:
ctx.strokeStyle = "blue";
break;
case Rasterizer.CONTOUR_DIR.UNKNOWN_DIR:
ctx.strokeStyle = "green";
break;
} }
p = q.firstVertex;
ctx.beginPath();
ctx.moveTo(p.x, p.y);
do {
p = p.next;
ctx.lineTo(p.x, p.y);
} while(p !== q.firstVertex);
ctx.stroke();
} }
switch (q.dir) {
case Rasterizer.CONTOUR_DIR.CW_DIR:
ctx.strokeStyle = "red";
break;
case Rasterizer.CONTOUR_DIR.CCW_DIR:
ctx.strokeStyle = "blue";
break;
case Rasterizer.CONTOUR_DIR.UNKNOWN_DIR:
ctx.strokeStyle = "green";
break;
}
p = q.firstVertex;
ctx.beginPath();
ctx.moveTo(p.x, p.y);
do {
p = p.next;
ctx.lineTo(p.x, p.y);
} while (p !== q.firstVertex);
ctx.stroke();
} }
} }
}; }
} };
}; }
};
return (Rasterizer); export default Rasterizer;
});

@ -1,59 +1,57 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ import ImageDebug from './image_debug';
/* global define */
define(["image_debug"], function(ImageDebug) { function contains(codeResult, list) {
"use strict"; if (list) {
return list.some(function (item) {
function contains(codeResult, list) { return Object.keys(item).every(function (key) {
if (list) { return item[key] === codeResult[key];
return list.some(function (item) {
return Object.keys(item).every(function (key) {
return item[key] === codeResult[key];
});
}); });
} });
return false;
} }
return false;
}
function passesFilter(codeResult, filter) { function passesFilter(codeResult, filter) {
if (typeof filter === 'function') { if (typeof filter === 'function') {
return filter(codeResult); return filter(codeResult);
}
return true;
} }
return true;
}
return { export default {
create: function(config) { create: function(config) {
var canvas = document.createElement("canvas"), var canvas = document.createElement("canvas"),
ctx = canvas.getContext("2d"), ctx = canvas.getContext("2d"),
results = [], results = [],
capacity = config.capacity || 20, capacity = config.capacity || 20,
capture = config.capture === true; capture = config.capture === true;
function matchesConstraints(codeResult) { function matchesConstraints(codeResult) {
return capacity && codeResult && !contains(codeResult, config.blacklist) && passesFilter(codeResult, config.filter); return capacity
} && codeResult
&& !contains(codeResult, config.blacklist)
&& passesFilter(codeResult, config.filter);
}
return { return {
addResult: function(data, imageSize, codeResult) { addResult: function(data, imageSize, codeResult) {
var result = {}; var result = {};
if (matchesConstraints(codeResult)) { if (matchesConstraints(codeResult)) {
capacity--; capacity--;
result.codeResult = codeResult; result.codeResult = codeResult;
if (capture) { if (capture) {
canvas.width = imageSize.x; canvas.width = imageSize.x;
canvas.height = imageSize.y; canvas.height = imageSize.y;
ImageDebug.drawImage(data, imageSize, ctx); ImageDebug.drawImage(data, imageSize, ctx);
result.frame = canvas.toDataURL(); result.frame = canvas.toDataURL();
}
results.push(result);
} }
}, results.push(result);
getResults: function() {
return results;
} }
}; },
} getResults: function() {
}; return results;
}); }
};
}
};

@ -1,204 +1,209 @@
/* jshint undef: true, unused: true, browser:true, devel: true, -W041: false */ /* @preserve ASM BEGIN */
/* global define */ /* eslint-disable eqeqeq*/
function Skeletonizer(stdlib, foreign, buffer) {
define(function() { "use asm";
"use strict";
var images = new stdlib.Uint8Array(buffer),
/* @preserve ASM BEGIN */ size = foreign.size | 0,
function Skeletonizer(stdlib, foreign, buffer) { imul = stdlib.Math.imul;
"use asm";
function erode(inImagePtr, outImagePtr) {
var images = new stdlib.Uint8Array(buffer), inImagePtr = inImagePtr | 0;
size = foreign.size | 0, outImagePtr = outImagePtr | 0;
imul = stdlib.Math.imul;
var v = 0,
function erode(inImagePtr, outImagePtr) { u = 0,
inImagePtr = inImagePtr | 0; sum = 0,
outImagePtr = outImagePtr | 0; yStart1 = 0,
yStart2 = 0,
var v = 0, xStart1 = 0,
u = 0, xStart2 = 0,
sum = 0, offset = 0;
yStart1 = 0,
yStart2 = 0, for ( v = 1; (v | 0) < ((size - 1) | 0); v = (v + 1) | 0) {
xStart1 = 0, offset = (offset + size) | 0;
xStart2 = 0, for ( u = 1; (u | 0) < ((size - 1) | 0); u = (u + 1) | 0) {
offset = 0; yStart1 = (offset - size) | 0;
yStart2 = (offset + size) | 0;
for ( v = 1; (v | 0) < ((size - 1) | 0); v = (v + 1) | 0) { xStart1 = (u - 1) | 0;
offset = (offset + size) | 0; xStart2 = (u + 1) | 0;
for ( u = 1; (u | 0) < ((size - 1) | 0); u = (u + 1) | 0) { sum = ((images[(inImagePtr + yStart1 + xStart1) | 0] | 0)
yStart1 = (offset - size) | 0; + (images[(inImagePtr + yStart1 + xStart2) | 0] | 0)
yStart2 = (offset + size) | 0; + (images[(inImagePtr + offset + u) | 0] | 0)
xStart1 = (u - 1) | 0; + (images[(inImagePtr + yStart2 + xStart1) | 0] | 0)
xStart2 = (u + 1) | 0; + (images[(inImagePtr + yStart2 + xStart2) | 0] | 0)) | 0;
sum = ((images[(inImagePtr + yStart1 + xStart1) | 0] | 0) + (images[(inImagePtr + yStart1 + xStart2) | 0] | 0) + (images[(inImagePtr + offset + u) | 0] | 0) + (images[(inImagePtr + yStart2 + xStart1) | 0] | 0) + (images[(inImagePtr + yStart2 + xStart2) | 0] | 0)) | 0; if ((sum | 0) == (5 | 0)) {
if ((sum | 0) == (5 | 0)) { images[(outImagePtr + offset + u) | 0] = 1;
images[(outImagePtr + offset + u) | 0] = 1; } else {
} else { images[(outImagePtr + offset + u) | 0] = 0;
images[(outImagePtr + offset + u) | 0] = 0;
}
} }
} }
return;
} }
return;
}
function subtract(aImagePtr, bImagePtr, outImagePtr) { function subtract(aImagePtr, bImagePtr, outImagePtr) {
aImagePtr = aImagePtr | 0; aImagePtr = aImagePtr | 0;
bImagePtr = bImagePtr | 0; bImagePtr = bImagePtr | 0;
outImagePtr = outImagePtr | 0; outImagePtr = outImagePtr | 0;
var length = 0; var length = 0;
length = imul(size, size) | 0; length = imul(size, size) | 0;
while ((length | 0) > 0) { while ((length | 0) > 0) {
length = (length - 1) | 0; length = (length - 1) | 0;
images[(outImagePtr + length) | 0] = ((images[(aImagePtr + length) | 0] | 0) - (images[(bImagePtr + length) | 0] | 0)) | 0; images[(outImagePtr + length) | 0] =
} ((images[(aImagePtr + length) | 0] | 0) - (images[(bImagePtr + length) | 0] | 0)) | 0;
} }
}
function bitwiseOr(aImagePtr, bImagePtr, outImagePtr) { function bitwiseOr(aImagePtr, bImagePtr, outImagePtr) {
aImagePtr = aImagePtr | 0; aImagePtr = aImagePtr | 0;
bImagePtr = bImagePtr | 0; bImagePtr = bImagePtr | 0;
outImagePtr = outImagePtr | 0; outImagePtr = outImagePtr | 0;
var length = 0; var length = 0;
length = imul(size, size) | 0; length = imul(size, size) | 0;
while ((length | 0) > 0) { while ((length | 0) > 0) {
length = (length - 1) | 0; length = (length - 1) | 0;
images[(outImagePtr + length) | 0] = ((images[(aImagePtr + length) | 0] | 0) | (images[(bImagePtr + length) | 0] | 0)) | 0; images[(outImagePtr + length) | 0] =
} ((images[(aImagePtr + length) | 0] | 0) | (images[(bImagePtr + length) | 0] | 0)) | 0;
} }
}
function countNonZero(imagePtr) { function countNonZero(imagePtr) {
imagePtr = imagePtr | 0; imagePtr = imagePtr | 0;
var sum = 0,
length = 0;
length = imul(size, size) | 0; var sum = 0,
length = 0;
while ((length | 0) > 0) { length = imul(size, size) | 0;
length = (length - 1) | 0;
sum = ((sum | 0) + (images[(imagePtr + length) | 0] | 0)) | 0;
}
return (sum | 0); while ((length | 0) > 0) {
length = (length - 1) | 0;
sum = ((sum | 0) + (images[(imagePtr + length) | 0] | 0)) | 0;
} }
function init(imagePtr, value) { return (sum | 0);
imagePtr = imagePtr | 0; }
value = value | 0;
var length = 0; function init(imagePtr, value) {
imagePtr = imagePtr | 0;
value = value | 0;
length = imul(size, size) | 0; var length = 0;
while ((length | 0) > 0) { length = imul(size, size) | 0;
length = (length - 1) | 0;
images[(imagePtr + length) | 0] = value; while ((length | 0) > 0) {
} length = (length - 1) | 0;
images[(imagePtr + length) | 0] = value;
} }
}
function dilate(inImagePtr, outImagePtr) { function dilate(inImagePtr, outImagePtr) {
inImagePtr = inImagePtr | 0; inImagePtr = inImagePtr | 0;
outImagePtr = outImagePtr | 0; outImagePtr = outImagePtr | 0;
var v = 0, var v = 0,
u = 0, u = 0,
sum = 0, sum = 0,
yStart1 = 0, yStart1 = 0,
yStart2 = 0, yStart2 = 0,
xStart1 = 0, xStart1 = 0,
xStart2 = 0, xStart2 = 0,
offset = 0; offset = 0;
for ( v = 1; (v | 0) < ((size - 1) | 0); v = (v + 1) | 0) { for ( v = 1; (v | 0) < ((size - 1) | 0); v = (v + 1) | 0) {
offset = (offset + size) | 0; offset = (offset + size) | 0;
for ( u = 1; (u | 0) < ((size - 1) | 0); u = (u + 1) | 0) { for ( u = 1; (u | 0) < ((size - 1) | 0); u = (u + 1) | 0) {
yStart1 = (offset - size) | 0; yStart1 = (offset - size) | 0;
yStart2 = (offset + size) | 0; yStart2 = (offset + size) | 0;
xStart1 = (u - 1) | 0; xStart1 = (u - 1) | 0;
xStart2 = (u + 1) | 0; xStart2 = (u + 1) | 0;
sum = ((images[(inImagePtr + yStart1 + xStart1) | 0] | 0) + (images[(inImagePtr + yStart1 + xStart2) | 0] | 0) + (images[(inImagePtr + offset + u) | 0] | 0) + (images[(inImagePtr + yStart2 + xStart1) | 0] | 0) + (images[(inImagePtr + yStart2 + xStart2) | 0] | 0)) | 0; sum = ((images[(inImagePtr + yStart1 + xStart1) | 0] | 0)
if ((sum | 0) > (0 | 0)) { + (images[(inImagePtr + yStart1 + xStart2) | 0] | 0)
images[(outImagePtr + offset + u) | 0] = 1; + (images[(inImagePtr + offset + u) | 0] | 0)
} else { + (images[(inImagePtr + yStart2 + xStart1) | 0] | 0)
images[(outImagePtr + offset + u) | 0] = 0; + (images[(inImagePtr + yStart2 + xStart2) | 0] | 0)) | 0;
} if ((sum | 0) > (0 | 0)) {
images[(outImagePtr + offset + u) | 0] = 1;
} else {
images[(outImagePtr + offset + u) | 0] = 0;
} }
} }
return;
} }
return;
}
function memcpy(srcImagePtr, dstImagePtr) { function memcpy(srcImagePtr, dstImagePtr) {
srcImagePtr = srcImagePtr | 0; srcImagePtr = srcImagePtr | 0;
dstImagePtr = dstImagePtr | 0; dstImagePtr = dstImagePtr | 0;
var length = 0; var length = 0;
length = imul(size, size) | 0; length = imul(size, size) | 0;
while ((length | 0) > 0) { while ((length | 0) > 0) {
length = (length - 1) | 0; length = (length - 1) | 0;
images[(dstImagePtr + length) | 0] = (images[(srcImagePtr + length) | 0] | 0); images[(dstImagePtr + length) | 0] = (images[(srcImagePtr + length) | 0] | 0);
}
} }
}
function zeroBorder(imagePtr) { function zeroBorder(imagePtr) {
imagePtr = imagePtr | 0; imagePtr = imagePtr | 0;
var x = 0, var x = 0,
y = 0; y = 0;
for ( x = 0; (x | 0) < ((size - 1) | 0); x = (x + 1) | 0) { for ( x = 0; (x | 0) < ((size - 1) | 0); x = (x + 1) | 0) {
images[(imagePtr + x) | 0] = 0; images[(imagePtr + x) | 0] = 0;
images[(imagePtr + y) | 0] = 0; images[(imagePtr + y) | 0] = 0;
y = ((y + size) - 1) | 0; y = ((y + size) - 1) | 0;
images[(imagePtr + y) | 0] = 0; images[(imagePtr + y) | 0] = 0;
y = (y + 1) | 0; y = (y + 1) | 0;
}
for ( x = 0; (x | 0) < (size | 0); x = (x + 1) | 0) {
images[(imagePtr + y) | 0] = 0;
y = (y + 1) | 0;
}
} }
for ( x = 0; (x | 0) < (size | 0); x = (x + 1) | 0) {
function skeletonize() { images[(imagePtr + y) | 0] = 0;
var subImagePtr = 0, y = (y + 1) | 0;
erodedImagePtr = 0,
tempImagePtr = 0,
skelImagePtr = 0,
sum = 0,
done = 0;
erodedImagePtr = imul(size, size) | 0;
tempImagePtr = (erodedImagePtr + erodedImagePtr) | 0;
skelImagePtr = (tempImagePtr + erodedImagePtr) | 0;
// init skel-image
init(skelImagePtr, 0);
zeroBorder(subImagePtr);
do {
erode(subImagePtr, erodedImagePtr);
dilate(erodedImagePtr, tempImagePtr);
subtract(subImagePtr, tempImagePtr, tempImagePtr);
bitwiseOr(skelImagePtr, tempImagePtr, skelImagePtr);
memcpy(erodedImagePtr, subImagePtr);
sum = countNonZero(subImagePtr) | 0;
done = ((sum | 0) == 0 | 0);
} while(!done);
} }
}
return { function skeletonize() {
skeletonize : skeletonize var subImagePtr = 0,
}; erodedImagePtr = 0,
tempImagePtr = 0,
skelImagePtr = 0,
sum = 0,
done = 0;
erodedImagePtr = imul(size, size) | 0;
tempImagePtr = (erodedImagePtr + erodedImagePtr) | 0;
skelImagePtr = (tempImagePtr + erodedImagePtr) | 0;
// init skel-image
init(skelImagePtr, 0);
zeroBorder(subImagePtr);
do {
erode(subImagePtr, erodedImagePtr);
dilate(erodedImagePtr, tempImagePtr);
subtract(subImagePtr, tempImagePtr, tempImagePtr);
bitwiseOr(skelImagePtr, tempImagePtr, skelImagePtr);
memcpy(erodedImagePtr, subImagePtr);
sum = countNonZero(subImagePtr) | 0;
done = ((sum | 0) == 0 | 0);
} while (!done);
} }
/* @preserve ASM END */
return Skeletonizer; return {
}); skeletonize: skeletonize
};
}
export default Skeletonizer;
/* eslint-enable eqeqeq*/
/* @preserve ASM END */

@ -1,97 +1,90 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ /**
/* global define */ * Construct representing a part of another {ImageWrapper}. Shares data
* between the parent and the child.
* @param from {ImageRef} The position where to start the {SubImage} from. (top-left corner)
* @param size {ImageRef} The size of the resulting image
* @param I {ImageWrapper} The {ImageWrapper} to share from
* @returns {SubImage} A shared part of the original image
*/
function SubImage(from, size, I) {
if (!I) {
I = {
data: null,
size: size
};
}
this.data = I.data;
this.originalSize = I.size;
this.I = I;
define(["typedefs"], function() { this.from = from;
"use strict"; this.size = size;
}
/** /**
* Construct representing a part of another {ImageWrapper}. Shares data * Displays the {SubImage} in a given canvas
* between the parent and the child. * @param canvas {Canvas} The canvas element to write to
* @param from {ImageRef} The position where to start the {SubImage} from. (top-left corner) * @param scale {Number} Scale which is applied to each pixel-value
* @param size {ImageRef} The size of the resulting image */
* @param I {ImageWrapper} The {ImageWrapper} to share from SubImage.prototype.show = function(canvas, scale) {
* @returns {SubImage} A shared part of the original image var ctx,
*/ frame,
function SubImage(from, size, I) { data,
if (!I) { current,
I = { y,
data : null, x,
size : size pixel;
};
}
this.data = I.data;
this.originalSize = I.size;
this.I = I;
this.from = from; if (!scale) {
this.size = size; scale = 1.0;
} }
ctx = canvas.getContext('2d');
/** canvas.width = this.size.x;
* Displays the {SubImage} in a given canvas canvas.height = this.size.y;
* @param canvas {Canvas} The canvas element to write to frame = ctx.getImageData(0, 0, canvas.width, canvas.height);
* @param scale {Number} Scale which is applied to each pixel-value data = frame.data;
*/ current = 0;
SubImage.prototype.show = function(canvas, scale) { for (y = 0; y < this.size.y; y++) {
var ctx, for (x = 0; x < this.size.x; x++) {
frame, pixel = y * this.size.x + x;
data, current = this.get(x, y) * scale;
current, data[pixel * 4 + 0] = current;
y, data[pixel * 4 + 1] = current;
x, data[pixel * 4 + 2] = current;
pixel; data[pixel * 4 + 3] = 255;
if (!scale) {
scale = 1.0;
} }
ctx = canvas.getContext('2d'); }
canvas.width = this.size.x; frame.data = data;
canvas.height = this.size.y; ctx.putImageData(frame, 0, 0);
frame = ctx.getImageData(0, 0, canvas.width, canvas.height); };
data = frame.data;
current = 0; /**
for (y = 0; y < this.size.y; y++) { * Retrieves a given pixel position from the {SubImage}
for (x = 0; x < this.size.x; x++) { * @param x {Number} The x-position
pixel = y * this.size.x + x; * @param y {Number} The y-position
current = this.get(x, y) * scale; * @returns {Number} The grayscale value at the pixel-position
data[pixel * 4 + 0] = current; */
data[pixel * 4 + 1] = current; SubImage.prototype.get = function(x, y) {
data[pixel * 4 + 2] = current; return this.data[(this.from.y + y) * this.originalSize.x + this.from.x + x];
data[pixel * 4 + 3] = 255; };
}
}
frame.data = data;
ctx.putImageData(frame, 0, 0);
};
/** /**
* Retrieves a given pixel position from the {SubImage} * Updates the underlying data from a given {ImageWrapper}
* @param x {Number} The x-position * @param image {ImageWrapper} The updated image
* @param y {Number} The y-position */
* @returns {Number} The grayscale value at the pixel-position SubImage.prototype.updateData = function(image) {
*/ this.originalSize = image.size;
SubImage.prototype.get = function(x, y) { this.data = image.data;
return this.data[(this.from.y + y) * this.originalSize.x + this.from.x + x]; };
};
/** /**
* Updates the underlying data from a given {ImageWrapper} * Updates the position of the shared area
* @param image {ImageWrapper} The updated image * @param from {x,y} The new location
*/ * @returns {SubImage} returns {this} for possible chaining
SubImage.prototype.updateData = function(image) { */
this.originalSize = image.size; SubImage.prototype.updateFrom = function(from) {
this.data = image.data; this.from = from;
}; return this;
};
/** export default (SubImage);
* Updates the position of the shared area
* @param from {x,y} The new location
* @returns {SubImage} returns {this} for possible chaining
*/
SubImage.prototype.updateFrom = function(from) {
this.from = from;
return this;
};
return (SubImage);
});

@ -1,108 +1,101 @@
/* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */
/** /**
* http://www.codeproject.com/Tips/407172/Connected-Component-Labeling-and-Vectorization * http://www.codeproject.com/Tips/407172/Connected-Component-Labeling-and-Vectorization
*/ */
define(function() { var Tracer = {
"use strict"; searchDirections: [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]],
create: function(imageWrapper, labelWrapper) {
var Tracer = { var imageData = imageWrapper.data,
searchDirections : [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]], labelData = labelWrapper.data,
create : function(imageWrapper, labelWrapper) { searchDirections = this.searchDirections,
var imageData = imageWrapper.data, width = imageWrapper.size.x,
labelData = labelWrapper.data, pos;
searchDirections = this.searchDirections,
width = imageWrapper.size.x,
pos;
function trace(current, color, label, edgelabel) { function trace(current, color, label, edgelabel) {
var i, var i,
y, y,
x; x;
for ( i = 0; i < 7; i++) { for ( i = 0; i < 7; i++) {
y = current.cy + searchDirections[current.dir][0]; y = current.cy + searchDirections[current.dir][0];
x = current.cx + searchDirections[current.dir][1]; x = current.cx + searchDirections[current.dir][1];
pos = y * width + x; pos = y * width + x;
if ((imageData[pos] === color) && ((labelData[pos] === 0) || (labelData[pos] === label))) { if ((imageData[pos] === color) && ((labelData[pos] === 0) || (labelData[pos] === label))) {
labelData[pos] = label; labelData[pos] = label;
current.cy = y; current.cy = y;
current.cx = x; current.cx = x;
return true; return true;
} else { } else {
if (labelData[pos] === 0) { if (labelData[pos] === 0) {
labelData[pos] = edgelabel; labelData[pos] = edgelabel;
}
current.dir = (current.dir + 1) % 8;
} }
current.dir = (current.dir + 1) % 8;
} }
return false;
} }
return false;
}
function vertex2D(x, y, dir) { function vertex2D(x, y, dir) {
return { return {
dir : dir, dir: dir,
x : x, x: x,
y : y, y: y,
next : null, next: null,
prev : null prev: null
}; };
} }
function contourTracing(sy, sx, label, color, edgelabel) { function contourTracing(sy, sx, label, color, edgelabel) {
var Fv = null, var Fv = null,
Cv, Cv,
P, P,
ldir, ldir,
current = { current = {
cx : sx, cx: sx,
cy : sy, cy: sy,
dir : 0 dir: 0
}; };
if (trace(current, color, label, edgelabel)) { if (trace(current, color, label, edgelabel)) {
Fv = vertex2D(sx, sy, current.dir); Fv = vertex2D(sx, sy, current.dir);
Cv = Fv; Cv = Fv;
ldir = current.dir;
P = vertex2D(current.cx, current.cy, 0);
P.prev = Cv;
Cv.next = P;
P.next = null;
Cv = P;
do {
current.dir = (current.dir + 6) % 8;
trace(current, color, label, edgelabel);
if (ldir !== current.dir) {
Cv.dir = current.dir;
P = vertex2D(current.cx, current.cy, 0);
P.prev = Cv;
Cv.next = P;
P.next = null;
Cv = P;
} else {
Cv.dir = ldir;
Cv.x = current.cx;
Cv.y = current.cy;
}
ldir = current.dir; ldir = current.dir;
P = vertex2D(current.cx, current.cy, 0); } while (current.cx !== sx || current.cy !== sy);
P.prev = Cv; Fv.prev = Cv.prev;
Cv.next = P; Cv.prev.next = Fv;
P.next = null;
Cv = P;
do {
current.dir = (current.dir + 6) % 8;
trace(current, color, label, edgelabel);
if (ldir != current.dir) {
Cv.dir = current.dir;
P = vertex2D(current.cx, current.cy, 0);
P.prev = Cv;
Cv.next = P;
P.next = null;
Cv = P;
} else {
Cv.dir = ldir;
Cv.x = current.cx;
Cv.y = current.cy;
}
ldir = current.dir;
} while(current.cx != sx || current.cy != sy);
Fv.prev = Cv.prev;
Cv.prev.next = Fv;
}
return Fv;
} }
return Fv;
return {
trace : function(current, color, label, edgelabel) {
return trace(current, color, label, edgelabel);
},
contourTracing : function(sy, sx, label, color, edgelabel) {
return contourTracing(sy, sx, label, color, edgelabel);
}
};
} }
};
return (Tracer); return {
}); trace: function(current, color, label, edgelabel) {
return trace(current, color, label, edgelabel);
},
contourTracing: function(sy, sx, label, color, edgelabel) {
return contourTracing(sy, sx, label, color, edgelabel);
}
};
}
};
export default (Tracer);

@ -3,7 +3,6 @@
* Normalizes browser-specific prefixes * Normalizes browser-specific prefixes
*/ */
glMatrixArrayType = Float32Array;
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
window.requestAnimFrame = (function () { window.requestAnimFrame = (function () {
return window.requestAnimationFrame || return window.requestAnimationFrame ||
@ -11,12 +10,13 @@ if (typeof window !== 'undefined') {
window.mozRequestAnimationFrame || window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame || window.oRequestAnimationFrame ||
window.msRequestAnimationFrame || window.msRequestAnimationFrame ||
function (/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) { function (/* function FrameRequestCallback */ callback) {
window.setTimeout(callback, 1000 / 60); window.setTimeout(callback, 1000 / 60);
}; };
})(); })();
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL; window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
} }
Math.imul = Math.imul || function(a, b) { Math.imul = Math.imul || function(a, b) {
@ -26,5 +26,5 @@ Math.imul = Math.imul || function(a, b) {
bl = b & 0xffff; bl = b & 0xffff;
// the shift by 0 fixes the sign on the high part // the shift by 0 fixes the sign on the high part
// 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);
}; };

@ -1,114 +1,103 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ import EANReader from './ean_reader';
/* global define */
function UPCEReader() {
define( EANReader.call(this);
[ }
"./ean_reader"
], var properties = {
function(EANReader) { CODE_FREQUENCY: {value: [
"use strict"; [ 56, 52, 50, 49, 44, 38, 35, 42, 41, 37 ],
[7, 11, 13, 14, 19, 25, 28, 21, 22, 26]]},
function UPCEReader() { STOP_PATTERN: { value: [1 / 6 * 7, 1 / 6 * 7, 1 / 6 * 7, 1 / 6 * 7, 1 / 6 * 7, 1 / 6 * 7]},
EANReader.call(this); FORMAT: {value: "upc_e", writeable: false}
};
UPCEReader.prototype = Object.create(EANReader.prototype, properties);
UPCEReader.prototype.constructor = UPCEReader;
UPCEReader.prototype._decodePayload = function(code, result, decodedCodes) {
var i,
self = this,
codeFrequency = 0x0;
for ( i = 0; i < 6; i++) {
code = self._decodeCode(code.end);
if (!code) {
return null;
} }
if (code.code >= self.CODE_G_START) {
code.code = code.code - self.CODE_G_START;
codeFrequency |= 1 << (5 - i);
}
result.push(code.code);
decodedCodes.push(code);
}
if (!self._determineParity(codeFrequency, result)) {
return null;
}
var properties = { return code;
CODE_FREQUENCY : {value: [ };
[ 56, 52, 50, 49, 44, 38, 35, 42, 41, 37 ],
[7, 11, 13, 14, 19, 25, 28, 21, 22, 26]]},
STOP_PATTERN: { value: [1 / 6 * 7, 1 / 6 * 7, 1 / 6 * 7, 1 / 6 * 7, 1 / 6 * 7, 1 / 6 * 7]},
FORMAT: {value: "upc_e", writeable: false}
};
UPCEReader.prototype = Object.create(EANReader.prototype, properties);
UPCEReader.prototype.constructor = UPCEReader;
UPCEReader.prototype._decodePayload = function(code, result, decodedCodes) {
var i,
self = this,
codeFrequency = 0x0;
for ( i = 0; i < 6; i++) {
code = self._decodeCode(code.end);
if (!code) {
return null;
}
if (code.code >= self.CODE_G_START) {
code.code = code.code - self.CODE_G_START;
codeFrequency |= 1 << (5 - i);
}
result.push(code.code);
decodedCodes.push(code);
}
if (!self._determineParity(codeFrequency, result)) {
return null;
}
return code; UPCEReader.prototype._determineParity = function(codeFrequency, result) {
}; var i,
nrSystem;
UPCEReader.prototype._determineParity = function(codeFrequency, result) {
var self =this,
i,
nrSystem;
for (nrSystem = 0; nrSystem < self.CODE_FREQUENCY.length; nrSystem++){
for ( i = 0; i < self.CODE_FREQUENCY[nrSystem].length; i++) {
if (codeFrequency === self.CODE_FREQUENCY[nrSystem][i]) {
result.unshift(nrSystem);
result.push(i);
return true;
}
}
}
return false;
};
UPCEReader.prototype._convertToUPCA = function(result) {
var upca = [result[0]],
lastDigit = result[result.length - 2];
if (lastDigit <= 2) {
upca = upca.concat(result.slice(1, 3))
.concat([lastDigit, 0, 0, 0, 0])
.concat(result.slice(3, 6));
} else if (lastDigit === 3) {
upca = upca.concat(result.slice(1, 4))
.concat([0 ,0, 0, 0, 0])
.concat(result.slice(4,6));
} else if (lastDigit === 4) {
upca = upca.concat(result.slice(1, 5))
.concat([0, 0, 0, 0, 0, result[5]]);
} else {
upca = upca.concat(result.slice(1, 6))
.concat([0, 0, 0, 0, lastDigit]);
}
upca.push(result[result.length - 1]); for (nrSystem = 0; nrSystem < this.CODE_FREQUENCY.length; nrSystem++){
return upca; for ( i = 0; i < this.CODE_FREQUENCY[nrSystem].length; i++) {
}; if (codeFrequency === this.CODE_FREQUENCY[nrSystem][i]) {
result.unshift(nrSystem);
result.push(i);
return true;
}
}
}
return false;
};
UPCEReader.prototype._convertToUPCA = function(result) {
var upca = [result[0]],
lastDigit = result[result.length - 2];
if (lastDigit <= 2) {
upca = upca.concat(result.slice(1, 3))
.concat([lastDigit, 0, 0, 0, 0])
.concat(result.slice(3, 6));
} else if (lastDigit === 3) {
upca = upca.concat(result.slice(1, 4))
.concat([0, 0, 0, 0, 0])
.concat(result.slice(4, 6));
} else if (lastDigit === 4) {
upca = upca.concat(result.slice(1, 5))
.concat([0, 0, 0, 0, 0, result[5]]);
} else {
upca = upca.concat(result.slice(1, 6))
.concat([0, 0, 0, 0, lastDigit]);
}
UPCEReader.prototype._checksum = function(result) { upca.push(result[result.length - 1]);
return EANReader.prototype._checksum.call(this, this._convertToUPCA(result)); return upca;
}; };
UPCEReader.prototype._findEnd = function(offset, isWhite) { UPCEReader.prototype._checksum = function(result) {
isWhite = true; return EANReader.prototype._checksum.call(this, this._convertToUPCA(result));
return EANReader.prototype._findEnd.call(this, offset, isWhite); };
};
UPCEReader.prototype._verifyTrailingWhitespace = function(endInfo) { UPCEReader.prototype._findEnd = function(offset, isWhite) {
var self = this, isWhite = true;
trailingWhitespaceEnd; return EANReader.prototype._findEnd.call(this, offset, isWhite);
};
trailingWhitespaceEnd = endInfo.end + ((endInfo.end - endInfo.start)/2); UPCEReader.prototype._verifyTrailingWhitespace = function(endInfo) {
if (trailingWhitespaceEnd < self._row.length) { var self = this,
if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) { trailingWhitespaceEnd;
return endInfo;
}
}
};
return (UPCEReader); trailingWhitespaceEnd = endInfo.end + ((endInfo.end - endInfo.start) / 2);
if (trailingWhitespaceEnd < self._row.length) {
if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) {
return endInfo;
}
} }
); };
export default UPCEReader;

@ -1,35 +1,24 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ import EANReader from './ean_reader';
/* global define */
define( function UPCReader() {
[ EANReader.call(this);
"./ean_reader" }
],
function(EANReader) {
"use strict";
function UPCReader() { var properties = {
EANReader.call(this); FORMAT: {value: "upc_a", writeable: false}
} };
var properties = { UPCReader.prototype = Object.create(EANReader.prototype, properties);
FORMAT: {value: "upc_a", writeable: false} UPCReader.prototype.constructor = UPCReader;
};
UPCReader.prototype = Object.create(EANReader.prototype, properties); UPCReader.prototype._decode = function() {
UPCReader.prototype.constructor = UPCReader; var result = EANReader.prototype._decode.call(this);
UPCReader.prototype._decode = function() { if (result && result.code && result.code.length === 13 && result.code.charAt(0) === "0") {
var result = EANReader.prototype._decode.call(this); result.code = result.code.substring(1);
return result;
if (result && result.code && result.code.length === 13 && result.code.charAt(0) === "0") {
result.code = result.code.substring(1);
return result;
}
return null;
};
return (UPCReader);
} }
); return null;
};
export default UPCReader;

@ -11,14 +11,17 @@ module.exports = function(grunt) {
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 */',
asmStartIdx = code.indexOf('/* @preserve ASM BEGIN */'), moduleFunctionRegex = /function\s*\((\w+,\s*\w+)\)\s*\{\s*\/\* \@preserve ASM BEGIN \*\//,
asmEndIdx = code.indexOf(commentEnd), commentStartIdx = code.indexOf("/* @preserve ASM BEGIN */"),
asmCode = code.substring(asmStartIdx, asmEndIdx + commentEnd.length), asmEndIdxTmp = code.indexOf(commentEnd),
asmFunctionRegex = /function (\w+)\(\w+,\s*\w+,\s*\w+\)\s*\{\s*"use asm";/, asmEndIdx = code.indexOf("}", asmEndIdxTmp),
asmCodeTmp = code.substring(commentStartIdx - Math.min(500, commentStartIdx),
asmEndIdx + 1),
asmStartIdx = asmCodeTmp.search(moduleFunctionRegex),
asmCode = asmCodeTmp.substring(asmStartIdx),
asmModule, asmModule,
asmModuleName, moduleArg1,
asmCodeMinified, asmCodeMinified;
asmMinifiedModuleName;
asmCodeMinified = asmCode asmCodeMinified = asmCode
.replace(/\s*\/\/.*/g, '') // remove single-line comments .replace(/\s*\/\/.*/g, '') // remove single-line comments
@ -29,28 +32,32 @@ module.exports = function(grunt) {
grunt.log.debug(asmCodeMinified); grunt.log.debug(asmCodeMinified);
asmModule = asmCode.match(asmFunctionRegex); asmModule = moduleFunctionRegex.exec(asmCode);
if (!asmModule) { if (!asmModule) {
grunt.log.error("No ASM module found"); grunt.log.error("No ASM module found");
return; return;
} }
asmModuleName = asmModule[1]; moduleArg1 = asmModule[1];
grunt.log.debug(asmModuleName); grunt.log.debug(moduleArg1);
asmModule = minifiedCode.match(asmFunctionRegex); var insertionPoint = minifiedCode.search(moduleFunctionRegex);
if (!asmModule) { if (insertionPoint === -1) {
grunt.log.error("No ASM module found in minified file"); grunt.log.error("No ASM module found in minified file");
return; return;
} }
grunt.log.debug(insertionPoint);
var insertionPointEnd = minifiedCode.indexOf(commentEnd, insertionPoint);
insertionPointEnd = minifiedCode.indexOf("}", insertionPointEnd) + 1;
asmMinifiedModuleName = asmModule[1]; grunt.log.debug(insertionPointEnd);
grunt.log.debug(asmMinifiedModuleName);
asmCodeMinified = asmCodeMinified.replace(asmModuleName, asmMinifiedModuleName); minifiedCode = minifiedCode.substring(0, insertionPoint)
+ asmCodeMinified
+ minifiedCode.substring(insertionPointEnd);
minifiedCode = minifiedCode.replace(/\/\* @preserve ASM BEGIN \*\/[^]*?\/\* @preserve ASM END \*\//, asmCodeMinified);
fs.writeFileSync('dist/quagga.min.js', minifiedCode); fs.writeFileSync('dist/quagga.min.js', minifiedCode);
grunt.log.ok('dist/quagga.min.js written'); grunt.log.ok('dist/quagga.min.js written');
}); });
}; };

@ -1,55 +0,0 @@
var allTestFiles = [];
var TEST_REGEXP = /(spec|test)\.js$/i;
var pathToModule = function(path) {
return path.replace(/^\/base\//, '').replace(/\.js$/, '');
};
Object.keys(window.__karma__.files).forEach(function(file) {
if (TEST_REGEXP.test(file)) {
allTestFiles.push(pathToModule(file));
}
});
require.config({
baseUrl: '/base',
paths: {
'array_helper': 'src/array_helper',
'cv_utils': 'src/cv_utils',
'typedefs': 'src/typedefs',
'cluster': 'src/cluster',
'camera_access': 'src/camera_access',
'events': 'src/events',
'html_utils': 'src/html_utils',
'quagga': 'src/quagga',
'barcode_decoder': 'src/barcode_decoder',
'barcode_locator': 'src/barcode_locator',
'barcode_reader': 'src/barcode_reader',
'bresenham': 'src/bresenham',
'codabar_reader': 'src/codabar_reader',
'code_39_reader': 'src/code_39_reader',
'code_39_vin_reader': 'src/code_39_vin_reader',
'code_128_reader': 'src/code_128_reader',
'config': 'src/config',
'ean_8_reader': 'src/ean_8_reader',
'ean_reader': 'src/ean_reader',
'frame_grabber': 'src/frame_grabber',
'image_debug': 'src/image_debug',
'image_loader': 'src/image_loader',
'image_wrapper': 'src/image_wrapper',
'input_stream': 'src/input_stream',
'rasterizer': 'src/rasterizer',
'skeletonizer': 'src/skeletonizer',
'subImage': 'src/subImage',
'tracer': 'src/tracer',
'upc_e_reader': 'src/upc_e_reader',
'upc_reader': 'src/upc_reader',
'async': 'node_modules/async/lib/async',
'gl-matrix': 'node_modules/gl-matrix/dist/gl-matrix-min',
'result_collector': 'src/result_collector',
'i2of5_reader': 'src/i2of5_reader'
},
deps: allTestFiles,
callback: window.__karma__.start
});

@ -0,0 +1,245 @@
const Quagga = require('../../src/quagga');
const async = require('async');
describe('decodeSingle', function () {
var baseFolder = "base/test/fixtures/";
function generateConfig() {
return {
inputStream: {
size: 640
},
locator: {
patchSize: "medium",
halfSample: true
},
numOfWorkers: 0,
decoder: {
readers: ["ean_reader"]
},
locate: true,
src: null
};
}
this.timeout(10000);
function _runTestSet(testSet, config) {
var readers = config.decoder.readers.slice(),
format,
folder;
if (typeof readers[0] === 'string'){
format = readers[0];
} else {
format = readers[0].format;
}
folder = baseFolder + format.split('_').slice(0, -1).join('_') + "/";
it('should decode ' + folder + " correctly", function(done) {
async.eachSeries(testSet, function (sample, callback) {
config.src = folder + sample.name;
config.readers = readers;
Quagga.decodeSingle(config, function(result) {
console.log(sample.name);
expect(result.codeResult.code).to.equal(sample.result);
expect(result.codeResult.format).to.equal(sample.format);
callback();
});
}, function() {
done();
});
});
}
describe("EAN", function() {
var config = generateConfig(),
testSet = [
{"name": "image-001.jpg", "result": "3574660239843"},
{"name": "image-002.jpg", "result": "8032754490297"},
{"name": "image-003.jpg", "result": "4006209700068"},
/* {"name": "image-004.jpg", "result": "9002233139084"}, */
/* {"name": "image-005.jpg", "result": "8004030044005"}, */
{"name": "image-006.jpg", "result": "4003626011159"},
{"name": "image-007.jpg", "result": "2111220009686"},
{"name": "image-008.jpg", "result": "9000275609022"},
{"name": "image-009.jpg", "result": "9004593978587"},
{"name": "image-010.jpg", "result": "9002244845578"}
];
testSet.forEach(function(sample) {
sample.format = "ean_13";
});
config.decoder.readers = ['ean_reader'];
_runTestSet(testSet, config);
});
describe("Code128", function() {
var config = generateConfig(),
testSet = [
{"name": "image-001.jpg", "result": "0001285112001000040801"},
// {"name": "image-002.jpg", "result": "FANAVF1461710"},
// {"name": "image-003.jpg", "result": "673023"},
// {"name": "image-004.jpg", "result": "010210150301625334"},
{"name": "image-005.jpg", "result": "419055603900009001012999"},
{"name": "image-006.jpg", "result": "419055603900009001012999"},
{"name": "image-007.jpg", "result": "T 000003552345"},
{"name": "image-008.jpg", "result": "FANAVF1461710"},
{"name": "image-009.jpg", "result": "0001285112001000040801"},
{"name": "image-010.jpg", "result": "673023"}
];
testSet.forEach(function(sample) {
sample.format = "code_128";
});
config.decoder.readers = ['code_128_reader'];
_runTestSet(testSet, config);
});
describe("Code39", function() {
var config = generateConfig(),
testSet = [
{"name": "image-001.jpg", "result": "B3% $DAD$"},
{"name": "image-003.jpg", "result": "CODE39"},
{"name": "image-004.jpg", "result": "QUAGGAJS"},
/* {"name": "image-005.jpg", "result": "CODE39"}, */
{"name": "image-006.jpg", "result": "2/4-8/16-32"},
{"name": "image-007.jpg", "result": "2/4-8/16-32"},
{"name": "image-008.jpg", "result": "CODE39"},
{"name": "image-009.jpg", "result": "2/4-8/16-32"},
{"name": "image-010.jpg", "result": "CODE39"}
];
testSet.forEach(function(sample) {
sample.format = "code_39";
});
config.decoder.readers = ['code_39_reader'];
_runTestSet(testSet, config);
});
describe("EAN-8", function() {
var config = generateConfig(),
testSet = [
{"name": "image-001.jpg", "result": "42191605"},
{"name": "image-002.jpg", "result": "42191605"},
{"name": "image-003.jpg", "result": "90311208"},
{"name": "image-004.jpg", "result": "24057257"},
{"name": "image-005.jpg", "result": "90162602"},
{"name": "image-006.jpg", "result": "24036153"},
{"name": "image-007.jpg", "result": "42176817"},
{"name": "image-008.jpg", "result": "42191605"},
{"name": "image-009.jpg", "result": "42242215"},
{"name": "image-010.jpg", "result": "42184799"}
];
testSet.forEach(function(sample) {
sample.format = "ean_8";
});
config.decoder.readers = ['ean_8_reader'];
_runTestSet(testSet, config);
});
describe("UPC", function() {
var config = generateConfig(),
testSet = [
{"name": "image-001.jpg", "result": "882428015268"},
{"name": "image-002.jpg", "result": "882428015268"},
{"name": "image-003.jpg", "result": "882428015084"},
{"name": "image-004.jpg", "result": "882428015343"},
{"name": "image-005.jpg", "result": "882428015343"},
/* {"name": "image-006.jpg", "result": "882428015046"}, */
{"name": "image-007.jpg", "result": "882428015084"},
{"name": "image-008.jpg", "result": "882428015046"},
{"name": "image-009.jpg", "result": "039047013551"},
{"name": "image-010.jpg", "result": "039047013551"}
];
testSet.forEach(function(sample) {
sample.format = "upc_a";
});
config.decoder.readers = ['upc_reader'];
_runTestSet(testSet, config);
});
describe("UPC-E", function() {
var config = generateConfig(),
testSet = [
{"name": "image-001.jpg", "result": "04965802"},
{"name": "image-002.jpg", "result": "04965802"},
{"name": "image-003.jpg", "result": "03897425"},
{"name": "image-004.jpg", "result": "05096893"},
{"name": "image-005.jpg", "result": "05096893"},
{"name": "image-006.jpg", "result": "05096893"},
{"name": "image-007.jpg", "result": "03897425"},
{"name": "image-008.jpg", "result": "01264904"},
/*{"name": "image-009.jpg", "result": "01264904"},*/
{"name": "image-010.jpg", "result": "01264904"}
];
testSet.forEach(function(sample) {
sample.format = "upc_e";
});
config.decoder.readers = ['upc_e_reader'];
_runTestSet(testSet, config);
});
describe("Codabar", function() {
var config = generateConfig(),
testSet = [
{"name": "image-001.jpg", "result": "A10/53+17-70D"},
{"name": "image-002.jpg", "result": "B546745735B"},
{"name": "image-003.jpg", "result": "C$399.95A"},
{"name": "image-004.jpg", "result": "B546745735B"},
{"name": "image-005.jpg", "result": "C$399.95A"},
{"name": "image-006.jpg", "result": "B546745735B"},
{"name": "image-007.jpg", "result": "C$399.95A"},
{"name": "image-008.jpg", "result": "A16:9/4:3/3:2D"},
{"name": "image-009.jpg", "result": "C$399.95A"},
{"name": "image-010.jpg", "result": "C$399.95A"}
];
testSet.forEach(function(sample) {
sample.format = "codabar";
});
config.decoder.readers = ['codabar_reader'];
_runTestSet(testSet, config);
});
describe("I2of5 with localization", function() {
var config = {
inputStream: {
size: 800,
singleChannel: false
},
locator: {
patchSize: "small",
halfSample: false
},
numOfWorkers: 0,
decoder: {
readers: ["i2of5_reader"]
},
locate: true,
src: null
},
testSet = [
{"name": "image-001.jpg", "result": "2167361334"},
{"name": "image-002.jpg", "result": "2167361334"},
{"name": "image-003.jpg", "result": "2167361334"},
{"name": "image-004.jpg", "result": "2167361334"},
{"name": "image-005.jpg", "result": "2167361334"}
];
testSet.forEach(function(sample) {
sample.format = "i2of5";
});
_runTestSet(testSet, config);
});
});

@ -0,0 +1,53 @@
const ArrayHelper = require('../../src/array_helper');
describe('init', function() {
it('initializes an array with the given value', function() {
var input = [0, 0, 0];
ArrayHelper.init(input, 5);
expect(input).to.deep.equal([5, 5, 5]);
});
});
describe('shuffle', function() {
before(function() {
sinon.stub(Math, 'random').returns(0.5);
});
after(function() {
sinon.restore(Math);
});
it('shuffles the content of an array', function() {
var input = [1, 2, 3];
expect(ArrayHelper.shuffle(input)).to.deep.equal([3, 1, 2]);
});
});
describe('toPointList', function() {
it('converts an Array to a List of poitns', function() {
var input = [[1, 2], [2, 2], [3, 2]];
expect(ArrayHelper.toPointList(input)).to.equal("[[1,2],\r\n[2,2],\r\n[3,2]]");
});
});
describe('threshold', function() {
it('returns all elements above the given threshold', function() {
var input = [1, 2, 3];
expect(ArrayHelper.threshold(input, 2, function(score) {
return score * 1.5;
})).to.deep.equal([2, 3]);
});
});
describe('maxIndex', function() {
it('gets the index of the biggest element in the array', function() {
var input = [1, 2, 3];
expect(ArrayHelper.maxIndex(input)).to.equal(2);
});
});
describe('max', function() {
it('gets the biggest element in the array', function() {
var input = [1, 3, 2];
expect(ArrayHelper.max(input)).to.equal(3);
});
});

@ -0,0 +1,130 @@
const BarcodeLocator = require('../../src/barcode_locator');
const Config = require('../../src/config');
const merge = require('lodash/object/merge');
describe('checkImageConstraints', function() {
var config,
inputStream,
imageSize,
streamConfig = {};
beforeEach(function() {
imageSize = {
x: 640, y: 480
};
config = merge({}, Config);
inputStream = {
getWidth: function() {
return imageSize.x;
},
getHeight: function() {
return imageSize.y;
},
setWidth: function() {},
setHeight: function() {},
setTopRight: function() {},
setCanvasSize: function() {},
getConfig: function() {
return streamConfig;
}
};
sinon.stub(inputStream, "setWidth", function(width) {
imageSize.x = width;
});
sinon.stub(inputStream, "setHeight", function(height) {
imageSize.y = height;
});
sinon.stub(inputStream, "setTopRight");
sinon.stub(inputStream, "setCanvasSize");
});
afterEach(function() {
inputStream.setWidth.restore();
inputStream.setHeight.restore();
});
it('should not adjust the image-size if not needed', function() {
var expected = {x: imageSize.x, y: imageSize.y};
BarcodeLocator.checkImageConstraints(inputStream, config.locator);
expect(inputStream.getWidth()).to.be.equal(expected.x);
expect(inputStream.getHeight()).to.be.equal(expected.y);
});
it('should adjust the image-size', function() {
var expected = {x: imageSize.x, y: imageSize.y};
config.locator.halfSample = true;
imageSize.y += 1;
BarcodeLocator.checkImageConstraints(inputStream, config.locator);
expect(inputStream.getWidth()).to.be.equal(expected.x);
expect(inputStream.getHeight()).to.be.equal(expected.y);
});
it('should adjust the image-size', function() {
var expected = {x: imageSize.x, y: imageSize.y};
imageSize.y += 1;
config.locator.halfSample = false;
BarcodeLocator.checkImageConstraints(inputStream, config.locator);
expect(inputStream.getHeight()).to.be.equal(expected.y);
expect(inputStream.getWidth()).to.be.equal(expected.x);
});
it("should take the defined area into account", function() {
var expectedSize = {
x: 420,
y: 315
},
expectedTopRight = {
x: 115,
y: 52
},
expectedCanvasSize = {
x: 640,
y: 480
};
streamConfig.area = {
top: "11%",
right: "15%",
bottom: "20%",
left: "18%"
};
config.locator.halfSample = false;
BarcodeLocator.checkImageConstraints(inputStream, config.locator);
expect(inputStream.getHeight()).to.be.equal(expectedSize.y);
expect(inputStream.getWidth()).to.be.equal(expectedSize.x);
expect(inputStream.setTopRight.getCall(0).args[0]).to.deep.equal(expectedTopRight);
expect(inputStream.setCanvasSize.getCall(0).args[0]).to.deep.equal(expectedCanvasSize);
});
it("should return the original size if set to full image", function() {
var expectedSize = {
x: 640,
y: 480
},
expectedTopRight = {
x: 0,
y: 0
},
expectedCanvasSize = {
x: 640,
y: 480
};
streamConfig.area = {
top: "0%",
right: "0%",
bottom: "0%",
left: "0%"
};
config.locator.halfSample = false;
BarcodeLocator.checkImageConstraints(inputStream, config.locator);
expect(inputStream.getHeight()).to.be.equal(expectedSize.y);
expect(inputStream.getWidth()).to.be.equal(expectedSize.x);
expect(inputStream.setTopRight.getCall(0).args[0]).to.deep.equal(expectedTopRight);
expect(inputStream.setCanvasSize.getCall(0).args[0]).to.deep.equal(expectedCanvasSize);
});
});

@ -0,0 +1,116 @@
const CameraAccess = require('../../src/camera_access');
var originalURL,
originalMediaStreamTrack,
video,
stream;
beforeEach(function() {
var tracks = [{
stop: function() {}
}];
originalURL = window.URL;
originalMediaStreamTrack = window.MediaStreamTrack;
window.MediaStreamTrack = {};
window.URL = null;
stream = {
getVideoTracks: function() {
return tracks;
}
};
sinon.spy(tracks[0], "stop");
video = {
src: null,
addEventListener: function() {},
removeEventListener: function() {},
play: function() {},
videoWidth: 320,
videoHeight: 480
};
sinon.stub(video, "addEventListener", function(event, cb) {
cb();
});
sinon.stub(video, "play");
});
afterEach(function() {
window.URL = originalURL;
window.MediaStreamTrack = originalMediaStreamTrack;
});
describe('success', function() {
beforeEach(function() {
sinon.stub(navigator, "getUserMedia", function(constraints, success) {
success(stream);
});
});
afterEach(function() {
navigator.getUserMedia.restore();
});
describe('request', function () {
it('should request the camera', function (done) {
CameraAccess.request(video, {}, function () {
expect(navigator.getUserMedia.calledOnce).to.equal(true);
expect(video.src).to.deep.equal(stream);
done();
});
});
});
describe('release', function () {
it('should release the camera', function (done) {
CameraAccess.request(video, {}, function () {
expect(video.src).to.deep.equal(stream);
CameraAccess.release();
expect(video.src.getVideoTracks()).to.have.length(1);
expect(video.src.getVideoTracks()[0].stop.calledOnce).to.equal(true);
done();
});
});
});
});
describe('failure', function() {
describe("permission denied", function(){
before(function() {
sinon.stub(navigator, "getUserMedia", function(constraints, success, failure) {
failure(new Error());
});
});
after(function() {
navigator.getUserMedia.restore();
});
it('should throw if getUserMedia not available', function(done) {
CameraAccess.request(video, {}, function(err) {
expect(err).to.be.defined;
done();
});
});
});
describe("not available", function(){
var originalGetUserMedia;
before(function() {
originalGetUserMedia = navigator.getUserMedia;
navigator.getUserMedia = undefined;
});
after(function() {
navigator.getUserMedia = originalGetUserMedia;
});
it('should throw if getUserMedia not available', function(done) {
CameraAccess.request(video, {}, function(err) {
expect(err).to.be.defined;
done();
});
});
});
});

@ -0,0 +1,144 @@
const CVUtils = require('../../src/cv_utils');
describe('imageRef', function() {
it('gets the image Reference for a coordinate', function() {
var res = CVUtils.imageRef(1, 2);
expect(res.x).to.equal(1);
expect(res.y).to.equal(2);
expect(res.toVec2()[0]).to.equal(1);
});
});
describe('calculatePatchSize', function() {
it('should not throw an error in case of valid image size', function() {
var expected = {x: 32, y: 32},
patchSize = CVUtils.calculatePatchSize("medium", {x: 640, y: 480});
expect(patchSize).to.be.deep.equal(expected);
});
it('should thow an error if image size it not valid', function() {
var expected = {x: 32, y: 32},
patchSize = CVUtils.calculatePatchSize("medium", {x: 640, y: 480});
expect(patchSize).to.be.deep.equal(expected);
});
});
describe('_parseCSSDimensionValues', function() {
it("should convert a percentual value correctly", function() {
var expected = {
value: 10,
unit: "%"
},
result = CVUtils._parseCSSDimensionValues("10%");
expect(result).to.be.deep.equal(expected);
});
it("should convert a 0% value correctly", function() {
var expected = {
value: 100,
unit: "%"
},
result = CVUtils._parseCSSDimensionValues("100%");
expect(result).to.be.deep.equal(expected);
});
it("should convert a 100% value correctly", function() {
var expected = {
value: 0,
unit: "%"
},
result = CVUtils._parseCSSDimensionValues("0%");
expect(result).to.be.deep.equal(expected);
});
it("should convert a pixel value to percentage", function() {
var expected = {
value: 26.3,
unit: "%"
},
result = CVUtils._parseCSSDimensionValues("26.3px");
console.log(result);
expect(result).to.be.deep.equal(expected);
});
});
describe("_dimensionsConverters", function(){
var context;
beforeEach(function() {
context = {
width: 640,
height: 480
};
});
it("should convert a top-value correclty", function() {
var expected = 48,
result = CVUtils._dimensionsConverters.top({value: 10, unit: "%"}, context);
expect(result).to.be.equal(expected);
});
it("should convert a right-value correclty", function() {
var expected = 640 - 128,
result = CVUtils._dimensionsConverters.right({value: 20, unit: "%"}, context);
expect(result).to.be.equal(expected);
});
it("should convert a bottom-value correclty", function() {
var expected = 480 - 77,
result = CVUtils._dimensionsConverters.bottom({value: 16, unit: "%"}, context);
expect(result).to.be.equal(expected);
});
it("should convert a left-value correclty", function() {
var expected = 57,
result = CVUtils._dimensionsConverters.left({value: 9, unit: "%"}, context);
expect(result).to.be.equal(expected);
});
});
describe("computeImageArea", function() {
it("should calculate an image-area", function() {
var expected = {
sx: 115,
sy: 48,
sw: 429,
sh: 336
},
result = CVUtils.computeImageArea(640, 480, {
top: "10%",
right: "15%",
bottom: "20%",
left: "18%"
});
expect(result).to.be.deep.equal(expected);
});
it("should calculate full image-area", function() {
var expected = {
sx: 0,
sy: 0,
sw: 640,
sh: 480
},
result = CVUtils.computeImageArea(640, 480, {
top: "0%",
right: "0%",
bottom: "0%",
left: "0%"
});
expect(result).to.be.deep.equal(expected);
});
});

@ -0,0 +1,114 @@
const Events = require('../../src/events');
beforeEach(function() {
Events.unsubscribe();
});
describe("subscribe", function() {
it("should call one callback for a single event", function() {
var callbackA = sinon.stub(),
callbackB = sinon.stub();
Events.subscribe("test", callbackA);
Events.subscribe("test", callbackB);
Events.publish("test");
expect(callbackA.calledOnce).to.be.equal(true);
expect(callbackB.calledOnce).to.be.equal(true);
});
it("should call one callback twice if published twice", function() {
var callback = sinon.stub();
Events.subscribe("test", callback);
Events.publish("test");
Events.publish("test");
expect(callback.calledTwice).to.be.equal(true);
});
it("should call the callback asynchronuously", function(done) {
var test = {
callback: function() {
}
};
sinon.stub(test, "callback", function() {
expect(test.callback.calledOnce).to.be.true;
done();
});
Events.subscribe("test", test.callback, true);
Events.publish("test");
expect(test.callback.called).to.be.false;
});
});
describe("once", function() {
it("should call the callback once, even when published twice", function() {
var callbackA = sinon.stub(),
callbackB = sinon.stub();
Events.once("test", callbackA);
Events.subscribe("test", callbackB);
Events.publish("test");
Events.publish("test");
expect(callbackA.calledOnce).to.be.equal(true);
expect(callbackB.calledTwice).to.be.equal(true);
});
});
describe("unsubscribe", function() {
it("should unsubscribe all callbacks from a single event", function() {
var callbackA = sinon.stub(),
callbackB = sinon.stub(),
callbackC = sinon.stub();
Events.subscribe("test", callbackA);
Events.subscribe("test", callbackB);
Events.subscribe("testC", callbackC);
Events.publish("test");
expect(callbackC.called).to.be.equal(false);
expect(callbackA.calledOnce).to.be.equal(true);
expect(callbackB.calledOnce).to.be.equal(true);
Events.publish("testC");
expect(callbackC.calledOnce).to.be.equal(true);
expect(callbackA.calledOnce).to.be.equal(true);
expect(callbackB.calledOnce).to.be.equal(true);
Events.unsubscribe("test");
Events.publish("test");
expect(callbackC.calledOnce).to.be.equal(true);
expect(callbackA.calledOnce).to.be.equal(true);
expect(callbackB.calledOnce).to.be.equal(true);
});
it("should unsubscribe a single callback from a single event", function() {
var callbackA = sinon.stub(),
callbackB = sinon.stub();
Events.subscribe("test", callbackA);
Events.subscribe("test", callbackB);
Events.publish("test");
expect(callbackA.calledOnce).to.be.equal(true);
expect(callbackB.calledOnce).to.be.equal(true);
Events.unsubscribe("test", callbackB);
Events.publish("test");
expect(callbackA.calledTwice).to.be.equal(true);
expect(callbackB.calledOnce).to.be.equal(true);
});
});

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

@ -0,0 +1,8 @@
require('events').EventEmitter.prototype._maxListeners = 0;
require('core-js/es5');
const testsContext = require.context("./integration", true, /.*js$/);
testsContext.keys().forEach(testsContext);
const componentsContext = require.context('../src/', true, /\.*js$/);
componentsContext.keys().forEach(componentsContext);

@ -0,0 +1,8 @@
require('events').EventEmitter.prototype._maxListeners = 0;
require('core-js/es5');
const testsContext = require.context("./spec", true, /.*js$/);
testsContext.keys().forEach(testsContext);
const componentsContext = require.context('../src/', true, /\.*js$/);
componentsContext.keys().forEach(componentsContext);

@ -0,0 +1,39 @@
var webpack = require('webpack'),
MyUmdPlugin = require('./plugins/umd'),
path = require('path');
module.exports = {
entry: [
'./src/quagga.js'
],
devtool: 'source-map',
module: {
loaders: [{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel'
}]
},
resolve: {
extensions: ['', '.js', '.jsx'],
alias: {
'input_stream$': path.resolve(__dirname, 'src/input_stream'),
'frame_grabber$': path.resolve(__dirname, 'src/frame_grabber')
}
},
output: {
path: __dirname + '/dist',
publicPath: '/',
filename: 'quagga.js',
sourceMapFilename: 'quagga.map'
},
devServer: {
contentBase: './',
hot: true
},
plugins: [
new MyUmdPlugin({
library: 'Quagga'
})
]
};

@ -0,0 +1,11 @@
var webpack = require('webpack');
module.exports = require('./webpack.config.js');
module.exports.plugins.unshift(
new webpack.optimize.UglifyJsPlugin()
);
module.exports.output.filename = 'quagga.min.js';
module.exports.output.sourceMapFilename = null;
module.exports.devtool = null;

@ -0,0 +1,24 @@
var webpack = require('webpack'),
path = require('path');
module.exports = require('./webpack.config.js');
module.exports.resolve = {
extensions: ['', '.js', '.jsx'],
alias: {
'input_stream$': path.resolve(__dirname, 'lib/input_stream'),
'frame_grabber$': path.resolve(__dirname, 'lib/frame_grabber')
}
};
module.exports.externals = [
"get-pixels",
"gl-matrix",
"lodash",
"ndarray",
"ndarray-linear-interpolate"
];
module.exports.output.libraryTarget = "commonjs2";
module.exports.plugins = [];
module.exports.output.path = __dirname + '/lib';
module.exports.output.filename = 'quagga.js';
Loading…
Cancel
Save