Merge pull request #12 from serratus/issue-1a

Issue 1a
pull/14/head
Christoph Oberhofer 11 years ago
commit 1efe8a2426

1
.gitignore vendored

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

@ -48,13 +48,18 @@ module.exports = function(grunt) {
"glMatrixAddon" : { "glMatrixAddon" : {
"deps" : ["glMatrix"], "deps" : ["glMatrix"],
"exports" : "glMatrixAddon" "exports" : "glMatrixAddon"
},
"async": {
"deps": [],
"exports": "async"
} }
}, },
"paths" : { "paths" : {
"typedefs" : "typedefs", "typedefs" : "typedefs",
"glMatrix" : "vendor/glMatrix", "glMatrix" : "vendor/glMatrix",
"glMatrixAddon" : "glMatrixAddon" "glMatrixAddon" : "glMatrixAddon",
"async": "vendor/async"
} }
} }
} }
@ -68,7 +73,7 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-requirejs'); grunt.loadNpmTasks('grunt-requirejs');
grunt.loadNpmTasks('grunt-karma'); grunt.loadNpmTasks('grunt-karma');
// Default task(s). // Default task(s).
grunt.registerTask('default', ['jshint', 'requirejs', 'uglify']); grunt.registerTask('default', ['jshint', 'requirejs']);
grunt.registerTask('test', ['karma']); grunt.registerTask('test', ['karma']);
}; };

@ -1,6 +1,8 @@
quaggaJS quaggaJS
======== ========
- [Changelog](#changelog) (2015-01-21)
QuaggaJS is a barcode-scanner entirely written in JavaScript supporting real-time localization and decoding of QuaggaJS is a barcode-scanner entirely written in JavaScript supporting real-time localization and decoding of
various types of barcodes such as __EAN__ and __CODE128__. The library is also capable of using `getUserMedia` to get direct various types of barcodes such as __EAN__ and __CODE128__. The library is also capable of using `getUserMedia` to get direct
access to the user's camera stream. Although the code relies on heavy image-processing even recent smartphones are access to the user's camera stream. Although the code relies on heavy image-processing even recent smartphones are
@ -46,9 +48,9 @@ by simply typing:
You can check out the [examples](http://serratus.github.io/quaggaJS/examples) to get an idea of how to use QuaggaJS. You can check out the [examples](http://serratus.github.io/quaggaJS/examples) to get an idea of how to use QuaggaJS.
Basically the library exposes the following API: Basically the library exposes the following API:
### Quagga.init(config) ### Quagga.init(config, callback)
This method initializes the library for a given configuration (see below) which also includes a callback function (`readyFunc`) which is called when Quagga is ready to start. The initialization process also requests for camera access if real-time detection is configured. This method initializes the library for a given configuration `config` (see below) and invokes the `callback` when Quagga is ready to start. The initialization process also requests for camera access if real-time detection is configured.
```javascript ```javascript
Quagga.init({ Quagga.init({
@ -58,12 +60,11 @@ Quagga.init({
}, },
decoder : { decoder : {
readers : ["code_128_reader"] readers : ["code_128_reader"]
}, }
readyFunc : function() { }, function() {
console.log("Initialization finished. Ready to start"); console.log("Initialization finished. Ready to start");
Quagga.start(); Quagga.start();
} });
});
``` ```
### Quagga.start() ### Quagga.start()
@ -73,18 +74,91 @@ the images.
### Quagga.stop() ### Quagga.stop()
If the decoder is currently running, after calling `stop()` the decoder does not process any more images. If the decoder is currently running, after calling `stop()` the decoder does not process any more images. Additionally, if a camera-stream was requested upon initialization, this operation also disconnects the camera.
### Quagga.onProcessed(callback)
This method registers a `callback(data)` function that is called for each frame after the processing is done. The `data` object contains detailed information about the success/failure of the operation. The output varies, depending whether the detection and/or decoding were successful or not.
### Quagga.onDetected(callback) ### Quagga.onDetected(callback)
Registers a callback function which is triggered whenever a barcode-pattern has been located and decoded Registers a `callback(data)` function which is triggered whenever a barcode-pattern has been located and decoded
successfully. The callback function is called with the decoded data as the first parameter. successfully. The passed `data` object contains information about the decoding process including the detected code which can be obtained by calling `data.codeResult.code`.
### Quagga.decodeSingle(config, callback) ### Quagga.decodeSingle(config, callback)
In contrast to the calls described above, this method does not rely on `getUserMedia` and operates on a In contrast to the calls described above, this method does not rely on `getUserMedia` and operates on a
single image instead. The provided callback is the same as in `onDetected` and contains the decoded data single image instead. The provided callback is the same as in `onDetected` and contains the result `data`
as first parameter. object.
## <a name="resultobject">The result object</a>
The callbacks passed into `onProcessed`, `onDetected` and `decodeSingle` receive a `data` object upon execution. The `data` object contains the following information. Depending on the success, some fields may be `undefined` or just empty.
```javascript
{
"codeResult": {
"code": "FANAVF1461710",
"start": 355,
"end": 26,
"codeset": 100,
"startInfo": {
"error": 1.0000000000000002,
"code": 104,
"start": 21,
"end": 41
},
"decodedCodes": [{
"code": 104,
"start": 21,
"end": 41
},
// stripped for brevity
{
"error": 0.8888888888888893,
"code": 106,
"start": 328,
"end": 350
}],
"endInfo": {
"error": 0.8888888888888893,
"code": 106,
"start": 328,
"end": 350
},
"direction": -1
},
"line": [{
"x": 25.97278706156836,
"y": 360.5616435369468
}, {
"x": 401.9220519377024,
"y": 70.87524989906444
}],
"angle": -0.6565217179979483,
"pattern": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, /* ... */ 1],
"box": [
[77.4074243622672, 410.9288668804402],
[0.050203235235130705, 310.53619724086366],
[360.15706727788256, 33.05711026051813],
[437.5142884049146, 133.44977990009465]
],
"boxes": [
[
[77.4074243622672, 410.9288668804402],
[0.050203235235130705, 310.53619724086366],
[360.15706727788256, 33.05711026051813],
[437.5142884049146, 133.44977990009465]
],
[
[248.90769330706507, 415.2041489551161],
[198.9532321622869, 352.62160512937635],
[339.546160777576, 240.3979259789976],
[389.5006219223542, 302.98046980473737]
]
]
}
```
## Config ## Config
@ -99,11 +173,13 @@ The default `config` object is set as followed:
debug: false, debug: false,
controls: false, controls: false,
locate: true, locate: true,
numOfWorkers: 4,
scriptName: 'quagga.js',
visual: { visual: {
show: true show: true
}, },
decoder:{ decoder:{
drawBoundingBox: true, drawBoundingBox: false,
showFrequency: false, showFrequency: false,
drawScanline: true, drawScanline: true,
showPattern: false, showPattern: false,
@ -112,6 +188,7 @@ The default `config` object is set as followed:
] ]
}, },
locator: { locator: {
halfSample: true,
showCanvas: false, showCanvas: false,
showPatches: false, showPatches: false,
showFoundPatches: false, showFoundPatches: false,
@ -151,7 +228,24 @@ Unit Tests can be run with [Karma][karmaUrl] and written using [Mocha][mochaUrl]
> npm install > npm install
> grunt test > grunt test
``` ```
## Image Debugging
In case you want to take a deeper dive into the inner workings of Quagga, get to know the _debugging_ capabilities of the current implementation. The various flags exposed through the `config` object give you the abilily to visualize almost every step in the processing. Because of the introduction of the 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 work.
## <a name="changelog">Changelog</a>
### 2015-01-21
- Features
- Added support for web-worker (using 4 workers as default, can be changed through `config.numOfWorkers`)
- Due to the way how web-workers are created, the name of the script file (`config.scriptName`) should be kept in sync with your actual filename
- Removed canvas-overlay for decoding (boxes & scanline) which can now be easily implemented using the existing API (see example)
- API Changes
In the course of implementing web-workers some breaking changes were introduced to the API.
- The `Quagga.init` function no longer receives the callback as part of the config but rather as a second argument: `Quagga.init(config, cb)`
- The callback to `Quagga.onDetected` now receives an object containing much more information in addition to the decoded code. (see [data](#resultobject))
- Added `Quagga.onProcessed(callback)` which provides a way to get information for each image processed.
The callback receives the same `data` object as `Quagga.onDetected` does. Depending on the success of the process the `data` object
might not contain any `resultCode` and/or `box` properties.
[zxing_github]: https://github.com/zxing/zxing [zxing_github]: https://github.com/zxing/zxing
[teaser_left]: https://github.com/serratus/quaggaJS/blob/master/doc/img/mobile-located.png [teaser_left]: https://github.com/serratus/quaggaJS/blob/master/doc/img/mobile-located.png

1449
dist/quagga.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -27,6 +27,7 @@
It works best if your camera has built-in auto-focus. It works best if your camera has built-in auto-focus.
</p> </p>
<div class="controls"> <div class="controls">
<button class="stop">Stop</button>
<fieldset class="reader-group"> <fieldset class="reader-group">
<label>Code128</label> <label>Code128</label>
<input type="radio" name="reader" value="code_128" checked /> <input type="radio" name="reader" value="code_128" checked />

@ -8,11 +8,10 @@ $(function() {
}, },
decoder : { decoder : {
readers : ["code_128_reader"] readers : ["code_128_reader"]
}, }
readyFunc : function() { }, function() {
App.attachListeners(); App.attachListeners();
Quagga.start(); Quagga.start();
}
}); });
}, },
attachListeners : function() { attachListeners : function() {
@ -20,23 +19,55 @@ $(function() {
e.preventDefault(); e.preventDefault();
Quagga.setReaders([e.target.value + "_reader"]); Quagga.setReaders([e.target.value + "_reader"]);
}); });
$(".controls").on("click", "button.stop", function(e) {
e.preventDefault();
Quagga.stop();
});
}, },
detachListeners : function() { detachListeners : function() {
$(".controls .reader-group").off("change", "input"); $(".controls .reader-group").off("change", "input");
$(".controls").off("click", "button.stop");
}, },
lastResult : null lastResult : null
}; };
App.init(); 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) { Quagga.onDetected(function(result) {
if (App.lastResult !== result) { var code = result.codeResult.code;
App.lastResult = result;
if (App.lastResult !== code) {
App.lastResult = code;
var $node = null, canvas = Quagga.canvas.dom.image; var $node = null, canvas = Quagga.canvas.dom.image;
$node = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>'); $node = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>');
$node.find("img").attr("src", canvas.toDataURL()); $node.find("img").attr("src", canvas.toDataURL());
$node.find("h4.code").html(result); $node.find("h4.code").html(code);
$("#result_strip ul.thumbnails").prepend($node); $("#result_strip ul.thumbnails").prepend($node);
} }
}); });

@ -41,6 +41,7 @@
<ul class="thumbnails"></ul> <ul class="thumbnails"></ul>
</div> </div>
<div id="interactive" class="viewport"></div> <div id="interactive" class="viewport"></div>
<div id="debug"></div>
</section> </section>
<footer> <footer>
<p> <p>

@ -4,16 +4,15 @@ $(function() {
Quagga.init({ Quagga.init({
inputStream: { name: "Test", inputStream: { name: "Test",
type: "ImageStream", type: "ImageStream",
src: "/test/fixtures/" + App.config.reader + "/", src: "../test/fixtures/" + App.config.reader + "/",
length: App.config.length length: App.config.length
}, },
decoder : { decoder : {
readers : [App.config.reader + "_reader"] readers : [App.config.reader + "_reader"]
}, }
readyFunc : function() { }, function() {
App.attachListeners(); App.attachListeners();
Quagga.start(); Quagga.start();
}
}); });
}, },
config: { config: {
@ -42,12 +41,38 @@ $(function() {
App.init(); 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) { Quagga.onDetected(function(result) {
var $node = null, canvas = Quagga.canvas.dom.image; var $node,
canvas = Quagga.canvas.dom.image,
detectedCode = result.codeResult.code;
$node = $('<li><div class="thumbnail"><div class="imgWrapper"><img /></div><div class="caption"><h4 class="code"></h4></div></div></li>'); $node = $('<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(result); $node.find("h4.code").html(detectedCode);
$("#result_strip ul.thumbnails").prepend($node); $("#result_strip ul.thumbnails").prepend($node);
}); });
}); });

@ -30,6 +30,7 @@ define(["bresenham", "image_debug", 'code_128_reader', 'ean_reader'], function(B
initConfig(); initConfig();
function initCanvas() { function initCanvas() {
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) {
@ -56,6 +57,7 @@ define(["bresenham", "image_debug", 'code_128_reader', 'ean_reader'], function(B
_canvas.ctx.overlay = _canvas.dom.overlay.getContext("2d"); _canvas.ctx.overlay = _canvas.dom.overlay.getContext("2d");
} }
} }
}
function initReaders() { function initReaders() {
var i; var i;
@ -66,6 +68,7 @@ define(["bresenham", "image_debug", 'code_128_reader', 'ean_reader'], function(B
} }
function initConfig() { function initConfig() {
if (typeof document !== 'undefined') {
var i, var i,
vis = [{ vis = [{
node : _canvas.dom.frequency, node : _canvas.dom.frequency,
@ -83,6 +86,7 @@ define(["bresenham", "image_debug", 'code_128_reader', 'ean_reader'], function(B
} }
} }
} }
}
/** /**
* extend the line on both ends * extend the line on both ends
@ -235,6 +239,7 @@ define(["bresenham", "image_debug", 'code_128_reader', 'ean_reader'], function(B
for ( i = 0; i < boxes.length; i++) { for ( i = 0; i < boxes.length; i++) {
result = decodeFromBoundingBox(boxes[i]); result = decodeFromBoundingBox(boxes[i]);
if (result && result.codeResult) { if (result && result.codeResult) {
result.box = boxes[i];
return result; return result;
} }
} }

@ -14,7 +14,6 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
_patchLabelGrid, _patchLabelGrid,
_imageToPatchGrid, _imageToPatchGrid,
_binaryImageWrapper, _binaryImageWrapper,
_halfSample = true,
_patchSize, _patchSize,
_canvasContainer = { _canvasContainer = {
ctx : { ctx : {
@ -26,12 +25,13 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
}, },
_numPatches = {x: 0, y: 0}, _numPatches = {x: 0, y: 0},
_inputImageWrapper, _inputImageWrapper,
_skeletonizer; _skeletonizer,
self = this;
function initBuffers() { function initBuffers() {
var skeletonImageData; var skeletonImageData;
if (_halfSample) { if (_config.halfSample) {
_currentImageWrapper = new ImageWrapper({ _currentImageWrapper = new ImageWrapper({
x : _inputImageWrapper.size.x / 2 | 0, x : _inputImageWrapper.size.x / 2 | 0,
y : _inputImageWrapper.size.y / 2 | 0 y : _inputImageWrapper.size.y / 2 | 0
@ -41,8 +41,8 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
} }
_patchSize = { _patchSize = {
x : 16 * ( _halfSample ? 1 : 2), x : 16 * ( _config.halfSample ? 1 : 2),
y : 16 * ( _halfSample ? 1 : 2) y : 16 * ( _config.halfSample ? 1 : 2)
}; };
_numPatches.x = _currentImageWrapper.size.x / _patchSize.x | 0; _numPatches.x = _currentImageWrapper.size.x / _patchSize.x | 0;
@ -52,10 +52,10 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
_labelImageWrapper = new ImageWrapper(_patchSize, undefined, Array, true); _labelImageWrapper = new ImageWrapper(_patchSize, undefined, Array, true);
skeletonImageData = new ArrayBuffer(_patchSize.x * _patchSize.y * 16); skeletonImageData = new ArrayBuffer(64*1024);
_subImageWrapper = new ImageWrapper(_patchSize, new Uint8Array(skeletonImageData, 0, _patchSize.x * _patchSize.y)); _subImageWrapper = new ImageWrapper(_patchSize, new Uint8Array(skeletonImageData, 0, _patchSize.x * _patchSize.y));
_skelImageWrapper = new ImageWrapper(_patchSize, new Uint8Array(skeletonImageData, _patchSize.x * _patchSize.y * 3, _patchSize.x * _patchSize.y), undefined, true); _skelImageWrapper = new ImageWrapper(_patchSize, new Uint8Array(skeletonImageData, _patchSize.x * _patchSize.y * 3, _patchSize.x * _patchSize.y), undefined, true);
_skeletonizer = skeletonizer(window, { _skeletonizer = skeletonizer(self, {
size : _patchSize.x size : _patchSize.x
}, skeletonImageData); }, skeletonImageData);
@ -68,6 +68,9 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
} }
function initCanvas() { function initCanvas() {
if (_config.useWorker || typeof document === 'undefined') {
return;
}
_canvasContainer.dom.binary = document.createElement("canvas"); _canvasContainer.dom.binary = document.createElement("canvas");
_canvasContainer.dom.binary.className = "binaryBuffer"; _canvasContainer.dom.binary.className = "binaryBuffer";
if (_config.showCanvas === true) { if (_config.showCanvas === true) {
@ -142,7 +145,7 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
ImageDebug.drawPath(box, {x: 0, y: 1}, _canvasContainer.ctx.binary, {color: '#ff0000', lineWidth: 2}); ImageDebug.drawPath(box, {x: 0, y: 1}, _canvasContainer.ctx.binary, {color: '#ff0000', lineWidth: 2});
} }
scale = _halfSample ? 2 : 1; scale = _config.halfSample ? 2 : 1;
// reverse rotation; // reverse rotation;
transMat = mat2.inverse(transMat); transMat = mat2.inverse(transMat);
for ( j = 0; j < 4; j++) { for ( j = 0; j < 4; j++) {
@ -472,9 +475,10 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
} }
return { return {
init : function(config, data) { init : function(inputImageWrapper, config) {
_config = config; _config = config;
_inputImageWrapper = data.inputImageWrapper; _inputImageWrapper = inputImageWrapper;
initBuffers(); initBuffers();
initCanvas(); initCanvas();
}, },
@ -483,7 +487,7 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
topLabels = [], topLabels = [],
boxes = []; boxes = [];
if (_halfSample) { if (_config.halfSample) {
CVUtils.halfSample(_inputImageWrapper, _currentImageWrapper); CVUtils.halfSample(_inputImageWrapper, _currentImageWrapper);
} }
@ -491,7 +495,7 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
patchesFound = findPatches(); patchesFound = findPatches();
// return unless 5% or more patches are found // return unless 5% or more patches are found
if (patchesFound.length < _numPatches.x * _numPatches.y * 0.05) { if (patchesFound.length < _numPatches.x * _numPatches.y * 0.05) {
return; return null;
} }
// rasterrize area by comparing angular similarity; // rasterrize area by comparing angular similarity;
@ -507,7 +511,6 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I
} }
boxes = findBoxes(topLabels, maxLabel); boxes = findBoxes(topLabels, maxLabel);
return boxes; return boxes;
} }
}; };

@ -11,19 +11,22 @@ define(function(){
debug: false, debug: false,
controls: false, controls: false,
locate: true, locate: true,
numOfWorkers: 4,
scriptName: 'quagga.js',
visual: { visual: {
show: true show: true
}, },
decoder:{ decoder:{
drawBoundingBox: true, drawBoundingBox: false,
showFrequency: false, showFrequency: false,
drawScanline: true, drawScanline: false,
showPattern: false, showPattern: false,
readers: [ readers: [
'code_128_reader' 'code_128_reader'
] ]
}, },
locator: { locator: {
halfSample: true,
showCanvas: false, showCanvas: false,
showPatches: false, showPatches: false,
showFoundPatches: false, showFoundPatches: false,

@ -23,10 +23,10 @@ define(function() {
function publishSubscription(subscription, data) { function publishSubscription(subscription, data) {
if (subscription.async) { if (subscription.async) {
setTimeout(function() { setTimeout(function() {
subscription.callback.call(null, data); subscription.callback(data);
}, 4); }, 4);
} else { } else {
subscription.callback.call(null, data); subscription.callback(data);
} }
} }

@ -1,8 +1,8 @@
/* jshint undef: true, unused: true, browser:true, devel: true */ /* jshint undef: true, unused: true, browser:true, devel: true */
/* global define, vec2 */ /* global define, vec2, importScripts */
define(["code_128_reader", "ean_reader", "input_stream", "image_wrapper", "barcode_locator", "barcode_decoder", "frame_grabber", "html_utils", "config", "events", "camera_access"], define(["code_128_reader", "ean_reader", "input_stream", "image_wrapper", "barcode_locator", "barcode_decoder", "frame_grabber", "html_utils", "config", "events", "camera_access", "async", "image_debug"],
function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, BarcodeDecoder, FrameGrabber, HtmlUtils, _config, Events, CameraAccess) { function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, BarcodeDecoder, FrameGrabber, HtmlUtils, _config, Events, CameraAccess, async, ImageDebug) {
"use strict"; "use strict";
var _inputStream, var _inputStream,
@ -21,11 +21,12 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
_inputImageWrapper, _inputImageWrapper,
_boxSize, _boxSize,
_decoder, _decoder,
_initialized = false; _workerPool = [],
_onUIThread = true;
function initialize(config) { function initializeData(imageWrapper) {
_config = HtmlUtils.mergeObjects(_config, config); initBuffers(imageWrapper);
initInputStream(); _decoder = BarcodeDecoder.create(_config.decoder, _inputImageWrapper);
} }
function initConfig() { function initConfig() {
@ -48,7 +49,7 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
} }
} }
function initInputStream() { function initInputStream(cb) {
var video; var video;
if (_config.inputStream.type == "VideoStream") { if (_config.inputStream.type == "VideoStream") {
video = document.createElement("video"); video = document.createElement("video");
@ -74,22 +75,28 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
_inputStream.setAttribute("preload", "auto"); _inputStream.setAttribute("preload", "auto");
_inputStream.setAttribute("autoplay", true); _inputStream.setAttribute("autoplay", true);
_inputStream.setInputStream(_config.inputStream); _inputStream.setInputStream(_config.inputStream);
_inputStream.addEventListener("canrecord", canRecord); _inputStream.addEventListener("canrecord", canRecord.bind(undefined, cb));
} }
function canRecord() { function canRecord(cb) {
initBuffers();
initCanvas(); initCanvas();
_decoder = BarcodeDecoder.create(_config.decoder, _inputImageWrapper);
_framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image); _framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image);
_framegrabber.attachData(_inputImageWrapper.data);
initConfig(); initConfig();
_inputStream.play();
_initialized = true; if (_config.numOfWorkers > 0) {
if (_config.readyFunc) { initWorkers(function() {
_config.readyFunc.apply(); console.log("Workers created");
ready(cb);
});
} else {
initializeData();
ready(cb);
}
} }
function ready(cb){
_inputStream.play();
cb();
} }
function initCanvas() { function initCanvas() {
@ -103,8 +110,8 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
} }
} }
_canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d"); _canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d");
_canvasContainer.dom.image.width = _inputImageWrapper.size.x; _canvasContainer.dom.image.width = _inputStream.getWidth();
_canvasContainer.dom.image.height = _inputImageWrapper.size.y; _canvasContainer.dom.image.height = _inputStream.getHeight();
_canvasContainer.dom.overlay = document.querySelector("canvas.drawingBuffer"); _canvasContainer.dom.overlay = document.querySelector("canvas.drawingBuffer");
if (!_canvasContainer.dom.overlay) { if (!_canvasContainer.dom.overlay) {
@ -120,73 +127,223 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
} }
} }
_canvasContainer.ctx.overlay = _canvasContainer.dom.overlay.getContext("2d"); _canvasContainer.ctx.overlay = _canvasContainer.dom.overlay.getContext("2d");
_canvasContainer.dom.overlay.width = _inputImageWrapper.size.x; _canvasContainer.dom.overlay.width = _inputStream.getWidth();
_canvasContainer.dom.overlay.height = _inputImageWrapper.size.y; _canvasContainer.dom.overlay.height = _inputStream.getHeight();
} }
function initBuffers() { function initBuffers(imageWrapper) {
if (imageWrapper) {
_inputImageWrapper = imageWrapper;
} else {
_inputImageWrapper = new ImageWrapper({ _inputImageWrapper = new ImageWrapper({
x : _inputStream.getWidth(), x : _inputStream.getWidth(),
y : _inputStream.getHeight() y : _inputStream.getHeight()
}); });
console.log(_inputStream.getWidth()); }
console.log(_inputStream.getHeight());
console.log(_inputImageWrapper.size);
_boxSize = [ _boxSize = [
vec2.create([20, _inputStream.getHeight() / 2 - 100]), vec2.create([20, _inputImageWrapper.size.y / 2 - 100]),
vec2.create([20, _inputStream.getHeight() / 2 + 100]), vec2.create([20, _inputImageWrapper.size.y / 2 + 100]),
vec2.create([_inputStream.getWidth() - 20, _inputStream.getHeight() / 2 + 100]), vec2.create([_inputImageWrapper.size.x - 20, _inputImageWrapper.size.y / 2 + 100]),
vec2.create([_inputStream.getWidth() - 20, _inputStream.getHeight() / 2 - 100]) vec2.create([_inputImageWrapper.size.x - 20, _inputImageWrapper.size.y / 2 - 100])
]; ];
BarcodeLocator.init(_config.locator, { BarcodeLocator.init(_inputImageWrapper, _config.locator);
inputImageWrapper : _inputImageWrapper
});
} }
function getBoundingBoxes() { function getBoundingBoxes() {
var boxes;
if (_config.locate) { if (_config.locate) {
boxes = BarcodeLocator.locate(); return BarcodeLocator.locate();
} else { } else {
boxes = [_boxSize]; return [_boxSize];
} }
return boxes;
} }
function update() { function locateAndDecode() {
var result, var result,
boxes; boxes;
if (_framegrabber.grab()) {
_canvasContainer.ctx.overlay.clearRect(0, 0, _inputImageWrapper.size.x, _inputImageWrapper.size.y);
boxes = getBoundingBoxes(); boxes = getBoundingBoxes();
if (boxes) { if (boxes) {
result = _decoder.decodeFromBoundingBoxes(boxes); result = _decoder.decodeFromBoundingBoxes(boxes);
result = result || {};
result.boxes = boxes;
Events.publish("processed", result);
if (result && result.codeResult) { if (result && result.codeResult) {
Events.publish("detected", result.codeResult.code); Events.publish("detected", result);
}
} else {
Events.publish("processed");
}
}
function update() {
var availableWorker;
if (_onUIThread) {
if (_workerPool.length > 0) {
availableWorker = _workerPool.filter(function(workerThread) {
return !workerThread.busy;
})[0];
if (availableWorker) {
_framegrabber.attachData(availableWorker.imageData);
} else {
return; // all workers are busy
}
} else {
_framegrabber.attachData(_inputImageWrapper.data);
} }
if (_framegrabber.grab()) {
if (availableWorker) {
availableWorker.busy = true;
availableWorker.worker.postMessage({
cmd: 'process',
imageData: availableWorker.imageData
}, [availableWorker.imageData.buffer]);
} else {
locateAndDecode();
} }
} }
} else {
locateAndDecode();
}
} }
function start() { function start() {
_stopped = false; _stopped = false;
( function frame() { ( function frame() {
if (!_stopped) { if (!_stopped) {
if (_config.inputStream.type == "LiveStream") { update();
if (_onUIThread && _config.inputStream.type == "LiveStream") {
window.requestAnimFrame(frame); window.requestAnimFrame(frame);
} }
update();
} }
}()); }());
} }
function initWorkers(cb) {
_workerPool = [];
async.times(_config.numOfWorkers, function(n, next) {
initWorker(function(workerThread) {
_workerPool.push(workerThread);
next(null);
});
}, cb);
}
function initWorker(cb) {
var blobURL,
workerThread = {
worker: null,
imageData: new Uint8Array(_inputStream.getWidth() * _inputStream.getHeight()),
busy: true
};
blobURL = generateWorkerBlob();
workerThread.worker = new Worker(blobURL);
URL.revokeObjectURL(blobURL);
workerThread.worker.onmessage = function(e) {
if (e.data.event === 'initialized') {
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;
Events.publish("processed", e.data.result);
if (e.data.result && e.data.result.codeResult) {
Events.publish("detected", e.data.result);
}
}
};
workerThread.worker.postMessage({
cmd: 'init',
size: {x: _inputStream.getWidth(), y: _inputStream.getHeight()},
imageData: workerThread.imageData,
config: _config
}, [workerThread.imageData.buffer]);
}
function workerInterface(scriptUrl) {
importScripts(scriptUrl);
/* jshint ignore:start */
var imageWrapper;
self.onmessage = function(e) {
if (e.data.cmd === 'init') {
var config = e.data.config;
config.numOfWorkers = 0;
imageWrapper = new Quagga.ImageWrapper({
x : e.data.size.x,
y : e.data.size.y
}, new Uint8Array(e.data.imageData));
Quagga.init(config, ready, imageWrapper);
Quagga.onProcessed(onProcessed);
} else if (e.data.cmd === 'process') {
imageWrapper.data = new Uint8Array(e.data.imageData);
Quagga.start();
} else if (e.data.cmd === 'setReaders') {
Quagga.setReaders(e.data.readers);
}
};
function onProcessed(result) {
self.postMessage({'event': 'processed', imageData: imageWrapper.data, result: result}, [imageWrapper.data.buffer]);
}
function ready() {
self.postMessage({'event': 'initialized', imageData: imageWrapper.data}, [imageWrapper.data.buffer]);
}
/* jshint ignore:end */
}
function generateWorkerBlob() {
var blob,
quaggaAbsoluteUrl,
scripts = document.getElementsByTagName('script'),
regex = new RegExp('\/' + _config.scriptName + '$');
quaggaAbsoluteUrl = Array.prototype.slice.apply(scripts).filter(function(script) {
return script.src && script.src.match(regex);
}).map(function(script) {
return script.src;
})[0];
blob = new Blob(['(' + workerInterface.toString() + ')("' + quaggaAbsoluteUrl + '");'],
{type : 'text/javascript'});
return window.URL.createObjectURL(blob);
}
function setReaders(readers) {
if (_decoder) {
_decoder.setReaders(readers);
} else if (_onUIThread && _workerPool.length > 0) {
_workerPool.forEach(function(workerThread) {
workerThread.worker.postMessage({cmd: 'setReaders', readers: readers});
});
}
}
return { return {
init : function(config) { init : function(config, cb, imageWrapper) {
initialize(config); _config = HtmlUtils.mergeObjects(_config, config);
if (imageWrapper) {
_onUIThread = false;
initializeData(imageWrapper);
return cb();
} else {
initInputStream(cb);
}
}, },
start : function() { start : function() {
console.log("Start!");
start(); start();
}, },
stop : function() { stop : function() {
@ -198,11 +355,11 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
onDetected : function(callback) { onDetected : function(callback) {
Events.subscribe("detected", callback); Events.subscribe("detected", callback);
}, },
isInitialized : function() { onProcessed: function(callback) {
return _initialized; Events.subscribe("processed", callback);
}, },
setReaders: function(readers) { setReaders: function(readers) {
_decoder.setReaders(readers); setReaders(readers);
}, },
canvas : _canvasContainer, canvas : _canvasContainer,
decodeSingle : function(config, resultCallback) { decodeSingle : function(config, resultCallback) {
@ -212,18 +369,20 @@ function(Code128Reader, EANReader, InputStream, ImageWrapper, BarcodeLocator, Ba
sequence : false, sequence : false,
size: 800 size: 800
}; };
config.readyFunc = function() { config.numOfWorkers = 1;
this.init(config, function() {
Events.once("detected", function(result) { Events.once("detected", function(result) {
_stopped = true; _stopped = true;
resultCallback.call(null, result); resultCallback.call(null, result);
}, true); }, true);
start(); start();
}; });
initialize(config);
}, },
Reader: { Reader: {
EANReader : EANReader, EANReader : EANReader,
Code128Reader : Code128Reader Code128Reader : Code128Reader
} },
ImageWrapper: ImageWrapper,
ImageDebug: ImageDebug
}; };
}); });

@ -4,18 +4,18 @@
*/ */
glMatrixArrayType = Float32Array; glMatrixArrayType = Float32Array;
if (typeof window !== 'undefined') {
window.requestAnimFrame = (function() { window.requestAnimFrame = (function () {
return window.requestAnimationFrame || return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame || window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame || window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame || window.oRequestAnimationFrame ||
window.msRequestAnimationFrame || window.msRequestAnimationFrame ||
function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) { function (/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
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;
}

1123
src/vendor/async.js vendored

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save