Merged master

pull/65/head
Christoph Oberhofer 10 years ago
commit f98e078707

@ -34,6 +34,7 @@ module.exports = function(grunt) {
}, },
"baseUrl" : "src", "baseUrl" : "src",
"name" : "quagga", "name" : "quagga",
"useStrict": true,
"out" : "dist/quagga.js", "out" : "dist/quagga.js",
"include" : ['quagga'], "include" : ['quagga'],
"optimize" : "none", "optimize" : "none",

@ -1,7 +1,7 @@
quaggaJS quaggaJS
======== ========
- [Changelog](#changelog) (2015-06-13) - [Changelog](#changelog) (2015-07-08)
## What is QuaggaJS? ## What is QuaggaJS?
@ -80,12 +80,16 @@ version `quagga.min.js` and places both files in the `dist` folder.
You can check out the [examples][github_examples] to get an idea of how to You can check out the [examples][github_examples] to get an idea of how to
use QuaggaJS. Basically the library exposes the following API: use QuaggaJS. Basically the library exposes the following API:
### Quagga.init(config, callback) ### <a name="quaggainit">Quagga.init(config, callback)</a>
This method initializes the library for a given configuration `config` (see This method initializes the library for a given configuration `config` (see
below) and invokes the `callback` when Quagga is ready to start. The below) and invokes the `callback(err)` when Quagga has finished its
initialization process also requests for camera access if real-time detection is bootstrapping phase. The initialization process also requests for camera
configured. access if real-time detection is configured. In case of an error, the `err`
parameter is set and contains information about the cause. A potential cause
may be the `inputStream.type` is set to `LiveStream`, but the browser does
not support this API, or simply if the user denies the permission to use the
camera.
```javascript ```javascript
Quagga.init({ Quagga.init({
@ -96,7 +100,11 @@ Quagga.init({
decoder : { decoder : {
readers : ["code_128_reader"] readers : ["code_128_reader"]
} }
}, function() { }, function(err) {
if (err) {
console.log(err);
return
}
console.log("Initialization finished. Ready to start"); console.log("Initialization finished. Ready to start");
Quagga.start(); Quagga.start();
}); });
@ -143,7 +151,8 @@ empty.
```javascript ```javascript
{ {
"codeResult": { "codeResult": {
"code": "FANAVF1461710", "code": "FANAVF1461710", // the decoded code as a string
"format": "code_128", // or code_39, codabar, ean_13, ean_8, upc_a, upc_e
"start": 355, "start": 355,
"end": 26, "end": 26,
"codeset": 100, "codeset": 100,
@ -223,7 +232,8 @@ The default `config` object is set as followed:
right: "0%", // right offset right: "0%", // right offset
left: "0%", // left offset left: "0%", // left offset
bottom: "0%" // bottom offset bottom: "0%" // bottom offset
} },
singleChannel: false // true: only the red color-channel is read
}, },
tracking: false, tracking: false,
debug: false, debug: false,
@ -269,7 +279,9 @@ locating-mechanism for more robust results.
```javascript ```javascript
Quagga.decodeSingle({ Quagga.decodeSingle({
readers: ['code_128_reader'], decoder: {
readers: ["code_128_reader"] // List of active readers
},
locate: true, // try to locate the barcode in the image locate: true, // try to locate the barcode in the image
src: '/test/fixtures/code_128/image-001.jpg' // or 'data:image/jpg;base64,' + data src: '/test/fixtures/code_128/image-001.jpg' // or 'data:image/jpg;base64,' + data
}, function(result){ }, function(result){
@ -297,8 +309,87 @@ web-workers, and their restriction not to have access to the DOM, the
configuration must be explicitly set to `config.numOfWorkers = 0` in order to configuration must be explicitly set to `config.numOfWorkers = 0` in order to
work. work.
## <a name="resultcollector">ResultCollector</a>
Quagga is not perfect by any means and may produce false positives from time
to time. In order to find out which images produced those false positives,
the built-in ``ResultCollector`` will support you and me helping squashing
bugs in the implementation.
### Creating a ``ResultCollector``
You can easily create a new ``ResultCollector`` by calling its ``create``
method with a configuration.
```javascript
var resultCollector = Quagga.ResultCollector.create({
capture: true, // keep track of the image producing this result
capacity: 20, // maximum number of results to store
blacklist: [ // list containing codes which should not be recorded
{code: "3574660239843", format: "ean_13"}],
filter: function(codeResult) {
// only store results which match this constraint
// returns true/false
// e.g.: return codeResult.format === "ean_13";
return true;
}
});
```
### Using a ``ResultCollector``
After creating a ``ResultCollector`` you have to attach it to Quagga by
calling ``Quagga.registerResultCollector(resultCollector)``.
### Reading results
After a test/recording session, you can now print the collected results which
do not fit into a certain schema. Calling ``getResults`` on the
``resultCollector`` returns an ``Array`` containing objects with:
```javascript
{
codeResult: {}, // same as in onDetected event
frame: "data:image/png;base64,iVBOR..." // dataURL of the gray-scaled image
}
```
The ``frame`` property is an internal representation of the image and
therefore only available in gray-scale. The dataURL representation allows
easy saving/rendering of the image.
### Comparing results
Now, having the frames available on disk, you can load each single image by
calling ``decodeSingle`` with the same configuration as used during recording
. In order to reproduce the exact same result, you have to make sure to turn
on the ``singleChannel`` flag in the configuration when using ``decodeSingle``.
## <a name="changelog">Changelog</a> ## <a name="changelog">Changelog</a>
### 2015-07-08
- Improvements
- Parameter tweaking to reduce false-positives significantly (for the
entire EAN and UPC family)
- Fixing bug in parity check for UPC-E codes
- Fixing bug in alignment for EAN-8 codes
### 2015-07-06
- Improvements
- Added `err` parameter to [Quagga.init()](#quaggainit) callback
function
### 2015-06-21
- Features
- Added ``singleChannel`` configuration to ``inputStream`` (in [config]
(#configobject))
- Added ``ResultCollector`` functionality (see [ResultCollector]
(#resultcollector))
### 2015-06-13
- Improvements
- Added ``format`` property to ``codeResult`` (in [result](#resultobject))
### 2015-06-13 ### 2015-06-13
- Improvements - Improvements
- Added fixes for ``Code39Reader`` (trailing whitespace was missing) - Added fixes for ``Code39Reader`` (trailing whitespace was missing)

371
dist/quagga.js vendored

@ -437,7 +437,7 @@ define("almond", function(){});
define( define(
'barcode_reader',[],function() { 'barcode_reader',[],function() {
"use strict";
function BarcodeReader() { function BarcodeReader() {
this._row = []; this._row = [];
@ -600,6 +600,9 @@ define(
} else { } else {
result.direction = BarcodeReader.DIRECTION.FORWARD; result.direction = BarcodeReader.DIRECTION.FORWARD;
} }
if (result) {
result.format = self.FORMAT;
}
return result; return result;
}; };
@ -615,6 +618,11 @@ define(
return true; return true;
}; };
Object.defineProperty(BarcodeReader.prototype, "FORMAT", {
value: 'unknown',
writeable: false
});
BarcodeReader.DIRECTION = { BarcodeReader.DIRECTION = {
FORWARD : 1, FORWARD : 1,
REVERSE : -1 REVERSE : -1
@ -638,7 +646,7 @@ define(
"./barcode_reader" "./barcode_reader"
], ],
function(BarcodeReader) { function(BarcodeReader) {
"use strict";
function Code128Reader() { function Code128Reader() {
BarcodeReader.call(this); BarcodeReader.call(this);
@ -764,7 +772,8 @@ define(
[2, 3, 3, 1, 1, 1, 2] [2, 3, 3, 1, 1, 1, 2]
]}, ]},
SINGLE_CODE_ERROR: {value: 1}, SINGLE_CODE_ERROR: {value: 1},
AVG_CODE_ERROR: {value: 0.5} AVG_CODE_ERROR: {value: 0.5},
FORMAT: {value: "code_128", writeable: false}
}; };
Code128Reader.prototype = Object.create(BarcodeReader.prototype, properties); Code128Reader.prototype = Object.create(BarcodeReader.prototype, properties);
@ -793,7 +802,8 @@ define(
} else { } else {
if (counterPos === counter.length - 1) { if (counterPos === counter.length - 1) {
normalized = self._normalize(counter); normalized = self._normalize(counter);
for ( code = 0; code < self.CODE_PATTERN.length; code++) { if (normalized) {
for (code = 0; code < self.CODE_PATTERN.length; 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;
@ -802,58 +812,7 @@ define(
} }
bestMatch.end = i; bestMatch.end = i;
return bestMatch; return bestMatch;
} else {
counterPos++;
}
counter[counterPos] = 1;
isWhite = !isWhite;
} }
}
return null;
};
Code128Reader.prototype._findEnd = function() {
var counter = [0, 0, 0, 0, 0, 0, 0],
i,
self = this,
offset = self._nextSet(self._row),
isWhite = !self._row[offset],
counterPos = 0,
bestMatch = {
error : Number.MAX_VALUE,
code : -1,
start : 0,
end : 0
},
error,
j,
sum,
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, 13);
error = self._matchPattern(normalized, self.CODE_PATTERN[self.STOP_CODE]);
if (error < self.AVG_CODE_ERROR) {
bestMatch.error = error;
bestMatch.start = i - sum;
bestMatch.end = i;
return bestMatch;
}
for ( j = 0; j < 5; j++) {
counter[j] = counter[j + 2];
}
counter[5] = 0;
counter[6] = 0;
counterPos--;
} else { } else {
counterPos++; counterPos++;
} }
@ -893,7 +852,8 @@ define(
sum += counter[j]; sum += counter[j];
} }
normalized = self._normalize(counter); normalized = self._normalize(counter);
for ( code = self.START_CODE_A; code <= self.START_CODE_C; code++) { if (normalized) {
for (code = self.START_CODE_A; code <= self.START_CODE_C; 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;
@ -905,6 +865,7 @@ define(
bestMatch.end = i; bestMatch.end = i;
return bestMatch; return bestMatch;
} }
}
for ( j = 0; j < 4; j++) { for ( j = 0; j < 4; j++) {
counter[j] = counter[j + 2]; counter[j] = counter[j + 2];
@ -1052,7 +1013,7 @@ define(
// find end bar // find end bar
code.end = self._nextUnset(self._row, code.end); code.end = self._nextUnset(self._row, code.end);
if (code.end === self._row.length) { if(!self._verifyTrailingWhitespace(code)){
return null; return null;
} }
@ -1063,9 +1024,15 @@ define(
return null; return null;
} }
if (!result.length) {
return null;
}
// remove last code from result (checksum) // remove last code from result (checksum)
result.splice(result.length - 1, 1); result.splice(result.length - 1, 1);
return { return {
code : result.join(""), code : result.join(""),
start : startInfo.start, start : startInfo.start,
@ -1077,6 +1044,20 @@ define(
}; };
}; };
BarcodeReader.prototype._verifyTrailingWhitespace = function(endInfo) {
var self = this,
trailingWhitespaceEnd;
trailingWhitespaceEnd = endInfo.end + ((endInfo.end - endInfo.start) / 2);
if (trailingWhitespaceEnd < self._row.length) {
if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) {
return endInfo;
}
}
return null;
};
return (Code128Reader); return (Code128Reader);
} }
); );
@ -1088,7 +1069,7 @@ define(
"./barcode_reader" "./barcode_reader"
], ],
function(BarcodeReader) { function(BarcodeReader) {
"use strict";
function EANReader(opts) { function EANReader(opts) {
BarcodeReader.call(this, opts); BarcodeReader.call(this, opts);
@ -1124,8 +1105,9 @@ define(
[2, 1, 1, 3] [2, 1, 1, 3]
]}, ]},
CODE_FREQUENCY : {value: [0, 11, 13, 14, 19, 25, 28, 21, 22, 26]}, CODE_FREQUENCY : {value: [0, 11, 13, 14, 19, 25, 28, 21, 22, 26]},
SINGLE_CODE_ERROR: {value: 0.7}, SINGLE_CODE_ERROR: {value: 0.67},
AVG_CODE_ERROR: {value: 0.3} AVG_CODE_ERROR: {value: 0.27},
FORMAT: {value: "ean_13", writeable: false}
}; };
EANReader.prototype = Object.create(BarcodeReader.prototype, properties); EANReader.prototype = Object.create(BarcodeReader.prototype, properties);
@ -1158,7 +1140,8 @@ define(
} else { } else {
if (counterPos === counter.length - 1) { if (counterPos === counter.length - 1) {
normalized = self._normalize(counter); normalized = self._normalize(counter);
for ( code = 0; code < coderange; code++) { if (normalized) {
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;
@ -1170,6 +1153,7 @@ define(
return null; return null;
} }
return bestMatch; return bestMatch;
}
} else { } else {
counterPos++; counterPos++;
} }
@ -1226,6 +1210,7 @@ define(
sum += counter[j]; sum += counter[j];
} }
normalized = self._normalize(counter); normalized = self._normalize(counter);
if (normalized) {
error = self._matchPattern(normalized, pattern); error = self._matchPattern(normalized, pattern);
if (error < epsilon) { if (error < epsilon) {
@ -1234,6 +1219,7 @@ define(
bestMatch.end = i; bestMatch.end = i;
return bestMatch; return bestMatch;
} }
}
if (tryHarder) { if (tryHarder) {
for ( j = 0; j < counter.length - 2; j++) { for ( j = 0; j < counter.length - 2; j++) {
counter[j] = counter[j + 2]; counter[j] = counter[j + 2];
@ -1416,7 +1402,7 @@ define(
/* global define */ /* global define */
define('image_loader',[],function() { define('image_loader',[],function() {
"use strict";
var ImageLoader = {}; var ImageLoader = {};
ImageLoader.load = function(directory, callback, offset, size, sequence) { ImageLoader.load = function(directory, callback, offset, size, sequence) {
@ -1480,7 +1466,7 @@ define('image_loader',[],function() {
/* global define */ /* global define */
define('input_stream',["image_loader"], function(ImageLoader) { define('input_stream',["image_loader"], function(ImageLoader) {
"use strict";
var InputStream = {}; var InputStream = {};
InputStream.createVideoStream = function(video) { InputStream.createVideoStream = function(video) {
@ -1827,7 +1813,7 @@ define("typedefs", (function (global) {
/* global define */ /* global define */
define('subImage',["typedefs"], function() { define('subImage',["typedefs"], function() {
"use strict";
/** /**
* Construct representing a part of another {ImageWrapper}. Shares data * Construct representing a part of another {ImageWrapper}. Shares data
@ -1951,7 +1937,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
(function() { (function() {
"use strict";
var shim = {}; var shim = {};
if (typeof(exports) === 'undefined') { if (typeof(exports) === 'undefined') {
@ -5796,7 +5782,7 @@ if(typeof(exports) !== 'undefined') {
/* global define */ /* global define */
define('cluster',["gl-matrix"], function(glMatrix) { define('cluster',["gl-matrix"], function(glMatrix) {
"use strict";
var vec2 = glMatrix.vec2; var vec2 = glMatrix.vec2;
/** /**
@ -5869,7 +5855,7 @@ define('cluster',["gl-matrix"], function(glMatrix) {
/* global define */ /* global define */
define('array_helper',[],function() { define('array_helper',[],function() {
"use strict";
return { return {
init : function(arr, val) { init : function(arr, val) {
@ -5956,7 +5942,7 @@ define('array_helper',[],function() {
define('cv_utils',['cluster', "array_helper", "gl-matrix"], function(Cluster2, ArrayHelper, glMatrix) { define('cv_utils',['cluster', "array_helper", "gl-matrix"], function(Cluster2, ArrayHelper, glMatrix) {
"use strict";
/* /*
* cv_utils.js * cv_utils.js
* Collection of CV functions and libraries * Collection of CV functions and libraries
@ -6439,14 +6425,20 @@ define('cv_utils',['cluster', "array_helper", "gl-matrix"], function(Cluster2, A
}; };
CVUtils.computeGray = function(imageData, outArray) { CVUtils.computeGray = function(imageData, outArray, config) {
var l = imageData.length / 4; var l = (imageData.length / 4) | 0,
var i = 0; i,
for ( i = 0; i < l; i++) { singleChannel = config && config.singleChannel === true;
//outArray[i] = (0.299*imageData[i*4+0] + 0.587*imageData[i*4+1] + 0.114*imageData[i*4+2]);
if (singleChannel) {
for (i = 0; i < l; i++) {
outArray[i] = imageData[i * 4 + 0];
}
} else {
for (i = 0; i < l; i++) {
outArray[i] = Math.floor(0.299 * imageData[i * 4 + 0] + 0.587 * imageData[i * 4 + 1] + 0.114 * imageData[i * 4 + 2]); outArray[i] = Math.floor(0.299 * imageData[i * 4 + 0] + 0.587 * imageData[i * 4 + 1] + 0.114 * imageData[i * 4 + 2]);
} }
}
}; };
CVUtils.loadImageArray = function(src, callback, canvas) { CVUtils.loadImageArray = function(src, callback, canvas) {
@ -6676,7 +6668,7 @@ define('image_wrapper',[
], ],
function(SubImage, CVUtils, ArrayHelper, glMatrix) { function(SubImage, CVUtils, ArrayHelper, glMatrix) {
'use strict';
var vec2 = glMatrix.vec2, var vec2 = glMatrix.vec2,
mat2 = glMatrix.mat2; mat2 = glMatrix.mat2;
@ -7099,7 +7091,7 @@ define('image_wrapper',[
* 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() { define('tracer',[],function() {
"use strict";
var Tracer = { var Tracer = {
searchDirections : [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]], searchDirections : [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]],
@ -7208,7 +7200,7 @@ define('tracer',[],function() {
* http://www.codeproject.com/Tips/407172/Connected-Component-Labeling-and-Vectorization * http://www.codeproject.com/Tips/407172/Connected-Component-Labeling-and-Vectorization
*/ */
define('rasterizer',["tracer"], function(Tracer) { define('rasterizer',["tracer"], function(Tracer) {
"use strict";
var Rasterizer = { var Rasterizer = {
createContour2D : function() { createContour2D : function() {
@ -7404,7 +7396,7 @@ define('rasterizer',["tracer"], function(Tracer) {
/* global define */ /* global define */
define('skeletonizer',[],function() { define('skeletonizer',[],function() {
"use strict";
Math.imul = Math.imul || function(a, b) { Math.imul = Math.imul || function(a, b) {
var ah = (a >>> 16) & 0xffff; var ah = (a >>> 16) & 0xffff;
@ -7619,7 +7611,7 @@ define('skeletonizer',[],function() {
/* global define */ /* global define */
define('image_debug',[],function() { define('image_debug',[],function() {
"use strict";
return { return {
drawRect: function(pos, size, ctx, style){ drawRect: function(pos, size, ctx, style){
@ -7640,6 +7632,26 @@ define('image_debug',[],function() {
} }
ctx.closePath(); ctx.closePath();
ctx.stroke(); ctx.stroke();
},
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) {
return false;
}
while(imageDataPos--){
value = imageData[imageDataPos];
data[--canvasDataPos] = 255;
data[--canvasDataPos] = value;
data[--canvasDataPos] = value;
data[--canvasDataPos] = value;
}
ctx.putImageData(canvasData, 0, 0);
return true;
} }
}; };
@ -8211,7 +8223,7 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
/* global define */ /* global define */
define('bresenham',["cv_utils", "image_wrapper"], function(CVUtils, ImageWrapper) { define('bresenham',["cv_utils", "image_wrapper"], function(CVUtils, ImageWrapper) {
"use strict";
var Bresenham = {}; var Bresenham = {};
var Slope = { var Slope = {
@ -8326,6 +8338,7 @@ define('bresenham',["cv_utils", "image_wrapper"], function(CVUtils, ImageWrapper
max = result.max, max = result.max,
line = result.line, line = result.line,
slope, slope,
slope2,
center = min + (max - min) / 2, center = min + (max - min) / 2,
extrema = [], extrema = [],
currentDir, currentDir,
@ -8341,11 +8354,12 @@ define('bresenham',["cv_utils", "image_wrapper"], function(CVUtils, ImageWrapper
pos : 0, pos : 0,
val : line[0] val : line[0]
}); });
for ( i = 0; i < line.length - 1; i++) { for ( i = 0; i < line.length - 2; i++) {
slope = (line[i + 1] - line[i]); slope = (line[i + 1] - line[i]);
if (slope < rThreshold && line[i + 1] < (center*1.5)) { slope2 = (line[i + 2] - line[i + 1]);
if ((slope + slope2) < rThreshold && line[i + 1] < (center*1.5)) {
dir = Slope.DIR.DOWN; dir = Slope.DIR.DOWN;
} else if (slope > 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;
@ -8431,7 +8445,7 @@ define(
"./array_helper" "./array_helper"
], ],
function(BarcodeReader, ArrayHelper) { function(BarcodeReader, ArrayHelper) {
"use strict";
function Code39Reader() { function Code39Reader() {
BarcodeReader.call(this); BarcodeReader.call(this);
@ -8441,7 +8455,8 @@ define(
ALPHABETH_STRING: {value: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *$/+%"}, 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]}, 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]}, 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]},
ASTERISK: {value: 0x094} ASTERISK: {value: 0x094},
FORMAT: {value: "code_39", writeable: false}
}; };
Code39Reader.prototype = Object.create(BarcodeReader.prototype, properties); Code39Reader.prototype = Object.create(BarcodeReader.prototype, properties);
@ -8650,7 +8665,7 @@ define(
"./code_39_reader" "./code_39_reader"
], ],
function(Code39Reader) { function(Code39Reader) {
"use strict";
function Code39VINReader() { function Code39VINReader() {
Code39Reader.call(this); Code39Reader.call(this);
@ -8710,7 +8725,7 @@ define(
"./barcode_reader" "./barcode_reader"
], ],
function(BarcodeReader) { function(BarcodeReader) {
"use strict";
function CodabarReader() { function CodabarReader() {
BarcodeReader.call(this); BarcodeReader.call(this);
@ -8724,7 +8739,8 @@ define(
START_END: {value: [0x01A, 0x029, 0x00B, 0x00E]}, START_END: {value: [0x01A, 0x029, 0x00B, 0x00E]},
MIN_ENCODED_CHARS: {value: 4}, MIN_ENCODED_CHARS: {value: 4},
MAX_ACCEPTABLE: {value: 2.0}, MAX_ACCEPTABLE: {value: 2.0},
PADDING: {value: 1.5} PADDING: {value: 1.5},
FORMAT: {value: "codabar", writeable: false}
}; };
CodabarReader.prototype = Object.create(BarcodeReader.prototype, properties); CodabarReader.prototype = Object.create(BarcodeReader.prototype, properties);
@ -9023,13 +9039,17 @@ define(
"./ean_reader" "./ean_reader"
], ],
function(EANReader) { function(EANReader) {
"use strict";
function UPCReader() { function UPCReader() {
EANReader.call(this); EANReader.call(this);
} }
UPCReader.prototype = Object.create(EANReader.prototype); var properties = {
FORMAT: {value: "upc_a", writeable: false}
};
UPCReader.prototype = Object.create(EANReader.prototype, properties);
UPCReader.prototype.constructor = UPCReader; UPCReader.prototype.constructor = UPCReader;
UPCReader.prototype._decode = function() { UPCReader.prototype._decode = function() {
@ -9054,13 +9074,17 @@ define(
"./ean_reader" "./ean_reader"
], ],
function(EANReader) { function(EANReader) {
"use strict";
function EAN8Reader() { function EAN8Reader() {
EANReader.call(this); EANReader.call(this);
} }
EAN8Reader.prototype = Object.create(EANReader.prototype); var properties = {
FORMAT: {value: "ean_8", writeable: false}
};
EAN8Reader.prototype = Object.create(EANReader.prototype, properties);
EAN8Reader.prototype.constructor = EAN8Reader; EAN8Reader.prototype.constructor = EAN8Reader;
EAN8Reader.prototype._decodePayload = function(code, result, decodedCodes) { EAN8Reader.prototype._decodePayload = function(code, result, decodedCodes) {
@ -9076,7 +9100,7 @@ define(
decodedCodes.push(code); decodedCodes.push(code);
} }
code = self._findPattern(self.MIDDLE_PATTERN, code.end, true); code = self._findPattern(self.MIDDLE_PATTERN, code.end, true, false);
if (code === null) { if (code === null) {
return null; return null;
} }
@ -9105,7 +9129,7 @@ define(
"./ean_reader" "./ean_reader"
], ],
function(EANReader) { function(EANReader) {
"use strict";
function UPCEReader() { function UPCEReader() {
EANReader.call(this); EANReader.call(this);
@ -9115,7 +9139,8 @@ define(
CODE_FREQUENCY : {value: [ CODE_FREQUENCY : {value: [
[ 56, 52, 50, 49, 44, 38, 35, 42, 41, 37 ], [ 56, 52, 50, 49, 44, 38, 35, 42, 41, 37 ],
[7, 11, 13, 14, 19, 25, 28, 21, 22, 26]]}, [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]} 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 = Object.create(EANReader.prototype, properties);
@ -9134,13 +9159,13 @@ define(
if (code.code >= self.CODE_G_START) { if (code.code >= self.CODE_G_START) {
code.code = code.code - self.CODE_G_START; code.code = code.code - self.CODE_G_START;
codeFrequency |= 1 << (5 - i); codeFrequency |= 1 << (5 - i);
} else {
codeFrequency |= 0 << (5 - i);
} }
result.push(code.code); result.push(code.code);
decodedCodes.push(code); decodedCodes.push(code);
} }
self._determineParity(codeFrequency, result); if (!self._determineParity(codeFrequency, result)) {
return null;
}
return code; return code;
}; };
@ -9155,10 +9180,11 @@ define(
if (codeFrequency === self.CODE_FREQUENCY[nrSystem][i]) { if (codeFrequency === self.CODE_FREQUENCY[nrSystem][i]) {
result.unshift(nrSystem); result.unshift(nrSystem);
result.push(i); result.push(i);
return; return true;
} }
} }
} }
return false;
}; };
UPCEReader.prototype._convertToUPCA = function(result) { UPCEReader.prototype._convertToUPCA = function(result) {
@ -9234,7 +9260,7 @@ define('barcode_decoder',[
UPCReader, UPCReader,
EAN8Reader, EAN8Reader,
UPCEReader) { UPCEReader) {
"use strict";
var readers = { var readers = {
code_128_reader: Code128Reader, code_128_reader: Code128Reader,
@ -9260,8 +9286,7 @@ define('barcode_decoder',[
overlay : null overlay : null
} }
}, },
_barcodeReaders = [], _barcodeReaders = [];
_barcodeReader = null;
initCanvas(); initCanvas();
initReaders(); initReaders();
@ -9346,13 +9371,10 @@ define('barcode_decoder',[
// check if inside image // check if inside image
extendLine(ext); extendLine(ext);
while (ext > 1 && !inputImageWrapper.inImageWithBorder(line[0], 0) || !inputImageWrapper.inImageWithBorder(line[1], 0)) { while (ext > 1 && (!inputImageWrapper.inImageWithBorder(line[0], 0) || !inputImageWrapper.inImageWithBorder(line[1], 0))) {
ext -= Math.floor(ext/2); ext -= Math.ceil(ext/2);
extendLine(-ext); extendLine(-ext);
} }
if (ext <= 1) {
return null;
}
return line; return line;
} }
@ -9382,9 +9404,6 @@ define('barcode_decoder',[
for ( i = 0; i < _barcodeReaders.length && result === null; i++) { for ( i = 0; i < _barcodeReaders.length && result === null; i++) {
result = _barcodeReaders[i].decodePattern(barcodeLine.line); result = _barcodeReaders[i].decodePattern(barcodeLine.line);
if (result !== null) {
_barcodeReader = _barcodeReaders[i];
}
} }
if(result === null){ if(result === null){
return null; return null;
@ -9513,7 +9532,7 @@ define('barcode_decoder',[
/* global define */ /* global define */
define('frame_grabber',["cv_utils"], function(CVUtils) { define('frame_grabber',["cv_utils"], function(CVUtils) {
"use strict";
var FrameGrabber = {}; var FrameGrabber = {};
@ -9570,7 +9589,7 @@ define('frame_grabber',["cv_utils"], function(CVUtils) {
if(doHalfSample){ if(doHalfSample){
CVUtils.grayAndHalfSampleFromCanvasData(ctxData, _size, _data); CVUtils.grayAndHalfSampleFromCanvasData(ctxData, _size, _data);
} else { } else {
CVUtils.computeGray(ctxData, _data); CVUtils.computeGray(ctxData, _data, _streamConfig);
} }
return true; return true;
} else { } else {
@ -9592,7 +9611,7 @@ define('frame_grabber',["cv_utils"], function(CVUtils) {
/* global define */ /* global define */
define('html_utils',[], function() { define('html_utils',[], function() {
"use strict";
function createNode(htmlStr) { function createNode(htmlStr) {
var temp = document.createElement('div'); var temp = document.createElement('div');
@ -9648,7 +9667,8 @@ define('config',[],function(){
right: "0%", right: "0%",
left: "0%", left: "0%",
bottom: "0%" bottom: "0%"
} },
singleChannel: false // true: only the red color-channel is read
}, },
tracking: false, tracking: false,
debug: false, debug: false,
@ -9692,7 +9712,7 @@ define('config',[],function(){
/* global define */ /* global define */
define('events',[],function() { define('events',[],function() {
"use strict";
var _events = function() { var _events = function() {
var events = {}; var events = {};
@ -9783,7 +9803,7 @@ define('events',[],function() {
/* global define, MediaStreamTrack */ /* global define, MediaStreamTrack */
define('camera_access',["html_utils"], function(HtmlUtils) { define('camera_access',["html_utils"], function(HtmlUtils) {
"use strict";
var streamRef, var streamRef,
loadedDataHandler; loadedDataHandler;
@ -9794,11 +9814,15 @@ define('camera_access',["html_utils"], function(HtmlUtils) {
* @param {Object} failure Callback * @param {Object} failure Callback
*/ */
function getUserMedia(constraints, success, failure) { function getUserMedia(constraints, success, failure) {
navigator.getUserMedia(constraints, function(stream) { if (typeof navigator.getUserMedia !== 'undefined') {
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 {
failure(new TypeError("getUserMedia not available"));
}
} }
function loadedData(video, callback) { function loadedData(video, callback) {
@ -9837,7 +9861,7 @@ define('camera_access',["html_utils"], function(HtmlUtils) {
video.addEventListener('loadeddata', loadedDataHandler, false); video.addEventListener('loadeddata', loadedDataHandler, false);
video.play(); video.play();
}, function(e) { }, function(e) {
console.log(e); callback(e);
}); });
} }
@ -9860,7 +9884,7 @@ define('camera_access',["html_utils"], function(HtmlUtils) {
facing: "environment" facing: "environment"
}, config); }, config);
if ( 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) {
@ -9919,10 +9943,68 @@ define('camera_access',["html_utils"], function(HtmlUtils) {
}; };
}); });
/* jshint undef: true, unused: true, browser:true, devel: true, evil: true */ /* jshint undef: true, unused: true, browser:true, devel: true */
/* global define */ /* global define */
define('result_collector',["image_debug"], function(ImageDebug) {
"use strict";
function contains(codeResult, list) {
if (list) {
return list.some(function (item) {
return Object.keys(item).every(function (key) {
return item[key] === codeResult[key];
});
});
}
return false;
}
function passesFilter(codeResult, filter) {
if (typeof filter === 'function') {
return filter(codeResult);
}
return true;
}
return {
create: function(config) {
var canvas = document.createElement("canvas"),
ctx = canvas.getContext("2d"),
results = [],
capacity = config.capacity || 20,
capture = config.capture === true;
function matchesConstraints(codeResult) {
return capacity && codeResult && !contains(codeResult, config.blacklist) && passesFilter(codeResult, config.filter);
}
return {
addResult: function(data, imageSize, codeResult) {
var result = {};
if (matchesConstraints(codeResult)) {
capacity--;
result.codeResult = codeResult;
if (capture) {
canvas.width = imageSize.x;
canvas.height = imageSize.y;
ImageDebug.drawImage(data, imageSize, ctx);
result.frame = canvas.toDataURL();
}
results.push(result);
}
},
getResults: function() {
return results;
}
};
}
};
});
/* jshint undef: true, unused: true, browser:true, devel: true, evil: true */
/* global define */
define('quagga',[ define('quagga',[
"code_128_reader", "code_128_reader",
"ean_reader", "ean_reader",
@ -9936,7 +10018,8 @@ define('quagga',[
"events", "events",
"camera_access", "camera_access",
"image_debug", "image_debug",
"gl-matrix"], "gl-matrix",
"result_collector"],
function(Code128Reader, function(Code128Reader,
EANReader, EANReader,
InputStream, InputStream,
@ -9949,8 +10032,9 @@ function(Code128Reader,
Events, Events,
CameraAccess, CameraAccess,
ImageDebug, ImageDebug,
glMatrix) { glMatrix,
ResultCollector) {
"use strict";
var _inputStream, var _inputStream,
_framegrabber, _framegrabber,
@ -9970,7 +10054,8 @@ function(Code128Reader,
_decoder, _decoder,
_workerPool = [], _workerPool = [],
_onUIThread = true, _onUIThread = true,
vec2 = glMatrix.vec2; vec2 = glMatrix.vec2,
_resultCollector;
function initializeData(imageWrapper) { function initializeData(imageWrapper) {
initBuffers(imageWrapper); initBuffers(imageWrapper);
@ -10020,7 +10105,7 @@ function(Code128Reader,
if (!err) { if (!err) {
_inputStream.trigger("canrecord"); _inputStream.trigger("canrecord");
} else { } else {
console.log(err); return cb(err);
} }
}); });
} }
@ -10032,9 +10117,7 @@ function(Code128Reader,
} }
function canRecord(cb) { function canRecord(cb) {
if (_config.locate) {
BarcodeLocator.checkImageConstraints(_inputStream, _config.locator); BarcodeLocator.checkImageConstraints(_inputStream, _config.locator);
}
initCanvas(); initCanvas();
_framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image); _framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image);
initConfig(); initConfig();
@ -10101,10 +10184,10 @@ function(Code128Reader,
console.log(_inputImageWrapper.size); console.log(_inputImageWrapper.size);
_boxSize = [ _boxSize = [
vec2.clone([20, _inputImageWrapper.size.y / 2 - 100]), vec2.clone([0, 0]),
vec2.clone([20, _inputImageWrapper.size.y / 2 + 100]), vec2.clone([0, _inputImageWrapper.size.y]),
vec2.clone([_inputImageWrapper.size.x - 20, _inputImageWrapper.size.y / 2 + 100]), vec2.clone([_inputImageWrapper.size.x, _inputImageWrapper.size.y]),
vec2.clone([_inputImageWrapper.size.x - 20, _inputImageWrapper.size.y / 2 - 100]) vec2.clone([_inputImageWrapper.size.x, 0])
]; ];
BarcodeLocator.init(_inputImageWrapper, _config.locator); BarcodeLocator.init(_inputImageWrapper, _config.locator);
} }
@ -10113,7 +10196,11 @@ function(Code128Reader,
if (_config.locate) { if (_config.locate) {
return BarcodeLocator.locate(); return BarcodeLocator.locate();
} else { } else {
return [_boxSize]; return [[
vec2.clone(_boxSize[0]),
vec2.clone(_boxSize[1]),
vec2.clone(_boxSize[2]),
vec2.clone(_boxSize[3])]];
} }
} }
@ -10154,10 +10241,16 @@ function(Code128Reader,
} }
} }
function publishResult(result) { function publishResult(result, imageData) {
if (_onUIThread) { if (_onUIThread) {
transformResult(result); transformResult(result);
if (imageData && result && result.codeResult) {
if (_resultCollector) {
_resultCollector.addResult(imageData, _inputStream.getCanvasSize(), result.codeResult);
} }
}
}
Events.publish("processed", result); Events.publish("processed", result);
if (result && result.codeResult) { if (result && result.codeResult) {
Events.publish("detected", result); Events.publish("detected", result);
@ -10173,7 +10266,7 @@ function(Code128Reader,
result = _decoder.decodeFromBoundingBoxes(boxes); result = _decoder.decodeFromBoundingBoxes(boxes);
result = result || {}; result = result || {};
result.boxes = boxes; result.boxes = boxes;
publishResult(result); publishResult(result, _inputImageWrapper.data);
} else { } else {
publishResult(); publishResult();
} }
@ -10242,7 +10335,7 @@ function(Code128Reader,
function initWorker(cb) { function initWorker(cb) {
var blobURL, var blobURL,
workerThread = { workerThread = {
worker: null, worker: undefined,
imageData: new Uint8Array(_inputStream.getWidth() * _inputStream.getHeight()), imageData: new Uint8Array(_inputStream.getWidth() * _inputStream.getHeight()),
busy: true busy: true
}; };
@ -10260,7 +10353,7 @@ function(Code128Reader,
} else if (e.data.event === 'processed') { } else if (e.data.event === 'processed') {
workerThread.imageData = new Uint8Array(e.data.imageData); workerThread.imageData = new Uint8Array(e.data.imageData);
workerThread.busy = false; workerThread.busy = false;
publishResult(e.data.result); publishResult(e.data.result, workerThread.imageData);
} }
}; };
@ -10375,6 +10468,11 @@ function(Code128Reader,
setReaders: function(readers) { setReaders: function(readers) {
setReaders(readers); setReaders(readers);
}, },
registerResultCollector: function(resultCollector) {
if (resultCollector && typeof resultCollector.addResult === 'function') {
_resultCollector = resultCollector;
}
},
canvas : _canvasContainer, canvas : _canvasContainer,
decodeSingle : function(config, resultCallback) { decodeSingle : function(config, resultCallback) {
config = HtmlUtils.mergeObjects({ config = HtmlUtils.mergeObjects({
@ -10402,7 +10500,8 @@ function(Code128Reader,
Code128Reader : Code128Reader Code128Reader : Code128Reader
}, },
ImageWrapper: ImageWrapper, ImageWrapper: ImageWrapper,
ImageDebug: ImageDebug ImageDebug: ImageDebug,
ResultCollector: ResultCollector
}; };
}); });

File diff suppressed because one or more lines are too long

@ -74,7 +74,7 @@
padding: 10px 0; padding: 10px 0;
} }
/* line 50, ../sass/_viewport.scss */ /* line 50, ../sass/_viewport.scss */
#result_strip ul.thumbnails { #result_strip > ul {
padding: 0; padding: 0;
margin: 0; margin: 0;
list-style-type: none; list-style-type: none;
@ -84,34 +84,34 @@
white-space: nowrap; white-space: nowrap;
} }
/* line 59, ../sass/_viewport.scss */ /* line 59, ../sass/_viewport.scss */
#result_strip ul.thumbnails > li { #result_strip > ul > li {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
width: 160px; width: 160px;
} }
/* line 63, ../sass/_viewport.scss */ /* line 63, ../sass/_viewport.scss */
#result_strip ul.thumbnails > li .thumbnail { #result_strip > ul > li .thumbnail {
padding: 5px; padding: 5px;
margin: 4px; margin: 4px;
border: 1px dashed #CCC; border: 1px dashed #CCC;
} }
/* line 68, ../sass/_viewport.scss */ /* line 68, ../sass/_viewport.scss */
#result_strip ul.thumbnails > li .thumbnail img { #result_strip > ul > li .thumbnail img {
max-width: 140px; max-width: 140px;
} }
/* line 71, ../sass/_viewport.scss */ /* line 71, ../sass/_viewport.scss */
#result_strip ul.thumbnails > li .thumbnail .caption { #result_strip > ul > li .thumbnail .caption {
white-space: normal; white-space: normal;
} }
/* line 73, ../sass/_viewport.scss */ /* line 73, ../sass/_viewport.scss */
#result_strip ul.thumbnails > li .thumbnail .caption h4 { #result_strip > ul > li .thumbnail .caption h4 {
text-align: center; text-align: center;
word-wrap: break-word; word-wrap: break-word;
height: 40px; height: 40px;
margin: 0px; margin: 0px;
} }
/* line 83, ../sass/_viewport.scss */ /* line 83, ../sass/_viewport.scss */
#result_strip ul.thumbnails:after { #result_strip > ul:after {
content: ""; content: "";
display: table; display: table;
clear: both; clear: both;

@ -77,14 +77,15 @@
<span>Half-Sample</span> <span>Half-Sample</span>
<input type="checkbox" name="locator_half-sample" /> <input type="checkbox" name="locator_half-sample" />
</label> </label>
<label>
<span>Single Channel</span>
<input type="checkbox" name="input-stream_single-channel" />
</label>
<label> <label>
<span>Workers</span> <span>Workers</span>
<select name="numOfWorkers"> <select name="numOfWorkers">
<option selected="selected" value="0">0</option> <option value="0">0</option>
<option value="1">1</option> <option selected="selected" value="1">1</option>
<option value="2">2</option>
<option value="4">4</option>
<option value="8">8</option>
</select> </select>
</label> </label>
</fieldset> </fieldset>

@ -88,17 +88,16 @@ $(function() {
}, },
state: { state: {
inputStream: { inputStream: {
size: 640 size: 640,
singleChannel: false
}, },
locator: { locator: {
patchSize: "large", patchSize: "large",
halfSample: false halfSample: false
}, },
numOfWorkers: 0, numOfWorkers: 1,
decoder: { decoder: {
readers: ["code_128_reader"], readers: ["code_128_reader"]
showFrequency: true,
showPattern: true
}, },
locate: true, locate: true,
src: null src: null

@ -68,24 +68,19 @@
<select name="locator_patch-size"> <select name="locator_patch-size">
<option value="x-small">x-small</option> <option value="x-small">x-small</option>
<option value="small">small</option> <option value="small">small</option>
<option value="medium">medium</option> <option selected="selected" value="medium">medium</option>
<option selected="selected" value="large">large</option> <option value="large">large</option>
<option value="x-large">x-large</option> <option value="x-large">x-large</option>
</select> </select>
</label> </label>
<label> <label>
<span>Half-Sample</span> <span>Half-Sample</span>
<input type="checkbox" name="locator_half-sample" /> <input type="checkbox" checked="checked"
name="locator_half-sample" />
</label> </label>
<label> <label>
<span>Workers</span> <span>Single Channel</span>
<select name="numOfWorkers"> <input type="checkbox" name="input-stream_single-channel" />
<option selected="selected" value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="4">4</option>
<option value="8">8</option>
</select>
</label> </label>
</fieldset> </fieldset>
</div> </div>

@ -112,11 +112,12 @@ define(['quagga'], function(Quagga) {
}, },
state: { state: {
inputStream: { inputStream: {
size: 640 size: 640,
singleChannel: false
}, },
locator: { locator: {
patchSize: "large", patchSize: "medium",
halfSample: false halfSample: true
}, },
numOfWorkers: 0, numOfWorkers: 0,
decoder: { decoder: {
@ -162,7 +163,7 @@ define(['quagga'], function(Quagga) {
$node = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>'); $node = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>');
$node.find("img").attr("src", canvas.toDataURL()); $node.find("img").attr("src", canvas.toDataURL());
$node.find("h4.code").html(code); $node.find("h4.code").html(code + " (" + result.codeResult.format + ")");
$("#result_strip ul.thumbnails").prepend($node); $("#result_strip ul.thumbnails").prepend($node);
}); });
}); });

@ -82,6 +82,7 @@
</div> </div>
<div id="result_strip"> <div id="result_strip">
<ul class="thumbnails"></ul> <ul class="thumbnails"></ul>
<ul class="collector"></ul>
</div> </div>
<div id="interactive" class="viewport"></div> <div id="interactive" class="viewport"></div>
</section> </section>

@ -1,17 +1,37 @@
$(function() { $(function() {
var resultCollector = Quagga.ResultCollector.create({
capture: true,
capacity: 20,
blacklist: [{code: "3574660239843", format: "ean_13"}],
filter: function(codeResult) {
// only store results which match this constraint
// e.g.: codeResult
return true;
}
});
var App = { var App = {
init : function() { init : function() {
Quagga.init(this.state, function() { var self = this;
Quagga.init(this.state, function(err) {
if (err) {
return self.handleError(err);
}
Quagga.registerResultCollector(resultCollector);
App.attachListeners(); App.attachListeners();
Quagga.start(); Quagga.start();
}); });
}, },
handleError: function(err) {
console.log(err);
},
attachListeners: function() { attachListeners: function() {
var self = this; var self = this;
$(".controls").on("click", "button.stop", function(e) { $(".controls").on("click", "button.stop", function(e) {
e.preventDefault(); e.preventDefault();
Quagga.stop(); Quagga.stop();
self._printCollectedResults();
}); });
$(".controls .reader-config-group").on("change", "input, select", function(e) { $(".controls .reader-config-group").on("change", "input, select", function(e) {
@ -25,6 +45,18 @@ $(function() {
self.setState(state, value); self.setState(state, value);
}); });
}, },
_printCollectedResults: function() {
var results = resultCollector.getResults(),
$ul = $("#result_strip ul.collector");
results.forEach(function(result) {
var $li = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>');
$li.find("img").attr("src", result.frame);
$li.find("h4.code").html(result.codeResult.code + " (" + result.codeResult.format + ")");
$ul.prepend($li);
});
},
_accessByPath: function(obj, path, val) { _accessByPath: function(obj, path, val) {
var parts = path.split('.'), var parts = path.split('.'),
depth = parts.length, depth = parts.length,

@ -47,7 +47,7 @@
border-bottom: 1px solid #EEE; border-bottom: 1px solid #EEE;
padding: 10px 0; padding: 10px 0;
ul.thumbnails{ & > ul {
padding: 0; padding: 0;
margin: 0; margin: 0;
list-style-type: none; list-style-type: none;

@ -1,6 +1,6 @@
{ {
"name": "quagga", "name": "quagga",
"version": "0.6.8", "version": "0.6.13",
"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.js",
@ -43,6 +43,8 @@
"ean", "ean",
"code128", "code128",
"code39", "code39",
"codabar",
"upc",
"getusermedia", "getusermedia",
"imageprocessing" "imageprocessing"
], ],

@ -1,6 +1,5 @@
define(['camera_access'], function(CameraAccess){ define(['camera_access'], function(CameraAccess){
var originalURL, var originalURL,
originalUserMedia,
originalMediaStreamTrack, originalMediaStreamTrack,
video, video,
stream; stream;
@ -11,7 +10,6 @@ define(['camera_access'], function(CameraAccess){
}]; }];
originalURL = window.URL; originalURL = window.URL;
originalUserMedia = window.getUserMedia;
originalMediaStreamTrack = window.MediaStreamTrack; originalMediaStreamTrack = window.MediaStreamTrack;
window.MediaStreamTrack = {}; window.MediaStreamTrack = {};
window.URL = null; window.URL = null;
@ -23,10 +21,7 @@ define(['camera_access'], function(CameraAccess){
} }
}; };
sinon.spy(tracks[0], "stop"); sinon.spy(tracks[0], "stop");
navigator.getUserMedia = function(constraints, cb) {
cb(stream);
};
sinon.spy(navigator, "getUserMedia");
video = { video = {
src: null, src: null,
addEventListener: function() { addEventListener: function() {
@ -48,14 +43,23 @@ define(['camera_access'], function(CameraAccess){
}); });
afterEach(function() { afterEach(function() {
navigator.getUserMedia = originalUserMedia;
window.URL = originalURL; window.URL = originalURL;
window.MediaStreamTrack = originalMediaStreamTrack; window.MediaStreamTrack = originalMediaStreamTrack;
}); });
describe('request', function() { describe('success', function() {
it('should request the camera', function(done) { beforeEach(function() {
CameraAccess.request(video, {}, 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(navigator.getUserMedia.calledOnce).to.equal(true);
expect(video.src).to.deep.equal(stream); expect(video.src).to.deep.equal(stream);
done(); done();
@ -63,9 +67,9 @@ define(['camera_access'], function(CameraAccess){
}); });
}); });
describe('release', function() { describe('release', function () {
it('should release the camera', function(done) { it('should release the camera', function (done) {
CameraAccess.request(video, {}, function() { CameraAccess.request(video, {}, function () {
expect(video.src).to.deep.equal(stream); expect(video.src).to.deep.equal(stream);
CameraAccess.release(); CameraAccess.release();
expect(video.src.getVideoTracks()).to.have.length(1); expect(video.src.getVideoTracks()).to.have.length(1);
@ -74,4 +78,46 @@ define(['camera_access'], function(CameraAccess){
}); });
}); });
}); });
});
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();
});
});
});
});
}); });

@ -35,6 +35,7 @@ define(['quagga', 'async'], function(Quagga, async) {
Quagga.decodeSingle(config, function(result) { Quagga.decodeSingle(config, function(result) {
console.log(sample.name); console.log(sample.name);
expect(result.codeResult.code).to.equal(sample.result); expect(result.codeResult.code).to.equal(sample.result);
expect(result.codeResult.format).to.equal(sample.format);
callback(); callback();
}); });
}, function() { }, function() {
@ -58,6 +59,10 @@ define(['quagga', 'async'], function(Quagga, async) {
{"name": "image-010.jpg", "result": "9002244845578"} {"name": "image-010.jpg", "result": "9002244845578"}
]; ];
testSet.forEach(function(sample) {
sample.format = "ean_13";
});
config.decoder.readers = ['ean_reader']; config.decoder.readers = ['ean_reader'];
_runTestSet(testSet, config); _runTestSet(testSet, config);
}); });
@ -67,8 +72,8 @@ define(['quagga', 'async'], function(Quagga, async) {
testSet = [ testSet = [
{"name": "image-001.jpg", "result": "0001285112001000040801"}, {"name": "image-001.jpg", "result": "0001285112001000040801"},
{"name": "image-002.jpg", "result": "FANAVF1461710"}, {"name": "image-002.jpg", "result": "FANAVF1461710"},
{"name": "image-003.jpg", "result": "673023"}, // {"name": "image-003.jpg", "result": "673023"},
// {"name": "image-004.jpg", "result": "010210150301625334"}, {"name": "image-004.jpg", "result": "010210150301625334"},
{"name": "image-005.jpg", "result": "419055603900009001012999"}, {"name": "image-005.jpg", "result": "419055603900009001012999"},
{"name": "image-006.jpg", "result": "419055603900009001012999"}, {"name": "image-006.jpg", "result": "419055603900009001012999"},
{"name": "image-007.jpg", "result": "T 000003552345"}, {"name": "image-007.jpg", "result": "T 000003552345"},
@ -77,6 +82,10 @@ define(['quagga', 'async'], function(Quagga, async) {
{"name": "image-010.jpg", "result": "673023"} {"name": "image-010.jpg", "result": "673023"}
]; ];
testSet.forEach(function(sample) {
sample.format = "code_128";
});
config.decoder.readers = ['code_128_reader']; config.decoder.readers = ['code_128_reader'];
_runTestSet(testSet, config); _runTestSet(testSet, config);
}); });
@ -85,10 +94,9 @@ define(['quagga', 'async'], function(Quagga, async) {
var config = generateConfig(), var config = generateConfig(),
testSet = [ testSet = [
{"name": "image-001.jpg", "result": "B3% $DAD$"}, {"name": "image-001.jpg", "result": "B3% $DAD$"},
/*{"name": "image-002.jpg", "result": "QUAGGAJS"},*/
{"name": "image-003.jpg", "result": "CODE39"}, {"name": "image-003.jpg", "result": "CODE39"},
{"name": "image-004.jpg", "result": "QUAGGAJS"}, {"name": "image-004.jpg", "result": "QUAGGAJS"},
/* {"name": "image-005.jpg", "result": "CODE39"}, */ {"name": "image-005.jpg", "result": "CODE39"},
{"name": "image-006.jpg", "result": "2/4-8/16-32"}, {"name": "image-006.jpg", "result": "2/4-8/16-32"},
{"name": "image-007.jpg", "result": "2/4-8/16-32"}, {"name": "image-007.jpg", "result": "2/4-8/16-32"},
{"name": "image-008.jpg", "result": "CODE39"}, {"name": "image-008.jpg", "result": "CODE39"},
@ -96,6 +104,10 @@ define(['quagga', 'async'], function(Quagga, async) {
{"name": "image-010.jpg", "result": "CODE39"} {"name": "image-010.jpg", "result": "CODE39"}
]; ];
testSet.forEach(function(sample) {
sample.format = "code_39";
});
config.decoder.readers = ['code_39_reader']; config.decoder.readers = ['code_39_reader'];
_runTestSet(testSet, config); _runTestSet(testSet, config);
}); });
@ -110,11 +122,15 @@ define(['quagga', 'async'], function(Quagga, async) {
{"name": "image-005.jpg", "result": "90162602"}, {"name": "image-005.jpg", "result": "90162602"},
{"name": "image-006.jpg", "result": "24036153"}, {"name": "image-006.jpg", "result": "24036153"},
{"name": "image-007.jpg", "result": "42176817"}, {"name": "image-007.jpg", "result": "42176817"},
/*{"name": "image-008.jpg", "result": "42191605"},*/ {"name": "image-008.jpg", "result": "42191605"},
{"name": "image-009.jpg", "result": "42242215"}, {"name": "image-009.jpg", "result": "42242215"},
{"name": "image-010.jpg", "result": "42184799"} {"name": "image-010.jpg", "result": "42184799"}
]; ];
testSet.forEach(function(sample) {
sample.format = "ean_8";
});
config.decoder.readers = ['ean_8_reader']; config.decoder.readers = ['ean_8_reader'];
_runTestSet(testSet, config); _runTestSet(testSet, config);
}); });
@ -127,13 +143,17 @@ define(['quagga', 'async'], function(Quagga, async) {
{"name": "image-003.jpg", "result": "882428015084"}, {"name": "image-003.jpg", "result": "882428015084"},
{"name": "image-004.jpg", "result": "882428015343"}, {"name": "image-004.jpg", "result": "882428015343"},
{"name": "image-005.jpg", "result": "882428015343"}, {"name": "image-005.jpg", "result": "882428015343"},
{"name": "image-006.jpg", "result": "882428015046"}, /* {"name": "image-006.jpg", "result": "882428015046"}, */
{"name": "image-007.jpg", "result": "882428015084"}, {"name": "image-007.jpg", "result": "882428015084"},
{"name": "image-008.jpg", "result": "882428015046"}, {"name": "image-008.jpg", "result": "882428015046"},
{"name": "image-009.jpg", "result": "039047013551"}, {"name": "image-009.jpg", "result": "039047013551"},
{"name": "image-010.jpg", "result": "039047013551"} {"name": "image-010.jpg", "result": "039047013551"}
]; ];
testSet.forEach(function(sample) {
sample.format = "upc_a";
});
config.decoder.readers = ['upc_reader']; config.decoder.readers = ['upc_reader'];
_runTestSet(testSet, config); _runTestSet(testSet, config);
}); });
@ -153,6 +173,10 @@ define(['quagga', 'async'], function(Quagga, async) {
{"name": "image-010.jpg", "result": "01264904"} {"name": "image-010.jpg", "result": "01264904"}
]; ];
testSet.forEach(function(sample) {
sample.format = "upc_e";
});
config.decoder.readers = ['upc_e_reader']; config.decoder.readers = ['upc_e_reader'];
_runTestSet(testSet, config); _runTestSet(testSet, config);
}); });
@ -167,11 +191,15 @@ define(['quagga', 'async'], function(Quagga, async) {
{"name": "image-005.jpg", "result": "C$399.95A"}, {"name": "image-005.jpg", "result": "C$399.95A"},
{"name": "image-006.jpg", "result": "B546745735B"}, {"name": "image-006.jpg", "result": "B546745735B"},
{"name": "image-007.jpg", "result": "C$399.95A"}, {"name": "image-007.jpg", "result": "C$399.95A"},
/* {"name": "image-008.jpg", "result": "01264904"}, */ {"name": "image-008.jpg", "result": "A16:9/4:3/3:2D"},
{"name": "image-009.jpg", "result": "C$399.95A"}, {"name": "image-009.jpg", "result": "C$399.95A"},
{"name": "image-010.jpg", "result": "C$399.95A"} {"name": "image-010.jpg", "result": "C$399.95A"}
]; ];
testSet.forEach(function(sample) {
sample.format = "codabar";
});
config.decoder.readers = ['codabar_reader']; config.decoder.readers = ['codabar_reader'];
_runTestSet(testSet, config); _runTestSet(testSet, config);
}); });

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

@ -49,8 +49,7 @@ define([
overlay : null overlay : null
} }
}, },
_barcodeReaders = [], _barcodeReaders = [];
_barcodeReader = null;
initCanvas(); initCanvas();
initReaders(); initReaders();
@ -135,13 +134,10 @@ define([
// check if inside image // check if inside image
extendLine(ext); extendLine(ext);
while (ext > 1 && !inputImageWrapper.inImageWithBorder(line[0], 0) || !inputImageWrapper.inImageWithBorder(line[1], 0)) { while (ext > 1 && (!inputImageWrapper.inImageWithBorder(line[0], 0) || !inputImageWrapper.inImageWithBorder(line[1], 0))) {
ext -= Math.floor(ext/2); ext -= Math.ceil(ext/2);
extendLine(-ext); extendLine(-ext);
} }
if (ext <= 1) {
return null;
}
return line; return line;
} }
@ -171,9 +167,6 @@ define([
for ( i = 0; i < _barcodeReaders.length && result === null; i++) { for ( i = 0; i < _barcodeReaders.length && result === null; i++) {
result = _barcodeReaders[i].decodePattern(barcodeLine.line); result = _barcodeReaders[i].decodePattern(barcodeLine.line);
if (result !== null) {
_barcodeReader = _barcodeReaders[i];
}
} }
if(result === null){ if(result === null){
return null; return null;

@ -166,6 +166,9 @@ define(
} else { } else {
result.direction = BarcodeReader.DIRECTION.FORWARD; result.direction = BarcodeReader.DIRECTION.FORWARD;
} }
if (result) {
result.format = self.FORMAT;
}
return result; return result;
}; };
@ -181,6 +184,11 @@ define(
return true; return true;
}; };
Object.defineProperty(BarcodeReader.prototype, "FORMAT", {
value: 'unknown',
writeable: false
});
BarcodeReader.DIRECTION = { BarcodeReader.DIRECTION = {
FORWARD : 1, FORWARD : 1,
REVERSE : -1 REVERSE : -1

@ -117,6 +117,7 @@ define(["cv_utils", "image_wrapper"], function(CVUtils, ImageWrapper) {
max = result.max, max = result.max,
line = result.line, line = result.line,
slope, slope,
slope2,
center = min + (max - min) / 2, center = min + (max - min) / 2,
extrema = [], extrema = [],
currentDir, currentDir,
@ -132,11 +133,12 @@ define(["cv_utils", "image_wrapper"], function(CVUtils, ImageWrapper) {
pos : 0, pos : 0,
val : line[0] val : line[0]
}); });
for ( i = 0; i < line.length - 1; i++) { for ( i = 0; i < line.length - 2; i++) {
slope = (line[i + 1] - line[i]); slope = (line[i + 1] - line[i]);
if (slope < rThreshold && line[i + 1] < (center*1.5)) { slope2 = (line[i + 2] - line[i + 1]);
if ((slope + slope2) < rThreshold && line[i + 1] < (center*1.5)) {
dir = Slope.DIR.DOWN; dir = Slope.DIR.DOWN;
} else if (slope > 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;

@ -13,11 +13,15 @@ define(["html_utils"], function(HtmlUtils) {
* @param {Object} failure Callback * @param {Object} failure Callback
*/ */
function getUserMedia(constraints, success, failure) { function getUserMedia(constraints, success, failure) {
navigator.getUserMedia(constraints, function(stream) { if (typeof navigator.getUserMedia !== 'undefined') {
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 {
failure(new TypeError("getUserMedia not available"));
}
} }
function loadedData(video, callback) { function loadedData(video, callback) {
@ -56,7 +60,7 @@ define(["html_utils"], function(HtmlUtils) {
video.addEventListener('loadeddata', loadedDataHandler, false); video.addEventListener('loadeddata', loadedDataHandler, false);
video.play(); video.play();
}, function(e) { }, function(e) {
console.log(e); callback(e);
}); });
} }
@ -79,7 +83,7 @@ define(["html_utils"], function(HtmlUtils) {
facing: "environment" facing: "environment"
}, config); }, config);
if ( 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) {

@ -20,7 +20,8 @@ define(
START_END: {value: [0x01A, 0x029, 0x00B, 0x00E]}, START_END: {value: [0x01A, 0x029, 0x00B, 0x00E]},
MIN_ENCODED_CHARS: {value: 4}, MIN_ENCODED_CHARS: {value: 4},
MAX_ACCEPTABLE: {value: 2.0}, MAX_ACCEPTABLE: {value: 2.0},
PADDING: {value: 1.5} PADDING: {value: 1.5},
FORMAT: {value: "codabar", writeable: false}
}; };
CodabarReader.prototype = Object.create(BarcodeReader.prototype, properties); CodabarReader.prototype = Object.create(BarcodeReader.prototype, properties);

@ -132,7 +132,8 @@ define(
[2, 3, 3, 1, 1, 1, 2] [2, 3, 3, 1, 1, 1, 2]
]}, ]},
SINGLE_CODE_ERROR: {value: 1}, SINGLE_CODE_ERROR: {value: 1},
AVG_CODE_ERROR: {value: 0.5} AVG_CODE_ERROR: {value: 0.5},
FORMAT: {value: "code_128", writeable: false}
}; };
Code128Reader.prototype = Object.create(BarcodeReader.prototype, properties); Code128Reader.prototype = Object.create(BarcodeReader.prototype, properties);
@ -161,7 +162,8 @@ define(
} else { } else {
if (counterPos === counter.length - 1) { if (counterPos === counter.length - 1) {
normalized = self._normalize(counter); normalized = self._normalize(counter);
for ( code = 0; code < self.CODE_PATTERN.length; code++) { if (normalized) {
for (code = 0; code < self.CODE_PATTERN.length; 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;
@ -170,58 +172,7 @@ define(
} }
bestMatch.end = i; bestMatch.end = i;
return bestMatch; return bestMatch;
} else {
counterPos++;
}
counter[counterPos] = 1;
isWhite = !isWhite;
}
}
return null;
};
Code128Reader.prototype._findEnd = function() {
var counter = [0, 0, 0, 0, 0, 0, 0],
i,
self = this,
offset = self._nextSet(self._row),
isWhite = !self._row[offset],
counterPos = 0,
bestMatch = {
error : Number.MAX_VALUE,
code : -1,
start : 0,
end : 0
},
error,
j,
sum,
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, 13);
error = self._matchPattern(normalized, self.CODE_PATTERN[self.STOP_CODE]);
if (error < self.AVG_CODE_ERROR) {
bestMatch.error = error;
bestMatch.start = i - sum;
bestMatch.end = i;
return bestMatch;
}
for ( j = 0; j < 5; j++) {
counter[j] = counter[j + 2];
}
counter[5] = 0;
counter[6] = 0;
counterPos--;
} else { } else {
counterPos++; counterPos++;
} }
@ -261,7 +212,8 @@ define(
sum += counter[j]; sum += counter[j];
} }
normalized = self._normalize(counter); normalized = self._normalize(counter);
for ( code = self.START_CODE_A; code <= self.START_CODE_C; code++) { if (normalized) {
for (code = self.START_CODE_A; code <= self.START_CODE_C; 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;
@ -273,6 +225,7 @@ define(
bestMatch.end = i; bestMatch.end = i;
return bestMatch; return bestMatch;
} }
}
for ( j = 0; j < 4; j++) { for ( j = 0; j < 4; j++) {
counter[j] = counter[j + 2]; counter[j] = counter[j + 2];
@ -420,7 +373,7 @@ define(
// find end bar // find end bar
code.end = self._nextUnset(self._row, code.end); code.end = self._nextUnset(self._row, code.end);
if (code.end === self._row.length) { if(!self._verifyTrailingWhitespace(code)){
return null; return null;
} }
@ -431,9 +384,15 @@ define(
return null; return null;
} }
if (!result.length) {
return null;
}
// remove last code from result (checksum) // remove last code from result (checksum)
result.splice(result.length - 1, 1); result.splice(result.length - 1, 1);
return { return {
code : result.join(""), code : result.join(""),
start : startInfo.start, start : startInfo.start,
@ -445,6 +404,20 @@ define(
}; };
}; };
BarcodeReader.prototype._verifyTrailingWhitespace = function(endInfo) {
var self = this,
trailingWhitespaceEnd;
trailingWhitespaceEnd = endInfo.end + ((endInfo.end - endInfo.start) / 2);
if (trailingWhitespaceEnd < self._row.length) {
if (self._matchRange(endInfo.end, trailingWhitespaceEnd, 0)) {
return endInfo;
}
}
return null;
};
return (Code128Reader); return (Code128Reader);
} }
); );

@ -17,7 +17,8 @@ define(
ALPHABETH_STRING: {value: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *$/+%"}, 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]}, 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]}, 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]},
ASTERISK: {value: 0x094} ASTERISK: {value: 0x094},
FORMAT: {value: "code_39", writeable: false}
}; };
Code39Reader.prototype = Object.create(BarcodeReader.prototype, properties); Code39Reader.prototype = Object.create(BarcodeReader.prototype, properties);

@ -18,7 +18,8 @@ define(function(){
right: "0%", right: "0%",
left: "0%", left: "0%",
bottom: "0%" bottom: "0%"
} },
singleChannel: false // true: only the red color-channel is read
}, },
tracking: false, tracking: false,
debug: false, debug: false,

@ -486,14 +486,20 @@ define(['cluster', "array_helper", "gl-matrix"], function(Cluster2, ArrayHelper,
}; };
CVUtils.computeGray = function(imageData, outArray) { CVUtils.computeGray = function(imageData, outArray, config) {
var l = imageData.length / 4; var l = (imageData.length / 4) | 0,
var i = 0; i,
for ( i = 0; i < l; i++) { singleChannel = config && config.singleChannel === true;
//outArray[i] = (0.299*imageData[i*4+0] + 0.587*imageData[i*4+1] + 0.114*imageData[i*4+2]);
if (singleChannel) {
for (i = 0; i < l; i++) {
outArray[i] = imageData[i * 4 + 0];
}
} else {
for (i = 0; i < l; i++) {
outArray[i] = Math.floor(0.299 * imageData[i * 4 + 0] + 0.587 * imageData[i * 4 + 1] + 0.114 * imageData[i * 4 + 2]); outArray[i] = Math.floor(0.299 * imageData[i * 4 + 0] + 0.587 * imageData[i * 4 + 1] + 0.114 * imageData[i * 4 + 2]);
} }
}
}; };
CVUtils.loadImageArray = function(src, callback, canvas) { CVUtils.loadImageArray = function(src, callback, canvas) {

@ -12,7 +12,11 @@ define(
EANReader.call(this); EANReader.call(this);
} }
EAN8Reader.prototype = Object.create(EANReader.prototype); var properties = {
FORMAT: {value: "ean_8", writeable: false}
};
EAN8Reader.prototype = Object.create(EANReader.prototype, properties);
EAN8Reader.prototype.constructor = EAN8Reader; EAN8Reader.prototype.constructor = EAN8Reader;
EAN8Reader.prototype._decodePayload = function(code, result, decodedCodes) { EAN8Reader.prototype._decodePayload = function(code, result, decodedCodes) {
@ -28,7 +32,7 @@ define(
decodedCodes.push(code); decodedCodes.push(code);
} }
code = self._findPattern(self.MIDDLE_PATTERN, code.end, true); code = self._findPattern(self.MIDDLE_PATTERN, code.end, true, false);
if (code === null) { if (code === null) {
return null; return null;
} }

@ -42,8 +42,9 @@ define(
[2, 1, 1, 3] [2, 1, 1, 3]
]}, ]},
CODE_FREQUENCY : {value: [0, 11, 13, 14, 19, 25, 28, 21, 22, 26]}, CODE_FREQUENCY : {value: [0, 11, 13, 14, 19, 25, 28, 21, 22, 26]},
SINGLE_CODE_ERROR: {value: 0.7}, SINGLE_CODE_ERROR: {value: 0.67},
AVG_CODE_ERROR: {value: 0.3} AVG_CODE_ERROR: {value: 0.27},
FORMAT: {value: "ean_13", writeable: false}
}; };
EANReader.prototype = Object.create(BarcodeReader.prototype, properties); EANReader.prototype = Object.create(BarcodeReader.prototype, properties);
@ -76,7 +77,8 @@ define(
} else { } else {
if (counterPos === counter.length - 1) { if (counterPos === counter.length - 1) {
normalized = self._normalize(counter); normalized = self._normalize(counter);
for ( code = 0; code < coderange; code++) { if (normalized) {
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;
@ -88,6 +90,7 @@ define(
return null; return null;
} }
return bestMatch; return bestMatch;
}
} else { } else {
counterPos++; counterPos++;
} }
@ -144,6 +147,7 @@ define(
sum += counter[j]; sum += counter[j];
} }
normalized = self._normalize(counter); normalized = self._normalize(counter);
if (normalized) {
error = self._matchPattern(normalized, pattern); error = self._matchPattern(normalized, pattern);
if (error < epsilon) { if (error < epsilon) {
@ -152,6 +156,7 @@ define(
bestMatch.end = i; bestMatch.end = i;
return bestMatch; return bestMatch;
} }
}
if (tryHarder) { if (tryHarder) {
for ( j = 0; j < counter.length - 2; j++) { for ( j = 0; j < counter.length - 2; j++) {
counter[j] = counter[j + 2]; counter[j] = counter[j + 2];

@ -59,7 +59,7 @@ define(["cv_utils"], function(CVUtils) {
if(doHalfSample){ if(doHalfSample){
CVUtils.grayAndHalfSampleFromCanvasData(ctxData, _size, _data); CVUtils.grayAndHalfSampleFromCanvasData(ctxData, _size, _data);
} else { } else {
CVUtils.computeGray(ctxData, _data); CVUtils.computeGray(ctxData, _data, _streamConfig);
} }
return true; return true;
} else { } else {

@ -23,6 +23,26 @@ define(function() {
} }
ctx.closePath(); ctx.closePath();
ctx.stroke(); ctx.stroke();
},
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) {
return false;
}
while(imageDataPos--){
value = imageData[imageDataPos];
data[--canvasDataPos] = 255;
data[--canvasDataPos] = value;
data[--canvasDataPos] = value;
data[--canvasDataPos] = value;
}
ctx.putImageData(canvasData, 0, 0);
return true;
} }
}; };

@ -1,7 +1,5 @@
/* jshint undef: true, unused: true, browser:true, devel: true, evil: true */ /* jshint undef: true, unused: true, browser:true, devel: true, evil: true */
/* global define */ /* global define */
define([ define([
"code_128_reader", "code_128_reader",
"ean_reader", "ean_reader",
@ -15,7 +13,8 @@ define([
"events", "events",
"camera_access", "camera_access",
"image_debug", "image_debug",
"gl-matrix"], "gl-matrix",
"result_collector"],
function(Code128Reader, function(Code128Reader,
EANReader, EANReader,
InputStream, InputStream,
@ -28,7 +27,8 @@ function(Code128Reader,
Events, Events,
CameraAccess, CameraAccess,
ImageDebug, ImageDebug,
glMatrix) { glMatrix,
ResultCollector) {
"use strict"; "use strict";
var _inputStream, var _inputStream,
@ -49,7 +49,8 @@ function(Code128Reader,
_decoder, _decoder,
_workerPool = [], _workerPool = [],
_onUIThread = true, _onUIThread = true,
vec2 = glMatrix.vec2; vec2 = glMatrix.vec2,
_resultCollector;
function initializeData(imageWrapper) { function initializeData(imageWrapper) {
initBuffers(imageWrapper); initBuffers(imageWrapper);
@ -99,7 +100,7 @@ function(Code128Reader,
if (!err) { if (!err) {
_inputStream.trigger("canrecord"); _inputStream.trigger("canrecord");
} else { } else {
console.log(err); return cb(err);
} }
}); });
} }
@ -111,9 +112,7 @@ function(Code128Reader,
} }
function canRecord(cb) { function canRecord(cb) {
if (_config.locate) {
BarcodeLocator.checkImageConstraints(_inputStream, _config.locator); BarcodeLocator.checkImageConstraints(_inputStream, _config.locator);
}
initCanvas(); initCanvas();
_framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image); _framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image);
initConfig(); initConfig();
@ -180,10 +179,10 @@ function(Code128Reader,
console.log(_inputImageWrapper.size); console.log(_inputImageWrapper.size);
_boxSize = [ _boxSize = [
vec2.clone([20, _inputImageWrapper.size.y / 2 - 100]), vec2.clone([0, 0]),
vec2.clone([20, _inputImageWrapper.size.y / 2 + 100]), vec2.clone([0, _inputImageWrapper.size.y]),
vec2.clone([_inputImageWrapper.size.x - 20, _inputImageWrapper.size.y / 2 + 100]), vec2.clone([_inputImageWrapper.size.x, _inputImageWrapper.size.y]),
vec2.clone([_inputImageWrapper.size.x - 20, _inputImageWrapper.size.y / 2 - 100]) vec2.clone([_inputImageWrapper.size.x, 0])
]; ];
BarcodeLocator.init(_inputImageWrapper, _config.locator); BarcodeLocator.init(_inputImageWrapper, _config.locator);
} }
@ -192,7 +191,11 @@ function(Code128Reader,
if (_config.locate) { if (_config.locate) {
return BarcodeLocator.locate(); return BarcodeLocator.locate();
} else { } else {
return [_boxSize]; return [[
vec2.clone(_boxSize[0]),
vec2.clone(_boxSize[1]),
vec2.clone(_boxSize[2]),
vec2.clone(_boxSize[3])]];
} }
} }
@ -233,10 +236,16 @@ function(Code128Reader,
} }
} }
function publishResult(result) { function publishResult(result, imageData) {
if (_onUIThread) { if (_onUIThread) {
transformResult(result); transformResult(result);
if (imageData && result && result.codeResult) {
if (_resultCollector) {
_resultCollector.addResult(imageData, _inputStream.getCanvasSize(), result.codeResult);
}
} }
}
Events.publish("processed", result); Events.publish("processed", result);
if (result && result.codeResult) { if (result && result.codeResult) {
Events.publish("detected", result); Events.publish("detected", result);
@ -252,7 +261,7 @@ function(Code128Reader,
result = _decoder.decodeFromBoundingBoxes(boxes); result = _decoder.decodeFromBoundingBoxes(boxes);
result = result || {}; result = result || {};
result.boxes = boxes; result.boxes = boxes;
publishResult(result); publishResult(result, _inputImageWrapper.data);
} else { } else {
publishResult(); publishResult();
} }
@ -321,7 +330,7 @@ function(Code128Reader,
function initWorker(cb) { function initWorker(cb) {
var blobURL, var blobURL,
workerThread = { workerThread = {
worker: null, worker: undefined,
imageData: new Uint8Array(_inputStream.getWidth() * _inputStream.getHeight()), imageData: new Uint8Array(_inputStream.getWidth() * _inputStream.getHeight()),
busy: true busy: true
}; };
@ -339,7 +348,7 @@ function(Code128Reader,
} else if (e.data.event === 'processed') { } else if (e.data.event === 'processed') {
workerThread.imageData = new Uint8Array(e.data.imageData); workerThread.imageData = new Uint8Array(e.data.imageData);
workerThread.busy = false; workerThread.busy = false;
publishResult(e.data.result); publishResult(e.data.result, workerThread.imageData);
} }
}; };
@ -454,6 +463,11 @@ function(Code128Reader,
setReaders: function(readers) { setReaders: function(readers) {
setReaders(readers); setReaders(readers);
}, },
registerResultCollector: function(resultCollector) {
if (resultCollector && typeof resultCollector.addResult === 'function') {
_resultCollector = resultCollector;
}
},
canvas : _canvasContainer, canvas : _canvasContainer,
decodeSingle : function(config, resultCallback) { decodeSingle : function(config, resultCallback) {
config = HtmlUtils.mergeObjects({ config = HtmlUtils.mergeObjects({
@ -481,6 +495,7 @@ function(Code128Reader,
Code128Reader : Code128Reader Code128Reader : Code128Reader
}, },
ImageWrapper: ImageWrapper, ImageWrapper: ImageWrapper,
ImageDebug: ImageDebug ImageDebug: ImageDebug,
ResultCollector: ResultCollector
}; };
}); });

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

@ -16,7 +16,8 @@ define(
CODE_FREQUENCY : {value: [ CODE_FREQUENCY : {value: [
[ 56, 52, 50, 49, 44, 38, 35, 42, 41, 37 ], [ 56, 52, 50, 49, 44, 38, 35, 42, 41, 37 ],
[7, 11, 13, 14, 19, 25, 28, 21, 22, 26]]}, [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]} 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 = Object.create(EANReader.prototype, properties);
@ -35,13 +36,13 @@ define(
if (code.code >= self.CODE_G_START) { if (code.code >= self.CODE_G_START) {
code.code = code.code - self.CODE_G_START; code.code = code.code - self.CODE_G_START;
codeFrequency |= 1 << (5 - i); codeFrequency |= 1 << (5 - i);
} else {
codeFrequency |= 0 << (5 - i);
} }
result.push(code.code); result.push(code.code);
decodedCodes.push(code); decodedCodes.push(code);
} }
self._determineParity(codeFrequency, result); if (!self._determineParity(codeFrequency, result)) {
return null;
}
return code; return code;
}; };
@ -56,10 +57,11 @@ define(
if (codeFrequency === self.CODE_FREQUENCY[nrSystem][i]) { if (codeFrequency === self.CODE_FREQUENCY[nrSystem][i]) {
result.unshift(nrSystem); result.unshift(nrSystem);
result.push(i); result.push(i);
return; return true;
} }
} }
} }
return false;
}; };
UPCEReader.prototype._convertToUPCA = function(result) { UPCEReader.prototype._convertToUPCA = function(result) {

@ -12,7 +12,11 @@ define(
EANReader.call(this); EANReader.call(this);
} }
UPCReader.prototype = Object.create(EANReader.prototype); var properties = {
FORMAT: {value: "upc_a", writeable: false}
};
UPCReader.prototype = Object.create(EANReader.prototype, properties);
UPCReader.prototype.constructor = UPCReader; UPCReader.prototype.constructor = UPCReader;
UPCReader.prototype._decode = function() { UPCReader.prototype._decode = function() {

@ -46,7 +46,8 @@ require.config({
'upc_e_reader': 'src/upc_e_reader', 'upc_e_reader': 'src/upc_e_reader',
'upc_reader': 'src/upc_reader', 'upc_reader': 'src/upc_reader',
'async': 'node_modules/async/lib/async', 'async': 'node_modules/async/lib/async',
'gl-matrix': 'node_modules/gl-matrix/dist/gl-matrix' 'gl-matrix': 'node_modules/gl-matrix/dist/gl-matrix',
'result_collector': 'src/result_collector'
}, },
deps: allTestFiles, deps: allTestFiles,
callback: window.__karma__.start callback: window.__karma__.start

Loading…
Cancel
Save